@askalf/dario 2.0.0 → 2.1.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
@@ -159,34 +159,18 @@ dario proxy --cli --model=opus
159
159
 
160
160
  ## OpenAI Compatibility
161
161
 
162
- Dario speaks both Anthropic and OpenAI API formats. Any tool built for OpenAI works with your Claude subscription Cursor, Continue, LiteLLM, anything.
162
+ Dario implements `/v1/chat/completions` any tool built for the OpenAI API works with your Claude subscription. No code changes needed.
163
163
 
164
164
  ```bash
165
- # Use with any OpenAI SDK or tool
165
+ dario proxy --model=opus
166
+
166
167
  export OPENAI_BASE_URL=http://localhost:3456/v1
167
168
  export OPENAI_API_KEY=dario
168
- ```
169
-
170
- ```python
171
- from openai import OpenAI
172
169
 
173
- client = OpenAI(base_url="http://localhost:3456/v1", api_key="dario")
174
- response = client.chat.completions.create(
175
- model="claude-opus-4-6", # or use "gpt-4" — auto-maps to Opus
176
- messages=[{"role": "user", "content": "Hello!"}]
177
- )
170
+ # Cursor, Continue, LiteLLM, any OpenAI SDK — all work
178
171
  ```
179
172
 
180
- Model mapping (automatic):
181
-
182
- | OpenAI model | Maps to |
183
- |---|---|
184
- | `gpt-4`, `gpt-4o`, `o1`, `o3` | `claude-opus-4-6` |
185
- | `o1-mini`, `o3-mini` | `claude-sonnet-4-6` |
186
- | `gpt-3.5-turbo`, `gpt-4o-mini` | `claude-haiku-4-5` |
187
- | Any `claude-*` model | Passed through directly |
188
-
189
- Streaming, system prompts, temperature, and stop sequences all translate automatically.
173
+ Use `--model=opus` to force the model regardless of what the client sends. Or pass `claude-opus-4-6` as the model name directly — Claude model names work as-is.
190
174
 
191
175
  ## Usage Examples
192
176
 
package/dist/cli.js CHANGED
@@ -13,9 +13,9 @@ import { readFile, unlink } from 'node:fs/promises';
13
13
  import { join } from 'node:path';
14
14
  import { homedir } from 'node:os';
15
15
  import { startAutoOAuthFlow, getStatus, refreshTokens } from './oauth.js';
16
- import { startProxy } from './proxy.js';
16
+ import { startProxy, sanitizeError } from './proxy.js';
17
17
  const args = process.argv.slice(2);
18
- const command = args[0] ?? (process.stdin.isTTY ? 'proxy' : 'proxy');
18
+ const command = args[0] ?? 'proxy';
19
19
  async function login() {
20
20
  console.log('');
21
21
  console.log(' dario — Claude Login');
@@ -50,7 +50,7 @@ async function login() {
50
50
  }
51
51
  catch (err) {
52
52
  console.error('');
53
- console.error(` Login failed: ${err instanceof Error ? err.message : err}`);
53
+ console.error(` Login failed: ${sanitizeError(err)}`);
54
54
  console.error(' Try again with `dario login`.');
55
55
  process.exit(1);
56
56
  }
@@ -89,7 +89,7 @@ async function refresh() {
89
89
  console.log(`[dario] Token refreshed. Expires in ${expiresIn} minutes.`);
90
90
  }
91
91
  catch (err) {
92
- console.error(`[dario] Refresh failed: ${err instanceof Error ? err.message : err}`);
92
+ console.error(`[dario] Refresh failed: ${sanitizeError(err)}`);
93
93
  process.exit(1);
94
94
  }
95
95
  }
@@ -172,7 +172,6 @@ if (!handler) {
172
172
  process.exit(1);
173
173
  }
174
174
  handler().catch(err => {
175
- const msg = err instanceof Error ? err.message : String(err);
176
- console.error('Fatal error:', msg.replace(/sk-ant-[a-zA-Z0-9_-]+/g, '[REDACTED]'));
175
+ console.error('Fatal error:', sanitizeError(err));
177
176
  process.exit(1);
178
177
  });
package/dist/index.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  */
7
7
  export { startAutoOAuthFlow, refreshTokens, getAccessToken, getStatus, loadCredentials } from './oauth.js';
8
8
  export type { OAuthTokens, CredentialsFile } from './oauth.js';
9
- export { startProxy } from './proxy.js';
9
+ export { startProxy, sanitizeError } from './proxy.js';
package/dist/index.js CHANGED
@@ -5,4 +5,4 @@
5
5
  * instead of running the CLI.
6
6
  */
7
7
  export { startAutoOAuthFlow, refreshTokens, getAccessToken, getStatus, loadCredentials } from './oauth.js';
8
- export { startProxy } from './proxy.js';
8
+ export { startProxy, sanitizeError } from './proxy.js';
package/dist/proxy.d.ts CHANGED
@@ -13,5 +13,6 @@ interface ProxyOptions {
13
13
  model?: string;
14
14
  cliBackend?: boolean;
15
15
  }
16
+ export declare function sanitizeError(err: unknown): string;
16
17
  export declare function startProxy(opts?: ProxyOptions): Promise<void>;
17
18
  export {};
package/dist/proxy.js CHANGED
@@ -8,7 +8,7 @@
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';
11
+ import { randomUUID, timingSafeEqual } from 'node:crypto';
12
12
  import { execSync, spawn } from 'node:child_process';
13
13
  import { arch, platform, version as nodeVersion } from 'node:process';
14
14
  import { getAccessToken, getStatus } from './oauth.js';
@@ -17,7 +17,6 @@ const DEFAULT_PORT = 3456;
17
17
  const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB — generous for large prompts, prevents abuse
18
18
  const UPSTREAM_TIMEOUT_MS = 300_000; // 5 min — matches Anthropic SDK default
19
19
  const LOCALHOST = '127.0.0.1';
20
- const CORS_ORIGIN = 'http://localhost';
21
20
  // Detect installed Claude Code version at startup
22
21
  function detectClaudeVersion() {
23
22
  try {
@@ -57,16 +56,20 @@ const MODEL_ALIASES = {
57
56
  };
58
57
  // OpenAI model name → Anthropic model name
59
58
  const OPENAI_MODEL_MAP = {
60
- 'gpt-4': 'claude-opus-4-6',
59
+ 'gpt-4.1': 'claude-opus-4-6',
60
+ 'gpt-4.1-mini': 'claude-sonnet-4-6',
61
+ 'gpt-4.1-nano': 'claude-haiku-4-5',
61
62
  'gpt-4o': 'claude-opus-4-6',
62
- 'gpt-4-turbo': 'claude-opus-4-6',
63
63
  'gpt-4o-mini': 'claude-haiku-4-5',
64
+ 'gpt-4-turbo': 'claude-opus-4-6',
65
+ 'gpt-4': 'claude-opus-4-6',
64
66
  'gpt-3.5-turbo': 'claude-haiku-4-5',
65
- 'o1': 'claude-opus-4-6',
66
- 'o1-mini': 'claude-sonnet-4-6',
67
- 'o1-preview': 'claude-opus-4-6',
68
67
  'o3': 'claude-opus-4-6',
69
68
  'o3-mini': 'claude-sonnet-4-6',
69
+ 'o4-mini': 'claude-sonnet-4-6',
70
+ 'o1': 'claude-opus-4-6',
71
+ 'o1-mini': 'claude-sonnet-4-6',
72
+ 'o1-pro': 'claude-opus-4-6',
70
73
  };
71
74
  /**
72
75
  * Translate OpenAI chat completion request → Anthropic Messages request.
@@ -177,10 +180,13 @@ function openaiModelsList() {
177
180
  })),
178
181
  };
179
182
  }
180
- function sanitizeError(err) {
183
+ export function sanitizeError(err) {
181
184
  const msg = err instanceof Error ? err.message : String(err);
182
- // Never leak tokens in error messages
183
- return msg.replace(/sk-ant-[a-zA-Z0-9_-]+/g, '[REDACTED]');
185
+ // Never leak tokens, JWTs, or bearer values in error messages
186
+ return msg
187
+ .replace(/sk-ant-[a-zA-Z0-9_-]+/g, '[REDACTED]')
188
+ .replace(/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g, '[REDACTED_JWT]')
189
+ .replace(/Bearer\s+[a-zA-Z0-9_-]+/gi, 'Bearer [REDACTED]');
184
190
  }
185
191
  /**
186
192
  * CLI Backend: route requests through `claude --print` instead of direct API.
@@ -286,11 +292,28 @@ export async function startProxy(opts = {}) {
286
292
  const useCli = opts.cliBackend ?? false;
287
293
  let requestCount = 0;
288
294
  let tokenCostEstimate = 0;
295
+ // Optional proxy authentication
296
+ const apiKey = process.env.DARIO_API_KEY;
297
+ const corsOrigin = `http://localhost:${port}`;
298
+ function checkAuth(req) {
299
+ if (!apiKey)
300
+ return true; // no key set = open access
301
+ const provided = req.headers['x-api-key']
302
+ || req.headers.authorization?.replace(/^Bearer\s+/i, '');
303
+ if (!provided)
304
+ return false;
305
+ try {
306
+ return timingSafeEqual(Buffer.from(provided), Buffer.from(apiKey));
307
+ }
308
+ catch {
309
+ return false;
310
+ }
311
+ }
289
312
  const server = createServer(async (req, res) => {
290
313
  // CORS preflight
291
314
  if (req.method === 'OPTIONS') {
292
315
  res.writeHead(204, {
293
- 'Access-Control-Allow-Origin': CORS_ORIGIN,
316
+ 'Access-Control-Allow-Origin': corsOrigin,
294
317
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
295
318
  'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta',
296
319
  'Access-Control-Max-Age': '86400',
@@ -312,6 +335,12 @@ export async function startProxy(opts = {}) {
312
335
  }));
313
336
  return;
314
337
  }
338
+ // Auth gate — everything below health requires auth when DARIO_API_KEY is set
339
+ if (!checkAuth(req)) {
340
+ res.writeHead(401, { 'Content-Type': 'application/json' });
341
+ res.end(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }));
342
+ return;
343
+ }
315
344
  // Status endpoint
316
345
  if (urlPath === '/status') {
317
346
  const s = await getStatus();
@@ -322,7 +351,7 @@ export async function startProxy(opts = {}) {
322
351
  // OpenAI-compatible models list
323
352
  if (urlPath === '/v1/models' && req.method === 'GET') {
324
353
  requestCount++;
325
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': CORS_ORIGIN });
354
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': corsOrigin });
326
355
  res.end(JSON.stringify(openaiModelsList()));
327
356
  return;
328
357
  }
@@ -368,7 +397,7 @@ export async function startProxy(opts = {}) {
368
397
  requestCount++;
369
398
  res.writeHead(cliResult.status, {
370
399
  'Content-Type': cliResult.contentType,
371
- 'Access-Control-Allow-Origin': CORS_ORIGIN,
400
+ 'Access-Control-Allow-Origin': corsOrigin,
372
401
  });
373
402
  res.end(cliResult.body);
374
403
  return;
@@ -447,7 +476,7 @@ export async function startProxy(opts = {}) {
447
476
  // Forward response headers
448
477
  const responseHeaders = {
449
478
  'Content-Type': contentType || 'application/json',
450
- 'Access-Control-Allow-Origin': CORS_ORIGIN,
479
+ 'Access-Control-Allow-Origin': corsOrigin,
451
480
  };
452
481
  // Forward rate limit headers (including unified subscription headers)
453
482
  for (const [key, value] of upstream.headers.entries()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
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": {