@gobi-ai/cli 0.9.0 → 0.9.2

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.8.0",
7
+ "version": "0.9.1",
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.8.0",
12
+ "version": "0.9.1",
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.8.0",
4
+ "version": "0.9.1",
5
5
  "author": {
6
6
  "name": "gobi-ai"
7
7
  },
@@ -3,7 +3,7 @@ import { BASE_URL, POLL_MAX_DURATION_MS } from "../constants.js";
3
3
  import { getValidToken } from "../auth/manager.js";
4
4
  import { ApiError } from "../errors.js";
5
5
  import { isJsonMode, jsonOut, unwrapResp } from "./utils.js";
6
- // ── Polling helper ──
6
+ // ── Helpers ──
7
7
  async function pollStatus(path, terminalStates, intervalMs = 3000) {
8
8
  const start = Date.now();
9
9
  while (Date.now() - start < POLL_MAX_DURATION_MS) {
@@ -12,10 +12,16 @@ async function pollStatus(path, terminalStates, intervalMs = 3000) {
12
12
  const status = data.status || "";
13
13
  if (terminalStates.includes(status))
14
14
  return data;
15
+ // If no status field but downloadUrl exists, treat as completed
16
+ if (!status && extractImageUrl(data))
17
+ return data;
15
18
  await new Promise((r) => setTimeout(r, intervalMs));
16
19
  }
17
20
  throw new Error(`Polling timed out after ${POLL_MAX_DURATION_MS / 1000}s`);
18
21
  }
22
+ function extractImageUrl(data) {
23
+ return (data.downloadUrl || data.download_url || data.url);
24
+ }
19
25
  export function registerMediaCommand(program) {
20
26
  const media = program
21
27
  .command("media")
@@ -240,7 +246,7 @@ export function registerMediaCommand(program) {
240
246
  .command("image-generate")
241
247
  .description("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")
242
248
  .requiredOption("--prompt <prompt>", "Text prompt for image generation")
243
- .requiredOption("--name <name>", "Name for the generated image")
249
+ .option("--name <name>", "Name for the generated image (auto-generated from prompt if omitted)")
244
250
  .option("--type <type>", "Generation type: image (default), thumbnail (YouTube-optimized), asset (logo/product)")
245
251
  .option("--aspect-ratio <aspectRatio>", "Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)")
246
252
  .option("--negative-prompt <negativePrompt>", "Negative prompt")
@@ -248,9 +254,10 @@ export function registerMediaCommand(program) {
248
254
  .option("--reference-media-id <referenceMediaId>", "Reference image media ID")
249
255
  .option("--wait", "Poll until generation completes")
250
256
  .action(async (opts) => {
257
+ const name = opts.name || opts.prompt.slice(0, 50).replace(/[^a-zA-Z0-9-_ ]/g, "").trim().replace(/\s+/g, "-");
251
258
  const body = {
252
259
  prompt: opts.prompt,
253
- name: opts.name,
260
+ name,
254
261
  };
255
262
  if (opts.type)
256
263
  body.type = opts.type;
@@ -278,10 +285,17 @@ export function registerMediaCommand(program) {
278
285
  jsonOut(data);
279
286
  return;
280
287
  }
288
+ const imgUrl = extractImageUrl(data);
289
+ const status = data.status || "queued";
281
290
  console.log(`Image generation started!\n` +
282
291
  ` Job ID: ${jobId}\n` +
283
- ` Status: ${data.status || "queued"}\n` +
284
- ` Check: gobi media image-status ${jobId}`);
292
+ ` Status: ${status}`);
293
+ if (imgUrl) {
294
+ console.log(` Download URL: ${imgUrl}`);
295
+ }
296
+ else if (status === "queued" || status === "inference_started" || status === "inference_working") {
297
+ console.log(` Check: gobi media image-status ${jobId}`);
298
+ }
285
299
  });
286
300
  media
287
301
  .command("image-edit")
@@ -311,9 +325,13 @@ export function registerMediaCommand(program) {
311
325
  jsonOut(data);
312
326
  return;
313
327
  }
328
+ const imgUrl = extractImageUrl(data);
314
329
  console.log(`Image edit started!\n` +
315
330
  ` Job ID: ${jobId}\n` +
316
331
  ` Status: ${data.status || "queued"}`);
332
+ if (imgUrl) {
333
+ console.log(` Download URL: ${imgUrl}`);
334
+ }
317
335
  });
318
336
  media
319
337
  .command("image-inpaint")
@@ -345,35 +363,82 @@ export function registerMediaCommand(program) {
345
363
  jsonOut(data);
346
364
  return;
347
365
  }
366
+ const imgUrl = extractImageUrl(data);
348
367
  console.log(`Inpainting started!\n` +
349
368
  ` Job ID: ${jobId}\n` +
350
369
  ` Status: ${data.status || "queued"}`);
370
+ if (imgUrl) {
371
+ console.log(` Download URL: ${imgUrl}`);
372
+ }
351
373
  });
352
374
  media
353
375
  .command("image-status <jobId>")
354
376
  .description("Check image generation job status.")
355
377
  .option("--wait", "Poll until a terminal state is reached")
356
378
  .action(async (jobId, opts) => {
379
+ let data;
357
380
  if (opts.wait) {
358
- const data = await pollStatus(`/media-gen/images/${jobId}`, [
381
+ data = await pollStatus(`/media-gen/images/${jobId}`, [
359
382
  "completed",
360
383
  "failed",
361
384
  "inference_complete",
362
385
  "inference_failed",
363
386
  ]);
364
- if (isJsonMode(media)) {
365
- jsonOut(data);
366
- return;
367
- }
368
- console.log(`Image job ${jobId} — status: ${data.status}`);
369
- return;
370
387
  }
371
- const resp = (await apiGet(`/media-gen/images/${jobId}`));
372
- const data = unwrapResp(resp);
388
+ else {
389
+ const resp = (await apiGet(`/media-gen/images/${jobId}`));
390
+ data = unwrapResp(resp);
391
+ }
373
392
  if (isJsonMode(media)) {
374
393
  jsonOut(data);
375
394
  return;
376
395
  }
396
+ const imgUrl = extractImageUrl(data);
377
397
  console.log(`Image job ${jobId} — status: ${data.status || "unknown"}`);
398
+ if (imgUrl) {
399
+ console.log(` Download URL: ${imgUrl}`);
400
+ }
401
+ });
402
+ media
403
+ .command("image-download <jobId>")
404
+ .description("Download a generated image.")
405
+ .option("--wait", "Poll until generation completes before downloading")
406
+ .option("--type <type>", "Image type (image, thumbnail, asset)")
407
+ .action(async (jobId, opts) => {
408
+ if (opts.wait) {
409
+ console.log(`Waiting for image job ${jobId} to complete…`);
410
+ await pollStatus(`/media-gen/images/${jobId}`, [
411
+ "completed",
412
+ "failed",
413
+ "inference_complete",
414
+ "inference_failed",
415
+ ]);
416
+ }
417
+ const token = await getValidToken();
418
+ const query = opts.type ? `?type=${opts.type}` : "";
419
+ const url = `${BASE_URL}/media-gen/images/${jobId}/download${query}`;
420
+ const res = await fetch(url, {
421
+ headers: { Authorization: `Bearer ${token}` },
422
+ });
423
+ if (!res.ok) {
424
+ const text = (await res.text()) || "(no body)";
425
+ throw new ApiError(res.status, `/media-gen/images/${jobId}/download`, text);
426
+ }
427
+ const contentType = res.headers.get("content-type") || "image/png";
428
+ const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg"
429
+ : contentType.includes("webp") ? "webp"
430
+ : "png";
431
+ const filename = `${jobId}.${ext}`;
432
+ if (isJsonMode(media)) {
433
+ // In JSON mode, return base64-encoded image
434
+ const buffer = Buffer.from(await res.arrayBuffer());
435
+ jsonOut({ filename, contentType, size: buffer.length, base64: buffer.toString("base64") });
436
+ return;
437
+ }
438
+ // Write to file
439
+ const { writeFile } = await import("fs/promises");
440
+ const buffer = Buffer.from(await res.arrayBuffer());
441
+ await writeFile(filename, buffer);
442
+ console.log(`Image saved to ${filename} (${buffer.length} bytes)`);
378
443
  });
379
444
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,6 +25,18 @@ For programmatic/agent usage, always pass `--json` as a **global** option (befor
25
25
  gobi --json media image-generate --prompt "a sunset over mountains"
26
26
  ```
27
27
 
28
+ ## Typical Workflow (Image Generation)
29
+
30
+ Always use `--wait` to poll until completion in a single command:
31
+
32
+ ```bash
33
+ gobi --json media image-generate --prompt "a sunset over mountains" --wait
34
+ ```
35
+
36
+ - `--name` is **optional** — auto-derived from prompt if omitted.
37
+ - `--wait` avoids needing a separate `image-status` call.
38
+ - `image-status` takes a **positional** jobId (NOT `--job-id`): `gobi media image-status <jobId>`
39
+
28
40
  ## Available Commands
29
41
 
30
42
  ### Upload
@@ -6,23 +6,24 @@ Usage: gobi media [options] [command]
6
6
  Media generation commands (videos, images).
7
7
 
8
8
  Options:
9
- -h, --help display help for command
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.
14
- avatars List available avatars.
15
- voices List available voices.
16
- video-create [options] Create an avatar video generation job.
17
- video-list List all videos.
18
- video-get <id> Get video metadata.
19
- video-status [options] <id> Poll video generation status.
20
- video-download <id> Get the download URL for a completed video.
21
- image-generate [options] 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
22
- image-edit [options] Edit an existing image with a prompt (image-to-image).
23
- image-inpaint [options] Inpaint an image region using a mask.
24
- image-status [options] <jobId> Check image generation job status.
25
- help [command] display help for command
12
+ upload-init [options] Get a presigned upload URL for a media file.
13
+ upload-finalize [options] Confirm that a media upload is complete.
14
+ avatars List available avatars.
15
+ voices List available voices.
16
+ video-create [options] Create an avatar video generation job.
17
+ video-list List all videos.
18
+ video-get <id> Get video metadata.
19
+ video-status [options] <id> Poll video generation status.
20
+ video-download <id> Get the download URL for a completed video.
21
+ image-generate [options] 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
22
+ image-edit [options] Edit an existing image with a prompt (image-to-image).
23
+ image-inpaint [options] Inpaint an image region using a mask.
24
+ image-status [options] <jobId> Check image generation job status.
25
+ image-download [options] <jobId> Download a generated image.
26
+ help [command] display help for command
26
27
  ```
27
28
 
28
29
  ## upload-init
@@ -144,7 +145,7 @@ Generate an image from a text prompt. Types: image (default), thumbnail (YouTube
144
145
 
145
146
  Options:
146
147
  --prompt <prompt> Text prompt for image generation
147
- --name <name> Name for the generated image
148
+ --name <name> Name for the generated image (auto-generated from prompt if omitted)
148
149
  --type <type> Generation type: image (default), thumbnail (YouTube-optimized), asset (logo/product)
149
150
  --aspect-ratio <aspectRatio> Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)
150
151
  --negative-prompt <negativePrompt> Negative prompt
@@ -196,3 +197,16 @@ Options:
196
197
  --wait Poll until a terminal state is reached
197
198
  -h, --help display help for command
198
199
  ```
200
+
201
+ ## image-download
202
+
203
+ ```
204
+ Usage: gobi media image-download [options] <jobId>
205
+
206
+ Download a generated image.
207
+
208
+ Options:
209
+ --wait Poll until generation completes before downloading
210
+ --type <type> Image type (image, thumbnail, asset)
211
+ -h, --help display help for command
212
+ ```