@vibeframe/cli 0.83.0 → 0.85.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 +193 -24
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -35865,8 +35865,9 @@ var init_video = __esm({
35865
35865
  // src/commands/_shared/cost-tier.ts
35866
35866
  function applyTier(cmd, tier) {
35867
35867
  cmd[COST_TIER_KEY] = tier;
35868
+ const colored = TIER_COLOR[tier](`Cost: ${tier} (${TIER_DESCRIPTION[tier]})`);
35868
35869
  cmd.addHelpText("after", `
35869
- Cost: ${tier} (${TIER_DESCRIPTION[tier]})
35870
+ ${colored}
35870
35871
  `);
35871
35872
  return cmd;
35872
35873
  }
@@ -35879,16 +35880,23 @@ function applyTiers(parent, tiers) {
35879
35880
  if (tier) applyTier(cmd, tier);
35880
35881
  }
35881
35882
  }
35882
- var TIER_DESCRIPTION, COST_TIER_KEY;
35883
+ var TIER_DESCRIPTION, TIER_COLOR, COST_TIER_KEY;
35883
35884
  var init_cost_tier = __esm({
35884
35885
  "src/commands/_shared/cost-tier.ts"() {
35885
35886
  "use strict";
35887
+ init_source();
35886
35888
  TIER_DESCRIPTION = {
35887
35889
  "free": "FFmpeg only, no API call",
35888
35890
  "low": "$0.01\u2013$0.10 per call",
35889
35891
  "high": "$1\u2013$5 per call",
35890
35892
  "very-high": "$5\u2013$50+ per call"
35891
35893
  };
35894
+ TIER_COLOR = {
35895
+ "free": source_default.green,
35896
+ "low": source_default.cyan,
35897
+ "high": source_default.yellow,
35898
+ "very-high": source_default.red
35899
+ };
35892
35900
  COST_TIER_KEY = Symbol.for("@vibeframe/cli.costTier");
35893
35901
  }
35894
35902
  });
@@ -51025,7 +51033,7 @@ function listWalkthroughs() {
51025
51033
  function isWalkthroughTopic(value) {
51026
51034
  return typeof value === "string" && WALKTHROUGH_TOPICS.includes(value);
51027
51035
  }
51028
- var SCENE_WALKTHROUGH, PIPELINE_WALKTHROUGH, META, CONTENT, WALKTHROUGH_TOPICS;
51036
+ var SCENE_WALKTHROUGH, PIPELINE_WALKTHROUGH, ARCHITECTURE_WALKTHROUGH, META, CONTENT, WALKTHROUGH_TOPICS;
51029
51037
  var init_walkthroughs = __esm({
51030
51038
  "src/commands/_shared/walkthroughs/walkthroughs.ts"() {
51031
51039
  "use strict";
@@ -51265,6 +51273,59 @@ When the user has a working shell sequence, extract steps:
51265
51273
 
51266
51274
  The \`compose\` action is the catch-all assembly step (audio mux, video
51267
51275
  overlay, etc.) \u2014 useful at the tail of a pipeline.
51276
+ `;
51277
+ ARCHITECTURE_WALKTHROUGH = `# vibe agent / build / run \u2014 when to pick which
51278
+
51279
+ The CLI has three orchestrating commands that coordinate other primitives:
51280
+ \`vibe agent\`, \`vibe build\`, \`vibe run\`. New users routinely ask which one
51281
+ they want for a given task. The full audit lives in
51282
+ [\`docs/cli-architecture.md\`](../../../docs/cli-architecture.md); this
51283
+ walkthrough is the operator-facing summary.
51284
+
51285
+ ## TL;DR
51286
+
51287
+ | | \`vibe agent\` | \`vibe build\` | \`vibe run\` |
51288
+ |---|---|---|---|
51289
+ | **Driving input** | natural-language prompt | \`STORYBOARD.md\` | YAML pipeline file |
51290
+ | **Interactivity** | REPL or one-shot | one-shot | one-shot |
51291
+ | **Reproducibility** | none | high (idempotent) | highest (checkpointed) |
51292
+ | **Budget caps** | per-session max-turns | none | \`--budget-usd\`, \`--budget-tokens\`, \`--max-errors\` |
51293
+ | **Resume after crash** | no | re-invoke | \`--resume\` |
51294
+ | **Best for** | exploration | finished script | repeatable workflows |
51295
+
51296
+ ## Decision tree
51297
+
51298
+ - "I want to play, I'll know it when I see it" \u2192 \`vibe agent\`
51299
+ - "I have a finished script + visual identity" \u2192 \`vibe build\`
51300
+ - "I want this to run again next month" \u2192 \`vibe run\`
51301
+
51302
+ If two seem to fit, pick the rightmost one \u2014 more reproducible, less surprise.
51303
+
51304
+ ## Cost-tier awareness (v0.83+)
51305
+
51306
+ Every primitive subcommand carries a cost tier (\`free\` / \`low\` / \`high\` /
51307
+ \`very-high\`). \`vibe schema --list\` exposes the tier as JSON, and each
51308
+ \`vibe <group> <sub> --help\` page shows it as a colored footer. Use
51309
+ \`vibe doctor --test-keys\` before kicking off any high-cost orchestrator \u2014
51310
+ a single bad key wastes time and money.
51311
+
51312
+ ## Cross-command primitives
51313
+
51314
+ All three commands ultimately call the same primitives:
51315
+ \`generate\` / \`edit\` / \`audio\` / \`inspect\` / \`detect\` / \`remix\`. The
51316
+ agent calls them through a tool manifest; \`build\` calls a curated subset
51317
+ (TTS + image + compose + render); \`run\` exposes 55+ actions one per primitive.
51318
+
51319
+ ## Known overlap
51320
+
51321
+ - \`vibe build\` is conceptually a 4-step pipeline; \`vibe run\` could express
51322
+ the same flow but build's STORYBOARD.md input + opinionated defaults are
51323
+ the value-add.
51324
+ - \`vibe agent\`'s tool registry currently exposes primitives, not
51325
+ orchestrators. Adding \`vibe.build\` / \`vibe.run\` as agent tools is on the
51326
+ table for a future major.
51327
+ - \`vibe run --resume\` has no analog in build/agent; build's idempotent
51328
+ re-invoke covers the common case.
51268
51329
  `;
51269
51330
  META = {
51270
51331
  scene: {
@@ -51303,13 +51364,32 @@ overlay, etc.) \u2014 useful at the tail of a pipeline.
51303
51364
  "vibe schema --list",
51304
51365
  "vibe doctor"
51305
51366
  ]
51367
+ },
51368
+ architecture: {
51369
+ title: "vibe agent / build / run \u2014 when to pick which",
51370
+ summary: "Compare the three orchestrating commands on the dimensions that actually decide the choice",
51371
+ steps: [
51372
+ "Pick agent for exploration (NL prompt, REPL, no checkpoints).",
51373
+ "Pick build for STORYBOARD.md \u2192 MP4 with opinionated defaults.",
51374
+ "Pick run for repeatable, budget-capped, checkpointed YAML pipelines.",
51375
+ "Run `vibe doctor --test-keys` before any high-cost orchestrator to validate keys upfront.",
51376
+ "Use `vibe schema --list` (cost field, v0.84) to plan the budget per step before kicking off `vibe run --budget-usd`."
51377
+ ],
51378
+ relatedCommands: [
51379
+ "vibe agent",
51380
+ "vibe build",
51381
+ "vibe run",
51382
+ "vibe doctor",
51383
+ "vibe schema --list"
51384
+ ]
51306
51385
  }
51307
51386
  };
51308
51387
  CONTENT = {
51309
51388
  scene: SCENE_WALKTHROUGH,
51310
- pipeline: PIPELINE_WALKTHROUGH
51389
+ pipeline: PIPELINE_WALKTHROUGH,
51390
+ architecture: ARCHITECTURE_WALKTHROUGH
51311
51391
  };
51312
- WALKTHROUGH_TOPICS = ["scene", "pipeline"];
51392
+ WALKTHROUGH_TOPICS = ["scene", "pipeline", "architecture"];
51313
51393
  }
51314
51394
  });
51315
51395
 
@@ -51423,22 +51503,30 @@ var init_key_live_test = __esm({
51423
51503
  signal
51424
51504
  )
51425
51505
  },
51426
- // The three below still have no cheap authenticated GET as of v0.83:
51427
- // fal lacks a documented validation endpoint, Kling auth is per-
51428
- // request HMAC-signed, ImgBB's only authenticated route is image
51429
- // upload. Surface them as `skipped` so the user knows we didn't
51430
- // blank-check.
51506
+ // The three below still have no cheap authenticated GET as of
51507
+ // 2026-04-30 (re-checked while shipping v0.85). Surface them as
51508
+ // `skipped` so the user knows we didn't blank-check; revisit when a
51509
+ // provider publishes an account/me endpoint.
51431
51510
  fal: {
51511
+ // Probed: /api/account, /v1/account, /v1/me, /queue/health, /v1/users/me,
51512
+ // /api/users/me, /v1/billing — all 404 with the standard `Authorization:
51513
+ // Key <key>` header. The platform docs reference an "Accounts API" but
51514
+ // its endpoint paths are unpublished as of this check.
51432
51515
  name: "fal.ai",
51433
- test: async () => ({ ok: false, skipped: true, message: "no auth-only endpoint published" })
51516
+ test: async () => ({ ok: false, skipped: true, message: "no auth-only endpoint (checked 2026-04-30)" })
51434
51517
  },
51435
51518
  kling: {
51519
+ // Per-request HMAC signing means we'd need to sign every probe with the
51520
+ // key's secret half plus a timestamp — possible, but the cheapest
51521
+ // signed endpoint is also a generation request. Not worth the code.
51436
51522
  name: "Kling",
51437
- test: async () => ({ ok: false, skipped: true, message: "per-request HMAC; can't test cheaply" })
51523
+ test: async () => ({ ok: false, skipped: true, message: "HMAC-signed per request (checked 2026-04-30)" })
51438
51524
  },
51439
51525
  imgbb: {
51526
+ // Only documented route is POST /1/upload. Probing it would consume an
51527
+ // upload quota even if we send a 1×1 PNG, so SKIP is the right call.
51440
51528
  name: "ImgBB",
51441
- test: async () => ({ ok: false, skipped: true, message: "only auth route is image upload" })
51529
+ test: async () => ({ ok: false, skipped: true, message: "only route is upload (checked 2026-04-30)" })
51442
51530
  }
51443
51531
  };
51444
51532
  }
@@ -62677,6 +62765,10 @@ function toAgentDefinition(t) {
62677
62765
  return {
62678
62766
  name: t.name,
62679
62767
  description: t.description,
62768
+ // Propagate the manifest's cost tier so the agent's executor can
62769
+ // mandate a confirm prompt for high-spend tools without needing a
62770
+ // parallel SSOT.
62771
+ cost: t.cost,
62680
62772
  parameters: {
62681
62773
  type: "object",
62682
62774
  properties,
@@ -62745,7 +62837,7 @@ init_source();
62745
62837
  // package.json
62746
62838
  var package_default = {
62747
62839
  name: "@vibeframe/cli",
62748
- version: "0.83.0",
62840
+ version: "0.85.0",
62749
62841
  description: "VibeFrame CLI - natural language video editing from the terminal",
62750
62842
  private: false,
62751
62843
  license: "MIT",
@@ -65086,15 +65178,18 @@ function listCommands(program3) {
65086
65178
  if (subCmds.length === 0) {
65087
65179
  const desc = group.description() || "";
65088
65180
  if (desc.toLowerCase().includes("deprecated")) continue;
65089
- commands.push({ path: name, description: desc });
65181
+ const cost = getCostTier(group);
65182
+ commands.push({ path: name, description: desc, ...cost ? { cost } : {} });
65090
65183
  continue;
65091
65184
  }
65092
65185
  for (const sub of subCmds) {
65093
65186
  const desc = sub.description() || "";
65094
65187
  if (desc.toLowerCase().includes("deprecated")) continue;
65188
+ const cost = getCostTier(sub);
65095
65189
  commands.push({
65096
65190
  path: `${name}.${sub.name()}`,
65097
- description: desc
65191
+ description: desc,
65192
+ ...cost ? { cost } : {}
65098
65193
  });
65099
65194
  }
65100
65195
  }
@@ -72365,10 +72460,26 @@ function maskSensitiveArgs(args) {
72365
72460
  }
72366
72461
  return masked;
72367
72462
  }
72463
+ function estimateToolCost(cost) {
72464
+ if (!cost) return 0;
72465
+ switch (cost) {
72466
+ case "free":
72467
+ return 0;
72468
+ case "low":
72469
+ return 0.05;
72470
+ case "medium":
72471
+ return 0.5;
72472
+ case "high":
72473
+ return 3;
72474
+ case "very-high":
72475
+ return 25;
72476
+ }
72477
+ }
72368
72478
  var AgentExecutor = class {
72369
72479
  adapter = null;
72370
72480
  registry;
72371
72481
  memory;
72482
+ cumulativeUsd = 0;
72372
72483
  context;
72373
72484
  config;
72374
72485
  initialized = false;
@@ -72456,12 +72567,39 @@ var AgentExecutor = class {
72456
72567
  }
72457
72568
  /**
72458
72569
  * Execute a single tool
72570
+ *
72571
+ * Confirm-prompt logic:
72572
+ * - `noConfirm: true` → never prompt (automation / CI)
72573
+ * - `confirmAlways: true` → prompt for every tool call
72574
+ * - default → prompt only when the tool's
72575
+ * cost is high/very-high (the
72576
+ * `costGate`)
72577
+ *
72578
+ * The two non-trivial cases require `confirmCallback` to be set; if
72579
+ * it isn't, no prompt fires and the tool runs.
72459
72580
  */
72460
72581
  async executeTool(toolCall) {
72461
- if (this.config.confirmCallback) {
72582
+ const def = this.registry.getDefinition(toolCall.name);
72583
+ const cost = def?.cost;
72584
+ const isExpensive = cost === "high" || cost === "very-high";
72585
+ if (this.config.budgetUsd !== void 0) {
72586
+ const estimate = estimateToolCost(cost);
72587
+ if (this.cumulativeUsd + estimate > this.config.budgetUsd) {
72588
+ const remaining = (this.config.budgetUsd - this.cumulativeUsd).toFixed(2);
72589
+ return {
72590
+ toolCallId: toolCall.id,
72591
+ success: false,
72592
+ output: "",
72593
+ error: `Budget exceeded: ${toolCall.name} estimated at $${estimate.toFixed(2)} but only $${remaining} remains of $${this.config.budgetUsd.toFixed(2)} cap.`
72594
+ };
72595
+ }
72596
+ }
72597
+ const shouldConfirm = !this.config.noConfirm && this.config.confirmCallback && (this.config.confirmAlways || isExpensive);
72598
+ if (shouldConfirm && this.config.confirmCallback) {
72462
72599
  const confirmed = await this.config.confirmCallback(
72463
72600
  toolCall.name,
72464
- toolCall.arguments
72601
+ toolCall.arguments,
72602
+ cost
72465
72603
  );
72466
72604
  if (!confirmed) {
72467
72605
  return {
@@ -72477,8 +72615,18 @@ var AgentExecutor = class {
72477
72615
  this.context
72478
72616
  );
72479
72617
  result.toolCallId = toolCall.id;
72618
+ if (result.success) {
72619
+ this.cumulativeUsd += estimateToolCost(cost);
72620
+ }
72480
72621
  return result;
72481
72622
  }
72623
+ /**
72624
+ * Cumulative tier-estimated USD spent in this session. Updates after
72625
+ * every successful tool call. Useful for status output / `tools` REPL.
72626
+ */
72627
+ getCumulativeUsd() {
72628
+ return this.cumulativeUsd;
72629
+ }
72482
72630
  /**
72483
72631
  * Update context (e.g., when project changes)
72484
72632
  */
@@ -72518,7 +72666,7 @@ init_config();
72518
72666
  init_tty();
72519
72667
  init_api_key();
72520
72668
  init_output();
72521
- async function promptConfirm2(toolName, args) {
72669
+ async function promptConfirm2(toolName, args, cost) {
72522
72670
  const { createInterface: createInterface4 } = await import("node:readline");
72523
72671
  const rl = createInterface4({
72524
72672
  input: process.stdin,
@@ -72528,7 +72676,15 @@ async function promptConfirm2(toolName, args) {
72528
72676
  return new Promise((resolve64) => {
72529
72677
  const argsStr = JSON.stringify(args, null, 2);
72530
72678
  console.log();
72531
- console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?`));
72679
+ const tierColor = {
72680
+ "free": source_default.green,
72681
+ "low": source_default.cyan,
72682
+ "medium": source_default.yellow,
72683
+ "high": source_default.yellow,
72684
+ "very-high": source_default.red
72685
+ };
72686
+ const costBadge = cost ? ` ${tierColor[cost](`[${cost.toUpperCase()}]`)}` : "";
72687
+ console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?${costBadge}`));
72532
72688
  console.log(source_default.dim(argsStr));
72533
72689
  rl.question(source_default.cyan("(y/n): "), (answer) => {
72534
72690
  rl.close();
@@ -72538,7 +72694,12 @@ async function promptConfirm2(toolName, args) {
72538
72694
  }
72539
72695
  async function startAgent(options = {}) {
72540
72696
  const isNonInteractive = !!options.input;
72541
- const confirmMode = options.confirm || false;
72697
+ const confirmAlways = options.confirm || false;
72698
+ const noConfirm = options.noConfirm || false;
72699
+ const budgetUsd = options.budgetUsd ? parseFloat(options.budgetUsd) : void 0;
72700
+ if (budgetUsd !== void 0 && (Number.isNaN(budgetUsd) || budgetUsd < 0)) {
72701
+ exitWithError(generalError(`Invalid --budget-usd: ${options.budgetUsd}`, "Must be a non-negative number (e.g., --budget-usd 5)"));
72702
+ }
72542
72703
  if (!isNonInteractive && !hasTTY()) {
72543
72704
  exitWithError(generalError("Agent mode requires a terminal.", "Run 'vibe agent' directly from your terminal, or use --input <query> for non-interactive mode."));
72544
72705
  }
@@ -72575,7 +72736,13 @@ async function startAgent(options = {}) {
72575
72736
  maxTurns,
72576
72737
  verbose,
72577
72738
  projectPath: options.project,
72578
- confirmCallback: confirmMode ? promptConfirm2 : void 0
72739
+ // Always supply the callback so the cost gate (high/very-high
72740
+ // tools) can prompt even without `--confirm`. The executor
72741
+ // decides whether to invoke based on `confirmAlways` / `noConfirm`.
72742
+ confirmCallback: noConfirm ? void 0 : promptConfirm2,
72743
+ confirmAlways,
72744
+ noConfirm,
72745
+ budgetUsd
72579
72746
  });
72580
72747
  await agent.initialize();
72581
72748
  spinner2.succeed(source_default.green("Agent initialized"));
@@ -72612,9 +72779,11 @@ async function startAgent(options = {}) {
72612
72779
  console.log();
72613
72780
  console.log(source_default.dim(` v${version} \xB7 ${provider}${options.model ? ` \xB7 ${options.model}` : ""} \xB7 ${cwd}`));
72614
72781
  console.log();
72782
+ const confirmStatus = noConfirm ? source_default.dim("no-confirm") : confirmAlways ? source_default.yellow("confirm mode") : source_default.dim("cost gate (high/very-high)");
72615
72783
  const statusParts = [
72616
72784
  source_default.green(`${toolCount} tools`),
72617
- confirmMode ? source_default.yellow("confirm mode") : null,
72785
+ confirmStatus,
72786
+ budgetUsd !== void 0 ? source_default.magenta(`budget: $${budgetUsd.toFixed(2)}`) : null,
72618
72787
  options.project ? source_default.blue(`project: ${options.project}`) : null
72619
72788
  ].filter(Boolean);
72620
72789
  console.log(source_default.dim(" ") + statusParts.join(source_default.dim(" \xB7 ")));
@@ -72728,7 +72897,7 @@ async function startAgent(options = {}) {
72728
72897
  rl.prompt();
72729
72898
  });
72730
72899
  }
72731
- var agentCommand = new Command("agent").description("Start the AI agent with natural language interface").option("-p, --provider <provider>", "LLM provider (openai, claude, gemini, ollama, xai, openrouter)", "openai").option("-m, --model <model>", "Model to use (provider-specific)").option("--project <path>", "Timeline file or directory to load").option("-v, --verbose", "Show verbose output including tool calls").option("--max-turns <n>", "Maximum turns per request", "10").option("-i, --input <query>", "Run a single query and exit (non-interactive)").option("-c, --confirm", "Confirm before each tool execution").action(async (options) => {
72900
+ var agentCommand = new Command("agent").description("Start the AI agent with natural language interface").option("-p, --provider <provider>", "LLM provider (openai, claude, gemini, ollama, xai, openrouter)", "openai").option("-m, --model <model>", "Model to use (provider-specific)").option("--project <path>", "Timeline file or directory to load").option("-v, --verbose", "Show verbose output including tool calls").option("--max-turns <n>", "Maximum turns per request", "10").option("-i, --input <query>", "Run a single query and exit (non-interactive)").option("-c, --confirm", "Confirm before EVERY tool execution (overrides cost gate)").option("--no-confirm", "Disable all confirm prompts including the high/very-high cost gate (CI / automation)").option("--budget-usd <usd>", "Reject tool calls past this cumulative USD ceiling (tier-estimated, conservative)").action(async (options) => {
72732
72901
  await startAgent(options);
72733
72902
  });
72734
72903
  function getEnvVar(provider) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.83.0",
3
+ "version": "0.85.0",
4
4
  "description": "VibeFrame CLI - natural language video editing from the terminal",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -183,8 +183,8 @@
183
183
  "tsx": "^4.21.0",
184
184
  "typescript": "^5.3.3",
185
185
  "vitest": "^1.2.2",
186
- "@vibeframe/ai-providers": "0.83.0",
187
- "@vibeframe/core": "0.83.0"
186
+ "@vibeframe/ai-providers": "0.85.0",
187
+ "@vibeframe/core": "0.85.0"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"