@askalf/dario 5.4.16 → 5.4.19
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/dist/cc-template.d.ts +11 -0
- package/dist/cc-template.js +68 -0
- package/dist/live-fingerprint.d.ts +0 -6
- package/dist/live-fingerprint.js +0 -8
- package/dist/proxy.js +26 -3
- package/dist/version.d.ts +0 -2
- package/dist/version.js +0 -4
- package/docs/admin-api.md +154 -0
- package/docs/commands.md +77 -0
- package/docs/configuration.md +109 -0
- package/docs/docker.md +233 -0
- package/docs/drift-monitor.md +290 -0
- package/docs/faq.md +145 -0
- package/docs/integrations/agent-compat.md +269 -0
- package/docs/integrations/compat-matrix.md +51 -0
- package/docs/integrations/hands-walkthrough.md +295 -0
- package/docs/integrations/openclaw-walkthrough.md +248 -0
- package/docs/integrations/openhands-walkthrough.md +255 -0
- package/docs/mcp-server.md +22 -0
- package/docs/multi-account-pool.md +68 -0
- package/docs/research/system-prompt-classifier-study.md +288 -0
- package/docs/returning.md +94 -0
- package/docs/sub-agent.md +13 -0
- package/docs/system-prompt.md +107 -0
- package/docs/usage.md +123 -0
- package/docs/vpn-routing.md +108 -0
- package/docs/why-now-2026-06.md +93 -0
- package/docs/wire-fidelity.md +16 -0
- package/package.json +4 -2
package/docs/usage.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Usage — SDK examples
|
|
2
|
+
|
|
3
|
+
## Python (Anthropic SDK)
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import anthropic
|
|
7
|
+
|
|
8
|
+
client = anthropic.Anthropic(
|
|
9
|
+
base_url="http://localhost:3456",
|
|
10
|
+
api_key="dario",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
msg = client.messages.create(
|
|
14
|
+
model="claude-opus-5",
|
|
15
|
+
max_tokens=1024,
|
|
16
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
17
|
+
)
|
|
18
|
+
print(msg.content[0].text)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Python (OpenAI SDK — same proxy, different provider)
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from openai import OpenAI
|
|
25
|
+
|
|
26
|
+
client = OpenAI(
|
|
27
|
+
base_url="http://localhost:3456/v1",
|
|
28
|
+
api_key="dario",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# gpt-4o routes to the configured OpenAI backend
|
|
32
|
+
msg = client.chat.completions.create(
|
|
33
|
+
model="gpt-4o",
|
|
34
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# claude-opus-5 routes to the Claude subscription backend — same SDK, same URL
|
|
38
|
+
claude_msg = client.chat.completions.create(
|
|
39
|
+
model="claude-opus-5",
|
|
40
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## TypeScript / Node.js
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
48
|
+
|
|
49
|
+
const client = new Anthropic({
|
|
50
|
+
baseURL: "http://localhost:3456",
|
|
51
|
+
apiKey: "dario",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const msg = await client.messages.create({
|
|
55
|
+
model: "claude-opus-5",
|
|
56
|
+
max_tokens: 1024,
|
|
57
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## OpenAI-compatible tools (universal env-var setup)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export OPENAI_BASE_URL=http://localhost:3456/v1
|
|
65
|
+
export OPENAI_API_KEY=dario
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use Claude model names (`claude-fable-5`, `claude-opus-5`, `claude-sonnet-5`, `claude-haiku-4-5`, plus `[1m]` long-context variants like `claude-fable-5[1m]` or `claude-opus-5[1m]` — every family except haiku has one, or shortcuts `fable` / `opus` / `sonnet` / `haiku` and their `1m` forms like `fable1m` / `opus1m`) for the Claude subscription backend, or GPT-family / Llama / any-other-model names for your configured OpenAI-compat backends. `GET /v1/models` autodetects the available set from Anthropic's live catalog (hourly TTL; baked fallback when offline), and the family shortcuts always resolve to the newest model of that family it lists.
|
|
69
|
+
|
|
70
|
+
For per-tool setup (Cursor, Continue, Aider, Cline, Roo, Zed, OpenHands, etc.), see [agent compatibility](./integrations/agent-compat.md#per-tool-setup).
|
|
71
|
+
|
|
72
|
+
## curl
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Claude backend via Anthropic format
|
|
76
|
+
curl http://localhost:3456/v1/messages \
|
|
77
|
+
-H "Content-Type: application/json" \
|
|
78
|
+
-H "anthropic-version: 2023-06-01" \
|
|
79
|
+
-d '{"model":"claude-opus-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello!"}]}'
|
|
80
|
+
|
|
81
|
+
# OpenAI backend via OpenAI format
|
|
82
|
+
curl http://localhost:3456/v1/chat/completions \
|
|
83
|
+
-H "Content-Type: application/json" \
|
|
84
|
+
-H "Authorization: Bearer dario" \
|
|
85
|
+
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello!"}]}'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Streaming, tool use, prompt caching, extended thinking
|
|
89
|
+
|
|
90
|
+
All supported. Claude backend: full Anthropic SSE format plus OpenAI-SSE translation for tool_use streaming. OpenAI-compat backend: streaming body forwarded byte-for-byte. See [Wire-fidelity axes](./wire-fidelity.md) for the v3.25 `--drain-on-close` knob that matches CC's read-to-EOF stream-consumption pattern.
|
|
91
|
+
|
|
92
|
+
## Provider prefix
|
|
93
|
+
|
|
94
|
+
Any request's `model` field can be written as `<provider>:<name>` to force which backend handles it, regardless of what the model name looks like.
|
|
95
|
+
|
|
96
|
+
| Prefix | Backend |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `openai:` | OpenAI-compat backend |
|
|
99
|
+
| `groq:` | OpenAI-compat backend |
|
|
100
|
+
| `openrouter:` | OpenAI-compat backend |
|
|
101
|
+
| `local:` | OpenAI-compat backend |
|
|
102
|
+
| `compat:` | OpenAI-compat backend |
|
|
103
|
+
| `claude:` | Claude subscription backend |
|
|
104
|
+
| `anthropic:` | Claude subscription backend |
|
|
105
|
+
|
|
106
|
+
The prefix gets stripped before the request goes upstream — the backend only sees the bare model name. Unrecognized prefixes are ignored, so Ollama-style `llama3:8b` passes through untouched. `dario proxy --model=openai:gpt-4o` applies the prefix to every request server-wide.
|
|
107
|
+
|
|
108
|
+
## Library mode
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { startProxy, getAccessToken, getStatus, listBackends } from "@askalf/dario";
|
|
112
|
+
|
|
113
|
+
await startProxy({ port: 3456, verbose: true });
|
|
114
|
+
const token = await getAccessToken();
|
|
115
|
+
const status = await getStatus();
|
|
116
|
+
const backends = await listBackends();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Health check
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
curl http://localhost:3456/health
|
|
123
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# VPN routing
|
|
2
|
+
|
|
3
|
+
For users who want their dario traffic — `api.anthropic.com` requests, OAuth flows, OpenAI-compat backend forwarding — routed through a VPN without putting the entire host on a system VPN. Three approaches, ordered by friction:
|
|
4
|
+
|
|
5
|
+
## Option A — System VPN (zero config, covers everyone)
|
|
6
|
+
|
|
7
|
+
The simplest approach. Run a system-level VPN client and **all** outbound from your machine — including dario's calls — goes through the tunnel.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 1. Install your provider's client (ProtonVPN, Mullvad, AirVPN, Tailscale, raw WireGuard…)
|
|
11
|
+
# 2. Connect.
|
|
12
|
+
# 3. Verify your egress IP changed:
|
|
13
|
+
curl ifconfig.me
|
|
14
|
+
# 4. Run dario normally:
|
|
15
|
+
dario proxy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This covers every dario use case. No flags needed. The tradeoff is that *all* traffic from the machine is now tunneled — fine if you wanted that anyway, less ideal if you only want dario egress to be private.
|
|
19
|
+
|
|
20
|
+
## Option B — Per-process via `--upstream-proxy=` (v3.35.0+)
|
|
21
|
+
|
|
22
|
+
Routes only dario's outbound through an HTTP/HTTPS proxy. The rest of your system stays on the default route.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Mullvad's HTTP proxy endpoint (Mullvad SOCKS5 also exists; see notes below)
|
|
26
|
+
dario proxy --upstream-proxy=http://10.64.0.1:80
|
|
27
|
+
|
|
28
|
+
# Or with credentials embedded:
|
|
29
|
+
dario proxy --upstream-proxy=http://user:pass@proxy.example.com:8080
|
|
30
|
+
|
|
31
|
+
# Or via env var:
|
|
32
|
+
DARIO_UPSTREAM_PROXY=http://127.0.0.1:8118 dario proxy
|
|
33
|
+
|
|
34
|
+
# Short alias is also supported:
|
|
35
|
+
dario proxy --via=http://127.0.0.1:8118
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
dario's startup banner confirms when it's active:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
[dario] Outbound proxy: http://10.64.0.1:80/ (all upstream fetches routed; localhost bypasses)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`dario doctor` surfaces the same:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
[INFO] Outbound proxy DARIO_UPSTREAM_PROXY=http://10.64.0.1:80/. Upstream fetches routed via this proxy; localhost calls bypass.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Provider matrix
|
|
51
|
+
|
|
52
|
+
| Provider | HTTP proxy | Notes |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| **Mullvad** | `http://10.64.0.1:80` (default) | SOCKS5 also at `:1080`; use HTTP for dario |
|
|
55
|
+
| **AirVPN** | `http://nl.airvpn.org:443` (varies by region) | HTTP available on all gateways |
|
|
56
|
+
| **ProtonVPN** | (no native HTTP proxy) | Use Option A (system VPN) instead |
|
|
57
|
+
| **Privoxy / Polipo** | `http://127.0.0.1:8118` | Local; useful with Tor (`forward-socks5 / 127.0.0.1:9050`) |
|
|
58
|
+
| **Cloudflare WARP** | `http://127.0.0.1:40000` | Native HTTP proxy mode in `warp-cli set-mode proxy` |
|
|
59
|
+
| **Corporate proxy** | `http://proxy.corp:8080` | Standard org pattern |
|
|
60
|
+
| **Squid (self-hosted)** | `http://your-squid:3128` | Run a squid instance in a desired jurisdiction |
|
|
61
|
+
|
|
62
|
+
### Constraints
|
|
63
|
+
|
|
64
|
+
- **Bun runtime required.** Bun's fetch implements the `proxy` option natively. Node's built-in fetch ignores it silently — to avoid a false-success failure mode where the flag appears to work while requests actually go direct, dario refuses to start with `--upstream-proxy` unless running under Bun. dario auto-relaunches under Bun when available; `bun run dario proxy --upstream-proxy=...` works directly.
|
|
65
|
+
- **HTTP/HTTPS schemes only.** SOCKS5 is not currently supported by Bun 1.3.x's fetch (`UnsupportedProxyProtocol`). If your VPN provider only exposes SOCKS5, run a local SOCKS-to-HTTP bridge such as `privoxy` with `forward-socks5 / 127.0.0.1:1080` and point dario at the privoxy HTTP side.
|
|
66
|
+
- **TLS terminates end-to-end at Anthropic.** The proxy sees only the destination hostname (via SNI) and byte timing in CONNECT mode — not your request bodies. Your `bun-match` BoringSSL ClientHello is preserved.
|
|
67
|
+
- **Localhost calls bypass the proxy.** Anything dario fetches at `localhost`, `127.0.0.1`, `::1`, or any `*.localhost` host goes direct (so self-tests and inbound aren't accidentally tunneled).
|
|
68
|
+
|
|
69
|
+
## Option C — Tailscale exit nodes (zero dario config, ideal for teams)
|
|
70
|
+
|
|
71
|
+
If you already run Tailscale, you can route through any peer node:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 1. Designate an exit node on a peer (e.g., a Tailscale-routed node in a desired region)
|
|
75
|
+
# 2. From your machine:
|
|
76
|
+
sudo tailscale up --exit-node=<peer-name-or-IP>
|
|
77
|
+
# 3. Run dario normally — egress is now via the Tailscale exit
|
|
78
|
+
dario proxy
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This is the cleanest pattern for teams: one peer runs in a known jurisdiction, every team member's dario egresses through it, audit trail lives at the peer. The hosted dario Pro tier can ship managed exit nodes as a turnkey feature.
|
|
82
|
+
|
|
83
|
+
## What this does NOT do
|
|
84
|
+
|
|
85
|
+
- **Doesn't change CC's wire fingerprint.** TLS ClientHello is still Bun's BoringSSL (or Node's OpenSSL if you're on Node — see `dario doctor`'s Runtime/TLS row). The proxy is at L4 transport; the L7 TLS fingerprint is end-to-end.
|
|
86
|
+
- **Doesn't hide your usage from Anthropic.** Anthropic still sees an authenticated OAuth subscription session billed against your account. Egress IP varies; the account does not.
|
|
87
|
+
- **Doesn't proxy CC's own traffic during live capture.** dario spawns the installed `claude` binary to capture its outbound — that subprocess uses the host's normal network. If you also want CC's capture traffic tunneled, run dario under Option A or C.
|
|
88
|
+
|
|
89
|
+
## Verifying it's working
|
|
90
|
+
|
|
91
|
+
The most direct check: hit a request-and-response endpoint that echoes your egress IP:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# With dario running:
|
|
95
|
+
DARIO_UPSTREAM_PROXY=http://your-proxy:port dario proxy --verbose &
|
|
96
|
+
|
|
97
|
+
# Then in another terminal, force a request through dario:
|
|
98
|
+
curl http://localhost:3456/v1/messages \
|
|
99
|
+
-H "Content-Type: application/json" \
|
|
100
|
+
-H "anthropic-version: 2023-06-01" \
|
|
101
|
+
-d '{"model":"claude-haiku-4-5","max_tokens":50,"messages":[{"role":"user","content":"hi"}]}'
|
|
102
|
+
|
|
103
|
+
# In the dario verbose log, the upstream connection will show as routed
|
|
104
|
+
# via the proxy. Provider-side logs (Mullvad / AirVPN / squid) will show
|
|
105
|
+
# a CONNECT to api.anthropic.com:443 from your dario process.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
If your VPN provider's status page or dashboard shows the connection, the routing is working. If it doesn't, double-check that dario relaunched under Bun (`dario doctor`'s Runtime/TLS row should say `bun-match`).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# The 2026 billing split: announced, paused, watched
|
|
2
|
+
|
|
3
|
+
Anthropic [announced on 2026-05-13](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) — via the Claude Help Center and a [@ClaudeDevs post](https://x.com/ClaudeDevs/status/2054610152817619388), with no anthropic.com blog post and no email to most subscribers — that starting **2026-06-15**, Claude Agent SDK and `claude -p` (Claude Code headless mode) usage would no longer count toward Claude plan usage limits. Instead, eligible plans would receive a fixed monthly Agent-SDK credit. The announced terms:
|
|
4
|
+
|
|
5
|
+
| Plan | Subscription pool (interactive Claude Code, Claude Cowork, Claude.ai) | Announced Agent-SDK / `claude -p` credit |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Pro | $20/mo | $20/mo |
|
|
8
|
+
| Max 5x | $100/mo | $100/mo |
|
|
9
|
+
| Max 20x | $200/mo | $200/mo |
|
|
10
|
+
| Team Premium | $100/mo | $100/mo |
|
|
11
|
+
| Enterprise (seat-based) | included | $200/mo |
|
|
12
|
+
|
|
13
|
+
Once the Agent-SDK credit was exhausted, those calls would flip to **metered per-token API pricing**, with the subscription pool reserved for interactive use.
|
|
14
|
+
|
|
15
|
+
## What actually happened: it was paused
|
|
16
|
+
|
|
17
|
+
**Anthropic paused the change before 2026-06-15, and it never took effect.** The Help Center now states that Agent-SDK usage, `claude -p`, and third-party app usage continue drawing from your existing subscription pool, unchanged. Anthropic said it is reworking the proposal "to better support how users build with Claude subscriptions" and pledged advance notice before implementing any revised version. No credits were issued; no subscription limits changed.
|
|
18
|
+
|
|
19
|
+
So as of this writing, the split is **not live**. A `claude -p` run or an Agent SDK app bills your subscription pool today whether or not it goes through dario. The reason this document still exists — and the reason dario's daily billing-classifier canary still runs — is that the change was announced once, on short public notice, and Anthropic has explicitly reserved the right to bring back a revised version.
|
|
20
|
+
|
|
21
|
+
## Why dario holds either way
|
|
22
|
+
|
|
23
|
+
Dario's Claude backend was designed to make every outbound request look like an **interactive Claude Code session** at the wire level. That predates the 2026-05-13 announcement — it's the same mechanism that's routed traffic through your Pro / Max 5x / Max 20x subscription pool since v3.22.
|
|
24
|
+
|
|
25
|
+
The relevant property: Anthropic's billing classifier identifies traffic by what arrives at `api.anthropic.com`, not by what initiated it locally. A `claude -p` invocation that talks to dario gets rewritten into an interactive Claude Code request shape before it leaves your machine. The classifier sees interactive CC and applies the subscription pool — today, with the split paused, and equally if a revised split ever makes the entrypoint billable.
|
|
26
|
+
|
|
27
|
+
This is the entire point of the wire-fidelity work in [`wire-fidelity.md`](./wire-fidelity.md). Axes — body key order, TLS ClientHello, inter-request timing, stream-consumption shape, session-ID lifecycle, request headers — were closed specifically so the upstream classifier has no observable signal that the request originated outside an interactive CC session.
|
|
28
|
+
|
|
29
|
+
## What this means concretely
|
|
30
|
+
|
|
31
|
+
| Setup | Today (split paused) | If a revised split ships |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Direct Anthropic API with API key (Cline default, Aider default, your scripts) | Per-token API billing | Per-token API billing (unchanged) |
|
|
34
|
+
| Proxy that forwards `claude -p` / Agent SDK request shape unchanged | Subscription pool | **Separate $20–200/mo Agent-SDK credit, then per-token API** |
|
|
35
|
+
| **dario** | **Subscription pool** | **Subscription pool (request rewritten as interactive CC)** |
|
|
36
|
+
| Claude Code itself (you, sitting at a terminal) | Subscription pool | Subscription pool (unchanged) |
|
|
37
|
+
|
|
38
|
+
No config change is needed on the user side, split or no split — same install, same `localhost:3456`, same `ANTHROPIC_BASE_URL=http://localhost:3456` env var. The wire-rewrite has been doing this job in production for months.
|
|
39
|
+
|
|
40
|
+
## The tripwire: how you'd know the day it returns
|
|
41
|
+
|
|
42
|
+
You don't have to watch Anthropic's Help Center. dario runs a **daily billing-classifier canary** ([`cc-billing-classifier-canary.yml`](../.github/workflows/cc-billing-classifier-canary.yml)) that fires one live request and asserts the `representative-claim` header still maps to a subscription bucket. A revived split — or any silent classifier change that reclassifies dario's traffic — surfaces as a canary failure and an auto-opened issue within a day, not on a surprise invoice. It's one of [three drift watchers](../README.md#how-it-works-and-how-it-stays-working) that keep the wire-rewrite current against a moving target.
|
|
43
|
+
|
|
44
|
+
You can run the same check by hand at any time:
|
|
45
|
+
|
|
46
|
+
### Verify the rate-limit headers
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 1. Direct claude -p, no dario.
|
|
50
|
+
unset ANTHROPIC_BASE_URL ANTHROPIC_API_KEY
|
|
51
|
+
claude -p "say hi" 2>&1 | grep -i 'rate-limit\|representative'
|
|
52
|
+
|
|
53
|
+
# 2. The same prompt through dario.
|
|
54
|
+
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
55
|
+
export ANTHROPIC_API_KEY=dario
|
|
56
|
+
claude -p "say hi" 2>&1 | grep -i 'rate-limit\|representative'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
What to look for:
|
|
60
|
+
- Both calls should reference `five_hour` or `seven_day` — subscription-billing accounting buckets — because the split is paused and `claude -p` bills subscription directly too. See [Discussion #1](https://github.com/askalf/dario/discussions/1) for the full breakdown of subscription rate-limit headers.
|
|
61
|
+
- If a revised split ships, the **direct** call would move to an Agent-SDK / credit bucket while the **dario** call should stay in `five_hour` / `seven_day`. If dario's path ever lands in an agent-credit or `overage` bucket, file an issue — that's the upstream classifier drift the live template extractor and the [drift detector](../scripts/capture-full-body.mjs) exist to catch.
|
|
62
|
+
|
|
63
|
+
### Verify via dario doctor
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
dario doctor # template drift + OAuth + runtime/TLS
|
|
67
|
+
dario doctor --usage # also fires one request and prints the live billing bucket
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A clean `dario doctor` report is the per-machine equivalent of the rate-limit-header check above.
|
|
71
|
+
|
|
72
|
+
## What this doesn't promise
|
|
73
|
+
|
|
74
|
+
1. **dario doesn't multiply your subscription.** It routes through the plan you have. Beyond the subscription cap, Anthropic still rate-limits; [multi-account pool mode](./multi-account-pool.md) is the answer for hitting the cap on a single account, and it's orthogonal to the billing-split question.
|
|
75
|
+
2. **No claim about competitor products specifically.** The tables above refer to architectural patterns (`proxy that forwards request shape unchanged`), not named tools. If a particular tool also implements wire-fidelity replay, it gets the same outcome dario does. This doc exists because most subscription-billing proxies don't — they extract OAuth tokens and forward the raw client request, which is exactly the shape a revived split would reclassify.
|
|
76
|
+
3. **Anthropic terms of service still apply.** This project is independent, unofficial, third-party — see [DISCLAIMER.md](../DISCLAIMER.md). Whether any particular use complies with Anthropic's current terms is between you and Anthropic.
|
|
77
|
+
|
|
78
|
+
## If you're coming from another proxy
|
|
79
|
+
|
|
80
|
+
1. `npm install -g @askalf/dario` (or pull `ghcr.io/askalf/dario:latest` — see [`docker.md`](./docker.md))
|
|
81
|
+
2. `dario login` — if you have Claude Code installed and logged in, this picks up existing credentials. Otherwise it runs its own OAuth flow. For SSH / headless setups, `dario login --manual`.
|
|
82
|
+
3. `dario proxy` — starts the local proxy on `localhost:3456`
|
|
83
|
+
4. Point your tools' `ANTHROPIC_BASE_URL` at `http://localhost:3456` and `ANTHROPIC_API_KEY` at `dario` (literal string `dario` on loopback; a real `DARIO_API_KEY` if you bind to `0.0.0.0`)
|
|
84
|
+
5. `dario doctor` — confirms everything is wired correctly
|
|
85
|
+
|
|
86
|
+
Backends other than the Claude subscription (OpenAI, Groq, OpenRouter, Ollama, etc.) are configured separately via `dario backend add` — see the [README](../README.md).
|
|
87
|
+
|
|
88
|
+
## Related reading
|
|
89
|
+
|
|
90
|
+
- [`wire-fidelity.md`](./wire-fidelity.md) — the wire-rewrite axes that make this possible
|
|
91
|
+
- [`returning.md`](./returning.md) — if you used dario before and are coming back
|
|
92
|
+
- [`multi-account-pool.md`](./multi-account-pool.md) — running multiple subscriptions in a pool
|
|
93
|
+
- [Discussion #1](https://github.com/askalf/dario/discussions/1) — rate-limit header reference for subscription billing
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Wire-fidelity axes
|
|
2
|
+
|
|
3
|
+
Between v3.22 and v3.28, dario's Claude backend closed six axes along which a proxy can diverge from real Claude Code. Each is a separate knob, each ships with its own test suite, each is surfaced through `dario doctor` where the axis has something to report. Defaults are chosen so existing setups don't regress.
|
|
4
|
+
|
|
5
|
+
| Axis | Release | What it does | How to tune |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| **Request body key order** | v3.22 | Top-level JSON key order of the outbound `/v1/messages` body is captured from CC's wire serialization and replayed byte-for-byte. Schema bumped v2 → v3; stale caches quarantined. | Automatic once a live capture exists. The baked fallback carries a v2.1.112 snapshot. |
|
|
8
|
+
| **Runtime / TLS ClientHello** | v3.23 | Classifies the runtime as `bun-match` / `bun-ja3-unverified` / `bun-bypassed` / `node-only` and surfaces the class + hint in `dario doctor`. Bun yields the BoringSSL ClientHello CC presents; Node yields OpenSSL's (distinct JA3). Being on Bun is necessary but not sufficient — only Bun ≥ v1.3.14 is measured to reproduce CC's JA3, so an older Bun is flagged `bun-ja3-unverified` rather than green (#813). | `--strict-tls` (or `DARIO_STRICT_TLS=1`) refuses to start proxy mode unless `bun-match`. `DARIO_QUIET_TLS=1` silences the startup banner in known-fine environments. |
|
|
9
|
+
| **Inter-request timing** | v3.24 | Replaces the hardcoded 500 ms floor with a configurable floor + uniform jitter. A fixed 500 ms minimum-inter-arrival is an observable edge at scale; jitter dissolves the edge. | `--pace-min=MS`, `--pace-jitter=MS`, or `DARIO_PACE_MIN_MS` / `DARIO_PACE_JITTER_MS`. Legacy `DARIO_MIN_INTERVAL_MS` still honored. |
|
|
10
|
+
| **Stream-consumption shape** | v3.25 | When a downstream client disconnects mid-stream, CC keeps reading SSE to EOF. Dario now offers the same: drain upstream to completion even when the consumer has left. Default off — don't silently burn tokens. | `--drain-on-close` / `DARIO_DRAIN_ON_CLOSE=1`. Bounded by the existing 5-minute upstream timeout. |
|
|
11
|
+
| **Session-ID lifecycle** | v3.28 | Generalizes the v3.19 hardcoded 15-minute idle rotation into a tunable `SessionRegistry` with jitter, max-age, and per-client bucketing. Fixes a v3.27 body/header rotation race as a side effect. | `--session-idle-rotate=MS` (default 900000), `--session-rotate-jitter=MS`, `--session-max-age=MS`, `--session-per-client`. Env mirrors `DARIO_SESSION_*`. Defaults are bit-identical to v3.27. |
|
|
12
|
+
| **MCP / sub-agent reach** | v3.26 + v3.27 | Not a wire axis — a *surface* axis. CC-aware tools can now address dario directly (sub-agent from inside CC, MCP server for any MCP client), so operators don't have to switch terminals to introspect the proxy. Read-only by design. | `dario subagent install` / `dario mcp`. See [`mcp-server.md`](./mcp-server.md) and [`sub-agent.md`](./sub-agent.md). |
|
|
13
|
+
|
|
14
|
+
| **Client identity headers** | v5.4.19 | On the passthrough path `isGenuineCCClient` has already established the caller *is* Claude Code, so its own identity headers (`user-agent`, `x-app`, `x-stainless-*`, `x-claude-code-*`, `x-client-*`) are forwarded unchanged instead of being replaced with template values. The template exists to synthesise CC's shape for clients that are not CC; where the genuine article is in hand, forwarding beats imitating. Measured before the change: of 13 identity headers a real client sent, 12 were replaced and 1 dropped — 0 forwarded (#885). Auth, session id and the merged beta set stay dario's. | Automatic on the genuine-CC path; nothing to tune. Non-CC clients are unaffected, and the gate reads the request **body**, so headers cannot self-authorise. |
|
|
15
|
+
|
|
16
|
+
The original six-direction roadmap is complete; the axes below it are later findings. Note that header **order** is deliberately absent as an axis: `orderHeadersForOutbound` builds the captured sequence, but `fetch()` re-normalises it before the wire, and on Bun you cannot have both CC's JA3 and raw header control in one process (#813). The honest framing of the whole table is byte-identical **body**, structurally close **headers** — not packet-identical.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.19",
|
|
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": {
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
|
+
"docs",
|
|
20
|
+
"!docs/recovery.md",
|
|
19
21
|
"README.md",
|
|
20
22
|
"LICENSE"
|
|
21
23
|
],
|
|
22
24
|
"scripts": {
|
|
23
25
|
"build": "tsc && cp src/cc-template-data.json dist/",
|
|
24
26
|
"test": "node --test --test-concurrency=8 test/all.test.mjs",
|
|
25
|
-
"test:serial": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/live-fingerprint.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/runtime-fingerprint.mjs && node test/pacing.mjs && node test/stream-drain.mjs && node test/subagent.mjs && node test/mcp-protocol.mjs && node test/mcp-tools.mjs && node test/mcp-e2e.mjs && node test/session-rotation.mjs && node test/drift-detection.mjs && node test/cc-authorize-probe-classifier.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/doctor-identity-drift.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/durable-token-persist.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs && node test/context-bleed.mjs && node test/capture-provenance.mjs && node test/sanitize-messages.mjs && node test/platform-tools.mjs && node test/strict-template-flags.mjs && node test/request-queue.mjs && node test/effort-flag.mjs && node test/template-invariants.mjs",
|
|
27
|
+
"test:serial": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/live-fingerprint.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/runtime-fingerprint.mjs && node test/pacing.mjs && node test/stream-drain.mjs && node test/subagent.mjs && node test/mcp-protocol.mjs && node test/mcp-tools.mjs && node test/mcp-e2e.mjs && node test/session-rotation.mjs && node test/drift-detection.mjs && node test/cc-authorize-probe-classifier.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/doctor-identity-drift.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/durable-token-persist.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs && node test/context-bleed.mjs && node test/passthrough-header-forwarding.mjs && node test/capture-provenance.mjs && node test/sanitize-messages.mjs && node test/platform-tools.mjs && node test/strict-template-flags.mjs && node test/request-queue.mjs && node test/effort-flag.mjs && node test/template-invariants.mjs",
|
|
26
28
|
"audit": "npm audit --production --audit-level=high",
|
|
27
29
|
"prepublishOnly": "npm run build",
|
|
28
30
|
"start": "node dist/cli.js",
|