@gobi-ai/cli 0.9.5 → 0.9.6

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.4",
7
+ "version": "0.9.5",
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.4",
12
+ "version": "0.9.5",
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.4",
4
+ "version": "0.9.5",
5
5
  "author": {
6
6
  "name": "gobi-ai"
7
7
  },
@@ -122,7 +122,9 @@ export function registerMediaCommand(program) {
122
122
  .requiredOption("--script <script>", "Script for the avatar to read")
123
123
  .option("--background-media-id <backgroundMediaId>", "Background media ID (from upload)")
124
124
  .option("--wait", "Poll until generation completes")
125
+ .option("-o, --output <path>", "Download video to this path when done (implies --wait)")
125
126
  .action(async (opts) => {
127
+ const shouldWait = opts.wait || !!opts.output;
126
128
  const body = {
127
129
  name: opts.name,
128
130
  avatarId: opts.avatarId,
@@ -134,10 +136,56 @@ export function registerMediaCommand(program) {
134
136
  const resp = (await apiPost("/media-gen/videos", body));
135
137
  let data = unwrapResp(resp);
136
138
  const videoId = data.id || data.videoId;
137
- if (opts.wait && videoId) {
139
+ if (shouldWait && videoId) {
138
140
  console.log(`Video ${videoId} queued — polling for completion…`);
139
141
  data = await pollStatus(`/media-gen/videos/${videoId}/status`, ["inference_complete", "inference_failed"]);
140
142
  }
143
+ // Download video to file if -o specified
144
+ if (opts.output && videoId && data.status === "inference_complete") {
145
+ const token = await getValidToken();
146
+ const dlUrl = `${BASE_URL}/media-gen/videos/${videoId}/download`;
147
+ const dlRes = await fetch(dlUrl, {
148
+ headers: { Authorization: `Bearer ${token}` },
149
+ redirect: "follow",
150
+ });
151
+ if (dlRes.ok) {
152
+ const { writeFile, mkdir } = await import("fs/promises");
153
+ const { dirname } = await import("path");
154
+ const buffer = Buffer.from(await dlRes.arrayBuffer());
155
+ await mkdir(dirname(opts.output), { recursive: true });
156
+ await writeFile(opts.output, buffer);
157
+ const contentType = dlRes.headers.get("content-type") || "video/mp4";
158
+ if (isJsonMode(media)) {
159
+ jsonOut({ ...data, filename: opts.output, contentType, size: buffer.length });
160
+ return;
161
+ }
162
+ console.log(`Video saved to ${opts.output} (${buffer.length} bytes)`);
163
+ return;
164
+ }
165
+ // If direct download fails, try getting the URL and fetching that
166
+ const dlRes2 = await fetch(dlUrl, {
167
+ headers: { Authorization: `Bearer ${token}` },
168
+ redirect: "manual",
169
+ });
170
+ const location = dlRes2.headers.get("location");
171
+ if (location) {
172
+ const videoRes = await fetch(location);
173
+ if (videoRes.ok) {
174
+ const { writeFile, mkdir } = await import("fs/promises");
175
+ const { dirname } = await import("path");
176
+ const buffer = Buffer.from(await videoRes.arrayBuffer());
177
+ await mkdir(dirname(opts.output), { recursive: true });
178
+ await writeFile(opts.output, buffer);
179
+ const contentType = videoRes.headers.get("content-type") || "video/mp4";
180
+ if (isJsonMode(media)) {
181
+ jsonOut({ ...data, filename: opts.output, contentType, size: buffer.length });
182
+ return;
183
+ }
184
+ console.log(`Video saved to ${opts.output} (${buffer.length} bytes)`);
185
+ return;
186
+ }
187
+ }
188
+ }
141
189
  if (isJsonMode(media)) {
142
190
  jsonOut(data);
143
191
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,6 +52,26 @@ Do NOT use markdown image syntax `![](...)` or `gobi://` URLs. Always use `![[me
52
52
  - `image-download` takes a **positional** jobId (NOT `--job-id`): `gobi media image-download <jobId>`
53
53
  - The `jobId` (or `id`) field is what you pass to `image-download` / `image-status` — NOT `mediaId`.
54
54
 
55
+ ## Typical Workflow (Video Generation)
56
+
57
+ Single command — create and download in one step:
58
+
59
+ ```bash
60
+ gobi --json media video-create --name "<NAME>" --avatar-id "<AVATAR_ID>" --voice-id "<VOICE_ID>" --script "<SCRIPT>" -o media/<NAME>.mp4
61
+ ```
62
+
63
+ Replace `<NAME>` with a short descriptive slug. Use `gobi media avatars` and `gobi media voices` to list available IDs.
64
+
65
+ The `-o` flag implies `--wait` and downloads the video when done.
66
+
67
+ **IMPORTANT: After downloading, show the video using Obsidian wiki-link syntax EXACTLY like this:**
68
+
69
+ ```
70
+ ![[media/<NAME>.mp4]]
71
+ ```
72
+
73
+ Do NOT use markdown image/link syntax `![](...)` or `gobi://` URLs. Always use `![[media/<NAME>.mp4]]`.
74
+
55
75
  ## Available Commands
56
76
 
57
77
  ### Upload