@vibeframe/cli 0.80.0 → 0.81.0

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.
Files changed (2) hide show
  1. package/dist/index.js +834 -479
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -14298,7 +14298,8 @@ function getSetupProviders() {
14298
14298
  key: k.configKey,
14299
14299
  name: k.label,
14300
14300
  env: k.envVar,
14301
- desc: k.setupDescription ?? ""
14301
+ desc: k.setupDescription ?? "",
14302
+ url: k.envExampleUrl
14302
14303
  }));
14303
14304
  }
14304
14305
  function getAllApiKeys() {
@@ -14371,7 +14372,11 @@ var init_api_keys = __esm({
14371
14372
  showInSetup: true,
14372
14373
  setupDescription: "Seedance 2.0 video gen ($$, default since v0.57)",
14373
14374
  envExampleComment: "fal.ai API Key (Seedance 2.0 video \u2014 default text/image-to-video since v0.57, Artificial Analysis ELO #2)",
14374
- envExampleUrl: "https://fal.ai/dashboard/keys"
14375
+ envExampleUrl: "https://fal.ai/dashboard/keys",
14376
+ // fal keys are `<key-id>:<key-secret>` (both UUIDs). The colon is the
14377
+ // structural marker — same approach as Kling. Don't pin the UUID shape
14378
+ // since fal could rotate to a different secret format.
14379
+ keyFormat: { prefix: /:/, example: "<key-id>:<key-secret>" }
14375
14380
  });
14376
14381
  defineApiKey({
14377
14382
  configKey: "xai",
@@ -14432,6 +14437,10 @@ var init_api_keys = __esm({
14432
14437
  // not prompted in setup wizard — internal upload host
14433
14438
  envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
14434
14439
  envExampleUrl: "https://api.imgbb.com/",
14440
+ // ImgBB issues 32-character lowercase hex keys. Soft-warn if a pasted
14441
+ // key doesn't match — covers the common "I copied my Kling key by
14442
+ // mistake" failure mode.
14443
+ keyFormat: { prefix: /^[a-f0-9]{32}$/, example: "32-char hex" },
14435
14444
  // ImgBB has no provider class (envvar-only); doctor still shows what it
14436
14445
  // unlocks at the apiKey level.
14437
14446
  commandsUnlocked: [
@@ -17263,18 +17272,18 @@ ${transcriptWithTimestamps}`
17263
17272
  return emptyViralAnalysis("Analysis failed");
17264
17273
  }
17265
17274
  }
17266
- async function suggestPlatformCuts(api, transcriptSegments, viralAnalysis, platform, clips) {
17267
- const emptyResult = { platform: platform.id, segments: [], totalDuration: 0 };
17275
+ async function suggestPlatformCuts(api, transcriptSegments, viralAnalysis, platform3, clips) {
17276
+ const emptyResult = { platform: platform3.id, segments: [], totalDuration: 0 };
17268
17277
  const transcriptWithTimestamps = transcriptSegments.map((seg) => `[${seg.startTime.toFixed(1)}s - ${seg.endTime.toFixed(1)}s] ${seg.text}`).join("\n");
17269
17278
  const clipsInfo = clips.map((c) => `[${c.id}] ${c.startTime.toFixed(1)}s - ${(c.startTime + c.duration).toFixed(1)}s (${c.duration.toFixed(1)}s)`).join("\n");
17270
- const systemPrompt = `You are an expert video editor optimizing content for ${platform.name}.
17279
+ const systemPrompt = `You are an expert video editor optimizing content for ${platform3.name}.
17271
17280
 
17272
17281
  Platform requirements:
17273
- - Aspect ratio: ${platform.aspectRatio}
17274
- - Maximum duration: ${platform.maxDuration}s
17275
- - Ideal duration: ${platform.idealDuration.min}-${platform.idealDuration.max}s
17276
- - Needs strong hook: ${platform.features.hook ? "Yes" : "No"}
17277
- - Needs captions: ${platform.features.captions ? "Yes" : "No"}
17282
+ - Aspect ratio: ${platform3.aspectRatio}
17283
+ - Maximum duration: ${platform3.maxDuration}s
17284
+ - Ideal duration: ${platform3.idealDuration.min}-${platform3.idealDuration.max}s
17285
+ - Needs strong hook: ${platform3.features.hook ? "Yes" : "No"}
17286
+ - Needs captions: ${platform3.features.captions ? "Yes" : "No"}
17278
17287
 
17279
17288
  Previous analysis found:
17280
17289
  - Hook strength: ${viralAnalysis.hookStrength}%
@@ -17290,7 +17299,7 @@ Your task:
17290
17299
 
17291
17300
  Respond with JSON only:
17292
17301
  {
17293
- "platform": "${platform.id}",
17302
+ "platform": "${platform3.id}",
17294
17303
  "segments": [
17295
17304
  {"sourceClipId": "clip-id", "startTime": 0, "endTime": 45.5, "priority": 1.0}
17296
17305
  ],
@@ -17301,7 +17310,7 @@ Respond with JSON only:
17301
17310
  system: systemPrompt,
17302
17311
  messages: [{
17303
17312
  role: "user",
17304
- content: `Optimize this video for ${platform.name}:
17313
+ content: `Optimize this video for ${platform3.name}:
17305
17314
 
17306
17315
  ## Transcript:
17307
17316
  ${transcriptWithTimestamps}
@@ -17997,10 +18006,10 @@ Instruction: ${instruction}`
17997
18006
  }
17998
18007
  return analyzeViralPotential(this.api, transcriptSegments, projectMeta, targetPlatforms);
17999
18008
  }
18000
- async suggestPlatformCuts(transcriptSegments, viralAnalysis, platform, clips) {
18009
+ async suggestPlatformCuts(transcriptSegments, viralAnalysis, platform3, clips) {
18001
18010
  if (!this.api)
18002
- return { platform: platform.id, segments: [], totalDuration: 0 };
18003
- return suggestPlatformCuts(this.api, transcriptSegments, viralAnalysis, platform, clips);
18011
+ return { platform: platform3.id, segments: [], totalDuration: 0 };
18012
+ return suggestPlatformCuts(this.api, transcriptSegments, viralAnalysis, platform3, clips);
18004
18013
  }
18005
18014
  async generateViralCaptions(transcriptSegments, style) {
18006
18015
  if (!this.api)
@@ -31526,6 +31535,44 @@ var init_storyboard = __esm({
31526
31535
  }
31527
31536
  });
31528
31537
 
31538
+ // src/utils/open-url.ts
31539
+ import { spawn } from "node:child_process";
31540
+ import { platform } from "node:os";
31541
+ async function openUrl(url) {
31542
+ const { command, args } = resolveOpener(url);
31543
+ if (!command) return;
31544
+ try {
31545
+ const child = spawn(command, args, {
31546
+ detached: true,
31547
+ stdio: "ignore",
31548
+ shell: false
31549
+ });
31550
+ child.on("error", () => {
31551
+ });
31552
+ child.unref();
31553
+ } catch {
31554
+ }
31555
+ }
31556
+ function resolveOpener(url) {
31557
+ switch (platform()) {
31558
+ case "darwin":
31559
+ return { command: "open", args: [url] };
31560
+ case "win32":
31561
+ return { command: "cmd", args: ["/c", "start", "", url] };
31562
+ case "linux":
31563
+ case "freebsd":
31564
+ case "openbsd":
31565
+ return { command: "xdg-open", args: [url] };
31566
+ default:
31567
+ return { command: null, args: [] };
31568
+ }
31569
+ }
31570
+ var init_open_url = __esm({
31571
+ "src/utils/open-url.ts"() {
31572
+ "use strict";
31573
+ }
31574
+ });
31575
+
31529
31576
  // src/utils/tty.ts
31530
31577
  import { createInterface as createInterface2 } from "node:readline";
31531
31578
  import { ReadStream } from "node:tty";
@@ -31579,7 +31626,7 @@ async function prompt2(question) {
31579
31626
  });
31580
31627
  });
31581
31628
  }
31582
- async function promptHidden(question) {
31629
+ async function promptHidden(question, opts = {}) {
31583
31630
  const input = getTTYInputStream();
31584
31631
  return new Promise((resolve64) => {
31585
31632
  process.stdout.write(question);
@@ -31598,6 +31645,8 @@ async function promptHidden(question) {
31598
31645
  input.setRawMode(false);
31599
31646
  process.stdout.write("\n");
31600
31647
  process.exit(1);
31648
+ } else if (opts.openHotkeyUrl && value.length === 0 && (char === "o" || char === "O")) {
31649
+ void openUrl(opts.openHotkeyUrl);
31601
31650
  } else if (char === "\x7F" || char === "\b") {
31602
31651
  if (value.length > 0) {
31603
31652
  value = value.slice(0, -1);
@@ -31681,6 +31730,59 @@ async function promptSelect(_question, options, defaultIndex = 0) {
31681
31730
  return index;
31682
31731
  }
31683
31732
  async function promptConfirm(question, defaultYes = true) {
31733
+ const input = getTTYInputStream();
31734
+ if (typeof input.setRawMode === "function") {
31735
+ return new Promise((resolve64) => {
31736
+ const options = ["Yes", "No"];
31737
+ let selected = defaultYes ? 0 : 1;
31738
+ let renderCount = 0;
31739
+ process.stdout.write(`${question}
31740
+ `);
31741
+ const render = () => {
31742
+ if (renderCount > 0) {
31743
+ process.stdout.write(`\x1B[${options.length}A`);
31744
+ }
31745
+ for (let i = 0; i < options.length; i++) {
31746
+ const marker = i === selected ? "\x1B[36m\u276F\x1B[0m" : " ";
31747
+ const text = i === selected ? `\x1B[1m${options[i]}\x1B[0m` : options[i];
31748
+ process.stdout.write(`\x1B[2K ${marker} ${text}
31749
+ `);
31750
+ }
31751
+ renderCount++;
31752
+ };
31753
+ render();
31754
+ input.setRawMode(true);
31755
+ input.resume();
31756
+ input.setEncoding("utf8");
31757
+ const onData = (char) => {
31758
+ if (char === "\r" || char === "\n") {
31759
+ input.setRawMode(false);
31760
+ input.removeListener("data", onData);
31761
+ resolve64(selected === 0);
31762
+ } else if (char === "") {
31763
+ input.setRawMode(false);
31764
+ process.stdout.write("\n");
31765
+ process.exit(1);
31766
+ } else if (char === "\x1B[A" || char === "\x1B[B" || char === "k" || char === "j") {
31767
+ selected = selected === 0 ? 1 : 0;
31768
+ render();
31769
+ } else if (char === "y" || char === "Y") {
31770
+ selected = 0;
31771
+ input.setRawMode(false);
31772
+ input.removeListener("data", onData);
31773
+ render();
31774
+ resolve64(true);
31775
+ } else if (char === "n" || char === "N") {
31776
+ selected = 1;
31777
+ input.setRawMode(false);
31778
+ input.removeListener("data", onData);
31779
+ render();
31780
+ resolve64(false);
31781
+ }
31782
+ };
31783
+ input.on("data", onData);
31784
+ });
31785
+ }
31684
31786
  const hint = defaultYes ? "(Y/n)" : "(y/N)";
31685
31787
  const answer = await prompt2(`${question} ${hint}: `);
31686
31788
  if (answer === "") return defaultYes;
@@ -31774,6 +31876,7 @@ var ttyStream;
31774
31876
  var init_tty = __esm({
31775
31877
  "src/utils/tty.ts"() {
31776
31878
  "use strict";
31879
+ init_open_url();
31777
31880
  ttyStream = null;
31778
31881
  }
31779
31882
  });
@@ -33531,7 +33634,7 @@ Examples:
33531
33634
  }
33532
33635
  }
33533
33636
  } else if (provider === "runway") {
33534
- const { spawn: spawn2 } = await import("child_process");
33637
+ const { spawn: spawn4 } = await import("child_process");
33535
33638
  const __filename2 = fileURLToPath(import.meta.url);
33536
33639
  const __dirname2 = dirname7(__filename2);
33537
33640
  const scriptPath = resolve23(
@@ -33546,7 +33649,7 @@ Examples:
33546
33649
  const args = [scriptPath, prompt3, "-o", outputPath, "-r", options.ratio || "16:9"];
33547
33650
  spinner2.text = "Generating image with Runway (gemini_2.5_flash)...";
33548
33651
  await new Promise((resolvePromise, reject) => {
33549
- const proc = spawn2("python3", args, {
33652
+ const proc = spawn4("python3", args, {
33550
33653
  env: { ...process.env, RUNWAY_API_SECRET: apiKey },
33551
33654
  stdio: ["ignore", "pipe", "pipe"]
33552
33655
  });
@@ -36906,8 +37009,8 @@ var init_translate_srt = __esm({
36906
37009
  import { existsSync as existsSync21 } from "node:fs";
36907
37010
  import { resolve as resolve27 } from "node:path";
36908
37011
  function detectSystemFont() {
36909
- const platform = process.platform;
36910
- if (platform === "darwin") {
37012
+ const platform3 = process.platform;
37013
+ if (platform3 === "darwin") {
36911
37014
  const candidates = [
36912
37015
  "/System/Library/Fonts/Helvetica.ttc",
36913
37016
  "/System/Library/Fonts/HelveticaNeue.ttc",
@@ -36916,7 +37019,7 @@ function detectSystemFont() {
36916
37019
  for (const f of candidates) {
36917
37020
  if (existsSync21(f)) return f;
36918
37021
  }
36919
- } else if (platform === "linux") {
37022
+ } else if (platform3 === "linux") {
36920
37023
  const candidates = [
36921
37024
  "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
36922
37025
  "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
@@ -36925,7 +37028,7 @@ function detectSystemFont() {
36925
37028
  for (const f of candidates) {
36926
37029
  if (existsSync21(f)) return f;
36927
37030
  }
36928
- } else if (platform === "win32") {
37031
+ } else if (platform3 === "win32") {
36929
37032
  const candidates = [
36930
37033
  "C:\\Windows\\Fonts\\arial.ttf",
36931
37034
  "C:\\Windows\\Fonts\\segoeui.ttf"
@@ -36976,11 +37079,11 @@ async function applyTextOverlays(options) {
36976
37079
  } = await Promise.resolve().then(() => (init_remotion(), remotion_exports));
36977
37080
  const remotionErr = await ensureRemotionInstalled2();
36978
37081
  if (remotionErr) {
36979
- const platform = process.platform;
37082
+ const platform3 = process.platform;
36980
37083
  let hint = "";
36981
- if (platform === "darwin") {
37084
+ if (platform3 === "darwin") {
36982
37085
  hint = "\n\nFix: brew uninstall ffmpeg && brew install ffmpeg\n(The default homebrew formula includes libfreetype)";
36983
- } else if (platform === "linux") {
37086
+ } else if (platform3 === "linux") {
36984
37087
  hint = "\n\nFix: sudo apt install ffmpeg (Ubuntu/Debian)\n or rebuild FFmpeg with --enable-libfreetype";
36985
37088
  }
36986
37089
  return {
@@ -50813,6 +50916,320 @@ var init_detect = __esm({
50813
50916
  }
50814
50917
  });
50815
50918
 
50919
+ // src/commands/_shared/walkthroughs/walkthroughs.ts
50920
+ function loadWalkthrough(topic) {
50921
+ const meta = META[topic];
50922
+ const content = CONTENT[topic];
50923
+ if (!meta || !content) {
50924
+ throw new Error(`Unknown walkthrough topic: ${topic}`);
50925
+ }
50926
+ return {
50927
+ topic,
50928
+ title: meta.title,
50929
+ summary: meta.summary,
50930
+ steps: meta.steps,
50931
+ relatedCommands: meta.relatedCommands,
50932
+ content
50933
+ };
50934
+ }
50935
+ function listWalkthroughs() {
50936
+ return WALKTHROUGH_TOPICS.map((topic) => ({
50937
+ topic,
50938
+ title: META[topic].title,
50939
+ summary: META[topic].summary
50940
+ }));
50941
+ }
50942
+ function isWalkthroughTopic(value) {
50943
+ return typeof value === "string" && WALKTHROUGH_TOPICS.includes(value);
50944
+ }
50945
+ var SCENE_WALKTHROUGH, PIPELINE_WALKTHROUGH, META, CONTENT, WALKTHROUGH_TOPICS;
50946
+ var init_walkthroughs = __esm({
50947
+ "src/commands/_shared/walkthroughs/walkthroughs.ts"() {
50948
+ "use strict";
50949
+ SCENE_WALKTHROUGH = `# Scene authoring with vibe
50950
+
50951
+ A scene project is a directory that is **bilingual**: it works with both
50952
+ \`vibe\` and \`npx hyperframes\`. Each scene is one HTML file with scoped CSS
50953
+ and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
50954
+ at render.
50955
+
50956
+ \`vibe build\` (v0.60+) is the supported one-shot driver from a
50957
+ written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
50958
+ host agent itself authors the per-beat HTML \u2014 no internal LLM call.
50959
+
50960
+ ## Three authoring paths
50961
+
50962
+ | Path | Command | When to use |
50963
+ |---|---|---|
50964
+ | **One-shot (default, v0.60+)** | \`vibe build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
50965
+ | **High-craft (manual)** | \`DESIGN.md\` + local composition rules in your agent | Maximum control: hand-author each scene |
50966
+ | **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
50967
+
50968
+ Recommend \`vibe build\` whenever the user has a STORYBOARD with
50969
+ narration / backdrop intent.
50970
+
50971
+ ## High-craft path
50972
+
50973
+ 1. \`vibe init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
50974
+ \`DESIGN.md\` (palette, typography, motion, transitions) plus the
50975
+ \`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
50976
+ In Plan H this **also installs local composition rules** at the
50977
+ right place for your host (\`.claude/skills/hyperframes/\` for Claude
50978
+ Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
50979
+ \`SKILL.md\` for everyone else).
50980
+ 2. Read \`SKILL.md\` (or the host-specific copy) \u2014 Hyperframes
50981
+ framework rules, motion principles, type system, transition recipes.
50982
+ 3. Read \`DESIGN.md\` \u2014 project-specific palette / typography / motion
50983
+ signature (visual identity hard-gate).
50984
+ 4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
50985
+ using the rules from steps 2 and 3. The skill enforces the visual
50986
+ identity contract \u2014 scenes that contradict DESIGN.md fail lint.
50987
+ 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe render my-promo\`
50988
+ to MP4.
50989
+
50990
+ ## Quick-draft path
50991
+
50992
+ \`\`\`bash
50993
+ vibe init my-promo -r 16:9 -d 30
50994
+ vibe scene add intro --style announcement \\
50995
+ --headline "Ship videos, not clicks"
50996
+ vibe scene lint
50997
+ vibe render my-promo
50998
+ \`\`\`
50999
+
51000
+ \`vibe init\` is **idempotent** \u2014 running it on an existing Hyperframes
51001
+ directory merges \`hyperframes.json\` instead of clobbering it. Safe to
51002
+ invoke on user-provided projects.
51003
+
51004
+ ## Subcommands
51005
+
51006
+ \`\`\`bash
51007
+ # Project flow (top-level \u2014 preferred entry points)
51008
+ vibe init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
51009
+ vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
51010
+ vibe render [<dir>] [--fps 30] [--quality standard] [--format mp4]
51011
+
51012
+ # Lower-level scene authoring
51013
+ vibe scene list-styles [<name>] # list / show vendored visual identities
51014
+ vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
51015
+ vibe scene add <name> --style <preset> [...]
51016
+ vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
51017
+ vibe scene lint [<root>] [--json] [--fix]
51018
+ \`\`\`
51019
+
51020
+ ## Style presets (for \`vibe scene add --style\`)
51021
+
51022
+ - **simple** \u2014 backdrop + bottom caption (default)
51023
+ - **announcement** \u2014 single huge headline, gradient text
51024
+ - **explainer** \u2014 kicker + title + subtitle stack
51025
+ - **kinetic-type** \u2014 words animate in word-by-word
51026
+ - **product-shot** \u2014 corner label + bottom headline + slow zoom
51027
+
51028
+ All presets accept \`--narration <text|file>\`, \`--visuals <prompt>\`,
51029
+ \`--headline\`, \`--kicker\`. With \`--narration\`, scene duration auto-derives
51030
+ from the generated TTS audio.
51031
+
51032
+ ## STORYBOARD-to-MP4 (one command, v0.60+)
51033
+
51034
+ \`\`\`bash
51035
+ vibe init my-promo --visual-style "Swiss Pulse" -d 12
51036
+ # (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
51037
+ vibe build my-promo
51038
+ \`\`\`
51039
+
51040
+ \`vibe build\` reads the STORYBOARD frontmatter + per-beat cues,
51041
+ dispatches TTS + image-gen per beat, then either:
51042
+
51043
+ - **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
51044
+ \`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
51045
+ authors each \`compositions/scene-<id>.html\` itself, then re-invoking
51046
+ \`vibe build\` proceeds to lint + render.
51047
+ - **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
51048
+ Gemini) to compose the HTML, then renders.
51049
+
51050
+ \`VIBE_BUILD_MODE\` env var overrides the auto-resolve.
51051
+
51052
+ ## Lint feedback loop
51053
+
51054
+ \`\`\`bash
51055
+ vibe scene lint --json --fix
51056
+ \`\`\`
51057
+
51058
+ Returns structured findings. The recommended loop: 1) run lint with
51059
+ \`--fix\` (mechanical fixes applied), 2) if \`errorCount > 0\`, edit the
51060
+ scene HTML, 3) re-lint. Cap retries at 3 \u2014 if errors persist, fall back
51061
+ to a template preset (\`vibe scene add <id> --style simple --force\`)
51062
+ and surface the error to the user.
51063
+
51064
+ ## When to use VibeFrame vs raw Hyperframes
51065
+
51066
+ | Task | Tool |
51067
+ |------|------|
51068
+ | Generate narration + image, then author scene | \`vibe scene add\` |
51069
+ | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
51070
+ | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
51071
+ | Render the project | \`vibe render\` (one canonical entry point) |
51072
+ | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
51073
+
51074
+ The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
51075
+ integration on top of Hyperframes' rendering primitives.
51076
+
51077
+ ## Quality checklist before render
51078
+
51079
+ - [ ] \`vibe scene lint\` exits 0 (or only warnings)
51080
+ - [ ] \`vibe doctor\` confirms a usable Chrome (required for render)
51081
+ - [ ] Root \`data-duration\` matches the sum of clip durations
51082
+ - [ ] Aspect ratio in \`vibe.project.yaml\` matches the destination platform
51083
+ `;
51084
+ PIPELINE_WALKTHROUGH = `# YAML pipelines (Video as Code)
51085
+
51086
+ A pipeline is a YAML manifest with steps that reference each other's
51087
+ outputs. \`vibe run pipeline.yaml\` executes them with checkpointing and
51088
+ cost estimation.
51089
+
51090
+ ## Minimal skeleton
51091
+
51092
+ \`\`\`yaml
51093
+ name: promo-video
51094
+ description: 15s product teaser
51095
+ steps:
51096
+ - id: backdrop
51097
+ action: generate-image
51098
+ prompt: "sleek product shot on white background"
51099
+ output: backdrop.png
51100
+ - id: scene
51101
+ action: generate-video
51102
+ image: $backdrop.output # reference previous step output
51103
+ prompt: "slow camera pan"
51104
+ duration: 5
51105
+ output: scene.mp4
51106
+ - id: voice
51107
+ action: generate-tts
51108
+ text: "Meet the new standard."
51109
+ output: voice.mp3
51110
+ - id: final
51111
+ action: compose
51112
+ video: $scene.output
51113
+ audio: $voice.output
51114
+ output: final.mp4
51115
+ \`\`\`
51116
+
51117
+ ## Supported actions
51118
+
51119
+ - \`generate-image\`, \`generate-video\`, \`generate-tts\`, \`generate-music\`,
51120
+ \`generate-sound-effect\`, \`generate-storyboard\`, \`generate-motion\`
51121
+ - \`edit-silence-cut\`, \`edit-jump-cut\`, \`edit-caption\`, \`edit-grade\`,
51122
+ \`edit-reframe\`, \`edit-speed-ramp\`, \`edit-fade\`, \`edit-noise-reduce\`,
51123
+ \`edit-text-overlay\`, \`edit-fill-gaps\`
51124
+ - \`analyze-media\`, \`analyze-video\`, \`analyze-review\`, \`analyze-suggest\`
51125
+ - \`audio-transcribe\`, \`audio-isolate\`, \`audio-voice-clone\`, \`audio-dub\`,
51126
+ \`audio-duck\`
51127
+ - \`detect-scenes\`, \`detect-silence\`, \`detect-beats\`
51128
+ - \`compose\`, \`export\`
51129
+ - \`scene-build\` (Plan H one-shot driver) and \`scene-render\`
51130
+ - \`compose-scenes-with-skills\` (internal-LLM compose pass)
51131
+
51132
+ The full set lives in \`packages/cli/src/pipeline/executor.ts\`.
51133
+
51134
+ ## Variable references
51135
+
51136
+ - \`$<step-id>.output\` \u2014 previous step's output path
51137
+ - \`$<step-id>.result.<field>\` \u2014 structured field from JSON result
51138
+ - \`\${ENV_VAR}\` \u2014 environment variable
51139
+ - Values can be templated: \`"\${SCRIPT_TITLE} - Episode \${EPISODE}"\`
51140
+
51141
+ ## Running
51142
+
51143
+ \`\`\`bash
51144
+ vibe run pipeline.yaml --dry-run # plan + cost estimate, no execution
51145
+ vibe run pipeline.yaml # execute
51146
+ vibe run pipeline.yaml --resume # retry from last successful step
51147
+ vibe run pipeline.yaml --from scene # start at specific step
51148
+ vibe run pipeline.yaml --provider-video kling # override provider
51149
+ \`\`\`
51150
+
51151
+ Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
51152
+
51153
+ ## Authoring tips
51154
+
51155
+ 1. **Start from a tiny YAML** \u2014 keep it in your project directory and run
51156
+ \`vibe run pipeline.yaml --dry-run\` before spending provider budget.
51157
+ 2. **Dry-run first** \u2014 you see estimated cost and resolved variable
51158
+ graph before spending API credits.
51159
+ 3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
51160
+ \`bgm\`) \u2014 they appear in logs and variable refs.
51161
+ 4. **Name outputs** with extensions matching the action (\`.mp4\`, \`.mp3\`,
51162
+ \`.png\`, \`.json\`).
51163
+ 5. **Declare \`budget:\`** on expensive pipelines:
51164
+ \`\`\`yaml
51165
+ budget:
51166
+ tokens: 500_000
51167
+ max_tool_errors: 3
51168
+ cost_usd: 5.00
51169
+ \`\`\`
51170
+ 6. **Split large pipelines** into smaller YAML files and compose via
51171
+ \`action: run-pipeline\` (nested).
51172
+
51173
+ ## Converting ad-hoc shell sessions to pipelines
51174
+
51175
+ When the user has a working shell sequence, extract steps:
51176
+
51177
+ - Each \`vibe ...\` command becomes one step
51178
+ - File outputs become step outputs; downstream \`-i <file>\` references
51179
+ become \`$<id>.output\`
51180
+ - Shared parameters move to a top-level \`defaults:\` section
51181
+ - Wrap the entire chain in a \`name:\` + \`steps:\` skeleton
51182
+
51183
+ The \`compose\` action is the catch-all assembly step (audio mux, video
51184
+ overlay, etc.) \u2014 useful at the tail of a pipeline.
51185
+ `;
51186
+ META = {
51187
+ scene: {
51188
+ title: "Scene authoring with vibe",
51189
+ summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
51190
+ steps: [
51191
+ 'Run `vibe init <dir> --visual-style "<style name>"` to scaffold the project + install local composition rules.',
51192
+ "Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
51193
+ "Read `SKILL.md` for the framework rules and `DESIGN.md` for the visual-identity hard-gate.",
51194
+ "Run `vibe build <dir>`. With an agent host detected, the CLI emits a `needs-author` plan; the host agent authors each `compositions/scene-<id>.html` and re-invokes to render.",
51195
+ "Run `vibe scene lint --fix` to validate, then `vibe render <dir>` to produce the MP4."
51196
+ ],
51197
+ relatedCommands: [
51198
+ "vibe init",
51199
+ "vibe build",
51200
+ "vibe render",
51201
+ "vibe scene list-styles",
51202
+ "vibe scene install-skill",
51203
+ "vibe scene compose-prompts",
51204
+ "vibe scene lint",
51205
+ "vibe scene add"
51206
+ ]
51207
+ },
51208
+ pipeline: {
51209
+ title: "YAML pipelines (Video as Code)",
51210
+ summary: "Author and run reproducible multi-step video workflows",
51211
+ steps: [
51212
+ "Sketch the workflow as YAML \u2014 `name`, `description`, then `steps:` with `id` + `action` + inputs/outputs.",
51213
+ "Reference previous step outputs via `$<step-id>.output` (or `$<step-id>.result.<field>` for structured returns).",
51214
+ "Run `vibe run pipeline.yaml --dry-run` to see the resolved graph + cost estimate before spending API budget.",
51215
+ "Add a `budget:` block (tokens / cost_usd / max_tool_errors) to cap expensive runs.",
51216
+ "Run `vibe run pipeline.yaml` to execute. Failed steps checkpoint to `pipeline.yaml.checkpoint.json`; resume with `--resume`."
51217
+ ],
51218
+ relatedCommands: [
51219
+ "vibe run",
51220
+ "vibe schema --list",
51221
+ "vibe doctor"
51222
+ ]
51223
+ }
51224
+ };
51225
+ CONTENT = {
51226
+ scene: SCENE_WALKTHROUGH,
51227
+ pipeline: PIPELINE_WALKTHROUGH
51228
+ };
51229
+ WALKTHROUGH_TOPICS = ["scene", "pipeline"];
51230
+ }
51231
+ });
51232
+
50816
51233
  // src/pipeline/renderers/chrome.ts
50817
51234
  import { existsSync as existsSync32 } from "node:fs";
50818
51235
  import { homedir as homedir5 } from "node:os";
@@ -51582,6 +51999,126 @@ var init_scene_build = __esm({
51582
51999
  }
51583
52000
  });
51584
52001
 
52002
+ // src/utils/key-live-test.ts
52003
+ var key_live_test_exports = {};
52004
+ __export(key_live_test_exports, {
52005
+ isTestableProvider: () => isTestableProvider,
52006
+ testKey: () => testKey
52007
+ });
52008
+ async function getOk(url, headers, signal) {
52009
+ try {
52010
+ const res = await fetch(url, { method: "GET", headers, signal });
52011
+ if (res.ok) return { ok: true, status: res.status };
52012
+ return {
52013
+ ok: false,
52014
+ status: res.status,
52015
+ message: `${res.status} ${res.statusText || ""}`.trim()
52016
+ };
52017
+ } catch (err) {
52018
+ const msg = err instanceof Error ? err.message : String(err);
52019
+ return { ok: false, message: msg };
52020
+ }
52021
+ }
52022
+ async function testKey(configKey, key, timeoutMs = DEFAULT_TIMEOUT_MS) {
52023
+ const tester = TESTERS[configKey];
52024
+ if (!tester) {
52025
+ return { ok: false, skipped: true, message: "no tester registered" };
52026
+ }
52027
+ const ctrl = new AbortController();
52028
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
52029
+ try {
52030
+ return await tester.test(key, ctrl.signal);
52031
+ } finally {
52032
+ clearTimeout(timer);
52033
+ }
52034
+ }
52035
+ function isTestableProvider(configKey) {
52036
+ const t = TESTERS[configKey];
52037
+ if (!t) return false;
52038
+ return true;
52039
+ }
52040
+ var DEFAULT_TIMEOUT_MS, TESTERS;
52041
+ var init_key_live_test = __esm({
52042
+ "src/utils/key-live-test.ts"() {
52043
+ "use strict";
52044
+ DEFAULT_TIMEOUT_MS = 5e3;
52045
+ TESTERS = {
52046
+ anthropic: {
52047
+ name: "Anthropic",
52048
+ test: (key, signal) => getOk(
52049
+ "https://api.anthropic.com/v1/models",
52050
+ { "x-api-key": key, "anthropic-version": "2023-06-01" },
52051
+ signal
52052
+ )
52053
+ },
52054
+ openai: {
52055
+ name: "OpenAI",
52056
+ test: (key, signal) => getOk(
52057
+ "https://api.openai.com/v1/models",
52058
+ { Authorization: `Bearer ${key}` },
52059
+ signal
52060
+ )
52061
+ },
52062
+ google: {
52063
+ name: "Google",
52064
+ test: (key, signal) => getOk(
52065
+ `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key)}`,
52066
+ {},
52067
+ signal
52068
+ )
52069
+ },
52070
+ xai: {
52071
+ name: "xAI",
52072
+ test: (key, signal) => getOk(
52073
+ "https://api.x.ai/v1/models",
52074
+ { Authorization: `Bearer ${key}` },
52075
+ signal
52076
+ )
52077
+ },
52078
+ elevenlabs: {
52079
+ name: "ElevenLabs",
52080
+ test: (key, signal) => getOk("https://api.elevenlabs.io/v1/user", { "xi-api-key": key }, signal)
52081
+ },
52082
+ replicate: {
52083
+ name: "Replicate",
52084
+ test: (key, signal) => getOk(
52085
+ "https://api.replicate.com/v1/account",
52086
+ { Authorization: `Bearer ${key}` },
52087
+ signal
52088
+ )
52089
+ },
52090
+ openrouter: {
52091
+ name: "OpenRouter",
52092
+ test: (key, signal) => getOk(
52093
+ "https://openrouter.ai/api/v1/models",
52094
+ { Authorization: `Bearer ${key}` },
52095
+ signal
52096
+ )
52097
+ },
52098
+ // The four below have no cheap authenticated GET — providing a tester
52099
+ // would either consume a paid API call (fal, Runway), require HMAC
52100
+ // signing per request (Kling), or burn an image upload (ImgBB). We
52101
+ // surface them as `skipped` so the user knows we didn't blank-check.
52102
+ fal: {
52103
+ name: "fal.ai",
52104
+ test: async () => ({ ok: false, skipped: true, message: "no list endpoint" })
52105
+ },
52106
+ kling: {
52107
+ name: "Kling",
52108
+ test: async () => ({ ok: false, skipped: true, message: "HMAC-signed; can't test cheaply" })
52109
+ },
52110
+ runway: {
52111
+ name: "Runway",
52112
+ test: async () => ({ ok: false, skipped: true, message: "POST-only API" })
52113
+ },
52114
+ imgbb: {
52115
+ name: "ImgBB",
52116
+ test: async () => ({ ok: false, skipped: true, message: "would consume an upload" })
52117
+ }
52118
+ };
52119
+ }
52120
+ });
52121
+
51585
52122
  // src/pipeline/resolver.ts
51586
52123
  function resolveStepParams(params, completedSteps) {
51587
52124
  const resolved = {};
@@ -58587,10 +59124,10 @@ var init_ai_suggest_edit = __esm({
58587
59124
  }
58588
59125
  });
58589
59126
 
58590
- // src/tools/manifest/analyze.ts
58591
- var analyzeMediaTool, analyzeVideoTool, analyzeReviewTool, analyzeSuggestTool, analyzeTools;
58592
- var init_analyze = __esm({
58593
- "src/tools/manifest/analyze.ts"() {
59127
+ // src/tools/manifest/inspect.ts
59128
+ var analyzeMediaTool, analyzeVideoTool, analyzeReviewTool, analyzeSuggestTool, inspectTools;
59129
+ var init_inspect = __esm({
59130
+ "src/tools/manifest/inspect.ts"() {
58594
59131
  "use strict";
58595
59132
  init_zod();
58596
59133
  init_define_tool();
@@ -58713,7 +59250,7 @@ var init_analyze = __esm({
58713
59250
  };
58714
59251
  }
58715
59252
  });
58716
- analyzeTools = [
59253
+ inspectTools = [
58717
59254
  analyzeMediaTool,
58718
59255
  analyzeVideoTool,
58719
59256
  analyzeReviewTool,
@@ -59049,13 +59586,13 @@ var init_generate2 = __esm({
59049
59586
  }
59050
59587
  });
59051
59588
 
59052
- // src/tools/manifest/pipeline.ts
59589
+ // src/tools/manifest/remix.ts
59053
59590
  import { writeFile as writeFile43 } from "node:fs/promises";
59054
59591
  import { tmpdir as tmpdir3 } from "node:os";
59055
59592
  import { join as join13 } from "node:path";
59056
- var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, pipelineTools;
59057
- var init_pipeline2 = __esm({
59058
- "src/tools/manifest/pipeline.ts"() {
59593
+ var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, remixTools;
59594
+ var init_remix = __esm({
59595
+ "src/tools/manifest/remix.ts"() {
59059
59596
  "use strict";
59060
59597
  init_zod();
59061
59598
  init_define_tool();
@@ -59238,7 +59775,7 @@ var init_pipeline2 = __esm({
59238
59775
  };
59239
59776
  }
59240
59777
  });
59241
- pipelineTools = [
59778
+ remixTools = [
59242
59779
  pipelineHighlightsTool,
59243
59780
  pipelineAutoShortsTool,
59244
59781
  pipelineRunTool,
@@ -60062,7 +60599,7 @@ var init_hyperframes = __esm({
60062
60599
  // src/commands/export.ts
60063
60600
  import { readFile as readFile40, access as access8 } from "node:fs/promises";
60064
60601
  import { resolve as resolve61, basename as basename21 } from "node:path";
60065
- import { spawn } from "node:child_process";
60602
+ import { spawn as spawn3 } from "node:child_process";
60066
60603
  function timelineBaseName(path2) {
60067
60604
  const name = basename21(path2);
60068
60605
  if (name.endsWith(".vibe.json")) return name.slice(0, -".vibe.json".length);
@@ -60536,7 +61073,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
60536
61073
  }
60537
61074
  function runFFmpegProcess(ffmpegPath, args, onProgress) {
60538
61075
  return new Promise((resolve64, reject) => {
60539
- const ffmpeg = spawn(ffmpegPath, args, {
61076
+ const ffmpeg = spawn3(ffmpegPath, args, {
60540
61077
  stdio: ["pipe", "pipe", "pipe"]
60541
61078
  });
60542
61079
  let duration = 0;
@@ -61819,320 +62356,6 @@ Audio:`);
61819
62356
  }
61820
62357
  });
61821
62358
 
61822
- // src/commands/_shared/walkthroughs/walkthroughs.ts
61823
- function loadWalkthrough(topic) {
61824
- const meta = META[topic];
61825
- const content = CONTENT[topic];
61826
- if (!meta || !content) {
61827
- throw new Error(`Unknown walkthrough topic: ${topic}`);
61828
- }
61829
- return {
61830
- topic,
61831
- title: meta.title,
61832
- summary: meta.summary,
61833
- steps: meta.steps,
61834
- relatedCommands: meta.relatedCommands,
61835
- content
61836
- };
61837
- }
61838
- function listWalkthroughs() {
61839
- return WALKTHROUGH_TOPICS.map((topic) => ({
61840
- topic,
61841
- title: META[topic].title,
61842
- summary: META[topic].summary
61843
- }));
61844
- }
61845
- function isWalkthroughTopic(value) {
61846
- return typeof value === "string" && WALKTHROUGH_TOPICS.includes(value);
61847
- }
61848
- var SCENE_WALKTHROUGH, PIPELINE_WALKTHROUGH, META, CONTENT, WALKTHROUGH_TOPICS;
61849
- var init_walkthroughs = __esm({
61850
- "src/commands/_shared/walkthroughs/walkthroughs.ts"() {
61851
- "use strict";
61852
- SCENE_WALKTHROUGH = `# Scene authoring with vibe
61853
-
61854
- A scene project is a directory that is **bilingual**: it works with both
61855
- \`vibe\` and \`npx hyperframes\`. Each scene is one HTML file with scoped CSS
61856
- and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
61857
- at render.
61858
-
61859
- \`vibe build\` (v0.60+) is the supported one-shot driver from a
61860
- written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
61861
- host agent itself authors the per-beat HTML \u2014 no internal LLM call.
61862
-
61863
- ## Three authoring paths
61864
-
61865
- | Path | Command | When to use |
61866
- |---|---|---|
61867
- | **One-shot (default, v0.60+)** | \`vibe build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
61868
- | **High-craft (manual)** | \`DESIGN.md\` + local composition rules in your agent | Maximum control: hand-author each scene |
61869
- | **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
61870
-
61871
- Recommend \`vibe build\` whenever the user has a STORYBOARD with
61872
- narration / backdrop intent.
61873
-
61874
- ## High-craft path
61875
-
61876
- 1. \`vibe init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
61877
- \`DESIGN.md\` (palette, typography, motion, transitions) plus the
61878
- \`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
61879
- In Plan H this **also installs local composition rules** at the
61880
- right place for your host (\`.claude/skills/hyperframes/\` for Claude
61881
- Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
61882
- \`SKILL.md\` for everyone else).
61883
- 2. Read \`SKILL.md\` (or the host-specific copy) \u2014 Hyperframes
61884
- framework rules, motion principles, type system, transition recipes.
61885
- 3. Read \`DESIGN.md\` \u2014 project-specific palette / typography / motion
61886
- signature (visual identity hard-gate).
61887
- 4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
61888
- using the rules from steps 2 and 3. The skill enforces the visual
61889
- identity contract \u2014 scenes that contradict DESIGN.md fail lint.
61890
- 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe render my-promo\`
61891
- to MP4.
61892
-
61893
- ## Quick-draft path
61894
-
61895
- \`\`\`bash
61896
- vibe init my-promo -r 16:9 -d 30
61897
- vibe scene add intro --style announcement \\
61898
- --headline "Ship videos, not clicks"
61899
- vibe scene lint
61900
- vibe render my-promo
61901
- \`\`\`
61902
-
61903
- \`vibe init\` is **idempotent** \u2014 running it on an existing Hyperframes
61904
- directory merges \`hyperframes.json\` instead of clobbering it. Safe to
61905
- invoke on user-provided projects.
61906
-
61907
- ## Subcommands
61908
-
61909
- \`\`\`bash
61910
- # Project flow (top-level \u2014 preferred entry points)
61911
- vibe init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61912
- vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
61913
- vibe render [<dir>] [--fps 30] [--quality standard] [--format mp4]
61914
-
61915
- # Lower-level scene authoring
61916
- vibe scene list-styles [<name>] # list / show vendored visual identities
61917
- vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
61918
- vibe scene add <name> --style <preset> [...]
61919
- vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
61920
- vibe scene lint [<root>] [--json] [--fix]
61921
- \`\`\`
61922
-
61923
- ## Style presets (for \`vibe scene add --style\`)
61924
-
61925
- - **simple** \u2014 backdrop + bottom caption (default)
61926
- - **announcement** \u2014 single huge headline, gradient text
61927
- - **explainer** \u2014 kicker + title + subtitle stack
61928
- - **kinetic-type** \u2014 words animate in word-by-word
61929
- - **product-shot** \u2014 corner label + bottom headline + slow zoom
61930
-
61931
- All presets accept \`--narration <text|file>\`, \`--visuals <prompt>\`,
61932
- \`--headline\`, \`--kicker\`. With \`--narration\`, scene duration auto-derives
61933
- from the generated TTS audio.
61934
-
61935
- ## STORYBOARD-to-MP4 (one command, v0.60+)
61936
-
61937
- \`\`\`bash
61938
- vibe init my-promo --visual-style "Swiss Pulse" -d 12
61939
- # (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
61940
- vibe build my-promo
61941
- \`\`\`
61942
-
61943
- \`vibe build\` reads the STORYBOARD frontmatter + per-beat cues,
61944
- dispatches TTS + image-gen per beat, then either:
61945
-
61946
- - **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
61947
- \`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
61948
- authors each \`compositions/scene-<id>.html\` itself, then re-invoking
61949
- \`vibe build\` proceeds to lint + render.
61950
- - **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
61951
- Gemini) to compose the HTML, then renders.
61952
-
61953
- \`VIBE_BUILD_MODE\` env var overrides the auto-resolve.
61954
-
61955
- ## Lint feedback loop
61956
-
61957
- \`\`\`bash
61958
- vibe scene lint --json --fix
61959
- \`\`\`
61960
-
61961
- Returns structured findings. The recommended loop: 1) run lint with
61962
- \`--fix\` (mechanical fixes applied), 2) if \`errorCount > 0\`, edit the
61963
- scene HTML, 3) re-lint. Cap retries at 3 \u2014 if errors persist, fall back
61964
- to a template preset (\`vibe scene add <id> --style simple --force\`)
61965
- and surface the error to the user.
61966
-
61967
- ## When to use VibeFrame vs raw Hyperframes
61968
-
61969
- | Task | Tool |
61970
- |------|------|
61971
- | Generate narration + image, then author scene | \`vibe scene add\` |
61972
- | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
61973
- | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
61974
- | Render the project | \`vibe render\` (one canonical entry point) |
61975
- | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
61976
-
61977
- The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
61978
- integration on top of Hyperframes' rendering primitives.
61979
-
61980
- ## Quality checklist before render
61981
-
61982
- - [ ] \`vibe scene lint\` exits 0 (or only warnings)
61983
- - [ ] \`vibe doctor\` confirms a usable Chrome (required for render)
61984
- - [ ] Root \`data-duration\` matches the sum of clip durations
61985
- - [ ] Aspect ratio in \`vibe.project.yaml\` matches the destination platform
61986
- `;
61987
- PIPELINE_WALKTHROUGH = `# YAML pipelines (Video as Code)
61988
-
61989
- A pipeline is a YAML manifest with steps that reference each other's
61990
- outputs. \`vibe run pipeline.yaml\` executes them with checkpointing and
61991
- cost estimation.
61992
-
61993
- ## Minimal skeleton
61994
-
61995
- \`\`\`yaml
61996
- name: promo-video
61997
- description: 15s product teaser
61998
- steps:
61999
- - id: backdrop
62000
- action: generate-image
62001
- prompt: "sleek product shot on white background"
62002
- output: backdrop.png
62003
- - id: scene
62004
- action: generate-video
62005
- image: $backdrop.output # reference previous step output
62006
- prompt: "slow camera pan"
62007
- duration: 5
62008
- output: scene.mp4
62009
- - id: voice
62010
- action: generate-tts
62011
- text: "Meet the new standard."
62012
- output: voice.mp3
62013
- - id: final
62014
- action: compose
62015
- video: $scene.output
62016
- audio: $voice.output
62017
- output: final.mp4
62018
- \`\`\`
62019
-
62020
- ## Supported actions
62021
-
62022
- - \`generate-image\`, \`generate-video\`, \`generate-tts\`, \`generate-music\`,
62023
- \`generate-sound-effect\`, \`generate-storyboard\`, \`generate-motion\`
62024
- - \`edit-silence-cut\`, \`edit-jump-cut\`, \`edit-caption\`, \`edit-grade\`,
62025
- \`edit-reframe\`, \`edit-speed-ramp\`, \`edit-fade\`, \`edit-noise-reduce\`,
62026
- \`edit-text-overlay\`, \`edit-fill-gaps\`
62027
- - \`analyze-media\`, \`analyze-video\`, \`analyze-review\`, \`analyze-suggest\`
62028
- - \`audio-transcribe\`, \`audio-isolate\`, \`audio-voice-clone\`, \`audio-dub\`,
62029
- \`audio-duck\`
62030
- - \`detect-scenes\`, \`detect-silence\`, \`detect-beats\`
62031
- - \`compose\`, \`export\`
62032
- - \`scene-build\` (Plan H one-shot driver) and \`scene-render\`
62033
- - \`compose-scenes-with-skills\` (internal-LLM compose pass)
62034
-
62035
- The full set lives in \`packages/cli/src/pipeline/executor.ts\`.
62036
-
62037
- ## Variable references
62038
-
62039
- - \`$<step-id>.output\` \u2014 previous step's output path
62040
- - \`$<step-id>.result.<field>\` \u2014 structured field from JSON result
62041
- - \`\${ENV_VAR}\` \u2014 environment variable
62042
- - Values can be templated: \`"\${SCRIPT_TITLE} - Episode \${EPISODE}"\`
62043
-
62044
- ## Running
62045
-
62046
- \`\`\`bash
62047
- vibe run pipeline.yaml --dry-run # plan + cost estimate, no execution
62048
- vibe run pipeline.yaml # execute
62049
- vibe run pipeline.yaml --resume # retry from last successful step
62050
- vibe run pipeline.yaml --from scene # start at specific step
62051
- vibe run pipeline.yaml --provider-video kling # override provider
62052
- \`\`\`
62053
-
62054
- Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
62055
-
62056
- ## Authoring tips
62057
-
62058
- 1. **Start from a tiny YAML** \u2014 keep it in your project directory and run
62059
- \`vibe run pipeline.yaml --dry-run\` before spending provider budget.
62060
- 2. **Dry-run first** \u2014 you see estimated cost and resolved variable
62061
- graph before spending API credits.
62062
- 3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
62063
- \`bgm\`) \u2014 they appear in logs and variable refs.
62064
- 4. **Name outputs** with extensions matching the action (\`.mp4\`, \`.mp3\`,
62065
- \`.png\`, \`.json\`).
62066
- 5. **Declare \`budget:\`** on expensive pipelines:
62067
- \`\`\`yaml
62068
- budget:
62069
- tokens: 500_000
62070
- max_tool_errors: 3
62071
- cost_usd: 5.00
62072
- \`\`\`
62073
- 6. **Split large pipelines** into smaller YAML files and compose via
62074
- \`action: run-pipeline\` (nested).
62075
-
62076
- ## Converting ad-hoc shell sessions to pipelines
62077
-
62078
- When the user has a working shell sequence, extract steps:
62079
-
62080
- - Each \`vibe ...\` command becomes one step
62081
- - File outputs become step outputs; downstream \`-i <file>\` references
62082
- become \`$<id>.output\`
62083
- - Shared parameters move to a top-level \`defaults:\` section
62084
- - Wrap the entire chain in a \`name:\` + \`steps:\` skeleton
62085
-
62086
- The \`compose\` action is the catch-all assembly step (audio mux, video
62087
- overlay, etc.) \u2014 useful at the tail of a pipeline.
62088
- `;
62089
- META = {
62090
- scene: {
62091
- title: "Scene authoring with vibe",
62092
- summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
62093
- steps: [
62094
- 'Run `vibe init <dir> --visual-style "<style name>"` to scaffold the project + install local composition rules.',
62095
- "Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
62096
- "Read `SKILL.md` for the framework rules and `DESIGN.md` for the visual-identity hard-gate.",
62097
- "Run `vibe build <dir>`. With an agent host detected, the CLI emits a `needs-author` plan; the host agent authors each `compositions/scene-<id>.html` and re-invokes to render.",
62098
- "Run `vibe scene lint --fix` to validate, then `vibe render <dir>` to produce the MP4."
62099
- ],
62100
- relatedCommands: [
62101
- "vibe init",
62102
- "vibe build",
62103
- "vibe render",
62104
- "vibe scene list-styles",
62105
- "vibe scene install-skill",
62106
- "vibe scene compose-prompts",
62107
- "vibe scene lint",
62108
- "vibe scene add"
62109
- ]
62110
- },
62111
- pipeline: {
62112
- title: "YAML pipelines (Video as Code)",
62113
- summary: "Author and run reproducible multi-step video workflows",
62114
- steps: [
62115
- "Sketch the workflow as YAML \u2014 `name`, `description`, then `steps:` with `id` + `action` + inputs/outputs.",
62116
- "Reference previous step outputs via `$<step-id>.output` (or `$<step-id>.result.<field>` for structured returns).",
62117
- "Run `vibe run pipeline.yaml --dry-run` to see the resolved graph + cost estimate before spending API budget.",
62118
- "Add a `budget:` block (tokens / cost_usd / max_tool_errors) to cap expensive runs.",
62119
- "Run `vibe run pipeline.yaml` to execute. Failed steps checkpoint to `pipeline.yaml.checkpoint.json`; resume with `--resume`."
62120
- ],
62121
- relatedCommands: [
62122
- "vibe run",
62123
- "vibe schema --list",
62124
- "vibe doctor"
62125
- ]
62126
- }
62127
- };
62128
- CONTENT = {
62129
- scene: SCENE_WALKTHROUGH,
62130
- pipeline: PIPELINE_WALKTHROUGH
62131
- };
62132
- WALKTHROUGH_TOPICS = ["scene", "pipeline"];
62133
- }
62134
- });
62135
-
62136
62359
  // src/tools/manifest/walkthrough.ts
62137
62360
  var walkthroughSchema, walkthroughTool, walkthroughTools;
62138
62361
  var init_walkthrough = __esm({
@@ -62183,15 +62406,15 @@ var init_walkthrough = __esm({
62183
62406
  var manifest_exports = {};
62184
62407
  __export(manifest_exports, {
62185
62408
  agentOnlyTools: () => agentOnlyTools,
62186
- analyzeTools: () => analyzeTools,
62187
62409
  audioTools: () => audioTools,
62188
62410
  detectTools: () => detectTools,
62189
62411
  editTools: () => editTools,
62190
62412
  exportTools: () => exportTools,
62191
62413
  generateTools: () => generateTools,
62414
+ inspectTools: () => inspectTools,
62192
62415
  manifest: () => manifest,
62193
- pipelineTools: () => pipelineTools,
62194
62416
  projectTools: () => projectTools,
62417
+ remixTools: () => remixTools,
62195
62418
  sceneTools: () => sceneTools,
62196
62419
  timelineTools: () => timelineTools,
62197
62420
  walkthroughTools: () => walkthroughTools
@@ -62203,9 +62426,9 @@ var init_manifest = __esm({
62203
62426
  init_scene2();
62204
62427
  init_audio2();
62205
62428
  init_edit();
62206
- init_analyze();
62429
+ init_inspect();
62207
62430
  init_generate2();
62208
- init_pipeline2();
62431
+ init_remix();
62209
62432
  init_detect2();
62210
62433
  init_timeline();
62211
62434
  init_project2();
@@ -62216,9 +62439,9 @@ var init_manifest = __esm({
62216
62439
  ...sceneTools,
62217
62440
  ...audioTools,
62218
62441
  ...editTools,
62219
- ...analyzeTools,
62442
+ ...inspectTools,
62220
62443
  ...generateTools,
62221
- ...pipelineTools,
62444
+ ...remixTools,
62222
62445
  ...detectTools,
62223
62446
  ...timelineTools,
62224
62447
  ...projectTools,
@@ -62428,7 +62651,7 @@ init_source();
62428
62651
  // package.json
62429
62652
  var package_default = {
62430
62653
  name: "@vibeframe/cli",
62431
- version: "0.80.0",
62654
+ version: "0.81.0",
62432
62655
  description: "VibeFrame CLI - natural language video editing from the terminal",
62433
62656
  private: false,
62434
62657
  license: "MIT",
@@ -63583,7 +63806,7 @@ function detectMediaType(path2) {
63583
63806
  init_generate();
63584
63807
  init_edit_cmd();
63585
63808
 
63586
- // src/commands/analyze.ts
63809
+ // src/commands/inspect.ts
63587
63810
  init_esm();
63588
63811
  init_source();
63589
63812
  init_ora();
@@ -63598,7 +63821,7 @@ init_sanitize();
63598
63821
  init_validate();
63599
63822
  import { readFile as readFile23, writeFile as writeFile30 } from "node:fs/promises";
63600
63823
  import { resolve as resolve33 } from "node:path";
63601
- var analyzeCommand = new Command("inspect").description("Inspect media using AI (images, videos, YouTube URLs)").addHelpText(
63824
+ var inspectCommand = new Command("inspect").description("Inspect media using AI (images, videos, YouTube URLs)").addHelpText(
63602
63825
  "after",
63603
63826
  `
63604
63827
  Examples:
@@ -63616,7 +63839,7 @@ Use '--fields response,model' to limit output size.
63616
63839
  Run 'vibe schema inspect.<command>' for structured parameter info.
63617
63840
  `
63618
63841
  );
63619
- analyzeCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
63842
+ inspectCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
63620
63843
  const startedAt = Date.now();
63621
63844
  try {
63622
63845
  rejectControlChars(prompt3);
@@ -63693,7 +63916,7 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
63693
63916
  exitWithError(apiError(`Analysis failed: ${error.message}`));
63694
63917
  }
63695
63918
  });
63696
- analyzeCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
63919
+ inspectCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
63697
63920
  const startedAt = Date.now();
63698
63921
  try {
63699
63922
  rejectControlChars(prompt3);
@@ -63769,8 +63992,8 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
63769
63992
  exitWithError(apiError(`Video analysis failed: ${error.message}`));
63770
63993
  }
63771
63994
  });
63772
- registerReviewCommand(analyzeCommand);
63773
- analyzeCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Timeline file or directory").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").option("--dry-run", "Preview parameters without executing").action(async (projectPath, instruction, options) => {
63995
+ registerReviewCommand(inspectCommand);
63996
+ inspectCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Timeline file or directory").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").option("--dry-run", "Preview parameters without executing").action(async (projectPath, instruction, options) => {
63774
63997
  const startedAt = Date.now();
63775
63998
  try {
63776
63999
  rejectControlChars(instruction);
@@ -64357,7 +64580,7 @@ audioCommand.command("duck").description("Auto-duck background music when voice
64357
64580
  }
64358
64581
  });
64359
64582
 
64360
- // src/commands/pipeline.ts
64583
+ // src/commands/remix.ts
64361
64584
  init_esm();
64362
64585
  init_source();
64363
64586
  init_ora();
@@ -64542,11 +64765,11 @@ function registerScriptPipelineCommands(aiCommand) {
64542
64765
  });
64543
64766
  }
64544
64767
 
64545
- // src/commands/pipeline.ts
64768
+ // src/commands/remix.ts
64546
64769
  init_ai_highlights();
64547
64770
  init_ai_animated_caption();
64548
64771
  init_output();
64549
- var pipelineCommand = new Command("remix").description(
64772
+ var remixCommand = new Command("remix").description(
64550
64773
  "Transform existing media (highlights, auto-shorts, animated captions, regenerate-scene)"
64551
64774
  ).addHelpText(
64552
64775
  "after",
@@ -64580,10 +64803,10 @@ Use '--dry-run' to preview parameters before execution.
64580
64803
  Run 'vibe schema remix.<command>' for structured parameter info.
64581
64804
  `
64582
64805
  );
64583
- registerScriptPipelineCommands(pipelineCommand);
64584
- registerHighlightsCommands(pipelineCommand);
64806
+ registerScriptPipelineCommands(remixCommand);
64807
+ registerHighlightsCommands(remixCommand);
64585
64808
  var ANIMATED_CAPTION_STYLES = ["highlight", "bounce", "pop-in", "neon", "karaoke-sweep", "typewriter"];
64586
- pipelineCommand.command("animated-caption").description("Add animated captions with word-by-word effects (Whisper + Remotion/ASS)").argument("<video>", "Video file path").option("--style <preset>", "Style preset (default: highlight)", "highlight").option("--highlight-color <color>", "Active word highlight color", "#FFFF00").option("--font-size <px>", "Font size (default: auto based on resolution)").option("--position <pos>", "Caption position: top, center, bottom", "bottom").option("--words-per-group <n>", "Words shown at once (default: auto 3-5)").option("--max-chars <n>", "Max characters per group").option("-l, --language <lang>", "Whisper language hint").option("--fast", "Use ASS/FFmpeg only (no Remotion, forces ASS tier styles)").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").addHelpText(
64809
+ remixCommand.command("animated-caption").description("Add animated captions with word-by-word effects (Whisper + Remotion/ASS)").argument("<video>", "Video file path").option("--style <preset>", "Style preset (default: highlight)", "highlight").option("--highlight-color <color>", "Active word highlight color", "#FFFF00").option("--font-size <px>", "Font size (default: auto based on resolution)").option("--position <pos>", "Caption position: top, center, bottom", "bottom").option("--words-per-group <n>", "Words shown at once (default: auto 3-5)").option("--max-chars <n>", "Max characters per group").option("-l, --language <lang>", "Whisper language hint").option("--fast", "Use ASS/FFmpeg only (no Remotion, forces ASS tier styles)").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").addHelpText(
64587
64810
  "after",
64588
64811
  `
64589
64812
  Examples:
@@ -69302,9 +69525,54 @@ function validateKeyFormat(configKey, value) {
69302
69525
  return { ok: false, expected: fmt.example };
69303
69526
  }
69304
69527
 
69528
+ // src/utils/clipboard.ts
69529
+ import { spawn as spawn2 } from "node:child_process";
69530
+ import { platform as platform2 } from "node:os";
69531
+ function resolveCopier() {
69532
+ switch (platform2()) {
69533
+ case "darwin":
69534
+ return { command: "pbcopy", args: [] };
69535
+ case "win32":
69536
+ return { command: "clip", args: [] };
69537
+ case "linux":
69538
+ case "freebsd":
69539
+ case "openbsd":
69540
+ if (process.env.WAYLAND_DISPLAY) {
69541
+ return { command: "wl-copy", args: [] };
69542
+ }
69543
+ return { command: "xclip", args: ["-selection", "clipboard"] };
69544
+ default:
69545
+ return null;
69546
+ }
69547
+ }
69548
+ async function copyToClipboard(text) {
69549
+ const opener = resolveCopier();
69550
+ if (!opener) return false;
69551
+ return new Promise((resolve64) => {
69552
+ let settled = false;
69553
+ const finish = (ok) => {
69554
+ if (settled) return;
69555
+ settled = true;
69556
+ resolve64(ok);
69557
+ };
69558
+ try {
69559
+ const child = spawn2(opener.command, opener.args, {
69560
+ stdio: ["pipe", "ignore", "ignore"],
69561
+ shell: false
69562
+ });
69563
+ child.on("error", () => finish(false));
69564
+ child.on("close", (code) => finish(code === 0));
69565
+ child.stdin.end(text, "utf8");
69566
+ } catch {
69567
+ finish(false);
69568
+ }
69569
+ });
69570
+ }
69571
+
69305
69572
  // src/commands/setup.ts
69306
69573
  init_agent_host_detect();
69307
69574
  init_dist2();
69575
+ init_walkthroughs();
69308
69576
  var VALID_LLM_PROVIDERS = [
69309
69577
  "claude",
69310
69578
  "openai",
@@ -69313,7 +69581,7 @@ var VALID_LLM_PROVIDERS = [
69313
69581
  "xai",
69314
69582
  "openrouter"
69315
69583
  ];
69316
- var setupCommand = new Command("setup").description("Configure VibeFrame (LLM provider, API keys)").option("--reset", "Reset configuration to defaults").option("--full", "Run full setup with all optional providers").option("--show", "Show current configuration (for debugging)").option("--claude-code", "Show Claude Code integration guide").option("-y, --yes", "Non-interactive: write config without prompting (CI / devcontainer)").option("--provider <id>", "Set the Agent LLM provider (claude | openai | gemini | xai | openrouter | ollama)").option("--import-env", "Promote API keys from .env / shell env into config.yaml").addHelpText(
69584
+ var setupCommand = new Command("setup").description("Configure VibeFrame (LLM provider, API keys)").option("--reset", "Reset configuration to defaults").option("--full", "Run full setup with all optional providers").option("--show", "Show current configuration (for debugging)").option("-v, --verbose", "With --show: include unset providers + Resolution order + Defaults block").option("--claude-code", "Show Claude Code integration guide").option("-y, --yes", "Non-interactive: write config without prompting (CI / devcontainer)").option("--provider <id>", "Set the Agent LLM provider (claude | openai | gemini | xai | openrouter | ollama)").option("--import-env", "Promote API keys from .env / shell env into config.yaml").addHelpText(
69317
69585
  "after",
69318
69586
  `
69319
69587
  Non-interactive examples (no TTY required):
@@ -69327,7 +69595,7 @@ Non-interactive examples (no TTY required):
69327
69595
  return;
69328
69596
  }
69329
69597
  if (options.show) {
69330
- await showConfig();
69598
+ await showConfig({ verbose: Boolean(options.verbose) });
69331
69599
  return;
69332
69600
  }
69333
69601
  if (options.reset) {
@@ -69478,7 +69746,7 @@ async function runSetupWizard(fullSetup = false) {
69478
69746
  console.log();
69479
69747
  if (topIndex === 0) {
69480
69748
  await saveConfig(config2);
69481
- showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4");
69749
+ await showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4");
69482
69750
  return;
69483
69751
  }
69484
69752
  if (topIndex === 3) {
@@ -69499,7 +69767,7 @@ async function runSetupWizard(fullSetup = false) {
69499
69767
  console.log();
69500
69768
  await collectKeys(config2, pipelineKeys);
69501
69769
  await saveConfig(config2);
69502
- showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
69770
+ await showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
69503
69771
  return;
69504
69772
  }
69505
69773
  console.log(source_default.dim("2. Features"));
@@ -69521,7 +69789,7 @@ async function runSetupWizard(fullSetup = false) {
69521
69789
  console.log(source_default.dim(" No features selected. You can re-run setup anytime."));
69522
69790
  console.log();
69523
69791
  await saveConfig(config2);
69524
- showComplete(config2, "vibe --help", []);
69792
+ await showComplete(config2, "vibe --help", []);
69525
69793
  return;
69526
69794
  }
69527
69795
  console.log(source_default.dim("3. API keys"));
@@ -69552,8 +69820,11 @@ async function runSetupWizard(fullSetup = false) {
69552
69820
  continue;
69553
69821
  }
69554
69822
  console.log(source_default.dim(` ${keyDef.what}`));
69555
- console.log(source_default.dim(` Get key: ${keyDef.url}`));
69556
- const newKey = await promptHidden(source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `));
69823
+ console.log(source_default.dim(` Get key: ${keyDef.url} [o] open in browser`));
69824
+ const newKey = await promptHidden(
69825
+ source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `),
69826
+ { openHotkeyUrl: keyDef.url }
69827
+ );
69557
69828
  if (newKey.trim()) {
69558
69829
  const trimmed = newKey.trim();
69559
69830
  config2.providers[keyDef.configKey] = trimmed;
@@ -69571,7 +69842,7 @@ async function runSetupWizard(fullSetup = false) {
69571
69842
  console.log();
69572
69843
  }
69573
69844
  await saveConfig(config2);
69574
- showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures);
69845
+ await showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures);
69575
69846
  }
69576
69847
  async function collectKeys(config2, keys) {
69577
69848
  loadEnv();
@@ -69587,8 +69858,11 @@ async function collectKeys(config2, keys) {
69587
69858
  continue;
69588
69859
  }
69589
69860
  console.log(source_default.dim(` ${keyDef.what}`));
69590
- console.log(source_default.dim(` Get key: ${keyDef.url}`));
69591
- const newKey = await promptHidden(source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `));
69861
+ console.log(source_default.dim(` Get key: ${keyDef.url} [o] open in browser`));
69862
+ const newKey = await promptHidden(
69863
+ source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `),
69864
+ { openHotkeyUrl: keyDef.url }
69865
+ );
69592
69866
  if (newKey.trim()) {
69593
69867
  const trimmed = newKey.trim();
69594
69868
  config2.providers[keyDef.configKey] = trimmed;
@@ -69639,7 +69913,11 @@ async function runCustomSetup(config2) {
69639
69913
  console.log(` ${source_default.green("\u2713")} ${p.name.padEnd(12)} ${maskApiKey(existing)} ${source_default.dim(p.desc)}`);
69640
69914
  const change = await promptConfirm(source_default.cyan(" Update?"), false);
69641
69915
  if (change) {
69642
- const newKey = await promptHidden(source_default.cyan(" New key: "));
69916
+ console.log(source_default.dim(` Get key: ${p.url} [o] open in browser`));
69917
+ const newKey = await promptHidden(
69918
+ source_default.cyan(" New key: "),
69919
+ { openHotkeyUrl: p.url }
69920
+ );
69643
69921
  if (newKey.trim()) {
69644
69922
  const trimmed = newKey.trim();
69645
69923
  config2.providers[p.key] = trimmed;
@@ -69652,7 +69930,11 @@ async function runCustomSetup(config2) {
69652
69930
  }
69653
69931
  }
69654
69932
  } else {
69655
- const newKey = await promptHidden(source_default.cyan(` ${source_default.dim("\u25CB")} ${p.name.padEnd(12)} ${source_default.dim(p.desc)}: `));
69933
+ console.log(source_default.dim(` ${source_default.dim("\u25CB")} ${p.name.padEnd(12)} ${source_default.dim(p.desc)} Get key: ${p.url} [o] open`));
69934
+ const newKey = await promptHidden(
69935
+ source_default.cyan(` ${p.env}: `),
69936
+ { openHotkeyUrl: p.url }
69937
+ );
69656
69938
  if (newKey.trim()) {
69657
69939
  const trimmed = newKey.trim();
69658
69940
  config2.providers[p.key] = trimmed;
@@ -69667,14 +69949,15 @@ async function runCustomSetup(config2) {
69667
69949
  }
69668
69950
  console.log();
69669
69951
  await saveConfig(config2);
69670
- showComplete(config2, "vibe doctor");
69952
+ await showComplete(config2, "vibe doctor");
69671
69953
  }
69672
- function showComplete(config2, defaultTryCommand, features = []) {
69954
+ async function showComplete(config2, defaultTryCommand, features = []) {
69673
69955
  console.log(source_default.dim("\u2500".repeat(40)));
69674
69956
  console.log(source_default.green.bold("\u2713 Setup complete!"));
69675
69957
  console.log();
69676
69958
  console.log(source_default.dim(` Config: ${CONFIG_PATH}`));
69677
69959
  console.log();
69960
+ const primaryTry = features.length > 1 ? features[0].tryCommand : defaultTryCommand;
69678
69961
  if (features.length > 1) {
69679
69962
  console.log(source_default.bold(" Try these:"));
69680
69963
  for (const f of features) {
@@ -69683,10 +69966,21 @@ function showComplete(config2, defaultTryCommand, features = []) {
69683
69966
  } else {
69684
69967
  console.log(` Try: ${source_default.cyan(defaultTryCommand)}`);
69685
69968
  }
69969
+ if (process.stdout.isTTY) {
69970
+ const copied = await copyToClipboard(primaryTry);
69971
+ if (copied) {
69972
+ console.log(source_default.dim(" \u2713 Copied to clipboard"));
69973
+ }
69974
+ }
69686
69975
  console.log();
69687
69976
  console.log(source_default.bold(" Next steps:"));
69688
69977
  console.log(source_default.dim(" cd <project>; vibe init Scaffold AGENTS.md / CLAUDE.md / .env.example (project scope)"));
69689
- console.log(source_default.dim(" vibe walkthrough scene 5-step authoring guide (storyboard \u2192 MP4)"));
69978
+ const hostDetected = detectedAgentHosts().length > 0;
69979
+ for (const w of listWalkthroughs()) {
69980
+ const cmd = `vibe walkthrough ${w.topic}`.padEnd(26);
69981
+ const tag = hostDetected && w.topic === "scene" ? source_default.cyan(" (recommended)") : "";
69982
+ console.log(source_default.dim(` ${cmd}${w.summary}${tag}`));
69983
+ }
69690
69984
  console.log(source_default.dim(" vibe doctor Check system health + available commands"));
69691
69985
  console.log(source_default.dim(" vibe schema --list Discover every command"));
69692
69986
  console.log(source_default.dim(" vibe setup Re-run user-scope setup anytime"));
@@ -69734,7 +70028,8 @@ function maskApiKey(key) {
69734
70028
  if (key.length <= 8) return "****";
69735
70029
  return `${key.slice(0, 4)}${"*".repeat(8)}${key.slice(-4)}`;
69736
70030
  }
69737
- async function showConfig() {
70031
+ async function showConfig(opts = { verbose: false }) {
70032
+ const { verbose } = opts;
69738
70033
  loadEnv();
69739
70034
  const cwdEnvPath = resolve42(process.cwd(), ".env");
69740
70035
  let hasCwdEnv = false;
@@ -69785,6 +70080,7 @@ async function showConfig() {
69785
70080
  { key: "replicate", name: "Replicate", env: "REPLICATE_API_TOKEN" },
69786
70081
  { key: "openrouter", name: "OpenRouter", env: "OPENROUTER_API_KEY" }
69787
70082
  ];
70083
+ let unsetCount = 0;
69788
70084
  for (const p of providerKeys) {
69789
70085
  const configValue = config2?.providers[p.key];
69790
70086
  const dotenvValue = dotenvKeys[p.env];
@@ -69796,23 +70092,31 @@ async function showConfig() {
69796
70092
  const status = source_default.green("\u2713");
69797
70093
  console.log(` ${status} ${p.name.padEnd(12)} ${maskApiKey(value)} (${sources.join(" + ")})`);
69798
70094
  } else {
69799
- const status = source_default.dim("\u25CB");
69800
- console.log(` ${status} ${p.name.padEnd(12)} ${source_default.dim("not set")}`);
70095
+ unsetCount++;
70096
+ if (verbose) {
70097
+ const status = source_default.dim("\u25CB");
70098
+ console.log(` ${status} ${p.name.padEnd(12)} ${source_default.dim("not set")}`);
70099
+ }
69801
70100
  }
69802
70101
  }
70102
+ if (!verbose && unsetCount > 0) {
70103
+ console.log(source_default.dim(` (${unsetCount} provider${unsetCount === 1 ? "" : "s"} unset \u2014 run with --verbose to list)`));
70104
+ }
69803
70105
  console.log();
69804
- if (config2) {
69805
- console.log(source_default.bold("Defaults:"));
69806
- console.log(` Aspect Ratio: ${config2.defaults.aspectRatio}`);
69807
- console.log(` Export Quality: ${config2.defaults.exportQuality}`);
70106
+ if (verbose) {
70107
+ if (config2) {
70108
+ console.log(source_default.bold("Defaults:"));
70109
+ console.log(` Aspect Ratio: ${config2.defaults.aspectRatio}`);
70110
+ console.log(` Export Quality: ${config2.defaults.exportQuality}`);
70111
+ console.log();
70112
+ }
70113
+ console.log(source_default.bold("Resolution order:"));
70114
+ console.log(source_default.dim(" 1. --api-key CLI option"));
70115
+ console.log(source_default.dim(` 2. ${CONFIG_PATH}`));
70116
+ console.log(source_default.dim(" 3. .env in current directory"));
70117
+ console.log(source_default.dim(" 4. Shell environment variables"));
69808
70118
  console.log();
69809
70119
  }
69810
- console.log(source_default.bold("Resolution order:"));
69811
- console.log(source_default.dim(" 1. --api-key CLI option"));
69812
- console.log(source_default.dim(` 2. ${CONFIG_PATH}`));
69813
- console.log(source_default.dim(" 3. .env in current directory"));
69814
- console.log(source_default.dim(" 4. Shell environment variables"));
69815
- console.log();
69816
70120
  }
69817
70121
 
69818
70122
  // src/commands/init.ts
@@ -70731,11 +71035,13 @@ var FREE_COMMANDS = [
70731
71035
  "timeline (all)",
70732
71036
  "batch import/concat"
70733
71037
  ];
70734
- var doctorCommand = new Command("doctor").description("Check system health and available commands").option("--json", "Output in JSON format").addHelpText(
71038
+ var doctorCommand = new Command("doctor").description("Check system health and available commands").option("--json", "Output in JSON format").option("-v, --verbose", "Show full report (every provider row, scene composer block, free-command list)").option("--test-keys", "Make a lightweight authenticated request to each provider (validates configured keys; skips providers without a cheap test endpoint)").addHelpText(
70735
71039
  "after",
70736
71040
  `
70737
71041
  Examples:
70738
- $ vibe doctor Show system health and capabilities
71042
+ $ vibe doctor Compact health summary (issues + Ready %)
71043
+ $ vibe doctor --verbose Full report \u2014 all providers, composer, free commands
71044
+ $ vibe doctor --test-keys Validate each configured key against its provider
70739
71045
  $ vibe doctor --json Machine-readable output
70740
71046
  `
70741
71047
  ).action(async (options) => {
@@ -70751,7 +71057,10 @@ Examples:
70751
71057
  });
70752
71058
  return;
70753
71059
  }
70754
- printReport(results);
71060
+ printReport(results, { verbose: Boolean(options.verbose) });
71061
+ if (options.testKeys) {
71062
+ await runLiveKeyTests(results);
71063
+ }
70755
71064
  });
70756
71065
  var REQUIRED_FFMPEG_FILTERS = {
70757
71066
  drawtext: {
@@ -70805,14 +71114,14 @@ async function runDiagnostics() {
70805
71114
  try {
70806
71115
  const filtersResult = await execSafe("ffmpeg", ["-filters"]);
70807
71116
  const filtersOutput = filtersResult.stdout;
70808
- const platform = process.platform;
71117
+ const platform3 = process.platform;
70809
71118
  ffmpegFilters = {};
70810
71119
  for (const [filterName, info] of Object.entries(REQUIRED_FFMPEG_FILTERS)) {
70811
71120
  const available = filtersOutput.includes(filterName);
70812
71121
  ffmpegFilters[filterName] = {
70813
71122
  available,
70814
71123
  commands: info.commands,
70815
- ...!available && info.fix[platform] ? { fix: info.fix[platform] } : {}
71124
+ ...!available && info.fix[platform3] ? { fix: info.fix[platform3] } : {}
70816
71125
  };
70817
71126
  }
70818
71127
  } catch {
@@ -70914,7 +71223,8 @@ async function runDiagnostics() {
70914
71223
  totalCount
70915
71224
  };
70916
71225
  }
70917
- function printReport(results) {
71226
+ function printReport(results, opts = { verbose: false }) {
71227
+ const { verbose } = opts;
70918
71228
  console.log();
70919
71229
  console.log(source_default.bold(" System"));
70920
71230
  const nodeIcon = results.system.node.ok ? source_default.green("OK") : source_default.red("MISSING");
@@ -70943,10 +71253,25 @@ function printReport(results) {
70943
71253
  }
70944
71254
  }
70945
71255
  if (results.system.optionalTools) {
70946
- for (const [name, info] of Object.entries(results.system.optionalTools)) {
70947
- if (info.installed) {
70948
- console.log(` ${name.padEnd(11)}${source_default.green("OK")} ${source_default.dim(info.commands.join(", "))}`);
70949
- } else {
71256
+ const tools = Object.entries(results.system.optionalTools);
71257
+ if (verbose) {
71258
+ for (const [name, info] of tools) {
71259
+ if (info.installed) {
71260
+ console.log(` ${name.padEnd(11)}${source_default.green("OK")} ${source_default.dim(info.commands.join(", "))}`);
71261
+ } else {
71262
+ console.log(
71263
+ ` ${name.padEnd(11)}${source_default.yellow("MISSING")} ${source_default.dim(`needed by: ${info.commands.join(", ")}`)}`
71264
+ );
71265
+ console.log(` ${source_default.dim(`Install: ${info.install}`)}`);
71266
+ }
71267
+ }
71268
+ } else {
71269
+ const okNames = tools.filter(([, t]) => t.installed).map(([n]) => n);
71270
+ const missing2 = tools.filter(([, t]) => !t.installed);
71271
+ if (okNames.length > 0) {
71272
+ console.log(` Tools ${source_default.green("OK")} ${source_default.dim(okNames.join(", "))}`);
71273
+ }
71274
+ for (const [name, info] of missing2) {
70950
71275
  console.log(
70951
71276
  ` ${name.padEnd(11)}${source_default.yellow("MISSING")} ${source_default.dim(`needed by: ${info.commands.join(", ")}`)}`
70952
71277
  );
@@ -70968,29 +71293,31 @@ function printReport(results) {
70968
71293
  console.log(` Project ${source_default.yellow("NOT INIT")} ${source_default.dim(`Run: vibe init (cwd: ${results.scope.project.cwd})`)}`);
70969
71294
  }
70970
71295
  console.log(` Agents ${source_default.dim(results.scope.agentHosts.summary)}`);
70971
- console.log();
70972
- console.log(source_default.bold(" Scene composer (vibe build)"));
70973
- const sc = results.scope.sceneComposer;
70974
- const modeBadge = sc.recommendedMode === "agent" ? source_default.cyan("agent") : source_default.dim("batch");
70975
- const modeNote = sc.recommendedMode === "agent" ? source_default.dim("host agent authors HTML; no internal LLM call") : source_default.dim("CLI's internal LLM authors HTML");
70976
- console.log(` Mode (auto) ${modeBadge} ${modeNote}`);
70977
- if (sc.composer) {
70978
- console.log(
70979
- ` Batch LLM ${source_default.green("OK")} ${source_default.dim(`${composerLabel(sc.composer)} (${sc.composerEnvVar})`)}`
70980
- );
70981
- } else {
70982
- console.log(
70983
- ` Batch LLM ${source_default.yellow("--")} ${source_default.dim("no ANTHROPIC_API_KEY / GOOGLE_API_KEY / OPENAI_API_KEY \u2014 agent-mode only")}`
70984
- );
70985
- }
70986
- if (sc.sceneProjectInCwd) {
70987
- if (sc.skillInstalled) {
70988
- console.log(` SKILL.md ${source_default.green("OK")} ${source_default.dim("installed in this scene project")}`);
71296
+ if (verbose) {
71297
+ console.log();
71298
+ console.log(source_default.bold(" Scene composer (vibe build)"));
71299
+ const sc = results.scope.sceneComposer;
71300
+ const modeBadge = sc.recommendedMode === "agent" ? source_default.cyan("agent") : source_default.dim("batch");
71301
+ const modeNote = sc.recommendedMode === "agent" ? source_default.dim("host agent authors HTML; no internal LLM call") : source_default.dim("CLI's internal LLM authors HTML");
71302
+ console.log(` Mode (auto) ${modeBadge} ${modeNote}`);
71303
+ if (sc.composer) {
71304
+ console.log(
71305
+ ` Batch LLM ${source_default.green("OK")} ${source_default.dim(`${composerLabel(sc.composer)} (${sc.composerEnvVar})`)}`
71306
+ );
70989
71307
  } else {
70990
- console.log(` SKILL.md ${source_default.yellow("MISSING")} ${source_default.dim("Run: vibe scene install-skill")}`);
71308
+ console.log(
71309
+ ` Batch LLM ${source_default.yellow("--")} ${source_default.dim("no ANTHROPIC_API_KEY / GOOGLE_API_KEY / OPENAI_API_KEY \u2014 agent-mode only")}`
71310
+ );
71311
+ }
71312
+ if (sc.sceneProjectInCwd) {
71313
+ if (sc.skillInstalled) {
71314
+ console.log(` SKILL.md ${source_default.green("OK")} ${source_default.dim("installed in this scene project")}`);
71315
+ } else {
71316
+ console.log(` SKILL.md ${source_default.yellow("MISSING")} ${source_default.dim("Run: vibe scene install-skill")}`);
71317
+ }
71318
+ } else {
71319
+ console.log(` SKILL.md ${source_default.dim("(no STORYBOARD.md in cwd \u2014 skill is per-scene-project)")}`);
70991
71320
  }
70992
- } else {
70993
- console.log(` SKILL.md ${source_default.dim("(no STORYBOARD.md in cwd \u2014 skill is per-scene-project)")}`);
70994
71321
  }
70995
71322
  console.log();
70996
71323
  console.log(source_default.bold(" API Keys"));
@@ -71007,16 +71334,24 @@ function printReport(results) {
71007
71334
  }
71008
71335
  }
71009
71336
  if (missing.length > 0) {
71010
- for (const name of missing) {
71011
- const info = results.providers[name];
71337
+ if (verbose) {
71338
+ for (const name of missing) {
71339
+ const info = results.providers[name];
71340
+ console.log(
71341
+ ` ${source_default.red("--")} ${info.envVar.padEnd(24)} ${source_default.dim(info.commands.join(", "))}`
71342
+ );
71343
+ }
71344
+ } else {
71012
71345
  console.log(
71013
- ` ${source_default.red("--")} ${info.envVar.padEnd(24)} ${source_default.dim(info.commands.join(", "))}`
71346
+ source_default.dim(` ${missing.length} provider${missing.length === 1 ? "" : "s"} unconfigured (${missing.slice(0, 4).join(", ")}${missing.length > 4 ? ", \u2026" : ""}). Run with --verbose to list.`)
71014
71347
  );
71015
71348
  }
71016
71349
  }
71017
- console.log();
71018
- console.log(source_default.bold(" No API key needed"));
71019
- console.log(` ${source_default.dim(FREE_COMMANDS.join(", "))}`);
71350
+ if (verbose) {
71351
+ console.log();
71352
+ console.log(source_default.bold(" No API key needed"));
71353
+ console.log(` ${source_default.dim(FREE_COMMANDS.join(", "))}`);
71354
+ }
71020
71355
  console.log();
71021
71356
  const pct = Math.round(results.readyCount / results.totalCount * 100);
71022
71357
  const readyColor = pct >= 80 ? source_default.green : pct >= 40 ? source_default.yellow : source_default.red;
@@ -71032,6 +71367,32 @@ function printReport(results) {
71032
71367
  }
71033
71368
  console.log();
71034
71369
  }
71370
+ async function runLiveKeyTests(results) {
71371
+ console.log();
71372
+ console.log(source_default.bold(" Live key tests"));
71373
+ console.log(source_default.dim(" Hits each provider's cheapest authenticated endpoint with a 5s timeout."));
71374
+ const configured = Object.entries(results.providers).filter(([, info]) => info.configured);
71375
+ if (configured.length === 0) {
71376
+ console.log(source_default.dim(" No keys configured \u2014 nothing to test."));
71377
+ return;
71378
+ }
71379
+ const { testKey: testKey2 } = await Promise.resolve().then(() => (init_key_live_test(), key_live_test_exports));
71380
+ for (const [name, info] of configured) {
71381
+ const value = process.env[info.envVar];
71382
+ if (!value) continue;
71383
+ process.stdout.write(` ${name.padEnd(12)} `);
71384
+ const result = await testKey2(name, value);
71385
+ if (result.skipped) {
71386
+ console.log(source_default.dim(`SKIP ${result.message ?? "no test available"}`));
71387
+ } else if (result.ok) {
71388
+ console.log(`${source_default.green("OK")} ${source_default.dim(`${result.status}`)}`);
71389
+ } else {
71390
+ const detail = result.message ?? `status ${result.status ?? "?"}`;
71391
+ console.log(`${source_default.red("FAIL")} ${source_default.dim(detail)}`);
71392
+ }
71393
+ }
71394
+ console.log();
71395
+ }
71035
71396
  function pickNextStep(results, hasMissingProviders) {
71036
71397
  if (!results.scope.user.configured) {
71037
71398
  return "Next: run 'vibe setup' to configure your user scope (API keys + LLM provider).";
@@ -72403,48 +72764,42 @@ program2.name("vibe").showSuggestionAfterError(true).description("VibeFrame CLI
72403
72764
  }).addHelpText(
72404
72765
  "after",
72405
72766
  `
72406
- Global flags (work with any command):
72407
- -V, --version Print version and exit
72408
- -h, --help Print help for the command and exit
72409
- --json Output JSON (auto-enabled when piped)
72410
- --fields Limit output fields (e.g., --fields "path,duration")
72411
- -q, --quiet Output only the result value (path, URL, or ID)
72412
- --stdin Read options from stdin as JSON (for agent/script use)
72413
- --describe Print the command's JSON schema and exit (no execution)
72414
- --dry-run Preview without executing (most commands)
72415
-
72416
- Get started (90% of users start here):
72417
- vibe doctor Check system health and API keys
72418
- vibe setup Configure API keys interactively
72419
- vibe init my-video Scaffold a video project
72420
- vibe build my-video Build STORYBOARD.md into scene compositions/assets
72421
- vibe render my-video Render the project to MP4
72422
- vibe demo Try VibeFrame without API keys
72423
-
72424
- One-shot media tools:
72425
- vibe generate image "..." Generate a still image
72426
- vibe generate video "..." Generate a standalone AI video
72427
- vibe edit caption clip.mp4 -o out.mp4
72428
- vibe inspect media clip.mp4 "Summarize this"
72429
- vibe remix highlights long.mp4 -d 60
72430
-
72431
- Advanced authoring:
72432
- vibe timeline create my-video Create low-level timeline JSON
72433
- vibe scene add intro --headline "Welcome"
72434
- vibe timeline add-clip my-video <source-id>
72435
-
72436
- Automation & agents:
72437
- vibe run workflow.yaml Run a Video-as-YAML pipeline
72438
- vibe agent Natural-language interface
72439
- vibe schema generate.video Tool schema for any command
72440
- vibe walkthrough scene Step-by-step authoring guide
72441
-
72442
- Cost tiers (per call):
72443
- Free detect, edit (silence-cut/fade/noise-reduce), timeline
72444
- Low inspect, audio transcribe, generate image ~$0.01-$0.10
72445
- High generate video, edit image ~$1-$5
72446
- V.High remix (regenerate-scene, highlights, auto-shorts) ~$5-$50+
72447
- Tip Add --dry-run before paid generation to preview the work.
72767
+ Common workflows (the alphabetical list above is the full command set;
72768
+ this section shows the typical entry points by use case):
72769
+
72770
+ Get started:
72771
+ vibe doctor System health + key status
72772
+ vibe setup Configure API keys interactively
72773
+ vibe init my-video Scaffold a video project
72774
+ vibe build my-video Build STORYBOARD.md \u2192 scene assets
72775
+ vibe render my-video Render the project to MP4
72776
+ vibe demo Try VibeFrame without API keys
72777
+
72778
+ One-shot media:
72779
+ vibe generate image "..." Generate a still image
72780
+ vibe generate video "..." Generate a standalone AI video
72781
+ vibe edit caption clip.mp4 -o out.mp4
72782
+ vibe inspect media clip.mp4 "Summarize this"
72783
+ vibe remix highlights long.mp4 -d 60
72784
+
72785
+ Authoring:
72786
+ vibe timeline create my-video Low-level timeline JSON
72787
+ vibe scene add intro --headline "Welcome"
72788
+
72789
+ Automation & agents:
72790
+ vibe run workflow.yaml Video-as-YAML pipeline
72791
+ vibe agent Natural-language interface
72792
+ vibe schema generate.video JSON schema for any command
72793
+ vibe walkthrough scene Step-by-step authoring guide
72794
+
72795
+ Common-flag note: most commands accept --dry-run to preview cost/output
72796
+ before invoking paid providers.
72797
+
72798
+ Cost tiers:
72799
+ Free detect, edit (silence-cut/fade/noise-reduce), timeline
72800
+ Low inspect, audio transcribe, generate image ~$0.01\u2013$0.10
72801
+ High generate video, edit image ~$1\u2013$5
72802
+ V.High remix (regenerate-scene, highlights, auto-shorts) ~$5\u2013$50+
72448
72803
  `
72449
72804
  );
72450
72805
  program2.hook("preAction", async (thisCommand, actionCommand) => {
@@ -72506,9 +72861,9 @@ program2.hook("preAction", async (thisCommand, actionCommand) => {
72506
72861
  });
72507
72862
  program2.addCommand(generateCommand);
72508
72863
  program2.addCommand(editCommand);
72509
- program2.addCommand(analyzeCommand);
72864
+ program2.addCommand(inspectCommand);
72510
72865
  program2.addCommand(audioCommand);
72511
- program2.addCommand(pipelineCommand);
72866
+ program2.addCommand(remixCommand);
72512
72867
  program2.addCommand(setupCommand);
72513
72868
  program2.addCommand(initCommand);
72514
72869
  program2.addCommand(buildCommand);