@gobi-ai/cli 0.7.3 → 0.8.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/dist/commands/media.js +379 -0
- package/dist/main.js +2 -0
- package/package.json +1 -1
- package/skills/{gobi → gobi-cli}/SKILL.md +25 -1
- package/skills/gobi-cli/references/media.md +215 -0
- package/skills/{gobi-dev-homepage → gobi-homepage}/SKILL.md +8 -8
- /package/skills/{gobi → gobi-cli}/SKILL.template.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/auth.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/brain.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/init.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/sense.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/session.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/space.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/sync.md +0 -0
- /package/skills/{gobi → gobi-cli}/references/update.md +0 -0
- /package/skills/{gobi → gobi-cli}/scripts/generate-docs.ts +0 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { apiGet, apiPost } from "../client.js";
|
|
2
|
+
import { BASE_URL, POLL_MAX_DURATION_MS } from "../constants.js";
|
|
3
|
+
import { getValidToken } from "../auth/manager.js";
|
|
4
|
+
import { ApiError } from "../errors.js";
|
|
5
|
+
import { isJsonMode, jsonOut, unwrapResp } from "./utils.js";
|
|
6
|
+
// ── Polling helper ──
|
|
7
|
+
async function pollStatus(path, terminalStates, intervalMs = 3000) {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
while (Date.now() - start < POLL_MAX_DURATION_MS) {
|
|
10
|
+
const resp = (await apiGet(path));
|
|
11
|
+
const data = unwrapResp(resp);
|
|
12
|
+
const status = data.status || "";
|
|
13
|
+
if (terminalStates.includes(status))
|
|
14
|
+
return data;
|
|
15
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`Polling timed out after ${POLL_MAX_DURATION_MS / 1000}s`);
|
|
18
|
+
}
|
|
19
|
+
export function registerMediaCommand(program) {
|
|
20
|
+
const media = program
|
|
21
|
+
.command("media")
|
|
22
|
+
.description("Media generation commands (videos, images).");
|
|
23
|
+
// ════════════════════════════════════════════════════════════════════
|
|
24
|
+
// Upload
|
|
25
|
+
// ════════════════════════════════════════════════════════════════════
|
|
26
|
+
media
|
|
27
|
+
.command("upload-init")
|
|
28
|
+
.description("Get a presigned upload URL for a media file.")
|
|
29
|
+
.requiredOption("--file-name <fileName>", "Name of the file to upload")
|
|
30
|
+
.requiredOption("--content-type <contentType>", "MIME type (e.g. image/png, video/mp4)")
|
|
31
|
+
.option("--file-size <fileSize>", "File size in bytes")
|
|
32
|
+
.action(async (opts) => {
|
|
33
|
+
const body = {
|
|
34
|
+
fileName: opts.fileName,
|
|
35
|
+
contentType: opts.contentType,
|
|
36
|
+
};
|
|
37
|
+
if (opts.fileSize)
|
|
38
|
+
body.fileSize = parseInt(opts.fileSize, 10);
|
|
39
|
+
const resp = (await apiPost("/media-gen/media/initialize", body));
|
|
40
|
+
const data = unwrapResp(resp);
|
|
41
|
+
if (isJsonMode(media)) {
|
|
42
|
+
jsonOut(data);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(`Upload initialized!\n` +
|
|
46
|
+
` Media ID: ${data.mediaId}\n` +
|
|
47
|
+
` Upload URL: ${data.uploadUrl}\n\n` +
|
|
48
|
+
`PUT your file to the upload URL, then run:\n` +
|
|
49
|
+
` gobi media upload-finalize --media-id ${data.mediaId}`);
|
|
50
|
+
});
|
|
51
|
+
media
|
|
52
|
+
.command("upload-finalize")
|
|
53
|
+
.description("Confirm that a media upload is complete.")
|
|
54
|
+
.requiredOption("--media-id <mediaId>", "Media ID from upload-init")
|
|
55
|
+
.action(async (opts) => {
|
|
56
|
+
const resp = (await apiPost("/media-gen/media/finalize", {
|
|
57
|
+
mediaId: opts.mediaId,
|
|
58
|
+
}));
|
|
59
|
+
const data = unwrapResp(resp);
|
|
60
|
+
if (isJsonMode(media)) {
|
|
61
|
+
jsonOut(data);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log(`Upload finalized for media ${opts.mediaId}.`);
|
|
65
|
+
});
|
|
66
|
+
// ════════════════════════════════════════════════════════════════════
|
|
67
|
+
// Avatars & Voices
|
|
68
|
+
// ════════════════════════════════════════════════════════════════════
|
|
69
|
+
media
|
|
70
|
+
.command("avatars")
|
|
71
|
+
.description("List available avatars.")
|
|
72
|
+
.action(async () => {
|
|
73
|
+
const resp = (await apiGet("/media-gen/avatars"));
|
|
74
|
+
const data = unwrapResp(resp);
|
|
75
|
+
if (isJsonMode(media)) {
|
|
76
|
+
jsonOut(data);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
80
|
+
console.log("No avatars available.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log("Available avatars:");
|
|
84
|
+
for (const a of data) {
|
|
85
|
+
console.log(` - ${a.id || a.avatarId}: ${a.name || "(unnamed)"}`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
media
|
|
89
|
+
.command("voices")
|
|
90
|
+
.description("List available voices.")
|
|
91
|
+
.action(async () => {
|
|
92
|
+
const resp = (await apiGet("/media-gen/voices"));
|
|
93
|
+
const data = unwrapResp(resp);
|
|
94
|
+
if (isJsonMode(media)) {
|
|
95
|
+
jsonOut(data);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
99
|
+
console.log("No voices available.");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
console.log("Available voices:");
|
|
103
|
+
for (const v of data) {
|
|
104
|
+
console.log(` - ${v.id || v.voiceId}: ${v.name || "(unnamed)"}`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// ════════════════════════════════════════════════════════════════════
|
|
108
|
+
// Videos
|
|
109
|
+
// ════════════════════════════════════════════════════════════════════
|
|
110
|
+
media
|
|
111
|
+
.command("video-create")
|
|
112
|
+
.description("Create an avatar video generation job.")
|
|
113
|
+
.requiredOption("--name <name>", "Name for the video")
|
|
114
|
+
.requiredOption("--avatar-id <avatarId>", "Avatar to use")
|
|
115
|
+
.requiredOption("--voice-id <voiceId>", "Voice to use")
|
|
116
|
+
.requiredOption("--script <script>", "Script for the avatar to read")
|
|
117
|
+
.option("--background-media-id <backgroundMediaId>", "Background media ID (from upload)")
|
|
118
|
+
.option("--wait", "Poll until generation completes")
|
|
119
|
+
.action(async (opts) => {
|
|
120
|
+
const body = {
|
|
121
|
+
name: opts.name,
|
|
122
|
+
avatarId: opts.avatarId,
|
|
123
|
+
voiceId: opts.voiceId,
|
|
124
|
+
script: opts.script,
|
|
125
|
+
};
|
|
126
|
+
if (opts.backgroundMediaId)
|
|
127
|
+
body.backgroundMediaId = opts.backgroundMediaId;
|
|
128
|
+
const resp = (await apiPost("/media-gen/videos", body));
|
|
129
|
+
let data = unwrapResp(resp);
|
|
130
|
+
const videoId = data.id || data.videoId;
|
|
131
|
+
if (opts.wait && videoId) {
|
|
132
|
+
console.log(`Video ${videoId} queued — polling for completion…`);
|
|
133
|
+
data = await pollStatus(`/media-gen/videos/${videoId}/status`, ["inference_complete", "inference_failed"]);
|
|
134
|
+
}
|
|
135
|
+
if (isJsonMode(media)) {
|
|
136
|
+
jsonOut(data);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const status = data.status || "queued";
|
|
140
|
+
console.log(`Video created!\n` +
|
|
141
|
+
` ID: ${videoId}\n` +
|
|
142
|
+
` Status: ${status}`);
|
|
143
|
+
if (status === "inference_complete") {
|
|
144
|
+
console.log(` Download: gobi media video-download ${videoId}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
media
|
|
148
|
+
.command("video-list")
|
|
149
|
+
.description("List all videos.")
|
|
150
|
+
.action(async () => {
|
|
151
|
+
const resp = (await apiGet("/media-gen/videos"));
|
|
152
|
+
const data = unwrapResp(resp);
|
|
153
|
+
if (isJsonMode(media)) {
|
|
154
|
+
jsonOut(data);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
158
|
+
console.log("No videos found.");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
console.log("Videos:");
|
|
162
|
+
for (const v of data) {
|
|
163
|
+
console.log(` - [${v.id}] status: ${v.status || "unknown"}, created: ${v.createdAt || "?"}`);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
media
|
|
167
|
+
.command("video-get <id>")
|
|
168
|
+
.description("Get video metadata.")
|
|
169
|
+
.action(async (id) => {
|
|
170
|
+
const resp = (await apiGet(`/media-gen/videos/${id}`));
|
|
171
|
+
const data = unwrapResp(resp);
|
|
172
|
+
if (isJsonMode(media)) {
|
|
173
|
+
jsonOut(data);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
console.log(`Video ${id}:`);
|
|
177
|
+
for (const [k, v] of Object.entries(data)) {
|
|
178
|
+
console.log(` ${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
media
|
|
182
|
+
.command("video-status <id>")
|
|
183
|
+
.description("Poll video generation status.")
|
|
184
|
+
.option("--wait", "Poll until a terminal state is reached")
|
|
185
|
+
.action(async (id, opts) => {
|
|
186
|
+
if (opts.wait) {
|
|
187
|
+
const data = await pollStatus(`/media-gen/videos/${id}/status`, ["inference_complete", "inference_failed"]);
|
|
188
|
+
if (isJsonMode(media)) {
|
|
189
|
+
jsonOut(data);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
console.log(`Video ${id} — status: ${data.status}`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const resp = (await apiGet(`/media-gen/videos/${id}/status`));
|
|
196
|
+
const data = unwrapResp(resp);
|
|
197
|
+
if (isJsonMode(media)) {
|
|
198
|
+
jsonOut(data);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
console.log(`Video ${id} — status: ${data.status || "unknown"}`);
|
|
202
|
+
});
|
|
203
|
+
media
|
|
204
|
+
.command("video-download <id>")
|
|
205
|
+
.description("Get the download URL for a completed video.")
|
|
206
|
+
.action(async (id) => {
|
|
207
|
+
const token = await getValidToken();
|
|
208
|
+
const url = `${BASE_URL}/media-gen/videos/${id}/download`;
|
|
209
|
+
const res = await fetch(url, {
|
|
210
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
211
|
+
redirect: "manual",
|
|
212
|
+
});
|
|
213
|
+
// If the server redirects, extract the Location header
|
|
214
|
+
if (res.status >= 300 && res.status < 400) {
|
|
215
|
+
const location = res.headers.get("location") || "";
|
|
216
|
+
if (isJsonMode(media)) {
|
|
217
|
+
jsonOut({ downloadUrl: location });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
console.log(`Download URL for video ${id}:\n ${location}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!res.ok) {
|
|
224
|
+
const text = (await res.text()) || "(no body)";
|
|
225
|
+
throw new ApiError(res.status, `/media-gen/videos/${id}/download`, text);
|
|
226
|
+
}
|
|
227
|
+
// If it returns JSON instead of a redirect
|
|
228
|
+
const resp = (await res.json());
|
|
229
|
+
const data = unwrapResp(resp);
|
|
230
|
+
if (isJsonMode(media)) {
|
|
231
|
+
jsonOut(data);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
console.log(`Download URL for video ${id}:\n ${data.url || data.downloadUrl || JSON.stringify(data)}`);
|
|
235
|
+
});
|
|
236
|
+
// ════════════════════════════════════════════════════════════════════
|
|
237
|
+
// Images
|
|
238
|
+
// ════════════════════════════════════════════════════════════════════
|
|
239
|
+
media
|
|
240
|
+
.command("image-generate")
|
|
241
|
+
.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
|
+
.requiredOption("--prompt <prompt>", "Text prompt for image generation")
|
|
243
|
+
.requiredOption("--name <name>", "Name for the generated image")
|
|
244
|
+
.option("--type <type>", "Generation type: image (default), thumbnail (YouTube-optimized), asset (logo/product)")
|
|
245
|
+
.option("--aspect-ratio <aspectRatio>", "Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)")
|
|
246
|
+
.option("--negative-prompt <negativePrompt>", "Negative prompt")
|
|
247
|
+
.option("--seed <seed>", "Random seed for reproducibility")
|
|
248
|
+
.option("--reference-media-id <referenceMediaId>", "Reference image media ID")
|
|
249
|
+
.option("--wait", "Poll until generation completes")
|
|
250
|
+
.action(async (opts) => {
|
|
251
|
+
const body = {
|
|
252
|
+
prompt: opts.prompt,
|
|
253
|
+
name: opts.name,
|
|
254
|
+
};
|
|
255
|
+
if (opts.type)
|
|
256
|
+
body.type = opts.type;
|
|
257
|
+
if (opts.aspectRatio)
|
|
258
|
+
body.aspectRatio = opts.aspectRatio;
|
|
259
|
+
if (opts.negativePrompt)
|
|
260
|
+
body.negativePrompt = opts.negativePrompt;
|
|
261
|
+
if (opts.seed)
|
|
262
|
+
body.seed = parseInt(opts.seed, 10);
|
|
263
|
+
if (opts.referenceMediaId)
|
|
264
|
+
body.referenceMediaId = opts.referenceMediaId;
|
|
265
|
+
const resp = (await apiPost("/media-gen/images/generate", body));
|
|
266
|
+
let data = unwrapResp(resp);
|
|
267
|
+
const jobId = data.jobId || data.id;
|
|
268
|
+
if (opts.wait && jobId) {
|
|
269
|
+
console.log(`Image job ${jobId} queued — polling for completion…`);
|
|
270
|
+
data = await pollStatus(`/media-gen/images/${jobId}`, [
|
|
271
|
+
"completed",
|
|
272
|
+
"failed",
|
|
273
|
+
"inference_complete",
|
|
274
|
+
"inference_failed",
|
|
275
|
+
]);
|
|
276
|
+
}
|
|
277
|
+
if (isJsonMode(media)) {
|
|
278
|
+
jsonOut(data);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
console.log(`Image generation started!\n` +
|
|
282
|
+
` Job ID: ${jobId}\n` +
|
|
283
|
+
` Status: ${data.status || "queued"}\n` +
|
|
284
|
+
` Check: gobi media image-status ${jobId}`);
|
|
285
|
+
});
|
|
286
|
+
media
|
|
287
|
+
.command("image-edit")
|
|
288
|
+
.description("Edit an existing image with a prompt (image-to-image).")
|
|
289
|
+
.requiredOption("--media-id <mediaId>", "Source image media ID")
|
|
290
|
+
.requiredOption("--prompt <prompt>", "Edit instruction")
|
|
291
|
+
.requiredOption("--name <name>", "Name for the edited image")
|
|
292
|
+
.option("--wait", "Poll until generation completes")
|
|
293
|
+
.action(async (opts) => {
|
|
294
|
+
const resp = (await apiPost("/media-gen/images/edit", {
|
|
295
|
+
mediaId: opts.mediaId,
|
|
296
|
+
prompt: opts.prompt,
|
|
297
|
+
name: opts.name,
|
|
298
|
+
}));
|
|
299
|
+
let data = unwrapResp(resp);
|
|
300
|
+
const jobId = data.jobId || data.id;
|
|
301
|
+
if (opts.wait && jobId) {
|
|
302
|
+
console.log(`Image edit job ${jobId} — polling for completion…`);
|
|
303
|
+
data = await pollStatus(`/media-gen/images/${jobId}`, [
|
|
304
|
+
"completed",
|
|
305
|
+
"failed",
|
|
306
|
+
"inference_complete",
|
|
307
|
+
"inference_failed",
|
|
308
|
+
]);
|
|
309
|
+
}
|
|
310
|
+
if (isJsonMode(media)) {
|
|
311
|
+
jsonOut(data);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
console.log(`Image edit started!\n` +
|
|
315
|
+
` Job ID: ${jobId}\n` +
|
|
316
|
+
` Status: ${data.status || "queued"}`);
|
|
317
|
+
});
|
|
318
|
+
media
|
|
319
|
+
.command("image-inpaint")
|
|
320
|
+
.description("Inpaint an image region using a mask.")
|
|
321
|
+
.requiredOption("--media-id <mediaId>", "Source image media ID")
|
|
322
|
+
.requiredOption("--mask-media-id <maskMediaId>", "Mask image media ID")
|
|
323
|
+
.requiredOption("--prompt <prompt>", "Inpainting prompt")
|
|
324
|
+
.requiredOption("--name <name>", "Name for the inpainted image")
|
|
325
|
+
.option("--wait", "Poll until generation completes")
|
|
326
|
+
.action(async (opts) => {
|
|
327
|
+
const resp = (await apiPost("/media-gen/images/inpaint", {
|
|
328
|
+
mediaId: opts.mediaId,
|
|
329
|
+
maskMediaId: opts.maskMediaId,
|
|
330
|
+
prompt: opts.prompt,
|
|
331
|
+
name: opts.name,
|
|
332
|
+
}));
|
|
333
|
+
let data = unwrapResp(resp);
|
|
334
|
+
const jobId = data.jobId || data.id;
|
|
335
|
+
if (opts.wait && jobId) {
|
|
336
|
+
console.log(`Inpaint job ${jobId} — polling for completion…`);
|
|
337
|
+
data = await pollStatus(`/media-gen/images/${jobId}`, [
|
|
338
|
+
"completed",
|
|
339
|
+
"failed",
|
|
340
|
+
"inference_complete",
|
|
341
|
+
"inference_failed",
|
|
342
|
+
]);
|
|
343
|
+
}
|
|
344
|
+
if (isJsonMode(media)) {
|
|
345
|
+
jsonOut(data);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
console.log(`Inpainting started!\n` +
|
|
349
|
+
` Job ID: ${jobId}\n` +
|
|
350
|
+
` Status: ${data.status || "queued"}`);
|
|
351
|
+
});
|
|
352
|
+
media
|
|
353
|
+
.command("image-status <jobId>")
|
|
354
|
+
.description("Check image generation job status.")
|
|
355
|
+
.option("--wait", "Poll until a terminal state is reached")
|
|
356
|
+
.action(async (jobId, opts) => {
|
|
357
|
+
if (opts.wait) {
|
|
358
|
+
const data = await pollStatus(`/media-gen/images/${jobId}`, [
|
|
359
|
+
"completed",
|
|
360
|
+
"failed",
|
|
361
|
+
"inference_complete",
|
|
362
|
+
"inference_failed",
|
|
363
|
+
]);
|
|
364
|
+
if (isJsonMode(media)) {
|
|
365
|
+
jsonOut(data);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
console.log(`Image job ${jobId} — status: ${data.status}`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const resp = (await apiGet(`/media-gen/images/${jobId}`));
|
|
372
|
+
const data = unwrapResp(resp);
|
|
373
|
+
if (isJsonMode(media)) {
|
|
374
|
+
jsonOut(data);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
console.log(`Image job ${jobId} — status: ${data.status || "unknown"}`);
|
|
378
|
+
});
|
|
379
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerSessionsCommand } from "./commands/sessions.js";
|
|
|
10
10
|
import { registerSenseCommand } from "./commands/sense.js";
|
|
11
11
|
import { registerSyncCommand } from "./commands/sync.js";
|
|
12
12
|
import { registerUpdateCommand } from "./commands/update.js";
|
|
13
|
+
import { registerMediaCommand } from "./commands/media.js";
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
14
15
|
const { version } = require("../package.json");
|
|
15
16
|
const SKIP_BANNER_COMMANDS = new Set(["auth", "init", "update"]);
|
|
@@ -36,6 +37,7 @@ export async function cli() {
|
|
|
36
37
|
registerSenseCommand(program);
|
|
37
38
|
registerSyncCommand(program);
|
|
38
39
|
registerUpdateCommand(program);
|
|
40
|
+
registerMediaCommand(program);
|
|
39
41
|
// Propagate helpWidth to all subcommands
|
|
40
42
|
const helpWidth = process.stdout.columns || 200;
|
|
41
43
|
for (const cmd of program.commands) {
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ description: >-
|
|
|
6
6
|
the outside world — checking what's happening, reading and writing threads,
|
|
7
7
|
and collaborating with others.
|
|
8
8
|
Use when the user wants to interact with Gobi spaces, vaults, brains, threads,
|
|
9
|
-
sessions, or
|
|
9
|
+
sessions, brain updates, or media generation (images, videos, thumbnails).
|
|
10
10
|
allowed-tools: Bash(gobi:*)
|
|
11
11
|
metadata:
|
|
12
12
|
author: gobi-ai
|
|
@@ -100,6 +100,14 @@ gobi auth status
|
|
|
100
100
|
|
|
101
101
|
`gobi brain` commands manage your vault's brain: search across all spaces, ask brains questions, and publish/unpublish your BRAIN.md. Public brains are accessible at `https://gobispace.com/@{vaultSlug}`.
|
|
102
102
|
|
|
103
|
+
## Gobi Media — Image & Video Generation
|
|
104
|
+
|
|
105
|
+
`gobi media` commands generate images, thumbnails, assets, and avatar videos using AI. All generation is async: create a job, poll for status (or use `--wait`), then retrieve the result.
|
|
106
|
+
|
|
107
|
+
- **Images**: Generate from text prompts, edit existing images, or inpaint with masks. Supports types: `image` (default), `thumbnail` (YouTube-optimized), `asset` (logo/product).
|
|
108
|
+
- **Videos**: Create avatar videos with script narration. Choose an avatar and voice, provide a script, and generate a talking-head video.
|
|
109
|
+
- **Upload**: Upload custom media files (backgrounds, references, masks) via presigned S3 URLs for use in generation.
|
|
110
|
+
|
|
103
111
|
## Gobi Session — Conversations
|
|
104
112
|
|
|
105
113
|
`gobi session` commands manage your conversations: list, read, and reply to sessions.
|
|
@@ -169,6 +177,20 @@ Note: `--space-slug` is not available on other `brain` subcommands or on `sessio
|
|
|
169
177
|
- `gobi session get` — Get a session and its messages (paginated).
|
|
170
178
|
- `gobi session list` — List all sessions you are part of, sorted by most recent activity.
|
|
171
179
|
- `gobi session reply` — Send a human reply to a session you are a member of.
|
|
180
|
+
- `gobi media` — Media generation commands (images, videos).
|
|
181
|
+
- `gobi media upload-init` — Get a presigned upload URL for a media file. Requires `--file-name`, `--content-type`, `--file-size`.
|
|
182
|
+
- `gobi media upload-finalize` — Confirm that a media upload is complete.
|
|
183
|
+
- `gobi media avatars` — List available avatars.
|
|
184
|
+
- `gobi media voices` — List available voices.
|
|
185
|
+
- `gobi media video-create` — Create an avatar video generation job. Requires `--name`, `--avatar-id`, `--voice-id`, `--script`.
|
|
186
|
+
- `gobi media video-list` — List all videos.
|
|
187
|
+
- `gobi media video-get <id>` — Get video metadata.
|
|
188
|
+
- `gobi media video-status <id>` — Poll video generation status. Use `--wait` to block until complete.
|
|
189
|
+
- `gobi media video-download <id>` — Get the download URL for a completed video.
|
|
190
|
+
- `gobi media image-generate` — Generate an image from a text prompt. Requires `--prompt`, `--name`. Optional: `--type` (image|thumbnail|asset), `--aspect-ratio`, `--negative-prompt`, `--seed`, `--reference-media-id`. Use `--wait` to block until complete.
|
|
191
|
+
- `gobi media image-edit` — Edit an existing image with a prompt. Requires `--media-id`, `--prompt`, `--name`.
|
|
192
|
+
- `gobi media image-inpaint` — Inpaint an image region using a mask. Requires `--media-id`, `--mask-media-id`, `--prompt`, `--name`.
|
|
193
|
+
- `gobi media image-status <jobId>` — Check image generation job status. Use `--wait` to block until complete.
|
|
172
194
|
- `gobi sense` — Sense commands (activities, transcriptions).
|
|
173
195
|
- `gobi sense activities` — Fetch activity records within a time range.
|
|
174
196
|
- `gobi sense transcriptions` — Fetch transcription records within a time range.
|
|
@@ -182,6 +204,7 @@ Note: `--space-slug` is not available on other `brain` subcommands or on `sessio
|
|
|
182
204
|
- [gobi space](references/space.md)
|
|
183
205
|
- [gobi brain](references/brain.md)
|
|
184
206
|
- [gobi session](references/session.md)
|
|
207
|
+
- [gobi media](references/media.md)
|
|
185
208
|
- [gobi sense](references/sense.md)
|
|
186
209
|
- [gobi sync](references/sync.md)
|
|
187
210
|
- [gobi update](references/update.md)
|
|
@@ -196,6 +219,7 @@ gobi auth --help
|
|
|
196
219
|
gobi space --help
|
|
197
220
|
gobi brain --help
|
|
198
221
|
gobi session --help
|
|
222
|
+
gobi media --help
|
|
199
223
|
gobi sense --help
|
|
200
224
|
gobi sync --help
|
|
201
225
|
```
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# gobi media
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
Usage: gobi media [options] [command]
|
|
5
|
+
|
|
6
|
+
Media generation commands (videos, images).
|
|
7
|
+
|
|
8
|
+
Options:
|
|
9
|
+
-h, --help display help for command
|
|
10
|
+
|
|
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
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Async Workflow
|
|
29
|
+
|
|
30
|
+
Video and image generation are async:
|
|
31
|
+
1. Call the create/generate endpoint — receive a job ID
|
|
32
|
+
2. Poll with `--wait` or manually check status until terminal state
|
|
33
|
+
3. Retrieve or download the result
|
|
34
|
+
|
|
35
|
+
## Media Upload
|
|
36
|
+
|
|
37
|
+
Upload media files (images, videos) for use as backgrounds, references, or masks:
|
|
38
|
+
|
|
39
|
+
1. `gobi media upload-init` — get a presigned S3 upload URL
|
|
40
|
+
2. PUT your file to the returned `uploadUrl`
|
|
41
|
+
3. `gobi media upload-finalize` — confirm the upload
|
|
42
|
+
|
|
43
|
+
The returned `mediaId` can then be used with `--background-media-id`, `--reference-media-id`, `--media-id`, or `--mask-media-id`.
|
|
44
|
+
|
|
45
|
+
## upload-init
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Usage: gobi media upload-init [options]
|
|
49
|
+
|
|
50
|
+
Get a presigned upload URL for a media file.
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
--file-name <fileName> Name of the file to upload
|
|
54
|
+
--content-type <contentType> MIME type (e.g. image/png, video/mp4)
|
|
55
|
+
--file-size <fileSize> File size in bytes
|
|
56
|
+
-h, --help display help for command
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## upload-finalize
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Usage: gobi media upload-finalize [options]
|
|
63
|
+
|
|
64
|
+
Confirm that a media upload is complete.
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
--media-id <mediaId> Media ID from upload-init
|
|
68
|
+
-h, --help display help for command
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## avatars
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Usage: gobi media avatars [options]
|
|
75
|
+
|
|
76
|
+
List available avatars.
|
|
77
|
+
|
|
78
|
+
Options:
|
|
79
|
+
-h, --help display help for command
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## voices
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Usage: gobi media voices [options]
|
|
86
|
+
|
|
87
|
+
List available voices.
|
|
88
|
+
|
|
89
|
+
Options:
|
|
90
|
+
-h, --help display help for command
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## video-create
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Usage: gobi media video-create [options]
|
|
97
|
+
|
|
98
|
+
Create an avatar video generation job.
|
|
99
|
+
|
|
100
|
+
Options:
|
|
101
|
+
--name <name> Name for the video
|
|
102
|
+
--avatar-id <avatarId> Avatar to use
|
|
103
|
+
--voice-id <voiceId> Voice to use
|
|
104
|
+
--script <script> Script for the avatar to read
|
|
105
|
+
--background-media-id <backgroundMediaId> Background media ID (from upload)
|
|
106
|
+
--wait Poll until generation completes
|
|
107
|
+
-h, --help display help for command
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## video-list
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
Usage: gobi media video-list [options]
|
|
114
|
+
|
|
115
|
+
List all videos.
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
-h, --help display help for command
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## video-get
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
Usage: gobi media video-get [options] <id>
|
|
125
|
+
|
|
126
|
+
Get video metadata.
|
|
127
|
+
|
|
128
|
+
Options:
|
|
129
|
+
-h, --help display help for command
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## video-status
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Usage: gobi media video-status [options] <id>
|
|
136
|
+
|
|
137
|
+
Poll video generation status.
|
|
138
|
+
|
|
139
|
+
Options:
|
|
140
|
+
--wait Poll until a terminal state is reached
|
|
141
|
+
-h, --help display help for command
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## video-download
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
Usage: gobi media video-download [options] <id>
|
|
148
|
+
|
|
149
|
+
Get the download URL for a completed video.
|
|
150
|
+
|
|
151
|
+
Options:
|
|
152
|
+
-h, --help display help for command
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## image-generate
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
Usage: gobi media image-generate [options]
|
|
159
|
+
|
|
160
|
+
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
|
|
161
|
+
|
|
162
|
+
Options:
|
|
163
|
+
--prompt <prompt> Text prompt for image generation
|
|
164
|
+
--name <name> Name for the generated image
|
|
165
|
+
--type <type> Generation type: image (default), thumbnail (YouTube-optimized), asset (logo/product)
|
|
166
|
+
--aspect-ratio <aspectRatio> Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)
|
|
167
|
+
--negative-prompt <negativePrompt> Negative prompt
|
|
168
|
+
--seed <seed> Random seed for reproducibility
|
|
169
|
+
--reference-media-id <referenceMediaId> Reference image media ID
|
|
170
|
+
--wait Poll until generation completes
|
|
171
|
+
-h, --help display help for command
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## image-edit
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Usage: gobi media image-edit [options]
|
|
178
|
+
|
|
179
|
+
Edit an existing image with a prompt (image-to-image).
|
|
180
|
+
|
|
181
|
+
Options:
|
|
182
|
+
--media-id <mediaId> Source image media ID
|
|
183
|
+
--prompt <prompt> Edit instruction
|
|
184
|
+
--name <name> Name for the edited image
|
|
185
|
+
--wait Poll until generation completes
|
|
186
|
+
-h, --help display help for command
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## image-inpaint
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
Usage: gobi media image-inpaint [options]
|
|
193
|
+
|
|
194
|
+
Inpaint an image region using a mask.
|
|
195
|
+
|
|
196
|
+
Options:
|
|
197
|
+
--media-id <mediaId> Source image media ID
|
|
198
|
+
--mask-media-id <maskMediaId> Mask image media ID
|
|
199
|
+
--prompt <prompt> Inpainting prompt
|
|
200
|
+
--name <name> Name for the inpainted image
|
|
201
|
+
--wait Poll until generation completes
|
|
202
|
+
-h, --help display help for command
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## image-status
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
Usage: gobi media image-status [options] <jobId>
|
|
209
|
+
|
|
210
|
+
Check image generation job status.
|
|
211
|
+
|
|
212
|
+
Options:
|
|
213
|
+
--wait Poll until a terminal state is reached
|
|
214
|
+
-h, --help display help for command
|
|
215
|
+
```
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gobi-homepage
|
|
3
3
|
description: >-
|
|
4
|
-
Developer reference for building Gobi
|
|
4
|
+
Developer reference for building Gobi Homepages — custom HTML pages hosted on
|
|
5
5
|
webdrive and served as a vault's public homepage at gobispace.com/@{vaultSlug}.
|
|
6
|
-
Use when a developer wants to build or modify a vault homepage
|
|
6
|
+
Use when a developer wants to build or modify a vault homepage.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# Gobi
|
|
9
|
+
# Gobi Homepage Developer Guide
|
|
10
10
|
|
|
11
|
-
A **Gobi
|
|
11
|
+
A **Gobi Homepage** is a custom HTML page hosted on a vault's webdrive and served as its public homepage at `https://gobispace.com/@{vaultSlug}`. Gobi injects a `window.gobi` bridge before any scripts run, giving the homepage access to vault data, files, brain updates, and chat.
|
|
12
12
|
|
|
13
|
-
> **Sandbox:** The
|
|
13
|
+
> **Sandbox:** The homepage runs in a sandboxed iframe with `origin: null`. Direct `fetch()` / `XMLHttpRequest` calls are blocked by CORS. All data access must go through `window.gobi.*`.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -20,9 +20,9 @@ A **Gobi Applet** is a custom HTML page hosted on a vault's webdrive and served
|
|
|
20
20
|
```bash
|
|
21
21
|
gobi sync
|
|
22
22
|
```
|
|
23
|
-
2. Set `
|
|
24
|
-
- `app/home.html` — Gobi sidebars visible alongside the
|
|
25
|
-
- `app/home.html?nav=false` — full-screen, no Gobi chrome
|
|
23
|
+
2. Set `homepage` in BRAIN.md (homepage property):
|
|
24
|
+
- `homepage: "[[app/home.html]]"` — Gobi sidebars visible alongside the homepage
|
|
25
|
+
- `homepage: "[[app/home.html?nav=false]]"` — full-screen, no Gobi chrome
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|