@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.
- package/LICENSE +21 -0
- package/dist/agent/adapters/index.d.ts +1 -0
- package/dist/agent/adapters/index.d.ts.map +1 -1
- package/dist/agent/adapters/index.js +5 -0
- package/dist/agent/adapters/index.js.map +1 -1
- package/dist/agent/adapters/openrouter.d.ts +16 -0
- package/dist/agent/adapters/openrouter.d.ts.map +1 -0
- package/dist/agent/adapters/openrouter.js +100 -0
- package/dist/agent/adapters/openrouter.js.map +1 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +3 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/ai-edit-cli.d.ts.map +1 -1
- package/dist/commands/ai-edit-cli.js +18 -0
- package/dist/commands/ai-edit-cli.js.map +1 -1
- package/dist/commands/generate.js +14 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/schema.d.ts +1 -0
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +122 -21
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/setup.js +5 -2
- package/dist/commands/setup.js.map +1 -1
- package/dist/config/schema.d.ts +2 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/index.js +0 -0
- package/package.json +16 -12
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-lint.log +0 -21
- package/.turbo/turbo-test.log +0 -689
- package/src/agent/adapters/claude.ts +0 -143
- package/src/agent/adapters/gemini.ts +0 -159
- package/src/agent/adapters/index.ts +0 -61
- package/src/agent/adapters/ollama.ts +0 -231
- package/src/agent/adapters/openai.ts +0 -116
- package/src/agent/adapters/xai.ts +0 -119
- package/src/agent/index.ts +0 -251
- package/src/agent/memory/index.ts +0 -151
- package/src/agent/prompts/system.ts +0 -106
- package/src/agent/tools/ai-editing.ts +0 -845
- package/src/agent/tools/ai-generation.ts +0 -1073
- package/src/agent/tools/ai-pipeline.ts +0 -1055
- package/src/agent/tools/ai.ts +0 -21
- package/src/agent/tools/batch.ts +0 -429
- package/src/agent/tools/e2e.test.ts +0 -545
- package/src/agent/tools/export.ts +0 -184
- package/src/agent/tools/filesystem.ts +0 -237
- package/src/agent/tools/index.ts +0 -150
- package/src/agent/tools/integration.test.ts +0 -775
- package/src/agent/tools/media.ts +0 -697
- package/src/agent/tools/project.ts +0 -313
- package/src/agent/tools/timeline.ts +0 -951
- package/src/agent/types.ts +0 -68
- package/src/commands/agent.ts +0 -340
- package/src/commands/ai-analyze.ts +0 -429
- package/src/commands/ai-animated-caption.ts +0 -390
- package/src/commands/ai-audio.ts +0 -941
- package/src/commands/ai-broll.ts +0 -490
- package/src/commands/ai-edit-cli.ts +0 -658
- package/src/commands/ai-edit.ts +0 -1542
- package/src/commands/ai-fill-gaps.ts +0 -566
- package/src/commands/ai-helpers.ts +0 -65
- package/src/commands/ai-highlights.ts +0 -1303
- package/src/commands/ai-image.ts +0 -761
- package/src/commands/ai-motion.ts +0 -347
- package/src/commands/ai-narrate.ts +0 -451
- package/src/commands/ai-review.ts +0 -309
- package/src/commands/ai-script-pipeline-cli.ts +0 -1710
- package/src/commands/ai-script-pipeline.ts +0 -1365
- package/src/commands/ai-suggest-edit.ts +0 -264
- package/src/commands/ai-video-fx.ts +0 -445
- package/src/commands/ai-video.ts +0 -915
- package/src/commands/ai-viral.ts +0 -595
- package/src/commands/ai-visual-fx.ts +0 -601
- package/src/commands/ai.test.ts +0 -627
- package/src/commands/ai.ts +0 -307
- package/src/commands/analyze.ts +0 -282
- package/src/commands/audio.ts +0 -644
- package/src/commands/batch.test.ts +0 -279
- package/src/commands/batch.ts +0 -440
- package/src/commands/detect.ts +0 -329
- package/src/commands/doctor.ts +0 -237
- package/src/commands/edit-cmd.ts +0 -1014
- package/src/commands/export.ts +0 -918
- package/src/commands/generate.ts +0 -2146
- package/src/commands/media.ts +0 -177
- package/src/commands/output.ts +0 -142
- package/src/commands/pipeline.ts +0 -398
- package/src/commands/project.test.ts +0 -127
- package/src/commands/project.ts +0 -149
- package/src/commands/sanitize.ts +0 -60
- package/src/commands/schema.ts +0 -130
- package/src/commands/setup.ts +0 -509
- package/src/commands/timeline.test.ts +0 -499
- package/src/commands/timeline.ts +0 -529
- package/src/commands/validate.ts +0 -77
- package/src/config/config.test.ts +0 -197
- package/src/config/index.ts +0 -125
- package/src/config/schema.ts +0 -82
- package/src/engine/index.ts +0 -2
- package/src/engine/project.test.ts +0 -702
- package/src/engine/project.ts +0 -439
- package/src/index.ts +0 -146
- package/src/utils/api-key.test.ts +0 -41
- package/src/utils/api-key.ts +0 -247
- package/src/utils/audio.ts +0 -83
- package/src/utils/exec-safe.ts +0 -75
- package/src/utils/first-run.ts +0 -52
- package/src/utils/provider-resolver.ts +0 -56
- package/src/utils/remotion.ts +0 -951
- package/src/utils/subtitle.test.ts +0 -227
- package/src/utils/subtitle.ts +0 -169
- package/src/utils/tty.ts +0 -196
- package/tsconfig.json +0 -20
package/src/commands/detect.ts
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { resolve, basename } from "node:path";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import ora from "ora";
|
|
6
|
-
import { Project, type ProjectFile } from "../engine/index.js";
|
|
7
|
-
import { execSafe, commandExists, ffprobeDuration } from "../utils/exec-safe.js";
|
|
8
|
-
|
|
9
|
-
export const detectCommand = new Command("detect")
|
|
10
|
-
.description("Auto-detect scenes, beats, and silences in media");
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Scene change detection using FFmpeg
|
|
14
|
-
*/
|
|
15
|
-
detectCommand
|
|
16
|
-
.command("scenes")
|
|
17
|
-
.description("Detect scene changes in video")
|
|
18
|
-
.argument("<video>", "Video file path")
|
|
19
|
-
.option("-t, --threshold <value>", "Scene change threshold (0-1)", "0.3")
|
|
20
|
-
.option("-o, --output <path>", "Output JSON file with timestamps")
|
|
21
|
-
.option("-p, --project <path>", "Add scenes as clips to project")
|
|
22
|
-
.action(async (videoPath: string, options) => {
|
|
23
|
-
const spinner = ora("Detecting scenes...").start();
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
// Check if FFmpeg is available
|
|
27
|
-
if (!commandExists("ffmpeg")) {
|
|
28
|
-
spinner.fail(chalk.red("FFmpeg not found. Please install FFmpeg."));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const absPath = resolve(process.cwd(), videoPath);
|
|
33
|
-
const threshold = parseFloat(options.threshold);
|
|
34
|
-
|
|
35
|
-
// Use FFmpeg to detect scene changes
|
|
36
|
-
spinner.text = "Analyzing video...";
|
|
37
|
-
|
|
38
|
-
const { stdout: sceneStdout, stderr: sceneStderr } = await execSafe("ffmpeg", [
|
|
39
|
-
"-i", absPath,
|
|
40
|
-
"-filter:v", `select='gt(scene,${threshold})',showinfo`,
|
|
41
|
-
"-f", "null", "-",
|
|
42
|
-
], { maxBuffer: 50 * 1024 * 1024 }).catch((err) => {
|
|
43
|
-
// ffmpeg writes filter output to stderr and exits non-zero with -f null
|
|
44
|
-
if (err.stdout !== undefined || err.stderr !== undefined) {
|
|
45
|
-
return { stdout: err.stdout || "", stderr: err.stderr || "" };
|
|
46
|
-
}
|
|
47
|
-
throw err;
|
|
48
|
-
});
|
|
49
|
-
const output = sceneStdout + sceneStderr;
|
|
50
|
-
|
|
51
|
-
// Parse scene change timestamps from showinfo output
|
|
52
|
-
const scenes: { timestamp: number; score: number }[] = [];
|
|
53
|
-
const regex = /pts_time:(\d+\.?\d*)/g;
|
|
54
|
-
let match;
|
|
55
|
-
|
|
56
|
-
// Always start with 0
|
|
57
|
-
scenes.push({ timestamp: 0, score: 1 });
|
|
58
|
-
|
|
59
|
-
while ((match = regex.exec(output)) !== null) {
|
|
60
|
-
const timestamp = parseFloat(match[1]);
|
|
61
|
-
scenes.push({ timestamp, score: threshold });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Get video duration
|
|
65
|
-
const totalDuration = await ffprobeDuration(absPath);
|
|
66
|
-
|
|
67
|
-
spinner.succeed(chalk.green(`Detected ${scenes.length} scenes`));
|
|
68
|
-
|
|
69
|
-
console.log();
|
|
70
|
-
console.log(chalk.bold.cyan("Scene Timestamps"));
|
|
71
|
-
console.log(chalk.dim("─".repeat(60)));
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < scenes.length; i++) {
|
|
74
|
-
const start = scenes[i].timestamp;
|
|
75
|
-
const end = i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration;
|
|
76
|
-
const duration = end - start;
|
|
77
|
-
console.log(
|
|
78
|
-
`${chalk.yellow(`[${i + 1}]`)} ${formatTimestamp(start)} - ${formatTimestamp(end)} ${chalk.dim(`(${duration.toFixed(1)}s)`)}`
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
console.log();
|
|
82
|
-
|
|
83
|
-
// Save to JSON
|
|
84
|
-
if (options.output) {
|
|
85
|
-
const outputPath = resolve(process.cwd(), options.output);
|
|
86
|
-
const result = {
|
|
87
|
-
source: absPath,
|
|
88
|
-
totalDuration,
|
|
89
|
-
threshold,
|
|
90
|
-
scenes: scenes.map((s, i) => ({
|
|
91
|
-
index: i,
|
|
92
|
-
startTime: s.timestamp,
|
|
93
|
-
endTime: i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration,
|
|
94
|
-
duration:
|
|
95
|
-
(i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration) - s.timestamp,
|
|
96
|
-
})),
|
|
97
|
-
};
|
|
98
|
-
await writeFile(outputPath, JSON.stringify(result, null, 2), "utf-8");
|
|
99
|
-
console.log(chalk.green(`Saved to: ${outputPath}`));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Add to project
|
|
103
|
-
if (options.project) {
|
|
104
|
-
const projectPath = resolve(process.cwd(), options.project);
|
|
105
|
-
const content = await readFile(projectPath, "utf-8");
|
|
106
|
-
const data: ProjectFile = JSON.parse(content);
|
|
107
|
-
const project = Project.fromJSON(data);
|
|
108
|
-
|
|
109
|
-
// Add video as source if not exists
|
|
110
|
-
let source = project.getSources().find((s) => s.url === absPath);
|
|
111
|
-
if (!source) {
|
|
112
|
-
source = project.addSource({
|
|
113
|
-
name: basename(absPath),
|
|
114
|
-
type: "video",
|
|
115
|
-
url: absPath,
|
|
116
|
-
duration: totalDuration,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Add clips for each scene
|
|
121
|
-
for (let i = 0; i < scenes.length; i++) {
|
|
122
|
-
const start = scenes[i].timestamp;
|
|
123
|
-
const end = i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration;
|
|
124
|
-
const duration = end - start;
|
|
125
|
-
|
|
126
|
-
project.addClip({
|
|
127
|
-
sourceId: source.id,
|
|
128
|
-
trackId: project.getTracks().find((t) => t.type === "video")?.id || "",
|
|
129
|
-
startTime: start,
|
|
130
|
-
duration,
|
|
131
|
-
sourceStartOffset: start,
|
|
132
|
-
sourceEndOffset: end,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
await writeFile(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
137
|
-
console.log(chalk.green(`Added ${scenes.length} clips to project`));
|
|
138
|
-
}
|
|
139
|
-
} catch (error) {
|
|
140
|
-
spinner.fail(chalk.red("Scene detection failed"));
|
|
141
|
-
console.error(error);
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Silence detection using FFmpeg
|
|
148
|
-
*/
|
|
149
|
-
detectCommand
|
|
150
|
-
.command("silence")
|
|
151
|
-
.description("Detect silence in audio/video")
|
|
152
|
-
.argument("<media>", "Media file path")
|
|
153
|
-
.option("-n, --noise <dB>", "Noise threshold in dB", "-30")
|
|
154
|
-
.option("-d, --duration <sec>", "Minimum silence duration", "0.5")
|
|
155
|
-
.option("-o, --output <path>", "Output JSON file with timestamps")
|
|
156
|
-
.action(async (mediaPath: string, options) => {
|
|
157
|
-
const spinner = ora("Detecting silence...").start();
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const absPath = resolve(process.cwd(), mediaPath);
|
|
161
|
-
const noise = options.noise;
|
|
162
|
-
const duration = options.duration;
|
|
163
|
-
|
|
164
|
-
const { stdout: silStdout, stderr: silStderr } = await execSafe("ffmpeg", [
|
|
165
|
-
"-i", absPath,
|
|
166
|
-
"-af", `silencedetect=noise=${noise}dB:d=${duration}`,
|
|
167
|
-
"-f", "null", "-",
|
|
168
|
-
], { maxBuffer: 50 * 1024 * 1024 }).catch((err) => {
|
|
169
|
-
if (err.stdout !== undefined || err.stderr !== undefined) {
|
|
170
|
-
return { stdout: err.stdout || "", stderr: err.stderr || "" };
|
|
171
|
-
}
|
|
172
|
-
throw err;
|
|
173
|
-
});
|
|
174
|
-
const output = silStdout + silStderr;
|
|
175
|
-
|
|
176
|
-
// Parse silence periods
|
|
177
|
-
const silences: { start: number; end: number; duration: number }[] = [];
|
|
178
|
-
const startRegex = /silence_start: (\d+\.?\d*)/g;
|
|
179
|
-
const endRegex = /silence_end: (\d+\.?\d*) \| silence_duration: (\d+\.?\d*)/g;
|
|
180
|
-
|
|
181
|
-
const starts: number[] = [];
|
|
182
|
-
let match;
|
|
183
|
-
|
|
184
|
-
while ((match = startRegex.exec(output)) !== null) {
|
|
185
|
-
starts.push(parseFloat(match[1]));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
let i = 0;
|
|
189
|
-
while ((match = endRegex.exec(output)) !== null) {
|
|
190
|
-
if (i < starts.length) {
|
|
191
|
-
silences.push({
|
|
192
|
-
start: starts[i],
|
|
193
|
-
end: parseFloat(match[1]),
|
|
194
|
-
duration: parseFloat(match[2]),
|
|
195
|
-
});
|
|
196
|
-
i++;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
spinner.succeed(chalk.green(`Detected ${silences.length} silence periods`));
|
|
201
|
-
|
|
202
|
-
console.log();
|
|
203
|
-
console.log(chalk.bold.cyan("Silence Periods"));
|
|
204
|
-
console.log(chalk.dim("─".repeat(60)));
|
|
205
|
-
|
|
206
|
-
for (let i = 0; i < silences.length; i++) {
|
|
207
|
-
const s = silences[i];
|
|
208
|
-
console.log(
|
|
209
|
-
`${chalk.yellow(`[${i + 1}]`)} ${formatTimestamp(s.start)} - ${formatTimestamp(s.end)} ${chalk.dim(`(${s.duration.toFixed(1)}s)`)}`
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
console.log();
|
|
213
|
-
|
|
214
|
-
if (options.output) {
|
|
215
|
-
const outputPath = resolve(process.cwd(), options.output);
|
|
216
|
-
await writeFile(
|
|
217
|
-
outputPath,
|
|
218
|
-
JSON.stringify({ source: absPath, silences }, null, 2),
|
|
219
|
-
"utf-8"
|
|
220
|
-
);
|
|
221
|
-
console.log(chalk.green(`Saved to: ${outputPath}`));
|
|
222
|
-
}
|
|
223
|
-
} catch (error) {
|
|
224
|
-
spinner.fail(chalk.red("Silence detection failed"));
|
|
225
|
-
console.error(error);
|
|
226
|
-
process.exit(1);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Beat detection for music sync
|
|
232
|
-
*/
|
|
233
|
-
detectCommand
|
|
234
|
-
.command("beats")
|
|
235
|
-
.description("Detect beats in audio (for music sync)")
|
|
236
|
-
.argument("<audio>", "Audio file path")
|
|
237
|
-
.option("-o, --output <path>", "Output JSON file with timestamps")
|
|
238
|
-
.action(async (audioPath: string, options) => {
|
|
239
|
-
const spinner = ora("Detecting beats...").start();
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
const absPath = resolve(process.cwd(), audioPath);
|
|
243
|
-
|
|
244
|
-
// Use FFmpeg's ebur128 filter for loudness analysis
|
|
245
|
-
// This gives us a rough approximation of beat positions
|
|
246
|
-
const { stdout: beatStdout, stderr: beatStderr } = await execSafe("ffmpeg", [
|
|
247
|
-
"-i", absPath,
|
|
248
|
-
"-af", "aresample=16000,ebur128=peak=true",
|
|
249
|
-
"-f", "null", "-",
|
|
250
|
-
], { maxBuffer: 50 * 1024 * 1024 }).catch((err) => {
|
|
251
|
-
if (err.stdout !== undefined || err.stderr !== undefined) {
|
|
252
|
-
return { stdout: err.stdout || "", stderr: err.stderr || "" };
|
|
253
|
-
}
|
|
254
|
-
throw err;
|
|
255
|
-
});
|
|
256
|
-
const beatOutput = beatStdout + beatStderr;
|
|
257
|
-
|
|
258
|
-
// Extract peak moments as approximate beats
|
|
259
|
-
const beats: number[] = [];
|
|
260
|
-
const peakRegex = /t:\s*(\d+\.?\d*)\s+M:\s*-?\d+\.?\d*\s+S:\s*-?\d+\.?\d*\s+I:\s*-?\d+\.?\d*\s+LUFS\s+LRA:\s*\d+\.?\d*\s+LU\s+FTPK:\s*(-?\d+\.?\d*)/g;
|
|
261
|
-
|
|
262
|
-
let match;
|
|
263
|
-
let lastBeatTime = -0.5;
|
|
264
|
-
|
|
265
|
-
while ((match = peakRegex.exec(beatOutput)) !== null) {
|
|
266
|
-
const time = parseFloat(match[1]);
|
|
267
|
-
const peak = parseFloat(match[2]);
|
|
268
|
-
|
|
269
|
-
// Consider it a beat if peak is high and enough time has passed
|
|
270
|
-
if (peak > -10 && time - lastBeatTime > 0.3) {
|
|
271
|
-
beats.push(time);
|
|
272
|
-
lastBeatTime = time;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Fallback: if no beats detected, use simple interval-based detection
|
|
277
|
-
if (beats.length === 0) {
|
|
278
|
-
spinner.text = "Using interval-based detection...";
|
|
279
|
-
|
|
280
|
-
// Get duration
|
|
281
|
-
const totalDuration = await ffprobeDuration(absPath);
|
|
282
|
-
|
|
283
|
-
// Estimate BPM from audio length (rough approximation)
|
|
284
|
-
const estimatedBPM = 120;
|
|
285
|
-
const beatInterval = 60 / estimatedBPM;
|
|
286
|
-
|
|
287
|
-
for (let t = 0; t < totalDuration; t += beatInterval) {
|
|
288
|
-
beats.push(t);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
spinner.succeed(chalk.green(`Detected ${beats.length} beats`));
|
|
293
|
-
|
|
294
|
-
console.log();
|
|
295
|
-
console.log(chalk.bold.cyan("Beat Timestamps"));
|
|
296
|
-
console.log(chalk.dim("─".repeat(60)));
|
|
297
|
-
|
|
298
|
-
// Show first 20 beats
|
|
299
|
-
const displayBeats = beats.slice(0, 20);
|
|
300
|
-
for (let i = 0; i < displayBeats.length; i++) {
|
|
301
|
-
console.log(`${chalk.yellow(`[${i + 1}]`)} ${formatTimestamp(displayBeats[i])}`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (beats.length > 20) {
|
|
305
|
-
console.log(chalk.dim(`... and ${beats.length - 20} more`));
|
|
306
|
-
}
|
|
307
|
-
console.log();
|
|
308
|
-
|
|
309
|
-
if (options.output) {
|
|
310
|
-
const outputPath = resolve(process.cwd(), options.output);
|
|
311
|
-
await writeFile(
|
|
312
|
-
outputPath,
|
|
313
|
-
JSON.stringify({ source: absPath, beatCount: beats.length, beats }, null, 2),
|
|
314
|
-
"utf-8"
|
|
315
|
-
);
|
|
316
|
-
console.log(chalk.green(`Saved to: ${outputPath}`));
|
|
317
|
-
}
|
|
318
|
-
} catch (error) {
|
|
319
|
-
spinner.fail(chalk.red("Beat detection failed"));
|
|
320
|
-
console.error(error);
|
|
321
|
-
process.exit(1);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
function formatTimestamp(seconds: number): string {
|
|
326
|
-
const mins = Math.floor(seconds / 60);
|
|
327
|
-
const secs = (seconds % 60).toFixed(2);
|
|
328
|
-
return `${mins.toString().padStart(2, "0")}:${secs.padStart(5, "0")}`;
|
|
329
|
-
}
|
package/src/commands/doctor.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Doctor command - System health check and capability report
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Command } from "commander";
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
import { access } from "node:fs/promises";
|
|
8
|
-
import { CONFIG_PATH } from "../config/index.js";
|
|
9
|
-
import { PROVIDER_ENV_VARS } from "../config/schema.js";
|
|
10
|
-
import { commandExists } from "../utils/exec-safe.js";
|
|
11
|
-
import { execSafe } from "../utils/exec-safe.js";
|
|
12
|
-
import { loadEnv } from "../utils/api-key.js";
|
|
13
|
-
|
|
14
|
-
/** Mapping of env vars to the commands they unlock */
|
|
15
|
-
const COMMAND_KEY_MAP: Record<string, string[]> = {
|
|
16
|
-
GOOGLE_API_KEY: [
|
|
17
|
-
"generate image",
|
|
18
|
-
"generate video -p veo",
|
|
19
|
-
"edit image",
|
|
20
|
-
"analyze media",
|
|
21
|
-
"analyze video",
|
|
22
|
-
"analyze review",
|
|
23
|
-
],
|
|
24
|
-
OPENAI_API_KEY: [
|
|
25
|
-
"agent -p openai",
|
|
26
|
-
"generate image -p openai",
|
|
27
|
-
"edit image -p openai",
|
|
28
|
-
"audio transcribe",
|
|
29
|
-
"edit caption",
|
|
30
|
-
"edit jump-cut",
|
|
31
|
-
],
|
|
32
|
-
ANTHROPIC_API_KEY: [
|
|
33
|
-
"agent -p claude",
|
|
34
|
-
"generate storyboard",
|
|
35
|
-
"generate motion",
|
|
36
|
-
"edit grade",
|
|
37
|
-
"edit reframe",
|
|
38
|
-
"edit speed-ramp",
|
|
39
|
-
"pipeline script-to-video",
|
|
40
|
-
],
|
|
41
|
-
XAI_API_KEY: [
|
|
42
|
-
"agent -p xai",
|
|
43
|
-
"generate image -p grok",
|
|
44
|
-
"generate video",
|
|
45
|
-
"edit image -p grok",
|
|
46
|
-
],
|
|
47
|
-
ELEVENLABS_API_KEY: [
|
|
48
|
-
"generate speech",
|
|
49
|
-
"generate sound-effect",
|
|
50
|
-
"generate music",
|
|
51
|
-
"audio voices",
|
|
52
|
-
"audio voice-clone",
|
|
53
|
-
"audio dub",
|
|
54
|
-
],
|
|
55
|
-
KLING_API_KEY: ["generate video -p kling"],
|
|
56
|
-
RUNWAY_API_SECRET: ["generate video -p runway"],
|
|
57
|
-
REPLICATE_API_TOKEN: ["generate music -p replicate"],
|
|
58
|
-
IMGBB_API_KEY: ["generate video -p kling (image-to-video)"],
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/** Commands that need no API key (FFmpeg only) */
|
|
62
|
-
const FREE_COMMANDS = [
|
|
63
|
-
"edit silence-cut",
|
|
64
|
-
"edit noise-reduce",
|
|
65
|
-
"edit fade",
|
|
66
|
-
"edit text-overlay",
|
|
67
|
-
"detect scenes",
|
|
68
|
-
"detect silence",
|
|
69
|
-
"detect beats",
|
|
70
|
-
"export",
|
|
71
|
-
"project create/info",
|
|
72
|
-
"timeline (all)",
|
|
73
|
-
"batch import/concat",
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
export const doctorCommand = new Command("doctor")
|
|
77
|
-
.description("Check system health and available commands")
|
|
78
|
-
.option("--json", "Output in JSON format")
|
|
79
|
-
.addHelpText(
|
|
80
|
-
"after",
|
|
81
|
-
`
|
|
82
|
-
Examples:
|
|
83
|
-
$ vibe doctor Show system health and capabilities
|
|
84
|
-
$ vibe doctor --json Machine-readable output
|
|
85
|
-
`
|
|
86
|
-
)
|
|
87
|
-
.action(async (options) => {
|
|
88
|
-
const isJson = options.json || process.env.VIBE_JSON_OUTPUT === "1";
|
|
89
|
-
const results = await runDiagnostics();
|
|
90
|
-
|
|
91
|
-
if (isJson) {
|
|
92
|
-
console.log(JSON.stringify(results, null, 2));
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
printReport(results);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
interface DiagnosticResults {
|
|
100
|
-
system: {
|
|
101
|
-
node: { version: string; ok: boolean };
|
|
102
|
-
ffmpeg: { version: string | null; ok: boolean };
|
|
103
|
-
config: { path: string; ok: boolean };
|
|
104
|
-
};
|
|
105
|
-
providers: Record<
|
|
106
|
-
string,
|
|
107
|
-
{ envVar: string; configured: boolean; commands: string[] }
|
|
108
|
-
>;
|
|
109
|
-
readyCount: number;
|
|
110
|
-
totalCount: number;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function runDiagnostics(): Promise<DiagnosticResults> {
|
|
114
|
-
loadEnv();
|
|
115
|
-
|
|
116
|
-
// System checks
|
|
117
|
-
const nodeVersion = process.version;
|
|
118
|
-
|
|
119
|
-
let ffmpegVersion: string | null = null;
|
|
120
|
-
const ffmpegExists = commandExists("ffmpeg");
|
|
121
|
-
if (ffmpegExists) {
|
|
122
|
-
try {
|
|
123
|
-
const result = await execSafe("ffmpeg", ["-version"]);
|
|
124
|
-
const match = result.stdout.match(/ffmpeg version (\S+)/);
|
|
125
|
-
ffmpegVersion = match ? match[1] : "unknown";
|
|
126
|
-
} catch {
|
|
127
|
-
ffmpegVersion = "unknown";
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
let configExists = false;
|
|
132
|
-
try {
|
|
133
|
-
await access(CONFIG_PATH);
|
|
134
|
-
configExists = true;
|
|
135
|
-
} catch {
|
|
136
|
-
// not configured
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Provider checks
|
|
140
|
-
const providers: DiagnosticResults["providers"] = {};
|
|
141
|
-
let readyCount = FREE_COMMANDS.length;
|
|
142
|
-
let totalCount = FREE_COMMANDS.length;
|
|
143
|
-
|
|
144
|
-
for (const [envVar, commands] of Object.entries(COMMAND_KEY_MAP)) {
|
|
145
|
-
const configured = !!process.env[envVar];
|
|
146
|
-
const providerName =
|
|
147
|
-
Object.entries(PROVIDER_ENV_VARS).find(([, v]) => v === envVar)?.[0] ??
|
|
148
|
-
envVar;
|
|
149
|
-
providers[providerName] = { envVar, configured, commands };
|
|
150
|
-
totalCount += commands.length;
|
|
151
|
-
if (configured) {
|
|
152
|
-
readyCount += commands.length;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
system: {
|
|
158
|
-
node: { version: nodeVersion, ok: true },
|
|
159
|
-
ffmpeg: { version: ffmpegVersion, ok: ffmpegExists },
|
|
160
|
-
config: { path: CONFIG_PATH, ok: configExists },
|
|
161
|
-
},
|
|
162
|
-
providers,
|
|
163
|
-
readyCount,
|
|
164
|
-
totalCount,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function printReport(results: DiagnosticResults): void {
|
|
169
|
-
console.log();
|
|
170
|
-
console.log(chalk.bold(" System"));
|
|
171
|
-
|
|
172
|
-
// Node
|
|
173
|
-
const nodeIcon = results.system.node.ok ? chalk.green("OK") : chalk.red("MISSING");
|
|
174
|
-
console.log(` Node.js ${results.system.node.version} ${nodeIcon}`);
|
|
175
|
-
|
|
176
|
-
// FFmpeg
|
|
177
|
-
if (results.system.ffmpeg.ok) {
|
|
178
|
-
console.log(
|
|
179
|
-
` FFmpeg ${results.system.ffmpeg.version} ${chalk.green("OK")}`
|
|
180
|
-
);
|
|
181
|
-
} else {
|
|
182
|
-
console.log(` FFmpeg ${chalk.red("NOT FOUND")} ${chalk.dim("Install: brew install ffmpeg")}`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Config
|
|
186
|
-
if (results.system.config.ok) {
|
|
187
|
-
console.log(` Config ${chalk.green("OK")} ${chalk.dim(results.system.config.path)}`);
|
|
188
|
-
} else {
|
|
189
|
-
console.log(
|
|
190
|
-
` Config ${chalk.yellow("NOT SET")} ${chalk.dim("Run: vibe setup")}`
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
console.log();
|
|
195
|
-
console.log(chalk.bold(" API Keys"));
|
|
196
|
-
|
|
197
|
-
const configured: string[] = [];
|
|
198
|
-
const missing: string[] = [];
|
|
199
|
-
|
|
200
|
-
for (const [name, info] of Object.entries(results.providers)) {
|
|
201
|
-
if (info.configured) {
|
|
202
|
-
configured.push(name);
|
|
203
|
-
console.log(
|
|
204
|
-
` ${chalk.green("OK")} ${info.envVar.padEnd(24)} ${chalk.dim(info.commands.join(", "))}`
|
|
205
|
-
);
|
|
206
|
-
} else {
|
|
207
|
-
missing.push(name);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (missing.length > 0) {
|
|
212
|
-
for (const name of missing) {
|
|
213
|
-
const info = results.providers[name];
|
|
214
|
-
console.log(
|
|
215
|
-
` ${chalk.red("--")} ${info.envVar.padEnd(24)} ${chalk.dim(info.commands.join(", "))}`
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Free commands
|
|
221
|
-
console.log();
|
|
222
|
-
console.log(chalk.bold(" No API key needed"));
|
|
223
|
-
console.log(` ${chalk.dim(FREE_COMMANDS.join(", "))}`);
|
|
224
|
-
|
|
225
|
-
// Summary
|
|
226
|
-
console.log();
|
|
227
|
-
const pct = Math.round((results.readyCount / results.totalCount) * 100);
|
|
228
|
-
const readyColor = pct >= 80 ? chalk.green : pct >= 40 ? chalk.yellow : chalk.red;
|
|
229
|
-
console.log(
|
|
230
|
-
` Ready: ${readyColor(`${results.readyCount}/${results.totalCount}`)} commands (${pct}%)`
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
if (missing.length > 0) {
|
|
234
|
-
console.log(chalk.dim(` Run 'vibe setup' to configure more providers.`));
|
|
235
|
-
}
|
|
236
|
-
console.log();
|
|
237
|
-
}
|