@vibeframe/cli 0.80.1 → 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 +396 -156
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -14372,7 +14372,11 @@ var init_api_keys = __esm({
14372
14372
  showInSetup: true,
14373
14373
  setupDescription: "Seedance 2.0 video gen ($$, default since v0.57)",
14374
14374
  envExampleComment: "fal.ai API Key (Seedance 2.0 video \u2014 default text/image-to-video since v0.57, Artificial Analysis ELO #2)",
14375
- 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>" }
14376
14380
  });
14377
14381
  defineApiKey({
14378
14382
  configKey: "xai",
@@ -14433,6 +14437,10 @@ var init_api_keys = __esm({
14433
14437
  // not prompted in setup wizard — internal upload host
14434
14438
  envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
14435
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" },
14436
14444
  // ImgBB has no provider class (envvar-only); doctor still shows what it
14437
14445
  // unlocks at the apiKey level.
14438
14446
  commandsUnlocked: [
@@ -17264,18 +17272,18 @@ ${transcriptWithTimestamps}`
17264
17272
  return emptyViralAnalysis("Analysis failed");
17265
17273
  }
17266
17274
  }
17267
- async function suggestPlatformCuts(api, transcriptSegments, viralAnalysis, platform2, clips) {
17268
- const emptyResult = { platform: platform2.id, segments: [], totalDuration: 0 };
17275
+ async function suggestPlatformCuts(api, transcriptSegments, viralAnalysis, platform3, clips) {
17276
+ const emptyResult = { platform: platform3.id, segments: [], totalDuration: 0 };
17269
17277
  const transcriptWithTimestamps = transcriptSegments.map((seg) => `[${seg.startTime.toFixed(1)}s - ${seg.endTime.toFixed(1)}s] ${seg.text}`).join("\n");
17270
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");
17271
- const systemPrompt = `You are an expert video editor optimizing content for ${platform2.name}.
17279
+ const systemPrompt = `You are an expert video editor optimizing content for ${platform3.name}.
17272
17280
 
17273
17281
  Platform requirements:
17274
- - Aspect ratio: ${platform2.aspectRatio}
17275
- - Maximum duration: ${platform2.maxDuration}s
17276
- - Ideal duration: ${platform2.idealDuration.min}-${platform2.idealDuration.max}s
17277
- - Needs strong hook: ${platform2.features.hook ? "Yes" : "No"}
17278
- - Needs captions: ${platform2.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"}
17279
17287
 
17280
17288
  Previous analysis found:
17281
17289
  - Hook strength: ${viralAnalysis.hookStrength}%
@@ -17291,7 +17299,7 @@ Your task:
17291
17299
 
17292
17300
  Respond with JSON only:
17293
17301
  {
17294
- "platform": "${platform2.id}",
17302
+ "platform": "${platform3.id}",
17295
17303
  "segments": [
17296
17304
  {"sourceClipId": "clip-id", "startTime": 0, "endTime": 45.5, "priority": 1.0}
17297
17305
  ],
@@ -17302,7 +17310,7 @@ Respond with JSON only:
17302
17310
  system: systemPrompt,
17303
17311
  messages: [{
17304
17312
  role: "user",
17305
- content: `Optimize this video for ${platform2.name}:
17313
+ content: `Optimize this video for ${platform3.name}:
17306
17314
 
17307
17315
  ## Transcript:
17308
17316
  ${transcriptWithTimestamps}
@@ -17998,10 +18006,10 @@ Instruction: ${instruction}`
17998
18006
  }
17999
18007
  return analyzeViralPotential(this.api, transcriptSegments, projectMeta, targetPlatforms);
18000
18008
  }
18001
- async suggestPlatformCuts(transcriptSegments, viralAnalysis, platform2, clips) {
18009
+ async suggestPlatformCuts(transcriptSegments, viralAnalysis, platform3, clips) {
18002
18010
  if (!this.api)
18003
- return { platform: platform2.id, segments: [], totalDuration: 0 };
18004
- return suggestPlatformCuts(this.api, transcriptSegments, viralAnalysis, platform2, clips);
18011
+ return { platform: platform3.id, segments: [], totalDuration: 0 };
18012
+ return suggestPlatformCuts(this.api, transcriptSegments, viralAnalysis, platform3, clips);
18005
18013
  }
18006
18014
  async generateViralCaptions(transcriptSegments, style) {
18007
18015
  if (!this.api)
@@ -33626,7 +33634,7 @@ Examples:
33626
33634
  }
33627
33635
  }
33628
33636
  } else if (provider === "runway") {
33629
- const { spawn: spawn3 } = await import("child_process");
33637
+ const { spawn: spawn4 } = await import("child_process");
33630
33638
  const __filename2 = fileURLToPath(import.meta.url);
33631
33639
  const __dirname2 = dirname7(__filename2);
33632
33640
  const scriptPath = resolve23(
@@ -33641,7 +33649,7 @@ Examples:
33641
33649
  const args = [scriptPath, prompt3, "-o", outputPath, "-r", options.ratio || "16:9"];
33642
33650
  spinner2.text = "Generating image with Runway (gemini_2.5_flash)...";
33643
33651
  await new Promise((resolvePromise, reject) => {
33644
- const proc = spawn3("python3", args, {
33652
+ const proc = spawn4("python3", args, {
33645
33653
  env: { ...process.env, RUNWAY_API_SECRET: apiKey },
33646
33654
  stdio: ["ignore", "pipe", "pipe"]
33647
33655
  });
@@ -37001,8 +37009,8 @@ var init_translate_srt = __esm({
37001
37009
  import { existsSync as existsSync21 } from "node:fs";
37002
37010
  import { resolve as resolve27 } from "node:path";
37003
37011
  function detectSystemFont() {
37004
- const platform2 = process.platform;
37005
- if (platform2 === "darwin") {
37012
+ const platform3 = process.platform;
37013
+ if (platform3 === "darwin") {
37006
37014
  const candidates = [
37007
37015
  "/System/Library/Fonts/Helvetica.ttc",
37008
37016
  "/System/Library/Fonts/HelveticaNeue.ttc",
@@ -37011,7 +37019,7 @@ function detectSystemFont() {
37011
37019
  for (const f of candidates) {
37012
37020
  if (existsSync21(f)) return f;
37013
37021
  }
37014
- } else if (platform2 === "linux") {
37022
+ } else if (platform3 === "linux") {
37015
37023
  const candidates = [
37016
37024
  "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
37017
37025
  "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
@@ -37020,7 +37028,7 @@ function detectSystemFont() {
37020
37028
  for (const f of candidates) {
37021
37029
  if (existsSync21(f)) return f;
37022
37030
  }
37023
- } else if (platform2 === "win32") {
37031
+ } else if (platform3 === "win32") {
37024
37032
  const candidates = [
37025
37033
  "C:\\Windows\\Fonts\\arial.ttf",
37026
37034
  "C:\\Windows\\Fonts\\segoeui.ttf"
@@ -37071,11 +37079,11 @@ async function applyTextOverlays(options) {
37071
37079
  } = await Promise.resolve().then(() => (init_remotion(), remotion_exports));
37072
37080
  const remotionErr = await ensureRemotionInstalled2();
37073
37081
  if (remotionErr) {
37074
- const platform2 = process.platform;
37082
+ const platform3 = process.platform;
37075
37083
  let hint = "";
37076
- if (platform2 === "darwin") {
37084
+ if (platform3 === "darwin") {
37077
37085
  hint = "\n\nFix: brew uninstall ffmpeg && brew install ffmpeg\n(The default homebrew formula includes libfreetype)";
37078
- } else if (platform2 === "linux") {
37086
+ } else if (platform3 === "linux") {
37079
37087
  hint = "\n\nFix: sudo apt install ffmpeg (Ubuntu/Debian)\n or rebuild FFmpeg with --enable-libfreetype";
37080
37088
  }
37081
37089
  return {
@@ -51991,6 +51999,126 @@ var init_scene_build = __esm({
51991
51999
  }
51992
52000
  });
51993
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
+
51994
52122
  // src/pipeline/resolver.ts
51995
52123
  function resolveStepParams(params, completedSteps) {
51996
52124
  const resolved = {};
@@ -58996,10 +59124,10 @@ var init_ai_suggest_edit = __esm({
58996
59124
  }
58997
59125
  });
58998
59126
 
58999
- // src/tools/manifest/analyze.ts
59000
- var analyzeMediaTool, analyzeVideoTool, analyzeReviewTool, analyzeSuggestTool, analyzeTools;
59001
- var init_analyze = __esm({
59002
- "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"() {
59003
59131
  "use strict";
59004
59132
  init_zod();
59005
59133
  init_define_tool();
@@ -59122,7 +59250,7 @@ var init_analyze = __esm({
59122
59250
  };
59123
59251
  }
59124
59252
  });
59125
- analyzeTools = [
59253
+ inspectTools = [
59126
59254
  analyzeMediaTool,
59127
59255
  analyzeVideoTool,
59128
59256
  analyzeReviewTool,
@@ -59458,13 +59586,13 @@ var init_generate2 = __esm({
59458
59586
  }
59459
59587
  });
59460
59588
 
59461
- // src/tools/manifest/pipeline.ts
59589
+ // src/tools/manifest/remix.ts
59462
59590
  import { writeFile as writeFile43 } from "node:fs/promises";
59463
59591
  import { tmpdir as tmpdir3 } from "node:os";
59464
59592
  import { join as join13 } from "node:path";
59465
- var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, pipelineTools;
59466
- var init_pipeline2 = __esm({
59467
- "src/tools/manifest/pipeline.ts"() {
59593
+ var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, remixTools;
59594
+ var init_remix = __esm({
59595
+ "src/tools/manifest/remix.ts"() {
59468
59596
  "use strict";
59469
59597
  init_zod();
59470
59598
  init_define_tool();
@@ -59647,7 +59775,7 @@ var init_pipeline2 = __esm({
59647
59775
  };
59648
59776
  }
59649
59777
  });
59650
- pipelineTools = [
59778
+ remixTools = [
59651
59779
  pipelineHighlightsTool,
59652
59780
  pipelineAutoShortsTool,
59653
59781
  pipelineRunTool,
@@ -60471,7 +60599,7 @@ var init_hyperframes = __esm({
60471
60599
  // src/commands/export.ts
60472
60600
  import { readFile as readFile40, access as access8 } from "node:fs/promises";
60473
60601
  import { resolve as resolve61, basename as basename21 } from "node:path";
60474
- import { spawn as spawn2 } from "node:child_process";
60602
+ import { spawn as spawn3 } from "node:child_process";
60475
60603
  function timelineBaseName(path2) {
60476
60604
  const name = basename21(path2);
60477
60605
  if (name.endsWith(".vibe.json")) return name.slice(0, -".vibe.json".length);
@@ -60945,7 +61073,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
60945
61073
  }
60946
61074
  function runFFmpegProcess(ffmpegPath, args, onProgress) {
60947
61075
  return new Promise((resolve64, reject) => {
60948
- const ffmpeg = spawn2(ffmpegPath, args, {
61076
+ const ffmpeg = spawn3(ffmpegPath, args, {
60949
61077
  stdio: ["pipe", "pipe", "pipe"]
60950
61078
  });
60951
61079
  let duration = 0;
@@ -62278,15 +62406,15 @@ var init_walkthrough = __esm({
62278
62406
  var manifest_exports = {};
62279
62407
  __export(manifest_exports, {
62280
62408
  agentOnlyTools: () => agentOnlyTools,
62281
- analyzeTools: () => analyzeTools,
62282
62409
  audioTools: () => audioTools,
62283
62410
  detectTools: () => detectTools,
62284
62411
  editTools: () => editTools,
62285
62412
  exportTools: () => exportTools,
62286
62413
  generateTools: () => generateTools,
62414
+ inspectTools: () => inspectTools,
62287
62415
  manifest: () => manifest,
62288
- pipelineTools: () => pipelineTools,
62289
62416
  projectTools: () => projectTools,
62417
+ remixTools: () => remixTools,
62290
62418
  sceneTools: () => sceneTools,
62291
62419
  timelineTools: () => timelineTools,
62292
62420
  walkthroughTools: () => walkthroughTools
@@ -62298,9 +62426,9 @@ var init_manifest = __esm({
62298
62426
  init_scene2();
62299
62427
  init_audio2();
62300
62428
  init_edit();
62301
- init_analyze();
62429
+ init_inspect();
62302
62430
  init_generate2();
62303
- init_pipeline2();
62431
+ init_remix();
62304
62432
  init_detect2();
62305
62433
  init_timeline();
62306
62434
  init_project2();
@@ -62311,9 +62439,9 @@ var init_manifest = __esm({
62311
62439
  ...sceneTools,
62312
62440
  ...audioTools,
62313
62441
  ...editTools,
62314
- ...analyzeTools,
62442
+ ...inspectTools,
62315
62443
  ...generateTools,
62316
- ...pipelineTools,
62444
+ ...remixTools,
62317
62445
  ...detectTools,
62318
62446
  ...timelineTools,
62319
62447
  ...projectTools,
@@ -62523,7 +62651,7 @@ init_source();
62523
62651
  // package.json
62524
62652
  var package_default = {
62525
62653
  name: "@vibeframe/cli",
62526
- version: "0.80.1",
62654
+ version: "0.81.0",
62527
62655
  description: "VibeFrame CLI - natural language video editing from the terminal",
62528
62656
  private: false,
62529
62657
  license: "MIT",
@@ -63678,7 +63806,7 @@ function detectMediaType(path2) {
63678
63806
  init_generate();
63679
63807
  init_edit_cmd();
63680
63808
 
63681
- // src/commands/analyze.ts
63809
+ // src/commands/inspect.ts
63682
63810
  init_esm();
63683
63811
  init_source();
63684
63812
  init_ora();
@@ -63693,7 +63821,7 @@ init_sanitize();
63693
63821
  init_validate();
63694
63822
  import { readFile as readFile23, writeFile as writeFile30 } from "node:fs/promises";
63695
63823
  import { resolve as resolve33 } from "node:path";
63696
- 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(
63697
63825
  "after",
63698
63826
  `
63699
63827
  Examples:
@@ -63711,7 +63839,7 @@ Use '--fields response,model' to limit output size.
63711
63839
  Run 'vibe schema inspect.<command>' for structured parameter info.
63712
63840
  `
63713
63841
  );
63714
- 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) => {
63715
63843
  const startedAt = Date.now();
63716
63844
  try {
63717
63845
  rejectControlChars(prompt3);
@@ -63788,7 +63916,7 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
63788
63916
  exitWithError(apiError(`Analysis failed: ${error.message}`));
63789
63917
  }
63790
63918
  });
63791
- 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) => {
63792
63920
  const startedAt = Date.now();
63793
63921
  try {
63794
63922
  rejectControlChars(prompt3);
@@ -63864,8 +63992,8 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
63864
63992
  exitWithError(apiError(`Video analysis failed: ${error.message}`));
63865
63993
  }
63866
63994
  });
63867
- registerReviewCommand(analyzeCommand);
63868
- 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) => {
63869
63997
  const startedAt = Date.now();
63870
63998
  try {
63871
63999
  rejectControlChars(instruction);
@@ -64452,7 +64580,7 @@ audioCommand.command("duck").description("Auto-duck background music when voice
64452
64580
  }
64453
64581
  });
64454
64582
 
64455
- // src/commands/pipeline.ts
64583
+ // src/commands/remix.ts
64456
64584
  init_esm();
64457
64585
  init_source();
64458
64586
  init_ora();
@@ -64637,11 +64765,11 @@ function registerScriptPipelineCommands(aiCommand) {
64637
64765
  });
64638
64766
  }
64639
64767
 
64640
- // src/commands/pipeline.ts
64768
+ // src/commands/remix.ts
64641
64769
  init_ai_highlights();
64642
64770
  init_ai_animated_caption();
64643
64771
  init_output();
64644
- var pipelineCommand = new Command("remix").description(
64772
+ var remixCommand = new Command("remix").description(
64645
64773
  "Transform existing media (highlights, auto-shorts, animated captions, regenerate-scene)"
64646
64774
  ).addHelpText(
64647
64775
  "after",
@@ -64675,10 +64803,10 @@ Use '--dry-run' to preview parameters before execution.
64675
64803
  Run 'vibe schema remix.<command>' for structured parameter info.
64676
64804
  `
64677
64805
  );
64678
- registerScriptPipelineCommands(pipelineCommand);
64679
- registerHighlightsCommands(pipelineCommand);
64806
+ registerScriptPipelineCommands(remixCommand);
64807
+ registerHighlightsCommands(remixCommand);
64680
64808
  var ANIMATED_CAPTION_STYLES = ["highlight", "bounce", "pop-in", "neon", "karaoke-sweep", "typewriter"];
64681
- 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(
64682
64810
  "after",
64683
64811
  `
64684
64812
  Examples:
@@ -69397,6 +69525,50 @@ function validateKeyFormat(configKey, value) {
69397
69525
  return { ok: false, expected: fmt.example };
69398
69526
  }
69399
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
+
69400
69572
  // src/commands/setup.ts
69401
69573
  init_agent_host_detect();
69402
69574
  init_dist2();
@@ -69409,7 +69581,7 @@ var VALID_LLM_PROVIDERS = [
69409
69581
  "xai",
69410
69582
  "openrouter"
69411
69583
  ];
69412
- 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(
69413
69585
  "after",
69414
69586
  `
69415
69587
  Non-interactive examples (no TTY required):
@@ -69423,7 +69595,7 @@ Non-interactive examples (no TTY required):
69423
69595
  return;
69424
69596
  }
69425
69597
  if (options.show) {
69426
- await showConfig();
69598
+ await showConfig({ verbose: Boolean(options.verbose) });
69427
69599
  return;
69428
69600
  }
69429
69601
  if (options.reset) {
@@ -69574,7 +69746,7 @@ async function runSetupWizard(fullSetup = false) {
69574
69746
  console.log();
69575
69747
  if (topIndex === 0) {
69576
69748
  await saveConfig(config2);
69577
- showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4");
69749
+ await showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4");
69578
69750
  return;
69579
69751
  }
69580
69752
  if (topIndex === 3) {
@@ -69595,7 +69767,7 @@ async function runSetupWizard(fullSetup = false) {
69595
69767
  console.log();
69596
69768
  await collectKeys(config2, pipelineKeys);
69597
69769
  await saveConfig(config2);
69598
- 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");
69599
69771
  return;
69600
69772
  }
69601
69773
  console.log(source_default.dim("2. Features"));
@@ -69617,7 +69789,7 @@ async function runSetupWizard(fullSetup = false) {
69617
69789
  console.log(source_default.dim(" No features selected. You can re-run setup anytime."));
69618
69790
  console.log();
69619
69791
  await saveConfig(config2);
69620
- showComplete(config2, "vibe --help", []);
69792
+ await showComplete(config2, "vibe --help", []);
69621
69793
  return;
69622
69794
  }
69623
69795
  console.log(source_default.dim("3. API keys"));
@@ -69670,7 +69842,7 @@ async function runSetupWizard(fullSetup = false) {
69670
69842
  console.log();
69671
69843
  }
69672
69844
  await saveConfig(config2);
69673
- showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures);
69845
+ await showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures);
69674
69846
  }
69675
69847
  async function collectKeys(config2, keys) {
69676
69848
  loadEnv();
@@ -69777,14 +69949,15 @@ async function runCustomSetup(config2) {
69777
69949
  }
69778
69950
  console.log();
69779
69951
  await saveConfig(config2);
69780
- showComplete(config2, "vibe doctor");
69952
+ await showComplete(config2, "vibe doctor");
69781
69953
  }
69782
- function showComplete(config2, defaultTryCommand, features = []) {
69954
+ async function showComplete(config2, defaultTryCommand, features = []) {
69783
69955
  console.log(source_default.dim("\u2500".repeat(40)));
69784
69956
  console.log(source_default.green.bold("\u2713 Setup complete!"));
69785
69957
  console.log();
69786
69958
  console.log(source_default.dim(` Config: ${CONFIG_PATH}`));
69787
69959
  console.log();
69960
+ const primaryTry = features.length > 1 ? features[0].tryCommand : defaultTryCommand;
69788
69961
  if (features.length > 1) {
69789
69962
  console.log(source_default.bold(" Try these:"));
69790
69963
  for (const f of features) {
@@ -69793,6 +69966,12 @@ function showComplete(config2, defaultTryCommand, features = []) {
69793
69966
  } else {
69794
69967
  console.log(` Try: ${source_default.cyan(defaultTryCommand)}`);
69795
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
+ }
69796
69975
  console.log();
69797
69976
  console.log(source_default.bold(" Next steps:"));
69798
69977
  console.log(source_default.dim(" cd <project>; vibe init Scaffold AGENTS.md / CLAUDE.md / .env.example (project scope)"));
@@ -69849,7 +70028,8 @@ function maskApiKey(key) {
69849
70028
  if (key.length <= 8) return "****";
69850
70029
  return `${key.slice(0, 4)}${"*".repeat(8)}${key.slice(-4)}`;
69851
70030
  }
69852
- async function showConfig() {
70031
+ async function showConfig(opts = { verbose: false }) {
70032
+ const { verbose } = opts;
69853
70033
  loadEnv();
69854
70034
  const cwdEnvPath = resolve42(process.cwd(), ".env");
69855
70035
  let hasCwdEnv = false;
@@ -69900,6 +70080,7 @@ async function showConfig() {
69900
70080
  { key: "replicate", name: "Replicate", env: "REPLICATE_API_TOKEN" },
69901
70081
  { key: "openrouter", name: "OpenRouter", env: "OPENROUTER_API_KEY" }
69902
70082
  ];
70083
+ let unsetCount = 0;
69903
70084
  for (const p of providerKeys) {
69904
70085
  const configValue = config2?.providers[p.key];
69905
70086
  const dotenvValue = dotenvKeys[p.env];
@@ -69911,23 +70092,31 @@ async function showConfig() {
69911
70092
  const status = source_default.green("\u2713");
69912
70093
  console.log(` ${status} ${p.name.padEnd(12)} ${maskApiKey(value)} (${sources.join(" + ")})`);
69913
70094
  } else {
69914
- const status = source_default.dim("\u25CB");
69915
- 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
+ }
69916
70100
  }
69917
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
+ }
69918
70105
  console.log();
69919
- if (config2) {
69920
- console.log(source_default.bold("Defaults:"));
69921
- console.log(` Aspect Ratio: ${config2.defaults.aspectRatio}`);
69922
- 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"));
69923
70118
  console.log();
69924
70119
  }
69925
- console.log(source_default.bold("Resolution order:"));
69926
- console.log(source_default.dim(" 1. --api-key CLI option"));
69927
- console.log(source_default.dim(` 2. ${CONFIG_PATH}`));
69928
- console.log(source_default.dim(" 3. .env in current directory"));
69929
- console.log(source_default.dim(" 4. Shell environment variables"));
69930
- console.log();
69931
70120
  }
69932
70121
 
69933
70122
  // src/commands/init.ts
@@ -70846,11 +71035,13 @@ var FREE_COMMANDS = [
70846
71035
  "timeline (all)",
70847
71036
  "batch import/concat"
70848
71037
  ];
70849
- 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(
70850
71039
  "after",
70851
71040
  `
70852
71041
  Examples:
70853
- $ 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
70854
71045
  $ vibe doctor --json Machine-readable output
70855
71046
  `
70856
71047
  ).action(async (options) => {
@@ -70866,7 +71057,10 @@ Examples:
70866
71057
  });
70867
71058
  return;
70868
71059
  }
70869
- printReport(results);
71060
+ printReport(results, { verbose: Boolean(options.verbose) });
71061
+ if (options.testKeys) {
71062
+ await runLiveKeyTests(results);
71063
+ }
70870
71064
  });
70871
71065
  var REQUIRED_FFMPEG_FILTERS = {
70872
71066
  drawtext: {
@@ -70920,14 +71114,14 @@ async function runDiagnostics() {
70920
71114
  try {
70921
71115
  const filtersResult = await execSafe("ffmpeg", ["-filters"]);
70922
71116
  const filtersOutput = filtersResult.stdout;
70923
- const platform2 = process.platform;
71117
+ const platform3 = process.platform;
70924
71118
  ffmpegFilters = {};
70925
71119
  for (const [filterName, info] of Object.entries(REQUIRED_FFMPEG_FILTERS)) {
70926
71120
  const available = filtersOutput.includes(filterName);
70927
71121
  ffmpegFilters[filterName] = {
70928
71122
  available,
70929
71123
  commands: info.commands,
70930
- ...!available && info.fix[platform2] ? { fix: info.fix[platform2] } : {}
71124
+ ...!available && info.fix[platform3] ? { fix: info.fix[platform3] } : {}
70931
71125
  };
70932
71126
  }
70933
71127
  } catch {
@@ -71029,7 +71223,8 @@ async function runDiagnostics() {
71029
71223
  totalCount
71030
71224
  };
71031
71225
  }
71032
- function printReport(results) {
71226
+ function printReport(results, opts = { verbose: false }) {
71227
+ const { verbose } = opts;
71033
71228
  console.log();
71034
71229
  console.log(source_default.bold(" System"));
71035
71230
  const nodeIcon = results.system.node.ok ? source_default.green("OK") : source_default.red("MISSING");
@@ -71058,10 +71253,25 @@ function printReport(results) {
71058
71253
  }
71059
71254
  }
71060
71255
  if (results.system.optionalTools) {
71061
- for (const [name, info] of Object.entries(results.system.optionalTools)) {
71062
- if (info.installed) {
71063
- console.log(` ${name.padEnd(11)}${source_default.green("OK")} ${source_default.dim(info.commands.join(", "))}`);
71064
- } 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) {
71065
71275
  console.log(
71066
71276
  ` ${name.padEnd(11)}${source_default.yellow("MISSING")} ${source_default.dim(`needed by: ${info.commands.join(", ")}`)}`
71067
71277
  );
@@ -71083,29 +71293,31 @@ function printReport(results) {
71083
71293
  console.log(` Project ${source_default.yellow("NOT INIT")} ${source_default.dim(`Run: vibe init (cwd: ${results.scope.project.cwd})`)}`);
71084
71294
  }
71085
71295
  console.log(` Agents ${source_default.dim(results.scope.agentHosts.summary)}`);
71086
- console.log();
71087
- console.log(source_default.bold(" Scene composer (vibe build)"));
71088
- const sc = results.scope.sceneComposer;
71089
- const modeBadge = sc.recommendedMode === "agent" ? source_default.cyan("agent") : source_default.dim("batch");
71090
- 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");
71091
- console.log(` Mode (auto) ${modeBadge} ${modeNote}`);
71092
- if (sc.composer) {
71093
- console.log(
71094
- ` Batch LLM ${source_default.green("OK")} ${source_default.dim(`${composerLabel(sc.composer)} (${sc.composerEnvVar})`)}`
71095
- );
71096
- } else {
71097
- console.log(
71098
- ` Batch LLM ${source_default.yellow("--")} ${source_default.dim("no ANTHROPIC_API_KEY / GOOGLE_API_KEY / OPENAI_API_KEY \u2014 agent-mode only")}`
71099
- );
71100
- }
71101
- if (sc.sceneProjectInCwd) {
71102
- if (sc.skillInstalled) {
71103
- 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
+ );
71104
71307
  } else {
71105
- 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)")}`);
71106
71320
  }
71107
- } else {
71108
- console.log(` SKILL.md ${source_default.dim("(no STORYBOARD.md in cwd \u2014 skill is per-scene-project)")}`);
71109
71321
  }
71110
71322
  console.log();
71111
71323
  console.log(source_default.bold(" API Keys"));
@@ -71122,16 +71334,24 @@ function printReport(results) {
71122
71334
  }
71123
71335
  }
71124
71336
  if (missing.length > 0) {
71125
- for (const name of missing) {
71126
- 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 {
71127
71345
  console.log(
71128
- ` ${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.`)
71129
71347
  );
71130
71348
  }
71131
71349
  }
71132
- console.log();
71133
- console.log(source_default.bold(" No API key needed"));
71134
- 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
+ }
71135
71355
  console.log();
71136
71356
  const pct = Math.round(results.readyCount / results.totalCount * 100);
71137
71357
  const readyColor = pct >= 80 ? source_default.green : pct >= 40 ? source_default.yellow : source_default.red;
@@ -71147,6 +71367,32 @@ function printReport(results) {
71147
71367
  }
71148
71368
  console.log();
71149
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
+ }
71150
71396
  function pickNextStep(results, hasMissingProviders) {
71151
71397
  if (!results.scope.user.configured) {
71152
71398
  return "Next: run 'vibe setup' to configure your user scope (API keys + LLM provider).";
@@ -72518,48 +72764,42 @@ program2.name("vibe").showSuggestionAfterError(true).description("VibeFrame CLI
72518
72764
  }).addHelpText(
72519
72765
  "after",
72520
72766
  `
72521
- Global flags (work with any command):
72522
- -V, --version Print version and exit
72523
- -h, --help Print help for the command and exit
72524
- --json Output JSON (auto-enabled when piped)
72525
- --fields Limit output fields (e.g., --fields "path,duration")
72526
- -q, --quiet Output only the result value (path, URL, or ID)
72527
- --stdin Read options from stdin as JSON (for agent/script use)
72528
- --describe Print the command's JSON schema and exit (no execution)
72529
- --dry-run Preview without executing (most commands)
72530
-
72531
- Get started (90% of users start here):
72532
- vibe doctor Check system health and API keys
72533
- vibe setup Configure API keys interactively
72534
- vibe init my-video Scaffold a video project
72535
- vibe build my-video Build STORYBOARD.md into scene compositions/assets
72536
- vibe render my-video Render the project to MP4
72537
- vibe demo Try VibeFrame without API keys
72538
-
72539
- One-shot media tools:
72540
- vibe generate image "..." Generate a still image
72541
- vibe generate video "..." Generate a standalone AI video
72542
- vibe edit caption clip.mp4 -o out.mp4
72543
- vibe inspect media clip.mp4 "Summarize this"
72544
- vibe remix highlights long.mp4 -d 60
72545
-
72546
- Advanced authoring:
72547
- vibe timeline create my-video Create low-level timeline JSON
72548
- vibe scene add intro --headline "Welcome"
72549
- vibe timeline add-clip my-video <source-id>
72550
-
72551
- Automation & agents:
72552
- vibe run workflow.yaml Run a Video-as-YAML pipeline
72553
- vibe agent Natural-language interface
72554
- vibe schema generate.video Tool schema for any command
72555
- vibe walkthrough scene Step-by-step authoring guide
72556
-
72557
- Cost tiers (per call):
72558
- Free detect, edit (silence-cut/fade/noise-reduce), timeline
72559
- Low inspect, audio transcribe, generate image ~$0.01-$0.10
72560
- High generate video, edit image ~$1-$5
72561
- V.High remix (regenerate-scene, highlights, auto-shorts) ~$5-$50+
72562
- 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+
72563
72803
  `
72564
72804
  );
72565
72805
  program2.hook("preAction", async (thisCommand, actionCommand) => {
@@ -72621,9 +72861,9 @@ program2.hook("preAction", async (thisCommand, actionCommand) => {
72621
72861
  });
72622
72862
  program2.addCommand(generateCommand);
72623
72863
  program2.addCommand(editCommand);
72624
- program2.addCommand(analyzeCommand);
72864
+ program2.addCommand(inspectCommand);
72625
72865
  program2.addCommand(audioCommand);
72626
- program2.addCommand(pipelineCommand);
72866
+ program2.addCommand(remixCommand);
72627
72867
  program2.addCommand(setupCommand);
72628
72868
  program2.addCommand(initCommand);
72629
72869
  program2.addCommand(buildCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.80.1",
3
+ "version": "0.81.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.80.1",
187
- "@vibeframe/core": "0.80.1"
186
+ "@vibeframe/ai-providers": "0.81.0",
187
+ "@vibeframe/core": "0.81.0"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"