@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 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
- Two commands. No API key. Your Claude Max/Pro subscription becomes a local API endpoint<br/>that any tool, SDK, or framework can use.
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 # authenticate with Claude
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
- Opens your browser to Claude's OAuth page. Log in, authorize, paste the redirect URL back. Takes 10 seconds.
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`** — Standard PKCE OAuth flow. Opens Claude's auth page in your browser. You authorize, dario stores the tokens locally in `~/.dario/credentials.json`. No server involved, no secrets leave your machine.
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` | Authenticate with your Claude account |
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 (owner-only) |
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 the same public OAuth client ID and PKCE flow that Claude Code uses. It authenticates you as you, with your subscription, through Anthropic's official OAuth endpoints. The `--cli` mode literally uses Claude Code itself as the backend.
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, or `dario login` to re-authenticate.
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. Check your current status with `echo "hi" | ANTHROPIC_LOG=debug claude --print --model claude-haiku-4-5 2>&1 | grep ratelimit`.
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 browser-based login on a machine with a browser, then copy `~/.dario/credentials.json` to your server. Auto-refresh will keep it alive from there.
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` | PKCE flow, token storage, refresh logic |
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 OAuth Login');
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 CLI's public OAuth client (PKCE, no secret needed)
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.ai/oauth/authorize';
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 getCredentialsPath() {
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
- try {
41
- const raw = await readFile(getCredentialsPath(), 'utf-8');
42
- const parsed = JSON.parse(raw);
43
- // Validate structure
44
- if (!parsed?.claudeAiOauth?.accessToken || !parsed?.claudeAiOauth?.refreshToken) {
45
- return null;
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
- credentialsCache = parsed;
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 = getCredentialsPath();
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
- response_type: 'code',
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:profile',
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 h of ['x-ratelimit-limit-requests', 'x-ratelimit-limit-tokens', 'x-ratelimit-remaining-requests', 'x-ratelimit-remaining-tokens', 'request-id']) {
312
- const v = upstream.headers.get(h);
313
- if (v)
314
- responseHeaders[h] = v;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Use your Claude subscription as an API. Two commands, no API key. OAuth bridge for Claude Max/Pro.",
5
5
  "type": "module",
6
6
  "bin": {