From 57ebe8bbb93fc1aac92a95ba7a50d9c86a9a3b01 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Sun, 5 Jul 2026 14:47:42 -0500 Subject: [PATCH] =?UTF-8?q?feat(skills):=20recoup-content-clip-video=20?= =?UTF-8?q?=E2=80=94=20clip=20long-form=20video=20into=20shorts=20via=20th?= =?UTF-8?q?e=20OpusClip=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minimal first pass: SKILL.md with auth, base URL, the submit→wait→fetch flow, and links to the OpusClip API docs (quickstart + llms.txt index). Route + fixture added per resolver rules. Also unblocks CI on main: recoup-internal-video-grok-1.5-imagine-facetime (#74) was unreachable because the resolver slug regex disallowed dots — broadened to [a-z0-9.-] in check_resolvable.py / run_resolver_eval.py and added its missing route + fixture. Co-Authored-By: Claude Fable 5 --- README.md | 1 + RESOLVER.md | 2 ++ resolver-eval.jsonl | 2 ++ scripts/check_resolvable.py | 2 +- scripts/run_resolver_eval.py | 4 +-- skills/recoup-content-clip-video/SKILL.md | 32 +++++++++++++++++++++++ 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 skills/recoup-content-clip-video/SKILL.md diff --git a/README.md b/README.md index 4fb4c01..8293b40 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ Every skill is named `recoup-[domain]-[verb]-[noun]`, so the `/` list clusters b | recoup-content-write-caption | Captions in the artist's own voice | | recoup-content-make-graphics | Cover art, thumbnails, carousels, promo/quote cards | | recoup-content-make-video | Short-form video, lyric videos, visualizers, reformats | +| recoup-content-clip-video | Clip long-form video into shorts via OpusClip | | recoup-content-asset-pack | A whole 15–30-piece clip family for one song | | recoup-content-reactive-post | Turn a real milestone/trend into a timely post | diff --git a/RESOLVER.md b/RESOLVER.md index 9281ac7..708f5e7 100644 --- a/RESOLVER.md +++ b/RESOLVER.md @@ -43,6 +43,7 @@ | write a caption in the artist's voice | `recoup-content-write-caption` | | cover art / thumbnail / carousel / promo / quote card | `recoup-content-make-graphics` | | short video / lyric video / visualizer / reformat for platforms | `recoup-content-make-video` | +| clip a long video (podcast/stream/interview) into shorts | `recoup-content-clip-video` | | a whole content pack (15–30 assets) for one song | `recoup-content-asset-pack` | | react to a milestone/trend, make something timely | `recoup-content-reactive-post` | @@ -82,6 +83,7 @@ | "recoup-internal" + account-health / account-status snapshot for a Recoup account | `recoup-internal-account-health-report` | | "recoup-internal" + audit scheduled-task email health (what tasks emailed, how many empties the guard blocked) over a window | `recoup-internal-task-email-audit` | | "recoup-internal" + draft/ship/measure LinkedIn or X posts | `recoup-internal-social-ship-posts` | +| "recoup-internal" + make a FaceTime-call ad / two-person video-call ad (Grok Imagine) | `recoup-internal-video-grok-1.5-imagine-facetime` | --- diff --git a/resolver-eval.jsonl b/resolver-eval.jsonl index 43bfb1d..2418870 100644 --- a/resolver-eval.jsonl +++ b/resolver-eval.jsonl @@ -68,3 +68,5 @@ {"intent": "how is this artist doing across streaming and socials", "expected": "recoup-research-artist-overview", "not": ["recoup-internal-account-health-report"]} {"intent": "recoup-internal audit how last night's task emails went", "expected": "recoup-internal-task-email-audit"} {"intent": "recoup-internal are we still sending empty footer-only emails from scheduled tasks", "expected": "recoup-internal-task-email-audit"} +{"intent": "clip this podcast episode into shorts for tiktok", "expected": "recoup-content-clip-video", "not": ["recoup-content-make-video", "recoup-song-find-hook"]} +{"intent": "recoup-internal make a facetime call ad for this artist", "expected": "recoup-internal-video-grok-1.5-imagine-facetime", "not": ["recoup-content-make-video"]} diff --git a/scripts/check_resolvable.py b/scripts/check_resolvable.py index 2537be2..79ad2e2 100644 --- a/scripts/check_resolvable.py +++ b/scripts/check_resolvable.py @@ -25,7 +25,7 @@ DEFAULT_PLUGIN = REPO_ROOT # A skill reference in RESOLVER.md is a backtick-wrapped slug: `recoup-foo-bar`. -SKILL_TOKEN = re.compile(r"`(recoup-[a-z0-9-]+)`") +SKILL_TOKEN = re.compile(r"`(recoup-[a-z0-9.-]+)`") def skill_dirs(plugin: Path) -> set[str]: diff --git a/scripts/run_resolver_eval.py b/scripts/run_resolver_eval.py index 3b53cea..887a17c 100644 --- a/scripts/run_resolver_eval.py +++ b/scripts/run_resolver_eval.py @@ -48,7 +48,7 @@ # Co-located with the plugin root (NOT under tests/, which is gitignored) so the # fixtures ship and CI can run them on a fresh checkout. FIXTURES = PLUGIN / "resolver-eval.jsonl" -SKILL_TOKEN = re.compile(r"`(recoup-[a-z0-9-]+)`") +SKILL_TOKEN = re.compile(r"`(recoup-[a-z0-9.-]+)`") def skill_dirs() -> set[str]: @@ -82,7 +82,7 @@ def load_fixtures() -> list[dict]: GATEWAY_KEY = os.environ.get("AI_GATEWAY_API_KEY") or os.environ.get("VERCEL_AI_GATEWAY_API_KEY") MIN_PASS = float(os.environ.get("RECOUP_RESOLVER_EVAL_MIN_PASS", "1.0")) FRONTMATTER_KEY = re.compile(r"^([A-Za-z][\w-]*):\s?(.*)$") -SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9-]+") +SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9.-]+") def _gateway_ssl_context() -> ssl.SSLContext: diff --git a/skills/recoup-content-clip-video/SKILL.md b/skills/recoup-content-clip-video/SKILL.md new file mode 100644 index 0000000..b8d069e --- /dev/null +++ b/skills/recoup-content-clip-video/SKILL.md @@ -0,0 +1,32 @@ +--- +name: recoup-content-clip-video +description: 'Clip long-form video (podcast, interview, livestream, live set, music video) into ready-to-post short clips using the OpusClip API. Use for "clip this video", "turn this podcast/stream into shorts", "make clips from this YouTube video", or "cut this into TikToks/Reels/Shorts". Input is an existing long video URL; output is a set of generated clips. To generate new video from scratch, use recoup-content-make-video; to find the single best moment in a song, use recoup-song-find-hook.' +--- + +# Recoup Content — Clip Video + +Turn one long-form video into a set of short, social-ready clips via the +[OpusClip API](https://help.opus.pro/api-reference/quickstart). + +## Setup + +- Auth: `Authorization: Bearer $OPUS_PRO_API_KEY` (never hardcode the key; get + one at https://clip.opus.pro/dashboard). +- Base URL: `https://api.opus.pro/api` + +## Flow + +1. **Submit** the long-form video: `POST /clip-projects` with `{ "videoUrl": "" }`. + The response includes a project `id`. +2. **Wait** for clipping to finish — configure a webhook via `conclusionActions` + (type `WEBHOOK`) when possible; otherwise poll step 3 until clips appear. +3. **Fetch clips**: `GET /exportable-clips?q=findByProjectId&projectId=`. +4. **Deliver** the clip URLs to the user with each clip's title/duration. + +## Docs + +- Quickstart: https://help.opus.pro/api-reference/quickstart +- Full endpoint index (LLM-readable): https://help.opus.pro/llms.txt +- Every doc page has a `.md` version — append `.md` to the URL and fetch it + before using any endpoint not covered here (curation preferences, brand + templates, social posting, collections, transcripts).