@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.
- 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/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/ai.ts
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module ai
|
|
3
|
-
* @description AI command orchestrator - registers all AI subcommands.
|
|
4
|
-
*
|
|
5
|
-
* This file is a thin wiring layer. Each command group lives in its own module:
|
|
6
|
-
* - ai-audio.ts — TTS, SFX, music generation
|
|
7
|
-
* - ai-image.ts — Image generation (OpenAI, Gemini)
|
|
8
|
-
* - ai-edit.ts — Post-production editing (silence-cut, caption, etc.)
|
|
9
|
-
* - ai-video.ts — Video generation (Runway, Kling, Grok)
|
|
10
|
-
* - ai-analyze.ts — Unified media analysis
|
|
11
|
-
* - ai-review.ts — AI video review & auto-fix
|
|
12
|
-
* - ai-highlights.ts — Highlight extraction + auto-shorts
|
|
13
|
-
* - ai-script-pipeline.ts — Script-to-video pipeline
|
|
14
|
-
* - ai-motion.ts — Remotion motion graphics
|
|
15
|
-
* - ai-suggest-edit.ts — Suggest, edit, storyboard commands
|
|
16
|
-
* - ai-fill-gaps.ts — Fill timeline gaps with AI video
|
|
17
|
-
* - ai-video-fx.ts — Video upscale, interpolate, inpaint, track
|
|
18
|
-
* - ai-broll.ts — B-roll matching
|
|
19
|
-
* - ai-viral.ts — Viral optimizer
|
|
20
|
-
* - ai-visual-fx.ts — Grade, speed-ramp, reframe, style-transfer
|
|
21
|
-
* - ai-narrate.ts — Auto-narration + providers list
|
|
22
|
-
*
|
|
23
|
-
* @see MODELS.md for AI model configuration
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { Command } from "commander";
|
|
27
|
-
import { Project } from "../engine/index.js";
|
|
28
|
-
import type { EffectType } from "@vibeframe/core/timeline";
|
|
29
|
-
import type { TimelineCommand } from "@vibeframe/ai-providers";
|
|
30
|
-
|
|
31
|
-
// Module registrations
|
|
32
|
-
import { registerAudioCommands } from "./ai-audio.js";
|
|
33
|
-
import { registerImageCommands } from "./ai-image.js";
|
|
34
|
-
import { registerEditCommands } from "./ai-edit-cli.js";
|
|
35
|
-
import { registerVideoCommands } from "./ai-video.js";
|
|
36
|
-
import { registerAnalyzeCommands } from "./ai-analyze.js";
|
|
37
|
-
import { registerReviewCommand } from "./ai-review.js";
|
|
38
|
-
import { registerHighlightsCommands } from "./ai-highlights.js";
|
|
39
|
-
import { registerScriptPipelineCommands } from "./ai-script-pipeline-cli.js";
|
|
40
|
-
import { registerMotionCommand } from "./ai-motion.js";
|
|
41
|
-
import { registerSuggestEditCommands } from "./ai-suggest-edit.js";
|
|
42
|
-
import { registerFillGapsCommand } from "./ai-fill-gaps.js";
|
|
43
|
-
import { registerVideoFxCommands } from "./ai-video-fx.js";
|
|
44
|
-
import { registerBrollCommand } from "./ai-broll.js";
|
|
45
|
-
import { registerViralCommand } from "./ai-viral.js";
|
|
46
|
-
import { registerVisualFxCommands } from "./ai-visual-fx.js";
|
|
47
|
-
import { registerNarrateCommands } from "./ai-narrate.js";
|
|
48
|
-
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Re-exports for backward compatibility (agent tools import from this file)
|
|
51
|
-
// ============================================================================
|
|
52
|
-
|
|
53
|
-
export {
|
|
54
|
-
executeMotion,
|
|
55
|
-
type MotionCommandOptions,
|
|
56
|
-
type MotionCommandResult,
|
|
57
|
-
} from "./ai-motion.js";
|
|
58
|
-
|
|
59
|
-
export {
|
|
60
|
-
executeSilenceCut, executeJumpCut, executeCaption, executeNoiseReduce,
|
|
61
|
-
executeFade, executeTranslateSrt, applyTextOverlays, executeTextOverlay,
|
|
62
|
-
type TextOverlayStyle, type TextOverlayOptions, type TextOverlayResult,
|
|
63
|
-
type CaptionStyle, type CaptionOptions, type CaptionResult,
|
|
64
|
-
type SilencePeriod, type SilenceCutOptions, type SilenceCutResult,
|
|
65
|
-
type FillerWord, type JumpCutOptions, type JumpCutResult,
|
|
66
|
-
type NoiseReduceOptions, type NoiseReduceResult,
|
|
67
|
-
type FadeOptions, type FadeResult,
|
|
68
|
-
type TranslateSrtOptions, type TranslateSrtResult,
|
|
69
|
-
DEFAULT_FILLER_WORDS, detectFillerRanges,
|
|
70
|
-
} from "./ai-edit.js";
|
|
71
|
-
|
|
72
|
-
export {
|
|
73
|
-
executeThumbnailBestFrame,
|
|
74
|
-
type ThumbnailBestFrameOptions,
|
|
75
|
-
type ThumbnailBestFrameResult,
|
|
76
|
-
} from "./ai-image.js";
|
|
77
|
-
|
|
78
|
-
export {
|
|
79
|
-
executeReview,
|
|
80
|
-
type ReviewOptions,
|
|
81
|
-
type ReviewResult,
|
|
82
|
-
} from "./ai-review.js";
|
|
83
|
-
|
|
84
|
-
export {
|
|
85
|
-
executeHighlights,
|
|
86
|
-
executeAutoShorts,
|
|
87
|
-
type HighlightsOptions,
|
|
88
|
-
type HighlightsExtractResult,
|
|
89
|
-
type AutoShortsOptions,
|
|
90
|
-
type AutoShortsResult,
|
|
91
|
-
} from "./ai-highlights.js";
|
|
92
|
-
|
|
93
|
-
export {
|
|
94
|
-
executeGeminiVideo,
|
|
95
|
-
executeAnalyze,
|
|
96
|
-
type GeminiVideoOptions,
|
|
97
|
-
type GeminiVideoResult,
|
|
98
|
-
type AnalyzeOptions,
|
|
99
|
-
type AnalyzeResult,
|
|
100
|
-
} from "./ai-analyze.js";
|
|
101
|
-
|
|
102
|
-
export {
|
|
103
|
-
executeScriptToVideo,
|
|
104
|
-
executeRegenerateScene,
|
|
105
|
-
type ScriptToVideoOptions,
|
|
106
|
-
type ScriptToVideoResult,
|
|
107
|
-
type NarrationEntry,
|
|
108
|
-
type RegenerateSceneOptions,
|
|
109
|
-
type RegenerateSceneResult,
|
|
110
|
-
} from "./ai-script-pipeline.js";
|
|
111
|
-
|
|
112
|
-
export {
|
|
113
|
-
autoNarrate,
|
|
114
|
-
type AutoNarrateOptions,
|
|
115
|
-
type AutoNarrateResult,
|
|
116
|
-
} from "./ai-narrate.js";
|
|
117
|
-
|
|
118
|
-
// ============================================================================
|
|
119
|
-
// AI Command — register all subcommands
|
|
120
|
-
// ============================================================================
|
|
121
|
-
|
|
122
|
-
export const aiCommand = new Command("ai")
|
|
123
|
-
.description("AI provider commands");
|
|
124
|
-
|
|
125
|
-
// Previously extracted modules
|
|
126
|
-
registerAudioCommands(aiCommand);
|
|
127
|
-
registerImageCommands(aiCommand);
|
|
128
|
-
registerEditCommands(aiCommand);
|
|
129
|
-
registerVideoCommands(aiCommand);
|
|
130
|
-
registerAnalyzeCommands(aiCommand);
|
|
131
|
-
registerReviewCommand(aiCommand);
|
|
132
|
-
registerHighlightsCommands(aiCommand);
|
|
133
|
-
registerScriptPipelineCommands(aiCommand);
|
|
134
|
-
registerMotionCommand(aiCommand);
|
|
135
|
-
|
|
136
|
-
// Newly extracted modules
|
|
137
|
-
registerSuggestEditCommands(aiCommand);
|
|
138
|
-
registerFillGapsCommand(aiCommand);
|
|
139
|
-
registerVideoFxCommands(aiCommand);
|
|
140
|
-
registerBrollCommand(aiCommand);
|
|
141
|
-
registerViralCommand(aiCommand);
|
|
142
|
-
registerVisualFxCommands(aiCommand);
|
|
143
|
-
registerNarrateCommands(aiCommand);
|
|
144
|
-
|
|
145
|
-
// ============================================================================
|
|
146
|
-
// executeCommand — applies parsed timeline commands to a project
|
|
147
|
-
// ============================================================================
|
|
148
|
-
|
|
149
|
-
export function executeCommand(project: Project, cmd: TimelineCommand): boolean {
|
|
150
|
-
const { action, clipIds, params } = cmd;
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
switch (action) {
|
|
154
|
-
case "trim":
|
|
155
|
-
for (const clipId of clipIds) {
|
|
156
|
-
if (params.newDuration) {
|
|
157
|
-
project.trimClipEnd(clipId, params.newDuration as number);
|
|
158
|
-
}
|
|
159
|
-
if (params.startTrim) {
|
|
160
|
-
project.trimClipStart(clipId, params.startTrim as number);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return true;
|
|
164
|
-
|
|
165
|
-
case "remove-clip":
|
|
166
|
-
for (const clipId of clipIds) {
|
|
167
|
-
project.removeClip(clipId);
|
|
168
|
-
}
|
|
169
|
-
return true;
|
|
170
|
-
|
|
171
|
-
case "split":
|
|
172
|
-
if (clipIds.length > 0 && params.splitTime) {
|
|
173
|
-
project.splitClip(clipIds[0], params.splitTime as number);
|
|
174
|
-
}
|
|
175
|
-
return true;
|
|
176
|
-
|
|
177
|
-
case "duplicate":
|
|
178
|
-
for (const clipId of clipIds) {
|
|
179
|
-
project.duplicateClip(clipId, params.newStartTime as number | undefined);
|
|
180
|
-
}
|
|
181
|
-
return true;
|
|
182
|
-
|
|
183
|
-
case "move":
|
|
184
|
-
for (const clipId of clipIds) {
|
|
185
|
-
const clip = project.getClips().find((c) => c.id === clipId);
|
|
186
|
-
if (clip) {
|
|
187
|
-
const newTrackId = (params.newTrackId as string) || clip.trackId;
|
|
188
|
-
const newStartTime = (params.newStartTime as number) ?? clip.startTime;
|
|
189
|
-
project.moveClip(clipId, newTrackId, newStartTime);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return true;
|
|
193
|
-
|
|
194
|
-
case "add-effect":
|
|
195
|
-
for (const clipId of clipIds) {
|
|
196
|
-
const effectType = ((params.effectType as string) || "fadeIn") as EffectType;
|
|
197
|
-
project.addEffect(clipId, {
|
|
198
|
-
type: effectType,
|
|
199
|
-
startTime: (params.startTime as number) || 0,
|
|
200
|
-
duration: (params.duration as number) || 1,
|
|
201
|
-
params: {},
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
return true;
|
|
205
|
-
|
|
206
|
-
case "remove-effect":
|
|
207
|
-
console.warn("remove-effect is not yet supported. Use the timeline UI to remove effects.");
|
|
208
|
-
return false;
|
|
209
|
-
|
|
210
|
-
case "set-volume":
|
|
211
|
-
console.warn("set-volume is not yet supported. Audio ducking via 'vibe ai duck' can adjust levels.");
|
|
212
|
-
return false;
|
|
213
|
-
|
|
214
|
-
case "add-track": {
|
|
215
|
-
const trackType = (params.trackType as "video" | "audio") || "video";
|
|
216
|
-
const tracks = project.getTracks();
|
|
217
|
-
project.addTrack({
|
|
218
|
-
type: trackType,
|
|
219
|
-
name: `${trackType}-track-${tracks.length + 1}`,
|
|
220
|
-
order: tracks.length,
|
|
221
|
-
isMuted: false,
|
|
222
|
-
isLocked: false,
|
|
223
|
-
isVisible: true,
|
|
224
|
-
});
|
|
225
|
-
return true;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
case "speed-change":
|
|
229
|
-
for (const clipId of clipIds) {
|
|
230
|
-
const clip = project.getClips().find((c) => c.id === clipId);
|
|
231
|
-
if (clip) {
|
|
232
|
-
const speed = (params.speed as number) || 1.0;
|
|
233
|
-
project.addEffect(clipId, {
|
|
234
|
-
type: "speed" as EffectType,
|
|
235
|
-
startTime: 0,
|
|
236
|
-
duration: clip.duration,
|
|
237
|
-
params: { speed },
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return true;
|
|
242
|
-
|
|
243
|
-
case "reverse":
|
|
244
|
-
for (const clipId of clipIds) {
|
|
245
|
-
const clip = project.getClips().find((c) => c.id === clipId);
|
|
246
|
-
if (clip) {
|
|
247
|
-
project.addEffect(clipId, {
|
|
248
|
-
type: "reverse" as EffectType,
|
|
249
|
-
startTime: 0,
|
|
250
|
-
duration: clip.duration,
|
|
251
|
-
params: {},
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return true;
|
|
256
|
-
|
|
257
|
-
case "crop":
|
|
258
|
-
for (const clipId of clipIds) {
|
|
259
|
-
const clip = project.getClips().find((c) => c.id === clipId);
|
|
260
|
-
if (clip) {
|
|
261
|
-
project.addEffect(clipId, {
|
|
262
|
-
type: "crop" as EffectType,
|
|
263
|
-
startTime: 0,
|
|
264
|
-
duration: clip.duration,
|
|
265
|
-
params: {
|
|
266
|
-
aspectRatio: params.aspectRatio as string,
|
|
267
|
-
x: params.x as number,
|
|
268
|
-
y: params.y as number,
|
|
269
|
-
width: params.width as number,
|
|
270
|
-
height: params.height as number,
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return true;
|
|
276
|
-
|
|
277
|
-
case "position":
|
|
278
|
-
for (const clipId of clipIds) {
|
|
279
|
-
const clip = project.getClips().find((c) => c.id === clipId);
|
|
280
|
-
if (clip) {
|
|
281
|
-
const position = params.position as string;
|
|
282
|
-
const allClips = project.getClips().filter((c) => c.trackId === clip.trackId);
|
|
283
|
-
let newStartTime = 0;
|
|
284
|
-
|
|
285
|
-
if (position === "end") {
|
|
286
|
-
const maxEnd = Math.max(...allClips.filter((c) => c.id !== clipId).map((c) => c.startTime + c.duration));
|
|
287
|
-
newStartTime = maxEnd;
|
|
288
|
-
} else if (position === "middle") {
|
|
289
|
-
const totalDuration = allClips.reduce((sum, c) => sum + c.duration, 0);
|
|
290
|
-
newStartTime = (totalDuration - clip.duration) / 2;
|
|
291
|
-
}
|
|
292
|
-
// "beginning" stays at 0
|
|
293
|
-
|
|
294
|
-
project.moveClip(clipId, clip.trackId, newStartTime);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return true;
|
|
298
|
-
|
|
299
|
-
default:
|
|
300
|
-
console.warn(`Unknown action: ${action}`);
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error(`Error executing ${action}:`, error);
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
}
|
package/src/commands/analyze.ts
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module analyze
|
|
3
|
-
*
|
|
4
|
-
* Top-level `vibe analyze` command group for media analysis.
|
|
5
|
-
*
|
|
6
|
-
* Commands:
|
|
7
|
-
* analyze media - Unified analysis for images, videos, and YouTube URLs (Gemini)
|
|
8
|
-
* analyze video - Analyze video files or YouTube URLs with Gemini
|
|
9
|
-
* analyze review - AI video quality review and auto-fix (Gemini)
|
|
10
|
-
* analyze suggest - Get AI edit suggestions using Gemini
|
|
11
|
-
*
|
|
12
|
-
* @dependencies Gemini (Google), FFmpeg (auto-fix filters)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { Command } from "commander";
|
|
16
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
17
|
-
import { resolve } from "node:path";
|
|
18
|
-
import chalk from "chalk";
|
|
19
|
-
import ora from "ora";
|
|
20
|
-
import { GeminiProvider } from "@vibeframe/ai-providers";
|
|
21
|
-
import { Project, type ProjectFile } from "../engine/index.js";
|
|
22
|
-
import { requireApiKey } from "../utils/api-key.js";
|
|
23
|
-
import { applySuggestion } from "./ai-helpers.js";
|
|
24
|
-
import { executeAnalyze, executeGeminiVideo } from "./ai-analyze.js";
|
|
25
|
-
import { registerReviewCommand } from "./ai-review.js";
|
|
26
|
-
import { isJsonMode, outputResult, exitWithError, apiError } from "./output.js";
|
|
27
|
-
import { sanitizeLLMResponse } from "./sanitize.js";
|
|
28
|
-
import { rejectControlChars } from "./validate.js";
|
|
29
|
-
|
|
30
|
-
export const analyzeCommand = new Command("analyze")
|
|
31
|
-
.alias("az")
|
|
32
|
-
.description("Analyze media using AI (images, videos, YouTube URLs)")
|
|
33
|
-
.addHelpText(
|
|
34
|
-
"after",
|
|
35
|
-
`
|
|
36
|
-
Examples:
|
|
37
|
-
$ vibe analyze media image.png "Describe this image"
|
|
38
|
-
$ vibe analyze media video.mp4 "Summarize this video"
|
|
39
|
-
$ vibe analyze media "https://youtube.com/watch?v=..." "Key takeaways"
|
|
40
|
-
$ vibe analyze video video.mp4 "List all scene changes" --low-res
|
|
41
|
-
$ vibe analyze review video.mp4 --auto-apply -o fixed.mp4
|
|
42
|
-
$ vibe analyze suggest project.vibe.json "make it more dramatic"
|
|
43
|
-
|
|
44
|
-
API Keys:
|
|
45
|
-
GOOGLE_API_KEY Required for all analyze commands (Gemini)
|
|
46
|
-
|
|
47
|
-
Use '--fields response,model' to limit output size.
|
|
48
|
-
Run 'vibe schema analyze.<command>' for structured parameter info.
|
|
49
|
-
`
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// ── analyze media ──────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
analyzeCommand
|
|
55
|
-
.command("media")
|
|
56
|
-
.description("Analyze any media: images, videos, or YouTube URLs using Gemini")
|
|
57
|
-
.argument("<source>", "Image/video file path, image URL, or YouTube URL")
|
|
58
|
-
.argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')")
|
|
59
|
-
.option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)")
|
|
60
|
-
.option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash")
|
|
61
|
-
.option("--fps <number>", "Frames per second for video (default: 1)")
|
|
62
|
-
.option("--start <seconds>", "Start offset in seconds (video only)")
|
|
63
|
-
.option("--end <seconds>", "End offset in seconds (video only)")
|
|
64
|
-
.option("--low-res", "Use low resolution mode (fewer tokens)")
|
|
65
|
-
.option("-v, --verbose", "Show token usage")
|
|
66
|
-
.option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)")
|
|
67
|
-
.action(async (source: string, prompt: string, options) => {
|
|
68
|
-
try {
|
|
69
|
-
rejectControlChars(prompt);
|
|
70
|
-
|
|
71
|
-
if (options.apiKey) {
|
|
72
|
-
process.env.GOOGLE_API_KEY = options.apiKey;
|
|
73
|
-
} else {
|
|
74
|
-
await requireApiKey("GOOGLE_API_KEY", "Google");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const spinner = ora("Analyzing source...").start();
|
|
78
|
-
const result = await executeAnalyze({
|
|
79
|
-
source,
|
|
80
|
-
prompt,
|
|
81
|
-
model: options.model as "flash" | "flash-2.5" | "pro",
|
|
82
|
-
fps: options.fps ? parseFloat(options.fps) : undefined,
|
|
83
|
-
start: options.start ? parseInt(options.start, 10) : undefined,
|
|
84
|
-
end: options.end ? parseInt(options.end, 10) : undefined,
|
|
85
|
-
lowRes: options.lowRes,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (!result.success) {
|
|
89
|
-
spinner.fail(chalk.red(result.error || "Analysis failed"));
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
spinner.succeed(chalk.green("Analysis complete"));
|
|
94
|
-
|
|
95
|
-
const response = sanitizeLLMResponse(result.response || "");
|
|
96
|
-
|
|
97
|
-
if (isJsonMode()) {
|
|
98
|
-
let result_obj: Record<string, unknown> = { success: true, response, sourceType: result.sourceType, model: result.model };
|
|
99
|
-
if (result.totalTokens) {
|
|
100
|
-
result_obj = { ...result_obj, promptTokens: result.promptTokens, responseTokens: result.responseTokens, totalTokens: result.totalTokens };
|
|
101
|
-
}
|
|
102
|
-
if (options.fields) {
|
|
103
|
-
const fields = options.fields.split(",").map((f: string) => f.trim());
|
|
104
|
-
result_obj = Object.fromEntries(Object.entries(result_obj).filter(([k]) => fields.includes(k) || k === "success"));
|
|
105
|
-
}
|
|
106
|
-
outputResult(result_obj);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
console.log();
|
|
111
|
-
console.log(response);
|
|
112
|
-
console.log();
|
|
113
|
-
|
|
114
|
-
if (options.verbose && result.totalTokens) {
|
|
115
|
-
console.log(chalk.dim("-".repeat(40)));
|
|
116
|
-
console.log(chalk.dim(`Source type: ${result.sourceType}`));
|
|
117
|
-
console.log(chalk.dim(`Model: ${result.model}`));
|
|
118
|
-
if (result.promptTokens) {
|
|
119
|
-
console.log(chalk.dim(`Prompt tokens: ${result.promptTokens.toLocaleString()}`));
|
|
120
|
-
}
|
|
121
|
-
if (result.responseTokens) {
|
|
122
|
-
console.log(chalk.dim(`Response tokens: ${result.responseTokens.toLocaleString()}`));
|
|
123
|
-
}
|
|
124
|
-
console.log(chalk.dim(`Total tokens: ${result.totalTokens.toLocaleString()}`));
|
|
125
|
-
}
|
|
126
|
-
} catch (error) {
|
|
127
|
-
exitWithError(apiError(`Analysis failed: ${(error as Error).message}`));
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// ── analyze video ──────────────────────────────────────────────────────
|
|
132
|
-
|
|
133
|
-
analyzeCommand
|
|
134
|
-
.command("video")
|
|
135
|
-
.description("Analyze video using Gemini (summarize, Q&A, extract info)")
|
|
136
|
-
.argument("<source>", "Video file path or YouTube URL")
|
|
137
|
-
.argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')")
|
|
138
|
-
.option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)")
|
|
139
|
-
.option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash")
|
|
140
|
-
.option("--fps <number>", "Frames per second (default: 1, higher for action)")
|
|
141
|
-
.option("--start <seconds>", "Start offset in seconds (for clipping)")
|
|
142
|
-
.option("--end <seconds>", "End offset in seconds (for clipping)")
|
|
143
|
-
.option("--low-res", "Use low resolution mode (fewer tokens, longer videos)")
|
|
144
|
-
.option("-v, --verbose", "Show token usage")
|
|
145
|
-
.option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)")
|
|
146
|
-
.action(async (source: string, prompt: string, options) => {
|
|
147
|
-
try {
|
|
148
|
-
rejectControlChars(prompt);
|
|
149
|
-
|
|
150
|
-
if (options.apiKey) {
|
|
151
|
-
process.env.GOOGLE_API_KEY = options.apiKey;
|
|
152
|
-
} else {
|
|
153
|
-
await requireApiKey("GOOGLE_API_KEY", "Google");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const spinner = ora("Analyzing video...").start();
|
|
157
|
-
const result = await executeGeminiVideo({
|
|
158
|
-
source,
|
|
159
|
-
prompt,
|
|
160
|
-
model: options.model as "flash" | "flash-2.5" | "pro",
|
|
161
|
-
fps: options.fps ? parseFloat(options.fps) : undefined,
|
|
162
|
-
start: options.start ? parseInt(options.start, 10) : undefined,
|
|
163
|
-
end: options.end ? parseInt(options.end, 10) : undefined,
|
|
164
|
-
lowRes: options.lowRes,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
if (!result.success) {
|
|
168
|
-
spinner.fail(chalk.red(result.error || "Video analysis failed"));
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
spinner.succeed(chalk.green("Video analyzed"));
|
|
173
|
-
|
|
174
|
-
const response = sanitizeLLMResponse(result.response || "");
|
|
175
|
-
|
|
176
|
-
if (isJsonMode()) {
|
|
177
|
-
let result_obj: Record<string, unknown> = { success: true, response, model: result.model };
|
|
178
|
-
if (result.totalTokens) {
|
|
179
|
-
result_obj = { ...result_obj, promptTokens: result.promptTokens, responseTokens: result.responseTokens, totalTokens: result.totalTokens };
|
|
180
|
-
}
|
|
181
|
-
if (options.fields) {
|
|
182
|
-
const fields = options.fields.split(",").map((f: string) => f.trim());
|
|
183
|
-
result_obj = Object.fromEntries(Object.entries(result_obj).filter(([k]) => fields.includes(k) || k === "success"));
|
|
184
|
-
}
|
|
185
|
-
outputResult(result_obj);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
console.log();
|
|
190
|
-
console.log(response);
|
|
191
|
-
console.log();
|
|
192
|
-
|
|
193
|
-
if (options.verbose && result.totalTokens) {
|
|
194
|
-
console.log(chalk.dim("-".repeat(40)));
|
|
195
|
-
console.log(chalk.dim(`Model: ${result.model}`));
|
|
196
|
-
if (result.promptTokens) {
|
|
197
|
-
console.log(chalk.dim(`Prompt tokens: ${result.promptTokens.toLocaleString()}`));
|
|
198
|
-
}
|
|
199
|
-
if (result.responseTokens) {
|
|
200
|
-
console.log(chalk.dim(`Response tokens: ${result.responseTokens.toLocaleString()}`));
|
|
201
|
-
}
|
|
202
|
-
console.log(chalk.dim(`Total tokens: ${result.totalTokens.toLocaleString()}`));
|
|
203
|
-
}
|
|
204
|
-
} catch (error) {
|
|
205
|
-
exitWithError(apiError(`Video analysis failed: ${(error as Error).message}`));
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// ── analyze review ─────────────────────────────────────────────────────
|
|
210
|
-
|
|
211
|
-
registerReviewCommand(analyzeCommand);
|
|
212
|
-
|
|
213
|
-
// ── analyze suggest ────────────────────────────────────────────────────
|
|
214
|
-
|
|
215
|
-
analyzeCommand
|
|
216
|
-
.command("suggest")
|
|
217
|
-
.description("Get AI edit suggestions using Gemini")
|
|
218
|
-
.argument("<project>", "Project file path")
|
|
219
|
-
.argument("<instruction>", "Natural language instruction")
|
|
220
|
-
.option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)")
|
|
221
|
-
.option("--apply", "Apply the first suggestion automatically")
|
|
222
|
-
.action(async (projectPath: string, instruction: string, options) => {
|
|
223
|
-
try {
|
|
224
|
-
rejectControlChars(instruction);
|
|
225
|
-
|
|
226
|
-
const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
227
|
-
|
|
228
|
-
const spinner = ora("Initializing Gemini...").start();
|
|
229
|
-
|
|
230
|
-
const filePath = resolve(process.cwd(), projectPath);
|
|
231
|
-
const content = await readFile(filePath, "utf-8");
|
|
232
|
-
const data: ProjectFile = JSON.parse(content);
|
|
233
|
-
const project = Project.fromJSON(data);
|
|
234
|
-
|
|
235
|
-
const gemini = new GeminiProvider();
|
|
236
|
-
await gemini.initialize({ apiKey });
|
|
237
|
-
|
|
238
|
-
spinner.text = "Analyzing...";
|
|
239
|
-
const clips = project.getClips();
|
|
240
|
-
const suggestions = await gemini.autoEdit(clips, instruction);
|
|
241
|
-
|
|
242
|
-
spinner.succeed(chalk.green(`Found ${suggestions.length} suggestion(s)`));
|
|
243
|
-
|
|
244
|
-
if (isJsonMode()) {
|
|
245
|
-
outputResult({ success: true, suggestions: suggestions.map(s => ({ type: s.type, description: s.description, confidence: s.confidence, clipIds: s.clipIds, params: s.params })) });
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
console.log();
|
|
250
|
-
console.log(chalk.bold.cyan("Edit Suggestions"));
|
|
251
|
-
console.log(chalk.dim("─".repeat(60)));
|
|
252
|
-
|
|
253
|
-
for (let i = 0; i < suggestions.length; i++) {
|
|
254
|
-
const sug = suggestions[i];
|
|
255
|
-
console.log();
|
|
256
|
-
console.log(chalk.yellow(`[${i + 1}] ${sug.type.toUpperCase()}`));
|
|
257
|
-
console.log(` ${sug.description}`);
|
|
258
|
-
console.log(chalk.dim(` Confidence: ${(sug.confidence * 100).toFixed(0)}%`));
|
|
259
|
-
console.log(chalk.dim(` Clips: ${sug.clipIds.join(", ")}`));
|
|
260
|
-
console.log(chalk.dim(` Params: ${JSON.stringify(sug.params)}`));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (options.apply && suggestions.length > 0) {
|
|
264
|
-
console.log();
|
|
265
|
-
spinner.start("Applying first suggestion...");
|
|
266
|
-
|
|
267
|
-
const sug = suggestions[0];
|
|
268
|
-
const applied = applySuggestion(project, sug);
|
|
269
|
-
|
|
270
|
-
if (applied) {
|
|
271
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
272
|
-
spinner.succeed(chalk.green("Suggestion applied"));
|
|
273
|
-
} else {
|
|
274
|
-
spinner.warn(chalk.yellow("Could not apply suggestion automatically"));
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
console.log();
|
|
279
|
-
} catch (error) {
|
|
280
|
-
exitWithError(apiError(`AI suggestion failed: ${(error as Error).message}`));
|
|
281
|
-
}
|
|
282
|
-
});
|