Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
2 changes: 2 additions & 0 deletions RESOLVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |

Expand Down Expand Up @@ -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` |

---

Expand Down
2 changes: 2 additions & 0 deletions resolver-eval.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
2 changes: 1 addition & 1 deletion scripts/check_resolvable.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
4 changes: 2 additions & 2 deletions scripts/run_resolver_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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.-]+")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep dot support, but stop matching sentence punctuation.

recoup-[a-z0-9.-]+ will also absorb a trailing . in model replies, so a correct answer like recoup-internal-video-grok-1.5-imagine-facetime. falls through to none. Make the pattern version-aware instead of letting . match anywhere.

♻️ Proposed fix
-SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9.-]+")
+SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9-]+(?:\.[a-z0-9-]+)*")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9.-]+")
SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9-]+(?:\.[a-z0-9-]+)*")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run_resolver_eval.py` at line 85, The SKILL_TOKEN_PLAIN regex
currently overmatches by allowing any dot anywhere, which causes trailing
sentence punctuation to be treated as part of the token in run_resolver_eval.py.
Update the pattern in SKILL_TOKEN_PLAIN to be version-aware so it still accepts
valid dotted version segments but stops before terminal punctuation, and make
sure the token parsing logic that relies on this regex returns the correct skill
name instead of falling back to none.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The broadened character class [a-z0-9.-]+ will greedily absorb a trailing . when the skill slug appears at the end of a sentence in a model reply (e.g., "…use recoup-internal-video-grok-1.5-imagine-facetime."). The resulting match won't exist in skill_dirs(), turning a correct answer into a false negative. A version-segment-aware pattern avoids this while still supporting dotted slugs like grok-1.5.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/run_resolver_eval.py, line 85:

<comment>The broadened character class `[a-z0-9.-]+` will greedily absorb a trailing `.` when the skill slug appears at the end of a sentence in a model reply (e.g., `"…use recoup-internal-video-grok-1.5-imagine-facetime."`). The resulting match won't exist in `skill_dirs()`, turning a correct answer into a false negative. A version-segment-aware pattern avoids this while still supporting dotted slugs like `grok-1.5`.</comment>

<file context>
@@ -82,7 +82,7 @@ def load_fixtures() -> list[dict]:
 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.-]+")
 
 
</file context>
Suggested change
SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9.-]+")
SKILL_TOKEN_PLAIN = re.compile(r"recoup-[a-z0-9-]+(?:\.[a-z0-9-]+)*")



def _gateway_ssl_context() -> ssl.SSLContext:
Expand Down
32 changes: 32 additions & 0 deletions skills/recoup-content-clip-video/SKILL.md
Original file line number Diff line number Diff line change
@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: For multi-org users, the GET /exportable-clips endpoint requires a x-opus-org-id header. The current Setup only documents Authorization: Bearer $OPUS_PRO_API_KEY, so users with multiple organizations will get a 400 error on step 3 (Fetch clips) with no guidance on how to resolve it. Consider adding a note that this header may be needed, referencing the Org ID from the OpusClip dashboard.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/recoup-content-clip-video/SKILL.md, line 13:

<comment>For multi-org users, the GET `/exportable-clips` endpoint requires a `x-opus-org-id` header. The current Setup only documents `Authorization: Bearer $OPUS_PRO_API_KEY`, so users with multiple organizations will get a 400 error on step 3 (Fetch clips) with no guidance on how to resolve it. Consider adding a note that this header may be needed, referencing the Org ID from the OpusClip dashboard.</comment>

<file context>
@@ -0,0 +1,32 @@
+
+## 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`
</file context>

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": "<source url>" }`.
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=<id>`.
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).
Loading