@vibeframe/cli 0.113.29 → 0.113.30

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.
package/CONTEXT.md CHANGED
@@ -125,13 +125,17 @@ vibe guide <topic> --json # Step-by-step guides (motion | scene | p
125
125
  }
126
126
  ```
127
127
 
128
- | Code | Exit | Meaning | Recovery |
129
- | --------------- | ---- | ------------------- | -------------------------- |
130
- | `USAGE_ERROR` | 2 | bad arg | check `vibe schema <cmd>` |
131
- | `NOT_FOUND` | 3 | file missing | verify path |
132
- | `AUTH_ERROR` | 4 | key missing/invalid | `vibe doctor` |
133
- | `API_ERROR` | 5 | provider failed | retry if `retryable: true` |
134
- | `NETWORK_ERROR` | 6 | connection | retry with backoff |
128
+ | Code | Exit | Meaning | Recovery |
129
+ | -------------------- | ---- | --------------------------- | --------------------------------- |
130
+ | `COST_CAP_EXCEEDED` | 1 | estimate over `--max-cost` | follow `retryWith`; do not re-run as-is |
131
+ | `USAGE_ERROR` | 2 | bad arg | check `vibe schema <cmd>` |
132
+ | `NOT_FOUND` | 3 | file missing | verify path |
133
+ | `AUTH_ERROR` | 4 | key missing/invalid | `vibe doctor` |
134
+ | `API_ERROR` | 5 | provider failed | retry if `retryable: true` |
135
+ | `NETWORK_ERROR` | 6 | connection | retry with backoff |
136
+
137
+ `COST_CAP_EXCEEDED` also carries `data.plan` - the priced plan the refusal was
138
+ based on - so a refused call still tells you what the build would have cost.
135
139
 
136
140
  ## Authentication
137
141
 
@@ -98,7 +98,7 @@ export declare function buildHyperframesConfig(): HyperframesConfig;
98
98
  export declare function buildHyperframesMeta(name: string, now?: Date): HyperframesMeta;
99
99
  /**
100
100
  * Merge an existing Hyperframes config with our defaults, preserving any
101
- * user-authored keys and nested values. `vibe scene init` is idempotent:
101
+ * user-authored keys and nested values. `vibe init` is idempotent:
102
102
  * running it on a directory that already has `hyperframes.json` must never
103
103
  * lose user config.
104
104
  */
@@ -22,6 +22,12 @@ export interface StructuredError {
22
22
  retryWith?: string[];
23
23
  recoverable?: boolean;
24
24
  retryable: boolean;
25
+ /**
26
+ * Structured context carried alongside the failure, so a refusal can hand
27
+ * back the evidence it refused on instead of forcing a second call. Used by
28
+ * the `--max-cost` gate to return the priced build plan with the refusal.
29
+ */
30
+ data?: Record<string, unknown>;
25
31
  }
26
32
  export declare function usageError(msg: string, suggestion?: string): StructuredError;
27
33
  export declare function authError(envVar: string, provider: string): StructuredError;
@@ -29,6 +35,21 @@ export declare function apiError(msg: string, retryable?: boolean): StructuredEr
29
35
  export declare function notFoundError(path: string): StructuredError;
30
36
  export declare function networkError(msg: string): StructuredError;
31
37
  export declare function generalError(msg: string, suggestion?: string): StructuredError;
38
+ /**
39
+ * The `--max-cost` refusal. A command that declines to spend is a failure, so
40
+ * it takes the structured error path like every other refusal: `success:false`
41
+ * on stderr, exit 1. `data` carries the priced plan the refusal was based on,
42
+ * so an agent gets the estimate and the reason from one call.
43
+ *
44
+ * `recoverable: true` because the caller has two real ways forward (raise the
45
+ * ceiling, or narrow the scope), both spelled out in `retryWith`.
46
+ */
47
+ export declare function costCapError(opts: {
48
+ estimatedCostUsd: number;
49
+ maxCostUsd: number;
50
+ retryWith: string[];
51
+ data?: Record<string, unknown>;
52
+ }): StructuredError;
32
53
  /** Output structured error then exit */
33
54
  export declare function exitWithError(err: StructuredError): never;
34
55
  /** Check if --json flag is active */
package/dist/index.js CHANGED
@@ -6557,6 +6557,19 @@ function notFoundError(path2) {
6557
6557
  function generalError(msg, suggestion) {
6558
6558
  return { success: false, error: msg, code: "ERROR", exitCode: 1 /* GENERAL */, suggestion, retryable: false };
6559
6559
  }
6560
+ function costCapError(opts) {
6561
+ return {
6562
+ success: false,
6563
+ error: `Estimated cost $${opts.estimatedCostUsd.toFixed(2)} exceeds --max-cost $${opts.maxCostUsd.toFixed(2)}.`,
6564
+ code: "COST_CAP_EXCEEDED",
6565
+ exitCode: 1 /* GENERAL */,
6566
+ suggestion: "Raise --max-cost or reduce the stage/provider scope.",
6567
+ retryWith: opts.retryWith,
6568
+ recoverable: true,
6569
+ retryable: false,
6570
+ data: opts.data
6571
+ };
6572
+ }
6560
6573
  function exitWithError(err) {
6561
6574
  if (isJsonMode()) {
6562
6575
  console.error(JSON.stringify({
@@ -6571,6 +6584,9 @@ function exitWithError(err) {
6571
6584
  if (err.suggestion) {
6572
6585
  console.error(source_default.dim(` ${err.suggestion}`));
6573
6586
  }
6587
+ if (err.retryWith && err.retryWith.length > 0) {
6588
+ console.error(source_default.dim(` Retry: ${err.retryWith[0]}`));
6589
+ }
6574
6590
  console.error();
6575
6591
  }
6576
6592
  process.exit(err.exitCode);
@@ -26945,7 +26961,7 @@ ${avoid}
26945
26961
  ---
26946
26962
 
26947
26963
  _Browse other named styles: \`vibe scene list-styles\`_
26948
- ${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
26964
+ ${style ? `_This file was seeded by \`vibe init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe init <dir> --visual-style "<name>"\`._`}
26949
26965
  `;
26950
26966
  }
26951
26967
  function buildScriptMd(name, kind = DEFAULT_SCENE_KIND) {
@@ -27090,7 +27106,7 @@ Single-asset requests (\`vibe generate image|video|speech|...\`) do NOT
27090
27106
  consult this file - run the generate command directly.
27091
27107
 
27092
27108
  Browse named styles: \`vibe scene list-styles\`. Re-seed from one with
27093
- \`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
27109
+ \`vibe init . --visual-style "Swiss Pulse"\` (idempotent).
27094
27110
 
27095
27111
  ## Brief and local media
27096
27112
 
@@ -27245,9 +27261,21 @@ npx hyperframes render
27245
27261
 
27246
27262
  ## Key Rules (for hand-authored scene HTML)
27247
27263
 
27264
+ 0. A scene file is a \`<template id="scene-<id>-template">\` wrapping ONE
27265
+ composition div - not a standalone \`<!doctype html>\` document:
27266
+ \`\`\`html
27267
+ <template id="scene-hook-template">
27268
+ <div data-composition-id="scene-hook" data-start="0" data-duration="3.3"
27269
+ data-width="1920" data-height="1080">\u2026</div>
27270
+ </template>
27271
+ \`\`\`
27272
+ The composition div **MUST** carry \`data-width\` and \`data-height\`.
27248
27273
  1. Every timed element needs \`data-start\`, \`data-duration\`, and \`data-track-index\`.
27249
27274
  2. Elements with timing **MUST** have \`class="clip"\` - the framework uses this for visibility control.
27250
- 3. Timelines must be paused and registered on \`window.__timelines\`:
27275
+ 3. Timelines must be paused and registered on \`window.__timelines\`, which is an
27276
+ OBJECT keyed by composition id - not an array, and \`.push()\` leaves the
27277
+ timeline undiscovered, which renders a black frame because \`gsap.from\` holds
27278
+ elements at their start state:
27251
27279
  \`\`\`js
27252
27280
  window.__timelines = window.__timelines || {};
27253
27281
  window.__timelines["composition-id"] = gsap.timeline({ paused: true });
@@ -28111,7 +28139,7 @@ function insertClipIntoRoot(rootHtml, clip) {
28111
28139
  const closeMatch = rootHtml.match(/\n(\s*)<\/div>(\s*\n\s*<script>)/);
28112
28140
  if (!closeMatch) {
28113
28141
  throw new Error(
28114
- "Could not find root composition closing </div>. Ensure index.html follows the layout from `vibe scene init`."
28142
+ "Could not find root composition closing </div>. Ensure index.html follows the layout from `vibe init`."
28115
28143
  );
28116
28144
  }
28117
28145
  const closeIdx = closeMatch.index;
@@ -29393,10 +29421,10 @@ async function getComposePrompts(opts) {
29393
29421
  error: msg
29394
29422
  });
29395
29423
  if (!existsSync6(designPath)) {
29396
- return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
29424
+ return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe init <dir>\` first.`);
29397
29425
  }
29398
29426
  if (!existsSync6(storyboardPath2)) {
29399
- return baseError(`STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
29427
+ return baseError(`STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
29400
29428
  }
29401
29429
  if (!hyperframesSkillAvailable(projectDir)) {
29402
29430
  warnings.push(
@@ -29476,7 +29504,9 @@ function buildInstructions(args) {
29476
29504
  if (args.skillRef) {
29477
29505
  lines.push(`1. Read \`${args.skillRef}\` for the Hyperframes framework rules + house style. This is the visual-identity hard-gate.`);
29478
29506
  } else {
29479
- lines.push(`1. Run \`vibe scene install-skill\` to install the Hyperframes composition rules, then read them.`);
29507
+ lines.push(
29508
+ `1. Read this project's \`AGENTS.md\` section "Key Rules (for hand-authored scene HTML)" - written by \`vibe init\`, and the authoritative local contract. \`vibe scene install-skill\` adds Hyperframes' house style on top, but note it installs a ROUTER whose full contract sits behind \`/hyperframes-core\`, which resolves only in a host that has upstream's Hyperframes skills.`
29509
+ );
29480
29510
  }
29481
29511
  lines.push(`2. Read \`DESIGN.md\` for project-specific palette, typography, motion signature.`);
29482
29512
  if (args.compositionRef) {
@@ -29486,6 +29516,7 @@ function buildInstructions(args) {
29486
29516
  lines.push(`3b. Use each beat's \`finalDurationSec\` (narration-synced) for \`data-duration\` and timeline anchors when present \u2014 NOT the storyboard \`duration\`, which is only the minimum. Scenes composed at the storyboard duration end early and render black tails.`);
29487
29517
  lines.push(`3c. Never give inner \`.clip\` elements a non-zero \`data-start\` \u2014 the renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render all phases at once (overlapping text). Use full-window clips and GSAP autoAlpha phase transitions instead. Also keep beats 6-15s; split anything longer in the storyboard first.`);
29488
29518
  lines.push(`3d. If your environment cannot write files (e.g. Claude Desktop / MCP-only hosts), author each beat's HTML and submit it with the \`scene_submit\` tool (beat id + html). It validates with the same Hyperframes lint and writes the file for you; on lint errors it returns the findings without writing \u2014 fix and resubmit.`);
29519
+ lines.push(`3e. Hard requirements the linter fails on, so satisfy them even without the full contract. A scene file is a \`<template id="scene-<id>-template">\` wrapping ONE \`<div data-composition-id="scene-<id>" data-start="0" data-duration="<sec>" data-width="1920" data-height="1080">\` \u2014 not a standalone \`<!doctype html>\` document. Register the paused GSAP timeline on the registry OBJECT keyed by composition id: \`window.__timelines = window.__timelines || {}; window.__timelines["scene-<id>"] = tl;\` \u2014 it is not an array, and \`.push()\` leaves the timeline undiscovered, which renders a black frame because \`gsap.from\` holds elements at their start state. Also put \`class="clip"\` on every timed element and give timeline-visible elements a stable \`id\`. Generate a reference scene with \`vibe scene add <id> --style <preset> --no-audio --no-image\` and match its shape.`);
29489
29520
  if (args.beatCount > 1) {
29490
29521
  lines.push(`4. After authoring all ${args.beatCount} beat(s), run \`vibe build <project> --stage sync --json\` to assemble the root composition (index.html) from the fragments. Lint needs that root, so sync comes first.`);
29491
29522
  } else if (args.filtered) {
@@ -30771,7 +30802,9 @@ async function executeSceneAdd(opts) {
30771
30802
  error
30772
30803
  });
30773
30804
  if (!await pathExists2(rootPath)) {
30774
- return errResult(`Root composition not found: ${rootPath}. Run \`vibe scene init\` first.`);
30805
+ return errResult(
30806
+ `Root composition not found: ${rootPath}. Run \`vibe build\` once to create the render scaffold, or scaffold it up front with \`vibe init <dir> --profile full\`.`
30807
+ );
30775
30808
  }
30776
30809
  if (!opts.force && await pathExists2(scenePath)) {
30777
30810
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
@@ -31313,7 +31346,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
31313
31346
  console.log(source_default.dim("Show details: "), source_default.cyan('vibe scene list-styles "<name>"'));
31314
31347
  console.log(
31315
31348
  source_default.dim("Seed DESIGN.md:"),
31316
- source_default.cyan('vibe scene init <dir> --visual-style "<name>"')
31349
+ source_default.cyan('vibe init <dir> --visual-style "<name>"')
31317
31350
  );
31318
31351
  return;
31319
31352
  }
@@ -31348,7 +31381,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
31348
31381
  console.log();
31349
31382
  console.log(
31350
31383
  source_default.dim("Seed DESIGN.md:"),
31351
- source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`)
31384
+ source_default.cyan(`vibe init <dir> --visual-style "${style.name}"`)
31352
31385
  );
31353
31386
  });
31354
31387
  sceneCommand.command("add").description("Add a new scene to a project: AI narration + image + per-scene HTML").argument("<name>", "Scene name (slugified into the composition id)").option("--style <preset>", `Style preset: ${SCENE_PRESETS.join(", ")}`, "simple").option(
@@ -61218,7 +61251,7 @@ async function executeSceneRender(opts = {}) {
61218
61251
  kind: "render",
61219
61252
  beat: opts.beatId ?? null,
61220
61253
  root,
61221
- error: `Root composition not found: ${resolve66(projectDir, root)}. Run \`vibe scene init\` first.`
61254
+ error: `Root composition not found: ${resolve66(projectDir, root)}. Run \`vibe build ${projectDir}\` once to create the render scaffold, or scaffold it up front with \`vibe init <dir> --profile full\`.`
61222
61255
  };
61223
61256
  }
61224
61257
  const chrome = await preflightChrome();
@@ -61709,7 +61742,7 @@ async function executeSceneBuild(opts) {
61709
61742
  phase: "failed",
61710
61743
  mode,
61711
61744
  selectedStage,
61712
- error: `STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
61745
+ error: `STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
61713
61746
  beats: [],
61714
61747
  stageReports,
61715
61748
  warnings,
@@ -74249,7 +74282,7 @@ init_source();
74249
74282
  // package.json
74250
74283
  var package_default = {
74251
74284
  name: "@vibeframe/cli",
74252
- version: "0.113.29",
74285
+ version: "0.113.30",
74253
74286
  description: "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
74254
74287
  private: false,
74255
74288
  license: "MIT",
@@ -84605,35 +84638,17 @@ var planCommand = new Command("plan").description("Read STORYBOARD.md and show b
84605
84638
  return;
84606
84639
  }
84607
84640
  if (maxCost !== void 0 && plan.estimatedCostUsd > maxCost) {
84608
- const warning = `Estimated cost $${plan.estimatedCostUsd.toFixed(2)} exceeds --max-cost $${maxCost.toFixed(2)}.`;
84609
- const data = {
84610
- ...plan,
84611
- costCapUsd: maxCost,
84612
- code: "COST_CAP_EXCEEDED",
84613
- message: warning,
84614
- suggestion: "Raise --max-cost or reduce the stage/provider scope.",
84615
- recoverable: true,
84616
- retryWith: [
84617
- ...plan.retryWith,
84618
- `vibe plan ${projectDirArg} --stage ${stage} --skip-backdrop --json`,
84619
- `vibe build ${projectDirArg} --stage ${stage} --max-cost ${plan.estimatedCostUsd} --json`
84620
- ].filter((value, index, all) => value.length > 0 && all.indexOf(value) === index)
84621
- };
84622
- if (isJsonMode()) {
84623
- outputSuccess({
84624
- command: "plan",
84625
- startedAt,
84626
- data,
84627
- warnings: [warning]
84628
- });
84629
- process.exitCode = 1;
84630
- return;
84631
- }
84632
84641
  exitWithError(
84633
- usageError(
84634
- `Estimated cost $${plan.estimatedCostUsd.toFixed(2)} exceeds --max-cost $${maxCost.toFixed(2)}.`,
84635
- `Raise --max-cost or reduce the stage/provider scope.`
84636
- )
84642
+ costCapError({
84643
+ estimatedCostUsd: plan.estimatedCostUsd,
84644
+ maxCostUsd: maxCost,
84645
+ retryWith: [
84646
+ ...plan.retryWith,
84647
+ `vibe plan ${projectDirArg} --stage ${stage} --skip-backdrop --json`,
84648
+ `vibe build ${projectDirArg} --stage ${stage} --max-cost ${plan.estimatedCostUsd} --json`
84649
+ ].filter((value, index, all) => value.length > 0 && all.indexOf(value) === index),
84650
+ data: { ...plan, costCapUsd: maxCost }
84651
+ })
84637
84652
  );
84638
84653
  }
84639
84654
  if (isJsonMode()) {
@@ -84844,38 +84859,18 @@ Advanced equivalent: \`vibe scene build\`.`
84844
84859
  return;
84845
84860
  }
84846
84861
  if (maxCostUsd !== void 0 && plan.estimatedCostUsd > maxCostUsd) {
84847
- const warning = `Estimated cost $${plan.estimatedCostUsd.toFixed(2)} exceeds --max-cost $${maxCostUsd.toFixed(2)}.`;
84848
- const retryWith = unique6([
84849
- ...plan.retryWith,
84850
- `vibe build ${projectDirArg} --stage ${stage} --skip-backdrop --json`,
84851
- `vibe build ${projectDirArg} --stage ${stage} --max-cost ${plan.estimatedCostUsd} --json`
84852
- ]);
84853
- if (isJsonMode() || isQuietMode()) {
84854
- outputSuccess({
84855
- command: "build",
84856
- startedAt,
84857
- dryRun: true,
84858
- warnings: [warning],
84859
- data: {
84860
- params,
84861
- plan,
84862
- costCapUsd: maxCostUsd,
84863
- code: "COST_CAP_EXCEEDED",
84864
- message: warning,
84865
- suggestion: "Raise --max-cost or reduce the stage/provider scope.",
84866
- retryWith,
84867
- recoverable: true
84868
- }
84869
- });
84870
- process.exitCode = 1;
84871
- return;
84872
- }
84873
- console.log(source_default.red(warning));
84874
- if (retryWith.length > 0) {
84875
- console.log(source_default.dim(`Retry: ${retryWith[0]}`));
84876
- }
84877
- process.exitCode = 1;
84878
- return;
84862
+ exitWithError(
84863
+ costCapError({
84864
+ estimatedCostUsd: plan.estimatedCostUsd,
84865
+ maxCostUsd,
84866
+ retryWith: unique6([
84867
+ ...plan.retryWith,
84868
+ `vibe build ${projectDirArg} --stage ${stage} --skip-backdrop --json`,
84869
+ `vibe build ${projectDirArg} --stage ${stage} --max-cost ${plan.estimatedCostUsd} --json`
84870
+ ]),
84871
+ data: { params, plan, costCapUsd: maxCostUsd }
84872
+ })
84873
+ );
84879
84874
  }
84880
84875
  if (!isJsonMode() && !isQuietMode()) {
84881
84876
  printBuildDryRun(projectDirArg, params, plan);
@@ -24010,6 +24010,9 @@ function exitWithError(err) {
24010
24010
  if (err.suggestion) {
24011
24011
  console.error(source_default.dim(` ${err.suggestion}`));
24012
24012
  }
24013
+ if (err.retryWith && err.retryWith.length > 0) {
24014
+ console.error(source_default.dim(` Retry: ${err.retryWith[0]}`));
24015
+ }
24013
24016
  console.error();
24014
24017
  }
24015
24018
  process.exit(err.exitCode);
@@ -26127,7 +26130,7 @@ ${avoid}
26127
26130
  ---
26128
26131
 
26129
26132
  _Browse other named styles: \`vibe scene list-styles\`_
26130
- ${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
26133
+ ${style ? `_This file was seeded by \`vibe init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe init <dir> --visual-style "<name>"\`._`}
26131
26134
  `;
26132
26135
  }
26133
26136
  function buildScriptMd(name, kind = DEFAULT_SCENE_KIND) {
@@ -26272,7 +26275,7 @@ Single-asset requests (\`vibe generate image|video|speech|...\`) do NOT
26272
26275
  consult this file - run the generate command directly.
26273
26276
 
26274
26277
  Browse named styles: \`vibe scene list-styles\`. Re-seed from one with
26275
- \`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
26278
+ \`vibe init . --visual-style "Swiss Pulse"\` (idempotent).
26276
26279
 
26277
26280
  ## Brief and local media
26278
26281
 
@@ -26427,9 +26430,21 @@ npx hyperframes render
26427
26430
 
26428
26431
  ## Key Rules (for hand-authored scene HTML)
26429
26432
 
26433
+ 0. A scene file is a \`<template id="scene-<id>-template">\` wrapping ONE
26434
+ composition div - not a standalone \`<!doctype html>\` document:
26435
+ \`\`\`html
26436
+ <template id="scene-hook-template">
26437
+ <div data-composition-id="scene-hook" data-start="0" data-duration="3.3"
26438
+ data-width="1920" data-height="1080">\u2026</div>
26439
+ </template>
26440
+ \`\`\`
26441
+ The composition div **MUST** carry \`data-width\` and \`data-height\`.
26430
26442
  1. Every timed element needs \`data-start\`, \`data-duration\`, and \`data-track-index\`.
26431
26443
  2. Elements with timing **MUST** have \`class="clip"\` - the framework uses this for visibility control.
26432
- 3. Timelines must be paused and registered on \`window.__timelines\`:
26444
+ 3. Timelines must be paused and registered on \`window.__timelines\`, which is an
26445
+ OBJECT keyed by composition id - not an array, and \`.push()\` leaves the
26446
+ timeline undiscovered, which renders a black frame because \`gsap.from\` holds
26447
+ elements at their start state:
26433
26448
  \`\`\`js
26434
26449
  window.__timelines = window.__timelines || {};
26435
26450
  window.__timelines["composition-id"] = gsap.timeline({ paused: true });
@@ -30750,10 +30765,10 @@ async function getComposePrompts(opts) {
30750
30765
  error: msg
30751
30766
  });
30752
30767
  if (!existsSync8(designPath)) {
30753
- return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
30768
+ return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe init <dir>\` first.`);
30754
30769
  }
30755
30770
  if (!existsSync8(storyboardPath)) {
30756
- return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
30771
+ return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
30757
30772
  }
30758
30773
  if (!hyperframesSkillAvailable(projectDir)) {
30759
30774
  warnings.push(
@@ -30833,7 +30848,9 @@ function buildInstructions(args) {
30833
30848
  if (args.skillRef) {
30834
30849
  lines.push(`1. Read \`${args.skillRef}\` for the Hyperframes framework rules + house style. This is the visual-identity hard-gate.`);
30835
30850
  } else {
30836
- lines.push(`1. Run \`vibe scene install-skill\` to install the Hyperframes composition rules, then read them.`);
30851
+ lines.push(
30852
+ `1. Read this project's \`AGENTS.md\` section "Key Rules (for hand-authored scene HTML)" - written by \`vibe init\`, and the authoritative local contract. \`vibe scene install-skill\` adds Hyperframes' house style on top, but note it installs a ROUTER whose full contract sits behind \`/hyperframes-core\`, which resolves only in a host that has upstream's Hyperframes skills.`
30853
+ );
30837
30854
  }
30838
30855
  lines.push(`2. Read \`DESIGN.md\` for project-specific palette, typography, motion signature.`);
30839
30856
  if (args.compositionRef) {
@@ -30843,6 +30860,7 @@ function buildInstructions(args) {
30843
30860
  lines.push(`3b. Use each beat's \`finalDurationSec\` (narration-synced) for \`data-duration\` and timeline anchors when present \u2014 NOT the storyboard \`duration\`, which is only the minimum. Scenes composed at the storyboard duration end early and render black tails.`);
30844
30861
  lines.push(`3c. Never give inner \`.clip\` elements a non-zero \`data-start\` \u2014 the renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render all phases at once (overlapping text). Use full-window clips and GSAP autoAlpha phase transitions instead. Also keep beats 6-15s; split anything longer in the storyboard first.`);
30845
30862
  lines.push(`3d. If your environment cannot write files (e.g. Claude Desktop / MCP-only hosts), author each beat's HTML and submit it with the \`scene_submit\` tool (beat id + html). It validates with the same Hyperframes lint and writes the file for you; on lint errors it returns the findings without writing \u2014 fix and resubmit.`);
30863
+ lines.push(`3e. Hard requirements the linter fails on, so satisfy them even without the full contract. A scene file is a \`<template id="scene-<id>-template">\` wrapping ONE \`<div data-composition-id="scene-<id>" data-start="0" data-duration="<sec>" data-width="1920" data-height="1080">\` \u2014 not a standalone \`<!doctype html>\` document. Register the paused GSAP timeline on the registry OBJECT keyed by composition id: \`window.__timelines = window.__timelines || {}; window.__timelines["scene-<id>"] = tl;\` \u2014 it is not an array, and \`.push()\` leaves the timeline undiscovered, which renders a black frame because \`gsap.from\` holds elements at their start state. Also put \`class="clip"\` on every timed element and give timeline-visible elements a stable \`id\`. Generate a reference scene with \`vibe scene add <id> --style <preset> --no-audio --no-image\` and match its shape.`);
30846
30864
  if (args.beatCount > 1) {
30847
30865
  lines.push(`4. After authoring all ${args.beatCount} beat(s), run \`vibe build <project> --stage sync --json\` to assemble the root composition (index.html) from the fragments. Lint needs that root, so sync comes first.`);
30848
30866
  } else if (args.filtered) {
@@ -31941,7 +31959,7 @@ async function executeSceneRender(opts = {}) {
31941
31959
  kind: "render",
31942
31960
  beat: opts.beatId ?? null,
31943
31961
  root,
31944
- error: `Root composition not found: ${resolve20(projectDir, root)}. Run \`vibe scene init\` first.`
31962
+ error: `Root composition not found: ${resolve20(projectDir, root)}. Run \`vibe build ${projectDir}\` once to create the render scaffold, or scaffold it up front with \`vibe init <dir> --profile full\`.`
31945
31963
  };
31946
31964
  }
31947
31965
  const chrome = await preflightChrome();
@@ -34189,7 +34207,7 @@ async function executeSceneBuild(opts) {
34189
34207
  phase: "failed",
34190
34208
  mode,
34191
34209
  selectedStage,
34192
- error: `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
34210
+ error: `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
34193
34211
  beats: [],
34194
34212
  stageReports,
34195
34213
  warnings,
@@ -52976,7 +52994,7 @@ function insertClipIntoRoot(rootHtml, clip) {
52976
52994
  const closeMatch = rootHtml.match(/\n(\s*)<\/div>(\s*\n\s*<script>)/);
52977
52995
  if (!closeMatch) {
52978
52996
  throw new Error(
52979
- "Could not find root composition closing </div>. Ensure index.html follows the layout from `vibe scene init`."
52997
+ "Could not find root composition closing </div>. Ensure index.html follows the layout from `vibe init`."
52980
52998
  );
52981
52999
  }
52982
53000
  const closeIdx = closeMatch.index;
@@ -53377,7 +53395,7 @@ sceneCommand.command("list-styles").description("List vendored visual styles (or
53377
53395
  console.log(source_default.dim("Show details: "), source_default.cyan('vibe scene list-styles "<name>"'));
53378
53396
  console.log(
53379
53397
  source_default.dim("Seed DESIGN.md:"),
53380
- source_default.cyan('vibe scene init <dir> --visual-style "<name>"')
53398
+ source_default.cyan('vibe init <dir> --visual-style "<name>"')
53381
53399
  );
53382
53400
  return;
53383
53401
  }
@@ -53412,7 +53430,7 @@ sceneCommand.command("list-styles").description("List vendored visual styles (or
53412
53430
  console.log();
53413
53431
  console.log(
53414
53432
  source_default.dim("Seed DESIGN.md:"),
53415
- source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`)
53433
+ source_default.cyan(`vibe init <dir> --visual-style "${style.name}"`)
53416
53434
  );
53417
53435
  });
53418
53436
  sceneCommand.command("add").description("Add a new scene to a project: AI narration + image + per-scene HTML").argument("<name>", "Scene name (slugified into the composition id)").option("--style <preset>", `Style preset: ${SCENE_PRESETS.join(", ")}`, "simple").option(
@@ -53670,7 +53688,9 @@ async function executeSceneAdd(opts) {
53670
53688
  error
53671
53689
  });
53672
53690
  if (!await pathExists2(rootPath)) {
53673
- return errResult(`Root composition not found: ${rootPath}. Run \`vibe scene init\` first.`);
53691
+ return errResult(
53692
+ `Root composition not found: ${rootPath}. Run \`vibe build\` once to create the render scaffold, or scaffold it up front with \`vibe init <dir> --profile full\`.`
53693
+ );
53674
53694
  }
53675
53695
  if (!opts.force && await pathExists2(scenePath)) {
53676
53696
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.113.29",
3
+ "version": "0.113.30",
4
4
  "description": "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -91,8 +91,8 @@
91
91
  "tsx": "^4.21.0",
92
92
  "typescript": "^5.3.3",
93
93
  "vitest": "^1.2.2",
94
- "@vibeframe/ai-providers": "0.113.29",
95
- "@vibeframe/core": "0.113.29"
94
+ "@vibeframe/core": "0.113.30",
95
+ "@vibeframe/ai-providers": "0.113.30"
96
96
  },
97
97
  "engines": {
98
98
  "node": ">=20"