@vibeframe/cli 0.27.0 → 0.29.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/adapters/index.d.ts +1 -0
  3. package/dist/agent/adapters/index.d.ts.map +1 -1
  4. package/dist/agent/adapters/index.js +5 -0
  5. package/dist/agent/adapters/index.js.map +1 -1
  6. package/dist/agent/adapters/openrouter.d.ts +16 -0
  7. package/dist/agent/adapters/openrouter.d.ts.map +1 -0
  8. package/dist/agent/adapters/openrouter.js +100 -0
  9. package/dist/agent/adapters/openrouter.js.map +1 -0
  10. package/dist/agent/types.d.ts +1 -1
  11. package/dist/agent/types.d.ts.map +1 -1
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +3 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/setup.js +5 -2
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/config/schema.d.ts +2 -1
  18. package/dist/config/schema.d.ts.map +1 -1
  19. package/dist/config/schema.js +2 -0
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/index.js +0 -0
  22. package/package.json +16 -12
  23. package/.turbo/turbo-build.log +0 -4
  24. package/.turbo/turbo-lint.log +0 -21
  25. package/.turbo/turbo-test.log +0 -689
  26. package/src/agent/adapters/claude.ts +0 -143
  27. package/src/agent/adapters/gemini.ts +0 -159
  28. package/src/agent/adapters/index.ts +0 -61
  29. package/src/agent/adapters/ollama.ts +0 -231
  30. package/src/agent/adapters/openai.ts +0 -116
  31. package/src/agent/adapters/xai.ts +0 -119
  32. package/src/agent/index.ts +0 -251
  33. package/src/agent/memory/index.ts +0 -151
  34. package/src/agent/prompts/system.ts +0 -106
  35. package/src/agent/tools/ai-editing.ts +0 -845
  36. package/src/agent/tools/ai-generation.ts +0 -1073
  37. package/src/agent/tools/ai-pipeline.ts +0 -1055
  38. package/src/agent/tools/ai.ts +0 -21
  39. package/src/agent/tools/batch.ts +0 -429
  40. package/src/agent/tools/e2e.test.ts +0 -545
  41. package/src/agent/tools/export.ts +0 -184
  42. package/src/agent/tools/filesystem.ts +0 -237
  43. package/src/agent/tools/index.ts +0 -150
  44. package/src/agent/tools/integration.test.ts +0 -775
  45. package/src/agent/tools/media.ts +0 -697
  46. package/src/agent/tools/project.ts +0 -313
  47. package/src/agent/tools/timeline.ts +0 -951
  48. package/src/agent/types.ts +0 -68
  49. package/src/commands/agent.ts +0 -340
  50. package/src/commands/ai-analyze.ts +0 -429
  51. package/src/commands/ai-animated-caption.ts +0 -390
  52. package/src/commands/ai-audio.ts +0 -941
  53. package/src/commands/ai-broll.ts +0 -490
  54. package/src/commands/ai-edit-cli.ts +0 -658
  55. package/src/commands/ai-edit.ts +0 -1542
  56. package/src/commands/ai-fill-gaps.ts +0 -566
  57. package/src/commands/ai-helpers.ts +0 -65
  58. package/src/commands/ai-highlights.ts +0 -1303
  59. package/src/commands/ai-image.ts +0 -761
  60. package/src/commands/ai-motion.ts +0 -347
  61. package/src/commands/ai-narrate.ts +0 -451
  62. package/src/commands/ai-review.ts +0 -309
  63. package/src/commands/ai-script-pipeline-cli.ts +0 -1710
  64. package/src/commands/ai-script-pipeline.ts +0 -1365
  65. package/src/commands/ai-suggest-edit.ts +0 -264
  66. package/src/commands/ai-video-fx.ts +0 -445
  67. package/src/commands/ai-video.ts +0 -915
  68. package/src/commands/ai-viral.ts +0 -595
  69. package/src/commands/ai-visual-fx.ts +0 -601
  70. package/src/commands/ai.test.ts +0 -627
  71. package/src/commands/ai.ts +0 -307
  72. package/src/commands/analyze.ts +0 -282
  73. package/src/commands/audio.ts +0 -644
  74. package/src/commands/batch.test.ts +0 -279
  75. package/src/commands/batch.ts +0 -440
  76. package/src/commands/detect.ts +0 -329
  77. package/src/commands/doctor.ts +0 -237
  78. package/src/commands/edit-cmd.ts +0 -1014
  79. package/src/commands/export.ts +0 -918
  80. package/src/commands/generate.ts +0 -2146
  81. package/src/commands/media.ts +0 -177
  82. package/src/commands/output.ts +0 -142
  83. package/src/commands/pipeline.ts +0 -398
  84. package/src/commands/project.test.ts +0 -127
  85. package/src/commands/project.ts +0 -149
  86. package/src/commands/sanitize.ts +0 -60
  87. package/src/commands/schema.ts +0 -130
  88. package/src/commands/setup.ts +0 -509
  89. package/src/commands/timeline.test.ts +0 -499
  90. package/src/commands/timeline.ts +0 -529
  91. package/src/commands/validate.ts +0 -77
  92. package/src/config/config.test.ts +0 -197
  93. package/src/config/index.ts +0 -125
  94. package/src/config/schema.ts +0 -82
  95. package/src/engine/index.ts +0 -2
  96. package/src/engine/project.test.ts +0 -702
  97. package/src/engine/project.ts +0 -439
  98. package/src/index.ts +0 -146
  99. package/src/utils/api-key.test.ts +0 -41
  100. package/src/utils/api-key.ts +0 -247
  101. package/src/utils/audio.ts +0 -83
  102. package/src/utils/exec-safe.ts +0 -75
  103. package/src/utils/first-run.ts +0 -52
  104. package/src/utils/provider-resolver.ts +0 -56
  105. package/src/utils/remotion.ts +0 -951
  106. package/src/utils/subtitle.test.ts +0 -227
  107. package/src/utils/subtitle.ts +0 -169
  108. package/src/utils/tty.ts +0 -196
  109. package/tsconfig.json +0 -20
@@ -1,60 +0,0 @@
1
- /**
2
- * @module sanitize
3
- * @description Sanitize AI/LLM responses to prevent prompt injection and terminal exploits.
4
- */
5
-
6
- /**
7
- * Strip suspicious prompt injection patterns from LLM text output.
8
- * Catches common injection attempts like "Ignore previous instructions",
9
- * fake system prompts, and markdown-disguised commands.
10
- */
11
- export function sanitizeLLMResponse(text: string): string {
12
- if (!text || typeof text !== "string") return text;
13
-
14
- let sanitized = text;
15
-
16
- // Strip ANSI escape sequences that could manipulate terminal
17
- // eslint-disable-next-line no-control-regex
18
- sanitized = sanitized.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
19
-
20
- // Strip other control characters (except newline, tab, carriage return)
21
- // eslint-disable-next-line no-control-regex
22
- sanitized = sanitized.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, "");
23
-
24
- return sanitized;
25
- }
26
-
27
- /**
28
- * Sanitize a file path returned by an AI model.
29
- * Prevents path traversal and null byte injection.
30
- */
31
- export function sanitizeFilePath(path: string): string {
32
- if (!path || typeof path !== "string") return path;
33
-
34
- // Remove null bytes
35
- let sanitized = path.replace(/\0/g, "");
36
-
37
- // Remove control characters
38
- // eslint-disable-next-line no-control-regex
39
- sanitized = sanitized.replace(/[\x00-\x1f\x7f-\x9f]/g, "");
40
-
41
- return sanitized;
42
- }
43
-
44
- /**
45
- * Sanitize structured data from AI responses (e.g., JSON parsed results).
46
- * Recursively sanitizes all string values.
47
- */
48
- export function sanitizeAIResult<T>(data: T): T {
49
- if (data === null || data === undefined) return data;
50
- if (typeof data === "string") return sanitizeLLMResponse(data) as T;
51
- if (Array.isArray(data)) return data.map(sanitizeAIResult) as T;
52
- if (typeof data === "object") {
53
- const result: Record<string, unknown> = {};
54
- for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
55
- result[key] = sanitizeAIResult(value);
56
- }
57
- return result as T;
58
- }
59
- return data;
60
- }
@@ -1,130 +0,0 @@
1
- /**
2
- * @module schema
3
- * @description Schema introspection command. Outputs JSON Schema for any CLI command,
4
- * enabling agent introspection without static docs.
5
- *
6
- * Usage: vibe schema generate.image
7
- * vibe schema edit.silence-cut
8
- */
9
-
10
- import { Command } from "commander";
11
-
12
- export const schemaCommand = new Command("schema")
13
- .description("Show JSON schema for a CLI command")
14
- .argument("<command>", "Command path (e.g., generate.image, edit.silence-cut)")
15
- .action((commandPath: string) => {
16
- // Access the parent program to find commands
17
- const program = schemaCommand.parent;
18
- if (!program) {
19
- console.error("Schema command must be registered on a program");
20
- process.exit(1);
21
- }
22
-
23
- const parts = commandPath.split(".");
24
- if (parts.length !== 2) {
25
- console.error(
26
- `Invalid command path: ${commandPath}. Use format: group.action (e.g., generate.image)`
27
- );
28
- process.exit(1);
29
- }
30
-
31
- const [groupName, actionName] = parts;
32
-
33
- // Find the group command
34
- const groupCmd = program.commands.find(
35
- (c: Command) => c.name() === groupName
36
- );
37
- if (!groupCmd) {
38
- console.error(`Unknown group: ${groupName}`);
39
- console.error(
40
- `Available groups: ${program.commands.map((c: Command) => c.name()).join(", ")}`
41
- );
42
- process.exit(1);
43
- }
44
-
45
- // Find the action command
46
- const actionCmd = (groupCmd as Command).commands.find(
47
- (c: Command) => c.name() === actionName
48
- );
49
- if (!actionCmd) {
50
- console.error(`Unknown action: ${actionName} in group ${groupName}`);
51
- console.error(
52
- `Available actions: ${(groupCmd as Command).commands
53
- .map((c: Command) => c.name())
54
- .join(", ")}`
55
- );
56
- process.exit(1);
57
- }
58
-
59
- // Build JSON schema from Commander options and arguments
60
- const toolName = `${groupName}_${actionName.replace(/-/g, "_")}`;
61
- const schema = buildSchema(actionCmd as Command, toolName);
62
- console.log(JSON.stringify(schema, null, 2));
63
- });
64
-
65
- function buildSchema(
66
- cmd: Command,
67
- toolName: string
68
- ): Record<string, unknown> {
69
- const properties: Record<string, unknown> = {};
70
- const required: string[] = [];
71
-
72
- // Extract arguments
73
- for (const arg of cmd.registeredArguments || []) {
74
- const name = arg.name();
75
- properties[name] = {
76
- type: "string",
77
- description: arg.description,
78
- };
79
- if (arg.required) {
80
- required.push(name);
81
- }
82
- }
83
-
84
- // Extract options
85
- for (const opt of cmd.options) {
86
- const name = camelCase(
87
- opt.long?.replace(/^--/, "") || opt.short?.replace(/^-/, "") || ""
88
- );
89
- if (!name || name === "help") continue;
90
-
91
- const prop: Record<string, unknown> = {
92
- description: opt.description,
93
- };
94
-
95
- // Detect type from default value or flags
96
- if (
97
- opt.flags.includes("<number>") ||
98
- opt.flags.includes("<seconds>") ||
99
- opt.flags.includes("<sec>") ||
100
- opt.flags.includes("<dB>") ||
101
- opt.flags.includes("<pixels>")
102
- ) {
103
- prop.type = "number";
104
- } else if (opt.flags.includes("<") && opt.flags.includes(">")) {
105
- prop.type = "string";
106
- } else {
107
- prop.type = "boolean";
108
- }
109
-
110
- if (opt.defaultValue !== undefined) {
111
- prop.default = opt.defaultValue;
112
- }
113
-
114
- properties[name] = prop;
115
- }
116
-
117
- return {
118
- name: toolName,
119
- description: cmd.description(),
120
- parameters: {
121
- type: "object",
122
- properties,
123
- required: required.length > 0 ? required : undefined,
124
- },
125
- };
126
- }
127
-
128
- function camelCase(str: string): string {
129
- return str.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
130
- }
@@ -1,509 +0,0 @@
1
- /**
2
- * Setup command - Interactive configuration wizard
3
- */
4
-
5
- import { Command } from "commander";
6
- import chalk from "chalk";
7
- import { resolve, dirname } from "node:path";
8
- import { access, readFile, mkdir, writeFile } from "node:fs/promises";
9
- import { fileURLToPath } from "node:url";
10
- import { parse as parseDotenv } from "dotenv";
11
- import {
12
- loadConfig,
13
- saveConfig,
14
- createDefaultConfig,
15
- CONFIG_PATH,
16
- type LLMProvider,
17
- PROVIDER_NAMES,
18
- } from "../config/index.js";
19
- import {
20
- promptHidden,
21
- promptSelect,
22
- promptConfirm,
23
- closeTTYStream,
24
- hasTTY,
25
- } from "../utils/tty.js";
26
- import { loadEnv } from "../utils/api-key.js";
27
-
28
- export const setupCommand = new Command("setup")
29
- .description("Configure VibeFrame (LLM provider, API keys)")
30
- .option("--reset", "Reset configuration to defaults")
31
- .option("--full", "Run full setup with all optional providers")
32
- .option("--show", "Show current configuration (for debugging)")
33
- .option("--claude-code", "Set up Claude Code integration (.claude/rules/) in current directory")
34
- .action(async (options) => {
35
- if (options.claudeCode) {
36
- await setupClaudeCode();
37
- return;
38
- }
39
-
40
- if (options.show) {
41
- await showConfig();
42
- return;
43
- }
44
-
45
- if (options.reset) {
46
- const config = createDefaultConfig();
47
- await saveConfig(config);
48
- console.log(chalk.green("✓ Configuration reset to defaults"));
49
- console.log(chalk.dim(` Saved to: ${CONFIG_PATH}`));
50
- return;
51
- }
52
-
53
- // Check if TTY is available
54
- if (!hasTTY()) {
55
- console.error(chalk.red("Error: Interactive setup requires a terminal."));
56
- console.log(chalk.dim("Run 'vibe setup' directly from your terminal."));
57
- process.exit(1);
58
- }
59
-
60
- try {
61
- await runSetupWizard(options.full);
62
- closeTTYStream();
63
- // Explicitly exit to ensure clean termination when run from install script
64
- // The TTY stream can keep the event loop alive otherwise
65
- process.exit(0);
66
- } catch (err) {
67
- closeTTYStream();
68
- throw err;
69
- }
70
- });
71
-
72
- /**
73
- * Run the interactive setup wizard
74
- */
75
- async function runSetupWizard(fullSetup = false): Promise<void> {
76
- console.log();
77
- console.log(chalk.bold.magenta("VibeFrame Setup"));
78
- console.log(chalk.dim("─".repeat(40)));
79
- console.log();
80
-
81
- // Load existing config or create default
82
- let config = await loadConfig();
83
- if (!config) {
84
- config = createDefaultConfig();
85
- }
86
-
87
- // Step 1: Select LLM Provider
88
- console.log(chalk.bold("1. Choose your AI provider"));
89
- console.log(chalk.dim(" This provider handles natural language commands."));
90
- console.log();
91
-
92
- const providers: LLMProvider[] = ["claude", "openai", "gemini", "xai", "ollama"];
93
- const providerDescriptions: Record<LLMProvider, string> = {
94
- claude: "Best understanding, most capable",
95
- openai: "GPT-5-mini, reliable and fast",
96
- gemini: "Google AI, good for general use",
97
- xai: "Grok 4.1, optimized for tool calling",
98
- ollama: "Free, local, no API key needed",
99
- };
100
- const providerLabels = providers.map((p) => {
101
- const rec = p === "claude" ? chalk.dim(" (recommended)") : "";
102
- const desc = chalk.dim(` - ${providerDescriptions[p]}`);
103
- return `${PROVIDER_NAMES[p]}${rec}${desc}`;
104
- });
105
-
106
- const currentIndex = providers.indexOf(config.llm.provider);
107
- const providerIndex = await promptSelect(
108
- chalk.cyan(" Select [1-5]: "),
109
- providerLabels,
110
- currentIndex >= 0 ? currentIndex : 0
111
- );
112
- config.llm.provider = providers[providerIndex];
113
- console.log();
114
-
115
- // Step 2: API Key for selected provider
116
- const selectedProvider = config.llm.provider;
117
-
118
- // Show Ollama-specific guidance
119
- if (selectedProvider === "ollama") {
120
- console.log(chalk.bold("2. Ollama Setup"));
121
- console.log();
122
- console.log(chalk.dim(" Ollama runs locally and requires no API key."));
123
- console.log(chalk.dim(" Make sure Ollama is running before using VibeFrame:"));
124
- console.log();
125
- console.log(chalk.cyan(" ollama serve") + chalk.dim(" # Start server"));
126
- console.log(chalk.cyan(" ollama pull llama3.2") + chalk.dim(" # Download model (first time)"));
127
- console.log();
128
- console.log(chalk.dim(" Server should be running at http://localhost:11434"));
129
- console.log();
130
- }
131
-
132
- if (selectedProvider !== "ollama") {
133
- const providerKey =
134
- selectedProvider === "gemini"
135
- ? "google"
136
- : selectedProvider === "claude"
137
- ? "anthropic"
138
- : selectedProvider;
139
-
140
- console.log(chalk.bold(`2. ${PROVIDER_NAMES[selectedProvider]} API Key`));
141
- console.log(
142
- chalk.dim(` You can also set ${getEnvVarName(selectedProvider)} environment variable.`)
143
- );
144
- console.log();
145
-
146
- const existingKey = config.providers[providerKey as keyof typeof config.providers];
147
- if (existingKey) {
148
- console.log(chalk.dim(` Current: ${maskApiKey(existingKey)}`));
149
- const change = await promptConfirm(chalk.cyan(" Update?"), false);
150
- if (change) {
151
- const newKey = await promptHidden(chalk.cyan(" Enter API key: "));
152
- if (newKey.trim()) {
153
- config.providers[providerKey as keyof typeof config.providers] = newKey.trim();
154
- console.log(chalk.green(" ✓ Updated"));
155
- }
156
- }
157
- } else {
158
- const newKey = await promptHidden(chalk.cyan(" Enter API key: "));
159
- if (newKey.trim()) {
160
- config.providers[providerKey as keyof typeof config.providers] = newKey.trim();
161
- console.log(chalk.green(" ✓ Saved"));
162
- } else {
163
- console.log(chalk.yellow(" ⚠ Skipped (required for AI features)"));
164
- }
165
- }
166
- console.log();
167
- }
168
-
169
- // Step 3: Optional providers (only in full setup mode)
170
- if (fullSetup) {
171
- console.log(chalk.bold("3. Additional Providers (optional)"));
172
- console.log(chalk.dim(" Natural language, video generation, TTS, images, etc."));
173
- console.log();
174
-
175
- // Build list of optional providers, excluding the one already configured as primary LLM
176
- const allOptionalProviders = [
177
- { key: "openai", name: "OpenAI", desc: "NL Commands, DALL-E, Whisper" },
178
- { key: "anthropic", name: "Anthropic", desc: "Claude, NL Commands" },
179
- { key: "google", name: "Google", desc: "Gemini" },
180
- { key: "xai", name: "xAI", desc: "Grok, NL Commands" },
181
- { key: "elevenlabs", name: "ElevenLabs", desc: "TTS & Voice" },
182
- { key: "runway", name: "Runway", desc: "Video Gen" },
183
- { key: "kling", name: "Kling", desc: "Video Gen" },
184
- { key: "imgbb", name: "ImgBB", desc: "Image Hosting (for Kling)" },
185
- { key: "replicate", name: "Replicate", desc: "Various" },
186
- ];
187
-
188
- // Get the key of the primary LLM provider to skip it
189
- const primaryProviderKey =
190
- selectedProvider === "gemini"
191
- ? "google"
192
- : selectedProvider === "claude"
193
- ? "anthropic"
194
- : selectedProvider;
195
-
196
- // Filter out the primary provider
197
- const optionalProviders = allOptionalProviders.filter(
198
- (p) => p.key !== primaryProviderKey
199
- );
200
-
201
- for (const provider of optionalProviders) {
202
- const existing = config.providers[provider.key as keyof typeof config.providers];
203
- const status = existing ? chalk.green("✓") : chalk.dim("○");
204
-
205
- const configure = await promptConfirm(
206
- chalk.cyan(` ${status} ${provider.name} ${chalk.dim(`(${provider.desc})`)}?`),
207
- false
208
- );
209
-
210
- if (configure) {
211
- const key = await promptHidden(chalk.cyan(` API key: `));
212
- if (key.trim()) {
213
- config.providers[provider.key as keyof typeof config.providers] = key.trim();
214
- console.log(chalk.green(" ✓ Saved"));
215
- }
216
- }
217
- }
218
- console.log();
219
-
220
- // Step 4: Default aspect ratio
221
- console.log(chalk.bold("4. Default Aspect Ratio"));
222
- console.log();
223
-
224
- const ratios = ["16:9", "9:16", "1:1", "4:5"] as const;
225
- const ratioLabels = [
226
- "16:9 (YouTube, landscape)",
227
- "9:16 (TikTok, Reels, Shorts)",
228
- "1:1 (Instagram, square)",
229
- "4:5 (Instagram portrait)",
230
- ];
231
-
232
- const currentRatioIndex = ratios.indexOf(config.defaults.aspectRatio);
233
- const ratioIndex = await promptSelect(
234
- chalk.cyan(" Select [1-4]: "),
235
- ratioLabels,
236
- currentRatioIndex >= 0 ? currentRatioIndex : 0
237
- );
238
- config.defaults.aspectRatio = ratios[ratioIndex];
239
- console.log();
240
- }
241
-
242
- // Save configuration
243
- await saveConfig(config);
244
-
245
- // Done
246
- console.log(chalk.dim("─".repeat(40)));
247
- console.log(chalk.green.bold("✓ Setup complete!"));
248
- console.log();
249
- console.log(chalk.dim(`Config: ${CONFIG_PATH}`));
250
- console.log();
251
-
252
- // Suggest a "try it" command based on configured providers
253
- const { hasApiKey } = await import("../utils/api-key.js");
254
- if (hasApiKey("GOOGLE_API_KEY")) {
255
- console.log(` Try: ${chalk.cyan('vibe generate image "a sunset over mountains" -o test.png')}`);
256
- } else if (hasApiKey("ELEVENLABS_API_KEY")) {
257
- console.log(` Try: ${chalk.cyan('vibe generate speech "Hello world" -o hello.mp3')}`);
258
- } else if (hasApiKey("OPENAI_API_KEY")) {
259
- console.log(` Try: ${chalk.cyan('vibe generate image "a cute robot" -p openai -o test.png')}`);
260
- } else {
261
- console.log(` Try: ${chalk.cyan("vibe")} to start the interactive Agent`);
262
- }
263
- console.log();
264
- console.log(chalk.dim(` vibe doctor Check what's ready`));
265
- console.log(chalk.dim(` vibe setup --show Verify configuration`));
266
- console.log(chalk.dim(` vibe setup --full Configure more providers`));
267
- console.log();
268
- }
269
-
270
- /**
271
- * Set up Claude Code integration in current directory
272
- */
273
- async function setupClaudeCode(): Promise<void> {
274
- const targetDir = resolve(process.cwd(), ".claude", "rules");
275
- const targetFile = resolve(targetDir, "cli-reference.md");
276
-
277
- // Check if already exists
278
- let isUpdate = false;
279
- try {
280
- await access(targetFile);
281
- isUpdate = true;
282
- } catch {
283
- // doesn't exist yet, fresh install
284
- }
285
-
286
- // Read the bundled CLI reference from the package
287
- // In built dist: packages/cli/dist/commands/setup.js
288
- // Source reference: .claude/rules/cli-reference.md (relative to repo root)
289
- // We'll embed it directly since the file ships with the npm package
290
-
291
- const cliReference = await getCliReference();
292
-
293
- await mkdir(targetDir, { recursive: true });
294
- await writeFile(targetFile, cliReference, "utf-8");
295
-
296
- console.log();
297
- console.log(chalk.green(`✓ Claude Code integration ${isUpdate ? "updated" : "set up"}!`));
298
- console.log();
299
- console.log(chalk.dim(` ${isUpdate ? "Updated" : "Created"}: ${targetFile}`));
300
- console.log();
301
- console.log(" Claude Code now knows all VibeFrame commands.");
302
- console.log(" It will use " + chalk.cyan("vibe") + " commands directly without running --help.");
303
- console.log();
304
- }
305
-
306
- /**
307
- * Get CLI reference content (embedded)
308
- */
309
- async function getCliReference(): Promise<string> {
310
- // Try to read from the repo's .claude/rules/ first (dev mode)
311
- const __filename = fileURLToPath(import.meta.url);
312
- const __dirname = dirname(__filename);
313
-
314
- // Walk up to find the repo root or package root
315
- const possiblePaths = [
316
- // Dev mode: repo root
317
- resolve(__dirname, "..", "..", "..", "..", ".claude", "rules", "cli-reference.md"),
318
- // Built CLI (dist/commands/setup.js): packages/cli/.claude/rules/cli-reference.md
319
- resolve(__dirname, "..", "..", ".claude", "rules", "cli-reference.md"),
320
- // Installed via curl (~/.vibeframe/): .claude/rules/cli-reference.md
321
- resolve(__dirname, "..", "..", "..", "..", ".claude", "rules", "cli-reference.md"),
322
- ];
323
-
324
- for (const p of possiblePaths) {
325
- try {
326
- return await readFile(p, "utf-8");
327
- } catch {
328
- // try next
329
- }
330
- }
331
-
332
- // Fallback: generate a minimal reference
333
- return generateMinimalReference();
334
- }
335
-
336
- /**
337
- * Generate minimal CLI reference if bundled file not found
338
- */
339
- function generateMinimalReference(): string {
340
- return `# VibeFrame CLI Reference
341
-
342
- > Use these commands directly — no need to run \`--help\` first.
343
-
344
- ## Quick Reference
345
-
346
- \`\`\`bash
347
- # Image
348
- vibe ai image "<prompt>" -o out.png
349
- vibe ai gemini-edit <image> "<instruction>" -o out.png
350
-
351
- # Video
352
- vibe ai video "<prompt>" -o out.mp4 -d 5
353
- vibe ai kling "<prompt>" -o out.mp4 -d 5
354
-
355
- # Audio
356
- vibe ai tts "<text>" -o out.mp3
357
- vibe ai transcribe <audio> -o out.srt
358
-
359
- # Editing
360
- vibe ai silence-cut <video> -o out.mp4
361
- vibe ai caption <video> -o out.mp4 -s bold
362
- vibe ai noise-reduce <input> -o out.mp4
363
- vibe ai fade <video> -o out.mp4 --fade-in 1 --fade-out 1
364
- vibe ai grade <video> -o out.mp4 -p cinematic-warm
365
- vibe ai jump-cut <video> -o out.mp4
366
-
367
- # Analysis
368
- vibe ai analyze <source> "<prompt>"
369
- vibe ai gemini-video <video> "<prompt>"
370
-
371
- # Pipeline
372
- vibe ai script-to-video "<script>" -o output-dir/ -g runway
373
- vibe ai highlights <video> -d 60
374
- vibe ai auto-shorts <video> -o shorts/ -n 3
375
-
376
- # Project
377
- vibe project create <name> -o project.vibe.json
378
- vibe timeline add-source <project> <media>
379
- vibe timeline add-clip <project> <source-id>
380
- vibe export <project> -o output.mp4 -y
381
- \`\`\`
382
-
383
- Run \`vibe ai --help\` for full command list with all options.
384
- `;
385
- }
386
-
387
- /**
388
- * Mask API key for display
389
- */
390
- function maskApiKey(key: string): string {
391
- if (key.length <= 8) return "****";
392
- return `${key.slice(0, 4)}${"*".repeat(8)}${key.slice(-4)}`;
393
- }
394
-
395
- /**
396
- * Get environment variable name for a provider
397
- */
398
- function getEnvVarName(provider: LLMProvider): string {
399
- const envVars: Record<LLMProvider, string> = {
400
- claude: "ANTHROPIC_API_KEY",
401
- openai: "OPENAI_API_KEY",
402
- gemini: "GOOGLE_API_KEY",
403
- xai: "XAI_API_KEY",
404
- ollama: "",
405
- };
406
- return envVars[provider];
407
- }
408
-
409
- /**
410
- * Show current configuration for debugging
411
- */
412
- async function showConfig(): Promise<void> {
413
- // Load CWD .env before showing config
414
- loadEnv();
415
-
416
- const cwdEnvPath = resolve(process.cwd(), ".env");
417
- let hasCwdEnv = false;
418
- try {
419
- await access(cwdEnvPath);
420
- hasCwdEnv = true;
421
- } catch {
422
- // no .env in CWD
423
- }
424
-
425
- console.log();
426
- console.log(chalk.bold.magenta("VibeFrame Configuration"));
427
- console.log(chalk.dim("─".repeat(40)));
428
- console.log();
429
- console.log(chalk.dim(`Config file: ${CONFIG_PATH}`));
430
- if (hasCwdEnv) {
431
- console.log(chalk.dim(`Project .env: ${cwdEnvPath}`));
432
- }
433
- console.log();
434
-
435
- const config = await loadConfig();
436
-
437
- // Parse .env file directly to know which keys are in it
438
- let dotenvKeys: Record<string, string> = {};
439
- if (hasCwdEnv) {
440
- try {
441
- const envContent = await readFile(cwdEnvPath, "utf-8");
442
- dotenvKeys = parseDotenv(Buffer.from(envContent));
443
- } catch {
444
- // ignore parse errors
445
- }
446
- }
447
-
448
- if (!config && !hasCwdEnv) {
449
- console.log(chalk.yellow("No configuration found."));
450
- console.log(chalk.dim("Run 'vibe setup' or create .env in your project directory."));
451
- return;
452
- }
453
-
454
- // Show LLM provider
455
- if (config) {
456
- console.log(chalk.bold("LLM Provider:"));
457
- console.log(` ${PROVIDER_NAMES[config.llm.provider]}`);
458
- console.log();
459
- }
460
-
461
- // Show API keys (masked) with accurate source detection
462
- console.log(chalk.bold("API Keys:"));
463
- const providerKeys = [
464
- { key: "anthropic", name: "Anthropic", env: "ANTHROPIC_API_KEY" },
465
- { key: "openai", name: "OpenAI", env: "OPENAI_API_KEY" },
466
- { key: "google", name: "Google", env: "GOOGLE_API_KEY" },
467
- { key: "xai", name: "xAI", env: "XAI_API_KEY" },
468
- { key: "elevenlabs", name: "ElevenLabs", env: "ELEVENLABS_API_KEY" },
469
- { key: "runway", name: "Runway", env: "RUNWAY_API_SECRET" },
470
- { key: "kling", name: "Kling", env: "KLING_API_KEY" },
471
- { key: "imgbb", name: "ImgBB", env: "IMGBB_API_KEY" },
472
- { key: "replicate", name: "Replicate", env: "REPLICATE_API_TOKEN" },
473
- ];
474
-
475
- for (const p of providerKeys) {
476
- const configValue = config?.providers[p.key as keyof typeof config.providers];
477
- const dotenvValue = dotenvKeys[p.env];
478
-
479
- if (configValue || dotenvValue) {
480
- // Show effective value (config wins) and all sources
481
- const sources: string[] = [];
482
- if (configValue) sources.push("config");
483
- if (dotenvValue) sources.push(".env");
484
- const value = configValue || dotenvValue;
485
- const status = chalk.green("✓");
486
- console.log(` ${status} ${p.name.padEnd(12)} ${maskApiKey(value)} (${sources.join(" + ")})`);
487
- } else {
488
- const status = chalk.dim("○");
489
- console.log(` ${status} ${p.name.padEnd(12)} ${chalk.dim("not set")}`);
490
- }
491
- }
492
- console.log();
493
-
494
- // Show defaults
495
- if (config) {
496
- console.log(chalk.bold("Defaults:"));
497
- console.log(` Aspect Ratio: ${config.defaults.aspectRatio}`);
498
- console.log(` Export Quality: ${config.defaults.exportQuality}`);
499
- console.log();
500
- }
501
-
502
- // Show resolution order
503
- console.log(chalk.bold("Resolution order:"));
504
- console.log(chalk.dim(" 1. --api-key CLI option"));
505
- console.log(chalk.dim(` 2. ${CONFIG_PATH}`));
506
- console.log(chalk.dim(" 3. .env in current directory"));
507
- console.log(chalk.dim(" 4. Shell environment variables"));
508
- console.log();
509
- }