ottoport 1.15.2 → 1.16.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/plugin.json +1 -1
- package/README.md +11 -11
- package/cli/ottoport.mjs +66 -32
- package/mcp/server.mjs +1 -1
- package/package.json +1 -1
- package/skills/ottoport/SKILL.md +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ottoport",
|
|
3
3
|
"description": "One API for every model. Call chat, image, video, speech and music models through the OttoPort gateway \u2014 as MCP tools, slash commands, or the bundled CLI.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.16.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "LITBOX LLC",
|
|
7
7
|
"email": "support@ottoport.ai"
|
package/README.md
CHANGED
|
@@ -21,23 +21,23 @@ ottoport chat "<prompt>" [--model claude-sonnet-5] [--system "..."] [--no-strea
|
|
|
21
21
|
[--temperature 0.7] [--max-tokens 512]
|
|
22
22
|
ottoport image "<prompt>" [--model gpt-image-2] [--size 1024x1024 | --resolution 2k]
|
|
23
23
|
[--quality high] [--aspect-ratio 16:9] [--n 1] [--seed 7]
|
|
24
|
-
[--image <url>] [--ref <url>[,<url>…]] [--out file.png]
|
|
24
|
+
[--image <url>] [--ref <url>[,<url>…]] [--wait --out file.png]
|
|
25
25
|
ottoport image --model qwen-image-layered --image <url> [--layers 4]
|
|
26
26
|
ottoport video "<prompt>" [--model kling-3.0] [--duration 5] [--resolution 1080p]
|
|
27
27
|
[--aspect-ratio 16:9] [--image <url>] [--last-frame <url>]
|
|
28
|
-
[--ref <url>[,<url>…]] [--out clip.mp4]
|
|
29
|
-
[--webhook-url https://…]
|
|
28
|
+
[--ref <url>[,<url>…]] [--wait --out clip.mp4]
|
|
29
|
+
[--webhook-url https://…]
|
|
30
30
|
ottoport webhook status|retry <job-id> # delivery attempts / redeliver now
|
|
31
|
-
ottoport speech "<text>" [--model gpt-4o-mini-tts] [--voice alloy] [--out speech.mp3]
|
|
32
|
-
ottoport music "<prompt>" [--model suno-v6] [--duration 15] [--out song.mp3]
|
|
31
|
+
ottoport speech "<text>" [--model gpt-4o-mini-tts] [--voice alloy] [--wait --out speech.mp3]
|
|
32
|
+
ottoport music "<prompt>" [--model suno-v6] [--duration 15] [--wait --out song.mp3]
|
|
33
33
|
ottoport music --model mirelo-sfx-2.0 --video <url> [--duration 8] # soundtrack for a video
|
|
34
34
|
|
|
35
35
|
ottoport install <host> # wire the MCP server into an agent
|
|
36
36
|
ottoport mcp # run the MCP server on stdio
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Chat streams by default. Image, video and audio commands
|
|
40
|
-
`--out` downloads it. `--url` and `--key` override the environment for a single
|
|
39
|
+
Chat streams by default. Image, video and audio commands return a job at once;
|
|
40
|
+
`--wait` waits for the result URL and `--wait --out` also downloads it. `--url` and `--key` override the environment for a single
|
|
41
41
|
call.
|
|
42
42
|
|
|
43
43
|
Every command is a thin client over the public REST API, so anything the CLI
|
|
@@ -159,9 +159,9 @@ https://ottoport.ai/docs#webhooks.
|
|
|
159
159
|
|
|
160
160
|
- Image, video, and audio calls return **URLs**, and provider URLs expire.
|
|
161
161
|
Download anything worth keeping.
|
|
162
|
-
- Video generation takes a minute or more
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
- Video generation takes a minute or more. The call returns a job at once and
|
|
163
|
+
only blocks with `--wait`. A retry is a second billable generation, not a
|
|
164
|
+
resumption.
|
|
165
165
|
- `resolution` and `quality` are price multipliers, not formatting hints. Leave
|
|
166
166
|
them unset to bill at the model's base tier.
|
|
167
167
|
- Requests are billed against the prepaid balance on your OttoPort account.
|
|
@@ -183,6 +183,6 @@ ottoport job image <job-id> --wait --out lake.png
|
|
|
183
183
|
ottoport image "a mountain lake" --wait --out lake.png
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
Use job kinds `image`, `video`, `speech`, or `music`. `--out`
|
|
186
|
+
Use job kinds `image`, `video`, `speech`, or `music`. On generation commands `--out` needs `--wait`, and is refused before anything is submitted without it. `ottoport job <kind> <id> --out file` saves a finished job and exits 3 if it is still running. `--wait` gives up after 30 minutes by default (`--timeout <seconds>`) and exits 3; the job keeps running and can be resumed with `ottoport job`. Pending and failed job responses retain the task ID and status route.
|
|
187
187
|
|
|
188
188
|
MCP generation tools return `job_id`, `status`, `status_path`, and the next tool call. Query with `ottoport_get_job({kind, job_id})`. Optional `wait: true` waits up to 45 seconds per MCP call, returning the same job if still pending. Querying a job does not submit another generation. Existing scripts that expected immediate URLs should add `--wait`, or adopt job polling.
|
package/cli/ottoport.mjs
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// ottoport models [--modality chat|image|video]
|
|
7
7
|
// ottoport chat "prompt" [--model claude-sonnet-5] [--system "..."] [--no-stream]
|
|
8
8
|
// [--video <url|file>] ask about a video (defaults to video-understanding)
|
|
9
|
-
// ottoport image "prompt" [--model gpt-image-2] [--resolution 2k] [--ref a.png,b.png] [--out img.png]
|
|
10
|
-
// ottoport video "prompt" [--model kling-3.0] [--duration 5] [--out clip.mp4]
|
|
11
|
-
// ottoport speech "text" [--model gpt-4o-mini-tts] [--voice alloy] [--out speech.mp3]
|
|
12
|
-
// ottoport music "prompt" [--model suno-v6] [--duration 15] [--out song.mp3]
|
|
9
|
+
// ottoport image "prompt" [--model gpt-image-2] [--resolution 2k] [--ref a.png,b.png] [--wait --out img.png]
|
|
10
|
+
// ottoport video "prompt" [--model kling-3.0] [--duration 5] [--wait --out clip.mp4]
|
|
11
|
+
// ottoport speech "text" [--model gpt-4o-mini-tts] [--voice alloy] [--wait --out speech.mp3]
|
|
12
|
+
// ottoport music "prompt" [--model suno-v6] [--duration 15] [--wait --out song.mp3]
|
|
13
13
|
// ottoport music --model mirelo-sfx-2.0 --video <url> [--duration 8]
|
|
14
14
|
// ottoport collections [list | create "name"]
|
|
15
15
|
// ottoport mcp
|
|
@@ -270,12 +270,43 @@ function references(flags, name = "ref") {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
const JOB_PATHS = { image: "images/generations", video: "videos/generations", speech: "audio/speech", music: "audio/music" };
|
|
273
|
+
const PENDING = ["queued", "processing"];
|
|
274
|
+
// Long enough for a slow video queue; the job itself carries on past it either way.
|
|
275
|
+
const DEFAULT_WAIT_SECONDS = 1800;
|
|
276
|
+
|
|
277
|
+
function waitSeconds(flags) {
|
|
278
|
+
if (flags.timeout === undefined) return DEFAULT_WAIT_SECONDS;
|
|
279
|
+
const seconds = Number(last(flags.timeout));
|
|
280
|
+
if (!(seconds > 0)) die("--timeout takes a number of seconds above 0");
|
|
281
|
+
return seconds;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Jobs are asynchronous and only --wait blocks. With neither a wait nor a later
|
|
286
|
+
* process to save it, --out could never produce a file, so it is refused before
|
|
287
|
+
* anything is spent.
|
|
288
|
+
*/
|
|
289
|
+
function checkSubmitFlags(kind, flags) {
|
|
290
|
+
waitSeconds(flags);
|
|
291
|
+
if (!flags.out || flags.wait === true) return;
|
|
292
|
+
const file = last(flags.out);
|
|
293
|
+
die(`--out needs --wait: jobs run asynchronously, so no file would be saved. Nothing was submitted.
|
|
294
|
+
wait and save now: add --wait to this command
|
|
295
|
+
save it later: drop --out, then run: ottoport job ${kind} <job-id> --wait --out ${file}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
273
298
|
async function resolveJob(kind, job, flags) {
|
|
274
|
-
if (!job.id || !job.status) return job;
|
|
299
|
+
if (!job.id || !job.status || flags.wait !== true) return job;
|
|
275
300
|
const { baseUrl, apiKey } = config(flags);
|
|
276
301
|
// Always expose the ID before a caller opts into waiting.
|
|
277
|
-
|
|
278
|
-
|
|
302
|
+
console.error(`job ${job.id} accepted`);
|
|
303
|
+
const seconds = waitSeconds(flags);
|
|
304
|
+
const deadline = Date.now() + seconds * 1000;
|
|
305
|
+
while (PENDING.includes(job.status)) {
|
|
306
|
+
if (Date.now() >= deadline) {
|
|
307
|
+
printJob(kind, job);
|
|
308
|
+
die(`job ${job.id} is still ${job.status} after ${seconds}s and keeps running. Resume with: ottoport job ${kind} ${job.id} --wait${flags.out ? ` --out ${last(flags.out)}` : ""}`, 3);
|
|
309
|
+
}
|
|
279
310
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
280
311
|
const res = await fetch(`${baseUrl}/api/v1/${JOB_PATHS[kind]}/${encodeURIComponent(job.id)}`, { headers: headers(apiKey, false) });
|
|
281
312
|
if (!res.ok) die(await readError(res));
|
|
@@ -287,13 +318,16 @@ function printJob(kind, job) {
|
|
|
287
318
|
console.log(JSON.stringify({ ...job, job_id: job.id, kind, status_path: `/api/v1/${JOB_PATHS[kind]}/${encodeURIComponent(job.id)}`, next: `ottoport job ${kind} ${job.id}` }, null, 2));
|
|
288
319
|
}
|
|
289
320
|
async function cmdJob(kind, id, flags) {
|
|
290
|
-
if (!Object.hasOwn(JOB_PATHS, kind) || !id) die("usage: ottoport job image|video|speech|music <id> [--wait] [--out file]");
|
|
321
|
+
if (!Object.hasOwn(JOB_PATHS, kind) || !id) die("usage: ottoport job image|video|speech|music <id> [--wait [--timeout seconds]] [--out file]");
|
|
322
|
+
waitSeconds(flags);
|
|
291
323
|
const { baseUrl, apiKey } = config(flags);
|
|
292
324
|
const res = await fetch(`${baseUrl}/api/v1/${JOB_PATHS[kind]}/${encodeURIComponent(id)}`, { headers: headers(apiKey, false) });
|
|
293
325
|
if (!res.ok) die(await readError(res));
|
|
294
326
|
const job = await resolveJob(kind, await res.json(), flags);
|
|
295
327
|
printJob(kind, job);
|
|
296
328
|
if (job.status === "failed") die(job.error || "generation failed", 2);
|
|
329
|
+
// Looking at a job never blocks unless asked, but a file that was named and not saved must not read as success.
|
|
330
|
+
if (flags.out && PENDING.includes(job.status)) die(`job ${id} is still ${job.status}, so nothing was saved. Wait and save with: ottoport job ${kind} ${id} --wait --out ${last(flags.out)}`, 3);
|
|
297
331
|
const item = job.data?.[0];
|
|
298
332
|
if (flags.out && (item?.asset_url || item?.url)) await download(item.asset_url || item.url, last(flags.out));
|
|
299
333
|
}
|
|
@@ -309,6 +343,7 @@ async function cmdImage(prompt, flags) {
|
|
|
309
343
|
if (wantsFile && outExtension && outExtension !== format) {
|
|
310
344
|
die(`--format ${format} returns a .${format} file, but --out names a .${outExtension} one. Change one of them.`);
|
|
311
345
|
}
|
|
346
|
+
checkSubmitFlags("image", flags);
|
|
312
347
|
const body = {
|
|
313
348
|
async: true,
|
|
314
349
|
// The default model has no GIF or SVG output, so asking for one picks a model that does.
|
|
@@ -351,6 +386,7 @@ async function cmdVideo(prompt, flags) {
|
|
|
351
386
|
// A draft's 1080p render takes everything from the draft, the prompt included.
|
|
352
387
|
const draftId = last(flags["draft-id"]);
|
|
353
388
|
if (!prompt && !last(flags.video) && !draftId) die("video requires a prompt: ottoport video \"a drone shot\" (or --video <url> on a depth model)");
|
|
389
|
+
checkSubmitFlags("video", flags);
|
|
354
390
|
const { baseUrl, apiKey } = config(flags);
|
|
355
391
|
// Drafts are Seedance 2.5's, so a draft with no model named is one of its.
|
|
356
392
|
const model = last(flags.model) || (flags.draft === true || draftId ? "seedance-2.5" : "kling-3.0");
|
|
@@ -382,15 +418,7 @@ async function cmdVideo(prompt, flags) {
|
|
|
382
418
|
let job = await res.json();
|
|
383
419
|
if (job.status === "failed") die(job.error || "video generation failed", 2);
|
|
384
420
|
if (body.webhook_url) process.stderr.write(`webhook: ${body.webhook_url} will be notified when the job finishes\n`);
|
|
385
|
-
|
|
386
|
-
process.stderr.write(`job ${job.id} accepted; waiting for completion…\n`);
|
|
387
|
-
while (job.status === "queued" || job.status === "processing") {
|
|
388
|
-
await new Promise((resolve) => setTimeout(resolve, 3_000));
|
|
389
|
-
const status = await fetch(`${baseUrl}/api/v1/videos/generations/${encodeURIComponent(job.id)}`, { headers: headers(apiKey, false) });
|
|
390
|
-
if (!status.ok) die(await readError(status));
|
|
391
|
-
job = await status.json();
|
|
392
|
-
}
|
|
393
|
-
}
|
|
421
|
+
job = await resolveJob("video", job, flags);
|
|
394
422
|
printJob("video", job);
|
|
395
423
|
if (job.status === "failed") die(job.error || "video generation failed", 2);
|
|
396
424
|
const url = job.data?.[0]?.url;
|
|
@@ -444,6 +472,7 @@ async function cmdAudio(kind, prompt, flags) {
|
|
|
444
472
|
// A sound-effects model scores a video, so the prompt is optional with one.
|
|
445
473
|
const video = kind === "music" ? last(flags.video) : undefined;
|
|
446
474
|
if (!prompt && !video) die(`${kind} requires a prompt: ottoport ${kind} "hello"`);
|
|
475
|
+
checkSubmitFlags(kind, flags);
|
|
447
476
|
const { baseUrl, apiKey } = config(flags);
|
|
448
477
|
const body = {
|
|
449
478
|
async: true,
|
|
@@ -537,7 +566,7 @@ Usage:
|
|
|
537
566
|
[--temperature 0.7] [--max-tokens 512]
|
|
538
567
|
ottoport image "<prompt>" [--model gpt-image-2] [--size 1024x1024 | --resolution 2k] [--quality high]
|
|
539
568
|
[--aspect-ratio 16:9] [--n 1] [--seed 7]
|
|
540
|
-
[--image <url>] [--ref <url>[,<url>…]] [--out file.png]
|
|
569
|
+
[--image <url>] [--ref <url>[,<url>…]] [--wait --out file.png]
|
|
541
570
|
ottoport image "<prompt>" --format gif|svg [--model gpt-image-2.6] [--frames 8] [--frame-ms 200] [--wait --out loop.gif]
|
|
542
571
|
ottoport image --model qwen-image-layered --image <url> [--layers 4]
|
|
543
572
|
split one image into RGBA layers (prompt optional)
|
|
@@ -546,17 +575,18 @@ Usage:
|
|
|
546
575
|
[--last-frame <url>] [--ref <url>[,<url>…]]
|
|
547
576
|
[--ref-video <url>[,<url>…]] [--ref-audio <url>[,<url>…]]
|
|
548
577
|
[--draft] [--webhook-url https://…] [--webhook-secret <s>]
|
|
549
|
-
[--
|
|
550
|
-
ottoport video --draft-id <job-id> [--model seedance-2.5] [--out clip.mp4]
|
|
578
|
+
[--wait --out clip.mp4]
|
|
579
|
+
ottoport video --draft-id <job-id> [--model seedance-2.5] [--wait --out clip.mp4]
|
|
551
580
|
render a Seedance 2.5 draft (--draft, or the
|
|
552
581
|
seedance-2.5-draft model) in 1080p, within 7 days
|
|
553
|
-
ottoport video --model video-depth-anything --video <url> [--out depth.mp4]
|
|
582
|
+
ottoport video --model video-depth-anything --video <url> [--wait --out depth.mp4]
|
|
554
583
|
turn a video (≤60s) into a grayscale depth video
|
|
555
584
|
ottoport webhook status <job-id> [--json] delivery attempts and responses
|
|
556
585
|
ottoport webhook retry <job-id> [--json] redeliver a finished job's webhook now
|
|
557
|
-
ottoport speech "<text>" [--model gpt-4o-mini-tts] [--voice alloy] [--format mp3] [--out speech.mp3]
|
|
558
|
-
ottoport music "<prompt>" [--model suno-v6] [--duration 15] [--format mp3] [--out song.mp3]
|
|
559
|
-
|
|
586
|
+
ottoport speech "<text>" [--model gpt-4o-mini-tts] [--voice alloy] [--format mp3] [--wait --out speech.mp3]
|
|
587
|
+
ottoport music "<prompt>" [--model suno-v6] [--duration 15] [--format mp3] [--wait --out song.mp3]
|
|
588
|
+
On Suno, --duration is a target length; the price per request stays flat
|
|
589
|
+
ottoport music ["<prompt>"] --model mirelo-sfx-2.0 --video <url> [--duration 8] [--wait --out sfx.wav]
|
|
560
590
|
a soundtrack synced to the video, dialogue kept;
|
|
561
591
|
set --duration to the clip's length (it bills)
|
|
562
592
|
ottoport collections [list] [--json] collections in the key's project
|
|
@@ -572,6 +602,10 @@ Global flags:
|
|
|
572
602
|
--webhook-secret <s> signs webhook deliveries (env OTTOPORT_WEBHOOK_SECRET)
|
|
573
603
|
--collection <id> on image, video, speech, music: keep the results in
|
|
574
604
|
that collection instead of letting them expire
|
|
605
|
+
--wait on image, video, speech, music, job: block until the job
|
|
606
|
+
finishes. Exit 3 if it is still running at the timeout
|
|
607
|
+
--timeout <seconds> how long --wait blocks (default ${DEFAULT_WAIT_SECONDS})
|
|
608
|
+
--out <file> save the result; generation commands need --wait with it
|
|
575
609
|
|
|
576
610
|
Examples:
|
|
577
611
|
export OTTOPORT_API_KEY=op-...
|
|
@@ -579,19 +613,19 @@ Examples:
|
|
|
579
613
|
ottoport models veo-3.1 # its modes, tiers, and every parameter
|
|
580
614
|
ottoport chat "explain MCP in one line" --model claude-haiku-4.5
|
|
581
615
|
ottoport chat "break this clip down shot by shot" --video https://example.com/clip.mp4
|
|
582
|
-
ottoport image "isometric city at dusk" --model gpt-image-2 --out city.png
|
|
616
|
+
ottoport image "isometric city at dusk" --model gpt-image-2 --wait --out city.png
|
|
583
617
|
ottoport video "cinematic product reveal" --model seedance-2.0-fast --resolution 1080p
|
|
584
618
|
ottoport video "the logo unfolds" --image start.png --last-frame end.png
|
|
585
|
-
ottoport video "drone over fjords" --webhook-url https://example.com/hooks/ottoport
|
|
619
|
+
ottoport video "drone over fjords" --webhook-url https://example.com/hooks/ottoport
|
|
586
620
|
ottoport webhook retry 7c1e9a52-…
|
|
587
621
|
ottoport image "her, on a beach" --ref face.png,style.png
|
|
588
622
|
ottoport image --model seedream-5.0-layers --image https://…/poster.png
|
|
589
|
-
ottoport speech "Welcome to OttoPort" --voice alloy --out welcome.mp3
|
|
590
|
-
ottoport music "lo-fi focus beat" --duration 20 --out focus.mp3
|
|
591
|
-
ottoport music "rain on a tin roof" --model mirelo-sfx-2.0 --duration 10 --format mp3 --out rain.mp3
|
|
592
|
-
ottoport music --model mirelo-sfx-2.0 --video https://example.com/chase.mp4 --duration 8 --out chase.wav
|
|
593
|
-
Media commands return jobs immediately. Add --wait to wait for
|
|
594
|
-
ottoport job image|video|speech|music <id> [--wait] [--out file]
|
|
623
|
+
ottoport speech "Welcome to OttoPort" --voice alloy --wait --out welcome.mp3
|
|
624
|
+
ottoport music "lo-fi focus beat" --duration 20 --wait --out focus.mp3
|
|
625
|
+
ottoport music "rain on a tin roof" --model mirelo-sfx-2.0 --duration 10 --format mp3 --wait --out rain.mp3
|
|
626
|
+
ottoport music --model mirelo-sfx-2.0 --video https://example.com/chase.mp4 --duration 8 --wait --out chase.wav
|
|
627
|
+
Media commands return jobs immediately. Add --wait to wait for the result, and --wait --out to save it.
|
|
628
|
+
ottoport job image|video|speech|music <id> [--wait] [--out file] --out alone saves a finished job, exit 3 if it is not done
|
|
595
629
|
ottoport collections create "Spring campaign"
|
|
596
630
|
ottoport image "spring hero banner" --collection <collection-id>
|
|
597
631
|
ottoport mcp
|
package/mcp/server.mjs
CHANGED
|
@@ -571,7 +571,7 @@ const TOOLS = [
|
|
|
571
571
|
wait: { type: "boolean", description: "Default false: return job immediately. True waits up to 45 seconds, then returns the same job for polling." },
|
|
572
572
|
prompt: { type: "string", description: "Music-generation prompt, or the sounds to make. Optional with video_url." },
|
|
573
573
|
model: { type: "string", description: "Music model id. Default suno-v6; mirelo-sfx-2.0 for sound effects." },
|
|
574
|
-
duration: { type: "number", description: "Requested
|
|
574
|
+
duration: { type: "number", description: "Requested length in seconds, billed per second. On the Suno models it is a target length instead: each request has one flat price, and the song may run longer or shorter." },
|
|
575
575
|
video_url: { type: "string", description: "mirelo-sfx-2.0 only: an http(s) video to score. Set duration to its length — duration is what bills." },
|
|
576
576
|
format: { type: "string", description: 'e.g. "mp3" or "wav".' },
|
|
577
577
|
collection: { type: "string", description: "Save the result for good into this collection — its id or exact name (see ottoport_list_collections). Without it the file expires after a few days. Needs collections unlocked on the account. Defaults to OTTOPORT_COLLECTION when set." },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ottoport",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Claude Code plugin, CLI and MCP server for OttoPort — one OpenAI-compatible API for every LLM, image, video, and speech model.",
|
|
5
5
|
"homepage": "https://ottoport.ai",
|
|
6
6
|
"repository": {
|
package/skills/ottoport/SKILL.md
CHANGED
|
@@ -123,12 +123,12 @@ export OTTOPORT_API_KEY=op-... # keys: https://ottoport.ai/api-keys
|
|
|
123
123
|
|
|
124
124
|
ottoport models --modality image
|
|
125
125
|
ottoport chat "explain MCP in one sentence" --model claude-haiku-4.5
|
|
126
|
-
ottoport image "isometric city at dusk" --out city.png
|
|
127
|
-
ottoport video "drone shot over a canyon" --duration 5 --out clip.mp4
|
|
128
|
-
ottoport speech "Welcome to OttoPort" --voice alloy --out welcome.mp3
|
|
126
|
+
ottoport image "isometric city at dusk" --wait --out city.png
|
|
127
|
+
ottoport video "drone shot over a canyon" --duration 5 --wait --out clip.mp4
|
|
128
|
+
ottoport speech "Welcome to OttoPort" --voice alloy --wait --out welcome.mp3
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
`--out` downloads the result; chat streams unless `--no-stream`.
|
|
131
|
+
`--wait --out` waits and downloads the result; chat streams unless `--no-stream`.
|
|
132
132
|
|
|
133
133
|
## Calling from code
|
|
134
134
|
|
|
@@ -159,6 +159,6 @@ ottoport job image <job-id> --wait --out lake.png
|
|
|
159
159
|
ottoport image "a mountain lake" --wait --out lake.png
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
Use job kinds `image`, `video`, `speech`, or `music`. `--out`
|
|
162
|
+
Use job kinds `image`, `video`, `speech`, or `music`. On generation commands `--out` needs `--wait`, and is refused before anything is submitted without it. `ottoport job <kind> <id> --out file` saves a finished job and exits 3 if it is still running. `--wait` gives up after 30 minutes by default (`--timeout <seconds>`) and exits 3; the job keeps running and can be resumed with `ottoport job`. Pending and failed job responses retain the task ID and status route.
|
|
163
163
|
|
|
164
164
|
MCP generation tools return `job_id`, `status`, `status_path`, and the next tool call. Query with `ottoport_get_job({kind, job_id})`. Optional `wait: true` waits up to 45 seconds per MCP call, returning the same job if still pending. Querying a job does not submit another generation. Existing scripts that expected immediate URLs should add `--wait`, or adopt job polling.
|