2 Commits

Author SHA1 Message Date
bradautomates 755c157466 Fix Windows compatibility: encoding, installer hints, interpreter name
- watch.py: drop the ⚠️ emoji from the long-video warning; cp1252
  consoles on Windows couldn't encode it and the script crashed.
- setup.py: add a Windows branch that prints winget / pip install
  commands, matching what the README already promised.
- SKILL.md: note that Windows users must invoke the scripts with
  `python`, not `python3` (which is the Microsoft Store stub).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:22:51 +10:00
bradautomates 26cfa5ca34 Add commands/watch.md so /watch is callable via Claude Code plugin
Claude Code plugins expose slash commands from commands/*.md; the root
SKILL.md alone isn't discovered. Thin shim delegates to the existing
skill contract. Strip commands/ from the claude.ai .skill bundle since
that surface uses SKILL.md directly. Bumped to 0.1.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:54:54 +10:00
7 changed files with 47 additions and 5 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "watch", "name": "watch",
"version": "0.1.0", "version": "0.1.2",
"description": "Let Claude watch a video. Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls captions or falls back to Whisper, and hands frames + transcript to Claude so it can answer questions about the video.", "description": "Let Claude watch a video. Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls captions or falls back to Whisper, and hands frames + transcript to Claude so it can answer questions about the video.",
"author": { "author": {
"name": "Bradley Bonanno" "name": "Bradley Bonanno"
+15
View File
@@ -2,6 +2,21 @@
All notable changes to `/watch` are documented here. All notable changes to `/watch` are documented here.
## [0.1.2] — 2026-04-24
### Fixed
- Windows console crash: removed the emoji from the long-video warning in `watch.py`; cp1252 consoles couldn't encode it.
- `setup.py` now prints `winget` / `pip` install commands on Windows instead of "unsupported platform" — matches what the README already promised.
### Changed
- `SKILL.md` notes that on Windows the scripts must be invoked with `python`, not `python3` (the latter is the Microsoft Store stub on Windows).
## [0.1.1] — 2026-04-24
### Fixed
- Added `commands/watch.md` shim so `/watch` is callable when installed as a Claude Code plugin. Without it, the plugin loaded but the skill wasn't exposed as a slash command.
- `scripts/build-skill.sh` now strips `commands/` from the claude.ai `.skill` bundle alongside `hooks/` and `.claude-plugin/`.
## [0.1.0] — 2026-04-24 ## [0.1.0] — 2026-04-24
Initial marketplace release. Initial marketplace release.
+2
View File
@@ -16,6 +16,8 @@ You don't have a video input; this skill gives you one. A Python script download
## Step 0 — Setup preflight (runs every `/watch` invocation, silent on success) ## Step 0 — Setup preflight (runs every `/watch` invocation, silent on success)
**Python interpreter:** every `python3 ...` command in this skill is for macOS/Linux. On **Windows**, substitute `python` — the `python3` command on Windows is the Microsoft Store stub and will not run the script.
Before every `/watch` run, verify that dependencies and an API key are in place: Before every `/watch` run, verify that dependencies and an API key are in place:
```bash ```bash
+9
View File
@@ -0,0 +1,9 @@
---
description: Watch a video (URL or local path). Downloads with yt-dlp, extracts frames with ffmpeg, transcribes from captions or Whisper, and answers questions about what's in the video.
argument-hint: <video-url-or-path> [question]
allowed-tools: [Bash, Read, AskUserQuestion]
---
Invoke the `watch` skill (defined in SKILL.md) with the user's arguments: $ARGUMENTS
Follow the skill's full pipeline: preflight setup check → download via yt-dlp → extract frames at auto-scaled fps → pull captions or Whisper transcript → Read each frame → answer the user grounded in frames and transcript. If the user provided no arguments, ask them for a video URL or local path before proceeding.
+5 -3
View File
@@ -21,11 +21,13 @@ OUT="dist/watch.skill"
git archive --format=zip --prefix=watch/ --output="$OUT" HEAD git archive --format=zip --prefix=watch/ --output="$OUT" HEAD
# claude.ai's .skill bundle needs only SKILL.md + scripts/ runtime. Claude Code # claude.ai's .skill bundle needs only SKILL.md + scripts/ runtime. Claude Code
# needs hooks/ and .claude-plugin/ in the git archive (that's why they are NOT # needs hooks/, commands/, and .claude-plugin/ in the git archive (that's why
# in .gitattributes export-ignore), but the .skill bundle should strip them to # they are NOT in .gitattributes export-ignore), but the .skill bundle should
# keep a single canonical SKILL.md and stay well under the 200-file cap. # strip them to keep a single canonical SKILL.md and stay well under the
# 200-file cap.
zip -d "$OUT" \ zip -d "$OUT" \
"watch/hooks/*" \ "watch/hooks/*" \
"watch/commands/*" \
"watch/.claude-plugin/*" \ "watch/.claude-plugin/*" \
> /dev/null 2>&1 || true > /dev/null 2>&1 || true
+14
View File
@@ -186,6 +186,16 @@ def _install_hint_linux(missing: list[str]) -> str:
return "\n ".join(hints) if hints else "nothing to install" return "\n ".join(hints) if hints else "nothing to install"
def _install_hint_windows(missing: list[str]) -> str:
pkgs = _brew_pkg(missing)
hints = []
if "ffmpeg" in pkgs:
hints.append("winget: `winget install Gyan.FFmpeg`")
if "yt-dlp" in pkgs:
hints.append("winget: `winget install yt-dlp.yt-dlp` or pip: `pip install --user yt-dlp`")
return "\n ".join(hints) if hints else "nothing to install"
def _status() -> dict: def _status() -> dict:
"""Structured preflight snapshot.""" """Structured preflight snapshot."""
missing = _check_binaries() missing = _check_binaries()
@@ -268,6 +278,10 @@ def cmd_install() -> int:
print("[setup] dependencies missing on Linux — please install:", file=sys.stderr) print("[setup] dependencies missing on Linux — please install:", file=sys.stderr)
print(" " + _install_hint_linux(missing), file=sys.stderr) print(" " + _install_hint_linux(missing), file=sys.stderr)
return 2 return 2
elif system == "Windows":
print("[setup] dependencies missing on Windows — please install:", file=sys.stderr)
print(" " + _install_hint_windows(missing), file=sys.stderr)
return 2
else: else:
print(f"[setup] unsupported platform ({system}) for auto-install. Install manually:", file=sys.stderr) print(f"[setup] unsupported platform ({system}) for auto-install. Install manually:", file=sys.stderr)
print(f" missing: {', '.join(missing)}", file=sys.stderr) print(f" missing: {', '.join(missing)}", file=sys.stderr)
+1 -1
View File
@@ -177,7 +177,7 @@ def main() -> int:
mins = int(full_duration // 60) mins = int(full_duration // 60)
print() print()
print( print(
f"> ⚠️ This is a {mins}-minute video. Frame coverage is sparse at this length — " f"> **Warning:** This is a {mins}-minute video. Frame coverage is sparse at this length — "
"accuracy degrades noticeably on anything over 10 minutes. For better results, " "accuracy degrades noticeably on anything over 10 minutes. For better results, "
"re-run with `--start HH:MM:SS --end HH:MM:SS` to zoom into a specific section." "re-run with `--start HH:MM:SS --end HH:MM:SS` to zoom into a specific section."
) )