@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/engine/project.ts
DELETED
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Headless Project Engine for CLI operations
|
|
3
|
-
* No React/Zustand dependency - pure TypeScript
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
TimelineState,
|
|
8
|
-
ProjectMeta,
|
|
9
|
-
Track,
|
|
10
|
-
Clip,
|
|
11
|
-
MediaSource,
|
|
12
|
-
Effect,
|
|
13
|
-
Transition,
|
|
14
|
-
Id,
|
|
15
|
-
TimeSeconds,
|
|
16
|
-
AspectRatio,
|
|
17
|
-
MediaType,
|
|
18
|
-
} from "@vibeframe/core/timeline";
|
|
19
|
-
|
|
20
|
-
/** Generate unique ID */
|
|
21
|
-
export const generateId = (): Id => {
|
|
22
|
-
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** Project file format */
|
|
26
|
-
export interface ProjectFile {
|
|
27
|
-
version: string;
|
|
28
|
-
state: TimelineState;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Create default project state */
|
|
32
|
-
function createDefaultState(name: string = "Untitled Project"): TimelineState {
|
|
33
|
-
return {
|
|
34
|
-
project: {
|
|
35
|
-
id: generateId(),
|
|
36
|
-
name,
|
|
37
|
-
createdAt: new Date(),
|
|
38
|
-
updatedAt: new Date(),
|
|
39
|
-
aspectRatio: "16:9",
|
|
40
|
-
frameRate: 30,
|
|
41
|
-
duration: 0,
|
|
42
|
-
},
|
|
43
|
-
tracks: [
|
|
44
|
-
{
|
|
45
|
-
id: "video-track-1",
|
|
46
|
-
name: "Video 1",
|
|
47
|
-
type: "video",
|
|
48
|
-
order: 1,
|
|
49
|
-
isMuted: false,
|
|
50
|
-
isLocked: false,
|
|
51
|
-
isVisible: true,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: "audio-track-1",
|
|
55
|
-
name: "Audio 1",
|
|
56
|
-
type: "audio",
|
|
57
|
-
order: 0,
|
|
58
|
-
isMuted: false,
|
|
59
|
-
isLocked: false,
|
|
60
|
-
isVisible: true,
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
clips: [],
|
|
64
|
-
sources: [],
|
|
65
|
-
transitions: [],
|
|
66
|
-
currentTime: 0,
|
|
67
|
-
isPlaying: false,
|
|
68
|
-
zoom: 50,
|
|
69
|
-
scrollX: 0,
|
|
70
|
-
selectedClipIds: [],
|
|
71
|
-
selectedTrackId: null,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Headless Project Engine
|
|
77
|
-
* Manages timeline state without React/Zustand
|
|
78
|
-
*/
|
|
79
|
-
export class Project {
|
|
80
|
-
private state: TimelineState;
|
|
81
|
-
private filePath: string | null = null;
|
|
82
|
-
|
|
83
|
-
constructor(name?: string) {
|
|
84
|
-
this.state = createDefaultState(name);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Get current state (immutable copy) */
|
|
88
|
-
getState(): TimelineState {
|
|
89
|
-
return structuredClone(this.state);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Get project metadata */
|
|
93
|
-
getMeta(): ProjectMeta {
|
|
94
|
-
return { ...this.state.project };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Get file path */
|
|
98
|
-
getFilePath(): string | null {
|
|
99
|
-
return this.filePath;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ============ Project Operations ============
|
|
103
|
-
|
|
104
|
-
setName(name: string): void {
|
|
105
|
-
this.state.project.name = name;
|
|
106
|
-
this.state.project.updatedAt = new Date();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
setAspectRatio(ratio: AspectRatio): void {
|
|
110
|
-
this.state.project.aspectRatio = ratio;
|
|
111
|
-
this.state.project.updatedAt = new Date();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
setFrameRate(fps: number): void {
|
|
115
|
-
this.state.project.frameRate = fps;
|
|
116
|
-
this.state.project.updatedAt = new Date();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ============ Media Source Operations ============
|
|
120
|
-
|
|
121
|
-
addSource(source: Omit<MediaSource, "id">): MediaSource {
|
|
122
|
-
const newSource: MediaSource = { ...source, id: generateId() };
|
|
123
|
-
this.state.sources.push(newSource);
|
|
124
|
-
return newSource;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
removeSource(id: Id): boolean {
|
|
128
|
-
const index = this.state.sources.findIndex((s) => s.id === id);
|
|
129
|
-
if (index === -1) return false;
|
|
130
|
-
|
|
131
|
-
this.state.sources.splice(index, 1);
|
|
132
|
-
// Also remove clips using this source
|
|
133
|
-
this.state.clips = this.state.clips.filter((c) => c.sourceId !== id);
|
|
134
|
-
this.calculateDuration();
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
getSource(id: Id): MediaSource | undefined {
|
|
139
|
-
return this.state.sources.find((s) => s.id === id);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
getSources(): MediaSource[] {
|
|
143
|
-
return [...this.state.sources];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ============ Track Operations ============
|
|
147
|
-
|
|
148
|
-
addTrack(track: Omit<Track, "id">): Track {
|
|
149
|
-
const newTrack: Track = { ...track, id: generateId() };
|
|
150
|
-
this.state.tracks.push(newTrack);
|
|
151
|
-
return newTrack;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
removeTrack(id: Id): boolean {
|
|
155
|
-
const index = this.state.tracks.findIndex((t) => t.id === id);
|
|
156
|
-
if (index === -1) return false;
|
|
157
|
-
|
|
158
|
-
this.state.tracks.splice(index, 1);
|
|
159
|
-
// Also remove clips on this track
|
|
160
|
-
this.state.clips = this.state.clips.filter((c) => c.trackId !== id);
|
|
161
|
-
this.calculateDuration();
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
updateTrack(id: Id, updates: Partial<Omit<Track, "id">>): boolean {
|
|
166
|
-
const track = this.state.tracks.find((t) => t.id === id);
|
|
167
|
-
if (!track) return false;
|
|
168
|
-
Object.assign(track, updates);
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
getTrack(id: Id): Track | undefined {
|
|
173
|
-
return this.state.tracks.find((t) => t.id === id);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getTracks(): Track[] {
|
|
177
|
-
return [...this.state.tracks];
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
getTracksByType(type: MediaType): Track[] {
|
|
181
|
-
return this.state.tracks.filter((t) => t.type === type);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ============ Clip Operations ============
|
|
185
|
-
|
|
186
|
-
addClip(clip: Omit<Clip, "id" | "effects">): Clip {
|
|
187
|
-
const newClip: Clip = {
|
|
188
|
-
...clip,
|
|
189
|
-
id: generateId(),
|
|
190
|
-
effects: [],
|
|
191
|
-
};
|
|
192
|
-
this.state.clips.push(newClip);
|
|
193
|
-
this.calculateDuration();
|
|
194
|
-
return newClip;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
removeClip(id: Id): boolean {
|
|
198
|
-
const index = this.state.clips.findIndex((c) => c.id === id);
|
|
199
|
-
if (index === -1) return false;
|
|
200
|
-
|
|
201
|
-
this.state.clips.splice(index, 1);
|
|
202
|
-
this.state.selectedClipIds = this.state.selectedClipIds.filter((cid) => cid !== id);
|
|
203
|
-
this.calculateDuration();
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
updateClip(id: Id, updates: Partial<Omit<Clip, "id">>): boolean {
|
|
208
|
-
const clip = this.state.clips.find((c) => c.id === id);
|
|
209
|
-
if (!clip) return false;
|
|
210
|
-
Object.assign(clip, updates);
|
|
211
|
-
this.calculateDuration();
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
moveClip(id: Id, trackId: Id, startTime: TimeSeconds): boolean {
|
|
216
|
-
const clip = this.state.clips.find((c) => c.id === id);
|
|
217
|
-
if (!clip) return false;
|
|
218
|
-
|
|
219
|
-
clip.trackId = trackId;
|
|
220
|
-
clip.startTime = Math.max(0, startTime);
|
|
221
|
-
this.calculateDuration();
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
trimClipStart(id: Id, newStartTime: TimeSeconds): boolean {
|
|
226
|
-
const clip = this.state.clips.find((c) => c.id === id);
|
|
227
|
-
if (!clip) return false;
|
|
228
|
-
|
|
229
|
-
const delta = newStartTime - clip.startTime;
|
|
230
|
-
clip.startTime = newStartTime;
|
|
231
|
-
clip.sourceStartOffset += delta;
|
|
232
|
-
clip.duration -= delta;
|
|
233
|
-
this.calculateDuration();
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
trimClipEnd(id: Id, newDuration: TimeSeconds): boolean {
|
|
238
|
-
const clip = this.state.clips.find((c) => c.id === id);
|
|
239
|
-
if (!clip) return false;
|
|
240
|
-
|
|
241
|
-
clip.duration = Math.max(0.1, newDuration);
|
|
242
|
-
clip.sourceEndOffset = clip.sourceStartOffset + clip.duration;
|
|
243
|
-
this.calculateDuration();
|
|
244
|
-
return true;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
getClip(id: Id): Clip | undefined {
|
|
248
|
-
return this.state.clips.find((c) => c.id === id);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
getClips(): Clip[] {
|
|
252
|
-
return [...this.state.clips];
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
getClipsByTrack(trackId: Id): Clip[] {
|
|
256
|
-
return this.state.clips.filter((c) => c.trackId === trackId);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Split a clip at a specific time, creating two clips
|
|
261
|
-
* @param id Clip ID to split
|
|
262
|
-
* @param splitTime Time relative to clip start (not timeline time)
|
|
263
|
-
* @returns [firstClip, secondClip] or null if failed
|
|
264
|
-
*/
|
|
265
|
-
splitClip(id: Id, splitTime: TimeSeconds): [Clip, Clip] | null {
|
|
266
|
-
const clip = this.state.clips.find((c) => c.id === id);
|
|
267
|
-
if (!clip) return null;
|
|
268
|
-
|
|
269
|
-
// Validate split time
|
|
270
|
-
if (splitTime <= 0 || splitTime >= clip.duration) {
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Create the second clip (after split point)
|
|
275
|
-
const secondClip: Clip = {
|
|
276
|
-
id: generateId(),
|
|
277
|
-
sourceId: clip.sourceId,
|
|
278
|
-
trackId: clip.trackId,
|
|
279
|
-
startTime: clip.startTime + splitTime,
|
|
280
|
-
duration: clip.duration - splitTime,
|
|
281
|
-
sourceStartOffset: clip.sourceStartOffset + splitTime,
|
|
282
|
-
sourceEndOffset: clip.sourceEndOffset,
|
|
283
|
-
effects: [], // Effects don't transfer to split clips
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
// Modify the first clip (before split point)
|
|
287
|
-
clip.duration = splitTime;
|
|
288
|
-
clip.sourceEndOffset = clip.sourceStartOffset + splitTime;
|
|
289
|
-
|
|
290
|
-
// Add the second clip
|
|
291
|
-
this.state.clips.push(secondClip);
|
|
292
|
-
this.calculateDuration();
|
|
293
|
-
|
|
294
|
-
return [clip, secondClip];
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Duplicate a clip
|
|
299
|
-
* @param id Clip ID to duplicate
|
|
300
|
-
* @param offsetTime Optional time offset for the duplicate (default: place after original)
|
|
301
|
-
* @returns The duplicated clip or null if failed
|
|
302
|
-
*/
|
|
303
|
-
duplicateClip(id: Id, offsetTime?: TimeSeconds): Clip | null {
|
|
304
|
-
const clip = this.state.clips.find((c) => c.id === id);
|
|
305
|
-
if (!clip) return null;
|
|
306
|
-
|
|
307
|
-
const newStartTime = offsetTime ?? clip.startTime + clip.duration;
|
|
308
|
-
|
|
309
|
-
const duplicatedClip: Clip = {
|
|
310
|
-
id: generateId(),
|
|
311
|
-
sourceId: clip.sourceId,
|
|
312
|
-
trackId: clip.trackId,
|
|
313
|
-
startTime: newStartTime,
|
|
314
|
-
duration: clip.duration,
|
|
315
|
-
sourceStartOffset: clip.sourceStartOffset,
|
|
316
|
-
sourceEndOffset: clip.sourceEndOffset,
|
|
317
|
-
effects: clip.effects.map((e) => ({
|
|
318
|
-
...e,
|
|
319
|
-
id: generateId(),
|
|
320
|
-
})),
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
this.state.clips.push(duplicatedClip);
|
|
324
|
-
this.calculateDuration();
|
|
325
|
-
|
|
326
|
-
return duplicatedClip;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ============ Effect Operations ============
|
|
330
|
-
|
|
331
|
-
addEffect(clipId: Id, effect: Omit<Effect, "id">): Effect | null {
|
|
332
|
-
const clip = this.state.clips.find((c) => c.id === clipId);
|
|
333
|
-
if (!clip) return null;
|
|
334
|
-
|
|
335
|
-
const newEffect: Effect = { ...effect, id: generateId() };
|
|
336
|
-
clip.effects.push(newEffect);
|
|
337
|
-
return newEffect;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
removeEffect(clipId: Id, effectId: Id): boolean {
|
|
341
|
-
const clip = this.state.clips.find((c) => c.id === clipId);
|
|
342
|
-
if (!clip) return false;
|
|
343
|
-
|
|
344
|
-
const index = clip.effects.findIndex((e) => e.id === effectId);
|
|
345
|
-
if (index === -1) return false;
|
|
346
|
-
|
|
347
|
-
clip.effects.splice(index, 1);
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
updateEffect(clipId: Id, effectId: Id, updates: Partial<Omit<Effect, "id">>): boolean {
|
|
352
|
-
const clip = this.state.clips.find((c) => c.id === clipId);
|
|
353
|
-
if (!clip) return false;
|
|
354
|
-
|
|
355
|
-
const effect = clip.effects.find((e) => e.id === effectId);
|
|
356
|
-
if (!effect) return false;
|
|
357
|
-
|
|
358
|
-
Object.assign(effect, updates);
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ============ Transition Operations ============
|
|
363
|
-
|
|
364
|
-
addTransition(transition: Omit<Transition, "id">): Transition {
|
|
365
|
-
const newTransition: Transition = { ...transition, id: generateId() };
|
|
366
|
-
this.state.transitions.push(newTransition);
|
|
367
|
-
return newTransition;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
removeTransition(id: Id): boolean {
|
|
371
|
-
const index = this.state.transitions.findIndex((t) => t.id === id);
|
|
372
|
-
if (index === -1) return false;
|
|
373
|
-
|
|
374
|
-
this.state.transitions.splice(index, 1);
|
|
375
|
-
return true;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
getTransitions(): Transition[] {
|
|
379
|
-
return [...this.state.transitions];
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ============ Duration Calculation ============
|
|
383
|
-
|
|
384
|
-
private calculateDuration(): void {
|
|
385
|
-
const maxEndTime = this.state.clips.reduce((max, clip) => {
|
|
386
|
-
const endTime = clip.startTime + clip.duration;
|
|
387
|
-
return Math.max(max, endTime);
|
|
388
|
-
}, 0);
|
|
389
|
-
this.state.project.duration = maxEndTime;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
getDuration(): TimeSeconds {
|
|
393
|
-
return this.state.project.duration;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ============ Serialization ============
|
|
397
|
-
|
|
398
|
-
toJSON(): ProjectFile {
|
|
399
|
-
return {
|
|
400
|
-
version: "1.0.0",
|
|
401
|
-
state: this.getState(),
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
static fromJSON(data: ProjectFile): Project {
|
|
406
|
-
const project = new Project();
|
|
407
|
-
// Convert date strings back to Date objects
|
|
408
|
-
data.state.project.createdAt = new Date(data.state.project.createdAt);
|
|
409
|
-
data.state.project.updatedAt = new Date(data.state.project.updatedAt);
|
|
410
|
-
project.state = data.state;
|
|
411
|
-
return project;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
setFilePath(path: string): void {
|
|
415
|
-
this.filePath = path;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// ============ Summary ============
|
|
419
|
-
|
|
420
|
-
getSummary(): {
|
|
421
|
-
name: string;
|
|
422
|
-
duration: TimeSeconds;
|
|
423
|
-
aspectRatio: AspectRatio;
|
|
424
|
-
frameRate: number;
|
|
425
|
-
trackCount: number;
|
|
426
|
-
clipCount: number;
|
|
427
|
-
sourceCount: number;
|
|
428
|
-
} {
|
|
429
|
-
return {
|
|
430
|
-
name: this.state.project.name,
|
|
431
|
-
duration: this.state.project.duration,
|
|
432
|
-
aspectRatio: this.state.project.aspectRatio,
|
|
433
|
-
frameRate: this.state.project.frameRate,
|
|
434
|
-
trackCount: this.state.tracks.length,
|
|
435
|
-
clipCount: this.state.clips.length,
|
|
436
|
-
sourceCount: this.state.sources.length,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Debug: Check if script starts at all
|
|
4
|
-
if (process.env.VIBE_DEBUG === "1") {
|
|
5
|
-
console.log("[CLI] Script started, loading modules...");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
import { Command } from "commander";
|
|
9
|
-
import { createRequire } from "module";
|
|
10
|
-
import chalk from "chalk";
|
|
11
|
-
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
|
-
const pkg = require("../package.json");
|
|
14
|
-
|
|
15
|
-
// Re-export engine for library usage
|
|
16
|
-
export { Project, generateId, type ProjectFile } from "./engine/index.js";
|
|
17
|
-
import { projectCommand } from "./commands/project.js";
|
|
18
|
-
import { timelineCommand } from "./commands/timeline.js";
|
|
19
|
-
import { generateCommand } from "./commands/generate.js";
|
|
20
|
-
import { editCommand } from "./commands/edit-cmd.js";
|
|
21
|
-
import { analyzeCommand } from "./commands/analyze.js";
|
|
22
|
-
import { audioCommand } from "./commands/audio.js";
|
|
23
|
-
import { pipelineCommand } from "./commands/pipeline.js";
|
|
24
|
-
import { schemaCommand } from "./commands/schema.js";
|
|
25
|
-
import { mediaCommand } from "./commands/media.js";
|
|
26
|
-
import { exportCommand } from "./commands/export.js";
|
|
27
|
-
import { batchCommand } from "./commands/batch.js";
|
|
28
|
-
import { detectCommand } from "./commands/detect.js";
|
|
29
|
-
import { setupCommand } from "./commands/setup.js";
|
|
30
|
-
import { doctorCommand } from "./commands/doctor.js";
|
|
31
|
-
import { agentCommand, startAgent } from "./commands/agent.js";
|
|
32
|
-
import { ApiKeyError } from "./utils/api-key.js";
|
|
33
|
-
import { isFirstRun, showFirstRunBanner } from "./utils/first-run.js";
|
|
34
|
-
import { exitWithError } from "./commands/output.js";
|
|
35
|
-
|
|
36
|
-
export { startAgent } from "./commands/agent.js";
|
|
37
|
-
export { loadConfig, saveConfig, isConfigured, type VibeConfig } from "./config/index.js";
|
|
38
|
-
export { AgentExecutor, ToolRegistry, ConversationMemory } from "./agent/index.js";
|
|
39
|
-
export type { AgentConfig, AgentContext, AgentMessage, ToolCall, ToolResult, LLMAdapter } from "./agent/index.js";
|
|
40
|
-
|
|
41
|
-
const program = new Command();
|
|
42
|
-
|
|
43
|
-
program
|
|
44
|
-
.name("vibe")
|
|
45
|
-
.showSuggestionAfterError(true)
|
|
46
|
-
.description("VibeFrame CLI - AI-First Video Editor")
|
|
47
|
-
.version(pkg.version)
|
|
48
|
-
.option("--json", "Output in JSON format")
|
|
49
|
-
.option("-q, --quiet", "Output only the primary result value (path, URL, or ID)")
|
|
50
|
-
.option("--fields <fields>", "Limit JSON output to specific fields (comma-separated)")
|
|
51
|
-
.configureOutput({
|
|
52
|
-
outputError: (str, write) => {
|
|
53
|
-
write(chalk.red(str.trim()) + "\n");
|
|
54
|
-
write(chalk.dim("Run with --help for full options.\n"));
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
.addHelpText(
|
|
58
|
-
"after",
|
|
59
|
-
`
|
|
60
|
-
Tips:
|
|
61
|
-
vibe setup Configure API keys and preferences
|
|
62
|
-
vibe doctor Check system health and available commands
|
|
63
|
-
vibe schema <command> Show JSON schema for any command (e.g., vibe schema generate.image)
|
|
64
|
-
vibe Start interactive Agent mode (no args)
|
|
65
|
-
|
|
66
|
-
More commands: vibe project|timeline|export|batch|detect|schema --help
|
|
67
|
-
`
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
// Set JSON mode env var before subcommand parsing
|
|
71
|
-
// Also check for first-run and show banner
|
|
72
|
-
program.hook("preAction", async (thisCommand) => {
|
|
73
|
-
const opts = program.opts();
|
|
74
|
-
|
|
75
|
-
// --json flag or auto-detect non-TTY stdout
|
|
76
|
-
if (opts.json || (!process.stdout.isTTY && !process.env.VIBE_HUMAN_OUTPUT)) {
|
|
77
|
-
process.env.VIBE_JSON_OUTPUT = "1";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// --quiet flag
|
|
81
|
-
if (opts.quiet) {
|
|
82
|
-
process.env.VIBE_QUIET_OUTPUT = "1";
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// --fields flag
|
|
86
|
-
if (opts.fields) {
|
|
87
|
-
process.env.VIBE_OUTPUT_FIELDS = opts.fields;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Show first-run banner for non-setup/doctor commands
|
|
91
|
-
const cmdName = thisCommand.name();
|
|
92
|
-
const skipBannerCommands = ["setup", "doctor", "help"];
|
|
93
|
-
if (!skipBannerCommands.includes(cmdName) && process.stdin.isTTY) {
|
|
94
|
-
try {
|
|
95
|
-
if (await isFirstRun()) {
|
|
96
|
-
showFirstRunBanner();
|
|
97
|
-
}
|
|
98
|
-
} catch {
|
|
99
|
-
// Don't block on first-run check failure
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Main commands (visible in --help)
|
|
105
|
-
program.addCommand(generateCommand);
|
|
106
|
-
program.addCommand(editCommand);
|
|
107
|
-
program.addCommand(analyzeCommand);
|
|
108
|
-
program.addCommand(audioCommand);
|
|
109
|
-
program.addCommand(pipelineCommand);
|
|
110
|
-
program.addCommand(setupCommand);
|
|
111
|
-
program.addCommand(doctorCommand);
|
|
112
|
-
program.addCommand(agentCommand);
|
|
113
|
-
|
|
114
|
-
// Infrastructure commands (hidden from --help, still fully functional)
|
|
115
|
-
program.addCommand(projectCommand, { hidden: true });
|
|
116
|
-
program.addCommand(timelineCommand, { hidden: true });
|
|
117
|
-
program.addCommand(schemaCommand, { hidden: true });
|
|
118
|
-
program.addCommand(mediaCommand, { hidden: true });
|
|
119
|
-
program.addCommand(exportCommand, { hidden: true });
|
|
120
|
-
program.addCommand(batchCommand, { hidden: true });
|
|
121
|
-
program.addCommand(detectCommand, { hidden: true });
|
|
122
|
-
|
|
123
|
-
// Check if any arguments provided
|
|
124
|
-
if (process.argv.length <= 2) {
|
|
125
|
-
// No arguments - start Agent mode
|
|
126
|
-
if (process.env.VIBE_DEBUG === "1") {
|
|
127
|
-
console.log("[CLI] No args, starting Agent...");
|
|
128
|
-
}
|
|
129
|
-
startAgent().catch((err) => {
|
|
130
|
-
console.error("Failed to start Agent:", err);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
});
|
|
133
|
-
} else {
|
|
134
|
-
// Arguments provided - parse normally with global error handling
|
|
135
|
-
(async () => {
|
|
136
|
-
try {
|
|
137
|
-
await program.parseAsync();
|
|
138
|
-
} catch (err) {
|
|
139
|
-
if (err instanceof ApiKeyError) {
|
|
140
|
-
exitWithError(err.toStructured());
|
|
141
|
-
}
|
|
142
|
-
// Re-throw non-ApiKeyError errors
|
|
143
|
-
throw err;
|
|
144
|
-
}
|
|
145
|
-
})();
|
|
146
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import { loadEnv, getApiKey } from "./api-key.js";
|
|
3
|
-
|
|
4
|
-
describe("api-key utilities", () => {
|
|
5
|
-
const originalEnv = process.env;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
vi.resetModules();
|
|
9
|
-
process.env = { ...originalEnv };
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
process.env = originalEnv;
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe("loadEnv", () => {
|
|
17
|
-
it("does not throw when .env file is missing", () => {
|
|
18
|
-
expect(() => loadEnv()).not.toThrow();
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe("getApiKey", () => {
|
|
23
|
-
it("returns option value if provided", async () => {
|
|
24
|
-
const result = await getApiKey("TEST_KEY", "Test", "my-api-key");
|
|
25
|
-
expect(result).toBe("my-api-key");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("returns env value if option not provided", async () => {
|
|
29
|
-
process.env.TEST_KEY = "env-api-key";
|
|
30
|
-
const result = await getApiKey("TEST_KEY", "Test");
|
|
31
|
-
expect(result).toBe("env-api-key");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("returns null when no key available and not TTY", async () => {
|
|
35
|
-
// In test environment, stdin is not TTY, so it should return null
|
|
36
|
-
delete process.env.TEST_KEY;
|
|
37
|
-
const result = await getApiKey("TEST_KEY", "Test");
|
|
38
|
-
expect(result).toBeNull();
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
});
|