@gobi-ai/cli 0.9.9 → 0.9.10

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.
@@ -4,12 +4,12 @@
4
4
  "name": "gobi-ai"
5
5
  },
6
6
  "description": "Claude Code plugin for the Gobi collaborative knowledge platform CLI",
7
- "version": "0.9.9",
7
+ "version": "0.9.10",
8
8
  "plugins": [
9
9
  {
10
10
  "name": "gobi",
11
11
  "description": "Manage the Gobi collaborative knowledge platform from the command line. Search and ask brains, publish brain documents, create threads, manage sessions, generate images and videos.",
12
- "version": "0.9.9",
12
+ "version": "0.9.10",
13
13
  "author": {
14
14
  "name": "gobi-ai"
15
15
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gobi",
3
3
  "description": "Manage the Gobi collaborative knowledge platform from the command line",
4
- "version": "0.9.9",
4
+ "version": "0.9.10",
5
5
  "author": {
6
6
  "name": "gobi-ai"
7
7
  },
@@ -61,6 +61,47 @@ async function downloadVideoToFile(videoId, outputPath) {
61
61
  await writeFile(outputPath, buffer);
62
62
  return { contentType: ct || "video/mp4", size: buffer.length };
63
63
  }
64
+ const MIME_MAP = {
65
+ ".png": "image/png",
66
+ ".jpg": "image/jpeg",
67
+ ".jpeg": "image/jpeg",
68
+ ".webp": "image/webp",
69
+ ".gif": "image/gif",
70
+ ".mp4": "video/mp4",
71
+ ".mov": "video/quicktime",
72
+ ".mp3": "audio/mpeg",
73
+ ".wav": "audio/wav",
74
+ };
75
+ /**
76
+ * Upload a local file and return its media ID.
77
+ * Handles init → PUT → finalize in one call.
78
+ */
79
+ async function uploadFile(filePath) {
80
+ const { readFile, stat } = await import("fs/promises");
81
+ const { basename, extname } = await import("path");
82
+ const buffer = await readFile(filePath);
83
+ const fileName = basename(filePath);
84
+ const fileSize = (await stat(filePath)).size;
85
+ const ext = extname(filePath).toLowerCase();
86
+ const contentType = MIME_MAP[ext] || "application/octet-stream";
87
+ const initResp = (await apiPost("/media-gen/media/initialize", {
88
+ fileName, contentType, fileSize,
89
+ }));
90
+ const initData = unwrapResp(initResp);
91
+ const mediaId = initData.mediaId;
92
+ const uploadUrl = initData.uploadUrl;
93
+ if (!mediaId || !uploadUrl)
94
+ throw new Error("Upload init failed: missing mediaId or uploadUrl");
95
+ const putRes = await fetch(uploadUrl, {
96
+ method: "PUT",
97
+ headers: { "Content-Type": contentType },
98
+ body: buffer,
99
+ });
100
+ if (!putRes.ok)
101
+ throw new Error(`Upload PUT failed: ${putRes.status}`);
102
+ await apiPost("/media-gen/media/finalize", { mediaId });
103
+ return mediaId;
104
+ }
64
105
  function extractImageUrl(data) {
65
106
  return (data.downloadUrl || data.download_url || data.url);
66
107
  }
@@ -72,44 +113,15 @@ export function registerMediaCommand(program) {
72
113
  // Upload
73
114
  // ════════════════════════════════════════════════════════════════════
74
115
  media
75
- .command("upload-init")
76
- .description("Get a presigned upload URL for a media file.")
77
- .requiredOption("--file-name <fileName>", "Name of the file to upload")
78
- .requiredOption("--content-type <contentType>", "MIME type (e.g. image/png, video/mp4)")
79
- .option("--file-size <fileSize>", "File size in bytes")
80
- .action(async (opts) => {
81
- const body = {
82
- fileName: opts.fileName,
83
- contentType: opts.contentType,
84
- };
85
- if (opts.fileSize)
86
- body.fileSize = parseInt(opts.fileSize, 10);
87
- const resp = (await apiPost("/media-gen/media/initialize", body));
88
- const data = unwrapResp(resp);
89
- if (isJsonMode(media)) {
90
- jsonOut(data);
91
- return;
92
- }
93
- console.log(`Upload initialized!\n` +
94
- ` Media ID: ${data.mediaId}\n` +
95
- ` Upload URL: ${data.uploadUrl}\n\n` +
96
- `PUT your file to the upload URL, then run:\n` +
97
- ` gobi media upload-finalize --media-id ${data.mediaId}`);
98
- });
99
- media
100
- .command("upload-finalize")
101
- .description("Confirm that a media upload is complete.")
102
- .requiredOption("--media-id <mediaId>", "Media ID from upload-init")
103
- .action(async (opts) => {
104
- const resp = (await apiPost("/media-gen/media/finalize", {
105
- mediaId: opts.mediaId,
106
- }));
107
- const data = unwrapResp(resp);
116
+ .command("upload <file>")
117
+ .description("Upload a local file and return its media ID.")
118
+ .action(async (file) => {
119
+ const mediaId = await uploadFile(file);
108
120
  if (isJsonMode(media)) {
109
- jsonOut(data);
121
+ jsonOut({ mediaId });
110
122
  return;
111
123
  }
112
- console.log(`Upload finalized for media ${opts.mediaId}.`);
124
+ console.log(`Uploaded Media ID: ${mediaId}`);
113
125
  });
114
126
  // ════════════════════════════════════════════════════════════════════
115
127
  // Avatars & Voices
@@ -162,7 +174,7 @@ export function registerMediaCommand(program) {
162
174
  .requiredOption("--avatar-id <avatarId>", "Avatar to use")
163
175
  .requiredOption("--voice-id <voiceId>", "Voice to use")
164
176
  .requiredOption("--script <script>", "Script for the avatar to read")
165
- .option("--background-media-id <backgroundMediaId>", "Background media ID (from upload)")
177
+ .option("--background <file>", "Background image file (auto-uploaded)")
166
178
  .option("--wait", "Poll until generation completes")
167
179
  .option("-o, --output <path>", "Download video to this path when done (implies --wait)")
168
180
  .action(async (opts) => {
@@ -174,8 +186,8 @@ export function registerMediaCommand(program) {
174
186
  voiceId: opts.voiceId,
175
187
  script: opts.script,
176
188
  };
177
- if (opts.backgroundMediaId)
178
- body.backgroundMediaId = opts.backgroundMediaId;
189
+ if (opts.background)
190
+ body.backgroundMediaId = await uploadFile(opts.background);
179
191
  const resp = (await apiPost("/media-gen/videos", body));
180
192
  let data = unwrapResp(resp);
181
193
  const videoId = data.id || data.videoId || data.jobId;
@@ -336,9 +348,9 @@ export function registerMediaCommand(program) {
336
348
  .option("--generate-audio", "Generate audio for the video")
337
349
  .option("--negative-prompt <negativePrompt>", "Negative prompt")
338
350
  .option("--sample-count <count>", "Number of samples (1-4)")
339
- .option("--first-frame-media-id <mediaId>", "First frame image media ID")
340
- .option("--last-frame-media-id <mediaId>", "Last frame image media ID")
341
- .option("--reference-media-ids <ids>", "Comma-separated reference image media IDs (max 3)")
351
+ .option("--first-frame <file>", "First frame image file (auto-uploaded)")
352
+ .option("--last-frame <file>", "Last frame image file (auto-uploaded)")
353
+ .option("--reference-images <files>", "Comma-separated reference image files (auto-uploaded, max 3)")
342
354
  .option("--wait", "Poll until generation completes")
343
355
  .option("-o, --output <path>", "Download video to this path when done (implies --wait)")
344
356
  .action(async (opts) => {
@@ -370,12 +382,14 @@ export function registerMediaCommand(program) {
370
382
  throw new Error("--sample-count must be a number");
371
383
  body.sampleCount = v;
372
384
  }
373
- if (opts.firstFrameMediaId)
374
- body.firstFrameImageMediaId = opts.firstFrameMediaId;
375
- if (opts.lastFrameMediaId)
376
- body.lastFrameImageMediaId = opts.lastFrameMediaId;
377
- if (opts.referenceMediaIds)
378
- body.referenceImageMediaIds = opts.referenceMediaIds.split(",").map((s) => s.trim());
385
+ if (opts.firstFrame)
386
+ body.firstFrameImageMediaId = await uploadFile(opts.firstFrame);
387
+ if (opts.lastFrame)
388
+ body.lastFrameImageMediaId = await uploadFile(opts.lastFrame);
389
+ if (opts.referenceImages) {
390
+ const files = opts.referenceImages.split(",").map((s) => s.trim());
391
+ body.referenceImageMediaIds = await Promise.all(files.map((f) => uploadFile(f)));
392
+ }
379
393
  const resp = (await apiPost("/media-gen/videos/cinematic", body));
380
394
  let data = unwrapResp(resp);
381
395
  const videoId = data.id || data.videoId || data.jobId;
@@ -413,18 +427,18 @@ export function registerMediaCommand(program) {
413
427
  media
414
428
  .command("avatar-design")
415
429
  .description("Start a design-your-avatar job.")
416
- .requiredOption("--name <name>", "Name for the avatar")
430
+ .option("--name <name>", "Name for the avatar (auto-generated if omitted)")
417
431
  .requiredOption("--gender <gender>", "Gender for the avatar design")
418
432
  .requiredOption("--age <age>", "Age range for the avatar")
419
433
  .requiredOption("--ethnicity <ethnicity>", "Ethnicity for the avatar")
420
434
  .requiredOption("--outfit <outfit>", "Outfit description")
421
435
  .requiredOption("--background <background>", "Background description")
422
436
  .option("--no-portrait", "Generate full-body instead of portrait")
423
- .option("--audio-media-id <mediaId>", "Custom voice audio media ID")
437
+ .option("--audio <file>", "Custom voice audio file (auto-uploaded)")
424
438
  .option("--wait", "Poll until variants are ready")
425
439
  .action(async (opts) => {
426
440
  const body = {
427
- name: opts.name,
441
+ name: opts.name || `avatar-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}`,
428
442
  gender: opts.gender,
429
443
  age: opts.age,
430
444
  ethnicity: opts.ethnicity,
@@ -432,8 +446,8 @@ export function registerMediaCommand(program) {
432
446
  background: opts.background,
433
447
  isPortrait: opts.portrait,
434
448
  };
435
- if (opts.audioMediaId)
436
- body.audioMediaId = opts.audioMediaId;
449
+ if (opts.audio)
450
+ body.audioMediaId = await uploadFile(opts.audio);
437
451
  const resp = (await apiPost("/media-gen/avatars/design", body));
438
452
  let data = unwrapResp(resp);
439
453
  const jobId = data.jobId || data.id;
@@ -479,20 +493,20 @@ export function registerMediaCommand(program) {
479
493
  media
480
494
  .command("avatar-from-selfie")
481
495
  .description("Create an avatar from a selfie (instant or enhanced with prompt).")
482
- .requiredOption("--name <name>", "Name for the avatar")
483
- .requiredOption("--photo-media-id <mediaId>", "Selfie photo media ID")
496
+ .option("--name <name>", "Name for the avatar (auto-generated if omitted)")
497
+ .requiredOption("--photo <file>", "Selfie photo file (auto-uploaded)")
484
498
  .option("--prompt <prompt>", "Enhancement prompt (triggers async enhance flow)")
485
- .option("--audio-media-id <mediaId>", "Custom voice audio media ID")
499
+ .option("--audio <file>", "Custom voice audio file (auto-uploaded)")
486
500
  .option("--wait", "Poll until job completes (only for enhance flow)")
487
501
  .action(async (opts) => {
488
502
  const body = {
489
- name: opts.name,
490
- photoMediaId: opts.photoMediaId,
503
+ name: opts.name || `avatar-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}`,
504
+ photoMediaId: await uploadFile(opts.photo),
491
505
  };
492
506
  if (opts.prompt)
493
507
  body.prompt = opts.prompt;
494
- if (opts.audioMediaId)
495
- body.audioMediaId = opts.audioMediaId;
508
+ if (opts.audio)
509
+ body.audioMediaId = await uploadFile(opts.audio);
496
510
  const resp = (await apiPost("/media-gen/avatars/from-selfie", body));
497
511
  let data = unwrapResp(resp);
498
512
  const jobId = data.jobId || data.id;
@@ -548,7 +562,7 @@ export function registerMediaCommand(program) {
548
562
  .option("--aspect-ratio <aspectRatio>", "Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)")
549
563
  .option("--negative-prompt <negativePrompt>", "Negative prompt")
550
564
  .option("--seed <seed>", "Random seed for reproducibility")
551
- .option("--reference-media-id <referenceMediaId>", "Reference image media ID")
565
+ .option("--reference-image <file>", "Reference image file (auto-uploaded)")
552
566
  .option("--wait", "Poll until generation completes")
553
567
  .option("-o, --output <path>", "Download image to this path when done (implies --wait)")
554
568
  .action(async (opts) => {
@@ -566,8 +580,8 @@ export function registerMediaCommand(program) {
566
580
  body.negativePrompt = opts.negativePrompt;
567
581
  if (opts.seed)
568
582
  body.seed = parseInt(opts.seed, 10);
569
- if (opts.referenceMediaId)
570
- body.referenceMediaId = opts.referenceMediaId;
583
+ if (opts.referenceImage)
584
+ body.referenceMediaId = await uploadFile(opts.referenceImage);
571
585
  const resp = (await apiPost("/media-gen/images/generate", body));
572
586
  let data = unwrapResp(resp);
573
587
  const jobId = data.jobId || data.id;
@@ -624,19 +638,23 @@ export function registerMediaCommand(program) {
624
638
  media
625
639
  .command("image-edit")
626
640
  .description("Edit an existing image with a prompt (image-to-image).")
627
- .requiredOption("--media-id <mediaId>", "Source image media ID")
641
+ .requiredOption("--image <file>", "Source image file (auto-uploaded)")
628
642
  .requiredOption("--prompt <prompt>", "Edit instruction")
629
- .requiredOption("--name <name>", "Name for the edited image")
643
+ .option("--name <name>", "Name for the edited image (auto-generated if omitted)")
630
644
  .option("--wait", "Poll until generation completes")
645
+ .option("-o, --output <path>", "Download image to this path when done (implies --wait)")
631
646
  .action(async (opts) => {
647
+ const shouldWait = opts.wait || !!opts.output;
648
+ const mediaId = await uploadFile(opts.image);
649
+ const autoName = opts.name || opts.prompt.slice(0, 50).replace(/[^a-zA-Z0-9-_ ]/g, "").trim().replace(/\s+/g, "-");
632
650
  const resp = (await apiPost("/media-gen/images/edit", {
633
- mediaId: opts.mediaId,
651
+ mediaId,
634
652
  prompt: opts.prompt,
635
- name: opts.name,
653
+ name: autoName,
636
654
  }));
637
655
  let data = unwrapResp(resp);
638
656
  const jobId = data.jobId || data.id;
639
- if (opts.wait && jobId) {
657
+ if (shouldWait && jobId) {
640
658
  console.log(`Image edit job ${jobId} — polling for completion…`);
641
659
  data = await pollStatus(`/media-gen/images/${jobId}`, [
642
660
  "completed",
@@ -645,6 +663,29 @@ export function registerMediaCommand(program) {
645
663
  "inference_failed",
646
664
  ]);
647
665
  }
666
+ // Download image to file if -o specified
667
+ if (opts.output && jobId) {
668
+ const token = await getValidToken();
669
+ const query = "";
670
+ const url = `${BASE_URL}/media-gen/images/${jobId}/download${query}`;
671
+ const res = await fetch(url, {
672
+ headers: { Authorization: `Bearer ${token}` },
673
+ });
674
+ if (res.ok) {
675
+ const { writeFile, mkdir } = await import("fs/promises");
676
+ const { dirname } = await import("path");
677
+ const buffer = Buffer.from(await res.arrayBuffer());
678
+ await mkdir(dirname(opts.output), { recursive: true });
679
+ await writeFile(opts.output, buffer);
680
+ const contentType = res.headers.get("content-type") || "image/png";
681
+ if (isJsonMode(media)) {
682
+ jsonOut({ ...data, filename: opts.output, contentType, size: buffer.length });
683
+ return;
684
+ }
685
+ console.log(`Image saved to ${opts.output} (${buffer.length} bytes)`);
686
+ return;
687
+ }
688
+ }
648
689
  if (isJsonMode(media)) {
649
690
  jsonOut(data);
650
691
  return;
@@ -660,21 +701,28 @@ export function registerMediaCommand(program) {
660
701
  media
661
702
  .command("image-inpaint")
662
703
  .description("Inpaint an image region using a mask.")
663
- .requiredOption("--media-id <mediaId>", "Source image media ID")
664
- .requiredOption("--mask-media-id <maskMediaId>", "Mask image media ID")
704
+ .requiredOption("--image <file>", "Source image file (auto-uploaded)")
705
+ .requiredOption("--mask <file>", "Mask image file (auto-uploaded)")
665
706
  .requiredOption("--prompt <prompt>", "Inpainting prompt")
666
- .requiredOption("--name <name>", "Name for the inpainted image")
707
+ .option("--name <name>", "Name for the inpainted image (auto-generated if omitted)")
667
708
  .option("--wait", "Poll until generation completes")
709
+ .option("-o, --output <path>", "Download image to this path when done (implies --wait)")
668
710
  .action(async (opts) => {
711
+ const shouldWait = opts.wait || !!opts.output;
712
+ const [mediaId, maskMediaId] = await Promise.all([
713
+ uploadFile(opts.image),
714
+ uploadFile(opts.mask),
715
+ ]);
716
+ const autoName = opts.name || opts.prompt.slice(0, 50).replace(/[^a-zA-Z0-9-_ ]/g, "").trim().replace(/\s+/g, "-");
669
717
  const resp = (await apiPost("/media-gen/images/inpaint", {
670
- mediaId: opts.mediaId,
671
- maskMediaId: opts.maskMediaId,
718
+ mediaId,
719
+ maskMediaId,
672
720
  prompt: opts.prompt,
673
- name: opts.name,
721
+ name: autoName,
674
722
  }));
675
723
  let data = unwrapResp(resp);
676
724
  const jobId = data.jobId || data.id;
677
- if (opts.wait && jobId) {
725
+ if (shouldWait && jobId) {
678
726
  console.log(`Inpaint job ${jobId} — polling for completion…`);
679
727
  data = await pollStatus(`/media-gen/images/${jobId}`, [
680
728
  "completed",
@@ -683,6 +731,28 @@ export function registerMediaCommand(program) {
683
731
  "inference_failed",
684
732
  ]);
685
733
  }
734
+ // Download image to file if -o specified
735
+ if (opts.output && jobId) {
736
+ const token = await getValidToken();
737
+ const url = `${BASE_URL}/media-gen/images/${jobId}/download`;
738
+ const res = await fetch(url, {
739
+ headers: { Authorization: `Bearer ${token}` },
740
+ });
741
+ if (res.ok) {
742
+ const { writeFile, mkdir } = await import("fs/promises");
743
+ const { dirname } = await import("path");
744
+ const buffer = Buffer.from(await res.arrayBuffer());
745
+ await mkdir(dirname(opts.output), { recursive: true });
746
+ await writeFile(opts.output, buffer);
747
+ const contentType = res.headers.get("content-type") || "image/png";
748
+ if (isJsonMode(media)) {
749
+ jsonOut({ ...data, filename: opts.output, contentType, size: buffer.length });
750
+ return;
751
+ }
752
+ console.log(`Image saved to ${opts.output} (${buffer.length} bytes)`);
753
+ return;
754
+ }
755
+ }
686
756
  if (isJsonMode(media)) {
687
757
  jsonOut(data);
688
758
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -67,10 +67,10 @@ gobi --json media video-create --avatar-id "<AVATAR_ID>" --voice-id "<VOICE_ID>"
67
67
 
68
68
  The `-o` flag implies `--wait` and downloads the video when done.
69
69
 
70
- To use a custom image as the **background** of a video, upload it via `upload-init` / `upload-finalize`, then pass the mediaId as `--background-media-id`:
70
+ To use a custom image as the **background** of a video, pass it directly as `--background <file>` (auto-uploaded):
71
71
 
72
72
  ```bash
73
- gobi --json media video-create --avatar-id "<AVATAR_ID>" --voice-id "<VOICE_ID>" --script "<SCRIPT>" --background-media-id "<MEDIA_ID>" -o media/<NAME>.mp4
73
+ gobi --json media video-create --avatar-id "<AVATAR_ID>" --voice-id "<VOICE_ID>" --script "<SCRIPT>" --background media/bg.png -o media/<NAME>.mp4
74
74
  ```
75
75
 
76
76
  ## Typical Workflow (Cinematic Video)
@@ -81,7 +81,17 @@ Generate a cinematic video from a text prompt (no avatar needed):
81
81
  gobi --json media cinematic-create --prompt "<PROMPT>" --aspect-ratio "<RATIO>" -o media/<NAME>.mp4
82
82
  ```
83
83
 
84
- Options: `--duration <4-8>`, `--resolution <720p|1080p>`, `--enhance-prompt`, `--generate-audio`, `--negative-prompt`, `--sample-count <1-4>`, `--first-frame-media-id`, `--last-frame-media-id`, `--reference-media-ids`.
84
+ Options: `--duration <4-8>`, `--resolution <720p|1080p>`, `--enhance-prompt`, `--generate-audio`, `--negative-prompt`, `--sample-count <1-4>`, `--first-frame <file>`, `--last-frame <file>`, `--reference-images <files>`.
85
+
86
+ ## Typical Workflow (Image Editing)
87
+
88
+ Edit an existing image with a prompt — single command:
89
+
90
+ ```bash
91
+ gobi --json media image-edit --image media/source.png --prompt "<EDIT_INSTRUCTION>" -o media/<NAME>.png
92
+ ```
93
+
94
+ All file arguments (`--image`, `--mask`, `--background`, `--photo`, `--audio`, `--reference-image`, `--first-frame`, `--last-frame`) accept local file paths and auto-upload them. No need to manually upload first.
85
95
 
86
96
  ## Custom Avatars
87
97
 
@@ -90,7 +100,7 @@ Three ways to create custom avatars:
90
100
  ### 1. Design from scratch
91
101
 
92
102
  ```bash
93
- gobi --json media avatar-design --name "<NAME>" --gender "<GENDER>" --age "<AGE>" --ethnicity "<ETHNICITY>" --outfit "<OUTFIT>" --background "<BACKGROUND>" --wait
103
+ gobi --json media avatar-design --gender "<GENDER>" --age "<AGE>" --ethnicity "<ETHNICITY>" --outfit "<OUTFIT>" --background "<BACKGROUND>" --wait
94
104
  ```
95
105
 
96
106
  When `variants_ready`, confirm with:
@@ -102,15 +112,13 @@ gobi --json media avatar-confirm --job-id "<JOB_ID>"
102
112
  ### 2. From a selfie (instant)
103
113
 
104
114
  ```bash
105
- gobi --json media avatar-from-selfie --name "<NAME>" --photo-media-id "<MEDIA_ID>"
115
+ gobi --json media avatar-from-selfie --photo media/selfie.png
106
116
  ```
107
117
 
108
- Upload the selfie first via `upload-init` / `upload-finalize` to get the media ID.
109
-
110
118
  ### 3. From a selfie (enhanced with prompt)
111
119
 
112
120
  ```bash
113
- gobi --json media avatar-from-selfie --name "<NAME>" --photo-media-id "<MEDIA_ID>" --prompt "<ENHANCEMENT>" --wait
121
+ gobi --json media avatar-from-selfie --photo media/selfie.png --prompt "<ENHANCEMENT>" --wait
114
122
  ```
115
123
 
116
124
  Check any avatar job status with:
@@ -131,8 +139,7 @@ Do NOT use markdown image/link syntax `![](...)` or `gobi://` URLs. Always use `
131
139
 
132
140
  ### Upload
133
141
 
134
- - `gobi media upload-init`Get a presigned upload URL for a media file.
135
- - `gobi media upload-finalize` — Confirm that a media upload is complete.
142
+ - `gobi media upload <file>` Upload a local file and return its media ID. Content type is auto-detected.
136
143
 
137
144
  ### Avatars & Voices
138
145
 
@@ -161,6 +168,8 @@ Do NOT use markdown image/link syntax `![](...)` or `gobi://` URLs. Always use `
161
168
  - `gobi media image-edit` — Edit an existing image with a prompt (image-to-image).
162
169
  - `gobi media image-inpaint` — Inpaint an image region using a mask.
163
170
  - `gobi media image-status` — Check image generation job status.
171
+ - `gobi media image-download` — Download a generated image.
172
+ - `gobi media image-status` — Check image generation job status.
164
173
 
165
174
  ## Reference Documentation
166
175
 
@@ -9,8 +9,7 @@ Options:
9
9
  -h, --help display help for command
10
10
 
11
11
  Commands:
12
- upload-init [options] Get a presigned upload URL for a media file.
13
- upload-finalize [options] Confirm that a media upload is complete.
12
+ upload <file> Upload a local file and return its media ID.
14
13
  avatars List available avatars.
15
14
  voices List available voices.
16
15
  video-create [options] Create an avatar video generation job.
@@ -31,30 +30,15 @@ Commands:
31
30
  help [command] display help for command
32
31
  ```
33
32
 
34
- ## upload-init
33
+ ## upload
35
34
 
36
35
  ```
37
- Usage: gobi media upload-init [options]
36
+ Usage: gobi media upload [options] <file>
38
37
 
39
- Get a presigned upload URL for a media file.
38
+ Upload a local file and return its media ID.
40
39
 
41
40
  Options:
42
- --file-name <fileName> Name of the file to upload
43
- --content-type <contentType> MIME type (e.g. image/png, video/mp4)
44
- --file-size <fileSize> File size in bytes
45
- -h, --help display help for command
46
- ```
47
-
48
- ## upload-finalize
49
-
50
- ```
51
- Usage: gobi media upload-finalize [options]
52
-
53
- Confirm that a media upload is complete.
54
-
55
- Options:
56
- --media-id <mediaId> Media ID from upload-init
57
- -h, --help display help for command
41
+ -h, --help display help for command
58
42
  ```
59
43
 
60
44
  ## avatars
@@ -87,14 +71,14 @@ Usage: gobi media video-create [options]
87
71
  Create an avatar video generation job.
88
72
 
89
73
  Options:
90
- --name <name> Name for the video (auto-generated if omitted)
91
- --avatar-id <avatarId> Avatar to use
92
- --voice-id <voiceId> Voice to use
93
- --script <script> Script for the avatar to read
94
- --background-media-id <backgroundMediaId> Background media ID (from upload)
95
- --wait Poll until generation completes
96
- -o, --output <path> Download video to this path when done (implies --wait)
97
- -h, --help display help for command
74
+ --name <name> Name for the video (auto-generated if omitted)
75
+ --avatar-id <avatarId> Avatar to use
76
+ --voice-id <voiceId> Voice to use
77
+ --script <script> Script for the avatar to read
78
+ --background <file> Background image file (auto-uploaded)
79
+ --wait Poll until generation completes
80
+ -o, --output <path> Download video to this path when done (implies --wait)
81
+ -h, --help display help for command
98
82
  ```
99
83
 
100
84
  ## video-list
@@ -161,9 +145,9 @@ Options:
161
145
  --generate-audio Generate audio for the video
162
146
  --negative-prompt <negativePrompt> Negative prompt
163
147
  --sample-count <count> Number of samples (1-4)
164
- --first-frame-media-id <mediaId> First frame image media ID
165
- --last-frame-media-id <mediaId> Last frame image media ID
166
- --reference-media-ids <ids> Comma-separated reference image media IDs (max 3)
148
+ --first-frame <file> First frame image file (auto-uploaded)
149
+ --last-frame <file> Last frame image file (auto-uploaded)
150
+ --reference-images <files> Comma-separated reference image files (auto-uploaded, max 3)
167
151
  --wait Poll until generation completes
168
152
  -o, --output <path> Download video to this path when done (implies --wait)
169
153
  -h, --help display help for command
@@ -177,16 +161,16 @@ Usage: gobi media avatar-design [options]
177
161
  Start a design-your-avatar job.
178
162
 
179
163
  Options:
180
- --name <name> Name for the avatar
181
- --gender <gender> Gender for the avatar design
182
- --age <age> Age range for the avatar
183
- --ethnicity <ethnicity> Ethnicity for the avatar
184
- --outfit <outfit> Outfit description
185
- --background <background> Background description
186
- --no-portrait Generate full-body instead of portrait
187
- --audio-media-id <mediaId> Custom voice audio media ID
188
- --wait Poll until variants are ready
189
- -h, --help display help for command
164
+ --name <name> Name for the avatar (auto-generated if omitted)
165
+ --gender <gender> Gender for the avatar design
166
+ --age <age> Age range for the avatar
167
+ --ethnicity <ethnicity> Ethnicity for the avatar
168
+ --outfit <outfit> Outfit description
169
+ --background <background> Background description
170
+ --no-portrait Generate full-body instead of portrait
171
+ --audio <file> Custom voice audio file (auto-uploaded)
172
+ --wait Poll until variants are ready
173
+ -h, --help display help for command
190
174
  ```
191
175
 
192
176
  ## avatar-confirm
@@ -210,12 +194,12 @@ Usage: gobi media avatar-from-selfie [options]
210
194
  Create an avatar from a selfie (instant or enhanced with prompt).
211
195
 
212
196
  Options:
213
- --name <name> Name for the avatar
214
- --photo-media-id <mediaId> Selfie photo media ID
215
- --prompt <prompt> Enhancement prompt (triggers async enhance flow)
216
- --audio-media-id <mediaId> Custom voice audio media ID
217
- --wait Poll until job completes (only for enhance flow)
218
- -h, --help display help for command
197
+ --name <name> Name for the avatar (auto-generated if omitted)
198
+ --photo <file> Selfie photo file (auto-uploaded)
199
+ --prompt <prompt> Enhancement prompt (triggers async enhance flow)
200
+ --audio <file> Custom voice audio file (auto-uploaded)
201
+ --wait Poll until job completes (only for enhance flow)
202
+ -h, --help display help for command
219
203
  ```
220
204
 
221
205
  ## avatar-job-status
@@ -238,16 +222,16 @@ Usage: gobi media image-generate [options]
238
222
  Generate an image from a text prompt. Types: image (default), thumbnail (YouTube-optimized), asset (logo/product). Aspect ratios: 1:1, 16:9, 9:16, 4:3, 3:4
239
223
 
240
224
  Options:
241
- --prompt <prompt> Text prompt for image generation
242
- --name <name> Name for the generated image (auto-generated from prompt if omitted)
243
- --type <type> Generation type: image (default), thumbnail (YouTube-optimized), asset (logo/product)
244
- --aspect-ratio <aspectRatio> Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)
245
- --negative-prompt <negativePrompt> Negative prompt
246
- --seed <seed> Random seed for reproducibility
247
- --reference-media-id <referenceMediaId> Reference image media ID
248
- --wait Poll until generation completes
249
- -o, --output <path> Download image to this path when done (implies --wait)
250
- -h, --help display help for command
225
+ --prompt <prompt> Text prompt for image generation
226
+ --name <name> Name for the generated image (auto-generated from prompt if omitted)
227
+ --type <type> Generation type: image (default), thumbnail (YouTube-optimized), asset (logo/product)
228
+ --aspect-ratio <aspectRatio> Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)
229
+ --negative-prompt <negativePrompt> Negative prompt
230
+ --seed <seed> Random seed for reproducibility
231
+ --reference-image <file> Reference image file (auto-uploaded)
232
+ --wait Poll until generation completes
233
+ -o, --output <path> Download image to this path when done (implies --wait)
234
+ -h, --help display help for command
251
235
  ```
252
236
 
253
237
  ## image-edit
@@ -258,11 +242,12 @@ Usage: gobi media image-edit [options]
258
242
  Edit an existing image with a prompt (image-to-image).
259
243
 
260
244
  Options:
261
- --media-id <mediaId> Source image media ID
262
- --prompt <prompt> Edit instruction
263
- --name <name> Name for the edited image
264
- --wait Poll until generation completes
265
- -h, --help display help for command
245
+ --image <file> Source image file (auto-uploaded)
246
+ --prompt <prompt> Edit instruction
247
+ --name <name> Name for the edited image (auto-generated if omitted)
248
+ --wait Poll until generation completes
249
+ -o, --output <path> Download image to this path when done (implies --wait)
250
+ -h, --help display help for command
266
251
  ```
267
252
 
268
253
  ## image-inpaint
@@ -273,12 +258,13 @@ Usage: gobi media image-inpaint [options]
273
258
  Inpaint an image region using a mask.
274
259
 
275
260
  Options:
276
- --media-id <mediaId> Source image media ID
277
- --mask-media-id <maskMediaId> Mask image media ID
278
- --prompt <prompt> Inpainting prompt
279
- --name <name> Name for the inpainted image
280
- --wait Poll until generation completes
281
- -h, --help display help for command
261
+ --image <file> Source image file (auto-uploaded)
262
+ --mask <file> Mask image file (auto-uploaded)
263
+ --prompt <prompt> Inpainting prompt
264
+ --name <name> Name for the inpainted image (auto-generated if omitted)
265
+ --wait Poll until generation completes
266
+ -o, --output <path> Download image to this path when done (implies --wait)
267
+ -h, --help display help for command
282
268
  ```
283
269
 
284
270
  ## image-status