@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 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
- console.log(` Status: ${s.status === 'expired' ? 'Expired (will auto-refresh)' : 'Not authenticated'}`);
87
- if (s.status === 'none') {
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
@@ -45,4 +45,5 @@ export declare function getStatus(): Promise<{
45
45
  status: 'healthy' | 'expiring' | 'expired' | 'none';
46
46
  expiresAt?: number;
47
47
  expiresIn?: string;
48
+ canRefresh?: boolean;
48
49
  }>;
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
- const err = await res.text().catch(() => 'Unknown error');
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
- return { authenticated: false, status: 'expired', expiresAt };
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(['oauth-2025-04-20']);
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('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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": {