@hyphen_solution/cli
v0.1.0
Published
Command line interface for the Hyphen AI gateway — chat, model list, agent setup, diagnostics and an MCP server.
Downloads
86
Maintainers
Readme
@hyphen_solution/cli
The command line for Hyphen — an OpenAI-compatible AI gateway at
https://api.hyphen-solution.com/v1.
hyphen login # store and verify your key
hyphen chat "explain CRDTs" # one-shot completion
hyphen setup claude-code --write # wire up your coding agent
hyphen doctor # find out what's brokenBuilt for humans and for agents: every command that emits data supports --json with a
documented, stable shape; every failure mode has its own exit code; and hyphen help --agent
prints the whole surface on one screen for pasting into an LLM's context.
- No telemetry. Nothing is ever sent anywhere except the gateway you point it at.
- Your API key is never printed in full. Not in output, not in errors, not in logs.
- Zero runtime dependencies. Node's builtins only.
Install
npm install -g @hyphen_solution/cli
pnpm add -g @hyphen_solution/cli
yarn global add @hyphen_solution/cli
bun add -g @hyphen_solution/cliOr run it without installing:
npx @hyphen_solution/cli models
pnpm dlx @hyphen_solution/cli models
yarn dlx @hyphen_solution/cli models
bunx @hyphen_solution/cli modelsRequires Node 20 or newer. Verify with hyphen --version. No runtime
dependencies. All four package managers are tested against the packed tarball
before release.
From a checkout:
npm install
npm run build
npm link # puts `hyphen` on your PATHQuickstart
# 1. Get a key at https://app.hyphen-solution.com -> Dashboard -> API Keys
hyphen login
# 2. Check everything works
hyphen doctor
# 3. See what you can call
hyphen models
# 4. Use it
hyphen chat "write a haiku about type systems" --stream
# 5. Point your coding agent at it
hyphen setup --list
hyphen setup claude-code --write --with-keyConfiguration
Credentials live at ~/.hyphen/config.json, written 0600 inside a 0700 directory.
Key precedence, everywhere:
--keyflagHYPHEN_API_KEYenvironment variable~/.hyphen/config.json
So CI never needs hyphen login — set HYPHEN_API_KEY and every command picks it up.
| Variable | Purpose |
| ------------------- | -------------------------------------------------------------------- |
| HYPHEN_API_KEY | API key. Overrides the stored config everywhere. |
| HYPHEN_BASE_URL | Gateway base URL including /v1. Default https://api.hyphen-solution.com/v1. |
| HYPHEN_MODEL | Default model for chat and mcp. Default minimax-m2.7. |
| HYPHEN_CONFIG_DIR | Where config.json and state.json live. Default ~/.hyphen. |
| NO_COLOR | Set to disable ANSI colour. |
Commands
Run hyphen <command> --help for the full reference on any of these.
hyphen login
Store an API key, after verifying it with GET /v1/models — a typo fails immediately rather than
at your next request. With no --key, the key is read from an interactive prompt with echo
disabled; when stdin is a pipe, the first line of stdin is used.
hyphen login
hyphen login --key sk-xxxxxxxx
echo "$MY_KEY" | hyphen loginhyphen logout
Delete ~/.hyphen/config.json. Warns if HYPHEN_API_KEY is still set in your environment, since
that would keep working.
hyphen whoami
Show which key is in use (masked), where it came from, and whether the gateway accepts it.
$ hyphen whoami
key sk-a1b2...f9e8 (from HYPHEN_API_KEY)
base url https://api.hyphen-solution.com/v1 (default)
default model minimax-m2.7
status authenticated 10 models availablehyphen models
List the catalog your key can reach, straight from GET /v1/models.
hyphen models
hyphen models --json | jq -r '.models[].id'hyphen chat ["<prompt>"]
Interactive with no arguments, one-shot with a prompt.
hyphen chat # interactive session
hyphen chat "explain the borrow checker in two sentences"
hyphen chat "give me an example" -c # continue the last thread
hyphen chat "find the bug" --file src/a.ts,src/b.ts
hyphen chat "summarise this" < README.md
hyphen chat "write a haiku" --stream
hyphen chat "list three colours" --json | jq -r .contentIn an interactive session: /model <id>, /system <text>, /reset, /save <file>, /tokens,
/exit. The thread is saved on exit and -c resumes it.
Input comes from a prompt argument, or piped stdin when no argument is given, or both — the
argument becomes the instruction and stdin is appended as context. --file adds file contents as
labelled blocks.
| Flag | Default | Notes |
| ------------------ | -------------- | ------------------------------------------- |
| --model | minimax-m2.7 | Any ID from hyphen models. |
| --max-tokens | 2000 | See the warning below. |
| --system | — | System prompt. |
| -c, --continue | off | Continue the saved conversation. |
| --file | — | Comma-separated files to attach. |
| --stream | off | SSE; tokens are printed as they arrive. |
| --show-reasoning | off | Print internal <think> output on stderr. |
| --json | off | One JSON document on stdout. |
Conversations live in ~/.hyphen/last-conversation.json. Only the transcript is stored, never
your key.
hyphen explain [<file>...]
Explain code, or diagnose a failure. It picks the mode from the input and you can force it with
--error or --code.
hyphen explain src/api.ts
npm test 2>&1 | hyphen explain
hyphen explain src/repl.ts --question "how is the thread saved?"hyphen commit
Write a commit message from the staged diff. Prints it by default; --apply commits with it.
hyphen commit
hyphen commit --apply
hyphen commit --json | jq -r .messageLarge diffs are trimmed before sending, so one enormous change cannot quietly cost a fortune.
hyphen review
Review changes before pushing. Working tree by default.
hyphen review
hyphen review --staged --stream
hyphen review --against mainA second opinion, not a gate. It misses things and is sometimes confidently wrong.
hyphen batch <file|->
Run a file of prompts concurrently and write JSONL. Built for unattended runs.
hyphen batch prompts.txt --output results.jsonl
hyphen batch titles.txt --template "Summarise: {{input}}" --concurrency 8
hyphen batch prompts.txt --output results.jsonl --resumeInput is one prompt per line, or JSONL with {"id","prompt"} plus optional system and model.
Both forms can be mixed. Every output record carries its id, so results match back to inputs
regardless of completion order.
| Flag | Default | Notes |
| --------------- | ------- | ------------------------------------------------------------ |
| --template | — | Applied to every line; {{input}} is the line. |
| --concurrency | 4 | Requests in flight, max 32. |
| --retries | 4 | Per prompt, with exponential backoff. |
| --output | stdout | JSONL destination. |
| --resume | off | Skip ids already succeeded in --output. Needs --output. |
Transient failures are retried. A single bad prompt is recorded and skipped. If the monthly budget
runs out the whole run stops immediately, because every remaining request would fail the same
way — exit code 5 says that happened, and --resume continues later.
max_tokens matters more than you'd expect. These are reasoning models: they spend tokens thinking before they answer, out of the same budget. A small
max_tokenscan be consumed entirely by internal reasoning and return empty content. The default is 2000, and the CLI warns you below 256.
Reply text goes to stdout. Token counts and warnings go to stderr, so hyphen chat ... > out.txt
gives you exactly the reply.
hyphen setup <tool>
The important one. Prints — and with --write applies — the correct config for your coding tool,
including the single mistake that most often breaks that particular tool.
hyphen setup --list # every supported tool
hyphen setup claude-code # print the config
hyphen setup claude-code --write --with-key
hyphen setup aider --model minimax-m3
hyphen setup zed --json | jq -r '.files[0].content'| Tool | Config written | The gotcha it handles for you |
| ------------- | ---------------------------------- | -------------------------------------------------------------------------- |
| claude-code | ~/.claude/settings.json | Model tiers must be remapped, or every request 400s on the model name. |
| codex | ~/.codex/config.toml | wire_api = "responses" — the only value Codex supports since Feb 2026. |
| cursor | UI only | Base URL must end in /v1. |
| cline | UI only | Needs output headroom or replies come back empty. |
| aider | ~/.aider.conf.yml | openai/ model prefix and OPENAI_API_BASE, not OPENAI_BASE_URL. |
| zed | ~/.config/zed/settings.json | Key goes in HYPHEN_API_KEY, never in the committed settings file. |
| continue | ~/.continue/config.json | provider: "openai" with apiBase; there is no native Hyphen provider. |
| opencode | ~/.config/opencode/opencode.json | model is provider_id/model_id, not a bare name. |
| goose | ~/.config/goose/config.yaml | OPENAI_HOST + OPENAI_BASE_PATH; Goose ignores OPENAI_BASE_URL. |
| openhands | env vars / UI | openai/ prefix, and the CLI ignores env vars without --override-with-envs. |
| kilo-code | ~/.config/kilo/kilo.jsonc | tool_call: true is required or the agent cannot edit files. |
--write never destroys your settings:
- JSON configs are deep-merged — your existing keys survive.
- TOML/YAML configs get a clearly marked block appended.
- An existing file is always copied to
<file>.hyphen.bakfirst. - If a Hyphen block is already present, the write is skipped (exit 1) unless you pass
--force. - If appending would corrupt the file — top-level TOML keys after a
[table]header, or a duplicate top-level YAML key — the write is refused with an explanation instead.
By default the config contains a sk-YOUR_KEY placeholder. Pass --with-key to inline the key
the CLI is configured with. (Configs that read the key from the environment, like Codex, Zed and
Kilo Code, never inline it at all — that is deliberate.)
hyphen doctor
Six checks, one line each, with the exit code of the first failure.
$ hyphen doctor
gateway https://api.hyphen-solution.com/v1 key sk-a1b2...f9e8 probe model minimax-m2.7
PASS API key present sk-a1b2...f9e8 from HYPHEN_API_KEY
PASS Gateway reachable https://api.hyphen-solution.com/v1 responded 214ms
PASS Key accepted GET /v1/models returned 200
PASS Model catalog 10 models: minimax-m2, minimax-m2-her, minimax-m2.1, minimax-m2.1-highspeed, ...
PASS Completion round-trip minimax-m2.7 answered (18 tokens) 1240ms
PASS Budget history no 429 recorded on this machine
All checks passed.hyphen doctor alone tells a script what is wrong: exit 3 no key, 4 bad key, 5 budget or
rate limit, 6 network, 8 gateway error.
hyphen mcp
Runs an MCP server over stdio. See MCP setup below.
hyphen help --agent
Prints a compact, machine-oriented summary of every command, flag, exit code and JSON shape,
designed to be pasted straight into an agent's context. hyphen help --json gives the same
information as a structured document.
Exit codes
Every code has exactly one meaning. They are part of the CLI's public contract.
| Code | Name | Meaning |
| ---- | ---------------- | -------------------------------------------------------------------- |
| 0 | OK | Success. |
| 1 | ERROR | Unexpected or unclassified failure. |
| 2 | USAGE | Bad usage: unknown command or flag, missing argument. |
| 3 | NO_CREDENTIALS | No API key configured (not logged in, HYPHEN_API_KEY unset). |
| 4 | AUTH | Key rejected by the gateway (HTTP 401/403). |
| 5 | RATE_LIMIT | HTTP 429: monthly budget exceeded, or a fair-use rate limit. |
| 6 | NETWORK | Could not reach the gateway (DNS, TLS, connection refused, timeout). |
| 7 | API | Gateway returned a 4xx other than 401/403/429. |
| 8 | SERVER | Gateway returned a 5xx. |
Note that 4 and 5 are distinct: a rejected key is not the same problem as a spent budget, and a script should not treat them the same way.
Machine-readable output
--json is available on every command that emits data. Success payloads go to stdout, always
as exactly one JSON document. Errors go to stderr.
Error envelope:
{
"ok": false,
"error": {
"code": "budget_exceeded",
"message": "Budget has been exceeded! Monthly budget resets on 2026-08-01.",
"exit_code": 5,
"status": 429,
"hint": "Monthly budget spent. It resets on 2026-08-01. ...",
"reset_date": "2026-08-01",
"retry_after_seconds": 1209600,
"rate_limit_kind": "budget"
}
}error.code is one of: usage, no_credentials, auth_failed, budget_exceeded,
rate_limited, network, api_error, server_error, error.
Success shapes, one per command:
| Command | --json shape |
| -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| login | {ok, key, base_url, config_path, model_count} |
| logout | {ok, removed, config_path, env_key_still_set} |
| whoami | {ok, key, key_source, base_url, base_url_source, config_path, default_model, authenticated, model_count, error?} |
| models | {ok, base_url, count, models:[{id, owned_by, note}]} |
| chat | {ok, model, content, finish_reason, usage:{prompt_tokens,completion_tokens,total_tokens}, streamed, max_tokens} |
| setup | {ok, tool, title, docs_url, writable, base_url, model, key, key_inlined, gotcha, files:[...], env:[...], steps, notes, verify, written:[...]} |
| doctor | {ok, base_url, key, key_source, probe_model, exit_code, checks:[{id,title,status,detail,latency_ms,error_code}]} |
| help | The full command spec: {name, version, commands:[...], exit_codes, environment, error_codes, setup_tools} |
Handling 429
A 429 means one of two very different things, and the CLI tells them apart:
- Budget (
error.rate_limit_kind: "budget", codebudget_exceeded) — the monthly cap on the key is spent.reset_datecarries the day it comes back. Retrying is pointless. Buy a credit pack or switch to a credit key. - Fair use (
rate_limited) — requests-per-minute or tokens-per-minute. Back off a few seconds and retry;retry_after_secondsis the server's hint.
Both exit 5; branch on error.code or error.rate_limit_kind when you need to distinguish.
MCP setup
hyphen mcp exposes Hyphen to any MCP-capable agent over stdio (JSON-RPC 2.0, one message per
line). It implements initialize, notifications/initialized, ping, tools/list and
tools/call, and negotiates protocol versions 2025-06-18, 2025-03-26 and 2024-11-05.
Tools exposed:
| Tool | Input | Returns |
| --------------------- | ---------------------------------------- | -------------------------------------------------------------------- |
| hyphen_chat | {prompt, model?, max_tokens?, system?} | The model's reply. |
| hyphen_list_models | {} | Model IDs the key can reach. |
| hyphen_check_budget | {} | Whether a 429 was seen, its kind, and the reset date if known. |
Claude Code
claude mcp add hyphen -- hyphen mcpAnything with an mcpServers block
Cursor (~/.cursor/mcp.json), Windsurf, Zed, Cline and most other clients:
{
"mcpServers": {
"hyphen": {
"command": "hyphen",
"args": ["mcp"],
"env": {
"HYPHEN_API_KEY": "sk-YOUR_KEY"
}
}
}
}The env block is optional — the server uses the same key resolution as every other command, so
if you have run hyphen login it already has what it needs.
Verifying it by hand
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| hyphen mcpA missing API key does not stop the server from starting — tools/call returns a structured
error instead, so the agent can report the problem rather than silently dying.
hyphen_check_budget reports from local history: the CLI records any 429 it sees in
~/.hyphen/state.json. There is no budget-query endpoint on the public API, so a fresh machine
correctly reports "no 429 recorded" rather than guessing.
Notes on the models
Ten models, one base URL. hyphen models is always authoritative; this is the shape of it:
minimax-m3— flagship, for hard reasoning and long agent runs.minimax-m2.7— balanced daily driver, the CLI's default forchat.minimax-m2.5— fast and cheap; good for background and commit-message work.*-highspeedvariants — same weights, latency-tuned serving, exactly 2x the rate.minimax-m2.1,minimax-m2— older generations, kept for compatibility.minimax-m2-her— roleplay/dialogue tuning. Don't wire it into a coding agent.minimax-text-01— 4M context, cheapest per token.
Two behaviours worth knowing:
max_tokenscan be eaten by reasoning. These models think before they answer, out of the same output budget. Too small a cap returns empty content rather than a short answer. Use 2000 as a floor for chat, 4000 for anything agentic.tool_choiceforcing does not work."required"and named-function forcing are ignored by these models. Only"auto"and"none"take effect. Design your tool loops accordingly.
Scope
This CLI only calls public /v1/* endpoints. Admin routes (/key/*, /user/*) are blocked at
the gateway's public edge, so there are deliberately no key-management or usage-reporting
commands here — do that in the console.
Development
npm install
npm run build # tsc -> dist/
npm test # builds, then runs node:test over test/Tests cover the pure parts — argument parsing, config precedence and masking, exit-code mapping, the setup-config generator for every tool, and the merge/write logic. None of them touch the network.
Links
- Developer docs: https://docs.hyphen-solution.com
- Console: https://app.hyphen-solution.com
- Support: [email protected]
