ottoport 1.2.2 → 1.3.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.
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "ottoport",
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.3.0",
5
+ "author": {
6
+ "name": "LITBOX LLC",
7
+ "email": "support@ottoport.ai"
8
+ },
9
+ "homepage": "https://ottoport.ai",
10
+ "repository": "https://github.com/Jet1217/OttoPort",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "ai",
14
+ "llm",
15
+ "api-gateway",
16
+ "openai-compatible",
17
+ "image-generation",
18
+ "video-generation",
19
+ "text-to-speech",
20
+ "mcp"
21
+ ]
22
+ }
package/.mcp.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "ottoport": {
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT:-.}/mcp/server.mjs"],
6
+ "env": {
7
+ "OTTOPORT_API_KEY": "${OTTOPORT_API_KEY}",
8
+ "OTTOPORT_BASE_URL": "${OTTOPORT_BASE_URL:-https://ottoport.ai}"
9
+ }
10
+ }
11
+ }
12
+ }
package/README.md CHANGED
@@ -1,167 +1,63 @@
1
- # OttoPort
1
+ # OttoPort for Claude Code
2
2
 
3
- **One API for every model.** A unified, OpenAI-compatible gateway that routes
4
- LLM, image, video, and speech generation through a single surface. Calls are
5
- dispatched through Provider Adapters, so the same public model can use an
6
- official API, a reseller, or a backup provider without changing the client
7
- contract.
3
+ One API for every model. This plugin bundles three things into a single
4
+ install: the OttoPort **MCP server**, the **`ottoport` skill**, and a set of
5
+ **slash commands** for chat, image, video, speech, and music generation.
8
6
 
9
- Design language is shared with Ottofy: Inter + Space Grotesk, lime-on-slate
10
- brand, soft shadows, hairline borders.
7
+ ## Install
11
8
 
12
- ## Stack
9
+ ```
10
+ /plugin marketplace add https://ottoport.ai/plugin/marketplace.json
11
+ /plugin install ottoport@ottoport
12
+ ```
13
13
 
14
- - **Next.js 14** (App Router) + TypeScript
15
- - **Tailwind CSS** + daisyui (`ottoport` theme)
16
- - **Supabase** — auth, accounts, API keys, usage ledger
17
- - **Stripe** — prepaid credits / plans
18
- - Provider SDKs: `openai`, `@anthropic-ai/sdk`
14
+ Then set your key in the shell that launches Claude Code — the MCP server reads
15
+ the environment it inherits, so a key exported inside a session is too late:
19
16
 
20
- ## Unified API
17
+ ```bash
18
+ export OTTOPORT_API_KEY=op-... # create one at https://ottoport.ai/api-keys
19
+ export OTTOPORT_BASE_URL=https://ottoport.ai # optional; this is the default
20
+ ```
21
21
 
22
- Base URL: `/api/v1` · Auth: `Authorization: Bearer <key>`
22
+ Run `/ottoport:setup` to confirm the key, the gateway, and the MCP tools are all
23
+ live.
23
24
 
24
- | Endpoint | Modality | Notes |
25
- | --- | --- | --- |
26
- | `POST /v1/chat/completions` | LLM | OpenAI-compatible, streaming supported. Routes GPT → OpenAI, Claude → Anthropic. |
27
- | `POST /v1/images/generations` | Image | `{ model, prompt, size?, n?, image_url? }` → `{ data: [{ url }] }`. |
28
- | `POST /v1/videos/generations` | Video | Submits `{ model, prompt, duration?, aspect_ratio?, webhook_url?, webhook_secret? }` and returns `202` with an OttoPort job ID. |
29
- | `GET /v1/videos/generations` | Video | Lists the authenticated project's persisted video generation history. |
30
- | `GET /v1/videos/generations/:id` | Video | Fetches the latest persisted and provider status. |
31
- | `POST /v1/videos/generations/:id/webhook` | Video | Retries a terminal job's webhook delivery. |
32
- | `POST /v1/audio/speech` | Speech | `{ model, input, voice?, format? }`; directly calls OpenAI, ElevenLabs, or Google. |
33
- | `GET /v1/models` | — | Catalog with pricing + modality. |
25
+ ## Slash commands
34
26
 
35
- The gateway is the heart of the product:
27
+ | Command | What it does |
28
+ | --- | --- |
29
+ | `/ottoport:models` | The catalog with live pricing, filterable by modality. |
30
+ | `/ottoport:chat` | Send a prompt to any chat model (GPT, Claude, Gemini, Kimi, GLM …). |
31
+ | `/ottoport:image` | Generate or edit an image. |
32
+ | `/ottoport:video` | Submit a video job and wait for the URL. |
33
+ | `/ottoport:speech` | Read text aloud, or generate music. |
34
+ | `/ottoport:setup` | Diagnose key / gateway / MCP wiring. |
36
35
 
37
- ```
38
- app/api/v1/* route handlers (thin)
39
- libs/gateway/
40
- registry.ts model catalog — single source of truth
41
- router.ts dispatch by model Provider Adapter
42
- auth.ts API-key verification (+ dev escape hatch)
43
- types.ts normalized request/response shapes
44
- providers/
45
- index.ts adapter registry + model route policy + fallback
46
- openrouter.ts unified LLM transport (except Kimi)
47
- moonshot.ts official Kimi chat API
48
- openai.ts official OpenAI image API
49
- google.ts official Gemini image API
50
- video.ts official Veo / Kling / Volcengine video APIs
51
- modelark.ts official Seedance 2.0 / Seedream 5.0 APIs
52
- atlascloud.ts optional Atlas Cloud transport for Seedance / Seedream
53
- legnext.ts Midjourney image generation via Legnext
54
- audio.ts official OpenAI / ElevenLabs / Google speech APIs
55
- ```
36
+ ## MCP tools
37
+
38
+ The bundled server exposes `ottoport_list_models`, `ottoport_chat`,
39
+ `ottoport_generate_image`, `ottoport_generate_video`, `ottoport_generate_speech`,
40
+ and `ottoport_generate_music`. Any agent in the session can call them directly —
41
+ the slash commands are just convenient entry points over the same tools.
56
42
 
57
- **Adding a model** is a one-line entry in `libs/gateway/registry.ts`. The
58
- model's `provider` remains its original vendor; runtime transport selection is
59
- separate. Configure model-specific transport routes and fallbacks in
60
- `libs/gateway/provider-config.ts`.
61
- For synchronous requests, routes are tried in order on provider failures. Video
62
- jobs persist their chosen route so later status polling always reaches the API
63
- that owns the upstream job ID.
43
+ ## CLI
64
44
 
65
- ## Getting started
45
+ The same package ships the `ottoport` CLI for terminal and CI use:
66
46
 
67
47
  ```bash
68
- cp .env.example .env.local # add OpenRouter and official media-provider keys
69
- npm install
70
- npm run dev # http://localhost:3014
48
+ npm install -g ottoport
49
+ ottoport models --modality video
50
+ ottoport image "isometric city at dusk" --out city.png
71
51
  ```
72
52
 
73
- The landing-page playground requires a Supabase session. Run
74
- [`sql/schema.sql`](sql/schema.sql), then [`sql/billing-wallet-migration.sql`](sql/billing-wallet-migration.sql)
75
- and [`sql/projects-migration.sql`](sql/projects-migration.sql), then [`sql/wallet-holds-migration.sql`](sql/wallet-holds-migration.sql)
76
- in the Supabase SQL editor, configure the
77
- three Supabase environment variables from `.env.example`, then sign in with
78
- email magic link or Google. Every new account receives the $5 free balance
79
- defined by the `accounts.credits_cents` default.
80
-
81
- ### Async video jobs and webhooks
82
-
83
- Run [`sql/generation-jobs-migration.sql`](sql/generation-jobs-migration.sql), then
84
- [`sql/generation-queue-migration.sql`](sql/generation-queue-migration.sql), then
85
- [`sql/account-concurrency-limits-migration.sql`](sql/account-concurrency-limits-migration.sql), after the project migration.
86
- Video submissions are persisted and queued, so the returned job ID can be queried after a process restart. Add an HTTPS
87
- `webhook_url` and a caller-held `webhook_secret` to receive a signed `video.completed` or `video.failed` event.
88
- The signature header is `x-ottoport-signature` and uses `HMAC-SHA256(secret, "timestamp.payload")`.
89
-
90
- Set `GENERATION_JOBS_CRON_SECRET`, then configure a trusted scheduler to call
91
- `GET /api/internal/generation-jobs/dispatch` every minute with `Authorization: Bearer <secret>`.
92
- The dispatcher atomically leases queued jobs, limits in-flight jobs per account and provider, retries provider
93
- failures with exponential backoff, and sends terminal webhooks even when the API client is no longer polling.
94
-
95
- ### Dynamic account concurrency
96
-
97
- Video concurrency is evaluated per account at the moment a job is claimed.
98
- `concurrency_limit_tiers` maps successful paid cash top-ups in the current UTC
99
- month to capacities; `account_concurrency_overrides` provides expiring
100
- per-account adjustments. Update either table in Supabase to change capacity
101
- without deploying. The migration seeds starter, growth, scale, and enterprise
102
- defaults; treat their thresholds and limits as product configuration.
103
-
104
- For example, to temporarily grant one account five in-flight video jobs for a
105
- week, upsert `account_concurrency_overrides` with `video_concurrency = 5` and
106
- an `expires_at` timestamp. The next worker claim uses that value immediately.
107
-
108
- ### Request reservations and idempotency
109
-
110
- Chat and video calls require an `Idempotency-Key` request header. OttoPort atomically reserves the worst-case
111
- cost before submitting upstream, then settles the reservation from actual usage. Reuse the same key only when
112
- retrying the identical request; a new key represents a new billable generation. Configure the gateway caps in
113
- `.env.example` before production traffic.
114
-
115
- ### Stripe prepaid wallet
116
-
117
- Set `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, and `NEXT_PUBLIC_APP_URL`.
118
- The required billing migration adds paid-vs-bonus
119
- balances, a tamper-resistant wallet ledger, saved payment methods, and
120
- automatic top-up settings.
121
-
122
- For an environment where `billing-wallet-migration.sql` was already applied,
123
- run [`sql/auto-topup-safety-migration.sql`](sql/auto-topup-safety-migration.sql)
124
- as well. Then configure a Stripe webhook for:
125
-
126
- - `checkout.session.completed`
127
- - `checkout.session.async_payment_succeeded`
128
- - `payment_intent.succeeded`
129
-
130
- pointing to `https://your-domain/api/billing/webhook`. The dashboard creates a
131
- one-time Checkout Session (not a subscription). Users can also save a card and
132
- enable automatic top-up after explicit consent. Credits are added only after
133
- the webhook verifies Stripe's raw-body signature and records the Checkout
134
- Session or PaymentIntent ID, so webhook retries cannot duplicate a top-up. Use
135
- Stripe CLI during local testing to forward events to
136
- `http://localhost:3014/api/billing/webhook`.
137
-
138
- ## Access surfaces
139
-
140
- The `/api/v1` gateway is the product; every other surface is a thin client over
141
- it. Point them at a running gateway with two env vars: `OTTOPORT_BASE_URL` and
142
- `OTTOPORT_API_KEY` (the `op-…` key, or `OTTOPORT_KEY_SECRET` in dev).
143
-
144
- - **HTTP** — OpenAI-compatible. Point any OpenAI SDK's `baseURL` at
145
- `${OTTOPORT_BASE_URL}/api/v1`.
146
- - **CLI** — [`cli/ottoport.mjs`](cli/ottoport.mjs), zero-dependency (Node 20+).
147
- `npm run cli -- models` · `chat` · `image` · `video` · `speech` · `music`. Installs as the
148
- `ottoport` bin. Streams chat, `--out` downloads image/video results.
149
- - **MCP** — [`mcp/server.mjs`](mcp/server.mjs), a dependency-free stdio server
150
- exposing `ottoport_list_models`, `ottoport_chat`, `ottoport_generate_image`,
151
- `ottoport_generate_video`, `ottoport_generate_speech`, and `ottoport_generate_music`. Register with
152
- `claude mcp add ottoport -- node $PWD/mcp/server.mjs`.
153
- - **Skill** — [`skills/ottoport/SKILL.md`](skills/ottoport/SKILL.md) teaches an
154
- agent to drive OttoPort via the CLI/MCP.
155
-
156
- Shared TS client for embedding in your own code: [`libs/client.ts`](libs/client.ts).
157
-
158
- ## What's wired vs. stubbed
159
-
160
- - ✅ Landing page, docs, playground, model catalog, unified gateway + adapters
161
- - ✅ CLI, MCP server, and Skill — all thin clients over `/api/v1`
162
- - ✅ Supabase sign-in requirement + metered landing-page playground (schema in `sql/schema.sql`)
163
- - ✅ Stripe Checkout top-ups with verified, idempotent webhook crediting
164
- - ✅ Hashed `api_keys` lookup in `libs/gateway/auth.ts` for the public `/api/v1` gateway
165
- - 🔲 Dashboard key management UI
166
-
167
- See the SQL migrations for the accounts / workspaces (projects) / api_keys / usage_events tables and RLS.
53
+ ## Notes
54
+
55
+ - Image, video, and audio calls return **URLs**, and provider URLs expire.
56
+ Download anything worth keeping.
57
+ - Video generation takes a minute or more and the call blocks until the job is
58
+ terminal. A retry is a second billable generation, not a resumption.
59
+ - Requests are billed against the prepaid balance on your OttoPort account.
60
+
61
+ Docs: <https://ottoport.ai/docs> · Support: support@ottoport.ai
62
+
63
+ MIT © LITBOX LLC
@@ -0,0 +1,169 @@
1
+ // ── ottoport install <host> ──────────────────────────────────────────
2
+ // Wires OttoPort into whatever agent the user actually runs. Only Claude Code
3
+ // has a plugin format, so everywhere else the installable unit is the MCP
4
+ // server plus — where the host understands one — the skill.
5
+ //
6
+ // The server is always registered as `npx -y ottoport mcp`, so the host
7
+ // resolves it from npm rather than from a path that only exists on this
8
+ // machine.
9
+
10
+ import { spawnSync } from "node:child_process";
11
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from "node:fs";
12
+ import { homedir, platform } from "node:os";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
17
+ const SKILL_SRC = join(PKG_ROOT, "skills/ottoport");
18
+ const HOME = homedir();
19
+ const CODEX_HOME = process.env.CODEX_HOME || join(HOME, ".codex");
20
+
21
+ const COMMAND = "npx";
22
+ const ARGS = ["-y", "ottoport", "mcp"];
23
+ // Names forwarded from the user's environment. Writing the key itself into a
24
+ // config file would leave an op-… secret in plain text on disk, and every host
25
+ // below can read it from the environment instead.
26
+ const ENV_NAMES = ["OTTOPORT_API_KEY", "OTTOPORT_BASE_URL"];
27
+
28
+ let DRY = false;
29
+ const done = [];
30
+ const notes = [];
31
+
32
+ function write(path, content) {
33
+ if (DRY) return void done.push(`would write ${path}`);
34
+ mkdirSync(dirname(path), { recursive: true });
35
+ if (existsSync(path)) copyFileSync(path, `${path}.bak`); // never edit a config without a copy beside it
36
+ writeFileSync(path, content);
37
+ done.push(`wrote ${path}${existsSync(`${path}.bak`) ? " (backup: .bak)" : ""}`);
38
+ }
39
+
40
+ function installSkill(dir) {
41
+ const dest = join(dir, "ottoport");
42
+ if (!existsSync(SKILL_SRC)) {
43
+ notes.push(`skill not found in this package (${SKILL_SRC}) — install ottoport@1.3.0 or newer`);
44
+ return;
45
+ }
46
+ if (DRY) return void done.push(`would install skill → ${dest}`);
47
+ mkdirSync(dir, { recursive: true });
48
+ cpSync(SKILL_SRC, dest, { recursive: true });
49
+ done.push(`installed skill → ${dest}`);
50
+ }
51
+
52
+ function hostCli(bin, args) {
53
+ const probe = spawnSync(bin, ["--version"], { stdio: "ignore" });
54
+ if (probe.error) return false;
55
+ if (DRY) {
56
+ done.push(`would run: ${bin} ${args.join(" ")}`);
57
+ return true;
58
+ }
59
+ const run = spawnSync(bin, args, { stdio: "inherit" });
60
+ if (run.status !== 0) throw new Error(`${bin} ${args[0]} ${args[1]} exited ${run.status}`);
61
+ done.push(`registered with ${bin}`);
62
+ return true;
63
+ }
64
+
65
+ // ── hosts ────────────────────────────────────────────────────────────
66
+
67
+ // Codex keeps MCP servers in TOML. `env_vars` forwards the named variables
68
+ // from the environment; the sibling `[...env]` table would hard-code values.
69
+ function codex() {
70
+ const path = join(CODEX_HOME, "config.toml");
71
+ const block =
72
+ `[mcp_servers.ottoport]\n` +
73
+ `command = "${COMMAND}"\n` +
74
+ `args = [${ARGS.map((a) => `"${a}"`).join(", ")}]\n` +
75
+ `env_vars = [${ENV_NAMES.map((n) => `"${n}"`).join(", ")}]\n`;
76
+
77
+ const current = existsSync(path) ? readFileSync(path, "utf8") : "";
78
+ // Replace an existing entry in place — from its header to the next
79
+ // top-of-line table header — so re-running is idempotent rather than additive.
80
+ const existing = /^\[mcp_servers\.ottoport\][^\n]*\n(?:(?!^\[)[^\n]*\n?)*/m;
81
+ const next = existing.test(current)
82
+ ? current.replace(existing, block)
83
+ : (current.trimEnd() + (current.trim() ? "\n\n" : "") + block);
84
+ write(path, next);
85
+ installSkill(join(CODEX_HOME, "skills"));
86
+ notes.push("restart Codex to pick up the server and skill");
87
+ }
88
+
89
+ // Claude Code has a real plugin (commands included), so the MCP-only path here
90
+ // is the fallback for someone who would rather not add the marketplace.
91
+ function claudeCode() {
92
+ if (!hostCli("claude", ["mcp", "add", "ottoport", "--", COMMAND, ...ARGS])) {
93
+ notes.push("claude CLI not found — run: claude mcp add ottoport -- npx -y ottoport mcp");
94
+ }
95
+ installSkill(join(HOME, ".claude/skills"));
96
+ notes.push(
97
+ "for the full plugin (slash commands too):\n" +
98
+ " /plugin marketplace add https://ottoport.ai/plugin/marketplace.json\n" +
99
+ " /plugin install ottoport@ottoport",
100
+ );
101
+ }
102
+
103
+ // Hermes stores MCP servers in YAML. Editing that by hand from here would risk
104
+ // mangling a user's config, and Hermes ships a command for exactly this.
105
+ function hermes() {
106
+ // `--args` takes the rest of the line, so it has to come last.
107
+ if (!hostCli("hermes", ["mcp", "add", "ottoport", "--command", COMMAND, "--args", ...ARGS])) {
108
+ notes.push(
109
+ "hermes CLI not found. Add to ~/.hermes/config.yaml:\n" +
110
+ " mcp_servers:\n ottoport:\n command: npx\n args: [-y, ottoport, mcp]",
111
+ );
112
+ }
113
+ notes.push("Hermes has no skill mechanism — the MCP tools carry their own descriptions");
114
+ }
115
+
116
+ // Cursor, Windsurf and Claude Desktop all read a JSON file with the same shape.
117
+ function jsonHost(label, path) {
118
+ const config = existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
119
+ config.mcpServers = {
120
+ ...config.mcpServers,
121
+ ottoport: { command: COMMAND, args: ARGS },
122
+ };
123
+ write(path, JSON.stringify(config, null, 2) + "\n");
124
+ notes.push(
125
+ `${label} launches the server from your desktop session, which may not see a key exported in a shell. ` +
126
+ `If the tools report 401, add an "env" block to the ottoport entry.`,
127
+ );
128
+ }
129
+
130
+ const HOSTS = {
131
+ codex,
132
+ claude: claudeCode,
133
+ "claude-code": claudeCode,
134
+ hermes,
135
+ cursor: () => jsonHost("Cursor", join(HOME, ".cursor/mcp.json")),
136
+ windsurf: () => jsonHost("Windsurf", join(HOME, ".codeium/windsurf/mcp_config.json")),
137
+ "claude-desktop": () =>
138
+ jsonHost(
139
+ "Claude Desktop",
140
+ platform() === "darwin"
141
+ ? join(HOME, "Library/Application Support/Claude/claude_desktop_config.json")
142
+ : join(HOME, ".config/Claude/claude_desktop_config.json"),
143
+ ),
144
+ };
145
+
146
+ export function installHosts() {
147
+ return Object.keys(HOSTS);
148
+ }
149
+
150
+ export async function cmdInstall(host, flags) {
151
+ DRY = flags["dry-run"] === true || flags.n === true;
152
+ const run = HOSTS[host];
153
+ if (!run) {
154
+ throw new Error(
155
+ `unknown host "${host ?? ""}". Try one of: ${installHosts().join(", ")}`,
156
+ );
157
+ }
158
+
159
+ run();
160
+
161
+ process.stdout.write(done.map((d) => ` ${d}\n`).join("") || " nothing to do\n");
162
+ if (!process.env.OTTOPORT_API_KEY && !process.env.OTTOPORT_KEY_SECRET) {
163
+ notes.unshift(
164
+ "OTTOPORT_API_KEY is not set in this environment. Export it from your shell profile " +
165
+ "(create a key at https://ottoport.ai/api-keys) — the server reads it at launch.",
166
+ );
167
+ }
168
+ if (notes.length) process.stdout.write("\n" + notes.map((n) => `note: ${n}\n`).join(""));
169
+ }
package/cli/ottoport.mjs CHANGED
@@ -10,6 +10,7 @@
10
10
  // ottoport speech "text" [--model gpt-4o-mini-tts] [--voice alloy] [--out speech.mp3]
11
11
  // ottoport music "prompt" [--model suno-v5] [--duration 15] [--out song.mp3]
12
12
  // ottoport mcp
13
+ // ottoport install <codex|claude|hermes|cursor|windsurf|claude-desktop>
13
14
  //
14
15
  // Config (flags override env):
15
16
  // OTTOPORT_API_KEY your op-… key (or the dev OTTOPORT_KEY_SECRET)
@@ -251,6 +252,9 @@ Usage:
251
252
  ottoport speech "<text>" [--model gpt-4o-mini-tts] [--voice alloy] [--format mp3] [--out speech.mp3]
252
253
  ottoport music "<prompt>" [--model suno-v5] [--duration 15] [--format mp3] [--out song.mp3]
253
254
  ottoport mcp
255
+ ottoport install <host> wire the MCP server (and skill) into an agent
256
+ hosts: codex, claude, hermes, cursor, windsurf, claude-desktop
257
+ [--dry-run]
254
258
 
255
259
  Global flags:
256
260
  --url <base> gateway base URL (env OTTOPORT_BASE_URL)
@@ -265,6 +269,7 @@ Examples:
265
269
  ottoport speech "Welcome to OttoPort" --voice alloy --out welcome.mp3
266
270
  ottoport music "lo-fi focus beat" --duration 20 --out focus.mp3
267
271
  ottoport mcp
272
+ ottoport install codex
268
273
  `;
269
274
 
270
275
  async function main() {
@@ -280,6 +285,10 @@ async function main() {
280
285
  case "speech": return await cmdAudio("speech", prompt, flags);
281
286
  case "music": return await cmdAudio("music", prompt, flags);
282
287
  case "mcp": return await import(new URL("../mcp/server.mjs", import.meta.url));
288
+ case "install": {
289
+ const { cmdInstall } = await import(new URL("./install.mjs", import.meta.url));
290
+ return await cmdInstall(rest[0], flags);
291
+ }
283
292
  case undefined:
284
293
  case "help":
285
294
  case "--help":
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: Run a prompt through any chat model on the OttoPort gateway
3
+ argument-hint: "<prompt> [--model <id>]"
4
+ allowed-tools: mcp__ottoport__ottoport_chat, mcp__ottoport__ottoport_list_models
5
+ ---
6
+
7
+ Send this to a model through the `ottoport_chat` MCP tool:
8
+
9
+ $ARGUMENTS
10
+
11
+ If the request names a model, pass it as `model`; otherwise let the tool default
12
+ to `claude-sonnet-5`. If the named model is rejected as unknown, call
13
+ `ottoport_list_models` with `modality: "chat"`, pick the closest real id, and say
14
+ which one you substituted.
15
+
16
+ Return the model's answer itself — do not summarize it away.
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Generate an image through the OttoPort gateway
3
+ argument-hint: "<prompt> [--model <id>] [--size 1024x1024]"
4
+ allowed-tools: mcp__ottoport__ottoport_generate_image, mcp__ottoport__ottoport_list_models, Bash(curl:*)
5
+ ---
6
+
7
+ Generate an image with the `ottoport_generate_image` MCP tool.
8
+
9
+ Request: $ARGUMENTS
10
+
11
+ - Pass `model` only if the user named one; the tool defaults to `gpt-image-2`.
12
+ - An edit of an existing image goes through `image_url`, not the prompt.
13
+ - The tool returns URLs, and provider URLs expire. Print the URL, and if the
14
+ user asked for a file, download it to the path they named with `curl -sSL`.
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: List the OttoPort model catalog with live pricing, optionally filtered by modality
3
+ argument-hint: "[chat|image|video|tts|music]"
4
+ allowed-tools: mcp__ottoport__ottoport_list_models
5
+ ---
6
+
7
+ List the OttoPort catalog using the `ottoport_list_models` MCP tool.
8
+
9
+ Modality filter (may be empty — then list everything): `$1`
10
+
11
+ Present the result as a table: model id, what it is good at, and price. Group by
12
+ modality when no filter was given, and keep it to the models worth choosing
13
+ between rather than every row. Never invent a model id that the tool did not
14
+ return.
@@ -0,0 +1,22 @@
1
+ ---
2
+ description: Check that the OttoPort plugin is wired up — key, gateway reachability, MCP tools
3
+ allowed-tools: Bash(node:*), Bash(printenv:*), mcp__ottoport__ottoport_list_models
4
+ ---
5
+
6
+ Verify this machine can reach OttoPort, then report what is and is not working.
7
+
8
+ 1. Read the environment: `printenv OTTOPORT_API_KEY OTTOPORT_BASE_URL` (print
9
+ only whether the key is set and what prefix it has — never echo the key).
10
+ 2. Call `ottoport_list_models`. Success means the MCP server, the base URL and
11
+ the key are all good; stop and report.
12
+ 3. If that fails, the fix depends on the error:
13
+ - no `ottoport` MCP tools at all → the plugin's MCP server did not start;
14
+ check `node --version` is 20+.
15
+ - `401`/`invalid key` → the key is missing or wrong. Keys are created at
16
+ https://ottoport.ai/api-keys and look like `op-…`.
17
+ - connection refused → `OTTOPORT_BASE_URL` points at a gateway that is not
18
+ running. It defaults to `https://ottoport.ai`; a local dev gateway is
19
+ `http://localhost:3014`.
20
+
21
+ Both variables belong in the user's shell profile, since the MCP server reads
22
+ them from the environment Claude Code was launched in.
@@ -0,0 +1,15 @@
1
+ ---
2
+ description: Turn text into speech, or a prompt into music, through the OttoPort gateway
3
+ argument-hint: "<text> [--voice alloy] [--music]"
4
+ allowed-tools: mcp__ottoport__ottoport_generate_speech, mcp__ottoport__ottoport_generate_music, Bash(curl:*)
5
+ ---
6
+
7
+ Request: $ARGUMENTS
8
+
9
+ Use `ottoport_generate_music` when the request is for music, a song, or a
10
+ backing track; otherwise use `ottoport_generate_speech` to read the text aloud.
11
+ Pass `voice` and `format` only when named — speech defaults to `gpt-4o-mini-tts`
12
+ with the `alloy` voice, music to `suno-v5`.
13
+
14
+ Print the returned audio URL, and save it to disk with `curl -sSL` only if the
15
+ user asked for a file.
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: Generate a video through the OttoPort gateway
3
+ argument-hint: "<prompt> [--model <id>] [--duration 5]"
4
+ allowed-tools: mcp__ottoport__ottoport_generate_video, mcp__ottoport__ottoport_list_models, Bash(curl:*)
5
+ ---
6
+
7
+ Generate a video with the `ottoport_generate_video` MCP tool.
8
+
9
+ Request: $ARGUMENTS
10
+
11
+ - Pass `model`, `duration`, `aspect_ratio` and `image_url` only when the request
12
+ supplies them; the tool defaults to `kling-3.0`.
13
+ - Video generation takes a minute or more and the call blocks until the job is
14
+ terminal. Say so before you start, and do not retry a call that is merely slow
15
+ — a retry is a second billable generation.
16
+ - Print the returned URL, and download it with `curl -sSL` only if the user asked
17
+ for a file on disk.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ottoport",
3
- "version": "1.2.2",
4
- "description": "CLI and MCP server for OttoPort — one OpenAI-compatible API for every LLM, image, video, and speech model.",
3
+ "version": "1.3.0",
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
  "license": "MIT",
7
7
  "keywords": [
@@ -14,7 +14,9 @@
14
14
  "text-to-speech",
15
15
  "mcp",
16
16
  "mcp-server",
17
- "cli"
17
+ "cli",
18
+ "claude-code",
19
+ "claude-code-plugin"
18
20
  ],
19
21
  "engines": {
20
22
  "node": ">=20.0.0"
@@ -22,46 +24,8 @@
22
24
  "bin": {
23
25
  "ottoport": "cli/ottoport.mjs"
24
26
  },
25
- "files": [
26
- "cli",
27
- "mcp",
28
- "README.md"
29
- ],
30
27
  "scripts": {
31
- "dev": "next dev -p 3014",
32
- "build": "next build",
33
- "start": "next start -p 3014",
34
- "lint": "next lint",
35
28
  "cli": "node cli/ottoport.mjs",
36
29
  "mcp": "node mcp/server.mjs"
37
- },
38
- "dependencies": {
39
- "@anthropic-ai/sdk": "^0.65.0",
40
- "@supabase/ssr": "^0.10.2",
41
- "@supabase/supabase-js": "^2.43.0",
42
- "@tailwindcss/typography": "^0.5.13",
43
- "clsx": "^2.1.1",
44
- "lucide-react": "^0.395.0",
45
- "next": "^14.2.0",
46
- "openai": "^4.104.0",
47
- "react": "^18.3.0",
48
- "react-dom": "^18.3.0",
49
- "sonner": "^1.5.0",
50
- "stripe": "^13.11.0",
51
- "swr": "^2.2.5",
52
- "tailwind-merge": "^2.3.0",
53
- "zod": "^3.23.8"
54
- },
55
- "devDependencies": {
56
- "@types/node": "^20.14.0",
57
- "@types/react": "^18.3.3",
58
- "@types/react-dom": "^18.3.0",
59
- "autoprefixer": "^10.4.19",
60
- "daisyui": "^4.12.0",
61
- "eslint": "^8.57.0",
62
- "eslint-config-next": "^14.2.0",
63
- "postcss": "^8.4.38",
64
- "tailwindcss": "^3.4.4",
65
- "typescript": "^5.4.5"
66
30
  }
67
31
  }
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: ottoport
3
+ description: Generate text, images, and video through OttoPort — one unified, OpenAI-compatible API for every model (GPT, Claude, FLUX, Nano Banana, Kling, Veo 3). Use when the user wants to call an LLM, generate an image, or generate a video and has OttoPort configured, or asks to "use OttoPort".
4
+ ---
5
+
6
+ # OttoPort
7
+
8
+ OttoPort is a single OpenAI-compatible gateway that routes to first-party model
9
+ providers. One key, one base URL, three modalities: **chat**, **image**, **video**.
10
+
11
+ ## Setup
12
+
13
+ Two environment variables drive every access path (CLI, MCP, raw HTTP):
14
+
15
+ - `OTTOPORT_API_KEY` — an `op-…` key (or the dev `OTTOPORT_KEY_SECRET`).
16
+ - `OTTOPORT_BASE_URL` — e.g. `https://ottoport.dev` or `http://localhost:3014`.
17
+
18
+ Check what's available before generating:
19
+
20
+ ```bash
21
+ node cli/ottoport.mjs models # full catalog with pricing
22
+ node cli/ottoport.mjs models --modality image
23
+ ```
24
+
25
+ ## Choosing a model
26
+
27
+ | Modality | Good default | Also available |
28
+ | --- | --- | --- |
29
+ | chat | `claude-sonnet-5` | `claude-haiku-4.5` (cheap), `gpt-5.5`, `gemini-3.5-flash` |
30
+ | image | `gpt-image-2` | `nano-banana-pro` (high fidelity), `nano-banana-2` (edits) |
31
+ | video | `kling-3.0` | `seedance-2.0-fast` (cheap), `veo-3.1` (with audio) |
32
+ | tts | `gpt-4o-mini-tts` | `eleven-v3` |
33
+ | music | `suno-v5` | `lyria-2` |
34
+
35
+ Never invent model ids — run `ottoport models` if unsure.
36
+
37
+ ## CLI (preferred for one-off calls)
38
+
39
+ ```bash
40
+ # Chat (streams by default)
41
+ node cli/ottoport.mjs chat "explain MCP in one sentence" --model claude-haiku-4.5
42
+ node cli/ottoport.mjs chat "summarize this" --system "You are terse." --no-stream
43
+
44
+ # Image → prints URL(s); --out downloads the first
45
+ node cli/ottoport.mjs image "isometric city at dusk" --model nano-banana-pro --out city.png
46
+
47
+ # Video → blocks on the queue, prints the URL
48
+ node cli/ottoport.mjs video "drone shot over a canyon" --duration 5 --out clip.mp4
49
+
50
+ # Speech and music → print an audio URL; --out downloads it
51
+ node cli/ottoport.mjs speech "Welcome to OttoPort" --voice alloy --out welcome.mp3
52
+ node cli/ottoport.mjs music "lo-fi focus beat" --duration 20 --out focus.mp3
53
+ ```
54
+
55
+ ## HTTP (for scripts / other languages)
56
+
57
+ The gateway is OpenAI-compatible, so existing SDKs work by pointing `baseURL`
58
+ at `${OTTOPORT_BASE_URL}/api/v1` with the OttoPort key.
59
+
60
+ ```bash
61
+ curl $OTTOPORT_BASE_URL/api/v1/chat/completions \
62
+ -H "Authorization: Bearer $OTTOPORT_API_KEY" -H "Content-Type: application/json" \
63
+ -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'
64
+ ```
65
+
66
+ Image: `POST /api/v1/images/generations` `{model, prompt, size?, n?, image_url?}` → `{data:[{url}]}`.
67
+ Video: `POST /api/v1/videos/generations` `{model, prompt, duration?, aspect_ratio?, image_url?}` → a job with `data:[{url}]`.
68
+
69
+ ## MCP (for agent tool use inside another client)
70
+
71
+ If OttoPort's MCP server is registered, prefer its tools over shelling out:
72
+ `ottoport_list_models`, `ottoport_chat`, `ottoport_generate_image`,
73
+ `ottoport_generate_video`, `ottoport_generate_speech`, `ottoport_generate_music`.
74
+ Register it with:
75
+
76
+ ```bash
77
+ claude mcp add ottoport -- node /abs/path/to/mcp/server.mjs
78
+ ```
79
+
80
+ ## Plugin (Claude Code)
81
+
82
+ Installing the OttoPort plugin brings the MCP server, this skill, and the
83
+ `/ottoport:*` slash commands in one step — no manual `claude mcp add`:
84
+
85
+ ```bash
86
+ /plugin marketplace add https://ottoport.ai/plugin/marketplace.json
87
+ /plugin install ottoport@ottoport
88
+ ```
89
+
90
+ Commands: `/ottoport:models`, `/ottoport:chat`, `/ottoport:image`,
91
+ `/ottoport:video`, `/ottoport:speech`, `/ottoport:setup`.
92
+
93
+ ## Tips
94
+
95
+ - Image and video calls return **URLs**, not files — download with `--out` (CLI)
96
+ or fetch the URL yourself. Provider URLs may be short-lived; persist anything
97
+ you need to keep.
98
+ - Video generation can take a minute; it blocks server-side and returns when done.
99
+ - Errors come back as `{error:{message, code}}`; surface the message to the user.