@askalf/dario 6.0.31 → 6.0.32
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/README.md +398 -214
- package/dist/claude-model.js +11 -1
- package/dist/proxy.js +53 -9
- package/dist/upstream-rejection.d.ts +8 -0
- package/dist/upstream-rejection.js +8 -0
- package/docs/integrations/cordon.md +2 -2
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/hero-dark.svg">
|
|
5
|
+
<img alt="dario routes every AI tool you use to the subscriptions you already pay for. Coding tools on the left send requests to a local dario endpoint at localhost:3456, which forwards each one to a Claude plan (a pool of seats routed by headroom), a ChatGPT plan, or an API-key backend. When the Claude pool returns 429, the request is served by the ChatGPT plan and the response carries an x-dario-pool-fallback header." src=".github/readme/hero-light.svg" width="100%">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
3
8
|
# `dario`
|
|
4
9
|
|
|
5
10
|
### Your Claude and ChatGPT subscriptions each work in exactly one place.<br/>dario makes them work **everywhere** — at subscription pricing, not per-token API bills.
|
|
@@ -7,42 +12,37 @@
|
|
|
7
12
|
<p>
|
|
8
13
|
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/v/@askalf/dario?color=6f42c1&label=npm&logo=npm" alt="npm version"></a>
|
|
9
14
|
<a href="https://github.com/askalf/dario/releases"><img src="https://img.shields.io/github/v/release/askalf/dario?color=6f42c1&label=release&logo=github" alt="Latest release"></a>
|
|
10
|
-
<a href="https://github.com/askalf/dario/actions/workflows/ci.yml"><img src="https://github.com/askalf/dario/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
11
|
-
<a href="https://github.com/askalf/dario/actions/workflows/codeql.yml"><img src="https://github.com/askalf/dario/actions/workflows/codeql.yml/badge.svg" alt="CodeQL"></a>
|
|
15
|
+
<a href="https://github.com/askalf/dario/actions/workflows/ci.yml"><img src="https://github.com/askalf/dario/actions/workflows/ci.yml/badge.svg?branch=master" alt="CI"></a>
|
|
16
|
+
<a href="https://github.com/askalf/dario/actions/workflows/codeql.yml"><img src="https://github.com/askalf/dario/actions/workflows/codeql.yml/badge.svg?branch=master" alt="CodeQL"></a>
|
|
12
17
|
<a href="https://scorecard.dev/viewer/?uri=github.com/askalf/dario"><img src="https://api.scorecard.dev/projects/github.com/askalf/dario/badge" alt="OpenSSF Scorecard"></a>
|
|
13
18
|
<a href="https://www.bestpractices.dev/projects/13638"><img src="https://www.bestpractices.dev/projects/13638/badge" alt="OpenSSF Best Practices"></a>
|
|
14
19
|
<a href="https://github.com/askalf/dario/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@askalf/dario?color=6f42c1" alt="License"></a>
|
|
15
20
|
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/dm/@askalf/dario?color=6f42c1" alt="Downloads"></a>
|
|
16
|
-
<a href="https://
|
|
21
|
+
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/node/v/@askalf/dario?color=6f42c1" alt="Node version"></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p>
|
|
25
|
+
<a href="https://github.com/askalf/dario/blob/master/src/cc-template-data.json"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Faskalf%2Fdario%2Fmaster%2Fsrc%2Fcc-template-data.json&query=%24._version&prefix=v&label=tracks%20Claude%20Code&color=8b5cf6" alt="Claude Code version the bundled template tracks (read live from master)"></a>
|
|
26
|
+
<a href="https://github.com/askalf/dario/actions/workflows/cc-billing-classifier-canary.yml"><img src="https://github.com/askalf/dario/actions/workflows/cc-billing-classifier-canary.yml/badge.svg" alt="Daily live billing canary"></a>
|
|
27
|
+
<a href="https://github.com/askalf/dario/actions/workflows/cc-drift-watch.yml"><img src="https://github.com/askalf/dario/actions/workflows/cc-drift-watch.yml/badge.svg" alt="Hourly Claude Code drift watch"></a>
|
|
28
|
+
<a href="https://github.com/askalf/dario/actions/workflows/cc-drift-template-watch.yml"><img src="https://github.com/askalf/dario/actions/workflows/cc-drift-template-watch.yml/badge.svg" alt="Live template drift watch"></a>
|
|
17
29
|
</p>
|
|
18
30
|
|
|
19
31
|
<p><strong>One local endpoint. Every AI tool you own. The subscriptions you already pay for.</strong></p>
|
|
20
32
|
|
|
21
33
|
<sub><code>npm i -g @askalf/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~31k lines you can read in a weekend · independent, unofficial, third-party (<a href="DISCLAIMER.md">DISCLAIMER.md</a>)</sub>
|
|
22
34
|
|
|
23
|
-
<sub>
|
|
35
|
+
<sub><a href="#start-in-60-seconds">Start</a> · <a href="#point-your-tools-at-it">Your tools</a> · <a href="#what-it-does-with-a-request">Routing</a> · <a href="#two-plans-one-endpoint">Two plans</a> · <a href="#many-seats-one-endpoint">Pool</a> · <a href="#it-tracks-a-moving-target">Drift</a> · <a href="#trust--transparency">Trust</a> · <a href="#will-my-account-get-suspended">Risk</a> · <a href="#commands">Commands</a> · <a href="#faq">FAQ</a> · <a href="docs/returning.md">Coming back after a while?</a></sub>
|
|
24
36
|
|
|
25
37
|
</div>
|
|
26
38
|
|
|
27
39
|
---
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
>
|
|
31
|
-
> Through v5, dario was a Claude proxy that had recently learned to reach a ChatGPT subscription on one path. v6 finishes that: **either subscription can serve either wire shape, and either one can cover for the other.**
|
|
32
|
-
>
|
|
33
|
-
> - **🔀 Both wire shapes, both plans.** Your ChatGPT plan now answers `/v1/messages`, not just `/v1/chat/completions` — so Claude Code, the Anthropic SDKs and agent runtimes can be served by it without knowing. Your Claude plan already answered both.
|
|
34
|
-
> - **🪂 Failover between subscriptions.** `--pool-fallback=gpt-5.6-sol,claude-sonnet-5` is a *chain*: a drained Claude pool is served by ChatGPT, and a rate-limited ChatGPT is handed back to Claude. Two consumer plans, no API keys, and neither one going down takes you with it. → [Failover](#failover-between-subscriptions)
|
|
35
|
-
> - **🧪 Shadow compare.** `x-dario-compare: <model>` answers you normally *and* runs the same prompt past the other family, writing both to `~/.dario/compare/`. Which model is better at **your** work, measured on your own traffic. → [Shadow compare](#shadow-compare)
|
|
36
|
-
> - **➕ `dario add altman` / `dario add amodei`.** Attach a plan by whose it is.
|
|
37
|
-
> - **🩺 `dario doctor` reports failover readiness** — including *armed but INERT*, the state that is green on every other check and cannot actually do anything.
|
|
38
|
-
>
|
|
39
|
-
> **Upgrading from v5?** Nothing to do — every v6 feature is opt-in and a single-value `--pool-fallback` behaves exactly as it did. [CHANGELOG](CHANGELOG.md#600---2026-08-30)
|
|
41
|
+
You're already paying $20, $100 or $200 a month for Claude,[^plans] or for a ChatGPT plan. Then Cursor wants an API key. Aider wants an API key. Cline, Continue, Zed, your own scripts — every one of them bills you **again**, per token, while the plan you bought sits idle in the one app it shipped with.
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
**dario is one local endpoint that routes all of them through the plans you already pay for.** Point any Anthropic- or OpenAI-compatible tool at `http://localhost:3456` and you're done. No per-tool config, no second bill, and when one plan hits its limit the other one takes the request.
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
**dario is one local endpoint that routes all of them through the Claude subscription you already pay for.** Point any Anthropic- or OpenAI-compatible tool at `http://localhost:3456` and you're done. No per-tool config, no second bill.
|
|
45
|
+
## Start in 60 seconds
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
# 1. Install
|
|
@@ -54,90 +54,216 @@ dario login # or `dario login --manual` for SSH / headless
|
|
|
54
54
|
# 3. Start the local proxy
|
|
55
55
|
dario proxy # separate terminal or background
|
|
56
56
|
|
|
57
|
-
# 4. Point any Anthropic-
|
|
57
|
+
# 4. Point any Anthropic-compatible tool at it
|
|
58
58
|
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
59
59
|
export ANTHROPIC_API_KEY=dario
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
<picture>
|
|
63
|
+
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/quickstart-dark.svg">
|
|
64
|
+
<img alt="Terminal: npm install -g @askalf/dario, dario login (Opening browser to sign in… Login successful!), dario proxy (dario — http://localhost:3456. Your Claude subscription is now an API. Usage: ANTHROPIC_BASE_URL=http://localhost:3456, ANTHROPIC_API_KEY=dario. OAuth healthy, Model passthrough, Pool: 1 account), then export the two variables and run aider --model sonnet." src=".github/readme/quickstart-light.svg" width="100%">
|
|
65
|
+
</picture>
|
|
66
|
+
|
|
67
|
+
That's the whole setup. Every tool that honors those env vars now runs on your subscription. OpenAI-shaped tools use `OPENAI_BASE_URL=http://localhost:3456/v1` instead, same key.
|
|
68
|
+
|
|
69
|
+
**Works with:** Claude Code, Cursor, Aider, Cline, Roo Code, Kilo Code, Continue.dev, Zed, OpenHands, OpenClaw, Hermes, Codex CLI, the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk), the Anthropic and OpenAI SDKs, curl, your own scripts. Per-tool snippets are one section down; the honest per-tool status is in the [compatibility matrix](./docs/integrations/compat-matrix.md).
|
|
70
|
+
|
|
71
|
+
Prefer Docker? `ghcr.io/askalf/dario:latest` — multi-arch (`amd64` + `arm64`), published from the same workflow as every npm release ([guide](./docs/docker.md)). Something off? `dario doctor` prints one paste-ready health report.
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
## Point your tools at it
|
|
65
74
|
|
|
66
|
-
|
|
75
|
+
Two base URLs, one key. Anthropic-shaped clients talk to `http://localhost:3456`; OpenAI-shaped clients talk to `http://localhost:3456/v1`. The key is `dario` (any value works until you set `DARIO_API_KEY`, which then has to match).
|
|
76
|
+
|
|
77
|
+
<details>
|
|
78
|
+
<summary><strong>Claude Code</strong> — forwarded verbatim</summary>
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
82
|
+
export ANTHROPIC_API_KEY=dario
|
|
83
|
+
claude
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A genuine Claude Code request already *is* the Claude Code shape, so dario forwards it byte-for-byte — system prompt, tools, thinking, key order untouched — swapping in only the pool's credential, its billing tag and cache breakpoints. That covers the main loop, its Task/Agent sub-agents and the permission classifier. What you gain is the pool: several seats behind the one URL, with headroom routing and 429 failover. Background: [#678](https://github.com/askalf/dario/issues/678).
|
|
87
|
+
</details>
|
|
88
|
+
|
|
89
|
+
<details>
|
|
90
|
+
<summary><strong>Cursor</strong> — needs a public HTTPS tunnel, and the <code>anthropic:</code> prefix</summary>
|
|
91
|
+
|
|
92
|
+
Cursor's BYOK is backend-mediated: the app sends your base URL up to Cursor's servers and *they* make the call, behind an SSRF guard that rejects `localhost` by design (confirmed by Cursor staff, threads linked in the long-form guide). So:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
dario proxy # terminal 1
|
|
96
|
+
cloudflared tunnel --url http://localhost:3456 # terminal 2 → https://<random>.trycloudflare.com
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
In Cursor → Settings → Models: enable **Override OpenAI Base URL** with `https://<random>.trycloudflare.com/v1`, key `dario`, and add models as `anthropic:opus` / `anthropic:sonnet` / `anthropic:haiku`. The `anthropic:` prefix routes to the Claude backend without the `claude-` substring that makes Cursor switch to a tool format the OpenAI path can't parse, and it dodges Cursor's built-in-name collision. Use **Agent** mode (<kbd>Cmd</kbd>/<kbd>Ctrl</kbd>+<kbd>I</kbd>); Chat sends no tools. Treat the tunnel URL as a credential. Full walkthrough with every gotcha: [agent-compat.md#cursor](./docs/integrations/agent-compat.md#cursor).
|
|
100
|
+
</details>
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary><strong>Cline · Roo Code · Kilo Code</strong> — API provider "Anthropic"</summary>
|
|
104
|
+
|
|
105
|
+
Provider **Anthropic** · API key `dario` · Anthropic Base URL `http://localhost:3456` · model `claude-sonnet-5` / `claude-opus-5` / `claude-haiku-4-5`.
|
|
106
|
+
|
|
107
|
+
These clients speak an XML tool protocol. dario detects them from their system-prompt identity markers and flips into preserve-tools mode on its own, so their schemas pass through and their parsers keep working. `--no-auto-detect` if you'd rather choose. [Details](./docs/integrations/agent-compat.md#cline--roo-code--kilo-code).
|
|
108
|
+
</details>
|
|
109
|
+
|
|
110
|
+
<details>
|
|
111
|
+
<summary><strong>Aider</strong></summary>
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
115
|
+
export ANTHROPIC_API_KEY=dario
|
|
116
|
+
aider --model sonnet # or opus, haiku, any claude-* id
|
|
117
|
+
```
|
|
118
|
+
</details>
|
|
119
|
+
|
|
120
|
+
<details>
|
|
121
|
+
<summary><strong>Continue.dev</strong></summary>
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
# ~/.continue/config.yaml
|
|
125
|
+
models:
|
|
126
|
+
- name: Claude Sonnet (dario)
|
|
127
|
+
provider: anthropic
|
|
128
|
+
model: claude-sonnet-5
|
|
129
|
+
apiBase: http://localhost:3456
|
|
130
|
+
apiKey: dario
|
|
131
|
+
```
|
|
132
|
+
</details>
|
|
133
|
+
|
|
134
|
+
<details>
|
|
135
|
+
<summary><strong>Zed</strong></summary>
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{ "language_models": { "anthropic": { "api_url": "http://localhost:3456", "version": "2023-06-01" } } }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Set `ANTHROPIC_API_KEY=dario` in the environment Zed launches from; the model picker then lists Claude models routed through your plan.
|
|
142
|
+
</details>
|
|
143
|
+
|
|
144
|
+
<details>
|
|
145
|
+
<summary><strong>OpenHands</strong></summary>
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
export LLM_BASE_URL=http://localhost:3456
|
|
149
|
+
export LLM_API_KEY=dario
|
|
150
|
+
export LLM_MODEL=anthropic/claude-sonnet-5
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The `anthropic/` prefix tells LiteLLM (OpenHands' router) to take the Anthropic path, which dario is now fronting. End-to-end walkthrough: [openhands-walkthrough.md](./docs/integrations/openhands-walkthrough.md).
|
|
154
|
+
</details>
|
|
155
|
+
|
|
156
|
+
<details>
|
|
157
|
+
<summary><strong>OpenClaw</strong></summary>
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
161
|
+
export ANTHROPIC_API_KEY=dario
|
|
162
|
+
openclaw "task description"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
OpenClaw's `exec` / `process` / `web_search` / `web_fetch` / `browser` / `message` tools are translated to Claude Code's set without a flag. Newer OpenClaw reads `auth-profiles.json` before env vars, so a stale key there wins — the [walkthrough](./docs/integrations/openclaw-walkthrough.md) covers it.
|
|
166
|
+
</details>
|
|
167
|
+
|
|
168
|
+
<details>
|
|
169
|
+
<summary><strong>Codex CLI · OpenAI SDK · any OpenAI-compatible tool</strong></summary>
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
export OPENAI_BASE_URL=http://localhost:3456/v1
|
|
173
|
+
export OPENAI_API_KEY=dario
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Ask for `gpt-5.5` and it is served by your ChatGPT plan once you've run `dario add altman`. Ask for `claude-sonnet-5` on the same URL and it is served by your Claude plan, translated both ways. Ask for `gpt-4o`, `llama-3.3-70b` or anything an API-key backend lists and it goes there byte-for-byte:
|
|
67
177
|
|
|
68
178
|
```bash
|
|
69
179
|
dario backend add openai --key=sk-proj-...
|
|
70
180
|
dario backend add groq --key=gsk_... --base-url=https://api.groq.com/openai/v1
|
|
71
181
|
dario backend add openrouter --key=sk-or-... --base-url=https://openrouter.ai/api/v1
|
|
72
182
|
dario backend add local --key=anything --base-url=http://127.0.0.1:11434/v1
|
|
73
|
-
|
|
74
|
-
export OPENAI_BASE_URL=http://localhost:3456/v1
|
|
75
|
-
export OPENAI_API_KEY=dario
|
|
76
183
|
```
|
|
77
184
|
|
|
78
|
-
Force a
|
|
185
|
+
Force a backend with a prefix: `openai:gpt-4o`, `claude:opus`, `groq:llama-3.3-70b`, `local:qwen-coder`.
|
|
186
|
+
</details>
|
|
79
187
|
|
|
80
|
-
|
|
188
|
+
<details>
|
|
189
|
+
<summary><strong>Claude Agent SDK · Anthropic SDK</strong> (TypeScript, Python)</summary>
|
|
81
190
|
|
|
82
|
-
|
|
191
|
+
```ts
|
|
192
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
193
|
+
const client = new Anthropic({ baseURL: "http://localhost:3456", apiKey: "dario" });
|
|
194
|
+
```
|
|
83
195
|
|
|
196
|
+
```python
|
|
197
|
+
import anthropic
|
|
198
|
+
client = anthropic.Anthropic(base_url="http://localhost:3456", api_key="dario")
|
|
84
199
|
```
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
│ Rate-limit: │
|
|
99
|
-
│ 5h ████░░░░░░░░░░░░░░░░░░░░░░░░ 18% │
|
|
100
|
-
│ 7d ██░░░░░░░░░░░░░░░░░░░░░░░░░░ 8% │
|
|
101
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
200
|
+
|
|
201
|
+
Zero code change beyond the base URL. Streaming, tool use, prompt caching and extended thinking all pass through. More in [usage.md](./docs/usage.md).
|
|
202
|
+
</details>
|
|
203
|
+
|
|
204
|
+
<details>
|
|
205
|
+
<summary><strong>curl</strong></summary>
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
curl http://localhost:3456/v1/messages -H "content-type: application/json" \
|
|
209
|
+
-d '{"model":"claude-sonnet-5","max_tokens":256,"messages":[{"role":"user","content":"Hello!"}]}'
|
|
210
|
+
|
|
211
|
+
curl http://localhost:3456/v1/chat/completions -H "content-type: application/json" \
|
|
212
|
+
-d '{"model":"gpt-5.5","messages":[{"role":"user","content":"Hello!"}]}'
|
|
102
213
|
```
|
|
214
|
+
</details>
|
|
103
215
|
|
|
104
|
-
|
|
216
|
+
<details>
|
|
217
|
+
<summary><strong>Docker</strong> · Kubernetes · a Pi in a closet</summary>
|
|
105
218
|
|
|
106
|
-
|
|
219
|
+
```bash
|
|
220
|
+
docker volume create dario-config
|
|
221
|
+
docker run --rm -it -v dario-config:/home/dario/.dario ghcr.io/askalf/dario:latest login --manual
|
|
222
|
+
docker run -d --name dario -p 3456:3456 -v dario-config:/home/dario/.dario \
|
|
223
|
+
-e DARIO_API_KEY="$(openssl rand -hex 32)" ghcr.io/askalf/dario:latest
|
|
224
|
+
```
|
|
107
225
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
| Cursor + Anthropic API direct | **$80–$300** |
|
|
111
|
-
| Multi-tool heavy use (Cursor + Aider + Cline + Continue), per-token | **$200–$600+** |
|
|
112
|
-
| **Any of the above + dario** | **$20–$200 flat** — your existing Pro/Max plan, nothing extra |
|
|
226
|
+
The image binds `0.0.0.0`, so a key is mandatory; without one dario refuses to start rather than become an open relay for your subscription. No console at all? Start empty with `DARIO_ADMIN=1` and provision the first account over HTTP with the [admin API](./docs/admin-api.md). Two replicas sharing accounts need the [refresh lock](./docs/multi-instance.md). [Docker guide](./docs/docker.md).
|
|
227
|
+
</details>
|
|
113
228
|
|
|
114
|
-
|
|
229
|
+
Something not listed? If it reads `ANTHROPIC_BASE_URL` or `OPENAI_BASE_URL`, or has a "Base URL" field, it works. The [compatibility matrix](./docs/integrations/compat-matrix.md) says which tools are exercised end-to-end, which are inferred from a shared code path, and which are untested — one honest cell per tool.
|
|
115
230
|
|
|
116
|
-
|
|
231
|
+
## What it does with a request
|
|
117
232
|
|
|
118
|
-
|
|
233
|
+
You point every tool at one URL. dario reads each request, decides which plan or backend owns it, and forwards it in that backend's native protocol.
|
|
119
234
|
|
|
120
|
-
|
|
235
|
+
```mermaid
|
|
236
|
+
flowchart LR
|
|
237
|
+
T["Any tool<br/>Anthropic or OpenAI shape"] --> D{"dario<br/>localhost:3456"}
|
|
238
|
+
D -->|"claude-* · opus · sonnet · haiku"| P["Claude pool<br/>headroom · sticky · 429 retry"]
|
|
239
|
+
D -->|"a slug your ChatGPT plan lists"| X["Codex engine<br/>ChatGPT plan"]
|
|
240
|
+
D -->|"gpt-4o · llama-* · provider prefix"| K["API-key backends"]
|
|
241
|
+
P -->|"OAuth swap + Claude Code template replay"| A[("api.anthropic.com")]
|
|
242
|
+
X -->|"Messages / chat → Responses"| O[("chatgpt.com backend")]
|
|
243
|
+
K -->|"byte-for-byte"| B[("OpenAI-compatible APIs")]
|
|
244
|
+
P -. "429 / 5xx" .-> X
|
|
245
|
+
X -. "429 / 5xx" .-> P
|
|
246
|
+
```
|
|
121
247
|
|
|
122
248
|
| Client speaks | Model | Routes to | What happens |
|
|
123
249
|
|---|---|---|---|
|
|
124
|
-
| Anthropic Messages | `claude-*` / `opus` / `sonnet` / `haiku` | Claude
|
|
125
|
-
| Anthropic Messages | a slug your ChatGPT account lists | Codex
|
|
250
|
+
| Anthropic Messages | `claude-*` / `opus` / `sonnet` / `haiku` | Claude pool | OAuth swap + Claude Code template, then `api.anthropic.com` |
|
|
251
|
+
| Anthropic Messages | a slug your ChatGPT account lists | Codex engine | Messages→Responses translation, subscription auth |
|
|
126
252
|
| Anthropic Messages | `gpt-*`, `llama-*`, … | OpenAI-compat backend | Anthropic→OpenAI translation, forwarded |
|
|
127
253
|
| OpenAI Chat | `gpt-*` / `o1-*` / `o3-*` | OpenAI-compat backend | Auth swap, body forwarded byte-for-byte |
|
|
128
|
-
| OpenAI Chat | a slug your ChatGPT account lists | Codex
|
|
129
|
-
| OpenAI Chat | `claude-*` | Claude
|
|
254
|
+
| OpenAI Chat | a slug your ChatGPT account lists | Codex engine | chat/completions→Responses translation, subscription auth |
|
|
255
|
+
| OpenAI Chat | `claude-*` | Claude pool | OpenAI→Anthropic translation, then the Claude path |
|
|
130
256
|
| Either | `<provider>:<model>` | Forced by prefix | Explicit override |
|
|
131
257
|
|
|
132
258
|
The tool doesn't know. The backend doesn't know. dario is the seam.
|
|
133
259
|
|
|
134
|
-
**The full Claude lineup, autodetected.** Fable 5, Opus 5, Sonnet 5
|
|
260
|
+
**The full Claude lineup, autodetected.** Fable 5, Opus 5, Sonnet 5 and Haiku 4.5, plus `[1m]` long-context variants on every family except Haiku, by full id (`claude-opus-5`) or shortcut (`fable` / `opus` / `sonnet` / `haiku`; append `1m` for the long-context form; `opus48` / `opus47` / `opus46` / `sonnet46` pin a generation and never float). `GET /v1/models` reads Anthropic's live catalog (TTL-cached, baked fallback offline), so a new model resolves the day it lands with no dario release, and the model-specific request shape is applied automatically. Families pulled upstream are filtered from both the live catalog and the fallback, so `/v1/models` never advertises a model that 404s. A name no provider lists at all, such as a ChatGPT slug your account doesn't have or a typo that belongs to no family, is refused locally with `400` and `x-dario-upstream-rejection: model_unroutable` instead of spending a pool request on an upstream 404.
|
|
135
261
|
|
|
136
|
-
|
|
262
|
+
## Two plans, one endpoint
|
|
137
263
|
|
|
138
|
-
|
|
264
|
+
### Your ChatGPT plan, on both endpoints
|
|
139
265
|
|
|
140
|
-
|
|
266
|
+
A ChatGPT Plus or Pro plan is served on **both** of dario's endpoints: any client that speaks `/v1/chat/completions` can use it (Codex CLI, the OpenAI SDKs, your scripts), and so can any client that speaks `/v1/messages` (Claude Code, the Anthropic SDKs, agent runtimes). The harness never needs to know which subscription is behind it.
|
|
141
267
|
|
|
142
268
|
```bash
|
|
143
269
|
dario add altman # prints an authorize URL; paste the redirect URL back
|
|
@@ -145,11 +271,7 @@ dario codex list
|
|
|
145
271
|
dario codex remove altman
|
|
146
272
|
```
|
|
147
273
|
|
|
148
|
-
`dario add altman` names whose plan you are attaching; `dario add amodei` attaches a Claude account instead. `
|
|
149
|
-
|
|
150
|
-
The browser lands on a `localhost` page that doesn't load — that's expected, nothing is listening there. Copy the whole address bar and paste it at the prompt; dario reads the code out of it. A bare code (or `code#state`) works too.
|
|
151
|
-
|
|
152
|
-
Once an account is stored, a request naming a model that account may use is served from the subscription — on either endpoint:
|
|
274
|
+
`dario add altman` names whose plan you are attaching; `dario add amodei` attaches a Claude account instead. The browser lands on a `localhost` page that doesn't load — expected, nothing is listening there. Copy the whole address bar and paste it at the prompt; dario reads the code out of it.
|
|
153
275
|
|
|
154
276
|
```bash
|
|
155
277
|
curl localhost:3456/v1/models | jq -r '.data[].id'
|
|
@@ -161,19 +283,13 @@ curl localhost:3456/v1/messages -H 'content-type: application/json' \
|
|
|
161
283
|
-d '{"model":"gpt-5.5","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}'
|
|
162
284
|
```
|
|
163
285
|
|
|
164
|
-
**Model names are discovered, not hardcoded.** The set a ChatGPT subscription may use is per-account and moves; dario asks the backend which models this account lists, caches the answer, and advertises them on `GET /v1/models
|
|
165
|
-
|
|
166
|
-
Streaming, tool calls, and tool-result round trips work on both shapes: dario translates chat/completions **or** Messages into the Responses API the subscription backend speaks, and translates the stream back into `chat.completion.chunk` or Anthropic message events to match what the client asked in. There is no `/v1/responses` inbound yet.
|
|
286
|
+
**Model names are discovered, not hardcoded.** The set a ChatGPT subscription may use is per-account and moves; dario asks the backend which models this account lists, caches the answer, and advertises them on `GET /v1/models`. Anything not on that list (`gpt-4o` and friends) still routes to a configured API-key backend as before. `codex:<model>` / `chatgpt:<model>` forces the route.
|
|
167
287
|
|
|
168
|
-
|
|
288
|
+
Streaming, tool calls and tool-result round trips work on both shapes: dario translates chat/completions **or** Messages into the Responses API the subscription backend speaks, and translates the stream back into `chat.completion.chunk` or Anthropic message events. There is no `/v1/responses` inbound yet. The Codex backend does not accept every chat field, so `response_format`, `stop`, `n`, `logprobs`, `stream_options` and the sampling parameters `temperature`, `top_p`, `max_tokens`, `max_completion_tokens` are intentionally lossy; with `--verbose`, dario reports each field that does not reach Codex once per process. Codex accounts live in `~/.dario/codex-accounts/`, separate from the Claude pool.
|
|
169
289
|
|
|
170
290
|
**Prompt caching:** the backend caches prompt prefixes of 1,024 tokens and up on its own; what dario adds is the `prompt_cache_key` that routes same-prefix requests to the cache that holds them, the way the Codex CLI does with its session id. A chat/completions client that sets its own key keeps it; an Anthropic-shape request gets one per Claude Code session (a hash of `metadata.user_id`, never the raw ids); anything else is keyed on its model, instructions and tool names, so repeated system prompts from any caller land together. Cached tokens come back as `prompt_tokens_details.cached_tokens` on chat/completions and as `cache_read_input_tokens` on `/v1/messages`, and show up in `/analytics` and the `-v` usage line like a Claude request's do.
|
|
171
291
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## Failover between subscriptions
|
|
292
|
+
### Failover between subscriptions
|
|
177
293
|
|
|
178
294
|
Two consumer plans, no API keys, and neither one able to take you down on its own.
|
|
179
295
|
|
|
@@ -181,13 +297,25 @@ Two consumer plans, no API keys, and neither one able to take you down on its ow
|
|
|
181
297
|
dario proxy --pool-fallback=gpt-5.6-sol,claude-sonnet-5
|
|
182
298
|
```
|
|
183
299
|
|
|
184
|
-
That is a **chain**, read left to right
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
300
|
+
That is a **chain**, read left to right; each provider takes the first entry it can actually serve. When the Claude pool is drained or cooling, the request is served as `gpt-5.6-sol` from your ChatGPT subscription. When the subscription is rate-limited or down, the request is handed back to the Claude pool as `claude-sonnet-5`. Every substituted response carries `x-dario-pool-fallback: <model>` — a silently swapped model family is exactly the surprise this project exists to avoid.
|
|
301
|
+
|
|
302
|
+
```mermaid
|
|
303
|
+
sequenceDiagram
|
|
304
|
+
participant T as Tool
|
|
305
|
+
participant D as dario
|
|
306
|
+
participant C as Claude pool
|
|
307
|
+
participant G as ChatGPT plan
|
|
308
|
+
T->>D: POST /v1/messages (claude-opus-5)
|
|
309
|
+
D->>C: seat with the most headroom
|
|
310
|
+
C-->>D: 429 rate_limit_error
|
|
311
|
+
D->>C: same request, next-best seat
|
|
312
|
+
C-->>D: 429 (pool drained)
|
|
313
|
+
D->>G: same request as gpt-5.6-sol (Messages → Responses)
|
|
314
|
+
G-->>D: 200
|
|
315
|
+
D-->>T: 200 + x-dario-pool-fallback: gpt-5.6-sol
|
|
316
|
+
```
|
|
189
317
|
|
|
190
|
-
Only a **429 or 5xx** fails over
|
|
318
|
+
A single-entry chain is one-way and means what it always meant, so an existing config is unaffected. Failover is opt-in: without `--pool-fallback`, a drained pool still returns its honest 429/503. Only a **429 or 5xx** fails over; a 400 surfaces, because a bad request that fails over just reproduces itself on the other provider and buries the real cause. The Claude entry has to be a model the pool can actually serve, checked positively against the live catalog, so a typo can't trade a recoverable 429 for an unrecoverable 404.
|
|
191
319
|
|
|
192
320
|
`dario doctor` tells you which of these you are actually in:
|
|
193
321
|
|
|
@@ -199,9 +327,10 @@ Only a **429 or 5xx** fails over. A 400 surfaces to you, because a bad request t
|
|
|
199
327
|
|
|
200
328
|
That warning is the whole reason the check exists. Armed with nothing to fall back to is green on every other check and incapable of doing anything.
|
|
201
329
|
|
|
202
|
-
|
|
330
|
+
> [!NOTE]
|
|
331
|
+
> Upgrading from v5? Nothing to do. Every v6 feature is opt-in and a single-value `--pool-fallback` behaves exactly as it did. [CHANGELOG](CHANGELOG.md#600---2026-08-30)
|
|
203
332
|
|
|
204
|
-
|
|
333
|
+
### Shadow compare
|
|
205
334
|
|
|
206
335
|
Once either subscription can serve either wire shape, the interesting question stops being *can I reach GPT* and becomes *which of these is better at my work*. Benchmarks answer that badly. Your own traffic answers it well.
|
|
207
336
|
|
|
@@ -209,20 +338,14 @@ Once either subscription can serve either wire shape, the interesting question s
|
|
|
209
338
|
curl localhost:3456/v1/messages \
|
|
210
339
|
-H 'content-type: application/json' \
|
|
211
340
|
-H 'x-dario-compare: gpt-5.6-sol' \
|
|
212
|
-
-d '{"model":"claude-opus-
|
|
341
|
+
-d '{"model":"claude-opus-5","max_tokens":1024,"messages":[…]}'
|
|
213
342
|
```
|
|
214
343
|
|
|
215
|
-
You get the Claude answer, exactly as you would have. Beside it, dario runs the same prompt past `gpt-5.6-sol` and writes both to `~/.dario/compare/<timestamp>-<model>.json`, in your own wire shape so you are comparing like with like
|
|
344
|
+
You get the Claude answer, exactly as you would have. Beside it, dario runs the same prompt past `gpt-5.6-sol` and writes both to `~/.dario/compare/<timestamp>-<model>.json`, in your own wire shape, so you are comparing like with like. The comparison cannot degrade the request it observes: it only reads bytes already on their way out, your request is never held open for it, and a comparison that fails, times out or has nowhere to go is dropped with the record still written. Both sides are stored as raw payloads, because extracting text is where a bug would quietly make two answers look more alike than they are.
|
|
216
345
|
|
|
217
|
-
|
|
346
|
+
## Many seats, one endpoint
|
|
218
347
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
---
|
|
222
|
-
|
|
223
|
-
## Multi-account pool
|
|
224
|
-
|
|
225
|
-
**In v5 every dario is a pool** — a plain `dario login` is a pool of one, no separate mode to switch on. One Claude subscription has a ceiling; hold more than one seat — a personal Max and a work Max, a couple of Pros, team seats — and the same `localhost:3456` routes every request to whichever seat has the most headroom, live, per request. A single `dario accounts add` even bootstraps a servable proxy with no `dario login` step:
|
|
348
|
+
**Every dario is a pool.** A plain `dario login` is a pool of one; there is no separate mode to switch on. Hold more than one seat — a personal Max and a work Max, a couple of Pros, team seats — and the same `localhost:3456` routes every request to whichever seat has the most headroom, live, per request.
|
|
226
349
|
|
|
227
350
|
```bash
|
|
228
351
|
dario accounts add work
|
|
@@ -230,95 +353,87 @@ dario accounts add personal
|
|
|
230
353
|
dario proxy
|
|
231
354
|
```
|
|
232
355
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
- **In-flight 429 failover.** A seat hits its wall mid-request and dario retries the *same request* against the next-best account before your client ever sees an error. The sticky binding follows to the new seat, so the next turn doesn't re-select the cold one.
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
┌─ dario ─────────────────────────────[ q quit · Tab next · ? help ]──┐
|
|
241
|
-
│ Status Config Analytics Hits ▎Accounts▎ Backends │
|
|
242
|
-
├─────────────────────────────────────────────────────────────────────┤
|
|
243
|
-
│ ACCOUNTS — 3 pooled · routing by headroom │
|
|
244
|
-
│ │
|
|
245
|
-
│ work Max 20x 5h ██░░░░░ 12% 7d ████░░░ 41% ← next opus │
|
|
246
|
-
│ personal Max 5x 5h █████░░ 78% 7d ██████░ 88% │
|
|
247
|
-
│ side Pro 5h ░░░░░░░ 3% 7d █░░░░░░ 9% ← next sonnet│
|
|
248
|
-
│ │
|
|
249
|
-
│ sticky bindings: 4 active · 429 failovers (1h): 2 │
|
|
250
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
251
|
-
```
|
|
356
|
+
<picture>
|
|
357
|
+
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/tui-accounts-dark.svg">
|
|
358
|
+
<img alt="The dario TUI Accounts tab: a table of pooled seats (work, personal, side) with token expiry, 5-hour and 7-day utilization, and status." src=".github/readme/tui-accounts-light.svg" width="100%">
|
|
359
|
+
</picture>
|
|
252
360
|
|
|
253
|
-
|
|
361
|
+
Three things it does that a round-robin doesn't:
|
|
254
362
|
|
|
255
|
-
|
|
363
|
+
- **Per-model headroom routing.** Anthropic meters each model family separately: a `5h` bucket, a `7d` bucket and a per-model `7d_<family>` bucket. dario reads all of them off every response and routes each request by the bucket that governs it — an Opus call to the seat with Opus room, a Sonnet call to the seat with Sonnet room, independently. Plan tiers mix freely; dario cares about headroom, not tier.
|
|
364
|
+
- **Session stickiness.** Claude's prompt cache is scoped to `{account × cache key}`, so rotating a long conversation across seats on headroom alone re-pays cache-create every turn, a **5–10× token-cost multiplier** on the cached portion. dario pins each conversation to one seat (hashed from its first message, deterministic) for the life of the session and rebinds only when that seat is exhausted.
|
|
365
|
+
- **In-flight 429 failover.** A seat hits its wall mid-request and dario retries the *same request* against the next-best seat before your client ever sees an error. The sticky binding follows, so the next turn doesn't re-select the cold one.
|
|
256
366
|
|
|
257
|
-
|
|
367
|
+
`--pool-strategy=fill-first` concentrates new conversations on one seat until it drains, for primary/backup setups. Refresh tokens expire about 28 days after the original grant regardless of rotation, so every seat's grant age is tracked and surfaced in `dario accounts list`, `dario doctor` and `GET /accounts` before it becomes a silent outage. Provision over HTTP with the headless [admin API](./docs/admin-api.md); pin one request to one seat with `dario accounts check <alias>`. Internals and the live `/accounts` + `/analytics` endpoints: [multi-account-pool.md](./docs/multi-account-pool.md); covered end-to-end by [`test/pool-e2e.mjs`](./test/pool-e2e.mjs).
|
|
258
368
|
|
|
259
|
-
|
|
369
|
+
### Watch it happen
|
|
260
370
|
|
|
261
|
-
|
|
371
|
+
Type `dario` with no arguments for a full-screen control panel: live request stream, per-model burn rate, rate-limit utilization per seat, billing-bucket breakdown, and an in-place config editor that writes `~/.dario/config.json`. Pure ANSI, zero new runtime deps. <kbd>Tab</kbd> moves between tabs, <kbd>r</kbd> refreshes, <kbd>R</kbd> resumes a halted overage guard, <kbd>q</kbd> quits.
|
|
262
372
|
|
|
263
|
-
|
|
373
|
+
<picture>
|
|
374
|
+
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/tui-analytics-dark.svg">
|
|
375
|
+
<img alt="The dario TUI Analytics tab: requests per minute, tokens in and out, thinking tokens, average latency, subscription percentage, a per-model bar chart, per-account rate-limit bars for the 5-hour and 7-day windows, and a billing breakdown." src=".github/readme/tui-analytics-light.svg" width="100%">
|
|
376
|
+
</picture>
|
|
264
377
|
|
|
265
|
-
|
|
378
|
+
<sub>Both screenshots are rendered from the real TUI against a fixture proxy by <a href="scripts/readme/tui.mjs"><code>scripts/readme/tui.mjs</code></a>, so a layout change shows up here instead of rotting a mock-up. The numbers are illustrative; the pixels are not.</sub>
|
|
266
379
|
|
|
267
|
-
|
|
380
|
+
## It tracks a moving target
|
|
268
381
|
|
|
269
|
-
|
|
382
|
+
Claude Code's request shape changes between releases — new betas, tool renames, per-model thinking configs — usually with no subscriber-facing note. dario doesn't *guess* that shape: it captures it live from your own installed `claude` binary on every startup, diffs it against each upstream release, and replays it faithfully. That's why your subscription routes the same through dario as it does through Claude Code itself: the request that leaves your machine *is* the shape your plan expects. Details: [wire-fidelity.md](./docs/wire-fidelity.md) · [#13](https://github.com/askalf/dario/discussions/13) · [#14](https://github.com/askalf/dario/discussions/14).
|
|
270
383
|
|
|
271
|
-
|
|
272
|
-
- **Same-binary remote-config drift** — [`cc-drift-template-watch.yml`](./.github/workflows/cc-drift-template-watch.yml) runs on a self-hosted runner with a live Claude session (the only place this class is visible) and opens an auto-rebake PR with the diff inline. Anthropic ships changes through Claude Code's *remote config*, not just npm.
|
|
273
|
-
- **Rate-map drift** — [`cc-billing-classifier-canary.yml`](./.github/workflows/cc-billing-classifier-canary.yml) sends one live request a day and asserts the response still bills to a subscription bucket.
|
|
384
|
+
Keeping that current is the whole job, and it's automated. These watchers run unattended; each badge is the live status of that workflow's latest run, and its label is the cadence:
|
|
274
385
|
|
|
275
|
-
|
|
386
|
+
| Watcher | Catches | Live |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| [`cc-drift-watch`](./.github/workflows/cc-drift-watch.yml) | A new Claude Code npm release that changes the wire shape. Auto-drafts the fix; [`cc-drift-auto-release`](./.github/workflows/cc-drift-auto-release.yml) merges and ships it within minutes. |  |
|
|
389
|
+
| [`cc-drift-template-watch`](./.github/workflows/cc-drift-template-watch.yml) | Same-binary *remote-config* drift, which no npm diff can see. Runs against a live Claude session on a self-hosted runner and opens a rebake PR with the diff inline. |  |
|
|
390
|
+
| [`cc-billing-classifier-canary`](./.github/workflows/cc-billing-classifier-canary.yml) | Classifier drift: one real request a day must still bill to a subscription bucket. |  |
|
|
391
|
+
| [`wire-drift-self-hosted`](./.github/workflows/wire-drift-self-hosted.yml) | Per-model beta headers and billing blocks the installed `claude` actually sends, model by model. |  |
|
|
392
|
+
| [`sdk-drift-watch`](./.github/workflows/sdk-drift-watch.yml) | Agent SDK / Stainless pins drifting from what the template assumes. |  |
|
|
393
|
+
| [`pricing-drift-watch`](./.github/workflows/pricing-drift-watch.yml) | dario's pricing table drifting from Anthropic's published rates, so the TUI's cost figures stay honest. |  |
|
|
394
|
+
| [`codex-drift-watch`](./.github/workflows/codex-drift-watch.yml) | The ChatGPT backend's model list or wire contract moving under the translator. |  |
|
|
395
|
+
| [`cc-oauth-health`](./.github/workflows/cc-oauth-health.yml) | The maintainer's own production proxy going unhealthy on any axis. |  |
|
|
396
|
+
| [`dario-doctor-watch`](./.github/workflows/dario-doctor-watch.yml) | Runtime drift only a live `dario doctor --obedience` surfaces: identity, obedience, usage buckets. |  |
|
|
397
|
+
| [`deployed-version-watch`](./.github/workflows/deployed-version-watch.yml) | Publishing is not deploying: is what's running what was last released? |  |
|
|
398
|
+
| [`cc-drift-watcher-liveness`](./.github/workflows/cc-drift-watcher-liveness.yml) | The watcher itself going quiet. Lives on GitHub-hosted infrastructure on purpose, so it survives the failures it watches for. |  |
|
|
399
|
+
|
|
400
|
+
Guarded by a PR-time compat gate that runs the full suite against a live proxy before any wire-shape change merges. A few changes the watchers caught and shipped fixes for, same day:
|
|
276
401
|
|
|
277
402
|
| Change (no subscriber-facing note) | Effect | dario shipped |
|
|
278
403
|
|---|---|---|
|
|
279
404
|
| `context-1m` dropped from the default beta set on the OAuth path | Subscription requests default to the 200K window on Sonnet/Opus | v3.38.3–4 |
|
|
280
405
|
| `thinking: {type:"adaptive"}` gated per-model server-side | Sonnet/Opus 4-5 400 every request through any proxy | [v3.38.5](https://github.com/askalf/dario/pull/273) |
|
|
281
|
-
| Per-model `anthropic-beta` sets
|
|
406
|
+
| Per-model `anthropic-beta` sets | Proxies sending one set diverge for non-Opus models | [v4.8.53](https://github.com/askalf/dario/pull/478) |
|
|
282
407
|
|
|
283
|
-
The full ledger lives in the [CHANGELOG](CHANGELOG.md). Setup
|
|
408
|
+
The full ledger lives in the [CHANGELOG](CHANGELOG.md), 500+ releases since April 2026. Setup and walkthrough: [drift-monitor.md](./docs/drift-monitor.md). The residual manual cases — OAuth rotation, runner re-registration — are in the [recovery runbook](./docs/recovery.md).
|
|
284
409
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
## The billing split — a contingency dario is built for
|
|
410
|
+
## Guardrails
|
|
288
411
|
|
|
289
|
-
|
|
412
|
+
### Overage guard
|
|
290
413
|
|
|
291
|
-
|
|
414
|
+
During normal operation, a subscriber should never see a single response billed outside their subscription pool. If one is, something is wrong — wire-shape drift, an account misconfig, a change upstream — and forwarding more requests in the same shape either bleeds real money (accounts with extra usage enabled) or returns a wall of rejections. The first hit is the signal; the rest are damage.
|
|
292
415
|
|
|
293
|
-
|
|
416
|
+
So the moment any upstream response bills to something other than your subscription pool, dario **halts the proxy**. The check is an allow-list, not a match on one string: anything that isn't a known subscription claim (`five_hour` / `seven_day` and their fallbacks) and isn't the `unknown` no-header sentinel trips it, so a billing bucket dario has never seen still halts. Subsequent requests return `503` with an Anthropic-shaped error body until you run `dario resume`, press <kbd>R</kbd> in the TUI, or the cooldown clears (default 30 min). The halt shows across the TUI, fires a best-effort OS notification, and emits named SSE events. Tune it via `~/.dario/config.json` → `overageGuard`, or `--overage-behavior=warn` / `--no-overage-guard` / `--overage-cooldown=<ms>`. In upstream-API-key passthrough mode (`ANTHROPIC_UPSTREAM_API_KEY`) the guard is off; `api` billing is the point there. Verified end-to-end by [`test/overage-guard-e2e-live.mjs`](./test/overage-guard-e2e-live.mjs). Background: [#288](https://github.com/askalf/dario/issues/288).
|
|
294
417
|
|
|
295
|
-
|
|
418
|
+
### The billing split, a contingency dario is built for
|
|
296
419
|
|
|
297
|
-
|
|
298
|
-
- **Byte-faithful passthrough for real Claude Code.** A genuine CC request already *is* the CC shape, so dario forwards it verbatim — system prompt, tools, thinking, key order untouched — keeping only its billing tag, identity, and cache breakpoints. Covers CC's whole family: the main loop, its Task/Agent sub-agents, and the permission classifier. Non-CC clients get the full template rebuild that keeps them routing. Background: [#678](https://github.com/askalf/dario/issues/678).
|
|
299
|
-
- **Headless admin API (`DARIO_ADMIN=1`).** Provision and manage pool accounts entirely over HTTP — start with zero accounts, `POST /admin/login/start`, paste the code back, routable the moment the `200` lands (live hot-reload, no restart). Token-gated even on loopback, audit-logged, rate-limited. Built for Docker / k8s / Pi. → [`docs/admin-api.md`](./docs/admin-api.md)
|
|
300
|
-
- **More than one instance, same accounts.** Anthropic's refresh tokens are single-use, so two replicas refreshing the same account leaves one holding a dead token. An optional refresh lock (Redis or Cloudflare backend, same contract, fails open) makes the loser adopt the winner's fresh credentials instead. Safe credential sharing — *not* full HA: rate-limit accounting and sticky routing stay per-instance. → [`docs/multi-instance.md`](./docs/multi-instance.md)
|
|
301
|
-
- **Runs any agent.** A 64-entry schema-verified `TOOL_MAP` pre-maps Cline, Roo, Kilo, Cursor, Windsurf, Continue, Copilot, OpenHands, OpenClaw, Hermes, and [hands](https://github.com/askalf/hands) tool names to CC's native set — no flag, no validator errors. MCP tools (`mcp__server__tool`) forward verbatim. [Compatibility matrix](./docs/integrations/compat-matrix.md) · [agent-compat.md](./docs/integrations/agent-compat.md).
|
|
302
|
-
- **Behavioral stealth (`--stealth`).** Adds *when* a request arrives to *what* it looks like — response-length-correlated think time and session-start latency. → [`docs/wire-fidelity.md`](./docs/wire-fidelity.md)
|
|
303
|
-
- **VPN / egress routing.** Route dario's upstream traffic through a VPN without putting the whole host on one. → [`docs/vpn-routing.md`](./docs/vpn-routing.md)
|
|
304
|
-
- **Recover output (`--system-prompt=partial`).** Strips CC's tone/verbosity constraints for 1.2–2.8× more output on open-ended work, without changing which pool you bill to. [#183](https://github.com/askalf/dario/discussions/183) · [`docs/system-prompt.md`](./docs/system-prompt.md)
|
|
305
|
-
- **Client-shape overrides.** `--honor-client-thinking` passes a client's own `thinking` block through unchanged; `--preserve-output-format` carries a client's `output_config.format` JSON schema through so structured-output SDKs (e.g. the Vercel AI SDK's `generateObject`) get schema-constrained output. Both off by default.
|
|
306
|
-
- **Reachable from inside CC / any MCP client.** `dario subagent install` registers a CC sub-agent for in-session diagnostics; `dario mcp` exposes dario as a read-only MCP server. → [`docs/sub-agent.md`](./docs/sub-agent.md) · [`docs/mcp-server.md`](./docs/mcp-server.md)
|
|
420
|
+
On **2026-05-13** Anthropic [announced](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) that, from 2026-06-15, Agent SDK and `claude -p` (headless) traffic would leave the subscription pool for a small separate monthly credit, then metered API rates. **They paused it before that date.** Those surfaces still bill subscription today, and Anthropic says it will give advance notice before any revised version. Nothing changed; no credits were issued.
|
|
307
421
|
|
|
308
|
-
|
|
422
|
+
The split isn't live, but it was announced once on short notice and could return, so dario is built for it either way. Every request is rebuilt into interactive Claude Code shape before it leaves your machine (and, with `--stealth`, the response-correlated timing an interactive session has), so your traffic sits in the subscription pool whether a split is paused or live. The daily canary above is the tripwire: it surfaces a revived split within a day instead of on a surprise invoice. Verify on your own machine right now: `dario doctor --usage` fires one request and prints the rate-limit headers; `representative-claim` should read `five_hour` or `seven_day`, both subscription buckets. Full timeline: [why-now-2026-06.md](./docs/why-now-2026-06.md).
|
|
309
423
|
|
|
310
424
|
## Trust & transparency
|
|
311
425
|
|
|
312
426
|
| Signal | Status |
|
|
313
427
|
|---|---|
|
|
314
|
-
| Source | **~31k** lines of TypeScript across **67** files
|
|
428
|
+
| Source | **~31k** lines of TypeScript across **67** files, auditable in a weekend. One credential path since v5: the pool. |
|
|
315
429
|
| Dependencies | **0 runtime.** Verify: `npm ls --production` |
|
|
316
|
-
| Provenance | Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions + Sigstore |
|
|
317
|
-
| Scanning | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) on every push and weekly |
|
|
318
|
-
| Tests | **
|
|
430
|
+
| Provenance | Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions + Sigstore, published with OIDC trusted publishing — no long-lived npm token exists to leak |
|
|
431
|
+
| Scanning | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) on every push and weekly · [ClusterFuzzLite](./.github/workflows/cflite.yml) fuzzes the SSE translator and rejection parsers weekly · [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/askalf/dario) and [Best Practices](https://www.bestpractices.dev/projects/13638) badges above are live |
|
|
432
|
+
| Tests | **176 test files** run in parallel by `npm test` on Node 18, 20 and 22; the live e2e / compat / stealth suites have their own entry points. Green on every release |
|
|
319
433
|
| Credentials | Your own subscription tokens, never logged, redacted from errors, `0600` on disk in `0700` dirs |
|
|
320
|
-
| Network | Binds `127.0.0.1` by default; upstream only to configured backends over HTTPS; hardcoded SSRF allow-list |
|
|
434
|
+
| Network | Binds `127.0.0.1` by default; upstream only to configured backends over HTTPS; hardcoded SSRF allow-list; refuses a non-loopback bind without `DARIO_API_KEY` |
|
|
321
435
|
| Telemetry | **None.** No analytics, no tracking, nothing phones home |
|
|
436
|
+
| This README | CI fails if the line count above drifts from `src/` or a link or anchor here stops resolving ([`check-readme-line-count.mjs`](./scripts/check-readme-line-count.mjs), [`check-readme-links.mjs`](./scripts/check-readme-links.mjs)); the screenshots are generated from the real TUI ([how](./scripts/readme/README.md)) |
|
|
322
437
|
|
|
323
438
|
```bash
|
|
324
439
|
npm audit signatures
|
|
@@ -326,13 +441,11 @@ npm view @askalf/dario dist.integrity
|
|
|
326
441
|
cd $(npm root -g)/@askalf/dario && npm ls --production
|
|
327
442
|
```
|
|
328
443
|
|
|
329
|
-
|
|
444
|
+
Security reports go to **security@askalf.org**, not a public issue: [SECURITY.md](SECURITY.md). API stability commitments (`@stable` / `@experimental` / `@deprecated`, deprecation cycles): [STABILITY.md](STABILITY.md).
|
|
330
445
|
|
|
331
446
|
## Honest about what this is
|
|
332
447
|
|
|
333
|
-
dario uses your own subscription credentials, authenticates you as you, and impersonates nobody. What it changes is the **client
|
|
334
|
-
|
|
335
|
-
---
|
|
448
|
+
dario uses your own subscription credentials, authenticates you as you, and impersonates nobody. What it changes is the **client**: it rebuilds each request into the exact shape Claude Code emits (captured live from your installed binary) so your plan routes the same no matter which tool actually sent it. Be clear-eyed on both sides of that. It's a transparency tool, in that it documents request behavior Anthropic doesn't publish for subscribers, and it's also, plainly, running through your subscription traffic that Anthropic's own tools bill differently. Both are true. dario is unofficial and unaffiliated ([DISCLAIMER.md](./DISCLAIMER.md)); decide with both in view.
|
|
336
449
|
|
|
337
450
|
## Will my account get suspended?
|
|
338
451
|
|
|
@@ -340,109 +453,184 @@ The most common question about dario, and it deserves a straight answer: **I can
|
|
|
340
453
|
|
|
341
454
|
**What dario does:**
|
|
342
455
|
|
|
343
|
-
- **Runs entirely on your machine.** Your subscription token never touches my servers or anyone else's
|
|
344
|
-
- **Authenticates as you, with your own Claude login
|
|
456
|
+
- **Runs entirely on your machine.** Your subscription token never touches my servers or anyone else's; requests go straight from your computer to Anthropic.
|
|
457
|
+
- **Authenticates as you, with your own Claude login**, the same OAuth credential Claude Code itself uses. It impersonates nobody and shares nothing.
|
|
345
458
|
- **Doesn't modify your account, billing, or subscription settings.**
|
|
346
|
-
- **Sends requests in the shape the official client sends them
|
|
347
|
-
- **Reports nothing, anywhere.** No telemetry, no analytics, nothing phones home
|
|
459
|
+
- **Sends requests in the shape the official client sends them**, rebuilt from your own installed binary, not spoofed from a hardcoded fake.
|
|
460
|
+
- **Reports nothing, anywhere.** No telemetry, no analytics, nothing phones home; [verifiable in the source](#trust--transparency), which is the point of keeping it auditable in a weekend.
|
|
348
461
|
|
|
349
|
-
**What dario does that Claude Code doesn't:** it lets tools *other than* Claude Code use that subscription. That's the whole point of it, and it's also the part that sits outside what Anthropic's own client does. Whether that falls within your plan's terms is Anthropic's call, not mine
|
|
462
|
+
**What dario does that Claude Code doesn't:** it lets tools *other than* Claude Code use that subscription. That's the whole point of it, and it's also the part that sits outside what Anthropic's own client does. Whether that falls within your plan's terms is Anthropic's call, not mine. Read [their terms](https://www.anthropic.com/legal/consumer-terms), read [DISCLAIMER.md](./DISCLAIMER.md), and decide deliberately.
|
|
350
463
|
|
|
351
|
-
**On policy risk specifically:** Anthropic's position on third-party clients has moved before and can move again. dario is built to surface that fast rather than paper over it
|
|
464
|
+
**On policy risk specifically:** Anthropic's position on third-party clients has moved before and can move again. dario is built to surface that fast rather than paper over it; see [the billing split](#the-billing-split-a-contingency-dario-is-built-for) for the contingency already in place and the daily canary watching for it.
|
|
352
465
|
|
|
353
466
|
Ongoing discussion, including other users' experiences: [#724](https://github.com/askalf/dario/discussions/724).
|
|
354
467
|
|
|
355
|
-
---
|
|
356
|
-
|
|
357
468
|
## Who it's for
|
|
358
469
|
|
|
359
|
-
**Best fit:** developers juggling multiple LLM tools and per-tool API keys · Claude Pro/Max subscribers who want their plan usable everywhere, not just in Claude Code · teams running local
|
|
470
|
+
**Best fit:** developers juggling multiple LLM tools and per-tool API keys · Claude Pro/Max subscribers who want their plan usable everywhere, not just in Claude Code · ChatGPT Plus/Pro subscribers who want their plan in OpenAI-compatible harnesses · teams running local or hosted OpenAI-compat servers who want one stable local endpoint · Agent SDK users who want subscription routing with zero code change · power users wanting multi-account pooling with 429 failover.
|
|
360
471
|
|
|
361
|
-
**Not a fit:** you need vendor-managed production SLAs (use the provider APIs) · you want a hosted multi-tenant team platform with dashboards
|
|
472
|
+
**Not a fit:** you need vendor-managed production SLAs (use the provider APIs) · you want a hosted multi-tenant team platform with dashboards and SSO (dario is a single-owner local proxy) · you want a chat UI (use claude.ai).
|
|
362
473
|
|
|
363
|
-
|
|
474
|
+
**How it compares.** Only one of these routes a consumer subscription; the others route API keys, and that is the whole split.
|
|
364
475
|
|
|
365
|
-
|
|
476
|
+
| Tool | What it is | When it wins |
|
|
477
|
+
|---|---|---|
|
|
478
|
+
| **dario** | Local proxy that routes your Claude and ChatGPT plans, plus any OpenAI-compatible API | You already pay for a plan and want every tool on your machine to use it |
|
|
479
|
+
| **LiteLLM** | Python SDK + proxy, 100+ providers via API keys, enterprise features | You have API keys, want central spend controls, or run a hosted multi-tenant service |
|
|
480
|
+
| **OpenRouter** | Hosted aggregator, one API key for hundreds of models | You want model breadth and are fine with pay-per-token |
|
|
481
|
+
| **Kong AI Gateway** | Enterprise on-prem API gateway for LLMs | You already run Kong and need AI traffic under the same governance |
|
|
366
482
|
|
|
367
|
-
|
|
483
|
+
Longer version, with specifics: [#68](https://github.com/askalf/dario/discussions/68).
|
|
368
484
|
|
|
369
|
-
|
|
485
|
+
## Commands
|
|
370
486
|
|
|
371
|
-
|
|
487
|
+
| Command | What it does |
|
|
488
|
+
|---|---|
|
|
489
|
+
| `dario` | The TUI: status, config editor, analytics, hits, accounts, backends |
|
|
490
|
+
| `dario login [--manual]` | Log in to your Claude plan. Picks up Claude Code's credentials or runs its own OAuth flow; `--manual` for SSH / containers |
|
|
491
|
+
| `dario proxy` | Start the local endpoint on `:3456` |
|
|
492
|
+
| `dario doctor [--usage] [--probe] [--json]` | One aggregated health report: runtime/TLS, template and drift, OAuth, pool, refresh-grant age, failover readiness, backends |
|
|
493
|
+
| `dario add altman` / `dario add amodei` | Attach a ChatGPT plan / a Claude account, by whose it is |
|
|
494
|
+
| `dario accounts list` / `add` / `remove` / `check <alias>` | Pool management; `check` sends one pinned request per model through the running proxy |
|
|
495
|
+
| `dario backend list` / `add` / `remove` | OpenAI-compatible API-key backends |
|
|
496
|
+
| `dario codex list` / `add` / `remove` | ChatGPT accounts (the long form of `dario add altman`) |
|
|
497
|
+
| `dario usage` · `dario config` · `dario status` | Burn rate for the last hour · effective config, redacted · token health |
|
|
498
|
+
| `dario resume` · `dario refresh` · `dario logout` · `dario upgrade` | Clear an overage halt · force a token refresh · delete credentials · safe self-update |
|
|
499
|
+
| `dario mcp` · `dario subagent install` | Reach dario from inside any MCP client, or from inside a Claude Code session, read-only |
|
|
500
|
+
|
|
501
|
+
| Endpoint | Description |
|
|
502
|
+
|---|---|
|
|
503
|
+
| `POST /v1/messages` · `POST /v1/chat/completions` | The two wire shapes, any plan behind either |
|
|
504
|
+
| `GET /v1/models` | Live model list: the Claude catalog plus whatever your ChatGPT plan lists |
|
|
505
|
+
| `GET /health` · `GET /livez` | Serviceability (503 when not) · liveness. `/health?probe=1` sends one real request |
|
|
506
|
+
| `GET /status` · `GET /accounts` · `GET /analytics` | OAuth detail · per-seat utilization and grant age · per-account / per-model stats and burn rate |
|
|
507
|
+
|
|
508
|
+
Every flag and env var: [commands.md](./docs/commands.md) · env vars grouped by task, for Docker / k8s / systemd: [configuration.md](./docs/configuration.md) · SDK examples: [usage.md](./docs/usage.md).
|
|
509
|
+
|
|
510
|
+
<details>
|
|
511
|
+
<summary><strong>More knobs</strong> — stealth timing, system-prompt modes, client-shape overrides, VPN egress, MCP</summary>
|
|
512
|
+
|
|
513
|
+
- **Behavioral stealth (`--stealth`).** Adds *when* a request arrives to *what* it looks like: response-length-correlated think time and session-start latency. [wire-fidelity.md](./docs/wire-fidelity.md)
|
|
514
|
+
- **Recover output (`--system-prompt=partial`).** Strips Claude Code's tone and verbosity constraints for 1.2–2.8× more output on open-ended work, without changing which pool you bill to. [#183](https://github.com/askalf/dario/discussions/183) · [system-prompt.md](./docs/system-prompt.md)
|
|
515
|
+
- **Client-shape overrides.** `--honor-client-thinking` passes a client's own `thinking` block through; `--preserve-output-format` carries a client's `output_config.format` JSON schema through so structured-output SDKs get schema-constrained output. Both off by default.
|
|
516
|
+
- **Runs any agent.** A 64-entry schema-verified `TOOL_MAP` pre-maps Cline, Roo, Kilo, Cursor, Windsurf, Continue, Copilot, OpenHands, OpenClaw and Hermes tool names to Claude Code's native set; MCP tools (`mcp__server__tool`) forward verbatim. Custom schemas: `--preserve-tools` or `--hybrid-tools`. [agent-compat.md](./docs/integrations/agent-compat.md)
|
|
517
|
+
- **VPN / egress routing.** Route dario's upstream traffic through a VPN without putting the whole host on one. [vpn-routing.md](./docs/vpn-routing.md)
|
|
518
|
+
- **More than one instance, same accounts.** Refresh tokens are single-use, so two replicas refreshing the same seat leave one holding a dead token; the optional refresh lock (Redis or Cloudflare) makes the loser adopt the winner's credentials. [multi-instance.md](./docs/multi-instance.md)
|
|
519
|
+
- **PII redaction in front of dario.** Pair it with [cordon](https://github.com/askalf/cordon): [integrations/cordon.md](./docs/integrations/cordon.md)
|
|
520
|
+
- **Reachable from inside Claude Code or any MCP client.** `dario subagent install` registers a sub-agent for in-session diagnostics; `dario mcp` exposes dario as a read-only MCP server. [sub-agent.md](./docs/sub-agent.md) · [mcp-server.md](./docs/mcp-server.md)
|
|
521
|
+
</details>
|
|
372
522
|
|
|
373
523
|
## FAQ
|
|
374
524
|
|
|
375
|
-
|
|
376
|
-
|
|
525
|
+
<details>
|
|
526
|
+
<summary><strong>Does this violate Anthropic's terms?</strong></summary>
|
|
377
527
|
|
|
378
|
-
|
|
379
|
-
|
|
528
|
+
Mechanically, dario uses your existing Claude Code OAuth tokens: it authenticates you as you, with your subscription, through Anthropic's official endpoints. Whether any particular use complies with current terms is between you and Anthropic; consult their terms and your agreement. Independent, unofficial, third-party — see [DISCLAIMER.md](DISCLAIMER.md). On the suspension question specifically: [Will my account get suspended?](#will-my-account-get-suspended)
|
|
529
|
+
</details>
|
|
380
530
|
|
|
381
|
-
|
|
382
|
-
|
|
531
|
+
<details>
|
|
532
|
+
<summary><strong>Do I need Claude Code installed?</strong></summary>
|
|
383
533
|
|
|
384
|
-
|
|
385
|
-
|
|
534
|
+
Recommended, not required. With it, `dario login` picks up credentials automatically and the template extractor reads your binary on every startup. Without it, dario runs its own OAuth flow and falls back to the bundled (scrubbed) template snapshot, which the drift watchers keep current.
|
|
535
|
+
</details>
|
|
386
536
|
|
|
387
|
-
|
|
388
|
-
|
|
537
|
+
<details>
|
|
538
|
+
<summary><strong>Do I need Bun?</strong></summary>
|
|
389
539
|
|
|
390
|
-
|
|
391
|
-
|
|
540
|
+
Optional, recommended: Bun's TLS ClientHello matches Claude Code's runtime, and dario relaunches itself under Bun when it finds one on `PATH`. Without it dario works fine on Node; `dario doctor` flags the mismatch and `--strict-tls` hard-fails until resolved.
|
|
541
|
+
</details>
|
|
392
542
|
|
|
393
|
-
|
|
543
|
+
<details>
|
|
544
|
+
<summary><strong>Can I use dario without a Claude subscription?</strong></summary>
|
|
394
545
|
|
|
395
|
-
|
|
546
|
+
Yes. Skip `dario login`, run `dario add altman` for a ChatGPT plan or `dario backend add openai --key=…` for an API key, and you have a local router with no Claude involvement. `--no-claude-auth` keeps the Claude token untouched entirely.
|
|
547
|
+
</details>
|
|
548
|
+
|
|
549
|
+
<details>
|
|
550
|
+
<summary><strong><code>representative-claim: seven_day</code> in my headers — am I downgraded?</strong></summary>
|
|
551
|
+
|
|
552
|
+
No. `five_hour` and `seven_day` are both subscription billing, different accounting buckets in the same mode. `overage` is the one that flips you to per-token, and the overage guard halts on it. [#1](https://github.com/askalf/dario/discussions/1)
|
|
553
|
+
</details>
|
|
554
|
+
|
|
555
|
+
<details>
|
|
556
|
+
<summary><strong>My usage through dario is higher than through Claude Code directly. Why?</strong></summary>
|
|
396
557
|
|
|
397
|
-
|
|
558
|
+
Almost always the prompt-cache TTL, not proxy overhead: dario mirrors whatever cache stamp your client sends, and many harnesses send the 5-minute one, so gaps longer than five minutes between turns re-create the prefix. `DARIO_CACHE_TTL_1H=1` forces the 1-hour TTL. The full breakdown, with the two-message check that tells you which case you're in: [faq.md](./docs/faq.md).
|
|
559
|
+
</details>
|
|
398
560
|
|
|
399
|
-
|
|
561
|
+
<details>
|
|
562
|
+
<summary><strong>Will the billing split break my setup?</strong></summary>
|
|
563
|
+
|
|
564
|
+
It was announced, then paused before it took effect; today nothing changed and your traffic still bills subscription. If it returns (Anthropic promised advance notice), dario already rewrites every request to interactive-Claude-Code shape, and the daily canary surfaces the change within a day. See [the billing split](#the-billing-split-a-contingency-dario-is-built-for).
|
|
565
|
+
</details>
|
|
566
|
+
|
|
567
|
+
<details>
|
|
568
|
+
<summary><strong>Why "dario"?</strong></summary>
|
|
569
|
+
|
|
570
|
+
It's a name, not an acronym. Don't overthink it.
|
|
571
|
+
</details>
|
|
572
|
+
|
|
573
|
+
Full FAQ, including per-tool 401s and Team/Enterprise plans: [faq.md](./docs/faq.md).
|
|
574
|
+
|
|
575
|
+
## Deep dives
|
|
576
|
+
|
|
577
|
+
- [#183 — Modifying Claude Code's system prompt doesn't change billing; stripping its constraints recovers 1.2–2.8× output](https://github.com/askalf/dario/discussions/183)
|
|
400
578
|
- [#68 — dario vs LiteLLM / OpenRouter / Kong AI Gateway (when each wins)](https://github.com/askalf/dario/discussions/68)
|
|
401
|
-
- [#14 — Template
|
|
579
|
+
- [#14 — Template replay: why we replay the shape instead of matching signals](https://github.com/askalf/dario/discussions/14)
|
|
402
580
|
- [#13 — Claude Code's request shape, documented](https://github.com/askalf/dario/discussions/13)
|
|
403
581
|
- [#1 — Rate-limit header analysis](https://github.com/askalf/dario/discussions/1)
|
|
404
|
-
|
|
405
|
-
---
|
|
582
|
+
- [system-prompt-classifier-study.md](./docs/research/system-prompt-classifier-study.md), the measurements behind `--system-prompt=partial`
|
|
406
583
|
|
|
407
584
|
## Contributing
|
|
408
585
|
|
|
409
|
-
PRs welcome. Small TypeScript codebase, zero runtime deps. Architecture
|
|
586
|
+
PRs welcome. Small TypeScript codebase, zero runtime deps. Architecture, file-by-file map and the review bar in [CONTRIBUTING.md](CONTRIBUTING.md); release mechanics in [RELEASING.md](RELEASING.md).
|
|
410
587
|
|
|
411
588
|
```bash
|
|
412
589
|
git clone https://github.com/askalf/dario && cd dario
|
|
413
590
|
npm install
|
|
414
591
|
npm run dev # tsx, no build step
|
|
415
|
-
npm test #
|
|
592
|
+
npm test # 176 files in parallel via test/all.test.mjs
|
|
416
593
|
npm run e2e # live proxy + OAuth (needs a working Claude backend)
|
|
417
594
|
```
|
|
418
595
|
|
|
419
596
|
Drift and audit runners, none of them part of `npm test`:
|
|
420
597
|
|
|
421
598
|
```bash
|
|
422
|
-
npm run drift:wire
|
|
423
|
-
npm run drift:sdk
|
|
424
|
-
npm run audit:tui
|
|
425
|
-
npm run check:overage
|
|
426
|
-
npm run stress
|
|
427
|
-
npm run cch:calibrate
|
|
599
|
+
npm run drift:wire # compare a live Claude Code capture against the baked template
|
|
600
|
+
npm run drift:sdk # Agent SDK / Stainless pin drift
|
|
601
|
+
npm run audit:tui # drives the real TUI through a fake TTY at 12 geometries
|
|
602
|
+
npm run check:overage # overage-classifier check against live headers
|
|
603
|
+
npm run stress # concurrency / queue behaviour under load
|
|
604
|
+
npm run cch:calibrate # re-derive the billing-tag cch seed for a new Claude Code build
|
|
605
|
+
npm run readme:assets # regenerate the diagrams and TUI screenshots above
|
|
428
606
|
```
|
|
429
607
|
|
|
430
|
-
Two easy ways to help beyond code: **star the repo
|
|
608
|
+
Two easy ways to help beyond code: **star the repo**, the clearest signal this is useful, and **file drift**: open an issue when a rate-limit header flips or a tool that worked yesterday breaks today, and it gets documented in public alongside the fix. Follow [@ask_alf](https://x.com/ask_alf) for drift bulletins as they land.
|
|
609
|
+
|
|
610
|
+
<picture>
|
|
611
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=askalf/dario&type=Date&theme=dark">
|
|
612
|
+
<img alt="Star history of askalf/dario" src="https://api.star-history.com/svg?repos=askalf/dario&type=Date" width="100%">
|
|
613
|
+
</picture>
|
|
431
614
|
|
|
432
615
|
### Contributors
|
|
433
616
|
|
|
434
617
|
| Who | Contributions |
|
|
435
618
|
|---|---|
|
|
436
619
|
| [@GodsBoy](https://github.com/GodsBoy) | Proxy auth, token redaction, error sanitization ([#2](https://github.com/askalf/dario/pull/2)) |
|
|
437
|
-
| [@belangertrading](https://github.com/belangertrading) | Billing-classification investigation ([#4](https://github.com/askalf/dario/issues/4), [#6](https://github.com/askalf/dario/issues/6), [#7](https://github.com/askalf/dario/issues/7), [#12](https://github.com/askalf/dario/issues/12), [#23](https://github.com/askalf/dario/issues/23)) |
|
|
620
|
+
| [@belangertrading](https://github.com/belangertrading) | Billing-classification investigation ([#4](https://github.com/askalf/dario/issues/4), [#6](https://github.com/askalf/dario/issues/6), [#7](https://github.com/askalf/dario/issues/7), [#12](https://github.com/askalf/dario/issues/12), [#23](https://github.com/askalf/dario/issues/23)), multi-agent billing FAQ ([#27](https://github.com/askalf/dario/pull/27)) |
|
|
621
|
+
| [@earlvanze](https://github.com/earlvanze) | OpenClaw tool mappings ([#19](https://github.com/askalf/dario/pull/19)), OAuth manual override ([#47](https://github.com/askalf/dario/pull/47)), HTTPS warning ([#53](https://github.com/askalf/dario/pull/53)) |
|
|
438
622
|
| [@iNicholasBE](https://github.com/iNicholasBE) | macOS keychain credential detection ([#30](https://github.com/askalf/dario/pull/30)) |
|
|
439
623
|
| [@boeingchoco](https://github.com/boeingchoco) | Reverse tool-param translation ([#29](https://github.com/askalf/dario/issues/29)), SSE framing regression catch, hybrid-tool motivation ([#33](https://github.com/askalf/dario/issues/33), [#36](https://github.com/askalf/dario/issues/36)) |
|
|
440
624
|
| [@tetsuco](https://github.com/tetsuco) | Scrubber path corruption ([#35](https://github.com/askalf/dario/issues/35)), OpenClaw reverse-mapping collisions ([#37](https://github.com/askalf/dario/issues/37)), 20x-tier report ([#42](https://github.com/askalf/dario/issues/42)) |
|
|
441
625
|
| [@mikelovatt](https://github.com/mikelovatt) | Silent subscription-drain surfaced via friendly billing buckets ([#34](https://github.com/askalf/dario/issues/34)) |
|
|
442
626
|
| [@ringge](https://github.com/ringge) | `--no-auto-detect` for text-tool auto-preserve ([#40](https://github.com/askalf/dario/issues/40)) |
|
|
443
|
-
| [@
|
|
444
|
-
|
|
445
|
-
|
|
627
|
+
| [@Saik0s](https://github.com/Saik0s) | Wildcard CORS allow-headers, Opus 4.7 catalog entry ([#222](https://github.com/askalf/dario/pull/222)) |
|
|
628
|
+
| [@boredland](https://github.com/boredland) | Time-to-reset in `dario doctor --usage` ([#550](https://github.com/askalf/dario/pull/550)) |
|
|
629
|
+
| [@pnewell](https://github.com/pnewell) | `--preserve-output-format` for structured-output SDKs ([#583](https://github.com/askalf/dario/pull/583)) |
|
|
630
|
+
| [@jerzydziewierz](https://github.com/jerzydziewierz) | TUI Config tab clipping and scrolling ([#861](https://github.com/askalf/dario/pull/861)) |
|
|
631
|
+
| [@chaogebaba](https://github.com/chaogebaba) | Auto-release must never fire from a fork ([#1029](https://github.com/askalf/dario/pull/1029)) |
|
|
632
|
+
| [@anupamme](https://github.com/anupamme) | Refresh-lock ownership by server-issued lock id ([#1059](https://github.com/askalf/dario/pull/1059)) |
|
|
633
|
+
| [@LiveNathan](https://github.com/LiveNathan) | Never send or stamp empty text blocks ([#1067](https://github.com/askalf/dario/pull/1067)) |
|
|
446
634
|
|
|
447
635
|
## Disclaimers
|
|
448
636
|
|
|
@@ -450,11 +638,11 @@ Two easy ways to help beyond code: **star the repo** (the clearest signal this i
|
|
|
450
638
|
|
|
451
639
|
## License
|
|
452
640
|
|
|
453
|
-
MIT — see [LICENSE](LICENSE) and [DISCLAIMER.md](DISCLAIMER.md).
|
|
641
|
+
MIT — see [LICENSE](LICENSE) and [DISCLAIMER.md](DISCLAIMER.md). The embedded README font is Space Mono under the [SIL Open Font License](./scripts/readme/fonts/OFL.txt).
|
|
454
642
|
|
|
455
643
|
## Own Your Stack
|
|
456
644
|
|
|
457
|
-
dario is the routing layer of **[Own Your Stack](https://github.com/askalf)
|
|
645
|
+
dario is the routing layer of **[Own Your Stack](https://github.com/askalf)**, open tools for owning your AI infrastructure instead of renting it by the token. One subscription. Your box. Your terms.
|
|
458
646
|
|
|
459
647
|
- **[dario](https://github.com/askalf/dario)** — own your routing _(you are here)_
|
|
460
648
|
- **[hybrid](https://github.com/askalf/hybrid)** — own your inference
|
|
@@ -463,20 +651,16 @@ dario is the routing layer of **[Own Your Stack](https://github.com/askalf)**
|
|
|
463
651
|
- **[truecopy](https://github.com/askalf/truecopy)** — own your agent skills
|
|
464
652
|
- **[agent-security-stack](https://github.com/askalf/agent-security-stack)** — own your agent security stack: redstamp + truecopy + strongroom leases, one MCP server
|
|
465
653
|
- **[cordon](https://github.com/askalf/cordon)** — own your prompts · [pair it with dario](./docs/integrations/cordon.md)
|
|
466
|
-
- **[fieldpass](https://github.com/askalf/fieldpass)** — own your agent browser
|
|
467
654
|
- **[plumbline](https://github.com/askalf/plumbline)** — own your agent oversight
|
|
468
655
|
- **[amnesia](https://github.com/askalf/amnesia)** — own your search
|
|
469
656
|
- **[pgflex](https://github.com/askalf/pgflex)** — own your Postgres
|
|
470
657
|
- **[redisflex](https://github.com/askalf/redisflex)** — own your Redis
|
|
471
658
|
- **[askalf](https://askalf.org)** — own your operation: the AI operation that runs Sprayberry Labs
|
|
472
659
|
|
|
473
|
-
---
|
|
474
|
-
|
|
475
660
|
## Built by Thomas Sprayberry
|
|
476
661
|
|
|
477
|
-
dario is part of **Own Your Stack
|
|
662
|
+
dario is part of **Own Your Stack**, the open toolkit behind **[Sprayberry Labs](https://sprayberrylabs.com)**, the software studio with one human on staff, run by [askalf](https://askalf.org), the AI operation these tools are part of.
|
|
478
663
|
|
|
479
|
-
Built in the open, scars included. Follow the build
|
|
664
|
+
Built in the open, scars included. Follow the build: **[@ask_alf](https://x.com/ask_alf)** · **[sprayberrylabs.com/own-your-stack](https://sprayberrylabs.com/own-your-stack)**
|
|
480
665
|
|
|
481
|
-
|
|
482
|
-
Part of **[Own Your Stack](https://github.com/askalf)** — own your AI infrastructure instead of renting it. Built by Thomas Sprayberry.
|
|
666
|
+
[^plans]: Pro at $20 a month, Max 5x at $100, Max 20x at $200, as listed on [claude.com/pricing](https://claude.com/pricing) on 2026-09-06. Annual billing is cheaper; check the page for what's current.
|
package/dist/claude-model.js
CHANGED
|
@@ -72,7 +72,17 @@ function servableTarget(target, bases) {
|
|
|
72
72
|
return null;
|
|
73
73
|
const resolved = resolveAliasAgainst(stripped, bases) ?? stripped;
|
|
74
74
|
const base = resolved.endsWith('[1m]') ? resolved.slice(0, -4) : resolved;
|
|
75
|
-
|
|
75
|
+
// The catalog keeps ONE spelling per model — the short id when upstream
|
|
76
|
+
// lists both `claude-opus-4-8` and `claude-opus-4-8-YYYYMMDD` (see
|
|
77
|
+
// normalizeUpstreamIds) — while a client may send either. Compare with the
|
|
78
|
+
// date stripped on both sides, and return the name as written: Anthropic
|
|
79
|
+
// accepts both forms, so the pool forwards whichever the caller chose.
|
|
80
|
+
const key = undated(base);
|
|
81
|
+
return bases.some((b) => undated(b.toLowerCase()) === key) ? resolved : null;
|
|
82
|
+
}
|
|
83
|
+
/** `claude-opus-4-8-20260101` → `claude-opus-4-8`; anything else unchanged. */
|
|
84
|
+
function undated(id) {
|
|
85
|
+
return id.replace(/-\d{8}$/, '');
|
|
76
86
|
}
|
|
77
87
|
/**
|
|
78
88
|
* The id the Claude pool would serve `model` as AND the effort the entry asked
|
package/dist/proxy.js
CHANGED
|
@@ -23,6 +23,8 @@ import { handleAdminRequest } from './admin-api.js';
|
|
|
23
23
|
import { createTokenBucket } from './rate-limit.js';
|
|
24
24
|
import { getOpenAIBackend, isOpenAIModel, forwardToOpenAI } from './openai-backend.js';
|
|
25
25
|
import { forwardToCodex, getCodexModelSlugs, peekCodexModelSlugs, pickCodexFallback, pickClaudeTarget, CODEX_BACKEND_BASE_URL } from './codex-backend.js';
|
|
26
|
+
import { isClaudeServableModel } from './claude-model.js';
|
|
27
|
+
import { MODEL_UNROUTABLE } from './upstream-rejection.js';
|
|
26
28
|
import { readCompareTarget, teeResponse, runCompare, writeCompareRecord, COMPARE_RESULT_HEADER } from './compare.js';
|
|
27
29
|
import { listCodexAccountAliases, loadAllCodexAccounts, codexAccountNeedsRefresh, hasAnyCodexAccount, selectCodexAccount, getFreshCodexAccount, getCodexRefreshFailure, CodexCredentialsUnavailableError } from './codex-accounts.js';
|
|
28
30
|
import { route as routeProvider } from './provider-adapter.js';
|
|
@@ -2726,6 +2728,11 @@ export async function startProxy(opts = {}) {
|
|
|
2726
2728
|
// wire shape, and no upstream round-trip for a request nothing can serve.
|
|
2727
2729
|
// Upstreams do the same (Anthropic: "The request body is not valid
|
|
2728
2730
|
// JSON"; OpenAI: "We could not parse the JSON body of your request").
|
|
2731
|
+
//
|
|
2732
|
+
// The object this guard parses is kept as `parsedBody` and reused by the
|
|
2733
|
+
// provider-prefix block and the template build below, so the bytes are
|
|
2734
|
+
// JSON.parsed once per request (#642-audit; second-read finding on #1231).
|
|
2735
|
+
let parsedBody = null;
|
|
2729
2736
|
{
|
|
2730
2737
|
let invalid = null;
|
|
2731
2738
|
if (body.length === 0)
|
|
@@ -2740,6 +2747,8 @@ export async function startProxy(opts = {}) {
|
|
|
2740
2747
|
const v = JSON.parse(text);
|
|
2741
2748
|
if (v === null || typeof v !== 'object' || Array.isArray(v))
|
|
2742
2749
|
invalid = 'request body must be a JSON object';
|
|
2750
|
+
else
|
|
2751
|
+
parsedBody = v;
|
|
2743
2752
|
}
|
|
2744
2753
|
catch (err) {
|
|
2745
2754
|
invalid = `request body is not valid JSON: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -2775,15 +2784,14 @@ export async function startProxy(opts = {}) {
|
|
|
2775
2784
|
// recognizes through its own Anthropic gateway, bypassing localhost).
|
|
2776
2785
|
let forcedProvider = cliProviderOverride;
|
|
2777
2786
|
let requestEffort; // dario#419 — per-request effort parsed from a model-name suffix (model:high / model-high)
|
|
2778
|
-
//
|
|
2779
|
-
// template-build block further down so the same bytes are not
|
|
2780
|
-
// twice per request (#642-audit). Mutations in the prefix block
|
|
2781
|
-
// `body` FROM this object, so it always represents the current
|
|
2782
|
-
|
|
2783
|
-
if (
|
|
2787
|
+
// `parsedBody` was parsed by the invalid-body guard above and is shared
|
|
2788
|
+
// with the template-build block further down so the same bytes are not
|
|
2789
|
+
// JSON.parsed twice per request (#642-audit). Mutations in the prefix block
|
|
2790
|
+
// re-serialize `body` FROM this object, so it always represents the current
|
|
2791
|
+
// body.
|
|
2792
|
+
if (parsedBody !== null) {
|
|
2784
2793
|
try {
|
|
2785
|
-
const parsed =
|
|
2786
|
-
parsedBody = parsed;
|
|
2794
|
+
const parsed = parsedBody;
|
|
2787
2795
|
// User-defined aliases first — before provider-prefix parsing, so
|
|
2788
2796
|
// an alias target carrying a prefix (`my-fast` → `openai:gpt-4o`)
|
|
2789
2797
|
// retargets the backend through the existing machinery below.
|
|
@@ -2927,7 +2935,7 @@ export async function startProxy(opts = {}) {
|
|
|
2927
2935
|
// reaches the subscription even when an API-key backend is configured too.
|
|
2928
2936
|
if (body.length > 0) {
|
|
2929
2937
|
try {
|
|
2930
|
-
const peek =
|
|
2938
|
+
const peek = (parsedBody ?? {}); // parsed once by the invalid-body guard; `body` is re-serialized from it
|
|
2931
2939
|
const rawModel = (peek.model || '').toString();
|
|
2932
2940
|
const requestPoolFallbackModels = selectPoolFallbackModels(poolFallbackSpec, rawModel);
|
|
2933
2941
|
const requestPoolFallbackModel = requestPoolFallbackModels[0] ?? null;
|
|
@@ -3117,6 +3125,42 @@ export async function startProxy(opts = {}) {
|
|
|
3117
3125
|
await forwardToOpenAI(req, res, body, openaiBackend, corsOrigin, SECURITY_HEADERS, upstreamTimeoutMs, verbose);
|
|
3118
3126
|
return;
|
|
3119
3127
|
}
|
|
3128
|
+
// dario#1236 — the Claude adapter claims by default, so a model NO
|
|
3129
|
+
// provider lists used to reach api.anthropic.com verbatim and come
|
|
3130
|
+
// back as Anthropic's 404 `model: gpt-5.6-sol`, attributed to
|
|
3131
|
+
// whichever seat sent it, after spending a pool request. Ask the
|
|
3132
|
+
// positive question the failover chain already asks (claude-model.ts)
|
|
3133
|
+
// and refuse locally instead.
|
|
3134
|
+
//
|
|
3135
|
+
// Deliberately NOT refused: a `claude-*` name the catalog does not
|
|
3136
|
+
// know (the live catalog can lag a model by a fetch, and on a cold
|
|
3137
|
+
// start it is the baked list — Anthropic's own 404 stays
|
|
3138
|
+
// authoritative for those); a request under a server-wide
|
|
3139
|
+
// --model/--fast-model override, which replaces the name; upstream
|
|
3140
|
+
// API-key mode, which has no pool to protect and may reach models the
|
|
3141
|
+
// OAuth catalog never lists; and an OpenAI-shape name the legacy
|
|
3142
|
+
// OPENAI_MODEL_MAP translates to a Claude model.
|
|
3143
|
+
if (rawModel && decision.provider === 'claude' && !upstreamApiKey && !modelOverride && !fastModelOverride
|
|
3144
|
+
&& !(isOpenAI && OPENAI_MODEL_MAP[rawModel])
|
|
3145
|
+
&& !/^claude-/i.test(rawModel.trim())
|
|
3146
|
+
&& !isClaudeServableModel(rawModel, getCachedBases(), (m) => resolveClaudeAlias(applyModelAlias(m, modelAliases) ?? m))) {
|
|
3147
|
+
const consulted = [
|
|
3148
|
+
codexCreds || codexUnavailable
|
|
3149
|
+
? `codex account ${(codexCreds ?? codexUnavailable).alias} (${codexModels.length} listed slug${codexModels.length === 1 ? '' : 's'})`
|
|
3150
|
+
: 'no codex account',
|
|
3151
|
+
openaiBackend ? `openai backend ${openaiBackend.name}${isOpenAI ? '' : ' (OpenAI path only)'}` : 'no openai backend',
|
|
3152
|
+
`claude catalog (${getCachedBases().length} bases)`,
|
|
3153
|
+
].join(', ');
|
|
3154
|
+
if (verbose)
|
|
3155
|
+
console.log(`[dario] #${requestCount} ${req.method} ${urlPath} (model: ${rawModel}) no provider lists ${rawModel}; refusing — consulted ${consulted}`);
|
|
3156
|
+
requestCount++;
|
|
3157
|
+
const message = `no provider lists model "${rawModel}" (consulted ${consulted}); refused locally rather than forwarded to the Claude pool, which would 404 it after spending a request`;
|
|
3158
|
+
res.writeHead(400, { ...JSON_HEADERS, 'x-dario-upstream-rejection': MODEL_UNROUTABLE });
|
|
3159
|
+
res.end(JSON.stringify(isOpenAI
|
|
3160
|
+
? { error: { message, type: 'invalid_request_error', param: 'model', code: 'model_not_found' } }
|
|
3161
|
+
: { type: 'error', error: { type: 'invalid_request_error', message } }));
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3120
3164
|
}
|
|
3121
3165
|
catch { /* not JSON — fall through to existing path */ }
|
|
3122
3166
|
}
|
|
@@ -3,6 +3,14 @@ export interface UpstreamRejection {
|
|
|
3
3
|
class: UpstreamRejectionClass;
|
|
4
4
|
marker: 'billing_required' | 'rate_limited' | 'credential_rejected' | 'upstream_rejected';
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* `x-dario-upstream-rejection` value for a request dario refused LOCALLY
|
|
8
|
+
* because no provider lists its model (dario#1236). Not a classification of
|
|
9
|
+
* an upstream answer — there was no upstream request, which is the point — but
|
|
10
|
+
* it rides the same header so a fleet log or tracker reads one field for
|
|
11
|
+
* every "this request was not served" verdict.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MODEL_UNROUTABLE = "model_unroutable";
|
|
6
14
|
/** Classify subscription entitlement failures separately from temporary quota exhaustion. */
|
|
7
15
|
export declare function classifyUpstreamRejection(status: number, body: string): UpstreamRejection;
|
|
8
16
|
/** Operator action paired with the failure class. Never suggest credential churn for billing. */
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `x-dario-upstream-rejection` value for a request dario refused LOCALLY
|
|
3
|
+
* because no provider lists its model (dario#1236). Not a classification of
|
|
4
|
+
* an upstream answer — there was no upstream request, which is the point — but
|
|
5
|
+
* it rides the same header so a fleet log or tracker reads one field for
|
|
6
|
+
* every "this request was not served" verdict.
|
|
7
|
+
*/
|
|
8
|
+
export const MODEL_UNROUTABLE = 'model_unroutable';
|
|
1
9
|
/** Classify subscription entitlement failures separately from temporary quota exhaustion. */
|
|
2
10
|
export function classifyUpstreamRejection(status, body) {
|
|
3
11
|
const normalized = body.toLowerCase();
|
|
@@ -24,8 +24,8 @@ services:
|
|
|
24
24
|
expose: ["3456"]
|
|
25
25
|
|
|
26
26
|
cordon:
|
|
27
|
-
#
|
|
28
|
-
|
|
27
|
+
# multi-arch, Sigstore-attested: gh attestation verify oci://ghcr.io/askalf/cordon:v0.2.0 --repo askalf/cordon
|
|
28
|
+
image: ghcr.io/askalf/cordon:v0.2.0
|
|
29
29
|
environment:
|
|
30
30
|
ANTHROPIC_BASE: http://dario:3456
|
|
31
31
|
OPENAI_BASE: http://dario:3456
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.32",
|
|
4
4
|
"description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
"check:overage": "node scripts/check-overage-live.mjs",
|
|
41
41
|
"cch:calibrate": "node scripts/cch-calibrate.mjs",
|
|
42
42
|
"fix:pkg": "node -e \"const fs=require('fs');fs.writeFileSync('package.json',JSON.stringify(JSON.parse(fs.readFileSync('package.json','utf-8')),null,2)+'\\n')\"",
|
|
43
|
-
"audit:tui": "node tools/tui-audit/audit.mjs"
|
|
43
|
+
"audit:tui": "node tools/tui-audit/audit.mjs",
|
|
44
|
+
"readme:assets": "node scripts/readme/hero.mjs && node scripts/readme/terminal.mjs && node scripts/readme/tui.mjs",
|
|
45
|
+
"check:readme": "node scripts/check-readme-line-count.mjs && node scripts/check-readme-links.mjs"
|
|
44
46
|
},
|
|
45
47
|
"keywords": [
|
|
46
48
|
"llm",
|