@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,445 +0,0 @@
1
- /**
2
- * @module ai-video-fx
3
- * @description Video FX commands (upscale, interpolate, inpaint, track-object).
4
- *
5
- * ## Commands: vibe ai upscale, vibe ai interpolate, vibe ai inpaint, vibe ai track-object
6
- * ## Dependencies: Replicate
7
- *
8
- * Extracted from ai.ts as part of modularisation.
9
- * ai.ts re-exports all public types and functions from this module.
10
- * @see MODELS.md for AI model configuration
11
- */
12
-
13
- import { type Command } from "commander";
14
- import { writeFile } from "node:fs/promises";
15
- import { resolve } from "node:path";
16
- import chalk from "chalk";
17
- import ora from "ora";
18
- import { ReplicateProvider } from "@vibeframe/ai-providers";
19
- import { getApiKey } from "../utils/api-key.js";
20
- import { execSafe } from "../utils/exec-safe.js";
21
- import { downloadVideo } from "./ai-helpers.js";
22
-
23
- // ── Register all video FX commands ───────────────────────────────────────────
24
-
25
- export function registerVideoFxCommands(ai: Command): void {
26
- // Video Upscale command
27
- ai.command("video-upscale")
28
- .description("Upscale video resolution using AI or FFmpeg")
29
- .argument("<video>", "Video file path")
30
- .option("-o, --output <path>", "Output file path")
31
- .option("-s, --scale <factor>", "Scale factor: 2 or 4", "2")
32
- .option("-m, --model <model>", "Model: real-esrgan, topaz", "real-esrgan")
33
- .option("--ffmpeg", "Use FFmpeg lanczos (free, no API)")
34
- .option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)")
35
- .option("--no-wait", "Start processing and return task ID without waiting")
36
- .action(async (videoPath: string, options) => {
37
- try {
38
- const absPath = resolve(process.cwd(), videoPath);
39
- const scale = parseInt(options.scale);
40
-
41
- if (scale !== 2 && scale !== 4) {
42
- console.error(chalk.red("Scale must be 2 or 4"));
43
- process.exit(1);
44
- }
45
-
46
- // Use FFmpeg if requested (free fallback)
47
- if (options.ffmpeg) {
48
- const outputPath = options.output
49
- ? resolve(process.cwd(), options.output)
50
- : absPath.replace(/(\.[^.]+)$/, `-upscaled-${scale}x$1`);
51
-
52
- const spinner = ora(`Upscaling video with FFmpeg (${scale}x)...`).start();
53
-
54
- try {
55
- // Get original dimensions
56
- const { stdout: probeOut } = await execSafe("ffprobe", [
57
- "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of", "csv=p=0", absPath,
58
- ]);
59
- const [width, height] = probeOut.trim().split(",").map(Number);
60
- const newWidth = width * scale;
61
- const newHeight = height * scale;
62
-
63
- // Use lanczos scaling
64
- await execSafe("ffmpeg", ["-i", absPath, "-vf", `scale=${newWidth}:${newHeight}:flags=lanczos`, "-c:a", "copy", outputPath, "-y"]);
65
-
66
- spinner.succeed(chalk.green(`Upscaled to ${newWidth}x${newHeight}`));
67
- console.log(`Output: ${outputPath}`);
68
- } catch (err) {
69
- spinner.fail(chalk.red("FFmpeg upscaling failed"));
70
- console.error(err);
71
- process.exit(1);
72
- }
73
- return;
74
- }
75
-
76
- // Use Replicate API
77
- const apiKey = await getApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
78
- if (!apiKey) {
79
- console.error(chalk.red("Replicate API token required for AI upscaling."));
80
- console.error(chalk.dim("Use --api-key or set REPLICATE_API_TOKEN"));
81
- console.error(chalk.dim("Or use --ffmpeg for free FFmpeg upscaling"));
82
- process.exit(1);
83
- }
84
-
85
- const spinner = ora("Initializing Replicate...").start();
86
-
87
- const { ReplicateProvider } = await import("@vibeframe/ai-providers");
88
- const replicate = new ReplicateProvider();
89
- await replicate.initialize({ apiKey });
90
-
91
- // For Replicate, we need a URL. Upload to temporary hosting or require URL
92
- spinner.text = "Note: Replicate requires video URL. Reading file...";
93
-
94
- // For now, we'll show an error suggesting URL or ffmpeg
95
- spinner.fail(chalk.yellow("Replicate requires a video URL"));
96
- console.log();
97
- console.log(chalk.dim("Options:"));
98
- console.log(chalk.dim(" 1. Use --ffmpeg for local processing"));
99
- console.log(chalk.dim(" 2. Upload video to a URL and run:"));
100
- console.log(chalk.dim(` pnpm vibe ai video-upscale https://example.com/video.mp4 -s ${scale}`));
101
- console.log();
102
- process.exit(1);
103
- } catch (error) {
104
- console.error(chalk.red("Video upscaling failed"));
105
- console.error(error);
106
- process.exit(1);
107
- }
108
- });
109
-
110
- // Frame Interpolation (Slow Motion)
111
- ai.command("video-interpolate")
112
- .description("Create slow motion with frame interpolation (FFmpeg)")
113
- .argument("<video>", "Video file path")
114
- .option("-o, --output <path>", "Output file path")
115
- .option("-f, --factor <number>", "Slow motion factor: 2, 4, or 8", "2")
116
- .option("--fps <number>", "Target output FPS")
117
- .option("-q, --quality <mode>", "Quality: fast or quality", "quality")
118
- .action(async (videoPath: string, options) => {
119
- try {
120
- const absPath = resolve(process.cwd(), videoPath);
121
- const factor = parseInt(options.factor);
122
-
123
- if (![2, 4, 8].includes(factor)) {
124
- console.error(chalk.red("Factor must be 2, 4, or 8"));
125
- process.exit(1);
126
- }
127
-
128
- const outputPath = options.output
129
- ? resolve(process.cwd(), options.output)
130
- : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
131
-
132
- const spinner = ora(`Creating ${factor}x slow motion...`).start();
133
-
134
- try {
135
- // Get original FPS
136
- const { stdout: fpsOut } = await execSafe("ffprobe", [
137
- "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=r_frame_rate", "-of", "default=noprint_wrappers=1:nokey=1", absPath,
138
- ]);
139
- const [num, den] = fpsOut.trim().split("/").map(Number);
140
- const originalFps = num / (den || 1);
141
-
142
- // Calculate target FPS
143
- const targetFps = options.fps ? parseInt(options.fps) : originalFps * factor;
144
-
145
- // Use minterpolate for frame interpolation
146
- const mi = options.quality === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
147
-
148
- spinner.text = `Interpolating frames (${originalFps.toFixed(1)} → ${targetFps}fps)...`;
149
-
150
- // First interpolate frames, then slow down
151
- await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 600000 });
152
-
153
- spinner.succeed(chalk.green(`Created ${factor}x slow motion`));
154
- console.log();
155
- console.log(chalk.dim("─".repeat(60)));
156
- console.log(`Original FPS: ${originalFps.toFixed(1)}`);
157
- console.log(`Interpolated FPS: ${targetFps}`);
158
- console.log(`Slow factor: ${factor}x`);
159
- console.log(`Output: ${outputPath}`);
160
- console.log();
161
- } catch (err: unknown) {
162
- spinner.fail(chalk.red("Frame interpolation failed"));
163
- if (err instanceof Error && err.message.includes("timeout")) {
164
- console.error(chalk.yellow("Processing timed out. Try with a shorter video or --quality fast"));
165
- } else {
166
- console.error(err);
167
- }
168
- process.exit(1);
169
- }
170
- } catch (error) {
171
- console.error(chalk.red("Frame interpolation failed"));
172
- console.error(error);
173
- process.exit(1);
174
- }
175
- });
176
-
177
- // Video Inpainting (Object Removal)
178
- ai.command("video-inpaint")
179
- .description("Remove objects from video using AI inpainting")
180
- .argument("<video>", "Video file path or URL")
181
- .option("-o, --output <path>", "Output file path")
182
- .option("-t, --target <description>", "Object to remove (text description)")
183
- .option("-m, --mask <path>", "Mask video file path (white = remove)")
184
- .option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)")
185
- .option("--provider <name>", "Provider: replicate", "replicate")
186
- .option("--no-wait", "Start processing and return task ID without waiting")
187
- .action(async (videoPath: string, options) => {
188
- try {
189
- if (!options.target && !options.mask) {
190
- console.error(chalk.red("Either --target or --mask is required"));
191
- console.error(chalk.dim("Examples:"));
192
- console.error(chalk.dim(' pnpm vibe ai video-inpaint video.mp4 --target "watermark"'));
193
- console.error(chalk.dim(" pnpm vibe ai video-inpaint video.mp4 --mask mask.mp4"));
194
- process.exit(1);
195
- }
196
-
197
- const apiKey = await getApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
198
- if (!apiKey) {
199
- console.error(chalk.red("Replicate API token required for video inpainting."));
200
- console.error(chalk.dim("Use --api-key or set REPLICATE_API_TOKEN"));
201
- process.exit(1);
202
- }
203
-
204
- const spinner = ora("Initializing Replicate...").start();
205
-
206
- const { ReplicateProvider } = await import("@vibeframe/ai-providers");
207
- const replicate = new ReplicateProvider();
208
- await replicate.initialize({ apiKey });
209
-
210
- // Check if video is URL or file
211
- let videoUrl: string;
212
- if (videoPath.startsWith("http://") || videoPath.startsWith("https://")) {
213
- videoUrl = videoPath;
214
- } else {
215
- spinner.fail(chalk.yellow("Video inpainting requires a video URL"));
216
- console.log();
217
- console.log(chalk.dim("Upload your video to a URL and run:"));
218
- console.log(chalk.dim(` pnpm vibe ai video-inpaint https://example.com/video.mp4 --mask https://example.com/mask.mp4`));
219
- console.log();
220
- process.exit(1);
221
- }
222
-
223
- let maskVideo: string | undefined;
224
- if (options.mask) {
225
- if (options.mask.startsWith("http://") || options.mask.startsWith("https://")) {
226
- maskVideo = options.mask;
227
- } else {
228
- spinner.fail(chalk.yellow("Mask must also be a URL"));
229
- process.exit(1);
230
- }
231
- }
232
-
233
- spinner.text = "Starting video inpainting...";
234
-
235
- const result = await replicate.inpaintVideo(videoUrl, {
236
- target: options.target,
237
- maskVideo,
238
- });
239
-
240
- if (result.status === "failed") {
241
- spinner.fail(chalk.red(result.error || "Failed to start inpainting"));
242
- process.exit(1);
243
- }
244
-
245
- console.log();
246
- console.log(chalk.bold.cyan("Video Inpainting Started"));
247
- console.log(chalk.dim("─".repeat(60)));
248
- console.log(`Task ID: ${chalk.bold(result.id)}`);
249
-
250
- if (!options.wait) {
251
- spinner.succeed(chalk.green("Inpainting started"));
252
- console.log();
253
- console.log(chalk.dim("Check status with:"));
254
- console.log(chalk.dim(` curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" https://api.replicate.com/v1/predictions/${result.id}`));
255
- console.log();
256
- return;
257
- }
258
-
259
- spinner.text = "Processing video (this may take several minutes)...";
260
-
261
- const finalResult = await replicate.waitForCompletion(
262
- result.id,
263
- (status) => {
264
- spinner.text = `Processing... ${status.status}`;
265
- },
266
- 600000
267
- );
268
-
269
- if (finalResult.status !== "completed") {
270
- spinner.fail(chalk.red(finalResult.error || "Inpainting failed"));
271
- process.exit(1);
272
- }
273
-
274
- spinner.succeed(chalk.green("Video inpainting complete"));
275
-
276
- console.log();
277
- if (finalResult.videoUrl) {
278
- console.log(`Video URL: ${finalResult.videoUrl}`);
279
-
280
- // Download if output specified
281
- if (options.output) {
282
- const downloadSpinner = ora("Downloading video...").start();
283
- try {
284
- const buffer = await downloadVideo(finalResult.videoUrl);
285
- const outputPath = resolve(process.cwd(), options.output);
286
- await writeFile(outputPath, buffer);
287
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
288
- } catch (err) {
289
- downloadSpinner.fail(chalk.red("Failed to download video"));
290
- }
291
- }
292
- }
293
- console.log();
294
- } catch (error) {
295
- console.error(chalk.red("Video inpainting failed"));
296
- console.error(error);
297
- process.exit(1);
298
- }
299
- });
300
-
301
- // Object Tracking
302
- ai.command("track-object")
303
- .description("Track objects in video (Replicate SAM-2)")
304
- .argument("<video>", "Video file path or URL")
305
- .option("-p, --point <x,y>", "Point to track (x,y coordinates)")
306
- .option("-b, --box <x,y,w,h>", "Bounding box to track (x,y,width,height)")
307
- .option("--prompt <text>", "Object description to track")
308
- .option("-o, --output <path>", "Output JSON or MP4 file path", "track.json")
309
- .option("-v, --visualize", "Output video with tracking overlay")
310
- .option("--no-wait", "Start processing without waiting")
311
- .option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)")
312
- .action(async (videoPath: string, options) => {
313
- try {
314
- if (!options.point && !options.box && !options.prompt) {
315
- console.error(chalk.red("Tracking target required. Use --point, --box, or --prompt"));
316
- console.log(chalk.dim("Examples:"));
317
- console.log(chalk.dim(" pnpm vibe ai track-object video.mp4 --point 500,300"));
318
- console.log(chalk.dim(" pnpm vibe ai track-object video.mp4 --box 100,100,200,200"));
319
- console.log(chalk.dim(' pnpm vibe ai track-object video.mp4 --prompt "the person"'));
320
- process.exit(1);
321
- }
322
-
323
- const apiKey = await getApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
324
- if (!apiKey) {
325
- console.error(chalk.red("Replicate API token required."));
326
- console.error(chalk.dim("Set REPLICATE_API_TOKEN environment variable"));
327
- process.exit(1);
328
- }
329
-
330
- const spinner = ora("Initializing object tracking...").start();
331
-
332
- const replicate = new ReplicateProvider();
333
- await replicate.initialize({ apiKey });
334
-
335
- // Video must be URL
336
- let videoUrl: string;
337
- if (videoPath.startsWith("http://") || videoPath.startsWith("https://")) {
338
- videoUrl = videoPath;
339
- } else {
340
- spinner.fail(chalk.yellow("Video must be a URL for Replicate processing."));
341
- console.log(chalk.dim("Upload your video to a URL and try again."));
342
- process.exit(1);
343
- }
344
-
345
- // Parse tracking target
346
- let point: [number, number] | undefined;
347
- let box: [number, number, number, number] | undefined;
348
-
349
- if (options.point) {
350
- const [x, y] = options.point.split(",").map(Number);
351
- point = [x, y];
352
- }
353
-
354
- if (options.box) {
355
- const [x, y, w, h] = options.box.split(",").map(Number);
356
- box = [x, y, w, h];
357
- }
358
-
359
- spinner.text = "Starting object tracking...";
360
-
361
- const result = await replicate.trackObject({
362
- videoUrl,
363
- point,
364
- box,
365
- prompt: options.prompt,
366
- });
367
-
368
- if (result.status === "failed") {
369
- spinner.fail(chalk.red(result.error || "Object tracking failed"));
370
- process.exit(1);
371
- }
372
-
373
- console.log();
374
- console.log(chalk.bold.cyan("Object Tracking Started"));
375
- console.log(chalk.dim("─".repeat(60)));
376
- console.log(`Task ID: ${chalk.bold(result.id)}`);
377
- if (point) console.log(`Point: ${point[0]}, ${point[1]}`);
378
- if (box) console.log(`Box: ${box[0]}, ${box[1]}, ${box[2]}, ${box[3]}`);
379
- if (options.prompt) console.log(`Prompt: ${options.prompt}`);
380
-
381
- if (!options.wait) {
382
- spinner.succeed(chalk.green("Tracking started"));
383
- console.log();
384
- console.log(chalk.dim("Check status with:"));
385
- console.log(chalk.dim(` curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" https://api.replicate.com/v1/predictions/${result.id}`));
386
- console.log();
387
- return;
388
- }
389
-
390
- spinner.text = "Processing tracking (this may take several minutes)...";
391
-
392
- const finalResult = await replicate.getTrackingResult(result.id);
393
-
394
- // Poll for completion
395
- let pollResult = finalResult;
396
- const startTime = Date.now();
397
- const maxWait = 600000;
398
-
399
- while (pollResult.status !== "completed" && pollResult.status !== "failed" && Date.now() - startTime < maxWait) {
400
- await new Promise((r) => setTimeout(r, 3000));
401
- pollResult = await replicate.getTrackingResult(result.id);
402
- spinner.text = `Processing... ${pollResult.status}`;
403
- }
404
-
405
- if (pollResult.status !== "completed") {
406
- spinner.fail(chalk.red(pollResult.error || "Tracking failed or timed out"));
407
- process.exit(1);
408
- }
409
-
410
- spinner.succeed(chalk.green("Object tracking complete"));
411
-
412
- console.log();
413
- if (pollResult.maskUrl) {
414
- console.log(`Mask URL: ${pollResult.maskUrl}`);
415
-
416
- const outputPath = resolve(process.cwd(), options.output);
417
- if (options.visualize || options.output.endsWith(".mp4")) {
418
- const downloadSpinner = ora("Downloading tracking mask...").start();
419
- try {
420
- const response = await fetch(pollResult.maskUrl);
421
- const buffer = Buffer.from(await response.arrayBuffer());
422
- await writeFile(outputPath, buffer);
423
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
424
- } catch (err) {
425
- downloadSpinner.fail(chalk.red("Failed to download mask"));
426
- }
427
- } else {
428
- // Save tracking data as JSON
429
- const trackData = {
430
- taskId: result.id,
431
- maskUrl: pollResult.maskUrl,
432
- trackingData: pollResult.trackingData,
433
- };
434
- await writeFile(outputPath, JSON.stringify(trackData, null, 2));
435
- console.log(chalk.green(`Tracking data saved to: ${outputPath}`));
436
- }
437
- }
438
- console.log();
439
- } catch (error) {
440
- console.error(chalk.red("Object tracking failed"));
441
- console.error(error);
442
- process.exit(1);
443
- }
444
- });
445
- }