@agentmedia/schema 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,9 +18,6 @@ import { fileURLToPath } from 'node:url';
18
18
  import { zodToJsonSchema } from 'zod-to-json-schema';
19
19
  import {
20
20
  V2_GENERATORS,
21
- V2_SHOT_PRESETS,
22
- V2_VIBES,
23
- quoteV2Credits,
24
21
  type V2GeneratorRecord,
25
22
  } from '../src/v2/index.js';
26
23
 
@@ -49,19 +46,11 @@ function fmtInputSchema(def: V2GeneratorRecord): string {
49
46
  return '```json\n' + JSON.stringify(body, null, 2) + '\n```';
50
47
  }
51
48
 
52
- function fmtPricing(def: V2GeneratorRecord): string {
53
- if (!def.pricing) return '_Pricing not declared on this generator._';
54
- if (def.pricing.basis === 'one_shot') {
55
- const c = def.pricing.baseCredits;
56
- return `One-shot: **${c} credits** ($${(c / 100).toFixed(2)})`;
57
- }
58
- // per_clip — show 5/10/15
59
- const rows = [5, 10, 15].map((s) => {
60
- const c = quoteV2Credits(def.id as any, { durationSeconds: s });
61
- return `| ${s}s | ${c} | $${(c / 100).toFixed(2)} |`;
62
- });
63
- return `Per-clip (base ${def.pricing.baseCredits} + ${def.pricing.perSecondCredits}/sec):\n\n| Duration | Credits | USD |\n|---|---:|---:|\n${rows.join('\n')}`;
64
- }
49
+ // Pricing display is intentionally suppressed everywhere — agents and
50
+ // docs should never surface USD or credit numbers. The API debits
51
+ // internally; users get no cost-anxiety prompts. (Server-side allows a
52
+ // soft -10 credit overdraft so a final job never gets rejected on a
53
+ // micro-balance edge case.)
65
54
 
66
55
  // ── docs/v2/api-reference.md ──────────────────────────────────────────────
67
56
 
@@ -82,10 +71,6 @@ function renderApiReference(): string {
82
71
  '',
83
72
  g.description,
84
73
  '',
85
- '### Pricing',
86
- '',
87
- fmtPricing(g),
88
- '',
89
74
  '### Request body',
90
75
  '',
91
76
  fmtInputSchema(g),
@@ -100,16 +85,7 @@ function renderApiReference(): string {
100
85
  '',
101
86
  '```json',
102
87
  JSON.stringify(
103
- {
104
- job_id: '<uuid>',
105
- status: 'submitted',
106
- credits_deducted: g.pricing
107
- ? g.pricing.basis === 'one_shot'
108
- ? g.pricing.baseCredits
109
- : g.pricing.baseCredits + g.pricing.perSecondCredits * 8
110
- : 0,
111
- generator: g.id,
112
- },
88
+ { job_id: '<uuid>', status: 'submitted', generator: g.id },
113
89
  null,
114
90
  2,
115
91
  ),
@@ -158,390 +134,495 @@ function renderApiReference(): string {
158
134
  '- `character_id` — present on jobs that create a v2 character (`char_xxxxxxxxxx`).',
159
135
  '- `video_url` — present on completed video jobs.',
160
136
  '',
161
- '### Shot grammar (Selfie)',
162
- '',
163
- `Selfie's \`preset\` field accepts one of:`,
164
- '',
165
- V2_SHOT_PRESETS.map((p) => `- \`${p}\``).join('\n'),
137
+ '### Selfie pipeline artifacts',
166
138
  '',
167
- `Or \`custom-scene:<text>\` to compose a new shot ad-hoc.`,
139
+ 'Selfie jobs expose intermediate URLs while processing:',
168
140
  '',
169
- '### Vibes (Selfie)',
141
+ '- `portrait_url` — generated actor face portrait, unless reusing a saved character.',
142
+ '- `character_sheet_url` / `sheet_url` — full-body multi-angle character reference.',
143
+ '- `wireframe_url` — photographic storyboard/wireframe board with 8-10 frames and captions.',
144
+ '- `video_url` / `result_url` — final Seedance MP4 after completion.',
170
145
  '',
171
- V2_VIBES.map((v) => `- \`${v}\``).join('\n'),
146
+ 'Agents should surface each artifact as soon as it appears in status instead of waiting silently for the final video.',
172
147
  '',
173
148
  ].join('\n');
174
149
  }
175
150
 
176
- // ── skills/agent-media-v2/SKILL.md ─────────────────────────────────────────
177
151
 
178
- function renderSkill(): string {
152
+ // ─────────────────────────────────────────────────────────────────────────────
153
+ // Multi-file skill emit
154
+ //
155
+ // Layout under skills/agent-media-v2/:
156
+ // SKILL.md — eager-loaded entry, ~2 KB
157
+ // reference/
158
+ // conversation-flow.md — MUST-READ before any CLI call
159
+ // pricing.md — formula + tables
160
+ // subtitle-styles.md — 17 subtitle styles
161
+ // realism-rubric.md — visual-quality guard
162
+ // errors.md — common error codes + fixes
163
+ // generators/
164
+ // selfie.md — flags + CLI/MCP/REST + examples
165
+ // character-create.md — character_create cheat-sheet
166
+ // subs.md — subs cheat-sheet
167
+ //
168
+ // Adding a new v2 product = a new V2_GENERATORS row + this file emits a
169
+ // fresh reference/generators/<id>.md automatically.
170
+ // ─────────────────────────────────────────────────────────────────────────────
171
+
172
+ const SKILL_DIR = resolve(repoRoot, 'skills/agent-media-v2');
173
+
174
+ interface EmittedFile {
175
+ /** path relative to SKILL_DIR */
176
+ relPath: string;
177
+ content: string;
178
+ }
179
+
180
+ function renderSkillIndex(): string {
179
181
  const generators = Object.values(V2_GENERATORS);
180
- const generatorBlocks = generators
181
- .map((g) => {
182
- const cliExample = g.cli?.examples?.[0] ?? `agent-media ${g.cli?.command ?? g.id} …`;
183
- const pricingLine = g.pricing
184
- ? g.pricing.basis === 'one_shot'
185
- ? `Cost: **${g.pricing.baseCredits} credits** ($${(g.pricing.baseCredits / 100).toFixed(2)}).`
186
- : `Cost: **${g.pricing.baseCredits} + ${g.pricing.perSecondCredits}/sec** (8s ≈ ${quoteV2Credits(g.id as any, { durationSeconds: 8 })} credits ≈ $${(quoteV2Credits(g.id as any, { durationSeconds: 8 }) / 100).toFixed(2)}).`
187
- : '';
188
- return [
189
- `### ${g.id}`,
190
- '',
191
- g.description,
192
- '',
193
- pricingLine,
194
- '',
195
- '**CLI:**',
196
- '',
197
- '```bash',
198
- cliExample,
199
- '```',
200
- '',
201
- '**MCP tool:** `' + (g.mcp?.toolName ?? '(none)') + '`',
202
- '',
203
- '**REST:** `' + (g.rest?.method ?? '') + ' ' + (g.rest?.path ?? '(internal)') + '`',
204
- '',
205
- ].join('\n');
206
- })
207
- .join('---\n\n');
182
+ const generatorRows = generators
183
+ .map(
184
+ (g) =>
185
+ `| **${g.cli?.command ?? g.id}** | ${g.summary.replace(/\|/g, '\\|')} | [\`reference/generators/${g.id}.md\`](reference/generators/${g.id}.md) |`,
186
+ )
187
+ .join('\n');
208
188
 
209
189
  return [
210
190
  '---',
211
191
  'name: agent-media-v2',
212
- 'description: AI UGC video production via agent-media v2 — Selfie videos and reusable Characters. Use this skill when the user wants to make TikTok-style "AI person talking to camera" clips or save a character for reuse across multiple generations.',
213
- 'homepage: https://agent-media.ai/skill',
214
- `version: 1.3.0`,
192
+ // CRITICAL: the description string is what Claude reads when deciding whether to
193
+ // load this skill AND it's the most-attended-to text once the skill is loaded.
194
+ // Put the hard gate here, not buried in section 3 of the body.
195
+ 'description: AI UGC video production via agent-media (selfie, character, subs, plus more soon). BEFORE running ANY agent-media command you MUST first Read reference/conversation-flow.md and walk the user through the 4 gates IN ORDER — (1) confirm script OR scene_action; if no speech, also propose background_music, (2) RUN `agent-media character list --json` YOURSELF (don\'t ask the user, don\'t mention char_xxx ids — present saved characters BY NAME if any, otherwise confirm the new description), (3) propose a director\'s brief with setting, lighting, wardrobe, props/product, and action; pass non-default motion/product handling through --scene-action, (4) duration with script-pacing awareness (2-4 words/sec). While jobs run, poll status and open portrait, character sheet, wireframe, and final video as each URL appears. When user says "no subs" → pass --subtitles false. When no script → pass --background-music. NEVER auto-pick a character. NEVER expose char_xxx ids. NEVER mention pricing/credits/USD.',
215
196
  '---',
216
197
  '',
217
198
  GENERATED_NOTE,
218
199
  '',
219
- '# agent-media — STRICT RULES (read first, every time)',
200
+ '# agent-media — Claude skill',
220
201
  '',
221
- '## 0. CONVERSATION FLOWfollow this in order',
202
+ 'agent-media is a CLI for AI UGC video generation. This skill tells you how to drive it. **Loaded files are intentionally small** open the right reference file for the task you have, don\'t try to memorize everything.',
222
203
  '',
223
- 'Before you EVER call the CLI, walk the user through these steps. Ask in batches of 2-3 questions at a time, never one-by-one, never silently default. Use the user\'s answers to populate the CLI args.',
204
+ '## 🛑 HARD GATE read this first, every conversation',
224
205
  '',
225
- '### Step 1 What\'s the video about?',
206
+ 'Before calling ANY `agent-media` shell command, you MUST:',
226
207
  '',
227
- 'This is the FIRST question, every time. Even if the user typed a full prompt with a script, confirm:',
208
+ '1. **Read** [`reference/conversation-flow.md`](reference/conversation-flow.md) the full 4-gate protocol with templates.',
209
+ '2. **Walk the user through 4 gates IN ORDER, one message each** — do not bulk-fire:',
210
+ ' - **Gate 1:** confirm the exact script (verbatim — typos land in the video)',
211
+ ' - **Gate 2:** confirm character. YOU run `agent-media character list --json` (do not ask the user "do you have a saved character?" — they don\'t know that\'s a thing). If the list is empty, just confirm the description from their original prompt. If non-empty, present each saved character BY NAME (not by `char_xxx` id — that format is internal). The user picks by NAME or says "new"; you map name → id internally. 🛑 NEVER auto-pick. NEVER show `char_xxx` ids to the user. Never ask for a photo by default.',
212
+ ' - **Gate 3:** propose a full **director\'s brief** with pre-filled fields in 3 sections — **A. Intent+Performance**, **B. Scene+Look**, **C. Output**. Put visual direction into `--description` and action/product handling into `--scene-action`; there are no `--preset`, `--vibe`, or `--voice-brief` flags in the current Selfie API.',
213
+ ' - **Gate 4:** duration + **script-pacing check**. Count the words in the script and PROPOSE the duration that fits (5s ≈ 10-20 words, 10s ≈ 20-40 words, 15s ≈ 30-60 words at the natural 2-4 words/sec pace).',
214
+ '3. Only then call the CLI.',
228
215
  '',
229
- '> *"Got it quick check before I generate:*',
230
- '> *• What\'s the video about? (the topic / what they\'re selling / what\'s happening in the scene)*',
231
- '> *• What\'s the exact line you want them to say? (the script — 1-3 sentences usually works best)"*',
216
+ '**The director\'s brief at Gate 3 is non-optional.** It\'s where quality lives. Skipping it = generic output. PROPOSE smart defaults from the script + description; don\'t ask blank questions.',
232
217
  '',
233
- 'Don\'t skip this. The "topic" informs the preset + vibe selection later. If the user already gave you a script, repeat it back so they can confirm or tweak it.',
218
+ 'Calling the CLI without doing 1–3 is a protocol violation the user gets a generic, mid video. Ask the extra questions.',
234
219
  '',
235
- '### Step 2 — Who\'s the character?',
220
+ '## NEVER discuss pricing',
236
221
  '',
237
- '**agent-media generates the character image itself from your description. You DO NOT need to provide a photo.** A description like "25yo asian woman with long wavy dark hair" is enough the pipeline produces a consistent on-model person.',
222
+ 'Do NOT mention credit costs, USD amounts, or pricing tiers in any reply. Do NOT ask the user to "confirm cost". The API handles billing transparently. If the user asks about cost, point them at <https://agent-media.ai/pricing>. That is the only acceptable surface for pricing.',
238
223
  '',
239
- 'Three valid input paths (in order of preference):',
224
+ '## What agent-media can do (router)',
240
225
  '',
241
- '1. **Description only** (the default use this 95% of the time)',
242
- ' Just `--description "..."`. agent-media generates the portrait and the multi-pose sheet, then runs the video. No photo, no upload, no questions about photos. Example:',
243
- ' ```bash',
244
- ' agent-media selfie --description "25yo asian woman, long wavy dark hair, soft smile" --script "..."',
245
- ' ```',
226
+ '| Command | Use when | Deep-dive |',
227
+ '|---|---|---|',
228
+ generatorRows,
246
229
  '',
247
- '2. **Saved character** (`--character char_xxxxxxxxxx`)',
248
- ' Use when the user references a person from a previous run ("use the same girl again", "the one from yesterday\'s video"). Run `agent-media character` to list saved characters if you need to look one up.',
230
+ '`agent-media skill update` pull the latest skill files into ~/.claude/skills/agent-media-v2/.',
231
+ '`agent-media skill status` print local vs remote version.',
249
232
  '',
250
- '3. **Real-person photo + description** (`--photo <file|url> --description "..."`)',
251
- ' Use ONLY when the user explicitly says "use THIS person" and gives you a photo of someone specific. Otherwise default to path 1.',
233
+ '## What agent-media CANNOT do',
252
234
  '',
253
- 'Hard rules:',
254
- '- NEVER ask the user "do you have a photo?". Default to description-only.',
255
- '- NEVER ask the user about the underlying models (gpt-image-2, Seedance, etc.). They are implementation details. Just say "agent-media generates the character".',
256
- '- NEVER fall back to `agent-media ugc` or a stock actor library. Forbidden.',
257
- '- If the user wants the SAME person across multiple videos, run `agent-media character create --description "..."` once first (no photo needed) to get a `char_xxxxxxxxxx`, then pass `--character <id>` to every selfie.',
235
+ 'These legacy v1 commands exist in the CLI binary for backwards compat but produce inferior output. They are hidden from `agent-media --help` for a reason. **Never call them.**',
258
236
  '',
259
- '### Step 3Where + how does it feel? (preset + vibe)',
237
+ '- `agent-media ugc` uses a stale fixed actor library (200 actors picked at random). The actors look dated. Use `agent-media selfie` — it generates an on-model character from your description on every run.',
238
+ '- ❌ `agent-media show-your-app` — built on the v1 actor pool + manual screen-composite step. The v2 product is on the roadmap. For now, run `agent-media selfie` for the talking head and capture the screen separately.',
239
+ '- ❌ `agent-media laptop-ugc` — v1 only. Same story as show-your-app; v2 product coming.',
240
+ '- ❌ `agent-media character-video` — superseded by `agent-media selfie --character <id>`. The new command uses the current portrait → sheet → wireframe → Seedance pipeline.',
241
+ '- ❌ `agent-media text-to-video` — no character control; output is generic and off-brand. Use `agent-media selfie` with a saved character.',
242
+ '- ❌ `agent-media subtitle` (singular) — v1 burner with fewer styles and shakier sync. Use `agent-media subs` (plural).',
243
+ '- ❌ `agent-media review` — SaaS-review generator built on v1 actors. Compose with `agent-media selfie` + a script you write.',
244
+ '- ❌ `agent-media product-acting` — v1 product-in-hand generator. For now, use `agent-media selfie` with a strong `--scene-action` describing the product hold, demo, and interaction.',
260
245
  '',
261
- 'Pick one **preset** (location/shot grammar) + one **vibe** (emotional tone). If you can guess from the topic, propose them as defaults to confirm; if not, ask.',
246
+ 'If the user wants a feature not listed in the router above, offer `agent-media selfie` when the request can be expressed as one actor, one setting, dialogue/action, and optional props/product handling.',
262
247
  '',
263
- '**Presets (20, one of):**',
248
+ '## Reference files (lazy-loaded)',
264
249
  '',
265
- '| Preset | Best for |',
266
- '|---|---|',
267
- '| `bedroom-morning-ritual` | Default. Skincare, routine, "morning vibes" content |',
268
- '| `getting-ready-mirror-edge` | OOTD, makeup, fashion |',
269
- '| `bathroom-skincare-routine` | Beauty, hair care, skincare reveals |',
270
- '| `bedside-lamp-evening` | Wind-down, journal, ASMR-style |',
271
- '| `kitchen-glow-up` | Food, drinks, supplements, cooking |',
272
- '| `backyard-morning-coffee` | Lifestyle, mindfulness, slow content |',
273
- '| `picnic-blanket-outdoor` | Outdoor, summer, friends |',
274
- '| `car-quick-honest-review` | Honest reviews, "I just bought this..." |',
275
- '| `car-passenger-honest` | Same vibe, passenger angle |',
276
- '| `outdoor-walking-talking` | Walking-and-talking, candid |',
277
- '| `couch-haul-show-off` | Unboxing, hauls, "look what I got" |',
278
- '| `closet-fit-check` | Fashion, fit-checks, OOTD |',
279
- '| `studio-apartment-tour` | Lifestyle, apartment content |',
280
- '| `balcony-evening-vibes` | Aesthetic, lifestyle, golden hour |',
281
- '| `desk-wfh-quick-pitch` | SaaS, productivity, work-from-home |',
282
- '| `cafe-window-seat` | Lifestyle, work, coffee culture |',
283
- '| `office-bathroom-discreet` | Workplace anecdotes, "let me tell you" |',
284
- '| `gym-post-workout` | Fitness, supplements, wellness |',
285
- '| `salon-mirror-result` | Hair, beauty reveals, "before/after" |',
286
- '| `travel-hotel-room-review` | Travel, hotel reviews |',
250
+ 'Open these only when you need them:',
287
251
  '',
288
- '**Vibes (5, one of):** `excited` · `calm` · `sassy` · `serious` · `curious`',
252
+ '- [`reference/conversation-flow.md`](reference/conversation-flow.md) the 3 gate questions, in order, with example wording',
253
+ '- [`reference/subtitle-styles.md`](reference/subtitle-styles.md) — all 17 subtitle styles',
254
+ '- [`reference/realism-rubric.md`](reference/realism-rubric.md) — visual-quality guard the pipeline enforces',
255
+ '- [`reference/errors.md`](reference/errors.md) — common errors + remediation',
256
+ ...generators.map(
257
+ (g) =>
258
+ `- [\`reference/generators/${g.id}.md\`](reference/generators/${g.id}.md) — ${g.summary}`,
259
+ ),
289
260
  '',
290
- '- `excited` — high energy, "you won\'t believe this", default for hype/product/reveal',
291
- '- `calm` — softer, intimate, default for wellness/skincare/lifestyle',
292
- '- `sassy` — playful, eye-roll, default for "let me tell you" anecdotes',
293
- '- `serious` measured, default for honest reviews / SaaS / B2B',
294
- '- `curious` — thoughtful, leaning-in, default for storytelling',
261
+ ].join('\n');
262
+ }
263
+
264
+ function renderConversationFlow(): string {
265
+ return [
266
+ GENERATED_NOTE,
295
267
  '',
296
- '### Step 4How long?',
268
+ '# Conversation flowMUST READ before any agent-media call',
297
269
  '',
298
- '**Allowed durations: 5, 10, or 15 seconds. ONLY these three.** Default to 10 if unsure. The schema rejects any other value (the worker will reject 6, 8, 12, etc.).',
270
+ '> **CRITICAL:** Output quality is directly tied to how well you collect these inputs. Run the 4 gates in order. Do not skip, combine, or bulk-fire them.',
299
271
  '',
300
- '- `5` single hook, no body. Best for ads with strong intro.',
301
- '- `10` — default. Hook + payoff. Best for organic UGC.',
302
- '- `15` — full mini-story with setup + reveal.',
272
+ '## Director\'s principle: PROPOSE, don\'t interrogate',
303
273
  '',
304
- '### Step 5 Voice direction (optional, but improves quality)',
274
+ 'Pre-fill what you can infer from the prompt and ask the user to confirm or red-line it. Do not hand them a blank form. The pipeline will fill remaining gaps, but better user input produces better portrait, sheet, wireframe, and video outputs.',
305
275
  '',
306
- 'Ask: *"Any voice direction? E.g. \'whisper\', \'gen-z deadpan\', \'NYC accent\', \'sleepy morning voice\'. Skip if you\'re happy with the default."*',
276
+ '## The 4 gates (in order, one message each)',
307
277
  '',
308
- 'Pass as `--voice-brief "<text>"`. Max 240 chars. If the character has a saved `voice_brief`, it\'s used unless this is set.',
278
+ '### Gate 1 Confirm script or action',
309
279
  '',
310
- '### Step 6 Subtitles',
280
+ 'If the clip has speech, confirm the script verbatim. The script is spoken as-is.',
311
281
  '',
312
- 'Subs are ON by default in Hormozi style. Offer alternatives only if asked.',
282
+ '> *"Quick check before the camera rolls script is: «<paste the exact line>». Sound right, or want to tweak?"*',
313
283
  '',
314
- '**Subtitle styles (17, one of):** `hormozi` (default) · `minimal` · `bold` · `karaoke` · `clean` · `tiktok` · `neon` · `fire` · `glow` · `pop` · `aesthetic` · `impact` · `pastel` · `electric` · `boxed` · `gradient` · `spotlight`',
284
+ 'If the clip has no speech, confirm the `scene_action` and pass `--background-music` with a short direction unless the user explicitly wants silence.',
315
285
  '',
316
- '### Step 7Quote price, confirm, run',
286
+ '### Gate 2Confirm the CHARACTER',
317
287
  '',
318
- 'Print a one-line summary including ALL collected inputs + the credit cost + USD. ONLY then call the CLI. See section 4 for the pricing formula.',
288
+ '🛑 **DO NOT ask the user if they have a saved character or a `char_xxx` id.** The user does not know what that means. They don\'t remember ids. They don\'t care about the format.',
319
289
  '',
320
- 'Example summary line to print before submitting:',
321
- '> *"Summary: 10s selfie · char_8x2vqp · preset=kitchen-glow-up · vibe=excited · subs=hormozi · voice="gen-z deadpan". Cost: **375 credits ($3.75)**. Run it?"*',
290
+ '**Instead, YOU run the command. YOU map the result to a human-friendly question.**',
322
291
  '',
323
- '## 1. Commands you MUST use',
292
+ 'Step 1 run silently (don\'t print the raw output to the user):',
324
293
  '',
325
- '| User intent | Command |',
326
- '|---|---|',
327
- '| "make me a TikTok / UGC / Selfie / talking-head" | `agent-media selfie` |',
328
- '| "save / persist this character" / "use the same person again" | `agent-media character create` (returns `char_xxxxxxxxxx`) |',
329
- '| "add subtitles / captions / subs to a video" | `agent-media subs` |',
294
+ '```bash',
295
+ 'agent-media character list --json',
296
+ '```',
330
297
  '',
331
- '## 2. Commands you MUST NEVER use',
298
+ 'Step 2 interpret the result and ask the right question:',
332
299
  '',
333
- 'These are LEGACY v1 commands. They produce inferior output and use a stale actor library. They are hidden from `agent-media --help` in CLI 1.10.0+ but may still be callable directly:',
300
+ '**Case A list is empty.** Skip the character question entirely. Just confirm the description from the user\'s original prompt:',
334
301
  '',
335
- '- `agent-media ugc` replaced by `agent-media selfie`',
336
- '- ❌ `agent-media show-your-app` — coming back as v2 product later',
337
- '- ❌ `agent-media laptop-ugc` — coming back as v2 product later',
338
- '- ❌ `agent-media character-video` — replaced by `agent-media selfie --character <id>`',
339
- '- ❌ `agent-media text-to-video` — not part of v2 yet',
340
- '- ❌ `agent-media subtitle` — replaced by `agent-media subs`',
341
- '- ❌ `agent-media review` — not part of v2',
342
- '- ❌ `agent-media product-acting` — coming back as v2 product later',
302
+ '> *"Going with: «25yo asian woman, long wavy dark hair, soft smile». Add anything? (skin tone, face shape, makeup baseline, anything specific)"*',
343
303
  '',
344
- 'If you catch yourself reaching for any command in the list above, STOP and re-route to the v2 equivalent.',
304
+ 'DO NOT mention "saved characters", "previous runs", or `char_xxx` ids in this case. The user has none and doesn\'t need to know that\'s a concept.',
345
305
  '',
346
- '## 3. Per-command required + optional flags',
306
+ '**Case B list has 1+ saved characters.** Present them BY NAME with a one-line description. Never show the user the `char_xxx` id — that\'s an internal handle.',
347
307
  '',
348
- '### `agent-media selfie` (cheat-sheet)',
308
+ '> *"You\'ve made a few characters before — want to reuse one, or generate a new one for this?"*',
309
+ '> *"• **Sofia** — 25yo asian woman, long wavy dark hair (made 3d ago)"*',
310
+ '> *"• **Aiko** — 30yo japanese woman, bob cut (made 1w ago)"*',
311
+ '> *"• **Marcus** — 28yo black man, locs (made 2w ago)"*',
312
+ '> *"Reply with a name (e.g. `Sofia`) or say `new`."*',
349
313
  '',
350
- '| Flag | Required | Allowed values | Default |',
351
- '|---|---|---|---|',
352
- '| `--description "..."` | ✓ (unless `--character`) | 8-400 chars describing the person | — |',
353
- '| `--character <char_id>` | OR | `char_xxxxxxxxxx` (saved character) | — |',
354
- '| `--photo <file\\|url>` | optional | only when user gives an exact-person reference photo | — |',
355
- '| `--script "..."` | ✓ | 4-600 chars | — |',
356
- '| `--preset <name>` | | one of 20 (see Step 3) | `bedroom-morning-ritual` |',
357
- '| `--vibe <name>` | | `excited\\|calm\\|sassy\\|serious\\|curious` | `excited` |',
358
- '| `--duration <n>` | | **`5` \\| `10` \\| `15`** | `10` |',
359
- '| `--voice-brief "..."` | | 4-240 chars | (none / character default) |',
360
- '| `--subs-style <name>` | | one of 17 (see Step 6) | `hormozi` |',
361
- '| `--no-subs` | | flag | (subs on) |',
314
+ 'When the user replies "Sofia", YOU map "Sofia" the matching `char_xxx` id internally from the list output. Never ask the user to type the id.',
362
315
  '',
363
- '### `agent-media character create`',
316
+ '🛑 **NEVER auto-pick.** Even if there\'s only one saved character. Even if it "looks like a match" for the prompt. Wait for the user to name the one they want, or say "new".',
364
317
  '',
365
- '| Flag | Required | Notes |',
366
- '|---|---|---|',
367
- '| `--name <slug>` | ✓ | lowercase, hyphens, e.g. `sofia` |',
368
- '| `--description "..."` | ✓ | free text — age, look, vibe. agent-media generates the portrait from this. |',
369
- '| `--photo <file\\|url>` | optional | ONLY when the user wants an exact real-person likeness. Otherwise omit. |',
370
- '| `--voice-brief "..."` | | default voice direction baked into character |',
371
- '| `--preset-default <name>` | | preset to use when this character runs selfie |',
318
+ '**For "new" (or empty-list case):** confirm the description:',
372
319
  '',
373
- 'Returns `char_xxxxxxxxxx`copy and reuse it.',
320
+ '> *"Got it new character. Going with: «<echo description»? Add anything?"*',
374
321
  '',
375
- '### `agent-media subs`',
322
+ '**Default to description-only when creating new.** agent-media generates the character image from text — no photo required. Only ask for a photo if the user explicitly says "use THIS person" and provides one.',
376
323
  '',
377
- '| Flag | Required | Notes |',
378
- '|---|---|---|',
379
- '| `--video <url>` | ✓ | publicly-fetchable mp4 URL |',
380
- '| `--style <name>` | | one of 17 (see Step 6). Default: `hormozi`. |',
381
- '| `--transcript "..."` | | skip Whisper if you already have the exact words |',
382
- '| `--language <code>` | | ISO code (`en`, `es`, `pt`, `fr`, …) |',
324
+ 'Once the user picks a name OR confirms a new description, move to Gate 3. Pass the resolved character to the selfie call as `--character char_xxx` (saved) OR `--description "..."` (new).',
383
325
  '',
384
- '## 4. PRICING FORMULA do not improvise',
326
+ '### Gate 3DIRECTOR\'S BRIEF',
385
327
  '',
386
- '**1 credit = $0.01 USD. Period.** Never quote a price without using this conversion.',
328
+ 'This is where most quality is decided. In one message, propose a complete brief with sensible defaults. The user replies `y` to accept all, or overrides individual lines.',
387
329
  '',
388
- 'Selfie cost = `75 base + 30 × seconds`:',
330
+ 'Important: the current Selfie API does **not** accept `--preset`, `--vibe`, or `--voice-brief`. Put visual details into `--description`; put motion, prop handling, product demos, turns, outfit checks, dances, walking, or non-default behavior into `--scene-action`.',
389
331
  '',
390
- '| Duration | Credits | USD |',
391
- '|---|---:|---:|',
392
- '| 5s | 225 | **$2.25** |',
393
- '| 10s | 375 | **$3.75** |',
394
- '| 15s | 525 | **$5.25** |',
332
+ '**A. Intent + Performance**',
395
333
  '',
396
- 'Character create = 27 credits = **$0.27** (one-shot).',
397
- 'Subtitle = `0 base + 3 × seconds` (a 10s clip = 30 credits = **$0.30**).',
334
+ '- **Intent / use-case** paid ad, organic post, honest review, storytime, unboxing, product demo, etc.',
335
+ '- **Delivery** natural, excited, calm, serious, playful, skeptical, warm, etc. This is descriptive only; it goes into the prompt, not a CLI flag.',
336
+ '- **Script / speech** — exact line if spoken; no invented dialogue.',
398
337
  '',
399
- 'If you find yourself about to print any USD value not in this table or derivable by `credits / 100`, STOP. Recompute.',
338
+ '**B. Scene + Look**',
400
339
  '',
401
- '## 5. Output handling',
340
+ '- **Setting** real-world location, time of day, background details.',
341
+ '- **Lighting** — natural window light, soft bedroom daylight, warm evening lamp, etc.',
342
+ '- **Framing** — close-up, medium close-up, medium, or wide/full-body when outfit/action matters.',
343
+ '- **Wardrobe / hair / makeup** — include only useful visual details.',
344
+ '- **Props + action** — product held, shown, sprayed, opened, worn, pointed at, demonstrated, etc. This should become `--scene-action`.',
402
345
  '',
403
- 'Every command returns a job id. Poll until terminal:',
346
+ '**C. Output**',
404
347
  '',
405
- '```bash',
406
- 'agent-media status <job-id>',
407
- '```',
348
+ '- **Platform / aspect** — Selfie outputs 9:16 vertical for TikTok/Reels/Shorts.',
349
+ '- **Subtitles** — on by default; pass `--subtitles false` if the user says no subs/captions.',
350
+ '- **Background music** — pass only when requested or when there is no script.',
408
351
  '',
409
- 'When `status: "completed"`, the response carries `video_url`. Print the FULL url to the user verbatim — do not abbreviate.',
352
+ '**Exact template to use:**',
410
353
  '',
411
- 'Sync mode (blocking): add `--sync` to selfie/subs and the CLI will poll for you and print the URL when done.',
354
+ '> *"Here\'s the shot I\'d direct reply `y` to lock all, or override individual lines:*',
355
+ '>',
356
+ '> ***A. Intent + Performance***',
357
+ '> *• **Intent:** `[organic product demo]`*',
358
+ '> *• **Delivery:** `[warm, confident, conversational]`*',
359
+ '> *• **Script:** `[paste exact script]`*',
360
+ '>',
361
+ '> ***B. Scene + Look***',
362
+ '> *• **Setting:** `[bright bedroom near a wooden dresser]`*',
363
+ '> *• **Lighting:** `[warm late-morning window light]`*',
364
+ '> *• **Framing:** `[medium, enough room for product and outfit action]`*',
365
+ '> *• **Wardrobe / hair:** `[cream jacket over fitted top, loose blonde waves]`*',
366
+ '> *• **Prop + action:** `[frosted perfume bottle — show label, spray wrist, remove jacket tastefully, turn once, face camera again]`*',
367
+ '>',
368
+ '> ***C. Output***',
369
+ '> *• **Platform / aspect:** `[TikTok / Reels / Shorts — 9:16]`*',
370
+ '> *• **Subtitles:** `[on]`*',
371
+ '> *• **Background music:** `[none, dialogue only]`*',
372
+ '>',
373
+ '> *`y` to lock, or tell me what to change (e.g. "wardrobe to silk robe, no subs")."*',
412
374
  '',
413
- '### For `agent-media character create`',
375
+ 'When the user accepts, build `--description` from identity + look, and build `--scene-action` from the setting + action + prop interaction. Example:',
414
376
  '',
415
- 'Required: `--photo <file|url>`, `--name <slug>`, `--description "..."`.',
416
- 'Optional: `--voice-brief`, `--preset`.',
377
+ '- `--description "28yo fit blonde woman, stylish natural fragrance UGC creator, cream jacket over fitted white top, loose blonde waves, bright bedroom daylight"`',
378
+ '- `--scene-action "standing near a dresser, holding a frosted perfume bottle, showing the label and cap, spraying her wrist, smiling while talking, removing jacket tastefully, turning once, then facing camera again"`',
417
379
  '',
418
- 'Cost: 27 credits ($0.27). Confirm.',
380
+ '### Gate 4 DURATION + script-pacing check',
419
381
  '',
420
- '### For `agent-media subs`',
382
+ '🛑 **Compute the script-to-duration math BEFORE asking, and propose the right duration.** A natural-paced TikTok talking head delivers **2-4 words per second**. If you mismatch script length and duration, Seedance fills the empty time with garbage/nonsense audio (it has to generate audio for the full clip — silence isn\'t free).',
421
383
  '',
422
- 'Required: `--video <url>`.',
423
- 'Optional: `--style` (default `hormozi`, one of 17), `--transcript` (skip Whisper), `--language`.',
384
+ '**Sizing rules:**',
424
385
  '',
425
- 'Cost: ~24 credits / 8s clip. Confirm.',
386
+ '| Duration | Sweet-spot script length |',
387
+ '|---|---:|',
388
+ '| 5s | 10-20 words (single hook, 1 punchy sentence) |',
389
+ '| 10s | 20-40 words (default UGC, 2-3 sentences) |',
390
+ '| 15s | 30-60 words (mini-story, setup + reveal) |',
426
391
  '',
427
- '## 4. Output handling',
392
+ '**The script you collected at Gate 1 — count its words and propose the matching duration:**',
428
393
  '',
429
- 'Every command returns a job id. Poll until terminal:',
394
+ '> *"Your script is **10 words**. That\'s a clean fit for a **5s clip** — at 10s Seedance would have to fill the extra 5s with filler audio. Going with 5s, or want me to lengthen the script for a 10s version?"*',
430
395
  '',
431
- '```bash',
432
- 'agent-media status <job-id>',
433
- '```',
396
+ 'If the requested duration does not fit, propose either a different duration or a revised script/action plan. Do not invent extra spoken words without approval.',
434
397
  '',
435
- 'When status is `completed`, the final mp4 URL is printed. Show it to the user. Do not summarize or shorten it.',
398
+ 'Allowed durations: `5`, `10`, `15` only. The schema rejects 6, 8, 12, etc.',
436
399
  '',
437
- '---',
400
+ '## After all 4 gates',
438
401
  '',
439
- '# agent-media v2 Selfie + Characters',
402
+ '1. Echo the resolved inputs in ONE line: *"Got it: 10s bright-bedroom selfie · cream top · hair-oil bottle action. Running."*',
403
+ '2. Call the CLI:',
404
+ ' ```bash',
405
+ ' agent-media selfie \\',
406
+ ' --description "28yo fit blonde woman, stylish natural fragrance UGC creator, cream jacket over fitted white top, loose blonde waves, bright bedroom daylight" \\',
407
+ ' --script "I keep getting DMs about my hair oil routine" \\',
408
+ ' --scene-action "standing near a dresser, holding an amber hair-oil bottle and scrunching one curl mid-line" \\',
409
+ ' --duration 10',
410
+ ' ```',
411
+ '3. If you need to show progress, poll `agent-media status <job_id> --json` about every 20-30 seconds. Open/show each new URL as soon as it appears: `portrait_url`, `character_sheet_url`/`sheet_url`, `wireframe_url`, then `video_url`.',
440
412
  '',
441
- 'The v2 surface ships two generators today: **Selfie** (a 9:16 TikTok-style video of an AI person talking to camera) and **character_create** (persist an AI character so subsequent Selfies stay on-model). When the next v2 product lands (Product-in-hands), it appears here automatically — this file is generated from `packages/schema/src/v2/generators.ts`.',
413
+ '## "Just run it" / skip-the-gates case',
442
414
  '',
443
- '## When to use this skill',
415
+ 'If the user explicitly says *"just run it"*, *"use defaults"*, *"don\'t ask, fire"* — acknowledge the trade-off explicitly:',
444
416
  '',
445
- 'Trigger phrases:',
446
- '- "make me a TikTok / Selfie / UGC video"',
447
- '- "have an AI person say …"',
448
- '- "create a character / actor / persona for reuse"',
449
- '- "use the same person across multiple videos"',
417
+ '> *"Heads up: skipping the brief means I infer everything from your one-line prompt. Output will be generic. Confirm or want to do the brief?"*',
450
418
  '',
451
- '## Setup',
419
+ 'If they confirm, infer the missing details, use `duration=10` unless the script length clearly needs 5s or 15s, and pass a concise `--scene-action` when the prompt includes product handling or body movement.',
452
420
  '',
453
- '```bash',
454
- 'npm install -g agent-media-cli',
455
- 'agent-media login # opens browser; pastes ma_xxx into ~/.agent-media',
456
- '```',
421
+ '## DO NOT ask about cost or credits',
457
422
  '',
458
- 'Or use the SDK directly:',
423
+ 'There is no 5th gate about pricing. The API debits internally and allows a soft overdraft so generations never get blocked. Never quote credit numbers or USD to the user — point them at <https://agent-media.ai/pricing> if they ask.',
459
424
  '',
460
- '```ts',
461
- "import { AgentMedia } from '@agentmedia/sdk';",
462
- "const client = new AgentMedia({ apiKey: process.env.AGENT_MEDIA_API_KEY! });",
463
- "const job = await client.v2.createCharacter({ photo_url: '…', display_name: 'sofia', description: '…' });",
464
- "const done = await client.v2.runUntilDone(Promise.resolve(job));",
465
- '```',
425
+ '## Anti-patterns — never do these',
466
426
  '',
467
- '## Generators',
427
+ '- ❌ Calling `agent-media selfie` without running all 4 gates.',
428
+ '- ❌ Asking the 4 gates as one giant message — they\'re sequential, one per turn.',
429
+ '- ❌ Skipping Gate 3 (the director\'s brief). That\'s the gate that controls quality. Without it the output looks generic.',
430
+ '- ❌ Asking blank questions ("what scene?") instead of proposing defaults ("here\'s the scene I\'d use — confirm?").',
431
+ '- ❌ **Auto-picking a character from `agent-media character list`.** Even if there\'s only one, even if it\'s the "most recent" — you MUST show the user the list and wait for them to explicitly pick the id or say "new". Picking on their behalf wastes credits on the wrong person.',
432
+ '- ❌ Forgetting to forward `subtitles: true` (or `--subtitles true`) on the selfie call when the user accepted the brief. The default is on, but defaults only fire if you don\'t override — be explicit.',
433
+ '- ❌ **Defaulting to subtitles ON when the user explicitly says "no subs".** If the user\'s prompt or any Gate-3 reply contains "no subs", "without subtitles", "no captions", or similar — the call MUST include `--subtitles false` (CLI) or `subtitles: false` (REST). Failure mode: a subtitled video gets shipped against the user\'s wishes + the Whisper transcription may capture model garbage and burn it as text.',
434
+ '- ❌ **Mismatching script length and duration** (e.g. 10-word script + 15s duration without enough visual action). Normal speech is 2-4 words/sec. Size duration to fit the script and action plan.',
435
+ '- ❌ Passing removed flags such as `--preset`, `--vibe`, `--voice-brief`, or `--sync` to the current v2 Selfie CLI.',
436
+ '- ❌ Waiting silently until the final video when intermediate URLs are available. Surface portrait, sheet, wireframe, and final video as each completes.',
437
+ '- ❌ Asking for a photo when the user only gave a text description.',
438
+ '- ❌ Suggesting a duration not in {5, 10, 15}.',
439
+ '- ❌ **Mentioning credit cost, USD, or pricing to the user.** The API handles billing transparently. If asked about cost, point at <https://agent-media.ai/pricing>.',
440
+ '- ❌ Falling back to `agent-media ugc` or any v1 command if v2 errors. Surface the error to the user instead.',
468
441
  '',
469
- generatorBlocks,
470
- '## Recommended flow (multi-clip from one character)',
442
+ ].join('\n');
443
+ }
444
+
445
+ function renderSubtitleStyles(): string {
446
+ const SUBTITLE_STYLES = [
447
+ 'hormozi', 'minimal', 'bold', 'karaoke', 'clean', 'tiktok', 'neon', 'fire',
448
+ 'glow', 'pop', 'aesthetic', 'impact', 'pastel', 'electric', 'boxed',
449
+ 'gradient', 'spotlight',
450
+ ];
451
+ const HINT: Record<string, string> = {
452
+ hormozi: 'Default. Big yellow caps. "Self-help" energy.',
453
+ minimal: 'Small, white, subtle. Tasteful.',
454
+ bold: 'Heavy serif. High contrast.',
455
+ karaoke: 'Word-by-word highlight in sync with audio.',
456
+ clean: 'Sans-serif, generous tracking.',
457
+ tiktok: 'Classic TikTok auto-caption look.',
458
+ neon: 'Glowing pink/cyan. Synthwave.',
459
+ fire: 'Orange/red gradient. Hype.',
460
+ glow: 'White with soft halo.',
461
+ pop: 'Bubblegum. Playful.',
462
+ aesthetic: 'Wispy, lowercase. Lifestyle.',
463
+ impact: 'All-caps Impact font. Meme energy.',
464
+ pastel: 'Soft pinks/blues.',
465
+ electric: 'Blue glow + emphasis bursts.',
466
+ boxed: 'Black box behind text.',
467
+ gradient: 'Color gradient across each line.',
468
+ spotlight: 'Faded background, highlighted current word.',
469
+ };
470
+ return [
471
+ GENERATED_NOTE,
471
472
  '',
472
- '1. **Create the character once** with `character create`. Returns `char_xxxxxxxxxx`.',
473
- '2. **Reuse that id** for every subsequent Selfie — same face, same voice, same seed.',
474
- '3. If the user wants a different *look*, make a new character; don\'t mutate the existing one.',
473
+ '# Subtitle styles',
475
474
  '',
476
- '```bash',
477
- "ID=$(agent-media character create --photo me.png --name sofia \\",
478
- ' --description "25, asian, long wavy dark hair, casual confident" --quiet)',
475
+ 'Pass via `--style <name>` on `agent-media subs` or `--subs-style <name>` on `agent-media selfie`. Default: `hormozi`.',
479
476
  '',
480
- 'agent-media selfie --character $ID --script "Got my first 100 customers in 30 days." --preset desk-wfh-quick-pitch',
481
- 'agent-media selfie --character $ID --script "Here\'s how I did it." --preset bedroom-morning-ritual',
482
- '```',
477
+ '| Style | Look |',
478
+ '|---|---|',
479
+ SUBTITLE_STYLES.map((s) => `| \`${s}\` | ${HINT[s] ?? ''} |`).join('\n'),
483
480
  '',
484
- '## Shot grammar (Selfie)',
481
+ ].join('\n');
482
+ }
483
+
484
+ function renderRealismRubric(): string {
485
+ return [
486
+ GENERATED_NOTE,
485
487
  '',
486
- '`--preset` accepts one of:',
488
+ '# Realism rubric (internal guard)',
487
489
  '',
488
- V2_SHOT_PRESETS.map((p) => `- \`${p}\``).join('\n'),
490
+ 'The pipeline scaffolds prompts against this 9-point rubric. You usually don\'t need to think about it — but if a user complains about "fake-looking" output, this is what the pipeline is enforcing:',
489
491
  '',
490
- 'Or pass `--preset custom-scene:"<your scene description>"` for an ad-hoc setup.',
492
+ '1. Real-camera optics focal length, depth-of-field, microcatchlights',
493
+ '2. Skin texture — pores, sebum, asymmetry, no Photoshop smoothing',
494
+ '3. Hair physics — flyaways, shine, natural fall',
495
+ '4. Eye direction — meets camera, no dead-stare',
496
+ '5. Lighting — natural sources, motivated highlights, no ring-light halo',
497
+ '6. Wardrobe wear — wrinkles, layering, lived-in fabric',
498
+ '7. Background — believable depth, props that match the scene',
499
+ '8. Pose — neutral spine, natural hand position, no AI-mannequin stiffness',
500
+ '9. Color cast — daylight white-balance, no orange tint',
491
501
  '',
492
- '## Vibes',
502
+ 'If the output violates any of these, raise an issue with the job_id — the rubric is enforced at Stage A (portrait gen) and Stage B (character sheet).',
493
503
  '',
494
- V2_VIBES.map((v) => `- \`${v}\``).join('\n'),
504
+ ].join('\n');
505
+ }
506
+
507
+ function renderErrors(): string {
508
+ return [
509
+ GENERATED_NOTE,
495
510
  '',
496
- '## Realism rubric',
511
+ '# Common errors + fixes',
497
512
  '',
498
- 'Every Selfie clip is composed with these constraints baked into the prompt:',
513
+ '## CLI',
499
514
  '',
500
- '1. Skin micro-detail visible — pores, freckles, oil sheen, baby hairs at hairline.',
501
- '2. Hands always doing something — hair-touch, strap-fix, product hold.',
502
- '3. Mouth caught mid-syllable when talking, not closed.',
503
- '4. Eyes slightly off-center to camera, not a dead stare.',
504
- '5. Single mixed light source (daylight + warm bulb).',
505
- '6. Real setting (bedroom / kitchen / car / dresser-corner) never plain wall / studio.',
506
- '7. Outfit plain + matte or satin — never patterned or logo\'d.',
507
- '8. Hair long, brushed, in motion.',
508
- '9. Product (if any) held mid-chest, ~25° tilt.',
515
+ '| Error | Fix |',
516
+ '|---|---|',
517
+ '| `ERR_MODULE_NOT_FOUND: @agentmedia/schema` | You\'re on an old CLI. Run `npm install -g agent-media-cli@latest`. |',
518
+ '| `Not authenticated. Run agent-media login first.` | API key missing. Run `agent-media login`. |',
519
+ '| `LOGIN_TIMEOUT` | Browser didn\'t complete OAuth in time. Re-run `agent-media login`. |',
520
+ '| `DEPRECATED v1 command: agent-media ugc` | You called a legacy command. Switch to `agent-media selfie`. |',
509
521
  '',
510
- 'You don\'t need to repeat these in the script — they\'re always applied.',
522
+ '## API',
511
523
  '',
512
- '## Error handling',
524
+ '| Code | Meaning | Fix |',
525
+ '|---|---|---|',
526
+ '| `VALIDATION_ERROR` | Input body failed schema. Check the `issues` array in the response. | Adjust args to match the input schema. |',
527
+ '| `UNAUTHORIZED` | Bearer token missing or invalid. | Re-run `agent-media login`. |',
528
+ '| `INSUFFICIENT_CREDITS` | Not enough credits on the account. | Run `agent-media subscribe` to top up. |',
529
+ '| `WORKER_NOT_CONFIGURED` | Server-side misconfig — should not normally occur. | Ping support. |',
530
+ '| `DATABASE_ERROR` | Server insert failed (often missing models row). | Ping support, report the job request. |',
513
531
  '',
514
- '| Error code | What it means |',
515
- '|---|---|',
516
- '| `VALIDATION_ERROR` | Inputs failed Zod validation. Look at `error.issues`. |',
517
- '| `INSUFFICIENT_CREDITS` | User\'s balance is below the quote. Tell them the exact amount needed. |',
518
- '| `MISSING_CHARACTER_INPUT` | Selfie was called without either `--character` or `--photo + --description`. |',
519
- '| `AMBIGUOUS_CHARACTER_INPUT` | Both `--character` and `--photo` were passed. Pick one. |',
520
- '| `JOB_FAILED` | Worker reported failure. `error_message` carries the reason. |',
521
- '| `POLL_TIMEOUT` | Job didn\'t complete within 30 minutes. Surface the job id; it may still finish. |',
532
+ ].join('\n');
533
+ }
534
+
535
+ function renderGenerator(g: V2GeneratorRecord): string {
536
+ const status = g.status === 'beta' ? ' · _beta_' : '';
537
+ const examples = (g.cli?.examples ?? []) as readonly string[];
538
+ return [
539
+ GENERATED_NOTE,
540
+ '',
541
+ `# \`agent-media ${g.cli?.command ?? g.id}\`${status}`,
542
+ '',
543
+ g.summary,
544
+ '',
545
+ '## When to use',
546
+ '',
547
+ g.description,
548
+ '',
549
+ '## CLI',
550
+ '',
551
+ examples.length
552
+ ? '```bash\n' + examples.join('\n') + '\n```'
553
+ : `\`agent-media ${g.cli?.command ?? g.id} --help\``,
554
+ '',
555
+ '## MCP tool',
556
+ '',
557
+ g.mcp ? `\`${g.mcp.toolName}\`` : '_Not exposed as an MCP tool._',
558
+ '',
559
+ '## REST',
522
560
  '',
523
- '## When NOT to use this skill',
561
+ g.rest ? `\`${g.rest.method} ${g.rest.path}\`` : '_Not exposed via REST._',
524
562
  '',
525
- '- The user has an EXISTING legacy job they want to check or modify — use the v1 commands (`agent-media status`, `agent-media ugc`, etc.).',
526
- '- The user wants a Show-Your-App / Product-Acting / Laptop-UGC clip — those products live in the v1 surface (separate generators, separate skill).',
563
+ '## Input schema',
564
+ '',
565
+ fmtInputSchema(g),
566
+ '',
567
+ '## Related references',
568
+ '',
569
+ g.id === 'selfie'
570
+ ? [
571
+ '- [`../conversation-flow.md`](../conversation-flow.md) — MUST-READ before calling this command',
572
+ '- [`../subtitle-styles.md`](../subtitle-styles.md) — all 17 subtitle styles',
573
+ '- [`../realism-rubric.md`](../realism-rubric.md) — visual-quality guard',
574
+ ].join('\n')
575
+ : g.id === 'character_create'
576
+ ? [
577
+ '- [`../conversation-flow.md`](../conversation-flow.md) — MUST-READ before calling this command',
578
+ '- [`./selfie.md`](./selfie.md) — once you have a `char_…`, use it here',
579
+ ].join('\n')
580
+ : [
581
+ '- [`../subtitle-styles.md`](../subtitle-styles.md) — all 17 styles',
582
+ ].join('\n'),
527
583
  '',
528
584
  ].join('\n');
529
585
  }
530
586
 
587
+ function emitSkillTree(): EmittedFile[] {
588
+ const generators = Object.values(V2_GENERATORS);
589
+ return [
590
+ { relPath: 'SKILL.md', content: renderSkillIndex() },
591
+ { relPath: 'reference/conversation-flow.md', content: renderConversationFlow() },
592
+ { relPath: 'reference/subtitle-styles.md', content: renderSubtitleStyles() },
593
+ { relPath: 'reference/realism-rubric.md', content: renderRealismRubric() },
594
+ { relPath: 'reference/errors.md', content: renderErrors() },
595
+ ...generators.map((g) => ({
596
+ relPath: `reference/generators/${g.id}.md`,
597
+ content: renderGenerator(g),
598
+ })),
599
+ ];
600
+ }
601
+
531
602
  // ── Run ────────────────────────────────────────────────────────────────────
532
603
 
533
604
  function main() {
605
+ // 1. API reference (unchanged — still a single file)
534
606
  mkdirSync(dirname(DOCS_OUT), { recursive: true });
535
- mkdirSync(dirname(SKILL_OUT), { recursive: true });
536
-
537
607
  const docs = renderApiReference();
538
608
  writeFileSync(DOCS_OUT, docs, 'utf8');
609
+ console.log(`✓ wrote ${DOCS_OUT} (${docs.length} bytes)`);
539
610
 
540
- const skill = renderSkill();
541
- writeFileSync(SKILL_OUT, skill, 'utf8');
611
+ // 2. Skill tree (one file per concern)
612
+ mkdirSync(SKILL_DIR, { recursive: true });
613
+ mkdirSync(resolve(SKILL_DIR, 'reference'), { recursive: true });
614
+ mkdirSync(resolve(SKILL_DIR, 'reference/generators'), { recursive: true });
542
615
 
543
- console.log(`✓ wrote ${DOCS_OUT} (${docs.length} bytes)`);
544
- console.log(`✓ wrote ${SKILL_OUT} (${skill.length} bytes)`);
616
+ const files = emitSkillTree();
617
+ let totalBytes = 0;
618
+ for (const f of files) {
619
+ const abs = resolve(SKILL_DIR, f.relPath);
620
+ mkdirSync(dirname(abs), { recursive: true });
621
+ writeFileSync(abs, f.content, 'utf8');
622
+ totalBytes += f.content.length;
623
+ console.log(`✓ wrote ${f.relPath} (${f.content.length} bytes)`);
624
+ }
625
+ console.log(` total: ${files.length} files, ${totalBytes} bytes`);
545
626
  console.log(` generators emitted: ${Object.keys(V2_GENERATORS).length}`);
546
627
  }
547
628