ottoport 1.6.0 → 1.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/plugin.json +1 -1
- package/README.md +8 -3
- package/cli/ottoport.mjs +43 -4
- package/commands/image.md +4 -1
- package/mcp/server.mjs +1 -0
- package/package.json +1 -1
- package/skills/ottoport/SKILL.md +7 -1
|
@@ -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 — as MCP tools, slash commands, or the bundled CLI.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "LITBOX LLC",
|
|
7
7
|
"email": "support@ottoport.ai"
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ ottoport models [--modality chat|image|video|tts|music] [--json]
|
|
|
20
20
|
ottoport chat "<prompt>" [--model claude-sonnet-5] [--system "..."] [--no-stream]
|
|
21
21
|
[--temperature 0.7] [--max-tokens 512]
|
|
22
22
|
ottoport image "<prompt>" [--model gpt-image-2] [--size 1024x1024 | --resolution 2k]
|
|
23
|
-
[--aspect-ratio 16:9] [--n 1] [--seed 7]
|
|
23
|
+
[--quality high] [--aspect-ratio 16:9] [--n 1] [--seed 7]
|
|
24
24
|
[--image <url>] [--ref <url>[,<url>…]] [--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]
|
|
@@ -90,6 +90,11 @@ first-and-last-frame (`image_url` + `last_frame_url`), and reference-to-video
|
|
|
90
90
|
(`reference_image_urls`). Both take a `resolution`: `480p`…`4k` for video,
|
|
91
91
|
`1k`/`2k`/`4k` for images, and a tier above a model's base rate costs more.
|
|
92
92
|
|
|
93
|
+
The GPT Image family reads a `quality` on top of that — `low`, `medium` (the
|
|
94
|
+
default every catalog rate is measured at), `high`, and on `gpt-image-2.5` and
|
|
95
|
+
`gpt-image-2.5-flare` also `xhigh` and `max`. It is how much compute the model
|
|
96
|
+
spends, and the ladder is steep: `max` bills sixteen times `medium`.
|
|
97
|
+
|
|
93
98
|
Two image models take a picture apart instead: `qwen-image-layered` and
|
|
94
99
|
`seedream-5.0-layers` split `image_url` into RGBA layers and return one URL per
|
|
95
100
|
layer, bottom first (the prompt is an optional caption; `num_layers`, 2–10, is
|
|
@@ -133,8 +138,8 @@ https://ottoport.ai/docs#webhooks.
|
|
|
133
138
|
- Video generation takes a minute or more and the call blocks until the job is
|
|
134
139
|
terminal, unless a webhook is given or `--no-wait` is passed. A retry is a
|
|
135
140
|
second billable generation, not a resumption.
|
|
136
|
-
- `resolution`
|
|
137
|
-
bill at the model's base tier.
|
|
141
|
+
- `resolution` and `quality` are price multipliers, not formatting hints. Leave
|
|
142
|
+
them unset to bill at the model's base tier.
|
|
138
143
|
- Requests are billed against the prepaid balance on your OttoPort account.
|
|
139
144
|
|
|
140
145
|
Docs: <https://ottoport.ai/docs> · Support: support@ottoport.ai
|
package/cli/ottoport.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// ottoport models [--modality chat|image|video]
|
|
7
7
|
// ottoport chat "prompt" [--model claude-sonnet-5] [--system "..."] [--no-stream]
|
|
8
|
+
// [--video <url|file>] ask about a video (defaults to video-understanding)
|
|
8
9
|
// ottoport image "prompt" [--model gpt-image-2] [--resolution 2k] [--ref a.png,b.png] [--out img.png]
|
|
9
10
|
// ottoport video "prompt" [--model kling-3.0] [--duration 5] [--out clip.mp4]
|
|
10
11
|
// ottoport speech "text" [--model gpt-4o-mini-tts] [--voice alloy] [--out speech.mp3]
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
// The public CLI always targets https://ottoport.ai unless --url is passed
|
|
18
19
|
// explicitly for local development or a self-hosted gateway.
|
|
19
20
|
|
|
20
|
-
import { writeFile } from "node:fs/promises";
|
|
21
|
+
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
21
22
|
|
|
22
23
|
const BASE = "https://ottoport.ai";
|
|
23
24
|
|
|
@@ -152,11 +153,13 @@ async function cmdChat(prompt, flags) {
|
|
|
152
153
|
const { baseUrl, apiKey } = config(flags);
|
|
153
154
|
const messages = [];
|
|
154
155
|
if (flags.system) messages.push({ role: "system", content: flags.system });
|
|
155
|
-
|
|
156
|
+
const videos = await videoParts(flags);
|
|
157
|
+
messages.push({ role: "user", content: videos.length ? [{ type: "text", text: prompt }, ...videos] : prompt });
|
|
156
158
|
|
|
157
159
|
const stream = flags.stream !== false;
|
|
158
160
|
const body = {
|
|
159
|
-
|
|
161
|
+
// A video question goes to the model built for it unless one is named.
|
|
162
|
+
model: last(flags.model) || (videos.length ? "video-understanding" : "claude-sonnet-5"),
|
|
160
163
|
messages,
|
|
161
164
|
stream,
|
|
162
165
|
...(flags.temperature ? { temperature: Number(last(flags.temperature)) } : {}),
|
|
@@ -202,6 +205,39 @@ async function cmdChat(prompt, flags) {
|
|
|
202
205
|
process.stdout.write("\n");
|
|
203
206
|
}
|
|
204
207
|
|
|
208
|
+
const VIDEO_TYPES = { ".mp4": "video/mp4", ".m4v": "video/mp4", ".mov": "video/quicktime", ".webm": "video/webm", ".mpeg": "video/mpeg", ".mpg": "video/mpeg" };
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* `--video`, once or several times (or comma-separated), as chat content parts.
|
|
212
|
+
*
|
|
213
|
+
* A URL is sent as-is: the gateway's transport fetches it, so it must be
|
|
214
|
+
* publicly reachable. A local file is inlined as a base64 data URL, which is
|
|
215
|
+
* convenient for trying a clip but costs a third more than the file on the
|
|
216
|
+
* wire — for anything large, host it and pass the URL.
|
|
217
|
+
*/
|
|
218
|
+
async function videoParts(flags) {
|
|
219
|
+
const list = (Array.isArray(flags.video) ? flags.video : [flags.video])
|
|
220
|
+
.filter((value) => typeof value === "string")
|
|
221
|
+
.flatMap((value) => value.split(",").map((one) => one.trim()))
|
|
222
|
+
.filter(Boolean);
|
|
223
|
+
const parts = [];
|
|
224
|
+
for (const source of list) {
|
|
225
|
+
if (/^(https?:|data:)/i.test(source)) {
|
|
226
|
+
parts.push({ type: "video_url", video_url: { url: source } });
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const info = await stat(source).catch(() => null);
|
|
230
|
+
if (!info?.isFile()) die(`--video ${source}: not a URL and no such file`);
|
|
231
|
+
const ext = source.slice(source.lastIndexOf(".")).toLowerCase();
|
|
232
|
+
const type = VIDEO_TYPES[ext];
|
|
233
|
+
if (!type) die(`--video ${source}: unsupported type ${ext || "(none)"}; use ${Object.keys(VIDEO_TYPES).join(", ")}`);
|
|
234
|
+
const mb = info.size / 1e6;
|
|
235
|
+
if (mb > 20) console.error(`ottoport: ${source} is ${mb.toFixed(0)}MB; inlined it will be ~${(mb * 4 / 3).toFixed(0)}MB and may be refused — host it and pass the URL instead`);
|
|
236
|
+
parts.push({ type: "video_url", video_url: { url: `data:${type};base64,${(await readFile(source)).toString("base64")}` } });
|
|
237
|
+
}
|
|
238
|
+
return parts;
|
|
239
|
+
}
|
|
240
|
+
|
|
205
241
|
async function download(url, out) {
|
|
206
242
|
const res = await fetch(url);
|
|
207
243
|
if (!res.ok) die(`could not download ${url}: ${res.status}`);
|
|
@@ -236,6 +272,7 @@ async function cmdImage(prompt, flags) {
|
|
|
236
272
|
...(last(flags.layers) ? { num_layers: Number(last(flags.layers)) } : {}),
|
|
237
273
|
...(last(flags.size) ? { size: last(flags.size) } : {}),
|
|
238
274
|
...(last(flags.resolution) ? { resolution: last(flags.resolution) } : {}),
|
|
275
|
+
...(last(flags.quality) ? { quality: last(flags.quality) } : {}),
|
|
239
276
|
...(last(flags["aspect-ratio"]) ? { aspect_ratio: flags["aspect-ratio"] } : {}),
|
|
240
277
|
...(last(flags.n) ? { n: Number(last(flags.n)) } : {}),
|
|
241
278
|
...(last(flags.seed) ? { seed: Number(last(flags.seed)) } : {}),
|
|
@@ -376,8 +413,9 @@ Usage:
|
|
|
376
413
|
ottoport models [--modality chat|image|video|tts|music] [--json]
|
|
377
414
|
ottoport models <model-id> [--json] what that one model accepts
|
|
378
415
|
ottoport chat "<prompt>" [--model claude-sonnet-5] [--system "..."] [--no-stream]
|
|
416
|
+
[--video <url|file>[,<url|file>…]] ask about a video
|
|
379
417
|
[--temperature 0.7] [--max-tokens 512]
|
|
380
|
-
ottoport image "<prompt>" [--model gpt-image-2] [--size 1024x1024 | --resolution 2k]
|
|
418
|
+
ottoport image "<prompt>" [--model gpt-image-2] [--size 1024x1024 | --resolution 2k] [--quality high]
|
|
381
419
|
[--aspect-ratio 16:9] [--n 1] [--seed 7]
|
|
382
420
|
[--image <url>] [--ref <url>[,<url>…]] [--out file.png]
|
|
383
421
|
ottoport image --model qwen-image-layered --image <url> [--layers 4]
|
|
@@ -408,6 +446,7 @@ Examples:
|
|
|
408
446
|
ottoport models --modality image # prints what each one accepts
|
|
409
447
|
ottoport models veo-3.1 # its modes, tiers, and every parameter
|
|
410
448
|
ottoport chat "explain MCP in one line" --model claude-haiku-4.5
|
|
449
|
+
ottoport chat "break this clip down shot by shot" --video https://example.com/clip.mp4
|
|
411
450
|
ottoport image "isometric city at dusk" --model gpt-image-2 --out city.png
|
|
412
451
|
ottoport video "cinematic product reveal" --model seedance-2.0-fast --resolution 1080p
|
|
413
452
|
ottoport video "the logo unfolds" --image start.png --last-frame end.png
|
package/commands/image.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Generate an image through the OttoPort gateway
|
|
3
|
-
argument-hint: "<prompt> [--model <id>] [--resolution 2k] [--ref <url>…]"
|
|
3
|
+
argument-hint: "<prompt> [--model <id>] [--resolution 2k] [--quality high] [--ref <url>…]"
|
|
4
4
|
allowed-tools: mcp__ottoport__ottoport_generate_image, mcp__ottoport__ottoport_list_models, Bash(curl:*)
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -15,6 +15,9 @@ Request: $ARGUMENTS
|
|
|
15
15
|
- Detail is `resolution` (`1k`/`2k`/`4k`), optionally with `aspect_ratio`; pass
|
|
16
16
|
`size` instead when the user named exact pixels. A higher tier costs more, so
|
|
17
17
|
send one only when the request asks for it.
|
|
18
|
+
- On the GPT Image models, `quality` buys compute rather than pixels — `low`,
|
|
19
|
+
`medium` (the default), `high`, and `xhigh`/`max` on `gpt-image-2.5`. It is
|
|
20
|
+
billed steeply, so send it only when the user asked for a draft or a finish.
|
|
18
21
|
- Models differ in how many references and which resolutions they take. If a
|
|
19
22
|
request needs more than one reference, or 4k, check `ottoport_list_models`
|
|
20
23
|
first — it prints each model's menu — rather than spending a failed call.
|
package/mcp/server.mjs
CHANGED
|
@@ -290,6 +290,7 @@ const TOOLS = [
|
|
|
290
290
|
// at best. The model's own menu is in `ottoport_list_models`.
|
|
291
291
|
resolution: { type: "string", description: 'Output detail — "1k", "2k" or "4k", but only the tiers this model lists in ottoport_list_models. Priced accordingly. Pair with `aspect_ratio` when you know the shape but not the pixel vocabulary; an explicit `size` wins.' },
|
|
292
292
|
aspect_ratio: { type: "string", description: 'e.g. "16:9". Read only when `size` is absent.' },
|
|
293
|
+
quality: { type: "string", description: 'How much compute the model spends, on the GPT Image family only — "low", "medium" (the default), "high", and on gpt-image-2.5 "xhigh" and "max". Priced by tier, and the ladder is steep: `max` costs sixteen times `medium`. Check the model\'s `qualities` in ottoport_list_models.' },
|
|
293
294
|
n: { type: "number" },
|
|
294
295
|
seed: { type: "number" },
|
|
295
296
|
image_url: { type: "string", description: "One reference image, for editing and image-to-image — or, on a layer-decomposition model, the image to split." },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ottoport",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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
|
@@ -16,7 +16,7 @@ and do not call the HTTP API, when a tool covers the job:
|
|
|
16
16
|
| --- | --- |
|
|
17
17
|
| `ottoport_list_models` | The catalog: what each model is for, and its rate in credits and USD. Filter with `modality`, or pass `model` for ONE model in full — its modes, its resolution tiers with prices, and every parameter it reads. |
|
|
18
18
|
| `ottoport_chat` | `prompt`, plus optional `model`, `system`, `temperature`, `max_tokens`. |
|
|
19
|
-
| `ottoport_generate_image` | `prompt`, plus optional `model`, `size` or `resolution`, `aspect_ratio`, `n`, `seed`, `image_url` (one reference), `reference_image_urls` (several). Layer decomposition too: `model` `qwen-image-layered` or `seedream-5.0-layers` with `image_url` alone (prompt optional, `num_layers` on Qwen) returns one URL per RGBA layer. |
|
|
19
|
+
| `ottoport_generate_image` | `prompt`, plus optional `model`, `size` or `resolution`, `quality` (GPT Image only), `aspect_ratio`, `n`, `seed`, `image_url` (one reference), `reference_image_urls` (several). Layer decomposition too: `model` `qwen-image-layered` or `seedream-5.0-layers` with `image_url` alone (prompt optional, `num_layers` on Qwen) returns one URL per RGBA layer. |
|
|
20
20
|
| `ottoport_generate_video` | `prompt`, plus optional `model`, `duration`, `resolution`, `aspect_ratio`, `seed`, `image_url` (first frame), `last_frame_url`, `reference_image_urls`, `webhook_url` (returns the job id at once and POSTs the result there; the secret comes from `webhook_secret` or the server's `OTTOPORT_WEBHOOK_SECRET`). |
|
|
21
21
|
| `ottoport_video_webhook` | `job_id`, plus `action` `status` (every delivery attempt and the endpoint's response) or `retry` (redeliver a finished job's webhook now). |
|
|
22
22
|
| `ottoport_generate_speech` | `prompt` (the text), plus optional `model`, `voice`, `format`. |
|
|
@@ -55,6 +55,12 @@ video and `"1k"`/`"2k"`/`"4k"` for images, but **no model offers all of it**:
|
|
|
55
55
|
`gpt-image-1.5` only 1k. Same for how many references a model takes —
|
|
56
56
|
`nano-banana-lite` accepts 3, `gpt-image-2` accepts 16.
|
|
57
57
|
|
|
58
|
+
`quality` is a second priced dial, on the GPT Image family alone: `low`,
|
|
59
|
+
`medium` (the default, and the tier every catalog rate was measured at),
|
|
60
|
+
`high`, and `xhigh`/`max` on `gpt-image-2.5` and `gpt-image-2.5-flare`. It buys
|
|
61
|
+
compute, not pixels, and the ladder is steep — `max` bills sixteen times
|
|
62
|
+
`medium`. A model's tiers are its `qualities` in `ottoport_list_models`.
|
|
63
|
+
|
|
58
64
|
So read the model's own menu before naming a mode, a resolution or a second
|
|
59
65
|
reference image: `ottoport_list_models` with `model` set to the one you are
|
|
60
66
|
about to call answers with exactly that — its modes, what each resolution tier
|