@vibeframe/cli 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/adapters/index.d.ts +1 -0
  3. package/dist/agent/adapters/index.d.ts.map +1 -1
  4. package/dist/agent/adapters/index.js +5 -0
  5. package/dist/agent/adapters/index.js.map +1 -1
  6. package/dist/agent/adapters/openrouter.d.ts +16 -0
  7. package/dist/agent/adapters/openrouter.d.ts.map +1 -0
  8. package/dist/agent/adapters/openrouter.js +100 -0
  9. package/dist/agent/adapters/openrouter.js.map +1 -0
  10. package/dist/agent/types.d.ts +1 -1
  11. package/dist/agent/types.d.ts.map +1 -1
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +3 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/setup.js +5 -2
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/config/schema.d.ts +2 -1
  18. package/dist/config/schema.d.ts.map +1 -1
  19. package/dist/config/schema.js +2 -0
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/index.js +0 -0
  22. package/package.json +16 -12
  23. package/.turbo/turbo-build.log +0 -4
  24. package/.turbo/turbo-lint.log +0 -21
  25. package/.turbo/turbo-test.log +0 -689
  26. package/src/agent/adapters/claude.ts +0 -143
  27. package/src/agent/adapters/gemini.ts +0 -159
  28. package/src/agent/adapters/index.ts +0 -61
  29. package/src/agent/adapters/ollama.ts +0 -231
  30. package/src/agent/adapters/openai.ts +0 -116
  31. package/src/agent/adapters/xai.ts +0 -119
  32. package/src/agent/index.ts +0 -251
  33. package/src/agent/memory/index.ts +0 -151
  34. package/src/agent/prompts/system.ts +0 -106
  35. package/src/agent/tools/ai-editing.ts +0 -845
  36. package/src/agent/tools/ai-generation.ts +0 -1073
  37. package/src/agent/tools/ai-pipeline.ts +0 -1055
  38. package/src/agent/tools/ai.ts +0 -21
  39. package/src/agent/tools/batch.ts +0 -429
  40. package/src/agent/tools/e2e.test.ts +0 -545
  41. package/src/agent/tools/export.ts +0 -184
  42. package/src/agent/tools/filesystem.ts +0 -237
  43. package/src/agent/tools/index.ts +0 -150
  44. package/src/agent/tools/integration.test.ts +0 -775
  45. package/src/agent/tools/media.ts +0 -697
  46. package/src/agent/tools/project.ts +0 -313
  47. package/src/agent/tools/timeline.ts +0 -951
  48. package/src/agent/types.ts +0 -68
  49. package/src/commands/agent.ts +0 -340
  50. package/src/commands/ai-analyze.ts +0 -429
  51. package/src/commands/ai-animated-caption.ts +0 -390
  52. package/src/commands/ai-audio.ts +0 -941
  53. package/src/commands/ai-broll.ts +0 -490
  54. package/src/commands/ai-edit-cli.ts +0 -658
  55. package/src/commands/ai-edit.ts +0 -1542
  56. package/src/commands/ai-fill-gaps.ts +0 -566
  57. package/src/commands/ai-helpers.ts +0 -65
  58. package/src/commands/ai-highlights.ts +0 -1303
  59. package/src/commands/ai-image.ts +0 -761
  60. package/src/commands/ai-motion.ts +0 -347
  61. package/src/commands/ai-narrate.ts +0 -451
  62. package/src/commands/ai-review.ts +0 -309
  63. package/src/commands/ai-script-pipeline-cli.ts +0 -1710
  64. package/src/commands/ai-script-pipeline.ts +0 -1365
  65. package/src/commands/ai-suggest-edit.ts +0 -264
  66. package/src/commands/ai-video-fx.ts +0 -445
  67. package/src/commands/ai-video.ts +0 -915
  68. package/src/commands/ai-viral.ts +0 -595
  69. package/src/commands/ai-visual-fx.ts +0 -601
  70. package/src/commands/ai.test.ts +0 -627
  71. package/src/commands/ai.ts +0 -307
  72. package/src/commands/analyze.ts +0 -282
  73. package/src/commands/audio.ts +0 -644
  74. package/src/commands/batch.test.ts +0 -279
  75. package/src/commands/batch.ts +0 -440
  76. package/src/commands/detect.ts +0 -329
  77. package/src/commands/doctor.ts +0 -237
  78. package/src/commands/edit-cmd.ts +0 -1014
  79. package/src/commands/export.ts +0 -918
  80. package/src/commands/generate.ts +0 -2146
  81. package/src/commands/media.ts +0 -177
  82. package/src/commands/output.ts +0 -142
  83. package/src/commands/pipeline.ts +0 -398
  84. package/src/commands/project.test.ts +0 -127
  85. package/src/commands/project.ts +0 -149
  86. package/src/commands/sanitize.ts +0 -60
  87. package/src/commands/schema.ts +0 -130
  88. package/src/commands/setup.ts +0 -509
  89. package/src/commands/timeline.test.ts +0 -499
  90. package/src/commands/timeline.ts +0 -529
  91. package/src/commands/validate.ts +0 -77
  92. package/src/config/config.test.ts +0 -197
  93. package/src/config/index.ts +0 -125
  94. package/src/config/schema.ts +0 -82
  95. package/src/engine/index.ts +0 -2
  96. package/src/engine/project.test.ts +0 -702
  97. package/src/engine/project.ts +0 -439
  98. package/src/index.ts +0 -146
  99. package/src/utils/api-key.test.ts +0 -41
  100. package/src/utils/api-key.ts +0 -247
  101. package/src/utils/audio.ts +0 -83
  102. package/src/utils/exec-safe.ts +0 -75
  103. package/src/utils/first-run.ts +0 -52
  104. package/src/utils/provider-resolver.ts +0 -56
  105. package/src/utils/remotion.ts +0 -951
  106. package/src/utils/subtitle.test.ts +0 -227
  107. package/src/utils/subtitle.ts +0 -169
  108. package/src/utils/tty.ts +0 -196
  109. package/tsconfig.json +0 -20
@@ -1,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
- });