@askalf/dario 1.0.6 → 1.0.8
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/cli.js +11 -3
- package/dist/oauth.d.ts +1 -0
- package/dist/oauth.js +6 -5
- package/dist/proxy.js +65 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { homedir } from 'node:os';
|
|
|
16
16
|
import { startOAuthFlow, exchangeCode, getStatus, refreshTokens } from './oauth.js';
|
|
17
17
|
import { startProxy } from './proxy.js';
|
|
18
18
|
const args = process.argv.slice(2);
|
|
19
|
-
const command = args[0] ?? 'proxy';
|
|
19
|
+
const command = args[0] ?? (process.stdin.isTTY ? 'proxy' : 'proxy');
|
|
20
20
|
function ask(question) {
|
|
21
21
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
22
22
|
return new Promise(resolve => {
|
|
@@ -83,10 +83,18 @@ async function status() {
|
|
|
83
83
|
console.log(' ─────────────');
|
|
84
84
|
console.log('');
|
|
85
85
|
if (!s.authenticated) {
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
if (s.status === 'expired' && s.canRefresh) {
|
|
87
|
+
console.log(' Status: Expired (will auto-refresh when proxy starts)');
|
|
88
|
+
console.log(' Run `dario refresh` to refresh now, or `dario proxy` to start.');
|
|
89
|
+
}
|
|
90
|
+
else if (s.status === 'none') {
|
|
91
|
+
console.log(' Status: Not authenticated');
|
|
88
92
|
console.log(' Run `dario login` to authenticate.');
|
|
89
93
|
}
|
|
94
|
+
else {
|
|
95
|
+
console.log(` Status: ${s.status}`);
|
|
96
|
+
console.log(' Run `dario login` to re-authenticate.');
|
|
97
|
+
}
|
|
90
98
|
}
|
|
91
99
|
else {
|
|
92
100
|
console.log(` Status: ${s.status}`);
|
package/dist/oauth.d.ts
CHANGED
package/dist/oauth.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Handles authorization, token exchange, storage, and auto-refresh.
|
|
6
6
|
*/
|
|
7
7
|
import { randomBytes, createHash } from 'node:crypto';
|
|
8
|
-
import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
|
|
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
11
|
// Claude CLI's public OAuth client (PKCE, no secret needed)
|
|
@@ -58,7 +58,6 @@ async function saveCredentials(creds) {
|
|
|
58
58
|
// Write atomically: write to temp file, then rename
|
|
59
59
|
const tmpPath = `${path}.tmp.${Date.now()}`;
|
|
60
60
|
await writeFile(tmpPath, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
61
|
-
const { rename } = await import('node:fs/promises');
|
|
62
61
|
await rename(tmpPath, path);
|
|
63
62
|
// Set permissions (best-effort — no-op on Windows where mode is ignored)
|
|
64
63
|
try {
|
|
@@ -105,10 +104,10 @@ export async function exchangeCode(code, codeVerifier) {
|
|
|
105
104
|
redirect_uri: OAUTH_REDIRECT_URI,
|
|
106
105
|
code_verifier: codeVerifier,
|
|
107
106
|
}),
|
|
107
|
+
signal: AbortSignal.timeout(30000),
|
|
108
108
|
});
|
|
109
109
|
if (!res.ok) {
|
|
110
|
-
|
|
111
|
-
throw new Error(`Token exchange failed (${res.status}): ${err}`);
|
|
110
|
+
throw new Error(`Token exchange failed (${res.status}). Check your authorization code and try again.`);
|
|
112
111
|
}
|
|
113
112
|
const data = await res.json();
|
|
114
113
|
const tokens = {
|
|
@@ -203,7 +202,9 @@ export async function getStatus() {
|
|
|
203
202
|
const { expiresAt } = creds.claudeAiOauth;
|
|
204
203
|
const now = Date.now();
|
|
205
204
|
if (expiresAt < now) {
|
|
206
|
-
|
|
205
|
+
// Expired but has refresh token — can be refreshed
|
|
206
|
+
const canRefresh = !!creds.claudeAiOauth.refreshToken;
|
|
207
|
+
return { authenticated: false, status: 'expired', expiresAt, canRefresh };
|
|
207
208
|
}
|
|
208
209
|
const ms = expiresAt - now;
|
|
209
210
|
const hours = Math.floor(ms / 3600000);
|
package/dist/proxy.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* No API key needed — your Claude subscription pays for it.
|
|
9
9
|
*/
|
|
10
10
|
import { createServer } from 'node:http';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { arch, platform, version as nodeVersion } from 'node:process';
|
|
11
14
|
import { getAccessToken, getStatus } from './oauth.js';
|
|
12
15
|
const ANTHROPIC_API = 'https://api.anthropic.com';
|
|
13
16
|
const DEFAULT_PORT = 3456;
|
|
@@ -15,6 +18,37 @@ const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB — generous for large prompts
|
|
|
15
18
|
const UPSTREAM_TIMEOUT_MS = 300_000; // 5 min — matches Anthropic SDK default
|
|
16
19
|
const LOCALHOST = '127.0.0.1';
|
|
17
20
|
const CORS_ORIGIN = 'http://localhost';
|
|
21
|
+
// Detect installed Claude Code version at startup
|
|
22
|
+
function detectClaudeVersion() {
|
|
23
|
+
try {
|
|
24
|
+
const out = execSync('claude --version', { timeout: 5000, stdio: 'pipe' }).toString().trim();
|
|
25
|
+
const match = out.match(/^([\d.]+)/);
|
|
26
|
+
return match?.[1] ?? '2.1.96';
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return '2.1.96';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getOsName() {
|
|
33
|
+
const p = platform;
|
|
34
|
+
if (p === 'win32')
|
|
35
|
+
return 'Windows';
|
|
36
|
+
if (p === 'darwin')
|
|
37
|
+
return 'MacOS';
|
|
38
|
+
return 'Linux';
|
|
39
|
+
}
|
|
40
|
+
// Persistent session ID per proxy lifetime (like Claude Code does per session)
|
|
41
|
+
const SESSION_ID = randomUUID();
|
|
42
|
+
// Detect @anthropic-ai/sdk version from installed package
|
|
43
|
+
function detectSdkVersion() {
|
|
44
|
+
try {
|
|
45
|
+
const pkg = require('@anthropic-ai/sdk/package.json');
|
|
46
|
+
return pkg.version ?? '0.81.0';
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return '0.81.0';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
18
52
|
function sanitizeError(err) {
|
|
19
53
|
const msg = err instanceof Error ? err.message : String(err);
|
|
20
54
|
// Never leak tokens in error messages
|
|
@@ -29,6 +63,8 @@ export async function startProxy(opts = {}) {
|
|
|
29
63
|
console.error('[dario] Not authenticated. Run `dario login` first.');
|
|
30
64
|
process.exit(1);
|
|
31
65
|
}
|
|
66
|
+
const cliVersion = detectClaudeVersion();
|
|
67
|
+
const sdkVersion = detectSdkVersion();
|
|
32
68
|
let requestCount = 0;
|
|
33
69
|
let tokenCostEstimate = 0;
|
|
34
70
|
const server = createServer(async (req, res) => {
|
|
@@ -84,7 +120,6 @@ export async function startProxy(opts = {}) {
|
|
|
84
120
|
// Proxy to Anthropic
|
|
85
121
|
try {
|
|
86
122
|
const accessToken = await getAccessToken();
|
|
87
|
-
requestCount++;
|
|
88
123
|
// Read request body with size limit
|
|
89
124
|
const chunks = [];
|
|
90
125
|
let totalBytes = 0;
|
|
@@ -106,7 +141,12 @@ export async function startProxy(opts = {}) {
|
|
|
106
141
|
const targetUrl = targetBase;
|
|
107
142
|
// Merge any client-provided beta flags with the required oauth flag
|
|
108
143
|
const clientBeta = req.headers['anthropic-beta'];
|
|
109
|
-
const betaFlags = new Set([
|
|
144
|
+
const betaFlags = new Set([
|
|
145
|
+
'oauth-2025-04-20',
|
|
146
|
+
'interleaved-thinking-2025-05-14',
|
|
147
|
+
'prompt-caching-scope-2026-01-05',
|
|
148
|
+
'claude-code-20250219',
|
|
149
|
+
]);
|
|
110
150
|
if (clientBeta) {
|
|
111
151
|
for (const flag of clientBeta.split(',')) {
|
|
112
152
|
const trimmed = flag.trim();
|
|
@@ -115,11 +155,24 @@ export async function startProxy(opts = {}) {
|
|
|
115
155
|
}
|
|
116
156
|
}
|
|
117
157
|
const headers = {
|
|
158
|
+
'accept': 'application/json',
|
|
118
159
|
'Authorization': `Bearer ${accessToken}`,
|
|
119
160
|
'Content-Type': 'application/json',
|
|
120
161
|
'anthropic-version': req.headers['anthropic-version'] || '2023-06-01',
|
|
121
162
|
'anthropic-beta': [...betaFlags].join(','),
|
|
163
|
+
'anthropic-dangerous-direct-browser-access': 'true',
|
|
164
|
+
'user-agent': `claude-cli/${cliVersion} (external, cli)`,
|
|
122
165
|
'x-app': 'cli',
|
|
166
|
+
'x-claude-code-session-id': SESSION_ID,
|
|
167
|
+
'x-client-request-id': randomUUID(),
|
|
168
|
+
'x-stainless-arch': arch,
|
|
169
|
+
'x-stainless-lang': 'js',
|
|
170
|
+
'x-stainless-os': getOsName(),
|
|
171
|
+
'x-stainless-package-version': sdkVersion,
|
|
172
|
+
'x-stainless-retry-count': '0',
|
|
173
|
+
'x-stainless-runtime': 'node',
|
|
174
|
+
'x-stainless-runtime-version': nodeVersion,
|
|
175
|
+
'x-stainless-timeout': '600',
|
|
123
176
|
};
|
|
124
177
|
const upstream = await fetch(targetUrl, {
|
|
125
178
|
method: req.method ?? 'POST',
|
|
@@ -143,6 +196,7 @@ export async function startProxy(opts = {}) {
|
|
|
143
196
|
if (v)
|
|
144
197
|
responseHeaders[h] = v;
|
|
145
198
|
}
|
|
199
|
+
requestCount++;
|
|
146
200
|
res.writeHead(upstream.status, responseHeaders);
|
|
147
201
|
if (isStream && upstream.body) {
|
|
148
202
|
// Stream SSE chunks through
|
|
@@ -186,6 +240,15 @@ export async function startProxy(opts = {}) {
|
|
|
186
240
|
res.end(JSON.stringify({ error: 'Proxy error', message: 'Failed to reach upstream API' }));
|
|
187
241
|
}
|
|
188
242
|
});
|
|
243
|
+
server.on('error', (err) => {
|
|
244
|
+
if (err.code === 'EADDRINUSE') {
|
|
245
|
+
console.error(`[dario] Port ${port} is already in use. Is another dario proxy running?`);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
console.error(`[dario] Server error: ${err.message}`);
|
|
249
|
+
}
|
|
250
|
+
process.exit(1);
|
|
251
|
+
});
|
|
189
252
|
server.listen(port, LOCALHOST, () => {
|
|
190
253
|
const oauthLine = `OAuth: ${status.status} (expires in ${status.expiresIn})`;
|
|
191
254
|
console.log('');
|