@askalf/dario 6.10.4 → 6.10.6
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 +31 -513
- package/dist/pool.js +9 -1
- package/docs/account-risk.md +21 -0
- package/docs/cli-and-endpoints.md +46 -0
- package/docs/faq.md +1 -1
- package/docs/guardrails.md +19 -0
- package/docs/integrations/cordon.md +2 -2
- package/docs/pool-keys-and-analytics.md +66 -0
- package/docs/routing.md +21 -0
- package/docs/tools.md +181 -0
- package/docs/tracking-claude-code.md +35 -0
- package/docs/two-plans.md +88 -0
- package/docs/who-its-for.md +20 -0
- package/docs/why-now-2026-06.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<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>
|
|
15
15
|
<a href="https://www.bestpractices.dev/projects/13638"><img src="https://www.bestpractices.dev/projects/13638/badge" alt="OpenSSF Best Practices"></a>
|
|
16
16
|
<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>
|
|
17
|
+
<a href="https://github.com/askalf/dario/stargazers"><img src="https://img.shields.io/github/stars/askalf/dario?color=6f42c1&logo=github" alt="GitHub stars"></a>
|
|
17
18
|
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/dm/@askalf/dario?color=6f42c1" alt="Downloads"></a>
|
|
18
19
|
<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>
|
|
19
20
|
</p>
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
|
|
30
31
|
<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 · ~39k lines you can read in a weekend · independent, unofficial, third-party (<a href="DISCLAIMER.md">DISCLAIMER.md</a>)</sub>
|
|
31
32
|
|
|
32
|
-
<sub><a href="#start-in-60-seconds">Start</a> · <a href="#
|
|
33
|
+
<sub><a href="#start-in-60-seconds">Start</a> · <a href="#why-people-pick-it">Why dario</a> · <a href="#what-it-does-with-a-request">Routing</a> · <a href="#will-my-account-get-suspended">Risk</a> · <a href="#reference">Reference</a> · <a href="#trust--transparency">Trust</a> · <a href="docs/returning.md">Coming back after a while?</a></sub>
|
|
33
34
|
|
|
34
35
|
</div>
|
|
35
36
|
|
|
@@ -42,18 +43,9 @@ You're already paying $20, $100 or $200 a month for Claude,[^plans] or for a Cha
|
|
|
42
43
|
## Start in 60 seconds
|
|
43
44
|
|
|
44
45
|
```bash
|
|
45
|
-
# 1. Install
|
|
46
46
|
npm install -g @askalf/dario
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
dario login # or `dario login --manual` for SSH / headless
|
|
50
|
-
|
|
51
|
-
# 3. Start the local proxy
|
|
52
|
-
dario proxy # separate terminal or background
|
|
53
|
-
|
|
54
|
-
# 4. Point any Anthropic-compatible tool at it
|
|
55
|
-
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
56
|
-
export ANTHROPIC_API_KEY=dario
|
|
47
|
+
dario login # your Claude plan (Pro, Max 5x or Max 20x); `dario login --manual` for SSH / headless
|
|
48
|
+
dario proxy # leave it running, then point any tool at http://localhost:3456 with key `dario`
|
|
57
49
|
```
|
|
58
50
|
|
|
59
51
|
<picture>
|
|
@@ -61,189 +53,23 @@ export ANTHROPIC_API_KEY=dario
|
|
|
61
53
|
<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%">
|
|
62
54
|
</picture>
|
|
63
55
|
|
|
64
|
-
|
|
56
|
+
Anthropic-shaped tools: `export ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario`. OpenAI-shaped tools use `OPENAI_BASE_URL=http://localhost:3456/v1` instead, same key. Every tool that honors those env vars now runs on your subscription.
|
|
65
57
|
|
|
66
|
-
**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
|
|
58
|
+
**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 setup →](docs/tools.md)**
|
|
67
59
|
|
|
68
60
|
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.
|
|
69
61
|
|
|
70
|
-
##
|
|
71
|
-
|
|
72
|
-
Two base URLs, one key. Anthropic-shaped clients talk to `http://localhost:3456`; OpenAI-shaped clients — chat/completions and, since 6.3, the Responses API — talk to `http://localhost:3456/v1`. The key is `dario` (any value works until you set `DARIO_API_KEY`, which then has to match).
|
|
73
|
-
|
|
74
|
-
<details>
|
|
75
|
-
<summary><strong>Codex CLI</strong> — OpenAI's agent, on your Claude plan</summary>
|
|
76
|
-
|
|
77
|
-
```toml
|
|
78
|
-
# ~/.codex/config.toml
|
|
79
|
-
model = "claude-opus-5"
|
|
80
|
-
model_provider = "dario"
|
|
81
|
-
|
|
82
|
-
[model_providers.dario]
|
|
83
|
-
name = "dario"
|
|
84
|
-
base_url = "http://127.0.0.1:3456/v1"
|
|
85
|
-
env_key = "DARIO_API_KEY"
|
|
86
|
-
wire_api = "responses"
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Codex CLI 0.154 dropped the chat wire for custom providers, so dario speaks the Responses API: the request is translated once at the front door and served like any Claude request — pool, failover, mid-stream continuation, the 17 KB Codex system prompt cached on the Claude side. The full agent loop runs: Claude calls `exec_command`, Codex executes it, the result goes back, Claude answers from it. Add a ChatGPT account too and `-m gpt-5.6-sol` on the same block goes to that plan through the same proxy. [Walkthrough and what is dropped](./docs/integrations/codex-cli.md).
|
|
90
|
-
</details>
|
|
91
|
-
|
|
92
|
-
<details>
|
|
93
|
-
<summary><strong>Claude Code</strong> — forwarded verbatim</summary>
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
97
|
-
export ANTHROPIC_API_KEY=dario
|
|
98
|
-
claude
|
|
99
|
-
```
|
|
62
|
+
## Why people pick it
|
|
100
63
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
dario proxy # terminal 1
|
|
111
|
-
cloudflared tunnel --url http://localhost:3456 # terminal 2 → https://<random>.trycloudflare.com
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
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).
|
|
115
|
-
</details>
|
|
116
|
-
|
|
117
|
-
<details>
|
|
118
|
-
<summary><strong>Cline · Roo Code · Kilo Code</strong> — API provider "Anthropic"</summary>
|
|
119
|
-
|
|
120
|
-
Provider **Anthropic** · API key `dario` · Anthropic Base URL `http://localhost:3456` · model `claude-sonnet-5` / `claude-opus-5` / `claude-haiku-4-5`.
|
|
121
|
-
|
|
122
|
-
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).
|
|
123
|
-
</details>
|
|
124
|
-
|
|
125
|
-
<details>
|
|
126
|
-
<summary><strong>Aider</strong></summary>
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
130
|
-
export ANTHROPIC_API_KEY=dario
|
|
131
|
-
aider --model sonnet # or opus, haiku, any claude-* id
|
|
132
|
-
```
|
|
133
|
-
</details>
|
|
134
|
-
|
|
135
|
-
<details>
|
|
136
|
-
<summary><strong>Continue.dev</strong></summary>
|
|
137
|
-
|
|
138
|
-
```yaml
|
|
139
|
-
# ~/.continue/config.yaml
|
|
140
|
-
models:
|
|
141
|
-
- name: Claude Sonnet (dario)
|
|
142
|
-
provider: anthropic
|
|
143
|
-
model: claude-sonnet-5
|
|
144
|
-
apiBase: http://localhost:3456
|
|
145
|
-
apiKey: dario
|
|
146
|
-
```
|
|
147
|
-
</details>
|
|
148
|
-
|
|
149
|
-
<details>
|
|
150
|
-
<summary><strong>Zed</strong></summary>
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{ "language_models": { "anthropic": { "api_url": "http://localhost:3456", "version": "2023-06-01" } } }
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Set `ANTHROPIC_API_KEY=dario` in the environment Zed launches from; the model picker then lists Claude models routed through your plan.
|
|
157
|
-
</details>
|
|
158
|
-
|
|
159
|
-
<details>
|
|
160
|
-
<summary><strong>OpenHands</strong></summary>
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
export LLM_BASE_URL=http://localhost:3456
|
|
164
|
-
export LLM_API_KEY=dario
|
|
165
|
-
export LLM_MODEL=anthropic/claude-sonnet-5
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
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).
|
|
169
|
-
</details>
|
|
170
|
-
|
|
171
|
-
<details>
|
|
172
|
-
<summary><strong>OpenClaw</strong></summary>
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
176
|
-
export ANTHROPIC_API_KEY=dario
|
|
177
|
-
openclaw "task description"
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
OpenClaw's `exec` / `process` / `web_search` / `web_fetch` / `browser` tools are translated to Claude Code's set without a flag; a tool outside the map (`message`, for one) rides a fallback slot instead. Newer OpenClaw reads `auth-profiles.json` before env vars, so a stale key there wins — the [walkthrough](./docs/integrations/openclaw-walkthrough.md) covers it.
|
|
181
|
-
</details>
|
|
182
|
-
|
|
183
|
-
<details>
|
|
184
|
-
<summary><strong>Codex CLI · OpenAI SDK · any OpenAI-compatible tool</strong></summary>
|
|
185
|
-
|
|
186
|
-
```bash
|
|
187
|
-
export OPENAI_BASE_URL=http://localhost:3456/v1
|
|
188
|
-
export OPENAI_API_KEY=dario
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
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` or anything else your API-key backend lists and it goes there byte-for-byte. Names that don't look like OpenAI's (`llama-3.3-70b`, `qwen-coder`) need the provider prefix below; dario refuses them rather than guess:
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
dario backend add openai --key=sk-proj-...
|
|
195
|
-
dario backend add groq --key=gsk_... --base-url=https://api.groq.com/openai/v1
|
|
196
|
-
dario backend add openrouter --key=sk-or-... --base-url=https://openrouter.ai/api/v1
|
|
197
|
-
dario backend add local --key=anything --base-url=http://127.0.0.1:11434/v1
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Force a backend with a prefix: `openai:gpt-4o`, `claude:opus`, `groq:llama-3.3-70b`, `local:qwen-coder`.
|
|
201
|
-
|
|
202
|
-
One holdover for old configs: six legacy OpenAI names (`gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.3`, `gpt-4`, `gpt-3.5-turbo`) sent to `/v1/chat/completions` are translated to Claude models when no other provider claims them.
|
|
203
|
-
</details>
|
|
204
|
-
|
|
205
|
-
<details>
|
|
206
|
-
<summary><strong>Claude Agent SDK · Anthropic SDK</strong> (TypeScript, Python)</summary>
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
210
|
-
const client = new Anthropic({ baseURL: "http://localhost:3456", apiKey: "dario" });
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
```python
|
|
214
|
-
import anthropic
|
|
215
|
-
client = anthropic.Anthropic(base_url="http://localhost:3456", api_key="dario")
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
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).
|
|
219
|
-
</details>
|
|
220
|
-
|
|
221
|
-
<details>
|
|
222
|
-
<summary><strong>curl</strong></summary>
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
curl http://localhost:3456/v1/messages -H "content-type: application/json" \
|
|
226
|
-
-d '{"model":"claude-sonnet-5","max_tokens":256,"messages":[{"role":"user","content":"Hello!"}]}'
|
|
227
|
-
|
|
228
|
-
curl http://localhost:3456/v1/chat/completions -H "content-type: application/json" \
|
|
229
|
-
-d '{"model":"gpt-5.5","messages":[{"role":"user","content":"Hello!"}]}'
|
|
230
|
-
```
|
|
231
|
-
</details>
|
|
232
|
-
|
|
233
|
-
<details>
|
|
234
|
-
<summary><strong>Docker</strong> · Kubernetes · a Pi in a closet</summary>
|
|
235
|
-
|
|
236
|
-
```bash
|
|
237
|
-
docker volume create dario-config
|
|
238
|
-
docker run --rm -it -v dario-config:/home/dario/.dario ghcr.io/askalf/dario:latest login --manual
|
|
239
|
-
docker run -d --name dario -p 3456:3456 -v dario-config:/home/dario/.dario \
|
|
240
|
-
-e DARIO_API_KEY="$(openssl rand -hex 32)" ghcr.io/askalf/dario:latest
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
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).
|
|
244
|
-
</details>
|
|
245
|
-
|
|
246
|
-
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.
|
|
64
|
+
- **Every tool, one URL.** No per-tool keys and no second bill: anything with a base-URL setting runs on the plan you already pay for. [Per-tool setup](docs/tools.md)
|
|
65
|
+
- **Either plan, either wire shape.** Your ChatGPT plan behind Claude Code and the Anthropic SDKs; Codex CLI on your Claude plan. [Two plans](docs/two-plans.md)
|
|
66
|
+
- **Failover that finishes the answer.** A 429 on one plan is re-served by the other, and a stream that dies mid-answer is completed on the same model first. [Failover](docs/two-plans.md#failover-between-subscriptions)
|
|
67
|
+
- **Many seats, one endpoint.** Per-model headroom routing, session-sticky prompt cache, in-flight 429 failover across every seat you hold. [The pool](docs/pool-keys-and-analytics.md)
|
|
68
|
+
- **One key per developer.** Named keys with a preferred seat, a model allowlist and a daily budget. [Keys](docs/pool-keys-and-analytics.md#one-key-per-developer)
|
|
69
|
+
- **Knows what it saved you.** A ledger prices every request at API rates; see it in the TUI, `/analytics`, Prometheus `/metrics` and a per-request timing split. [Analytics](docs/pool-keys-and-analytics.md#what-it-would-have-cost)
|
|
70
|
+
- **Tracks Claude Code on its own.** Eleven unattended watchers catch wire-shape drift and ship the fix, usually the same day. [Drift](docs/tracking-claude-code.md)
|
|
71
|
+
- **Stops before it costs you.** The overage guard halts the proxy the moment a response bills outside your subscription. [Guardrails](docs/guardrails.md)
|
|
72
|
+
- **Your traffic as the benchmark.** Shadow-compare any request against another model and read the results with `dario compare`. [Shadow compare](docs/two-plans.md#shadow-compare)
|
|
247
73
|
|
|
248
74
|
## What it does with a request
|
|
249
75
|
|
|
@@ -261,203 +87,25 @@ You point every tool at one URL. dario reads each request, decides which plan or
|
|
|
261
87
|
|
|
262
88
|
The tool doesn't know. The backend doesn't know. dario is the seam.
|
|
263
89
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
## Two plans, one endpoint
|
|
267
|
-
|
|
268
|
-
### Your ChatGPT plan, on both endpoints
|
|
269
|
-
|
|
270
|
-
A ChatGPT Plus or Pro plan is served on **all three** of dario's endpoints: any client that speaks `/v1/chat/completions` or `/v1/responses` can use it (Codex CLI, the OpenAI SDKs, the Agents SDK, 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 — and the symmetry holds: Codex CLI runs on a Claude plan the same way.
|
|
271
|
-
|
|
272
|
-
An Anthropic-shape client that declares Anthropic's hosted `web_search_20260209` tool gets **real web search on the ChatGPT plan** (since 6.4): the plan's own search runs, and the client sees Anthropic's own blocks — `server_tool_use` with the query, `web_search_tool_result` listing the pages searched, the answer with `web_search_result_location` citations. `allowed_domains` and `user_location` carry over, and so does forcing the search with `tool_choice`; `blocked_domains` and `max_uses` do not.
|
|
273
|
-
|
|
274
|
-
```bash
|
|
275
|
-
dario add altman # prints an authorize URL; paste the redirect URL back
|
|
276
|
-
dario codex list
|
|
277
|
-
dario codex remove altman
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
`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.
|
|
281
|
-
|
|
282
|
-
```bash
|
|
283
|
-
curl localhost:3456/v1/models | jq -r '.data[].id'
|
|
284
|
-
curl localhost:3456/v1/chat/completions -H 'content-type: application/json' \
|
|
285
|
-
-d '{"model":"gpt-5.5","messages":[{"role":"user","content":"hi"}]}'
|
|
286
|
-
|
|
287
|
-
# same subscription, Anthropic wire shape — this is what Claude Code speaks
|
|
288
|
-
curl localhost:3456/v1/messages -H 'content-type: application/json' \
|
|
289
|
-
-d '{"model":"gpt-5.5","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}'
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
**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.
|
|
293
|
-
|
|
294
|
-
Streaming, tool calls and tool-result round trips work on both shapes, and chat-shape `image_url` parts are carried as Responses `input_image` parts with `detail` preserved: 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.
|
|
295
|
-
|
|
296
|
-
**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.
|
|
297
|
-
|
|
298
|
-
### Failover between subscriptions
|
|
299
|
-
|
|
300
|
-
Two consumer plans, no API keys, and neither one able to take you down on its own.
|
|
301
|
-
|
|
302
|
-
```bash
|
|
303
|
-
dario proxy --pool-fallback=gpt-5.6-sol,claude-sonnet-5
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
That is a **chain**, read left to right; each provider takes the first entry it can actually serve. Prefix every entry with a tier and the same flag becomes a **tier map**: `--pool-fallback=haiku:gpt-5.6-luna,sonnet:gpt-5.6-terra,opus:gpt-5.6-sol` picks one rung per request from the tier of the model asked for (`default:` catches the rest, otherwise the first rung), so heartbeat work on Haiku never overflows onto a flagship. That shipped in 6.0.17, the same day a Haiku-tier fleet spent 79% of a weekly allowance doing exactly that. 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.
|
|
307
|
-
|
|
308
|
-
<img src=".github/readme/failover.jpg" alt="A tool sends a request to dario. The Claude plan answers 429, so dario re-serves the same request from the ChatGPT plan, which answers 200, and the response returns to the tool carrying the x-dario-pool-fallback header." width="100%">
|
|
309
|
-
|
|
310
|
-
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. A 429 also cools that provider for a bounded interval, its `retry-after` if it sent one and 60 s otherwise, never longer than 15 min, and an entry that already declined is not asked again within the same request. When every entry is cooling, the request ends on one honest `429` with a `retry-after` instead of a retry storm. 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.
|
|
311
|
-
|
|
312
|
-
The chain also covers a stream that dies **mid-answer**. Until 6.1 that was the one failure nothing could catch: bytes were on the wire, so the socket reset, the in-band `overloaded_error`, the codex `response.failed` all ended the stream where they happened, with no `message_stop`, and the client threw away every word it already had. Now dario finishes the same stream — on the same model first, a fresh request through its own front door; on the other subscription when that delivers nothing or dies too. The resume picks up inside the still-open content block, a comment marks each seam (`: dario continuation claude-opus-5 (same model) after 1240 chars`), and the client sees one message. The model is asked to repeat the last few words verbatim and dario trims the repeat, so the join is rendered by a model and cut by a parser, never guessed. Text only — a cut inside a tool call ends as it always did. On by default, `--no-midstream-continue` turns it off; without a chain the second hop is simply not there. [How it works](docs/midstream-continuation.md).
|
|
313
|
-
|
|
314
|
-
`dario doctor` tells you which of these you are actually in:
|
|
315
|
-
|
|
316
|
-
```
|
|
317
|
-
[ OK ] Failover symmetric: gpt-5.6-sol → claude-sonnet-5, across 1 Codex account
|
|
318
|
-
[WARN] Failover armed (gpt-5.6-sol) but INERT — no Codex account and no backend
|
|
319
|
-
to fall back to. Add one: `dario add altman`
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
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.
|
|
90
|
+
The full Claude lineup, shortcuts, the live model catalog and when a name is refused: [routing.md](docs/routing.md).
|
|
323
91
|
|
|
324
|
-
|
|
325
|
-
> 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)
|
|
326
|
-
|
|
327
|
-
### Shadow compare
|
|
328
|
-
|
|
329
|
-
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.
|
|
330
|
-
|
|
331
|
-
```bash
|
|
332
|
-
curl localhost:3456/v1/messages \
|
|
333
|
-
-H 'content-type: application/json' \
|
|
334
|
-
-H 'x-dario-compare: gpt-5.6-sol' \
|
|
335
|
-
-d '{"model":"claude-opus-5","max_tokens":1024,"messages":[…]}'
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
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.
|
|
339
|
-
|
|
340
|
-
Read them with **`dario compare`**: calls, success rate, median latency, how often each model's answer parsed as JSON, average length — and, above all, why any comparison was skipped. That last column is the point. The records had no reader until 6.7, and a week-long comparison on a box collected 919 of them without a single usable result, every one carrying its own reason inside the file. A log nobody can read is a log nobody reads.
|
|
341
|
-
|
|
342
|
-
```
|
|
343
|
-
Records: 1,515 (2026-09-06 → 2026-09-13)
|
|
344
|
-
Compared: 1,515 with both sides
|
|
345
|
-
|
|
346
|
-
model calls 200s median valid JSON avg chars
|
|
347
|
-
gpt-5.6-luna 1515 100% 2736ms 98% 410
|
|
348
|
-
gpt-5.5 1515 99% 2729ms 100% 523
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
## Many seats, one endpoint
|
|
352
|
-
|
|
353
|
-
**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.
|
|
354
|
-
|
|
355
|
-
```bash
|
|
356
|
-
dario accounts add work
|
|
357
|
-
dario accounts add personal
|
|
358
|
-
dario proxy
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
<picture>
|
|
362
|
-
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/tui-accounts-dark.svg">
|
|
363
|
-
<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%">
|
|
364
|
-
</picture>
|
|
365
|
-
|
|
366
|
-
Three things it does that a round-robin doesn't:
|
|
367
|
-
|
|
368
|
-
- **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.
|
|
369
|
-
- **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.
|
|
370
|
-
- **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. A seat parked on a 429 rejoins the pool on its own when its window resets.
|
|
371
|
-
|
|
372
|
-
<img src=".github/readme/pool.jpg" alt="Three pooled seats, work, personal and side, each with a headroom bar. dario routes the request to the seat with the most headroom." width="100%">
|
|
373
|
-
|
|
374
|
-
`--pool-strategy=fill-first` concentrates new conversations on one seat until it drains, for primary/backup setups. `--pool-headroom-floor=5%` (env `DARIO_POOL_HEADROOM_FLOOR`, config `pool.headroomFloor`; default 2%) moves the line at which a seat counts as drained: a sticky session rebinds off it and new conversations skip it once its headroom is at or below the floor, so a seat that answers with API errors in its last percent is left alone before the 429, not at it. 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>` (admin API required: `DARIO_ADMIN=1` and a `DARIO_ADMIN_TOKEN`). 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).
|
|
375
|
-
|
|
376
|
-
### One key per developer
|
|
377
|
-
|
|
378
|
-
A shared dario serves several people through one `DARIO_API_KEY`, and nothing says whose traffic is whose except a header any client can set. Since 6.8 a **named key** ties attribution to the credential:
|
|
379
|
-
|
|
380
|
-
```bash
|
|
381
|
-
dario keys create alice
|
|
382
|
-
dario keys create bob --seat=bobs-max --models=claude-sonnet-5,claude-haiku*
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
Since 6.10 a key can carry a **daily budget** — `--budget=$5/day`, `--budget-tokens=2M/day` — read from the ledger, refused with a `429` and a `retry-after` at UTC midnight, with the headroom on every response as `x-dario-budget-*` headers ([details](./docs/keys.md#budgets)). The secret is printed once and only its hash is kept, in `~/.dario/keys.json`. The request authenticated with alice's key *is* alice's in `/analytics`, in the ledger (`dario usage --by-key`) and on every log line; a key can prefer one pool seat (taken while it has headroom, normal routing otherwise, so a developer's conversations ride their own subscription) and can be held to a model allowlist (`403` before anything goes upstream, in either wire shape). The running proxy picks up a created, rotated or revoked key on its next request; the root `DARIO_API_KEY` keeps working beside them; `/admin/keys` does the same over HTTP. Details: [keys.md](./docs/keys.md).
|
|
386
|
-
|
|
387
|
-
### Watch it happen
|
|
388
|
-
|
|
389
|
-
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.
|
|
390
|
-
|
|
391
|
-
<picture>
|
|
392
|
-
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/tui-analytics-dark.svg">
|
|
393
|
-
<img alt="The dario TUI Analytics tab: requests per minute, tokens in and out, thinking tokens, average latency, subscription percentage, the lifetime API-equivalent spend from the ledger, 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%">
|
|
394
|
-
</picture>
|
|
395
|
-
|
|
396
|
-
<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>
|
|
397
|
-
|
|
398
|
-
### What it would have cost
|
|
399
|
-
|
|
400
|
-
The rolling window forgets on every restart; the **ledger** does not. Since 6.6 dario keeps one small row per day, per model, per billing bucket in `~/.dario/ledger.json` — request counts and the four token buckets, never a price — and prices them at read time from the published API rate cards (Anthropic's, and OpenAI's for the ChatGPT leg), so a pricing correction reprices history instead of freezing the old number in. `dario usage` opens with it, `/analytics` carries it as `lifetime`, the TUI shows it as **API-equivalent**, and it reads from the file when the proxy is down:
|
|
401
|
-
|
|
402
|
-
```
|
|
403
|
-
API-equivalent spend (since 2026-09-11, 3 days, 1,515 requests):
|
|
404
|
-
$413 would have been billed on the metered API — covered by subscriptions
|
|
405
|
-
Claude $388 1,204 reqs (Opus 5 $301 · Sonnet 5 $86.68)
|
|
406
|
-
ChatGPT $24.75 311 reqs (gpt-5.6-terra $24.75)
|
|
407
|
-
Today $48.20 · Last 7d $413 · Last 30d $413
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
Only served requests count. Traffic that was metered anyway — an API key upstream, or Anthropic's paid `extra_usage` overage — is kept in its own column and reported as spent, not saved. `dario usage --card` writes the headline as a 640×320 SVG you can drop in a README or a post, and `--donut` writes the same number as three rings — by model, by key, subscription vs metered; `--no-ledger` / `DARIO_LEDGER=0` turns the file off, `DARIO_LEDGER_PATH` moves it, and `GET /analytics/ledger` is the per-day table behind the number. Details: [api-equivalent-spend.md](./docs/api-equivalent-spend.md).
|
|
411
|
-
|
|
412
|
-
**Scrape it, or open it.** `GET /metrics` is the same state as Prometheus text exposition — window, seats, models, consumers, queue, latency quantiles, burn rates, ledger — so Grafana reads dario like anything else. Since 6.9 every request also carries its **timing split**: queue wait, governor sleep, the provider's time to first byte, the provider's total, and what is left — dario's own overhead — as `x-dario-*-ms` response headers on the request itself, as `window.timing` on `/analytics`, and as five more `/metrics` families, so "was that Anthropic or the proxy?" is a `curl -i` away ([details](./docs/analytics.md#the-timing-split)). `GET /analytics/ui` is a self-contained dashboard page with the headline, the rings and the tables, refreshing every minute. Both sit behind the same gate as `/analytics`; `--analytics-token` (env `DARIO_ANALYTICS_TOKEN`) adds a **read-only** credential accepted on those paths and nowhere else, so a scraper or a browser can hold the numbers without holding request rights. Families and the gate: [analytics.md](./docs/analytics.md).
|
|
413
|
-
|
|
414
|
-
## It tracks a moving target
|
|
415
|
-
|
|
416
|
-
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).
|
|
417
|
-
|
|
418
|
-
<img src=".github/readme/drift.jpg" alt="The installed claude binary feeds its request shape into dario. A timeline of Claude Code releases ends in a node flagged as drift." width="100%">
|
|
419
|
-
|
|
420
|
-
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:
|
|
421
|
-
|
|
422
|
-
| Watcher | Catches | Live |
|
|
423
|
-
|---|---|---|
|
|
424
|
-
| [`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. |  |
|
|
425
|
-
| [`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. |  |
|
|
426
|
-
| [`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. |  |
|
|
427
|
-
| [`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. |  |
|
|
428
|
-
| [`sdk-drift-watch`](./.github/workflows/sdk-drift-watch.yml) | Agent SDK / Stainless pins drifting from what the template assumes. |  |
|
|
429
|
-
| [`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. |  |
|
|
430
|
-
| [`codex-drift-watch`](./.github/workflows/codex-drift-watch.yml) | The ChatGPT backend's model list or wire contract moving under the translator. |  |
|
|
431
|
-
| [`cc-oauth-health`](./.github/workflows/cc-oauth-health.yml) | The maintainer's own production proxy going unhealthy on any axis. |  |
|
|
432
|
-
| [`dario-doctor-watch`](./.github/workflows/dario-doctor-watch.yml) | Runtime drift only a live `dario doctor --obedience` surfaces: identity, obedience, usage buckets. |  |
|
|
433
|
-
| [`deployed-version-watch`](./.github/workflows/deployed-version-watch.yml) | Publishing is not deploying: is what's running what was last released? |  |
|
|
434
|
-
| [`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. |  |
|
|
435
|
-
|
|
436
|
-
Guarded at PR time by [`live-test`](./.github/workflows/live-test.yml), a required check that runs the full suite against a live proxy on a self-hosted runner, plus [`compat-test-self-hosted`](./.github/workflows/compat-test-self-hosted.yml), which replays the compat suite through a passthrough proxy on wire-shape changes. A few changes the watchers caught and shipped fixes for, same day:
|
|
437
|
-
|
|
438
|
-
| Change (no subscriber-facing note) | Effect | dario shipped |
|
|
439
|
-
|---|---|---|
|
|
440
|
-
| `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 |
|
|
441
|
-
| `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) |
|
|
442
|
-
| Per-model `anthropic-beta` sets | Proxies sending one set diverge for non-Opus models | [v4.8.53](https://github.com/askalf/dario/pull/478) |
|
|
443
|
-
|
|
444
|
-
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).
|
|
445
|
-
|
|
446
|
-
## Guardrails
|
|
447
|
-
|
|
448
|
-
### Overage guard
|
|
449
|
-
|
|
450
|
-
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.
|
|
451
|
-
|
|
452
|
-
<img src=".github/readme/overage.jpg" alt="Requests from five tools are stopped short of dario, which is ringed in red and labeled halted, after the Claude plan returned a response billed as overage." width="100%">
|
|
92
|
+
## Will my account get suspended?
|
|
453
93
|
|
|
454
|
-
|
|
94
|
+
The most common question about dario, and it deserves a straight answer: **I can't promise you won't be actioned, and I'd be skeptical of anyone who does.** Only Anthropic decides how it enforces its terms. What I can do is lay out exactly how dario works, so you can weigh the risk yourself instead of taking anyone's word for it.
|
|
455
95
|
|
|
456
|
-
|
|
96
|
+
dario runs entirely on your machine, authenticates as you with your own Claude login, sends requests in the shape the official client sends, and reports nothing anywhere. What it adds is letting tools *other than* Claude Code use that subscription, and whether that falls within your plan's terms is Anthropic's call. **[The full answer](docs/account-risk.md)**, including the [billing-split contingency](docs/guardrails.md#the-billing-split-a-contingency-dario-is-built-for).
|
|
457
97
|
|
|
458
|
-
|
|
98
|
+
## Reference
|
|
459
99
|
|
|
460
|
-
|
|
100
|
+
- **[Per-tool setup](docs/tools.md)**: Codex CLI, Claude Code, Cursor, Cline / Roo / Kilo, Aider, Continue, Zed, OpenHands, OpenClaw, the SDKs, curl, Docker
|
|
101
|
+
- **[Routing and the model lineup](docs/routing.md)**
|
|
102
|
+
- **[Two plans](docs/two-plans.md)**: your ChatGPT plan on both endpoints, failover between subscriptions, shadow compare
|
|
103
|
+
- **[Seats, keys and analytics](docs/pool-keys-and-analytics.md)**: the pool, named keys and budgets, the TUI, the ledger, `/metrics`
|
|
104
|
+
- **[How dario tracks Claude Code](docs/tracking-claude-code.md)**: the watchers, their live status, and what they caught
|
|
105
|
+
- **[Guardrails](docs/guardrails.md)**: the overage guard and the billing split
|
|
106
|
+
- **[Will my account get suspended?](docs/account-risk.md)** · **[Who it's for, and how it compares](docs/who-its-for.md)**
|
|
107
|
+
- **[Commands, endpoints and more knobs](docs/cli-and-endpoints.md)** · [every flag](docs/commands.md) · [env vars](docs/configuration.md) · [SDK examples](docs/usage.md)
|
|
108
|
+
- **[FAQ](docs/faq.md)** · [Docker](docs/docker.md) · [Coming back after a while?](docs/returning.md)
|
|
461
109
|
|
|
462
110
|
## Trust & transparency
|
|
463
111
|
|
|
@@ -484,136 +132,6 @@ cd $(npm root -g)/@askalf/dario && npm ls --production
|
|
|
484
132
|
|
|
485
133
|
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).
|
|
486
134
|
|
|
487
|
-
## Will my account get suspended?
|
|
488
|
-
|
|
489
|
-
The most common question about dario, and it deserves a straight answer: **I can't promise you won't be actioned, and I'd be skeptical of anyone who does.** Only Anthropic decides how it enforces its terms. What I can do is lay out exactly how dario works, so you can weigh the risk yourself instead of taking anyone's word for it.
|
|
490
|
-
|
|
491
|
-
**What dario does:**
|
|
492
|
-
|
|
493
|
-
- **Runs entirely on your machine.** Your subscription token never touches my servers or anyone else's; requests go straight from your computer to Anthropic.
|
|
494
|
-
- **Authenticates as you, with your own Claude login**, the same OAuth credential Claude Code itself uses. It impersonates nobody and shares nothing.
|
|
495
|
-
- **Doesn't modify your account, billing, or subscription settings.**
|
|
496
|
-
- **Sends requests in the shape the official client sends them**, rebuilt from your own installed binary, not spoofed from a hardcoded fake.
|
|
497
|
-
- **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.
|
|
498
|
-
|
|
499
|
-
**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. dario is a transparency tool, in that it documents request behavior Anthropic doesn't publish for subscribers, and it is also, plainly, routing subscription traffic that Anthropic's own tools bill differently. Both are true; decide with both in view.
|
|
500
|
-
|
|
501
|
-
**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.
|
|
502
|
-
|
|
503
|
-
Ongoing discussion, including other users' experiences: [#724](https://github.com/askalf/dario/discussions/724).
|
|
504
|
-
|
|
505
|
-
## Who it's for
|
|
506
|
-
|
|
507
|
-
**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.
|
|
508
|
-
|
|
509
|
-
**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).
|
|
510
|
-
|
|
511
|
-
**How it compares.** Only one of these routes a consumer subscription; the others route API keys, and that is the whole split.
|
|
512
|
-
|
|
513
|
-
| Tool | What it is | When it wins |
|
|
514
|
-
|---|---|---|
|
|
515
|
-
| **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 |
|
|
516
|
-
| **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 |
|
|
517
|
-
| **OpenRouter** | Hosted aggregator, one API key for hundreds of models | You want model breadth and are fine with pay-per-token |
|
|
518
|
-
| **Kong AI Gateway** | Enterprise on-prem API gateway for LLMs | You already run Kong and need AI traffic under the same governance |
|
|
519
|
-
|
|
520
|
-
Longer version, with specifics: [#68](https://github.com/askalf/dario/discussions/68).
|
|
521
|
-
|
|
522
|
-
## Commands
|
|
523
|
-
|
|
524
|
-
| Command | What it does |
|
|
525
|
-
|---|---|
|
|
526
|
-
| `dario` | The TUI: status, config editor, analytics, hits, accounts, backends |
|
|
527
|
-
| `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 |
|
|
528
|
-
| `dario proxy` | Start the local endpoint on `:3456` |
|
|
529
|
-
| `dario doctor [--usage] [--probe] [--obedience] [--auth-check] [--bun-bootstrap] [--json]` | One aggregated health report: runtime/TLS, template and drift, OAuth, pool, refresh-grant age, failover readiness, backends |
|
|
530
|
-
| `dario add altman` / `dario add amodei` | Attach a ChatGPT plan / a Claude account, by whose it is |
|
|
531
|
-
| `dario accounts list` / `add` / `remove` / `check <alias>` | Pool management; `check` sends one pinned request per model through the running proxy (admin API on) |
|
|
532
|
-
| `dario keys create <name>` / `list` / `revoke` / `rotate` | One credential per developer on a shared dario: attributed by key, optional preferred seat and model allowlist, hashes on disk ([keys.md](./docs/keys.md)) |
|
|
533
|
-
| `dario backend list` / `add` / `remove` | OpenAI-compatible API-key backends |
|
|
534
|
-
| `dario codex list` / `add` / `remove` | ChatGPT accounts (the long form of `dario add altman`) |
|
|
535
|
-
| `dario usage` · `dario compare` · `dario config` · `dario status` | Lifetime API-equivalent spend + burn rate for the last hour (`--card` writes the share card, `--by-key` splits it per key) · read the shadow-compare log · effective config, redacted · token health |
|
|
536
|
-
| `dario resume` · `dario refresh` · `dario logout` · `dario upgrade` | Clear an overage halt · force a token refresh · delete credentials · safe self-update |
|
|
537
|
-
| `dario mcp` · `dario subagent install` / `remove` / `status` | Reach dario from inside any MCP client, or from inside a Claude Code session, read-only |
|
|
538
|
-
|
|
539
|
-
| Endpoint | Description |
|
|
540
|
-
|---|---|
|
|
541
|
-
| `POST /v1/messages` · `POST /v1/chat/completions` | The two wire shapes, any plan behind either |
|
|
542
|
-
| `GET /v1/models` | Live model list: the Claude catalog plus whatever your ChatGPT plan lists |
|
|
543
|
-
| `GET /health` · `GET /livez` | Serviceability (503 when not) · liveness. `/health?probe=1` sends one real request |
|
|
544
|
-
| `GET /status` · `GET /accounts` · `GET /analytics` | OAuth detail · per-seat utilization and grant age · per-account / per-model stats and burn rate |
|
|
545
|
-
| `POST /v1/messages/count_tokens` · `POST /v1/complete` | Token counting and the legacy Text Completions shape |
|
|
546
|
-
| `GET /analytics/stream` · `GET /analytics/ledger` · `GET /codex` | Live analytics over SSE · the ledger's per-day table · ChatGPT-seat status, read without spending or exposing a token |
|
|
547
|
-
| `/admin/*` | Provisioning, `GET /admin/accounts`, `/admin/keys`, `POST /admin/resume`; only with `DARIO_ADMIN=1` ([admin API](./docs/admin-api.md)) |
|
|
548
|
-
|
|
549
|
-
Flags: [commands.md](./docs/commands.md), plus `dario --help` for the ones it doesn't list yet (`--effort`, `--max-tokens`, `--model-alias`, `--fast-model`, session rotation, concurrency caps, the pacing knobs behind `--stealth`) · env vars grouped by task, for Docker / k8s / systemd: [configuration.md](./docs/configuration.md) · SDK examples: [usage.md](./docs/usage.md).
|
|
550
|
-
|
|
551
|
-
<details>
|
|
552
|
-
<summary><strong>More knobs</strong> — stealth timing, system-prompt modes, client-shape overrides, VPN egress, MCP</summary>
|
|
553
|
-
|
|
554
|
-
- **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)
|
|
555
|
-
- **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)
|
|
556
|
-
- **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.
|
|
557
|
-
- **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)
|
|
558
|
-
- **Model aliases and caps.** `--model-alias=<name=target>` (repeatable) advertises a name of your choosing on `/v1/models` and routes it; `--effort=<low|medium|high|xhigh|ultracode|max|client>` and `--max-tokens=<N|client>` set, or pass through, per-request effort and output caps.
|
|
559
|
-
- **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)
|
|
560
|
-
- **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)
|
|
561
|
-
- **PII redaction in front of dario.** Pair it with [cordon](https://github.com/askalf/cordon): [integrations/cordon.md](./docs/integrations/cordon.md)
|
|
562
|
-
- **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)
|
|
563
|
-
</details>
|
|
564
|
-
|
|
565
|
-
## FAQ
|
|
566
|
-
|
|
567
|
-
<details>
|
|
568
|
-
<summary><strong>Does this violate Anthropic's terms?</strong></summary>
|
|
569
|
-
|
|
570
|
-
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)
|
|
571
|
-
</details>
|
|
572
|
-
|
|
573
|
-
<details>
|
|
574
|
-
<summary><strong>Do I need Claude Code installed?</strong></summary>
|
|
575
|
-
|
|
576
|
-
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.
|
|
577
|
-
</details>
|
|
578
|
-
|
|
579
|
-
<details>
|
|
580
|
-
<summary><strong>Do I need Bun?</strong></summary>
|
|
581
|
-
|
|
582
|
-
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.
|
|
583
|
-
</details>
|
|
584
|
-
|
|
585
|
-
<details>
|
|
586
|
-
<summary><strong>Can I use dario without a Claude subscription?</strong></summary>
|
|
587
|
-
|
|
588
|
-
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.
|
|
589
|
-
</details>
|
|
590
|
-
|
|
591
|
-
<details>
|
|
592
|
-
<summary><strong><code>representative-claim: seven_day</code> in my headers — am I downgraded?</strong></summary>
|
|
593
|
-
|
|
594
|
-
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)
|
|
595
|
-
</details>
|
|
596
|
-
|
|
597
|
-
<details>
|
|
598
|
-
<summary><strong>My usage through dario is higher than through Claude Code directly. Why?</strong></summary>
|
|
599
|
-
|
|
600
|
-
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).
|
|
601
|
-
</details>
|
|
602
|
-
|
|
603
|
-
<details>
|
|
604
|
-
<summary><strong>Will the billing split break my setup?</strong></summary>
|
|
605
|
-
|
|
606
|
-
Not today: it was announced, then paused before it took effect, and your traffic still bills subscription. What dario already does about it, and the canary that would catch a revival: [the billing split](#the-billing-split-a-contingency-dario-is-built-for).
|
|
607
|
-
</details>
|
|
608
|
-
|
|
609
|
-
<details>
|
|
610
|
-
<summary><strong>Why "dario"?</strong></summary>
|
|
611
|
-
|
|
612
|
-
It's a name, not an acronym. Don't overthink it.
|
|
613
|
-
</details>
|
|
614
|
-
|
|
615
|
-
Full FAQ, including per-tool 401s and Team/Enterprise plans: [faq.md](./docs/faq.md).
|
|
616
|
-
|
|
617
135
|
## Deep dives
|
|
618
136
|
|
|
619
137
|
- [Claude Code wire drift](https://askalf.github.io/dario/drift-feed/) — every change to what Claude Code sends on the wire, as the template watcher observed it; [RSS](https://askalf.github.io/dario/drift-feed/feed.xml) · [JSON Feed](https://askalf.github.io/dario/drift-feed/feed.json)
|
|
@@ -646,7 +164,7 @@ npm run audit:tui # drives the real TUI through a fake TTY at 12 geometries
|
|
|
646
164
|
npm run check:overage # overage-classifier check against live headers
|
|
647
165
|
npm run stress # concurrency / queue behaviour under load
|
|
648
166
|
npm run cch:calibrate # re-derive the billing-tag cch seed for a new Claude Code build
|
|
649
|
-
npm run readme:assets # regenerate the diagrams and TUI screenshots
|
|
167
|
+
npm run readme:assets # regenerate the diagrams and TUI screenshots (README + docs/)
|
|
650
168
|
```
|
|
651
169
|
|
|
652
170
|
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.
|