@askalf/dario 1.1.3 → 1.2.0
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 +21 -16
- package/dist/cli.js +21 -3
- package/dist/oauth.js +23 -19
- package/dist/proxy.js +35 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<h1 align="center">dario</h1>
|
|
3
3
|
<p align="center"><strong>Use your Claude subscription as an API.</strong></p>
|
|
4
4
|
<p align="center">
|
|
5
|
-
|
|
5
|
+
One command. No API key. Your Claude Max/Pro subscription becomes a local API endpoint<br/>that any tool, SDK, or framework can use.
|
|
6
6
|
</p>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx @askalf/dario login #
|
|
21
|
-
npx @askalf/dario proxy # start local API on :3456
|
|
20
|
+
npx @askalf/dario login # detects Claude Code credentials, starts proxy
|
|
22
21
|
|
|
23
22
|
# now use it from anywhere
|
|
24
23
|
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
@@ -33,12 +32,16 @@ That's it. Any tool that speaks the Anthropic API now uses your subscription.
|
|
|
33
32
|
|
|
34
33
|
You pay $100-200/mo for Claude Max or Pro. But that subscription only works on claude.ai and Claude Code. If you want to use Claude with **any other tool** — OpenClaw, Cursor, Continue, Aider, your own scripts — you need a separate API key with separate billing.
|
|
35
34
|
|
|
36
|
-
**Note:** Claude subscriptions have [usage limits](https://support.claude.com/en/articles/11647753-how-do-usage-and-length-limits-work) that reset on rolling 5-hour and 7-day windows. When exceeded, Opus and Sonnet may return 429 errors while Haiku continues working. Use `--cli` mode to route through Claude Code's binary, which is not affected by these limits.
|
|
35
|
+
**Note:** Claude subscriptions have [usage limits](https://support.claude.com/en/articles/11647753-how-do-usage-and-length-limits-work) that reset on rolling 5-hour and 7-day windows. When exceeded, Opus and Sonnet may return 429 errors while Haiku continues working. You can check your utilization via Claude Code's `/usage` command or [statusline](https://code.claude.com/docs/en/statusline). Use `--cli` mode to route through Claude Code's binary, which is not affected by these limits.
|
|
37
36
|
|
|
38
37
|
**dario fixes this.** It creates a local proxy that translates API key auth into your subscription's OAuth tokens — and with `--cli` mode, routes through the Claude Code binary for uninterrupted access.
|
|
39
38
|
|
|
40
39
|
## Quick Start
|
|
41
40
|
|
|
41
|
+
### Prerequisites
|
|
42
|
+
|
|
43
|
+
[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) must be installed and logged in. Dario uses your existing Claude Code credentials — no separate authentication needed.
|
|
44
|
+
|
|
42
45
|
### Install
|
|
43
46
|
|
|
44
47
|
```bash
|
|
@@ -49,7 +52,6 @@ Or use npx (no install needed):
|
|
|
49
52
|
|
|
50
53
|
```bash
|
|
51
54
|
npx @askalf/dario login
|
|
52
|
-
npx @askalf/dario proxy
|
|
53
55
|
```
|
|
54
56
|
|
|
55
57
|
### Login
|
|
@@ -58,7 +60,9 @@ npx @askalf/dario proxy
|
|
|
58
60
|
dario login
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
If Claude Code is installed and authenticated, dario detects your credentials automatically and starts the proxy. No browser, no OAuth flow, no pasting URLs.
|
|
64
|
+
|
|
65
|
+
If Claude Code credentials aren't found, dario falls back to a manual OAuth flow.
|
|
62
66
|
|
|
63
67
|
### Start the proxy
|
|
64
68
|
|
|
@@ -115,8 +119,6 @@ Backend: Claude CLI (bypasses rate limits)
|
|
|
115
119
|
Model: claude-opus-4-6 (all requests)
|
|
116
120
|
```
|
|
117
121
|
|
|
118
|
-
**Requirements:** Claude Code must be installed (`npm install -g @anthropic-ai/claude-code` or already installed via the desktop app).
|
|
119
|
-
|
|
120
122
|
**Trade-offs vs direct API mode:**
|
|
121
123
|
|
|
122
124
|
| | Direct API (default) | CLI Backend (`--cli`) |
|
|
@@ -249,7 +251,7 @@ ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario your-tool-here
|
|
|
249
251
|
└──────────┘ └─────────────────┘ └──────────────────┘
|
|
250
252
|
```
|
|
251
253
|
|
|
252
|
-
1. **`dario login`** —
|
|
254
|
+
1. **`dario login`** — Detects your existing Claude Code credentials (`~/.claude/.credentials.json`) and starts the proxy automatically. If Claude Code isn't installed, falls back to a manual PKCE OAuth flow.
|
|
253
255
|
|
|
254
256
|
2. **`dario proxy`** — Starts an HTTP server on localhost that implements the Anthropic Messages API. In direct mode, it swaps your API key for an OAuth bearer token. In CLI mode, it routes through the Claude Code binary.
|
|
255
257
|
|
|
@@ -259,7 +261,7 @@ ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario your-tool-here
|
|
|
259
261
|
|
|
260
262
|
| Command | Description |
|
|
261
263
|
|---------|-------------|
|
|
262
|
-
| `dario login` |
|
|
264
|
+
| `dario login` | Detect credentials and start proxy |
|
|
263
265
|
| `dario proxy` | Start the local API proxy |
|
|
264
266
|
| `dario status` | Check if your token is healthy |
|
|
265
267
|
| `dario refresh` | Force an immediate token refresh |
|
|
@@ -319,7 +321,7 @@ curl http://localhost:3456/health
|
|
|
319
321
|
|
|
320
322
|
| Concern | How dario handles it |
|
|
321
323
|
|---------|---------------------|
|
|
322
|
-
| Credential storage | `~/.dario/credentials.json` with `0600` permissions
|
|
324
|
+
| Credential storage | Uses Claude Code's credentials or `~/.dario/credentials.json` with `0600` permissions |
|
|
323
325
|
| OAuth flow | PKCE (Proof Key for Code Exchange) — no client secret needed |
|
|
324
326
|
| Token transmission | OAuth tokens never leave localhost. Only forwarded to `api.anthropic.com` over HTTPS |
|
|
325
327
|
| Network exposure | Proxy binds to `127.0.0.1` only — not accessible from other machines |
|
|
@@ -331,7 +333,7 @@ curl http://localhost:3456/health
|
|
|
331
333
|
## FAQ
|
|
332
334
|
|
|
333
335
|
**Does this violate Anthropic's terms of service?**
|
|
334
|
-
Dario uses
|
|
336
|
+
Dario uses your existing Claude Code credentials with the same OAuth tokens. It authenticates you as you, with your subscription, through Anthropic's official API. The `--cli` mode literally uses Claude Code itself as the backend.
|
|
335
337
|
|
|
336
338
|
**What subscription plans work?**
|
|
337
339
|
Claude Max and Claude Pro. Any plan that lets you use Claude Code.
|
|
@@ -339,17 +341,20 @@ Claude Max and Claude Pro. Any plan that lets you use Claude Code.
|
|
|
339
341
|
**Does it work with Claude Team / Enterprise?**
|
|
340
342
|
Should work if your plan includes Claude Code access. Not tested yet — please open an issue with results.
|
|
341
343
|
|
|
344
|
+
**Do I need Claude Code installed?**
|
|
345
|
+
Yes. Dario reads your Claude Code credentials for authentication. Run `claude` to install and log in, then `dario login` picks up your credentials automatically.
|
|
346
|
+
|
|
342
347
|
**What happens when my token expires?**
|
|
343
|
-
Dario auto-refreshes tokens 30 minutes before expiry. You should never see an auth error in normal use. If something goes wrong, `dario refresh` forces an immediate refresh
|
|
348
|
+
Dario auto-refreshes tokens 30 minutes before expiry. You should never see an auth error in normal use. If something goes wrong, `dario refresh` forces an immediate refresh.
|
|
344
349
|
|
|
345
350
|
**I'm getting rate limited on Opus. What do I do?**
|
|
346
351
|
Use `--cli` mode: `dario proxy --cli`. This routes through the Claude Code binary, which continues working when direct API calls are rate limited. You can also enable [extra usage](https://support.claude.com/en/articles/12429409-manage-extra-usage-for-paid-claude-plans) in your Anthropic account settings to extend your limits at API rates.
|
|
347
352
|
|
|
348
353
|
**What are the usage limits?**
|
|
349
|
-
Claude subscriptions have rolling 5-hour and 7-day usage windows shared across claude.ai and Claude Code. See [Anthropic's docs](https://support.claude.com/en/articles/11647753-how-do-usage-and-length-limits-work) for details.
|
|
354
|
+
Claude subscriptions have rolling 5-hour and 7-day usage windows shared across claude.ai and Claude Code. See [Anthropic's docs](https://support.claude.com/en/articles/11647753-how-do-usage-and-length-limits-work) for details. In Claude Code, use `/usage` to check your current limits, or configure the [statusline](https://code.claude.com/docs/en/statusline) to show real-time 5h and 7d utilization percentages.
|
|
350
355
|
|
|
351
356
|
**Can I run this on a server?**
|
|
352
|
-
Dario binds to localhost by default. For server use, you'd need to handle the initial
|
|
357
|
+
Dario binds to localhost by default. For server use, you'd need to handle the initial Claude Code login on a machine with a browser, then copy `~/.claude/.credentials.json` to your server. Auto-refresh will keep it alive from there.
|
|
353
358
|
|
|
354
359
|
**Why "dario"?**
|
|
355
360
|
Named after [Dario Amodei](https://en.wikipedia.org/wiki/Dario_Amodei), CEO of Anthropic.
|
|
@@ -381,7 +386,7 @@ PRs welcome. The codebase is ~700 lines of TypeScript across 4 files:
|
|
|
381
386
|
|
|
382
387
|
| File | Purpose |
|
|
383
388
|
|------|---------|
|
|
384
|
-
| `src/oauth.ts` |
|
|
389
|
+
| `src/oauth.ts` | Token storage, refresh logic, Claude Code credential detection |
|
|
385
390
|
| `src/proxy.ts` | HTTP proxy server + CLI backend |
|
|
386
391
|
| `src/cli.ts` | CLI entry point |
|
|
387
392
|
| `src/index.ts` | Library exports |
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* dario logout — Remove saved credentials
|
|
11
11
|
*/
|
|
12
12
|
import { createInterface } from 'node:readline';
|
|
13
|
-
import { unlink } from 'node:fs/promises';
|
|
13
|
+
import { readFile, unlink } from 'node:fs/promises';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { homedir } from 'node:os';
|
|
16
16
|
import { startOAuthFlow, exchangeCode, getStatus, refreshTokens } from './oauth.js';
|
|
@@ -28,8 +28,26 @@ function ask(question) {
|
|
|
28
28
|
}
|
|
29
29
|
async function login() {
|
|
30
30
|
console.log('');
|
|
31
|
-
console.log(' dario — Claude
|
|
32
|
-
console.log('
|
|
31
|
+
console.log(' dario — Claude Login');
|
|
32
|
+
console.log(' ───────────────────');
|
|
33
|
+
console.log('');
|
|
34
|
+
// Check if Claude Code credentials exist
|
|
35
|
+
const ccPath = join(homedir(), '.claude', '.credentials.json');
|
|
36
|
+
try {
|
|
37
|
+
const raw = await readFile(ccPath, 'utf-8');
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (parsed?.claudeAiOauth?.accessToken && parsed?.claudeAiOauth?.refreshToken) {
|
|
40
|
+
const expiresAt = parsed.claudeAiOauth.expiresAt;
|
|
41
|
+
if (expiresAt > Date.now()) {
|
|
42
|
+
console.log(' Found Claude Code credentials. Starting proxy...');
|
|
43
|
+
console.log('');
|
|
44
|
+
await proxy();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch { /* no Claude Code credentials, fall through to OAuth */ }
|
|
50
|
+
console.log(' No Claude Code credentials found. Starting OAuth flow...');
|
|
33
51
|
console.log('');
|
|
34
52
|
const { authUrl, codeVerifier } = startOAuthFlow();
|
|
35
53
|
console.log(' Step 1: Open this URL in your browser:');
|
package/dist/oauth.js
CHANGED
|
@@ -8,9 +8,9 @@ import { randomBytes, createHash } from 'node:crypto';
|
|
|
8
8
|
import { readFile, writeFile, mkdir, chmod, rename } from 'node:fs/promises';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
|
-
// Claude
|
|
11
|
+
// Claude Code's public OAuth client (PKCE, no secret needed)
|
|
12
12
|
const OAUTH_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
13
|
-
const OAUTH_AUTHORIZE_URL = 'https://claude.
|
|
13
|
+
const OAUTH_AUTHORIZE_URL = 'https://platform.claude.com/oauth/authorize';
|
|
14
14
|
const OAUTH_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
15
15
|
const OAUTH_REDIRECT_URI = 'https://platform.claude.com/oauth/code/callback';
|
|
16
16
|
// Refresh 30 min before expiry
|
|
@@ -29,31 +29,34 @@ function generatePKCE() {
|
|
|
29
29
|
const codeChallenge = base64url(createHash('sha256').update(codeVerifier).digest());
|
|
30
30
|
return { codeVerifier, codeChallenge };
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function getDarioCredentialsPath() {
|
|
33
33
|
return join(homedir(), '.dario', 'credentials.json');
|
|
34
34
|
}
|
|
35
|
+
function getClaudeCodeCredentialsPath() {
|
|
36
|
+
return join(homedir(), '.claude', '.credentials.json');
|
|
37
|
+
}
|
|
35
38
|
export async function loadCredentials() {
|
|
36
39
|
// Return cached if fresh
|
|
37
40
|
if (credentialsCache && Date.now() - credentialsCacheTime < CACHE_TTL_MS) {
|
|
38
41
|
return credentialsCache;
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
// Try dario's own credentials first, then fall back to Claude Code's
|
|
44
|
+
for (const path of [getDarioCredentialsPath(), getClaudeCodeCredentialsPath()]) {
|
|
45
|
+
try {
|
|
46
|
+
const raw = await readFile(path, 'utf-8');
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
if (parsed?.claudeAiOauth?.accessToken && parsed?.claudeAiOauth?.refreshToken) {
|
|
49
|
+
credentialsCache = parsed;
|
|
50
|
+
credentialsCacheTime = Date.now();
|
|
51
|
+
return credentialsCache;
|
|
52
|
+
}
|
|
46
53
|
}
|
|
47
|
-
|
|
48
|
-
credentialsCacheTime = Date.now();
|
|
49
|
-
return credentialsCache;
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return null;
|
|
54
|
+
catch { /* try next */ }
|
|
53
55
|
}
|
|
56
|
+
return null;
|
|
54
57
|
}
|
|
55
58
|
async function saveCredentials(creds) {
|
|
56
|
-
const path =
|
|
59
|
+
const path = getDarioCredentialsPath();
|
|
57
60
|
await mkdir(dirname(path), { recursive: true });
|
|
58
61
|
// Write atomically: write to temp file, then rename
|
|
59
62
|
const tmpPath = `${path}.tmp.${Date.now()}`;
|
|
@@ -76,13 +79,14 @@ export function startOAuthFlow() {
|
|
|
76
79
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
77
80
|
const state = base64url(randomBytes(16));
|
|
78
81
|
const params = new URLSearchParams({
|
|
79
|
-
|
|
82
|
+
code: 'true',
|
|
80
83
|
client_id: OAUTH_CLIENT_ID,
|
|
84
|
+
response_type: 'code',
|
|
81
85
|
redirect_uri: OAUTH_REDIRECT_URI,
|
|
82
|
-
scope: 'user:inference user:
|
|
83
|
-
state,
|
|
86
|
+
scope: 'org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload',
|
|
84
87
|
code_challenge: codeChallenge,
|
|
85
88
|
code_challenge_method: 'S256',
|
|
89
|
+
state,
|
|
86
90
|
});
|
|
87
91
|
return {
|
|
88
92
|
authUrl: `${OAUTH_AUTHORIZE_URL}?${params.toString()}`,
|
package/dist/proxy.js
CHANGED
|
@@ -265,6 +265,7 @@ export async function startProxy(opts = {}) {
|
|
|
265
265
|
'interleaved-thinking-2025-05-14',
|
|
266
266
|
'prompt-caching-scope-2026-01-05',
|
|
267
267
|
'claude-code-20250219',
|
|
268
|
+
'context-management-2025-06-27',
|
|
268
269
|
]);
|
|
269
270
|
if (clientBeta) {
|
|
270
271
|
for (const flag of clientBeta.split(',')) {
|
|
@@ -280,6 +281,7 @@ export async function startProxy(opts = {}) {
|
|
|
280
281
|
'anthropic-version': req.headers['anthropic-version'] || '2023-06-01',
|
|
281
282
|
'anthropic-beta': [...betaFlags].join(','),
|
|
282
283
|
'anthropic-dangerous-direct-browser-access': 'true',
|
|
284
|
+
'anthropic-client-platform': 'cli',
|
|
283
285
|
'user-agent': `claude-cli/${cliVersion} (external, cli)`,
|
|
284
286
|
'x-app': 'cli',
|
|
285
287
|
'x-claude-code-session-id': SESSION_ID,
|
|
@@ -307,11 +309,11 @@ export async function startProxy(opts = {}) {
|
|
|
307
309
|
'Content-Type': contentType || 'application/json',
|
|
308
310
|
'Access-Control-Allow-Origin': CORS_ORIGIN,
|
|
309
311
|
};
|
|
310
|
-
// Forward rate limit headers
|
|
311
|
-
for (const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
// Forward rate limit headers (including unified subscription headers)
|
|
313
|
+
for (const [key, value] of upstream.headers.entries()) {
|
|
314
|
+
if (key.startsWith('x-ratelimit') || key.startsWith('anthropic-ratelimit') || key === 'request-id') {
|
|
315
|
+
responseHeaders[key] = value;
|
|
316
|
+
}
|
|
315
317
|
}
|
|
316
318
|
requestCount++;
|
|
317
319
|
res.writeHead(upstream.status, responseHeaders);
|
|
@@ -382,6 +384,33 @@ export async function startProxy(opts = {}) {
|
|
|
382
384
|
console.log(` ${modelLine}`);
|
|
383
385
|
console.log('');
|
|
384
386
|
});
|
|
387
|
+
// Session presence heartbeat — registers this proxy as an active Claude Code session
|
|
388
|
+
// Claude Code sends this every 5 seconds; the server uses it for priority routing
|
|
389
|
+
const clientId = randomUUID();
|
|
390
|
+
const connectedAt = new Date().toISOString();
|
|
391
|
+
let lastPresencePulse = 0;
|
|
392
|
+
const presenceInterval = setInterval(async () => {
|
|
393
|
+
const now = Date.now();
|
|
394
|
+
if (now - lastPresencePulse < 5000)
|
|
395
|
+
return;
|
|
396
|
+
lastPresencePulse = now;
|
|
397
|
+
try {
|
|
398
|
+
const token = await getAccessToken();
|
|
399
|
+
const presenceUrl = `${ANTHROPIC_API}/v1/code/sessions/${SESSION_ID}/client/presence`;
|
|
400
|
+
await fetch(presenceUrl, {
|
|
401
|
+
method: 'POST',
|
|
402
|
+
headers: {
|
|
403
|
+
'Authorization': `Bearer ${token}`,
|
|
404
|
+
'Content-Type': 'application/json',
|
|
405
|
+
'anthropic-version': '2023-06-01',
|
|
406
|
+
'anthropic-client-platform': 'cli',
|
|
407
|
+
},
|
|
408
|
+
body: JSON.stringify({ client_id: clientId, connected_at: connectedAt }),
|
|
409
|
+
signal: AbortSignal.timeout(5000),
|
|
410
|
+
}).catch(() => { });
|
|
411
|
+
}
|
|
412
|
+
catch { /* presence is best-effort */ }
|
|
413
|
+
}, 5000);
|
|
385
414
|
// Periodic token refresh (every 15 minutes)
|
|
386
415
|
const refreshInterval = setInterval(async () => {
|
|
387
416
|
try {
|
|
@@ -398,6 +427,7 @@ export async function startProxy(opts = {}) {
|
|
|
398
427
|
// Graceful shutdown
|
|
399
428
|
const shutdown = () => {
|
|
400
429
|
console.log('\n[dario] Shutting down...');
|
|
430
|
+
clearInterval(presenceInterval);
|
|
401
431
|
clearInterval(refreshInterval);
|
|
402
432
|
server.close(() => process.exit(0));
|
|
403
433
|
// Force exit after 5s if connections don't close
|