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>
65 lines
2.0 KiB
Python
65 lines
2.0 KiB
Python
"""yt-dlp argv construction for download.py.
|
|
|
|
Regression guard: ``--sub-langs all`` makes yt-dlp fetch YouTube's hundreds of
|
|
auto-translated caption tracks, which can take minutes and stalls before the
|
|
video download even starts. We only support English, so the request must stay
|
|
bounded to the English-only pattern.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "skills" / "watch" / "scripts"
|
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
|
|
import download # noqa: E402
|
|
|
|
URL = "https://www.youtube.com/watch?v=rlOpbu3Enkw"
|
|
|
|
|
|
def _capture_argv(monkeypatch: pytest.MonkeyPatch) -> list[list[str]]:
|
|
"""Stub subprocess.run inside download.py and record every argv."""
|
|
calls: list[list[str]] = []
|
|
|
|
class _Result:
|
|
returncode = 0
|
|
stdout = ""
|
|
stderr = ""
|
|
|
|
def fake_run(cmd, *args, **kwargs):
|
|
calls.append(list(cmd))
|
|
return _Result()
|
|
|
|
monkeypatch.setattr(download.subprocess, "run", fake_run)
|
|
return calls
|
|
|
|
|
|
def _sub_langs(argv: list[str]) -> str:
|
|
idx = argv.index("--sub-langs")
|
|
return argv[idx + 1]
|
|
|
|
|
|
def _assert_english_only(langs: str) -> None:
|
|
tokens = langs.split(",")
|
|
assert "all" not in tokens, f"sub-langs must not request all languages, got {langs!r}"
|
|
assert all(t.startswith("en") for t in tokens), f"sub-langs must be English-only, got {langs!r}"
|
|
|
|
|
|
def test_fetch_captions_requests_english_only(monkeypatch, tmp_path):
|
|
calls = _capture_argv(monkeypatch)
|
|
download.fetch_captions(URL, tmp_path / "download")
|
|
_assert_english_only(_sub_langs(calls[0]))
|
|
|
|
|
|
def test_download_url_requests_english_only(monkeypatch, tmp_path):
|
|
calls = _capture_argv(monkeypatch)
|
|
# _pick_video returns None with no real file, which raises SystemExit after
|
|
# the yt-dlp argv is already built — that's all we need to inspect.
|
|
with pytest.raises(SystemExit):
|
|
download.download_url(URL, tmp_path / "download")
|
|
_assert_english_only(_sub_langs(calls[0]))
|