fix: keep SKILL.md frontmatter within the Agent Skills spec

Claude's skill upload rejects frontmatter keys outside the spec. SKILL.md now
uses only spec keys, with the version under metadata.version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
bradautomates 2026-09-25 10:52:58 +10:00
parent de8b5b5dc0
commit 457627318c
6 changed files with 23 additions and 13 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "watch", "name": "watch",
"version": "0.3.0", "version": "0.3.1",
"description": "Give an agent video evidence: timestamped frames and native captions, with optional local WhisperX or Groq/OpenAI transcription.", "description": "Give an agent video evidence: timestamped frames and native captions, with optional local WhisperX or Groq/OpenAI transcription.",
"author": { "author": {
"name": "Bradley Bonanno" "name": "Bradley Bonanno"

View File

@ -1,6 +1,6 @@
{ {
"name": "watch", "name": "watch",
"version": "0.3.0", "version": "0.3.1",
"description": "Give an agent video evidence: timestamped frames and native captions, with optional local WhisperX or Groq/OpenAI transcription.", "description": "Give an agent video evidence: timestamped frames and native captions, with optional local WhisperX or Groq/OpenAI transcription.",
"author": { "author": {
"name": "Bradley Bonanno" "name": "Bradley Bonanno"

View File

@ -20,7 +20,8 @@ Agent Skills package that gives an agent a video input. Installable across Claud
- The product is the slash-command-invoked skill (`/watch <url-or-path> [question]`), not a CLI. `scripts/watch.py` is implementation. Features must work across every harness the skill installs into, not just Claude Code. - The product is the slash-command-invoked skill (`/watch <url-or-path> [question]`), not a CLI. `scripts/watch.py` is implementation. Features must work across every harness the skill installs into, not just Claude Code.
- **The skill is one self-contained folder: `skills/watch/`.** SKILL.md and `scripts/` are siblings inside it. This is what lets `npx skills add` copy a working skill as a unit — do NOT move SKILL.md or `scripts/` back to the repo root, or non-Claude installers will copy SKILL.md without the scripts. - **The skill is one self-contained folder: `skills/watch/`.** SKILL.md and `scripts/` are siblings inside it. This is what lets `npx skills add` copy a working skill as a unit — do NOT move SKILL.md or `scripts/` back to the repo root, or non-Claude installers will copy SKILL.md without the scripts.
- **Path resolution is harness-agnostic.** SKILL.md resolves `SKILL_DIR` as the directory of the SKILL.md the model just Read, then runs `${SKILL_DIR}/scripts/...`. Do NOT reintroduce `${CLAUDE_SKILL_DIR}` (Claude-Code-only) — it is unset on Codex/Cursor/agents and breaks every script call there. - **Path resolution is harness-agnostic.** SKILL.md resolves `SKILL_DIR` as the directory of the SKILL.md the model just Read, then runs `${SKILL_DIR}/scripts/...`. Do NOT reintroduce `${CLAUDE_SKILL_DIR}` (Claude-Code-only) — it is unset on Codex/Cursor/agents and breaks every script call there.
- **No `commands/` wrapper.** `/watch` is derived from SKILL.md frontmatter (`name: watch` + `user-invocable: true`). A separate command file creates a duplicate slash command. - **No `commands/` wrapper.** `/watch` comes from `name: watch` (skills are user-invocable by default). A separate command file creates a duplicate slash command.
- **SKILL.md frontmatter uses only Agent Skills keys:** `name`, `description`, `license`, `allowed-tools`, `metadata`, `compatibility`. Claude's skill upload rejects anything else; the version lives in `metadata.version`.
## Install surfaces ## Install surfaces
@ -45,6 +46,6 @@ bash skills/watch/scripts/build-skill.sh # → dist/watch.skill
## Rules ## Rules
- Keep the version in sync across `skills/watch/SKILL.md` (frontmatter), `.claude-plugin/plugin.json`, and `.codex-plugin/plugin.json` when cutting a release. - Keep the version in sync across `skills/watch/SKILL.md` (`metadata.version`), `.claude-plugin/plugin.json`, and `.codex-plugin/plugin.json` when cutting a release.
- Releasing: tag `vX.Y.Z` and push the tag; `.github/workflows/release.yml` builds `dist/watch.skill` and attaches it to the GitHub release. - Releasing: tag `vX.Y.Z` and push the tag; `.github/workflows/release.yml` builds `dist/watch.skill` and attaches it to the GitHub release.
- Never commit real API keys or `.env` contents; keys live in `~/.config/watch/.env` (mode `0600`) at runtime. - Never commit real API keys or `.env` contents; keys live in `~/.config/watch/.env` (mode `0600`) at runtime.

View File

@ -2,6 +2,11 @@
All notable changes to `/watch` are documented here. All notable changes to `/watch` are documented here.
## [0.3.1] — Unreleased
### Fixed
- `SKILL.md` frontmatter uses only Agent Skills keys, so `watch.skill` uploads to Claude (the upload rejected `version`, `argument-hint`, `user-invocable`, and other extra keys). The version moves to `metadata.version`.
## [0.3.0] — 2026-09-25 ## [0.3.0] — 2026-09-25
### Added ### Added

View File

@ -1,14 +1,10 @@
--- ---
name: watch name: watch
version: "0.3.0"
description: Watch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or local WhisperX / cloud Whisper fallback), and hands the result to the agent so it can answer questions about what's in the video. With a Gemini API key, Google's agentic video model watches the full video instead. description: Watch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or local WhisperX / cloud Whisper fallback), and hands the result to the agent so it can answer questions about what's in the video. With a Gemini API key, Google's agentic video model watches the full video instead.
argument-hint: "<video-url-or-path> [question]"
allowed-tools: Bash, Read, AskUserQuestion
homepage: https://github.com/bradautomates/claude-video
repository: https://github.com/bradautomates/claude-video
author: bradautomates
license: MIT license: MIT
user-invocable: true allowed-tools: Bash, Read, AskUserQuestion
metadata:
version: "0.3.1"
--- ---
# /watch # /watch
@ -17,7 +13,7 @@ Run the bundled Python script. With the **gemini** engine (a `GEMINI_API_KEY` is
## Resolve the skill and interpreter ## Resolve the skill and interpreter
Set `SKILL_DIR` to the **absolute directory containing this SKILL.md that you just read**. `scripts/` is its sibling in every host; do not use a Claude-specific environment variable. Check that `SKILL_DIR/scripts/watch.py` exists. The whole `skills/watch/` folder must be installed, with one canonical skill and no command wrapper. `SKILL_DIR` is the absolute directory containing this SKILL.md; `scripts/` sits beside it.
Commands below use `python3` for macOS/Linux. On Windows, verify a working Python 3.10+ with `python --version` or `py -3 --version` and use that interpreter. `python3` is not always a Store alias; inspect the actual result. In PowerShell use `$SKILL_DIR` rather than Bash variable syntax, for example: Commands below use `python3` for macOS/Linux. On Windows, verify a working Python 3.10+ with `python --version` or `py -3 --version` and use that interpreter. `python3` is not always a Store alias; inspect the actual result. In PowerShell use `$SKILL_DIR` rather than Bash variable syntax, for example:

View File

@ -10,6 +10,14 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
def test_frontmatter_uses_only_agent_skills_keys():
# claude.ai uploads reject any other top-level key.
text = (ROOT / 'skills/watch/SKILL.md').read_text(encoding='utf-8')
frontmatter = text.split('---\n')[1]
keys = {line.split(':')[0] for line in frontmatter.splitlines() if line and not line.startswith(' ')}
assert keys <= {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'}
def test_release_versions_match_canonical_skill(): def test_release_versions_match_canonical_skill():
skill = ROOT / 'skills/watch/SKILL.md' skill = ROOT / 'skills/watch/SKILL.md'
version = re.search(r'^ version: "([^"]+)"', skill.read_text(encoding='utf-8'), re.M).group(1) version = re.search(r'^ version: "([^"]+)"', skill.read_text(encoding='utf-8'), re.M).group(1)