@vibeframe/cli 0.27.0 → 0.30.0

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