openai-cli
Call OpenAI's Platform API from the terminal. Implementation: TypeScript on deno with the official openai SDK (consumed via npm:openai@5 — no pnpm install, no node_modules). Tool versions pinned via mise. Total stack: 2 tools.
Hard rules
- Never hardcode model names in any TS file you write. Always resolve via
lib/resolveModel.ts <family> first. See references/models.md for family heuristics.
- Never read or print secrets. Use existence checks only. Forbidden:
echo $OPENAI_API_KEY, printenv | grep, cat ~/.zshrc, cat .env, logging Authorization headers. See references/security.md.
- Always use scoped deno permissions. Base perms:
--allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read=$HOME/.openai-cli --allow-write=$HOME/.openai-cli. Add --allow-read=<input> and --allow-write=<output> per use case — never blanket --allow-read / --allow-net.
- Cross-OS only. All non-trivial logic lives in
assets/lib/*.ts. No bash, no PowerShell scripts, no shell heredocs.
- Upgrade prompts are mandatory even in auto-mode / dangerous-permission mode. When
resolveModel.ts reports UPGRADE_NEEDED, you MUST use AskUserQuestion.
Path conventions
- Skill cache (read-only, version-pinned):
${CLAUDE_PLUGIN_ROOT}/skills/openai-cli/assets/lib/*.ts. Use this only for the very first setup.ts invocation. ${CLAUDE_PLUGIN_ROOT} is set when Claude orchestrates the skill; in a plain user shell, resolve to ~/.claude/plugins/cache/hideki-plugins/openai-cli/<version>/skills/openai-cli/assets/lib/.
- User workspace (stable, version-independent):
~/.openai-cli/lib/*.ts. After setup.ts runs once, all skill scripts are copied here. Every command after first-time setup runs from this stable path.
Preflight (before every call)
deno run --allow-env=OPENAI_API_KEY --allow-read --allow-run=mise,deno ~/.openai-cli/lib/preflight.ts
If any check fails:
mise missing → instruct platform install and stop. macOS: brew install mise. Linux: curl https://mise.run | sh. Windows: winget install jdx.mise.
deno missing → run cd ~/.openai-cli && mise install.
workspace missing → run setup (below).
OPENAI_API_KEY missing → instruct user to export it in their shell rc. Do not offer a .env fallback.
First-time setup (run once)
From inside Claude (uses skill cache):
deno run --allow-read --allow-write --allow-env --allow-run=mise ${CLAUDE_PLUGIN_ROOT}/skills/openai-cli/assets/lib/setup.ts
From a user shell (manual run, version-pinned path):
deno run --allow-read --allow-write --allow-env --allow-run=mise ~/.claude/plugins/cache/hideki-plugins/openai-cli/1.0.0/skills/openai-cli/assets/lib/setup.ts
setup.ts creates ~/.openai-cli/{lib,.cache,tmp}/, copies assets/lib/*.ts into ~/.openai-cli/lib/, copies the mise.toml template into ~/.openai-cli/.mise.toml, runs mise trust && mise install, and existence-checks OPENAI_API_KEY.
Then, once OPENAI_API_KEY is set, populate models.json (uses the stable workspace path now that setup has copied the scripts):
deno run --allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts --init
Full details: references/setup.md.
Per-call workflow
For every API use case:
- Preflight (above). Bail if anything fails.
- Resolve the model for the relevant family:
deno run --allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts <family>
Where <family> is one of: chat, reasoning, embeddings, image, transcription, tts, moderation.
- Exit 0: stdout is the resolved model id. Capture it.
- Exit 2: stdout is
UPGRADE_NEEDED:<family>:<current>:<newest>. Use AskUserQuestion with options "Upgrade to <newest>", "Stay on <current>", "Use <newest> just-this-once". Then:
- On Upgrade: run
deno run --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts --set <family> <newest> and re-run resolve.
- On Stay: run
deno run --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts --set <family> <current> (refreshes the prompt-suppress timestamp) and use <current>.
- On Just-this-once: skip the
--set; use <newest> for this call only.
- Exit 1: fatal — surface the error and stop.
- Write the per-call TS file under
~/.openai-cli/tmp/<task>-<timestamp>.ts. Do not invent model names — embed the resolved id from step 2 verbatim. Always include a final log line of the form # meta: model=<id> usage=<json> so the user sees what ran.
- Estimate cost if the call may be expensive (input > 5K tokens, output > 1K tokens, batch, multiple images). Surface the estimate and confirm with
AskUserQuestion before running.
- Run with scoped permissions:
deno run --allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read=$HOME/.openai-cli --allow-write=$HOME/.openai-cli [+ I/O scopes for this use case] ~/.openai-cli/tmp/<task>-<timestamp>.ts
- Surface the response to the user, including the meta line for transparency.
For each endpoint's specific TS template, see the matching reference file (table below).
Endpoint reference table
| Use case |
Family |
Reference |
| Chat completion |
chat |
references/chat.md |
| Responses API |
chat |
references/responses.md |
| Reasoning (o-series) |
reasoning |
references/reasoning.md |
| Embeddings + similarity |
embeddings |
references/embeddings.md |
| Image generation / edit |
image |
references/images.md |
| Audio transcription / translation / TTS |
transcription / tts |
references/audio.md |
| Files (upload, list, retrieve, delete) |
n/a |
references/files-and-batches.md |
| Batches |
n/a |
references/files-and-batches.md |
| Moderation |
moderation |
references/moderations.md |
| Models (list / retrieve) |
n/a |
references/models.md |
For streaming, tool/function calling, structured outputs (zod), see references/advanced.md. For error handling and rate limits: references/error-handling.md.
Out of scope
The following are deliberately deferred from v1. If the user asks for any of these, surface this list and explain the deferral rather than silently failing:
Behavior scenarios
Scenario: Chat completion
When the user asks the skill a question
Then the skill returns the model's answer using the per-session-resolved chat model
Scenario: Vision request
When the user attaches an image and asks for a description
Then the skill returns the model's description with the image attached as chat content
Scenario: Structured output
When the user asks for a JSON-shaped response (e.g., extract entities into a schema)
Then the skill returns a typed object matching the requested shape
Scenario: Embeddings and similarity
When the user asks to embed N strings and find the closest pair
Then the skill returns the closest pair and their similarity score
Scenario: Image generation
When the user asks to generate an image and gives an output path
Then the skill writes the image file to that path and reports the location
Scenario: Audio transcription
When the user provides an audio file and asks for a transcript
Then the skill returns the transcript text and (optionally) writes a .txt/.vtt sidecar
Scenario: Text-to-speech
When the user asks to convert text to speech and gives an output path
Then the skill writes the audio file to that path and reports the location
Scenario: Moderation
When the user asks to moderate a string
Then the skill returns category scores from the moderation model
Scenario: Batch submission
When the user asks to run an embedding/completion job over thousands of records
Then the skill estimates cost, asks for confirmation, submits the batch, and returns the batch ID
Scenario: Cost gate triggered
When a request would consume substantial tokens (large input, big output, batch)
Then the skill surfaces a cost estimate and asks for confirmation before sending
Scenario: Newer model detected
When resolveModel.ts emits UPGRADE_NEEDED
Then the skill asks the user "Upgrade / Stay / Just-this-once" via AskUserQuestion —
even in auto-mode or dangerous-permission mode
And the skill never silently upgrades
Scenario: User pins a specific model
When the user names an exact model id for a single call
Then the skill uses that id for this call only and does not update the persisted preference
Scenario: User asks the skill to read the API key
When the user asks to print or reveal OPENAI_API_KEY
Then the skill refuses and points the user to their own (non-agent) terminal
Scenario: User asks for ChatGPT chat history
When the user asks the skill to read their ChatGPT chats
Then the skill explains that no API exposes that data and describes the Data Controls export route
Scenario: User asks for Assistants API features
When the user mentions OpenAI's Assistants / Threads / Runs / Vector Stores
Then the skill explains those are OpenAI products being sunset on 2026-08-26
(unrelated to Claude or this skill) and steers the user to the Responses API
Scenario: API key missing
When the user invokes any use case without OPENAI_API_KEY exported
Then the skill stops and explains how to export the key in their shell rc (no .env fallback)
Scenario: API failure mid-call
When the OpenAI API returns a rate-limit, auth error, or network failure
Then the skill surfaces a sanitized error message (no auth header / key value leakage)
and suggests the appropriate retry or fix
Retrospective
After completing a non-trivial use case, briefly note in feedback/log.md:
- What the user asked
- Which family/model resolved
- Any unexpected behavior (rate limit, missing scope, surprising response)
This feeds the improvement loop — see references/error-handling.md for the format.
1---2name: openai-cli3description: Call the OpenAI Platform API from the terminal via TypeScript on deno — chat completions, Responses API, reasoning (o-series), embeddings, image generation, audio (Whisper transcription, TTS), files, batches, moderation, and model listing. Two-tool stack: mise + deno (no node_modules, no pnpm, no bash). Models are resolved dynamically per session against `client.models.list()` — never hardcoded — and any detected upgrade prompts the user via AskUserQuestion (even in auto-mode). Secrets stay outside the agent: existence-only key checks, scoped `--allow-env=OPENAI_API_KEY` and `--allow-net=api.openai.com` flags, no `.env` files. Scoped to the Platform API specifically — for open-ended chat/conversation ("ask gpt", "chat with gpt") prefer the codex-server skill instead, which uses the user's ChatGPT subscription rather than API-key billing. Use this skill only when the request is explicitly about the Platform API surface (embeddings, image generation, audio, moderation, batches, files, or a chat/Responses call m4---56# openai-cli78Call OpenAI's Platform API from the terminal. Implementation: TypeScript on **deno** with the official `openai` SDK (consumed via `npm:openai@5` — no `pnpm install`, no `node_modules`). Tool versions pinned via **mise**. Total stack: 2 tools.910## Hard rules11121. **Never hardcode model names** in any TS file you write. Always resolve via `lib/resolveModel.ts <family>` first. See `references/models.md` for family heuristics.132. **Never read or print secrets.** Use existence checks only. Forbidden: `echo $OPENAI_API_KEY`, `printenv | grep`, `cat ~/.zshrc`, `cat .env`, logging `Authorization` headers. See `references/security.md`.143. **Always use scoped deno permissions.** Base perms: `--allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read=$HOME/.openai-cli --allow-write=$HOME/.openai-cli`. Add `--allow-read=<input>` and `--allow-write=<output>` per use case — never blanket `--allow-read` / `--allow-net`.154. **Cross-OS only.** All non-trivial logic lives in `assets/lib/*.ts`. No bash, no PowerShell scripts, no shell heredocs.165. **Upgrade prompts are mandatory** even in auto-mode / dangerous-permission mode. When `resolveModel.ts` reports `UPGRADE_NEEDED`, you MUST use `AskUserQuestion`.1718## Path conventions1920- **Skill cache** (read-only, version-pinned): `${CLAUDE_PLUGIN_ROOT}/skills/openai-cli/assets/lib/*.ts`. Use this only for the very first `setup.ts` invocation. `${CLAUDE_PLUGIN_ROOT}` is set when Claude orchestrates the skill; in a plain user shell, resolve to `~/.claude/plugins/cache/hideki-plugins/openai-cli/<version>/skills/openai-cli/assets/lib/`.21- **User workspace** (stable, version-independent): `~/.openai-cli/lib/*.ts`. After `setup.ts` runs once, all skill scripts are copied here. Every command after first-time setup runs from this stable path.2223## Preflight (before every call)2425```26deno run --allow-env=OPENAI_API_KEY --allow-read --allow-run=mise,deno ~/.openai-cli/lib/preflight.ts27```2829If any check fails:30- `mise` missing → instruct platform install and stop. macOS: `brew install mise`. Linux: `curl https://mise.run | sh`. Windows: `winget install jdx.mise`.31- `deno` missing → run `cd ~/.openai-cli && mise install`.32- `workspace` missing → run setup (below).33- `OPENAI_API_KEY` missing → instruct user to export it in their shell rc. **Do not** offer a `.env` fallback.3435## First-time setup (run once)3637From inside Claude (uses skill cache):38```39deno run --allow-read --allow-write --allow-env --allow-run=mise ${CLAUDE_PLUGIN_ROOT}/skills/openai-cli/assets/lib/setup.ts40```4142From a user shell (manual run, version-pinned path):43```44deno run --allow-read --allow-write --allow-env --allow-run=mise ~/.claude/plugins/cache/hideki-plugins/openai-cli/1.0.0/skills/openai-cli/assets/lib/setup.ts45```4647`setup.ts` creates `~/.openai-cli/{lib,.cache,tmp}/`, copies `assets/lib/*.ts` into `~/.openai-cli/lib/`, copies the `mise.toml` template into `~/.openai-cli/.mise.toml`, runs `mise trust && mise install`, and existence-checks `OPENAI_API_KEY`.4849Then, once `OPENAI_API_KEY` is set, populate `models.json` (uses the stable workspace path now that setup has copied the scripts):5051```52deno run --allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts --init53```5455Full details: `references/setup.md`.5657## Per-call workflow5859For every API use case:60611. **Preflight** (above). Bail if anything fails.622. **Resolve the model** for the relevant family:63 ```64 deno run --allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts <family>65 ```66 Where `<family>` is one of: `chat`, `reasoning`, `embeddings`, `image`, `transcription`, `tts`, `moderation`.67 - **Exit 0**: stdout is the resolved model id. Capture it.68 - **Exit 2**: stdout is `UPGRADE_NEEDED:<family>:<current>:<newest>`. Use `AskUserQuestion` with options "Upgrade to `<newest>`", "Stay on `<current>`", "Use `<newest>` just-this-once". Then:69 - On Upgrade: run `deno run --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts --set <family> <newest>` and re-run resolve.70 - On Stay: run `deno run --allow-read --allow-write ~/.openai-cli/lib/resolveModel.ts --set <family> <current>` (refreshes the prompt-suppress timestamp) and use `<current>`.71 - On Just-this-once: skip the `--set`; use `<newest>` for this call only.72 - **Exit 1**: fatal — surface the error and stop.733. **Write the per-call TS file** under `~/.openai-cli/tmp/<task>-<timestamp>.ts`. Do not invent model names — embed the resolved id from step 2 verbatim. Always include a final log line of the form `# meta: model=<id> usage=<json>` so the user sees what ran.744. **Estimate cost** if the call may be expensive (input > 5K tokens, output > 1K tokens, batch, multiple images). Surface the estimate and confirm with `AskUserQuestion` before running.755. **Run with scoped permissions**:76 ```77 deno run --allow-env=OPENAI_API_KEY --allow-net=api.openai.com --allow-read=$HOME/.openai-cli --allow-write=$HOME/.openai-cli [+ I/O scopes for this use case] ~/.openai-cli/tmp/<task>-<timestamp>.ts78 ```796. **Surface the response** to the user, including the meta line for transparency.8081For each endpoint's specific TS template, see the matching reference file (table below).8283## Endpoint reference table8485| Use case | Family | Reference |86|----------|--------|-----------|87| Chat completion | `chat` | [references/chat.md](references/chat.md) |88| Responses API | `chat` | [references/responses.md](references/responses.md) |89| Reasoning (o-series) | `reasoning` | [references/reasoning.md](references/reasoning.md) |90| Embeddings + similarity | `embeddings` | [references/embeddings.md](references/embeddings.md) |91| Image generation / edit | `image` | [references/images.md](references/images.md) |92| Audio transcription / translation / TTS | `transcription` / `tts` | [references/audio.md](references/audio.md) |93| Files (upload, list, retrieve, delete) | n/a | [references/files-and-batches.md](references/files-and-batches.md) |94| Batches | n/a | [references/files-and-batches.md](references/files-and-batches.md) |95| Moderation | `moderation` | [references/moderations.md](references/moderations.md) |96| Models (list / retrieve) | n/a | [references/models.md](references/models.md) |9798For streaming, tool/function calling, structured outputs (zod), see [references/advanced.md](references/advanced.md). For error handling and rate limits: [references/error-handling.md](references/error-handling.md).99100## Out of scope101102The following are **deliberately deferred** from v1. If the user asks for any of these, surface this list and explain the deferral rather than silently failing:103104- [ ] **Fine-tuning** (`client.fineTuning.jobs.*`) — high-cost, error-prone. Likely a separate `openai-finetune` skill in future, with cost gates.105- [ ] **Realtime API** (websocket, voice agents) — stateful and complex. Likely a dedicated `openai-realtime` skill.106- [ ] **Assistants API** (`client.beta.assistants.*`, threads, runs) — **OpenAI is sunsetting this on 2026-08-26**. Note: this is OpenAI's stateful-assistants product, unrelated to Claude or any Claude skill feature. The v1 skill refuses Assistants calls and steers users to the **Responses API**, OpenAI's official migration target.107- [ ] **Vector Stores** — tied to Assistants on the same deprecation track. Same handling.108- [ ] **ChatGPT chat history** — the API does not expose chats from chat.openai.com / mobile / desktop. The only sanctioned route is **Settings → Data Controls → Export Data**, which emails you a ZIP with `conversations.json`. Future work: a separate `chatgpt-export-reader` skill that parses that file locally.109- [ ] **Persistent-process optimization** — currently each `deno run` cold-starts. If session latency becomes a problem, consider `deno compile`-ing a single binary or running a localhost daemon.110111## Behavior scenarios112113```gherkin114Scenario: Chat completion115 When the user asks the skill a question116 Then the skill returns the model's answer using the per-session-resolved chat model117118Scenario: Vision request119 When the user attaches an image and asks for a description120 Then the skill returns the model's description with the image attached as chat content121122Scenario: Structured output123 When the user asks for a JSON-shaped response (e.g., extract entities into a schema)124 Then the skill returns a typed object matching the requested shape125126Scenario: Embeddings and similarity127 When the user asks to embed N strings and find the closest pair128 Then the skill returns the closest pair and their similarity score129130Scenario: Image generation131 When the user asks to generate an image and gives an output path132 Then the skill writes the image file to that path and reports the location133134Scenario: Audio transcription135 When the user provides an audio file and asks for a transcript136 Then the skill returns the transcript text and (optionally) writes a .txt/.vtt sidecar137138Scenario: Text-to-speech139 When the user asks to convert text to speech and gives an output path140 Then the skill writes the audio file to that path and reports the location141142Scenario: Moderation143 When the user asks to moderate a string144 Then the skill returns category scores from the moderation model145146Scenario: Batch submission147 When the user asks to run an embedding/completion job over thousands of records148 Then the skill estimates cost, asks for confirmation, submits the batch, and returns the batch ID149150Scenario: Cost gate triggered151 When a request would consume substantial tokens (large input, big output, batch)152 Then the skill surfaces a cost estimate and asks for confirmation before sending153154Scenario: Newer model detected155 When resolveModel.ts emits UPGRADE_NEEDED156 Then the skill asks the user "Upgrade / Stay / Just-this-once" via AskUserQuestion —157 even in auto-mode or dangerous-permission mode158 And the skill never silently upgrades159160Scenario: User pins a specific model161 When the user names an exact model id for a single call162 Then the skill uses that id for this call only and does not update the persisted preference163164Scenario: User asks the skill to read the API key165 When the user asks to print or reveal OPENAI_API_KEY166 Then the skill refuses and points the user to their own (non-agent) terminal167168Scenario: User asks for ChatGPT chat history169 When the user asks the skill to read their ChatGPT chats170 Then the skill explains that no API exposes that data and describes the Data Controls export route171172Scenario: User asks for Assistants API features173 When the user mentions OpenAI's Assistants / Threads / Runs / Vector Stores174 Then the skill explains those are OpenAI products being sunset on 2026-08-26175 (unrelated to Claude or this skill) and steers the user to the Responses API176177Scenario: API key missing178 When the user invokes any use case without OPENAI_API_KEY exported179 Then the skill stops and explains how to export the key in their shell rc (no .env fallback)180181Scenario: API failure mid-call182 When the OpenAI API returns a rate-limit, auth error, or network failure183 Then the skill surfaces a sanitized error message (no auth header / key value leakage)184 and suggests the appropriate retry or fix185```186187## Retrospective188189After completing a non-trivial use case, briefly note in `feedback/log.md`:190- What the user asked191- Which family/model resolved192- Any unexpected behavior (rate limit, missing scope, surprising response)193194This feeds the improvement loop — see `references/error-handling.md` for the format.