@askalf/dario 1.2.0 → 1.2.1
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 +47 -12
- package/dist/cli.js +15 -56
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/oauth.d.ts +3 -11
- package/dist/oauth.js +79 -24
- package/dist/proxy.js +6 -5
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
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
|
+
No API key needed. 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
|
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/v/@askalf/dario?color=blue" alt="npm version"></a>
|
|
11
|
+
<a href="https://github.com/askalf/dario/actions/workflows/ci.yml"><img src="https://github.com/askalf/dario/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
12
|
+
<a href="https://github.com/askalf/dario/actions/workflows/codeql.yml"><img src="https://github.com/askalf/dario/actions/workflows/codeql.yml/badge.svg" alt="CodeQL"></a>
|
|
13
|
+
<a href="https://github.com/askalf/dario/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@askalf/dario" alt="License"></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/dm/@askalf/dario" alt="Downloads"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
9
17
|
<p align="center">
|
|
10
18
|
<a href="#quick-start">Quick Start</a> •
|
|
11
19
|
<a href="#how-it-works">How It Works</a> •
|
|
@@ -40,7 +48,9 @@ You pay $100-200/mo for Claude Max or Pro. But that subscription only works on c
|
|
|
40
48
|
|
|
41
49
|
### Prerequisites
|
|
42
50
|
|
|
43
|
-
[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview)
|
|
51
|
+
[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) installed and logged in (recommended). Dario detects your existing Claude Code credentials automatically.
|
|
52
|
+
|
|
53
|
+
If Claude Code isn't installed, dario runs its own OAuth flow — opens your browser, you authorize, done.
|
|
44
54
|
|
|
45
55
|
### Install
|
|
46
56
|
|
|
@@ -60,9 +70,8 @@ npx @askalf/dario login
|
|
|
60
70
|
dario login
|
|
61
71
|
```
|
|
62
72
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
If Claude Code credentials aren't found, dario falls back to a manual OAuth flow.
|
|
73
|
+
- **With Claude Code installed:** Detects your credentials automatically and starts the proxy. No browser needed.
|
|
74
|
+
- **Without Claude Code:** Opens your browser to Claude's OAuth page. Authorize, and dario captures the token automatically via a local callback server. Then run `dario proxy` to start the server.
|
|
66
75
|
|
|
67
76
|
### Start the proxy
|
|
68
77
|
|
|
@@ -251,7 +260,7 @@ ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario your-tool-here
|
|
|
251
260
|
└──────────┘ └─────────────────┘ └──────────────────┘
|
|
252
261
|
```
|
|
253
262
|
|
|
254
|
-
1. **`dario login`** — Detects your existing Claude Code credentials (`~/.claude/.credentials.json`) and starts the proxy automatically. If Claude Code isn't installed,
|
|
263
|
+
1. **`dario login`** — Detects your existing Claude Code credentials (`~/.claude/.credentials.json`) and starts the proxy automatically. If Claude Code isn't installed, runs a PKCE OAuth flow with a local callback server to capture the token automatically.
|
|
255
264
|
|
|
256
265
|
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.
|
|
257
266
|
|
|
@@ -321,11 +330,11 @@ curl http://localhost:3456/health
|
|
|
321
330
|
|
|
322
331
|
| Concern | How dario handles it |
|
|
323
332
|
|---------|---------------------|
|
|
324
|
-
| Credential storage |
|
|
333
|
+
| Credential storage | Reads from Claude Code (`~/.claude/.credentials.json`) or its own store (`~/.dario/credentials.json`) with `0600` permissions |
|
|
325
334
|
| OAuth flow | PKCE (Proof Key for Code Exchange) — no client secret needed |
|
|
326
335
|
| Token transmission | OAuth tokens never leave localhost. Only forwarded to `api.anthropic.com` over HTTPS |
|
|
327
336
|
| Network exposure | Proxy binds to `127.0.0.1` only — not accessible from other machines |
|
|
328
|
-
| SSRF protection |
|
|
337
|
+
| SSRF protection | Hardcoded allowlist of API paths — only `/v1/messages`, `/v1/models`, `/v1/complete` are proxied |
|
|
329
338
|
| Token rotation | Refresh tokens rotate on every use (single-use) |
|
|
330
339
|
| Error sanitization | Token patterns redacted from all error messages |
|
|
331
340
|
| Data collection | Zero. No telemetry, no analytics, no phoning home |
|
|
@@ -342,7 +351,7 @@ Claude Max and Claude Pro. Any plan that lets you use Claude Code.
|
|
|
342
351
|
Should work if your plan includes Claude Code access. Not tested yet — please open an issue with results.
|
|
343
352
|
|
|
344
353
|
**Do I need Claude Code installed?**
|
|
345
|
-
|
|
354
|
+
Recommended but not required. If Claude Code is installed and logged in, `dario login` picks up your credentials automatically. Without Claude Code, dario runs its own OAuth flow to authenticate directly. Note: `--cli` mode requires Claude Code (`npm install -g @anthropic-ai/claude-code`).
|
|
346
355
|
|
|
347
356
|
**What happens when my token expires?**
|
|
348
357
|
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.
|
|
@@ -354,7 +363,7 @@ Use `--cli` mode: `dario proxy --cli`. This routes through the Claude Code binar
|
|
|
354
363
|
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.
|
|
355
364
|
|
|
356
365
|
**Can I run this on a server?**
|
|
357
|
-
Dario binds to localhost by default. For server use, you'd need to handle the initial
|
|
366
|
+
Dario binds to localhost by default. For server use, you'd need to handle the initial login on a machine with a browser, then copy `~/.claude/.credentials.json` (or `~/.dario/credentials.json`) to your server. Auto-refresh will keep it alive from there.
|
|
358
367
|
|
|
359
368
|
**Why "dario"?**
|
|
360
369
|
Named after [Dario Amodei](https://en.wikipedia.org/wiki/Dario_Amodei), CEO of Anthropic.
|
|
@@ -380,13 +389,39 @@ const status = await getStatus();
|
|
|
380
389
|
console.log(status.expiresIn); // "11h 42m"
|
|
381
390
|
```
|
|
382
391
|
|
|
392
|
+
## Trust & Transparency
|
|
393
|
+
|
|
394
|
+
Dario handles your OAuth tokens. Here's why you can trust it:
|
|
395
|
+
|
|
396
|
+
| Signal | Status |
|
|
397
|
+
|--------|--------|
|
|
398
|
+
| **Source code** | ~1000 lines of TypeScript — small enough to read in one sitting |
|
|
399
|
+
| **Dependencies** | 1 production dep (`@anthropic-ai/sdk`). Verify: `npm ls --production` |
|
|
400
|
+
| **npm provenance** | Every release is [SLSA attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions |
|
|
401
|
+
| **Security scanning** | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) runs on every push and weekly |
|
|
402
|
+
| **Credential handling** | Tokens never logged, redacted from errors, stored with 0600 permissions |
|
|
403
|
+
| **Network scope** | Binds to 127.0.0.1 only. Upstream traffic goes exclusively to `api.anthropic.com` over HTTPS |
|
|
404
|
+
| **No telemetry** | Zero analytics, tracking, or data collection of any kind |
|
|
405
|
+
| **Audit trail** | [CHANGELOG.md](CHANGELOG.md) documents every release |
|
|
406
|
+
| **Branch protection** | CI must pass before merge. CODEOWNERS enforces review |
|
|
407
|
+
|
|
408
|
+
Verify the npm package matches this repo:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
# Check provenance attestation
|
|
412
|
+
npm audit signatures 2>/dev/null; npm view @askalf/dario dist.integrity
|
|
413
|
+
|
|
414
|
+
# Check dependency tree (should be minimal)
|
|
415
|
+
cd $(npm root -g)/@askalf/dario && npm ls --production
|
|
416
|
+
```
|
|
417
|
+
|
|
383
418
|
## Contributing
|
|
384
419
|
|
|
385
|
-
PRs welcome. The codebase is ~
|
|
420
|
+
PRs welcome. The codebase is ~1000 lines of TypeScript across 4 files:
|
|
386
421
|
|
|
387
422
|
| File | Purpose |
|
|
388
423
|
|------|---------|
|
|
389
|
-
| `src/oauth.ts` | Token storage, refresh logic, Claude Code credential detection |
|
|
424
|
+
| `src/oauth.ts` | Token storage, refresh logic, Claude Code credential detection, auto OAuth flow |
|
|
390
425
|
| `src/proxy.ts` | HTTP proxy server + CLI backend |
|
|
391
426
|
| `src/cli.ts` | CLI entry point |
|
|
392
427
|
| `src/index.ts` | Library exports |
|
package/dist/cli.js
CHANGED
|
@@ -9,23 +9,13 @@
|
|
|
9
9
|
* dario refresh — Force token refresh
|
|
10
10
|
* dario logout — Remove saved credentials
|
|
11
11
|
*/
|
|
12
|
-
import { createInterface } from 'node:readline';
|
|
13
12
|
import { readFile, unlink } from 'node:fs/promises';
|
|
14
13
|
import { join } from 'node:path';
|
|
15
14
|
import { homedir } from 'node:os';
|
|
16
|
-
import {
|
|
15
|
+
import { startAutoOAuthFlow, getStatus, refreshTokens } from './oauth.js';
|
|
17
16
|
import { startProxy } from './proxy.js';
|
|
18
17
|
const args = process.argv.slice(2);
|
|
19
18
|
const command = args[0] ?? (process.stdin.isTTY ? 'proxy' : 'proxy');
|
|
20
|
-
function ask(question) {
|
|
21
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
-
return new Promise(resolve => {
|
|
23
|
-
rl.question(question, answer => {
|
|
24
|
-
rl.close();
|
|
25
|
-
resolve(answer.trim());
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
19
|
async function login() {
|
|
30
20
|
console.log('');
|
|
31
21
|
console.log(' dario — Claude Login');
|
|
@@ -49,38 +39,9 @@ async function login() {
|
|
|
49
39
|
catch { /* no Claude Code credentials, fall through to OAuth */ }
|
|
50
40
|
console.log(' No Claude Code credentials found. Starting OAuth flow...');
|
|
51
41
|
console.log('');
|
|
52
|
-
const { authUrl, codeVerifier } = startOAuthFlow();
|
|
53
|
-
console.log(' Step 1: Open this URL in your browser:');
|
|
54
|
-
console.log('');
|
|
55
|
-
console.log(` ${authUrl}`);
|
|
56
|
-
console.log('');
|
|
57
|
-
console.log(' Step 2: Log in to your Claude account and authorize.');
|
|
58
|
-
console.log('');
|
|
59
|
-
console.log(' Step 3: After authorization, you\'ll be redirected to a page');
|
|
60
|
-
console.log(' that shows a code. Copy the FULL URL from your browser\'s');
|
|
61
|
-
console.log(' address bar (it contains the authorization code).');
|
|
62
|
-
console.log('');
|
|
63
|
-
const input = await ask(' Paste the redirect URL or authorization code: ');
|
|
64
|
-
// Extract code from URL or use raw input
|
|
65
|
-
let code = input;
|
|
66
|
-
try {
|
|
67
|
-
const url = new URL(input);
|
|
68
|
-
// Only extract from trusted Anthropic redirect URLs
|
|
69
|
-
if (url.hostname === 'platform.claude.com' || url.hostname === 'claude.ai') {
|
|
70
|
-
code = url.searchParams.get('code') ?? input;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// Not a URL, use as-is (raw code)
|
|
75
|
-
}
|
|
76
|
-
if (!code || code.length < 10 || code.length > 2048) {
|
|
77
|
-
console.error(' Invalid authorization code.');
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
42
|
try {
|
|
81
|
-
const tokens = await
|
|
43
|
+
const tokens = await startAutoOAuthFlow();
|
|
82
44
|
const expiresIn = Math.round((tokens.expiresAt - Date.now()) / 60000);
|
|
83
|
-
console.log('');
|
|
84
45
|
console.log(' Login successful!');
|
|
85
46
|
console.log(` Token expires in ${expiresIn} minutes (auto-refreshes).`);
|
|
86
47
|
console.log('');
|
|
@@ -160,7 +121,7 @@ async function help() {
|
|
|
160
121
|
dario — Use your Claude subscription as an API.
|
|
161
122
|
|
|
162
123
|
Usage:
|
|
163
|
-
dario login
|
|
124
|
+
dario login Detect credentials + start proxy (or run OAuth)
|
|
164
125
|
dario proxy [options] Start the API proxy server
|
|
165
126
|
dario status Check authentication status
|
|
166
127
|
dario refresh Force token refresh
|
|
@@ -169,27 +130,25 @@ async function help() {
|
|
|
169
130
|
Proxy options:
|
|
170
131
|
--model=MODEL Force a model for all requests
|
|
171
132
|
Shortcuts: opus, sonnet, haiku
|
|
133
|
+
Full IDs: claude-opus-4-6, claude-sonnet-4-6
|
|
172
134
|
Default: passthrough (client decides)
|
|
173
135
|
--cli Use Claude CLI as backend (bypasses rate limits)
|
|
174
136
|
--port=PORT Port to listen on (default: 3456)
|
|
175
137
|
--verbose, -v Log all requests
|
|
176
138
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
3. Point any Anthropic SDK at http://localhost:3456
|
|
181
|
-
|
|
182
|
-
Example with OpenClaw, or any tool that uses the Anthropic API:
|
|
183
|
-
ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario openclaw start
|
|
139
|
+
Quick start:
|
|
140
|
+
dario login # auto-detects Claude Code credentials
|
|
141
|
+
dario proxy # or: dario proxy --cli --model=opus
|
|
184
142
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
143
|
+
Then point any Anthropic SDK at http://localhost:3456:
|
|
144
|
+
export ANTHROPIC_BASE_URL=http://localhost:3456
|
|
145
|
+
export ANTHROPIC_API_KEY=dario
|
|
188
146
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
147
|
+
Examples:
|
|
148
|
+
curl http://localhost:3456/v1/messages \\
|
|
149
|
+
-H "Content-Type: application/json" \\
|
|
150
|
+
-H "anthropic-version: 2023-06-01" \\
|
|
151
|
+
-d '{"model":"claude-opus-4-6","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'
|
|
193
152
|
|
|
194
153
|
Your subscription handles the billing. No API key needed.
|
|
195
154
|
Tokens auto-refresh in the background — set it and forget it.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Use this if you want to embed dario in your own app
|
|
5
5
|
* instead of running the CLI.
|
|
6
6
|
*/
|
|
7
|
-
export {
|
|
7
|
+
export { startAutoOAuthFlow, refreshTokens, getAccessToken, getStatus, loadCredentials } from './oauth.js';
|
|
8
8
|
export type { OAuthTokens, CredentialsFile } from './oauth.js';
|
|
9
9
|
export { startProxy } from './proxy.js';
|
package/dist/index.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Use this if you want to embed dario in your own app
|
|
5
5
|
* instead of running the CLI.
|
|
6
6
|
*/
|
|
7
|
-
export {
|
|
7
|
+
export { startAutoOAuthFlow, refreshTokens, getAccessToken, getStatus, loadCredentials } from './oauth.js';
|
|
8
8
|
export { startProxy } from './proxy.js';
|
package/dist/oauth.d.ts
CHANGED
|
@@ -15,18 +15,10 @@ export interface CredentialsFile {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function loadCredentials(): Promise<CredentialsFile | null>;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* Automatic OAuth flow using a local callback server (same as Claude Code).
|
|
19
|
+
* Opens browser, captures the authorization code automatically.
|
|
20
20
|
*/
|
|
21
|
-
export declare function
|
|
22
|
-
authUrl: string;
|
|
23
|
-
state: string;
|
|
24
|
-
codeVerifier: string;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Exchange authorization code for tokens and save them.
|
|
28
|
-
*/
|
|
29
|
-
export declare function exchangeCode(code: string, codeVerifier: string): Promise<OAuthTokens>;
|
|
21
|
+
export declare function startAutoOAuthFlow(): Promise<OAuthTokens>;
|
|
30
22
|
/**
|
|
31
23
|
* Refresh the access token using the refresh token.
|
|
32
24
|
* Retries with exponential backoff on transient failures.
|
package/dist/oauth.js
CHANGED
|
@@ -12,7 +12,7 @@ import { homedir } from 'node:os';
|
|
|
12
12
|
const OAUTH_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
13
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
|
-
const
|
|
15
|
+
const OAUTH_SCOPES = 'org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload';
|
|
16
16
|
// Refresh 30 min before expiry
|
|
17
17
|
const REFRESH_BUFFER_MS = 30 * 60 * 1000;
|
|
18
18
|
// In-memory credential cache — avoids disk reads on every request
|
|
@@ -72,46 +72,101 @@ async function saveCredentials(creds) {
|
|
|
72
72
|
credentialsCacheTime = Date.now();
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
75
|
+
* Automatic OAuth flow using a local callback server (same as Claude Code).
|
|
76
|
+
* Opens browser, captures the authorization code automatically.
|
|
77
77
|
*/
|
|
78
|
-
export function
|
|
78
|
+
export async function startAutoOAuthFlow() {
|
|
79
|
+
const { createServer } = await import('node:http');
|
|
79
80
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
80
81
|
const state = base64url(randomBytes(16));
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const server = createServer((req, res) => {
|
|
84
|
+
const url = new URL(req.url || '', `http://${req.headers.host || 'localhost'}`);
|
|
85
|
+
if (url.pathname !== '/callback') {
|
|
86
|
+
res.writeHead(404);
|
|
87
|
+
res.end();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const code = url.searchParams.get('code');
|
|
91
|
+
const returnedState = url.searchParams.get('state');
|
|
92
|
+
if (!code) {
|
|
93
|
+
res.writeHead(400);
|
|
94
|
+
res.end('No authorization code received');
|
|
95
|
+
server.close();
|
|
96
|
+
reject(new Error('No authorization code received'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (returnedState !== state) {
|
|
100
|
+
res.writeHead(400);
|
|
101
|
+
res.end('Invalid state parameter');
|
|
102
|
+
server.close();
|
|
103
|
+
reject(new Error('Invalid state parameter'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Redirect browser to success page
|
|
107
|
+
res.writeHead(302, { Location: 'https://platform.claude.com/oauth/code/success?app=claude-code' });
|
|
108
|
+
res.end();
|
|
109
|
+
// Exchange the code for tokens
|
|
110
|
+
server.close();
|
|
111
|
+
exchangeCodeWithRedirect(code, codeVerifier, state, port)
|
|
112
|
+
.then(resolve)
|
|
113
|
+
.catch(reject);
|
|
114
|
+
});
|
|
115
|
+
let port = 0;
|
|
116
|
+
server.listen(0, 'localhost', () => {
|
|
117
|
+
const addr = server.address();
|
|
118
|
+
port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
119
|
+
const params = new URLSearchParams({
|
|
120
|
+
code: 'true',
|
|
121
|
+
client_id: OAUTH_CLIENT_ID,
|
|
122
|
+
response_type: 'code',
|
|
123
|
+
redirect_uri: `http://localhost:${port}/callback`,
|
|
124
|
+
scope: OAUTH_SCOPES,
|
|
125
|
+
code_challenge: codeChallenge,
|
|
126
|
+
code_challenge_method: 'S256',
|
|
127
|
+
state,
|
|
128
|
+
});
|
|
129
|
+
const authUrl = `${OAUTH_AUTHORIZE_URL}?${params.toString()}`;
|
|
130
|
+
// Open browser
|
|
131
|
+
console.log(' Opening browser to sign in...');
|
|
132
|
+
console.log(` If the browser didn't open, visit: ${authUrl}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
// Open browser using platform-specific commands (no external deps)
|
|
135
|
+
const { exec } = require('node:child_process');
|
|
136
|
+
const cmd = process.platform === 'win32' ? `start "" "${authUrl}"`
|
|
137
|
+
: process.platform === 'darwin' ? `open "${authUrl}"`
|
|
138
|
+
: `xdg-open "${authUrl}"`;
|
|
139
|
+
exec(cmd, () => { });
|
|
140
|
+
});
|
|
141
|
+
server.on('error', (err) => {
|
|
142
|
+
reject(new Error(`Failed to start OAuth callback server: ${err.message}`));
|
|
143
|
+
});
|
|
144
|
+
// Timeout after 5 minutes
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
server.close();
|
|
147
|
+
reject(new Error('OAuth flow timed out. Try again with `dario login`.'));
|
|
148
|
+
}, 300_000);
|
|
90
149
|
});
|
|
91
|
-
return {
|
|
92
|
-
authUrl: `${OAUTH_AUTHORIZE_URL}?${params.toString()}`,
|
|
93
|
-
state,
|
|
94
|
-
codeVerifier,
|
|
95
|
-
};
|
|
96
150
|
}
|
|
97
151
|
/**
|
|
98
|
-
* Exchange
|
|
152
|
+
* Exchange code using the localhost redirect URI.
|
|
99
153
|
*/
|
|
100
|
-
|
|
154
|
+
async function exchangeCodeWithRedirect(code, codeVerifier, state, port) {
|
|
101
155
|
const res = await fetch(OAUTH_TOKEN_URL, {
|
|
102
156
|
method: 'POST',
|
|
103
|
-
headers: { 'Content-Type': 'application/
|
|
104
|
-
body:
|
|
157
|
+
headers: { 'Content-Type': 'application/json' },
|
|
158
|
+
body: JSON.stringify({
|
|
105
159
|
grant_type: 'authorization_code',
|
|
106
160
|
client_id: OAUTH_CLIENT_ID,
|
|
107
161
|
code,
|
|
108
|
-
redirect_uri:
|
|
162
|
+
redirect_uri: `http://localhost:${port}/callback`,
|
|
109
163
|
code_verifier: codeVerifier,
|
|
164
|
+
state,
|
|
110
165
|
}),
|
|
111
166
|
signal: AbortSignal.timeout(30000),
|
|
112
167
|
});
|
|
113
168
|
if (!res.ok) {
|
|
114
|
-
throw new Error(`Token exchange failed (${res.status}).
|
|
169
|
+
throw new Error(`Token exchange failed (${res.status}). Try again with \`dario login\`.`);
|
|
115
170
|
}
|
|
116
171
|
const data = await res.json();
|
|
117
172
|
const tokens = {
|
package/dist/proxy.js
CHANGED
|
@@ -176,8 +176,10 @@ export async function startProxy(opts = {}) {
|
|
|
176
176
|
res.end();
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
+
// Strip query parameters for endpoint matching
|
|
180
|
+
const urlPath = req.url?.split('?')[0] ?? '';
|
|
179
181
|
// Health check
|
|
180
|
-
if (
|
|
182
|
+
if (urlPath === '/health' || urlPath === '/') {
|
|
181
183
|
const s = await getStatus();
|
|
182
184
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
183
185
|
res.end(JSON.stringify({
|
|
@@ -189,20 +191,19 @@ export async function startProxy(opts = {}) {
|
|
|
189
191
|
return;
|
|
190
192
|
}
|
|
191
193
|
// Status endpoint
|
|
192
|
-
if (
|
|
194
|
+
if (urlPath === '/status') {
|
|
193
195
|
const s = await getStatus();
|
|
194
196
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
195
197
|
res.end(JSON.stringify(s));
|
|
196
198
|
return;
|
|
197
199
|
}
|
|
198
200
|
// Allowlisted API paths — only these are proxied (prevents SSRF)
|
|
199
|
-
const rawPath = req.url?.split('?')[0] ?? '';
|
|
200
201
|
const allowedPaths = {
|
|
201
202
|
'/v1/messages': `${ANTHROPIC_API}/v1/messages`,
|
|
202
203
|
'/v1/models': `${ANTHROPIC_API}/v1/models`,
|
|
203
204
|
'/v1/complete': `${ANTHROPIC_API}/v1/complete`,
|
|
204
205
|
};
|
|
205
|
-
const targetBase = allowedPaths[
|
|
206
|
+
const targetBase = allowedPaths[urlPath];
|
|
206
207
|
if (!targetBase) {
|
|
207
208
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
208
209
|
res.end(JSON.stringify({ error: 'Forbidden', message: 'Path not allowed' }));
|
|
@@ -232,7 +233,7 @@ export async function startProxy(opts = {}) {
|
|
|
232
233
|
}
|
|
233
234
|
const body = Buffer.concat(chunks);
|
|
234
235
|
// CLI backend mode: route through claude --print
|
|
235
|
-
if (useCli &&
|
|
236
|
+
if (useCli && urlPath === '/v1/messages' && req.method === 'POST' && body.length > 0) {
|
|
236
237
|
const cliResult = await handleViaCli(body, modelOverride, verbose);
|
|
237
238
|
requestCount++;
|
|
238
239
|
res.writeHead(cliResult.status, {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "Use your Claude subscription as an API.
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Use your Claude subscription as an API. No API key needed. Local proxy for Claude Max/Pro subscriptions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"dario": "./dist/cli.js"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc",
|
|
24
|
+
"audit": "npm audit --production --audit-level=high",
|
|
24
25
|
"prepublishOnly": "npm run build",
|
|
25
26
|
"start": "node dist/cli.js",
|
|
26
27
|
"dev": "tsx src/cli.ts"
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"cli",
|
|
41
42
|
"developer-tools"
|
|
42
43
|
],
|
|
43
|
-
"author": "askalf",
|
|
44
|
+
"author": "askalf (https://github.com/askalf)",
|
|
44
45
|
"license": "MIT",
|
|
45
46
|
"repository": {
|
|
46
47
|
"type": "git",
|