429f3143e5
Move the skill into skills/watch/ so SKILL.md and its scripts/ runtime are
siblings inside one folder. `npx skills add` (Codex/Cursor/Copilot/agents) now
copies a working skill as a unit; previously it grabbed the root SKILL.md alone
and left scripts/ behind, so the skill was dead on arrival on every non-Claude
host. Mirrors the layout last30days-skill adopted for the same reason.
- skills/watch/{SKILL.md,scripts/}: self-contained skill folder
- SKILL.md: resolve a harness-agnostic $SKILL_DIR (the dir it was Read from)
instead of the Claude-Code-only ${CLAUDE_SKILL_DIR}; guard + 19 call sites
- drop commands/watch.md: /watch derives from frontmatter (name + user-invocable)
- .codex-plugin/plugin.json: full manifest with "skills": "./skills/" + interface
- add .agents/plugins/marketplace.json, AGENTS.md, CLAUDE.md, .skillignore
- build-skill.sh: archive the skills/watch subtree (one SKILL.md, no zip -d)
- fix paths in tests, hooks hint, .gitattributes, release.yml
- relocate dev-sync.sh to repo root and fix REPO_ROOT
- README: content-ideas structure, npx skills install, star history
Verified: 37/37 tests pass; npx skills add bundles the full scripts/ runtime;
manifests valid; versions synced at 0.1.3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.2 KiB
Python
70 lines
2.2 KiB
Python
"""End-to-end routing of --detail through watch.py on a local clip."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
WATCH = Path(__file__).resolve().parent.parent / "skills" / "watch" / "scripts" / "watch.py"
|
|
|
|
|
|
def _run(clip: Path, *args: str, env_extra: dict | None = None) -> str:
|
|
env = dict(os.environ)
|
|
env.pop("WATCH_DETAIL", None)
|
|
if env_extra:
|
|
env.update(env_extra)
|
|
proc = subprocess.run(
|
|
[sys.executable, str(WATCH), str(clip), "--no-whisper", *args],
|
|
capture_output=True, text=True, env=env,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return proc.stdout
|
|
|
|
|
|
def test_efficient_uses_keyframe_engine(cut_clip: Path):
|
|
out = _run(cut_clip, "--detail", "efficient")
|
|
assert "(keyframe" in out
|
|
assert "**Detail:** efficient" in out
|
|
|
|
|
|
def test_balanced_uses_scene_engine(cut_clip: Path):
|
|
out = _run(cut_clip, "--detail", "balanced")
|
|
assert "(scene" in out
|
|
assert "**Detail:** balanced" in out
|
|
|
|
|
|
def test_token_burner_uses_scene_engine(cut_clip: Path):
|
|
out = _run(cut_clip, "--detail", "token-burner")
|
|
assert "(scene" in out
|
|
|
|
|
|
def test_transcript_skips_frames(cut_clip: Path):
|
|
out = _run(cut_clip, "--detail", "transcript")
|
|
assert "skipped" in out
|
|
assert "frame_0000.jpg" not in out
|
|
|
|
|
|
def test_flag_overrides_env(cut_clip: Path):
|
|
out = _run(cut_clip, "--detail", "efficient", env_extra={"WATCH_DETAIL": "balanced"})
|
|
assert "(keyframe" in out
|
|
|
|
|
|
def test_default_is_balanced(cut_clip: Path):
|
|
out = _run(cut_clip) # no flag, WATCH_DETAIL cleared
|
|
assert "**Detail:** balanced" in out
|
|
assert "(scene" in out
|
|
|
|
|
|
def test_timestamps_add_cue_frames_to_detail(cut_clip: Path):
|
|
out = _run(cut_clip, "--detail", "balanced", "--timestamps", "1,3")
|
|
assert "reason=transcript-cue" in out
|
|
assert "reason=scene-change" in out # detail frames still present (additive)
|
|
|
|
|
|
def test_timestamps_with_transcript_detail_is_cue_only(cut_clip: Path):
|
|
out = _run(cut_clip, "--detail", "transcript", "--timestamps", "1,3")
|
|
assert "reason=transcript-cue" in out
|
|
assert "reason=scene-change" not in out
|
|
assert "reason=keyframe" not in out
|