@vibeframe/cli 0.27.0 → 0.30.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 (118) 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/ai-edit-cli.d.ts.map +1 -1
  16. package/dist/commands/ai-edit-cli.js +18 -0
  17. package/dist/commands/ai-edit-cli.js.map +1 -1
  18. package/dist/commands/generate.js +14 -0
  19. package/dist/commands/generate.js.map +1 -1
  20. package/dist/commands/schema.d.ts +1 -0
  21. package/dist/commands/schema.d.ts.map +1 -1
  22. package/dist/commands/schema.js +122 -21
  23. package/dist/commands/schema.js.map +1 -1
  24. package/dist/commands/setup.js +5 -2
  25. package/dist/commands/setup.js.map +1 -1
  26. package/dist/config/schema.d.ts +2 -1
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/config/schema.js +2 -0
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/index.js +0 -0
  31. package/package.json +16 -12
  32. package/.turbo/turbo-build.log +0 -4
  33. package/.turbo/turbo-lint.log +0 -21
  34. package/.turbo/turbo-test.log +0 -689
  35. package/src/agent/adapters/claude.ts +0 -143
  36. package/src/agent/adapters/gemini.ts +0 -159
  37. package/src/agent/adapters/index.ts +0 -61
  38. package/src/agent/adapters/ollama.ts +0 -231
  39. package/src/agent/adapters/openai.ts +0 -116
  40. package/src/agent/adapters/xai.ts +0 -119
  41. package/src/agent/index.ts +0 -251
  42. package/src/agent/memory/index.ts +0 -151
  43. package/src/agent/prompts/system.ts +0 -106
  44. package/src/agent/tools/ai-editing.ts +0 -845
  45. package/src/agent/tools/ai-generation.ts +0 -1073
  46. package/src/agent/tools/ai-pipeline.ts +0 -1055
  47. package/src/agent/tools/ai.ts +0 -21
  48. package/src/agent/tools/batch.ts +0 -429
  49. package/src/agent/tools/e2e.test.ts +0 -545
  50. package/src/agent/tools/export.ts +0 -184
  51. package/src/agent/tools/filesystem.ts +0 -237
  52. package/src/agent/tools/index.ts +0 -150
  53. package/src/agent/tools/integration.test.ts +0 -775
  54. package/src/agent/tools/media.ts +0 -697
  55. package/src/agent/tools/project.ts +0 -313
  56. package/src/agent/tools/timeline.ts +0 -951
  57. package/src/agent/types.ts +0 -68
  58. package/src/commands/agent.ts +0 -340
  59. package/src/commands/ai-analyze.ts +0 -429
  60. package/src/commands/ai-animated-caption.ts +0 -390
  61. package/src/commands/ai-audio.ts +0 -941
  62. package/src/commands/ai-broll.ts +0 -490
  63. package/src/commands/ai-edit-cli.ts +0 -658
  64. package/src/commands/ai-edit.ts +0 -1542
  65. package/src/commands/ai-fill-gaps.ts +0 -566
  66. package/src/commands/ai-helpers.ts +0 -65
  67. package/src/commands/ai-highlights.ts +0 -1303
  68. package/src/commands/ai-image.ts +0 -761
  69. package/src/commands/ai-motion.ts +0 -347
  70. package/src/commands/ai-narrate.ts +0 -451
  71. package/src/commands/ai-review.ts +0 -309
  72. package/src/commands/ai-script-pipeline-cli.ts +0 -1710
  73. package/src/commands/ai-script-pipeline.ts +0 -1365
  74. package/src/commands/ai-suggest-edit.ts +0 -264
  75. package/src/commands/ai-video-fx.ts +0 -445
  76. package/src/commands/ai-video.ts +0 -915
  77. package/src/commands/ai-viral.ts +0 -595
  78. package/src/commands/ai-visual-fx.ts +0 -601
  79. package/src/commands/ai.test.ts +0 -627
  80. package/src/commands/ai.ts +0 -307
  81. package/src/commands/analyze.ts +0 -282
  82. package/src/commands/audio.ts +0 -644
  83. package/src/commands/batch.test.ts +0 -279
  84. package/src/commands/batch.ts +0 -440
  85. package/src/commands/detect.ts +0 -329
  86. package/src/commands/doctor.ts +0 -237
  87. package/src/commands/edit-cmd.ts +0 -1014
  88. package/src/commands/export.ts +0 -918
  89. package/src/commands/generate.ts +0 -2146
  90. package/src/commands/media.ts +0 -177
  91. package/src/commands/output.ts +0 -142
  92. package/src/commands/pipeline.ts +0 -398
  93. package/src/commands/project.test.ts +0 -127
  94. package/src/commands/project.ts +0 -149
  95. package/src/commands/sanitize.ts +0 -60
  96. package/src/commands/schema.ts +0 -130
  97. package/src/commands/setup.ts +0 -509
  98. package/src/commands/timeline.test.ts +0 -499
  99. package/src/commands/timeline.ts +0 -529
  100. package/src/commands/validate.ts +0 -77
  101. package/src/config/config.test.ts +0 -197
  102. package/src/config/index.ts +0 -125
  103. package/src/config/schema.ts +0 -82
  104. package/src/engine/index.ts +0 -2
  105. package/src/engine/project.test.ts +0 -702
  106. package/src/engine/project.ts +0 -439
  107. package/src/index.ts +0 -146
  108. package/src/utils/api-key.test.ts +0 -41
  109. package/src/utils/api-key.ts +0 -247
  110. package/src/utils/audio.ts +0 -83
  111. package/src/utils/exec-safe.ts +0 -75
  112. package/src/utils/first-run.ts +0 -52
  113. package/src/utils/provider-resolver.ts +0 -56
  114. package/src/utils/remotion.ts +0 -951
  115. package/src/utils/subtitle.test.ts +0 -227
  116. package/src/utils/subtitle.ts +0 -169
  117. package/src/utils/tty.ts +0 -196
  118. 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
- }