@gobi-ai/cli 0.9.6 → 0.9.7

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.5",
7
+ "version": "0.9.7",
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.5",
12
+ "version": "0.9.7",
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.5",
4
+ "version": "0.9.7",
5
5
  "author": {
6
6
  "name": "gobi-ai"
7
7
  },
@@ -116,7 +116,7 @@ export function registerMediaCommand(program) {
116
116
  media
117
117
  .command("video-create")
118
118
  .description("Create an avatar video generation job.")
119
- .requiredOption("--name <name>", "Name for the video")
119
+ .option("--name <name>", "Name for the video (auto-generated if omitted)")
120
120
  .requiredOption("--avatar-id <avatarId>", "Avatar to use")
121
121
  .requiredOption("--voice-id <voiceId>", "Voice to use")
122
122
  .requiredOption("--script <script>", "Script for the avatar to read")
@@ -125,8 +125,9 @@ export function registerMediaCommand(program) {
125
125
  .option("-o, --output <path>", "Download video to this path when done (implies --wait)")
126
126
  .action(async (opts) => {
127
127
  const shouldWait = opts.wait || !!opts.output;
128
+ const autoName = opts.name || `video-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
128
129
  const body = {
129
- name: opts.name,
130
+ name: autoName,
130
131
  avatarId: opts.avatarId,
131
132
  voiceId: opts.voiceId,
132
133
  script: opts.script,
@@ -236,9 +237,57 @@ export function registerMediaCommand(program) {
236
237
  .command("video-status <id>")
237
238
  .description("Poll video generation status.")
238
239
  .option("--wait", "Poll until a terminal state is reached")
240
+ .option("-o, --output <path>", "Download video to this path when complete (implies --wait)")
239
241
  .action(async (id, opts) => {
240
- if (opts.wait) {
242
+ const shouldWait = opts.wait || !!opts.output;
243
+ if (shouldWait) {
241
244
  const data = await pollStatus(`/media-gen/videos/${id}/status`, ["inference_complete", "inference_failed"]);
245
+ // Download if -o specified and completed
246
+ if (opts.output && data.status === "inference_complete") {
247
+ const token = await getValidToken();
248
+ const dlUrl = `${BASE_URL}/media-gen/videos/${id}/download`;
249
+ const dlRes = await fetch(dlUrl, {
250
+ headers: { Authorization: `Bearer ${token}` },
251
+ redirect: "follow",
252
+ });
253
+ if (dlRes.ok) {
254
+ const { writeFile, mkdir } = await import("fs/promises");
255
+ const { dirname } = await import("path");
256
+ const buffer = Buffer.from(await dlRes.arrayBuffer());
257
+ await mkdir(dirname(opts.output), { recursive: true });
258
+ await writeFile(opts.output, buffer);
259
+ const contentType = dlRes.headers.get("content-type") || "video/mp4";
260
+ if (isJsonMode(media)) {
261
+ jsonOut({ ...data, filename: opts.output, contentType, size: buffer.length });
262
+ return;
263
+ }
264
+ console.log(`Video ${id} — ${data.status}\nSaved to ${opts.output} (${buffer.length} bytes)`);
265
+ return;
266
+ }
267
+ // Try manual redirect
268
+ const dlRes2 = await fetch(dlUrl, {
269
+ headers: { Authorization: `Bearer ${token}` },
270
+ redirect: "manual",
271
+ });
272
+ const location = dlRes2.headers.get("location");
273
+ if (location) {
274
+ const videoRes = await fetch(location);
275
+ if (videoRes.ok) {
276
+ const { writeFile, mkdir } = await import("fs/promises");
277
+ const { dirname } = await import("path");
278
+ const buffer = Buffer.from(await videoRes.arrayBuffer());
279
+ await mkdir(dirname(opts.output), { recursive: true });
280
+ await writeFile(opts.output, buffer);
281
+ const contentType = videoRes.headers.get("content-type") || "video/mp4";
282
+ if (isJsonMode(media)) {
283
+ jsonOut({ ...data, filename: opts.output, contentType, size: buffer.length });
284
+ return;
285
+ }
286
+ console.log(`Video ${id} — ${data.status}\nSaved to ${opts.output} (${buffer.length} bytes)`);
287
+ return;
288
+ }
289
+ }
290
+ }
242
291
  if (isJsonMode(media)) {
243
292
  jsonOut(data);
244
293
  return;
@@ -256,10 +305,57 @@ export function registerMediaCommand(program) {
256
305
  });
257
306
  media
258
307
  .command("video-download <id>")
259
- .description("Get the download URL for a completed video.")
260
- .action(async (id) => {
308
+ .description("Download a completed video (or get its URL).")
309
+ .option("-o, --output <path>", "Save video to this file path")
310
+ .action(async (id, opts) => {
261
311
  const token = await getValidToken();
262
312
  const url = `${BASE_URL}/media-gen/videos/${id}/download`;
313
+ // If -o specified, download directly to file
314
+ if (opts.output) {
315
+ const res = await fetch(url, {
316
+ headers: { Authorization: `Bearer ${token}` },
317
+ redirect: "follow",
318
+ });
319
+ if (res.ok) {
320
+ const { writeFile, mkdir } = await import("fs/promises");
321
+ const { dirname } = await import("path");
322
+ const buffer = Buffer.from(await res.arrayBuffer());
323
+ await mkdir(dirname(opts.output), { recursive: true });
324
+ await writeFile(opts.output, buffer);
325
+ const contentType = res.headers.get("content-type") || "video/mp4";
326
+ if (isJsonMode(media)) {
327
+ jsonOut({ filename: opts.output, contentType, size: buffer.length });
328
+ return;
329
+ }
330
+ console.log(`Video saved to ${opts.output} (${buffer.length} bytes)`);
331
+ return;
332
+ }
333
+ // If direct follow didn't work, try manual redirect
334
+ const res2 = await fetch(url, {
335
+ headers: { Authorization: `Bearer ${token}` },
336
+ redirect: "manual",
337
+ });
338
+ const location = res2.headers.get("location");
339
+ if (location) {
340
+ const videoRes = await fetch(location);
341
+ if (videoRes.ok) {
342
+ const { writeFile, mkdir } = await import("fs/promises");
343
+ const { dirname } = await import("path");
344
+ const buffer = Buffer.from(await videoRes.arrayBuffer());
345
+ await mkdir(dirname(opts.output), { recursive: true });
346
+ await writeFile(opts.output, buffer);
347
+ const contentType = videoRes.headers.get("content-type") || "video/mp4";
348
+ if (isJsonMode(media)) {
349
+ jsonOut({ filename: opts.output, contentType, size: buffer.length });
350
+ return;
351
+ }
352
+ console.log(`Video saved to ${opts.output} (${buffer.length} bytes)`);
353
+ return;
354
+ }
355
+ }
356
+ throw new ApiError(res.status, `/media-gen/videos/${id}/download`, "Failed to download video");
357
+ }
358
+ // No -o: just return the URL (existing behavior)
263
359
  const res = await fetch(url, {
264
360
  headers: { Authorization: `Bearer ${token}` },
265
361
  redirect: "manual",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,13 +57,27 @@ Do NOT use markdown image syntax `![](...)` or `gobi://` URLs. Always use `![[me
57
57
  Single command — create and download in one step:
58
58
 
59
59
  ```bash
60
- gobi --json media video-create --name "<NAME>" --avatar-id "<AVATAR_ID>" --voice-id "<VOICE_ID>" --script "<SCRIPT>" -o media/<NAME>.mp4
60
+ gobi --json media video-create --avatar-id "<AVATAR_ID>" --voice-id "<VOICE_ID>" --script "<SCRIPT>" -o media/<NAME>.mp4
61
61
  ```
62
62
 
63
- Replace `<NAME>` with a short descriptive slug. Use `gobi media avatars` and `gobi media voices` to list available IDs.
63
+ `--name` is optional (auto-generated if omitted). Replace `<NAME>` with a short descriptive slug. Use `gobi media avatars` and `gobi media voices` to list available IDs.
64
64
 
65
65
  The `-o` flag implies `--wait` and downloads the video when done.
66
66
 
67
+ **IMPORTANT: Avatars are pre-built system avatars ONLY.** You CANNOT create custom avatars from uploaded images. The `gobi media avatars` list is the complete set of available avatars. Do NOT attempt to upload an image and use its mediaId as an avatarId — it will fail.
68
+
69
+ To use a custom image (e.g. a generated image) as the **background** of a video, upload it first via `upload-init` / `upload-finalize`, then pass the mediaId as `--background-media-id`:
70
+
71
+ ```bash
72
+ # 1. Upload custom image as background
73
+ gobi --json media upload-init --file-name "bg.png" --content-type "image/png" --file-size <SIZE>
74
+ curl -T "media/bg.png" -H "Content-Type: image/png" "<UPLOAD_URL>"
75
+ gobi --json media upload-finalize --media-id "<MEDIA_ID>"
76
+
77
+ # 2. Create video with pre-built avatar + custom background
78
+ gobi --json media video-create --avatar-id "<AVATAR_ID>" --voice-id "<VOICE_ID>" --script "<SCRIPT>" --background-media-id "<MEDIA_ID>" -o media/<NAME>.mp4
79
+ ```
80
+
67
81
  **IMPORTANT: After downloading, show the video using Obsidian wiki-link syntax EXACTLY like this:**
68
82
 
69
83
  ```
@@ -90,7 +104,7 @@ Do NOT use markdown image/link syntax `![](...)` or `gobi://` URLs. Always use `
90
104
  - `gobi media video-list` — List all videos.
91
105
  - `gobi media video-get` — Get video metadata.
92
106
  - `gobi media video-status` — Poll video generation status.
93
- - `gobi media video-download` — Get the download URL for a completed video.
107
+ - `gobi media video-download` — Download a completed video (`-o` to save to file).
94
108
 
95
109
  ### Images
96
110
 
@@ -17,7 +17,7 @@ Commands:
17
17
  video-list List all videos.
18
18
  video-get <id> Get video metadata.
19
19
  video-status [options] <id> Poll video generation status.
20
- video-download <id> Get the download URL for a completed video.
20
+ video-download [options] <id> Download a completed video (or get its URL).
21
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
22
  image-edit [options] Edit an existing image with a prompt (image-to-image).
23
23
  image-inpaint [options] Inpaint an image region using a mask.
@@ -82,12 +82,13 @@ Usage: gobi media video-create [options]
82
82
  Create an avatar video generation job.
83
83
 
84
84
  Options:
85
- --name <name> Name for the video
85
+ --name <name> Name for the video (auto-generated if omitted)
86
86
  --avatar-id <avatarId> Avatar to use
87
87
  --voice-id <voiceId> Voice to use
88
88
  --script <script> Script for the avatar to read
89
89
  --background-media-id <backgroundMediaId> Background media ID (from upload)
90
90
  --wait Poll until generation completes
91
+ -o, --output <path> Download video to this path when done (implies --wait)
91
92
  -h, --help display help for command
92
93
  ```
93
94
 
@@ -121,8 +122,9 @@ Usage: gobi media video-status [options] <id>
121
122
  Poll video generation status.
122
123
 
123
124
  Options:
124
- --wait Poll until a terminal state is reached
125
- -h, --help display help for command
125
+ --wait Poll until a terminal state is reached
126
+ -o, --output <path> Download video to this path when complete (implies --wait)
127
+ -h, --help display help for command
126
128
  ```
127
129
 
128
130
  ## video-download
@@ -130,10 +132,11 @@ Options:
130
132
  ```
131
133
  Usage: gobi media video-download [options] <id>
132
134
 
133
- Get the download URL for a completed video.
135
+ Download a completed video (or get its URL).
134
136
 
135
137
  Options:
136
- -h, --help display help for command
138
+ -o, --output <path> Save video to this file path
139
+ -h, --help display help for command
137
140
  ```
138
141
 
139
142
  ## image-generate