@vibeframe/cli 0.31.1 → 0.33.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 (110) hide show
  1. package/dist/commands/agent.d.ts.map +1 -1
  2. package/dist/commands/agent.js +8 -15
  3. package/dist/commands/agent.js.map +1 -1
  4. package/dist/commands/ai-analyze.d.ts.map +1 -1
  5. package/dist/commands/ai-analyze.js +9 -16
  6. package/dist/commands/ai-analyze.js.map +1 -1
  7. package/dist/commands/ai-audio.d.ts.map +1 -1
  8. package/dist/commands/ai-audio.js +129 -111
  9. package/dist/commands/ai-audio.js.map +1 -1
  10. package/dist/commands/ai-broll.d.ts.map +1 -1
  11. package/dist/commands/ai-broll.js +38 -23
  12. package/dist/commands/ai-broll.js.map +1 -1
  13. package/dist/commands/ai-edit-cli.d.ts.map +1 -1
  14. package/dist/commands/ai-edit-cli.js +53 -65
  15. package/dist/commands/ai-edit-cli.js.map +1 -1
  16. package/dist/commands/ai-fill-gaps.d.ts.map +1 -1
  17. package/dist/commands/ai-fill-gaps.js +13 -17
  18. package/dist/commands/ai-fill-gaps.js.map +1 -1
  19. package/dist/commands/ai-highlights.d.ts.map +1 -1
  20. package/dist/commands/ai-highlights.js +87 -60
  21. package/dist/commands/ai-highlights.js.map +1 -1
  22. package/dist/commands/ai-image.d.ts.map +1 -1
  23. package/dist/commands/ai-image.js +75 -60
  24. package/dist/commands/ai-image.js.map +1 -1
  25. package/dist/commands/ai-motion.d.ts.map +1 -1
  26. package/dist/commands/ai-motion.js +30 -5
  27. package/dist/commands/ai-motion.js.map +1 -1
  28. package/dist/commands/ai-narrate.d.ts.map +1 -1
  29. package/dist/commands/ai-narrate.js +19 -16
  30. package/dist/commands/ai-narrate.js.map +1 -1
  31. package/dist/commands/ai-review.d.ts.map +1 -1
  32. package/dist/commands/ai-review.js +24 -5
  33. package/dist/commands/ai-review.js.map +1 -1
  34. package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -1
  35. package/dist/commands/ai-script-pipeline-cli.js +114 -88
  36. package/dist/commands/ai-script-pipeline-cli.js.map +1 -1
  37. package/dist/commands/ai-script-pipeline.d.ts +12 -2
  38. package/dist/commands/ai-script-pipeline.d.ts.map +1 -1
  39. package/dist/commands/ai-script-pipeline.js +113 -27
  40. package/dist/commands/ai-script-pipeline.js.map +1 -1
  41. package/dist/commands/ai-suggest-edit.d.ts.map +1 -1
  42. package/dist/commands/ai-suggest-edit.js +16 -21
  43. package/dist/commands/ai-suggest-edit.js.map +1 -1
  44. package/dist/commands/ai-video-fx.d.ts.map +1 -1
  45. package/dist/commands/ai-video-fx.js +72 -71
  46. package/dist/commands/ai-video-fx.js.map +1 -1
  47. package/dist/commands/ai-video.d.ts.map +1 -1
  48. package/dist/commands/ai-video.js +99 -90
  49. package/dist/commands/ai-video.js.map +1 -1
  50. package/dist/commands/ai-viral.d.ts.map +1 -1
  51. package/dist/commands/ai-viral.js +12 -24
  52. package/dist/commands/ai-viral.js.map +1 -1
  53. package/dist/commands/ai-visual-fx.d.ts.map +1 -1
  54. package/dist/commands/ai-visual-fx.js +76 -60
  55. package/dist/commands/ai-visual-fx.js.map +1 -1
  56. package/dist/commands/analyze.js +4 -4
  57. package/dist/commands/analyze.js.map +1 -1
  58. package/dist/commands/audio.js +44 -44
  59. package/dist/commands/audio.js.map +1 -1
  60. package/dist/commands/batch.d.ts.map +1 -1
  61. package/dist/commands/batch.js +92 -39
  62. package/dist/commands/batch.js.map +1 -1
  63. package/dist/commands/detect.d.ts.map +1 -1
  64. package/dist/commands/detect.js +62 -11
  65. package/dist/commands/detect.js.map +1 -1
  66. package/dist/commands/edit-cmd.js +60 -64
  67. package/dist/commands/edit-cmd.js.map +1 -1
  68. package/dist/commands/export.d.ts.map +1 -1
  69. package/dist/commands/export.js +137 -90
  70. package/dist/commands/export.js.map +1 -1
  71. package/dist/commands/generate.js +125 -128
  72. package/dist/commands/generate.js.map +1 -1
  73. package/dist/commands/media.d.ts.map +1 -1
  74. package/dist/commands/media.js +7 -9
  75. package/dist/commands/media.js.map +1 -1
  76. package/dist/commands/output.js +2 -2
  77. package/dist/commands/output.js.map +1 -1
  78. package/dist/commands/pipeline.d.ts.map +1 -1
  79. package/dist/commands/pipeline.js +21 -27
  80. package/dist/commands/pipeline.js.map +1 -1
  81. package/dist/commands/project.d.ts.map +1 -1
  82. package/dist/commands/project.js +42 -9
  83. package/dist/commands/project.js.map +1 -1
  84. package/dist/commands/schema.d.ts.map +1 -1
  85. package/dist/commands/schema.js +10 -16
  86. package/dist/commands/schema.js.map +1 -1
  87. package/dist/commands/setup.d.ts.map +1 -1
  88. package/dist/commands/setup.js +248 -234
  89. package/dist/commands/setup.js.map +1 -1
  90. package/dist/commands/timeline.d.ts.map +1 -1
  91. package/dist/commands/timeline.js +185 -59
  92. package/dist/commands/timeline.js.map +1 -1
  93. package/dist/commands/validate.d.ts +3 -1
  94. package/dist/commands/validate.d.ts.map +1 -1
  95. package/dist/commands/validate.js +9 -2
  96. package/dist/commands/validate.js.map +1 -1
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +95 -17
  99. package/dist/index.js.map +1 -1
  100. package/dist/utils/api-key.d.ts.map +1 -1
  101. package/dist/utils/api-key.js +4 -2
  102. package/dist/utils/api-key.js.map +1 -1
  103. package/dist/utils/first-run.d.ts.map +1 -1
  104. package/dist/utils/first-run.js +5 -6
  105. package/dist/utils/first-run.js.map +1 -1
  106. package/dist/utils/tty.d.ts +1 -1
  107. package/dist/utils/tty.d.ts.map +1 -1
  108. package/dist/utils/tty.js +62 -3
  109. package/dist/utils/tty.js.map +1 -1
  110. package/package.json +3 -3
@@ -18,6 +18,8 @@ import { applyTextOverlays } from "./ai-edit.js";
18
18
  import { executeReview } from "./ai-review.js";
19
19
  import { DEFAULT_VIDEO_RETRIES, RETRY_DELAY_MS, sleep, uploadToImgbb, extendVideoToTarget, generateVideoWithRetryKling, generateVideoWithRetryRunway, } from "./ai-script-pipeline.js";
20
20
  import { downloadVideo } from "./ai-helpers.js";
21
+ import { exitWithError, outputResult, authError, notFoundError, usageError, apiError, generalError } from "./output.js";
22
+ import { validateOutputPath } from "./validate.js";
21
23
  export function registerScriptPipelineCommands(aiCommand) {
22
24
  // Script-to-Video command
23
25
  aiCommand
@@ -29,7 +31,7 @@ export function registerScriptPipelineCommands(aiCommand) {
29
31
  .option("-o, --output <path>", "Output project file path", "script-video.vibe.json")
30
32
  .option("-d, --duration <seconds>", "Target total duration in seconds")
31
33
  .option("-v, --voice <id>", "ElevenLabs voice ID for narration")
32
- .option("-g, --generator <engine>", "Video generator: kling | runway | veo", "kling")
34
+ .option("-g, --generator <engine>", "Video generator: grok | kling | runway | veo", "grok")
33
35
  .option("-i, --image-provider <provider>", "Image provider: gemini | openai | grok", "gemini")
34
36
  .option("-a, --aspect-ratio <ratio>", "Aspect ratio: 16:9 | 9:16 | 1:1", "16:9")
35
37
  .option("--images-only", "Generate images only, skip video generation")
@@ -44,8 +46,36 @@ export function registerScriptPipelineCommands(aiCommand) {
44
46
  .option("--text-style <style>", "Text overlay style: lower-third, center-bold, subtitle, minimal", "lower-third")
45
47
  .option("--review", "Run AI review after assembly (requires GOOGLE_API_KEY)")
46
48
  .option("--review-auto-apply", "Auto-apply fixable issues from AI review")
49
+ .option("--dry-run", "Preview parameters without executing")
47
50
  .action(async (script, options) => {
48
51
  try {
52
+ if (options.output) {
53
+ validateOutputPath(options.output);
54
+ }
55
+ if (options.dryRun) {
56
+ outputResult({
57
+ dryRun: true,
58
+ command: "pipeline script-to-video",
59
+ params: {
60
+ script: script.slice(0, 200),
61
+ file: options.file ?? false,
62
+ output: options.output,
63
+ duration: options.duration,
64
+ generator: options.generator,
65
+ imageProvider: options.imageProvider,
66
+ aspectRatio: options.aspectRatio,
67
+ imagesOnly: options.imagesOnly ?? false,
68
+ voiceover: options.voiceover,
69
+ outputDir: options.outputDir,
70
+ creativity: options.creativity,
71
+ storyboardProvider: options.storyboardProvider,
72
+ textOverlay: options.textOverlay,
73
+ textStyle: options.textStyle,
74
+ review: options.review ?? false,
75
+ },
76
+ });
77
+ return;
78
+ }
49
79
  // Load environment variables from .env file
50
80
  loadEnv();
51
81
  // Get storyboard provider API key
@@ -54,27 +84,23 @@ export function registerScriptPipelineCommands(aiCommand) {
54
84
  if (storyboardProvider === "openai") {
55
85
  storyboardApiKey = (await getApiKey("OPENAI_API_KEY", "OpenAI")) ?? undefined;
56
86
  if (!storyboardApiKey) {
57
- console.error(chalk.red("OpenAI API key required for storyboard generation (-s openai). Set OPENAI_API_KEY in .env or run: vibe setup"));
58
- process.exit(1);
87
+ exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
59
88
  }
60
89
  }
61
90
  else if (storyboardProvider === "gemini") {
62
91
  storyboardApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
63
92
  if (!storyboardApiKey) {
64
- console.error(chalk.red("Google API key required for storyboard generation (-s gemini). Set GOOGLE_API_KEY in .env or run: vibe setup"));
65
- process.exit(1);
93
+ exitWithError(authError("GOOGLE_API_KEY", "Google"));
66
94
  }
67
95
  }
68
96
  else if (storyboardProvider === "claude") {
69
97
  storyboardApiKey = (await getApiKey("ANTHROPIC_API_KEY", "Anthropic")) ?? undefined;
70
98
  if (!storyboardApiKey) {
71
- console.error(chalk.red("Anthropic API key required for storyboard generation. Set ANTHROPIC_API_KEY in .env or run: vibe setup"));
72
- process.exit(1);
99
+ exitWithError(authError("ANTHROPIC_API_KEY", "Anthropic"));
73
100
  }
74
101
  }
75
102
  else {
76
- console.error(chalk.red(`Unknown storyboard provider: ${storyboardProvider}. Use claude, openai, or gemini`));
77
- process.exit(1);
103
+ exitWithError(usageError(`Unknown storyboard provider: ${storyboardProvider}`, "Use claude, openai, or gemini"));
78
104
  }
79
105
  // Get image provider API key
80
106
  let imageApiKey;
@@ -82,55 +108,50 @@ export function registerScriptPipelineCommands(aiCommand) {
82
108
  if (imageProvider === "openai" || imageProvider === "dalle") {
83
109
  imageApiKey = (await getApiKey("OPENAI_API_KEY", "OpenAI")) ?? undefined;
84
110
  if (!imageApiKey) {
85
- console.error(chalk.red("OpenAI API key required for DALL-E image generation. Set OPENAI_API_KEY in .env or run: vibe setup"));
86
- process.exit(1);
111
+ exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
87
112
  }
88
113
  }
89
114
  else if (imageProvider === "gemini") {
90
115
  imageApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
91
116
  if (!imageApiKey) {
92
- console.error(chalk.red("Google API key required for Gemini image generation. Set GOOGLE_API_KEY in .env or run: vibe setup"));
93
- process.exit(1);
117
+ exitWithError(authError("GOOGLE_API_KEY", "Google"));
94
118
  }
95
119
  }
96
120
  else if (imageProvider === "grok") {
97
121
  imageApiKey = (await getApiKey("XAI_API_KEY", "xAI")) ?? undefined;
98
122
  if (!imageApiKey) {
99
- console.error(chalk.red("xAI API key required for Grok image generation. Set XAI_API_KEY in .env or run: vibe setup"));
100
- process.exit(1);
123
+ exitWithError(authError("XAI_API_KEY", "xAI"));
101
124
  }
102
125
  }
103
126
  else {
104
- console.error(chalk.red(`Unknown image provider: ${imageProvider}. Use openai, gemini, or grok`));
105
- process.exit(1);
127
+ exitWithError(usageError(`Unknown image provider: ${imageProvider}`, "Use openai, gemini, or grok"));
106
128
  }
107
129
  let elevenlabsApiKey;
108
130
  if (options.voiceover !== false) {
109
131
  const key = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs");
110
132
  if (!key) {
111
- console.error(chalk.red("ElevenLabs API key required for voiceover (or use --no-voiceover). Set ELEVENLABS_API_KEY in .env or run: vibe setup"));
112
- process.exit(1);
133
+ exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
113
134
  }
114
135
  elevenlabsApiKey = key;
115
136
  }
116
137
  let videoApiKey;
117
138
  if (!options.imagesOnly) {
118
- if (options.generator === "kling") {
119
- const key = await getApiKey("KLING_API_KEY", "Kling");
120
- if (!key) {
121
- console.error(chalk.red("Kling API key required (or use --images-only). Set KLING_API_KEY in .env or run: vibe setup"));
122
- process.exit(1);
123
- }
124
- videoApiKey = key;
139
+ const generatorKeyMap = {
140
+ grok: { envVar: "XAI_API_KEY", name: "xAI" },
141
+ kling: { envVar: "KLING_API_KEY", name: "Kling" },
142
+ runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
143
+ veo: { envVar: "GOOGLE_API_KEY", name: "Google" },
144
+ };
145
+ const generator = options.generator || "grok";
146
+ const genInfo = generatorKeyMap[generator];
147
+ if (!genInfo) {
148
+ exitWithError(usageError(`Invalid generator: ${generator}`, `Available: ${Object.keys(generatorKeyMap).join(", ")}`));
125
149
  }
126
- else {
127
- const key = await getApiKey("RUNWAY_API_SECRET", "Runway");
128
- if (!key) {
129
- console.error(chalk.red("Runway API key required (or use --images-only). Set RUNWAY_API_SECRET in .env or run: vibe setup"));
130
- process.exit(1);
131
- }
132
- videoApiKey = key;
150
+ const key = await getApiKey(genInfo.envVar, genInfo.name);
151
+ if (!key) {
152
+ exitWithError(authError(genInfo.envVar, genInfo.name));
133
153
  }
154
+ videoApiKey = key;
134
155
  }
135
156
  // Read script content
136
157
  let scriptContent = script;
@@ -155,8 +176,7 @@ export function registerScriptPipelineCommands(aiCommand) {
155
176
  // Validate creativity level
156
177
  const creativity = options.creativity?.toLowerCase();
157
178
  if (creativity && creativity !== "low" && creativity !== "high") {
158
- console.error(chalk.red("Invalid creativity level. Use 'low' or 'high'."));
159
- process.exit(1);
179
+ exitWithError(usageError("Invalid creativity level.", "Use 'low' or 'high'."));
160
180
  }
161
181
  console.log();
162
182
  console.log(chalk.bold.cyan("🎬 Script-to-Video Pipeline"));
@@ -190,8 +210,8 @@ export function registerScriptPipelineCommands(aiCommand) {
190
210
  segments = await claude.analyzeContent(scriptContent, durationOpt, creativityOpts);
191
211
  }
192
212
  if (segments.length === 0) {
193
- storyboardSpinner.fail(chalk.red("Failed to generate storyboard (check API key and error above)"));
194
- process.exit(1);
213
+ storyboardSpinner.fail("Failed to generate storyboard");
214
+ exitWithError(apiError("Failed to generate storyboard (check API key and error above)", true));
195
215
  }
196
216
  let totalDuration = segments.reduce((sum, seg) => sum + seg.duration, 0);
197
217
  storyboardSpinner.succeed(chalk.green(`Generated ${segments.length} scenes (total: ${totalDuration}s)`));
@@ -447,8 +467,8 @@ export function registerScriptPipelineCommands(aiCommand) {
447
467
  const kling = new KlingProvider();
448
468
  await kling.initialize({ apiKey: videoApiKey });
449
469
  if (!kling.isConfigured()) {
450
- videoSpinner.fail(chalk.red("Invalid Kling API key format. Use ACCESS_KEY:SECRET_KEY"));
451
- process.exit(1);
470
+ videoSpinner.fail("Invalid Kling API key format");
471
+ exitWithError(authError("KLING_API_KEY", "Kling"));
452
472
  }
453
473
  // Check for ImgBB API key for image-to-video support (from config or env)
454
474
  const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
@@ -1000,9 +1020,7 @@ export function registerScriptPipelineCommands(aiCommand) {
1000
1020
  console.log();
1001
1021
  }
1002
1022
  catch (error) {
1003
- console.error(chalk.red("Script-to-Video failed"));
1004
- console.error(error);
1005
- process.exit(1);
1023
+ exitWithError(generalError(error instanceof Error ? error.message : "Script-to-Video failed"));
1006
1024
  }
1007
1025
  });
1008
1026
  // Regenerate Scene command
@@ -1014,12 +1032,13 @@ export function registerScriptPipelineCommands(aiCommand) {
1014
1032
  .option("--video-only", "Only regenerate video")
1015
1033
  .option("--narration-only", "Only regenerate narration")
1016
1034
  .option("--image-only", "Only regenerate image")
1017
- .option("-g, --generator <engine>", "Video generator: kling | runway | veo", "kling")
1035
+ .option("-g, --generator <engine>", "Video generator: grok | kling | runway | veo", "grok")
1018
1036
  .option("-i, --image-provider <provider>", "Image provider: gemini | openai | grok", "gemini")
1019
1037
  .option("-v, --voice <id>", "ElevenLabs voice ID for narration")
1020
1038
  .option("-a, --aspect-ratio <ratio>", "Aspect ratio: 16:9 | 9:16 | 1:1", "16:9")
1021
1039
  .option("--retries <count>", "Number of retries for video generation failures", String(DEFAULT_VIDEO_RETRIES))
1022
1040
  .option("--reference-scene <num>", "Use another scene's image as reference for character consistency")
1041
+ .option("--dry-run", "Preview parameters without executing")
1023
1042
  .action(async (projectDir, options) => {
1024
1043
  try {
1025
1044
  const outputDir = resolve(process.cwd(), projectDir);
@@ -1027,19 +1046,34 @@ export function registerScriptPipelineCommands(aiCommand) {
1027
1046
  const projectPath = resolve(outputDir, "project.vibe.json");
1028
1047
  // Validate project directory
1029
1048
  if (!existsSync(outputDir)) {
1030
- console.error(chalk.red(`Project directory not found: ${outputDir}`));
1031
- process.exit(1);
1049
+ exitWithError(notFoundError(outputDir));
1032
1050
  }
1033
1051
  if (!existsSync(storyboardPath)) {
1034
- console.error(chalk.red(`Storyboard not found: ${storyboardPath}`));
1035
- console.error(chalk.dim("This command requires a storyboard.json file from script-to-video output"));
1036
- process.exit(1);
1052
+ exitWithError(notFoundError(storyboardPath));
1037
1053
  }
1038
1054
  // Parse scene number(s) - supports "3" or "3,4,5"
1039
1055
  const sceneNums = options.scene.split(",").map((s) => parseInt(s.trim())).filter((n) => !isNaN(n) && n >= 1);
1040
1056
  if (sceneNums.length === 0) {
1041
- console.error(chalk.red("Scene number must be a positive integer (1-based), e.g., --scene 3 or --scene 3,4,5"));
1042
- process.exit(1);
1057
+ exitWithError(usageError("Scene number must be a positive integer (1-based)", "e.g., --scene 3 or --scene 3,4,5"));
1058
+ }
1059
+ if (options.dryRun) {
1060
+ outputResult({
1061
+ dryRun: true,
1062
+ command: "pipeline regenerate-scene",
1063
+ params: {
1064
+ projectDir,
1065
+ scene: sceneNums,
1066
+ videoOnly: options.videoOnly ?? false,
1067
+ narrationOnly: options.narrationOnly ?? false,
1068
+ imageOnly: options.imageOnly ?? false,
1069
+ generator: options.generator,
1070
+ imageProvider: options.imageProvider,
1071
+ aspectRatio: options.aspectRatio,
1072
+ retries: options.retries,
1073
+ referenceScene: options.referenceScene,
1074
+ },
1075
+ });
1076
+ return;
1043
1077
  }
1044
1078
  // Load storyboard
1045
1079
  const storyboardContent = await readFile(storyboardPath, "utf-8");
@@ -1047,8 +1081,7 @@ export function registerScriptPipelineCommands(aiCommand) {
1047
1081
  // Validate all scene numbers
1048
1082
  for (const sceneNum of sceneNums) {
1049
1083
  if (sceneNum > segments.length) {
1050
- console.error(chalk.red(`Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.`));
1051
- process.exit(1);
1084
+ exitWithError(usageError(`Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.`));
1052
1085
  }
1053
1086
  }
1054
1087
  // Determine what to regenerate
@@ -1071,48 +1104,44 @@ export function registerScriptPipelineCommands(aiCommand) {
1071
1104
  if (imageProvider === "openai" || imageProvider === "dalle") {
1072
1105
  imageApiKey = (await getApiKey("OPENAI_API_KEY", "OpenAI")) ?? undefined;
1073
1106
  if (!imageApiKey) {
1074
- console.error(chalk.red("OpenAI API key required for image generation. Set OPENAI_API_KEY in .env or run: vibe setup"));
1075
- process.exit(1);
1107
+ exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
1076
1108
  }
1077
1109
  }
1078
1110
  else if (imageProvider === "gemini") {
1079
1111
  imageApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
1080
1112
  if (!imageApiKey) {
1081
- console.error(chalk.red("Google API key required for Gemini image generation. Set GOOGLE_API_KEY in .env or run: vibe setup"));
1082
- process.exit(1);
1113
+ exitWithError(authError("GOOGLE_API_KEY", "Google"));
1083
1114
  }
1084
1115
  }
1085
1116
  else if (imageProvider === "grok") {
1086
1117
  imageApiKey = (await getApiKey("XAI_API_KEY", "xAI")) ?? undefined;
1087
1118
  if (!imageApiKey) {
1088
- console.error(chalk.red("xAI API key required for Grok image generation. Set XAI_API_KEY in .env or run: vibe setup"));
1089
- process.exit(1);
1119
+ exitWithError(authError("XAI_API_KEY", "xAI"));
1090
1120
  }
1091
1121
  }
1092
1122
  }
1093
1123
  if (regenerateVideo) {
1094
- if (options.generator === "kling") {
1095
- const key = await getApiKey("KLING_API_KEY", "Kling");
1096
- if (!key) {
1097
- console.error(chalk.red("Kling API key required. Set KLING_API_KEY in .env or run: vibe setup"));
1098
- process.exit(1);
1099
- }
1100
- videoApiKey = key;
1124
+ const generatorKeyMap = {
1125
+ grok: { envVar: "XAI_API_KEY", name: "xAI" },
1126
+ kling: { envVar: "KLING_API_KEY", name: "Kling" },
1127
+ runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
1128
+ veo: { envVar: "GOOGLE_API_KEY", name: "Google" },
1129
+ };
1130
+ const generator = options.generator || "grok";
1131
+ const genInfo = generatorKeyMap[generator];
1132
+ if (!genInfo) {
1133
+ exitWithError(usageError(`Invalid generator: ${generator}`, `Available: ${Object.keys(generatorKeyMap).join(", ")}`));
1101
1134
  }
1102
- else {
1103
- const key = await getApiKey("RUNWAY_API_SECRET", "Runway");
1104
- if (!key) {
1105
- console.error(chalk.red("Runway API key required. Set RUNWAY_API_SECRET in .env or run: vibe setup"));
1106
- process.exit(1);
1107
- }
1108
- videoApiKey = key;
1135
+ const key = await getApiKey(genInfo.envVar, genInfo.name);
1136
+ if (!key) {
1137
+ exitWithError(authError(genInfo.envVar, genInfo.name));
1109
1138
  }
1139
+ videoApiKey = key;
1110
1140
  }
1111
1141
  if (regenerateNarration) {
1112
1142
  const key = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs");
1113
1143
  if (!key) {
1114
- console.error(chalk.red("ElevenLabs API key required for narration. Set ELEVENLABS_API_KEY in .env or run: vibe setup"));
1115
- process.exit(1);
1144
+ exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
1116
1145
  }
1117
1146
  elevenlabsApiKey = key;
1118
1147
  }
@@ -1133,8 +1162,8 @@ export function registerScriptPipelineCommands(aiCommand) {
1133
1162
  voiceId: options.voice,
1134
1163
  });
1135
1164
  if (!ttsResult.success || !ttsResult.audioBuffer) {
1136
- ttsSpinner.fail(chalk.red(`Failed to generate narration: ${ttsResult.error || "Unknown error"}`));
1137
- process.exit(1);
1165
+ ttsSpinner.fail(`Failed to generate narration: ${ttsResult.error || "Unknown error"}`);
1166
+ exitWithError(apiError(`Failed to generate narration: ${ttsResult.error || "Unknown error"}`, true));
1138
1167
  }
1139
1168
  await writeFile(narrationPath, ttsResult.audioBuffer);
1140
1169
  narrationDuration = await getAudioDuration(narrationPath);
@@ -1286,8 +1315,8 @@ Generate the single-person scene image now.`;
1286
1315
  }
1287
1316
  else {
1288
1317
  const errorMsg = imageError || "Unknown error";
1289
- imageSpinner.fail(chalk.red(`Failed to generate image: ${errorMsg}`));
1290
- process.exit(1);
1318
+ imageSpinner.fail(`Failed to generate image: ${errorMsg}`);
1319
+ exitWithError(apiError(`Failed to generate image: ${errorMsg}`, true));
1291
1320
  }
1292
1321
  }
1293
1322
  // Step 3: Regenerate video if needed
@@ -1296,9 +1325,8 @@ Generate the single-person scene image now.`;
1296
1325
  const videoSpinner = ora(`🎬 Regenerating video for scene ${sceneNum}...`).start();
1297
1326
  // Check if image exists
1298
1327
  if (!existsSync(imagePath)) {
1299
- videoSpinner.fail(chalk.red(`Reference image not found: ${imagePath}`));
1300
- console.error(chalk.dim("Generate an image first with --image-only or regenerate all assets"));
1301
- process.exit(1);
1328
+ videoSpinner.fail(`Reference image not found: ${imagePath}`);
1329
+ exitWithError(notFoundError(imagePath));
1302
1330
  }
1303
1331
  const imageBuffer = await readFile(imagePath);
1304
1332
  const ext = extname(imagePath).toLowerCase().slice(1);
@@ -1311,8 +1339,8 @@ Generate the single-person scene image now.`;
1311
1339
  const kling = new KlingProvider();
1312
1340
  await kling.initialize({ apiKey: videoApiKey });
1313
1341
  if (!kling.isConfigured()) {
1314
- videoSpinner.fail(chalk.red("Invalid Kling API key format. Use ACCESS_KEY:SECRET_KEY"));
1315
- process.exit(1);
1342
+ videoSpinner.fail("Invalid Kling API key format");
1343
+ exitWithError(authError("KLING_API_KEY", "Kling"));
1316
1344
  }
1317
1345
  // Try to use image-to-video if ImgBB API key is available
1318
1346
  const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
@@ -1412,8 +1440,8 @@ Generate the single-person scene image now.`;
1412
1440
  videoSpinner.succeed(chalk.green("Generated video"));
1413
1441
  }
1414
1442
  else {
1415
- videoSpinner.fail(chalk.red("Failed to generate video after all retries"));
1416
- process.exit(1);
1443
+ videoSpinner.fail("Failed to generate video after all retries");
1444
+ exitWithError(apiError("Failed to generate video after all retries", true));
1417
1445
  }
1418
1446
  }
1419
1447
  // Step 4: Recalculate startTime for ALL segments and re-save storyboard
@@ -1485,9 +1513,7 @@ Generate the single-person scene image now.`;
1485
1513
  console.log();
1486
1514
  }
1487
1515
  catch (error) {
1488
- console.error(chalk.red("Scene regeneration failed"));
1489
- console.error(error);
1490
- process.exit(1);
1516
+ exitWithError(generalError(error instanceof Error ? error.message : "Scene regeneration failed"));
1491
1517
  }
1492
1518
  });
1493
1519
  }