# `@velum-labs/routekit`

`packages/cli` publishes the independent `@velum-labs/routekit` npm package
and its `routekit` executable. It configures and serves model routes directly;
it does not start auxiliary sidecars or download local models.

## Install

```sh
curl -fsSL https://github.com/velum-labs/routekit/releases/download/routekit-latest/install.sh | sh
routekit setup
routekit codex
```

Or, with Node.js 22 or newer already on PATH: `npm install -g @velum-labs/routekit`.
Upgrade later with `routekit self-update`.

Self-update proves which installation owns the running executable before it
mutates anything. It updates public-installer, npm, pnpm, Yarn Classic, Bun, and
Volta installations on macOS and Linux. Homebrew, apt/dpkg, rpm/dnf, pacman,
Snap, and Nix installations receive manager-specific guidance instead. Local,
linked, ephemeral, ambiguous, and unknown installations are never overwritten.

`routekit setup` is the interactive multi-route wizard. It reads existing API
credentials from the environment, performs live discovery before writing a
fresh config, enrolls selected subscriptions, and selects a live default
model. Use `routekit config init --provider <provider>` or
`routekit config init --empty` for deterministic automation.

The singleton daemon loads `~/.config/routekit/router.yaml`; replace that
canonical document from a project file explicitly with
`routekit config import --from .routekit/router.yaml`. Import validates and
atomically replaces the complete document; it does not merge configuration.
Embedded callers can supply one explicit complete config document.
`ROUTEKIT_HOME` relocates runtime state.

## Local checkout development

Contributors can install a separate global `routekit-dev` command that always
runs their local checkout instead of the published npm package:

```bash
corepack pnpm install --frozen-lockfile
corepack pnpm dev:link-routekit
routekit-dev --version
```

If the current shell previously resolved `routekit-dev` from another checkout,
run `rehash` in zsh or `hash -r` in bash after linking.

Run it from any project repo:

```bash
cd your-project
routekit-dev doctor
routekit-dev codex
```

The dev command rebuilds `packages/cli` before launch, preserves the
caller's working directory, uses the repository-pinned pnpm version throughout
the Turbo build, and does not replace the normal `routekit` binary. Set
`ROUTEKIT_DEV_SKIP_BUILD=1` after a successful build for a faster local check.

## Command ownership

| Command | RouteKit responsibility |
| --- | --- |
| `setup` | Interactively configure and verify selected API and subscription routes. |
| `start`, `status`, `stop` | Start, inspect, and gracefully stop RouteKit through its singleton daemon. |
| `codex`, `claude` | Ask the daemon to prepare a launch, then run the supported coding tool locally against the singleton gateway. Native arguments after `--` remain owned by the native client. |
| `codex install`, `codex uninstall` | Add or remove one RouteKit-owned Codex provider/profile and its dedicated gateway token. |
| `claude install`, `claude uninstall` | Add or remove RouteKit-owned Claude Code gateway settings and a dedicated gateway token while preserving user configuration. |
| `providers add`, `remove`, `status` | Manage explicit providers and run live discovery without printing credentials. |
| `models list` | Discover and list the live namespaced model catalog. |
| `models info <provider/model>` | Explain the effective provider and native model, account class, billing mode, default status, capabilities, and reasoning metadata without printing credentials. |
| `accounts login` | Enroll a supported subscription kind (`claude-code` or `codex`) with RouteKit OAuth, then enable the matching provider. `--no-browser` prefers a device-code / copyable-URL flow for headless hosts. Official Codex/Claude CLIs are not required. |
| `accounts add`, `remove`, `list`, `status` | Import an existing official CLI login, or manage enrolled subscription accounts. |
| `usage` | Show subscription rate limits, credits, banked Codex resets, and reset windows from the running daemon. |
| `usage redeem` | Redeem a banked Codex rate-limit reset for an enrolled account (`--provider codex --label <name>`). |
| `config path`, `show`, `init`, `edit`, `import` | Manage the daemon's canonical global router config with revision-checked writes. |
| `doctor` | Check router configuration, referenced credential variables, and optional coding-agent binaries. |
| `self-update` | Update through the verified installer or package-manager context that owns the running CLI, then verify the fresh executable, manifest, and owner context. |
| `telemetry status`, `on`, `off` | Control RouteKit's anonymous, opt-in product telemetry. |
| `completion <bash\|zsh\|fish>` | Print shell completion setup. |
| `version`, `--version` | Print the `@velum-labs/routekit` version. |

Global options are `--json`, `--no-input`, `--yes`, and `--quiet`.
Daemon-backed commands use the canonical `~/.config/routekit/router.yaml`;
embedded and diagnostic execution can select an explicit config.
`routekit usage` asks the daemon-owned account pools directly.
Provider activation, live model catalogs, account relays, and registry-defined
credential environment variables are RouteKit-owned.

The first-launch subscription kinds are `claude-code` and `codex`; the Claude
Code launcher command remains `routekit claude [provider/model]`.

For an explicit repository routing profile, use
`routekit codex --profile quality`, `routekit claude --profile cost`, or set
`ROUTEKIT_ROUTING_PROFILE`. Local preparation discovers the profile from the
launch working directory and receives a memory-only gateway credential bound
to that repository and name. The tool sends `auto` for that explicitly bound
session; it does not change the global router document, activation, or durable
token store. External gateways receive the self-contained `auto:<name>`
selector and must provide their own repository-aware policy reader.

### Persistent native-client integration

```sh
routekit codex install
routekit claude install
```

The installer writes only RouteKit-owned additions to the user's real Codex or
Claude configuration. Codex receives one additive `routekit` profile, never a
default model change; launch it with `codex --profile routekit` and choose from
the RouteKit-backed model picker. Claude receives RouteKit-managed native
`availableModels` entries derived from the gateway catalog, so its normal
`/model` picker lists the policy-allowed routes without synthetic `claude-*`
models. The command issues a dedicated data token and stores it in macOS
Keychain (or a private `0600` RouteKit secret file elsewhere). Codex and Claude
retrieve it on demand through native credential helpers, so terminal, IDE, and
GUI launches need no shell configuration. Claude's `--bare` mode intentionally ignores
normal user settings, including `apiKeyHelper`; use a normal launch or pass its
settings file explicitly. Reinstalling the same target keeps the token;
`--rotate-token` replaces it. Uninstall revokes the tracked dedicated token.
Native clients own transcripts, history, resume, and deletion; RouteKit does
not track native sessions.
Pool policy uses the same provider map as API-key sources:

```yaml
providers:
  claude-code:
    strategy: capacity_weighted
    switchThreshold: 0.9
  codex:
    strategy: capacity_weighted
    switchThreshold: 0.9
defaultModel: codex/gpt-5.6-sol
```

The one enrollment path is `routekit accounts login <kind>`. The supported
`claude-code` and `codex` kinds run RouteKit OAuth, enroll the resulting
credential, and enable the matching provider. Official Codex and Claude CLIs
are not required (`accounts add` remains the explicit current-login import
path). `--no-browser` prefers a device-code / copyable-URL flow so a headless
host only needs a browser on some other device.

API providers infer their key and optional base URL from registry-defined
environment variables. Subscription providers discover the union of models
offered by healthy enrolled accounts and keep per-account quota, refresh,
cooldown, and model eligibility state. An explicitly requested unknown or
unnamespaced model is rejected rather than routed to the default.

`routekit models info <provider/model>` is the machine-verifiable route and
billing explanation surface. Its JSON fields are `id`, `provider`,
`nativeModel`, `accountClass`, `billingMode`, `default`, `capabilities`, and
`reasoning`; unavailable reasoning metadata is reported as `null`. API-key
routes report `api-key` / `metered-api`, managed subscription routes report
`subscription` / `subscription`, and retained proxy routes report `proxy` /
`upstream-managed`.

## First-launch support contract

RouteKit's public first-launch set is:

- API providers: OpenAI, Anthropic, and OpenRouter;
- subscriptions: Codex and Claude Code; and
- harnesses: Codex CLI `0.146.0` and Claude Code `2.1.216` or `2.1.220`.

Read the
[per-route credential, billing, egress, failover, and limitation disclosures](../../docs/routekit-routes-and-billing.md)
and the [exact client compatibility contract](../../docs/routekit-supported-clients.md)
before enabling a route. Unlisted client versions are unqualified, not
necessarily incompatible. OpenRouter is an aggregator; API-key and subscription
routes have different billing and quota boundaries.

Public support remains conditional on L06 qualification. Additional internal
providers, connectors, and tool integrations are not first-launch UX, are not
qualified, and are not a support contract.

## Singleton daemon

Every product command is a thin client of one daemon per `ROUTEKIT_HOME`.
The daemon owns:

- a private, random-token-authenticated `control.v2` listener on loopback;
- one stable OpenAI-compatible gateway listener;
- the canonical config, provider discovery/cache, subscription account pools,
  usage, and telemetry state; and
- transactional router generations. Config/account changes build and validate
  a replacement router first, atomically switch new traffic, then drain the old
  generation so active LLM streams finish.

`accounts login` and `accounts add` use one `accounts.enrollActivate` control
mutation. OAuth capture is isolated from daemon-owned stores; the daemon keeps
a private rollback vault while it commits account files, provider config,
account/config revisions, and the router generation. An error restores prior
state, and startup rolls back any prepared transaction before loading config.
Committed retries are no-ops. Status and doctor report sanitized recovery and
account/provider consistency without returning transaction credentials.

Help, version, completion, terminal rendering, OAuth/editor interaction, and
the final coding-tool process remain local. Interactive results are committed
back through authenticated RPC, so the daemon remains the sole RouteKit state
writer. Project `.routekit/router.yaml` files are SDK/embedded-router inputs,
not standalone daemon scopes. To use one as the singleton configuration,
explicitly replace its canonical document:

```sh
routekit config import --from .routekit/router.yaml
```

The first product command race-safely ensures the singleton exists. Where a
systemd user manager or launchd is available it installs/starts the persistent
unit; unsupported container/WSL environments use the documented detached
fallback. Users do not need to select foreground, detached, or supervised
operation. The public lifecycle is:

```sh
routekit start
routekit status
routekit stop
```

`start` is idempotent and uses the same daemon bootstrap as every product
command. It writes `routekit-daemon.service` / the launchd agent when an OS
supervisor is available (with lingering on Linux so it survives logout and
reboot), starts it, and verifies authenticated control health before printing
the data URL.

Supervised installs capture an explicit provider environment. Direct API
providers use their registry-defined credential variable and pin either the
custom base URL supplied at install time or the registry default. Native-client
credentials and endpoint overrides for subscription providers such as Codex and
Claude Code are removed before daemon startup; those providers authenticate
through RouteKit-managed account files instead. RouteKit does not modify the
launchd GUI domain or systemd manager environment.

On systemd, captured values are stored in private
`~/.routekit/env/daemon.env` (mode 0600); launchd stores them in its private
agent plist. `routekit daemon restart` rolls the request-serving worker inside
the existing service process, and `daemon reload` reloads router/account state,
so neither command refreshes
provider environment. After changing a provider key or base URL, run
`routekit daemon service install` to recapture the contract. Existing services
created before this isolation contract also need one reinstall after upgrade;
`routekit doctor` warns when it detects one. The gateway bearer is generated
into `~/.routekit/secrets/data-token` (0600) and never appears in status, logs,
or process arguments; `routekit daemon auth show` reveals it only when
explicitly requested for an external HTTP client. Where no init supervisor
exists (containers, some WSL setups), `start` falls back to a detached daemon.

### Advanced lifecycle operations

```sh
routekit daemon reload
routekit daemon restart
routekit daemon upgrade
routekit daemon logs -f
routekit daemon service install
routekit daemon service status
routekit daemon service uninstall
```

The hidden `daemon` command group is available for repair and diagnostics.
`daemon service install` rewrites a moved
systemd/launchd unit. The only foreground entrypoint is the internal
`daemon run`, which supervisors and the detached spawner execute; it is not a
user workflow.

### Lifecycle command boundary

Use `routekit start`, `routekit status`, and `routekit stop` for normal
operation. `routekit daemon service install` is reserved for unit repair,
inspection, removal, or recapturing provider environment. External clients read
the gateway URL from `routekit status` and the data token from
`routekit daemon auth show`.

Import project configuration with
`routekit config import --from .routekit/router.yaml`; the singleton never
silently adopts a project overlay.

### Graceful shutdown and rolling upgrades

`routekit stop` is a full drain: `/health` flips to 503, new requests are
rejected, and in-flight requests (long-lived LLM streams) get up to the drain
grace (default 30s; `--drain-grace <seconds>` or `ROUTEKIT_DRAIN_GRACE`) before
the listener is severed.

`routekit daemon restart` and `routekit daemon upgrade` use a stable cluster
primary and roll one active worker. The data URL, public port, control port, and
host PID remain unchanged. The candidate loads and validates state on the same
shared ports, commits atomically, and only then retires the previous worker.
Requests already admitted by the previous worker may finish during the drain
grace; new connections reach the committed worker without an intentional
`ECONNREFUSED` or 503 interval. Candidate startup, synchronization, version, or
readiness failure leaves the previous worker active.

After installing a new `@velum-labs/routekit`, the next product command negotiates
the package/protocol version and gracefully restarts an older daemon before
retrying. The explicit form is:

```sh
routekit daemon upgrade
```

rolls the worker to the installed CLI entrypoint. `upgrade --force` performs the
same roll without version skew. systemd and launchd keep the supervised primary
running throughout.

The zero-downtime guarantee does not cover `routekit stop`, machine reboot,
host-process crash, manual supervisor restart/bootout, listener/Portless/owner
token path changes, or an incompatible host-protocol upgrade. Those operations
require a hard lifecycle transition and may restart the listener.
