Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,13 @@ to your agent.

### Use as a Claude Code skill

Drop [`skills/watch-cli/`](skills/watch-cli/) into your
`~/.claude/skills/` folder and the agent will pick up `/watch <url>`
as a first-class command, including the prompt library above.
Copy the portable [`SKILL.md`](SKILL.md) into a `watch-cli` skill directory
and the agent will pick up `/watch <url>` as a first-class command, including
the prompt library above.

```bash
mkdir -p ~/.claude/skills
cp -r skills/watch-cli ~/.claude/skills/
mkdir -p ~/.claude/skills/watch-cli
cp SKILL.md ~/.claude/skills/watch-cli/SKILL.md
```

---
Expand Down
5 changes: 5 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Reach for `watch` whenever the user gives you a video URL and wants you to do so

Supported platforms: YouTube, X / Twitter, LinkedIn, TikTok, Vimeo, Reddit, Facebook. Login-walled sources fall back to the user's signed-in browser cookies automatically.

If the user also provides [Xquik REST API](https://docs.xquik.com/api-reference/overview) or [MCP](https://docs.xquik.com/mcp/overview) output for an X post, treat it as read-only metadata context: post text, author, timestamp, source URL, media notes, and public metrics. It does not replace `watch`; still run `watch` when the user asks to inspect video frames, audio, or transcript, leave missing metadata unknown, and never place credentials in prompts or output.

## What you get back

The `watch` output gives you the raw materials to map to five concrete artifacts. Match the user's intent to one of them.
Expand Down Expand Up @@ -72,3 +74,6 @@ Default frame count is 8. For a fast-cut or dense UI demo, double it. For a mult
- Do not hard-fail on every non-zero exit. `exit 4` is recoverable partial success — frames populated, transcript `null`. Branch on `exit_code` before parsing.
- Do not surface API keys or environment variable names in chat. `KYMA_API_KEY` setup lives in the README.
- Do not embed the locked pitch into a longer marketing paragraph. The description line above is the source of truth; reuse it verbatim where the host shows skill metadata.

Xquik is an independent third-party service. Not affiliated with X Corp.
"Twitter" and "X" are trademarks of X Corp.
41 changes: 32 additions & 9 deletions bin/watch
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,23 @@ if [[ "$FORMAT" != "text" && "$FORMAT" != "json" ]]; then
exit 64
fi

# Dependency check up-front so we exit 2, not 1, when a binary is missing.
for dep in yt-dlp ffmpeg ffprobe jq curl python3; do
if ! command -v "$dep" >/dev/null 2>&1; then
echo "[watch] error: $dep not found on PATH tag=missing-dep:$dep" >&2
exit 2
fi
done
MISSING_DEP=""
check_dependencies() {
local dep
for dep in yt-dlp ffmpeg ffprobe jq curl python3; do
if ! command -v "$dep" >/dev/null 2>&1; then
MISSING_DEP="$dep"
echo "[watch] error: $dep not found on PATH tag=missing-dep:$dep" >&2
return 2
fi
done
}

if [[ $PIPE -eq 0 ]]; then
check_dependencies
DEPENDENCY_RC=$?
[[ $DEPENDENCY_RC -ne 0 ]] && exit "$DEPENDENCY_RC"
fi

# ── Per-URL pipeline ──────────────────────────────────────────────
# Runs the download → frames → transcribe sequence for one URL.
Expand Down Expand Up @@ -245,6 +255,8 @@ if [[ $PIPE -eq 1 ]]; then
# Track the worst-case exit code so the pipeline as a whole reports
# a non-zero exit when any URL failed. Successful runs return 0.
PIPE_WORST=0
PIPE_DEPENDENCIES_CHECKED=0
PIPE_DEPENDENCIES_RC=0
while IFS= read -r LINE || [[ -n "$LINE" ]]; do
# Skip blank lines / comments.
LINE="${LINE%$'\r'}"
Expand All @@ -260,8 +272,19 @@ if [[ $PIPE -eq 1 ]]; then
(( PIPE_WORST < 64 )) && PIPE_WORST=64
continue
fi
if ! process_url "$LINE" "$COUNT" "$FORMAT" "$COOKIES_ARG" 1; then
RC=$?
if [[ $PIPE_DEPENDENCIES_CHECKED -eq 0 ]]; then
check_dependencies
PIPE_DEPENDENCIES_RC=$?
PIPE_DEPENDENCIES_CHECKED=1
fi
if [[ $PIPE_DEPENDENCIES_RC -ne 0 ]]; then
_emit_pipe_error "$LINE" "$PIPE_DEPENDENCIES_RC" "missing-dep:$MISSING_DEP"
(( PIPE_WORST < PIPE_DEPENDENCIES_RC )) && PIPE_WORST=$PIPE_DEPENDENCIES_RC
continue
fi
process_url "$LINE" "$COUNT" "$FORMAT" "$COOKIES_ARG" 1
RC=$?
if [[ $RC -ne 0 ]]; then
(( PIPE_WORST < RC )) && PIPE_WORST=$RC
fi
done
Expand Down
20 changes: 19 additions & 1 deletion tests/test-output-schema.sh
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,25 @@ else
fail "watch --pipe with invalid URL expected 1-line JSON error + non-zero; got rc=$PIPE_BAD_RC lines=$PIPE_BAD_LINES out='$PIPE_BAD_OUT'"
fi

# 10. Forced local mode with no whisper-cli on PATH → exit 2, tag=missing-dep.
# 10. A valid pipe item with a missing dependency emits one JSON error and exits 2.
MISSING_DEP_PATH="$(mktemp -d)"
for dep in bash ffmpeg ffprobe jq curl python3 dirname readlink sed; do
dep_path="$(command -v "$dep")"
ln -s "$dep_path" "$MISSING_DEP_PATH/$dep"
done
PIPE_DEP_OUT="$(printf 'https://example.invalid/video\n' | PATH="$MISSING_DEP_PATH" "$WATCH" --pipe 2>/dev/null)"
PIPE_DEP_RC=$?
PIPE_DEP_LINES="$(printf '%s' "$PIPE_DEP_OUT" | grep -c .)"
rm -rf "$MISSING_DEP_PATH"
if [[ $PIPE_DEP_RC -eq 2 ]] \
&& [[ "$PIPE_DEP_LINES" == "1" ]] \
&& echo "$PIPE_DEP_OUT" | jq -e '.version == 1 and .exit_code == 2 and .error == "missing-dep:yt-dlp"' >/dev/null 2>&1; then
pass "watch --pipe with missing yt-dlp emits one v1 error object and exits 2"
else
fail "watch --pipe missing-dep expected 1-line JSON error + exit 2; got rc=$PIPE_DEP_RC lines=$PIPE_DEP_LINES out='$PIPE_DEP_OUT'"
fi

# 11. Forced local mode with no whisper-cli on PATH → exit 2, tag=missing-dep.
# Use a subshell with a PATH that excludes whisper-cli, set WATCH_AUDIO_MODE=local,
# and verify the contract from docs/offline-mode.md.
TR_BIN="$REPO_ROOT/bin/transcribe"
Expand Down