@askalf/dario 1.0.9 → 1.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/dist/cli.js CHANGED
@@ -132,9 +132,10 @@ async function proxy() {
132
132
  process.exit(1);
133
133
  }
134
134
  const verbose = args.includes('--verbose') || args.includes('-v');
135
+ const cliBackend = args.includes('--cli');
135
136
  const modelArg = args.find(a => a.startsWith('--model='));
136
137
  const model = modelArg ? modelArg.split('=')[1] : undefined;
137
- await startProxy({ port, verbose, model });
138
+ await startProxy({ port, verbose, model, cliBackend });
138
139
  }
139
140
  async function help() {
140
141
  console.log(`
@@ -151,6 +152,7 @@ async function help() {
151
152
  --model=MODEL Force a model for all requests
152
153
  Shortcuts: opus, sonnet, haiku
153
154
  Default: passthrough (client decides)
155
+ --cli Use Claude CLI as backend (bypasses rate limits)
154
156
  --port=PORT Port to listen on (default: 3456)
155
157
  --verbose, -v Log all requests
156
158
 
package/dist/proxy.d.ts CHANGED
@@ -11,6 +11,7 @@ interface ProxyOptions {
11
11
  port?: number;
12
12
  verbose?: boolean;
13
13
  model?: string;
14
+ cliBackend?: boolean;
14
15
  }
15
16
  export declare function startProxy(opts?: ProxyOptions): Promise<void>;
16
17
  export {};
package/dist/proxy.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { createServer } from 'node:http';
11
11
  import { randomUUID } from 'node:crypto';
12
- import { execSync } from 'node:child_process';
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';
15
15
  const ANTHROPIC_API = 'https://api.anthropic.com';
@@ -60,6 +60,95 @@ function sanitizeError(err) {
60
60
  // Never leak tokens in error messages
61
61
  return msg.replace(/sk-ant-[a-zA-Z0-9_-]+/g, '[REDACTED]');
62
62
  }
63
+ /**
64
+ * CLI Backend: route requests through `claude --print` instead of direct API.
65
+ * This bypasses rate limiting because Claude Code's binary has priority routing.
66
+ */
67
+ async function handleViaCli(body, model, verbose) {
68
+ try {
69
+ const parsed = JSON.parse(body.toString());
70
+ // Extract the last user message as the prompt
71
+ const messages = parsed.messages ?? [];
72
+ const lastUser = [...messages].reverse().find(m => m.role === 'user');
73
+ if (!lastUser) {
74
+ return { status: 400, body: JSON.stringify({ error: 'No user message' }), contentType: 'application/json' };
75
+ }
76
+ const effectiveModel = model ?? parsed.model ?? 'claude-opus-4-6';
77
+ const prompt = typeof lastUser.content === 'string'
78
+ ? lastUser.content
79
+ : JSON.stringify(lastUser.content);
80
+ // Build claude --print command
81
+ const args = ['--print', '--model', effectiveModel];
82
+ // Build system prompt from messages context
83
+ let systemPrompt = parsed.system ?? '';
84
+ // Include conversation history as context
85
+ const history = messages.slice(0, -1);
86
+ if (history.length > 0) {
87
+ const historyText = history.map(m => `${m.role}: ${typeof m.content === 'string' ? m.content : JSON.stringify(m.content)}`).join('\n');
88
+ systemPrompt = systemPrompt ? `${systemPrompt}\n\nConversation history:\n${historyText}` : `Conversation history:\n${historyText}`;
89
+ }
90
+ if (systemPrompt) {
91
+ args.push('--append-system-prompt', systemPrompt);
92
+ }
93
+ if (verbose) {
94
+ console.log(`[dario:cli] model=${effectiveModel} prompt=${prompt.substring(0, 60)}...`);
95
+ }
96
+ // Spawn claude --print
97
+ return new Promise((resolve) => {
98
+ const child = spawn('claude', args, {
99
+ stdio: ['pipe', 'pipe', 'pipe'],
100
+ timeout: 300_000,
101
+ });
102
+ let stdout = '';
103
+ let stderr = '';
104
+ child.stdout.on('data', (d) => { stdout += d.toString(); });
105
+ child.stderr.on('data', (d) => { stderr += d.toString(); });
106
+ child.stdin.write(prompt);
107
+ child.stdin.end();
108
+ child.on('close', (code) => {
109
+ if (code !== 0 || !stdout.trim()) {
110
+ resolve({
111
+ status: 502,
112
+ body: JSON.stringify({ type: 'error', error: { type: 'api_error', message: stderr.substring(0, 200) || 'CLI backend failed' } }),
113
+ contentType: 'application/json',
114
+ });
115
+ return;
116
+ }
117
+ // Build a proper Messages API response
118
+ const text = stdout.trim();
119
+ const estimatedTokens = Math.ceil(text.length / 4);
120
+ const response = {
121
+ id: `msg_${randomUUID().replace(/-/g, '').substring(0, 24)}`,
122
+ type: 'message',
123
+ role: 'assistant',
124
+ model: effectiveModel,
125
+ content: [{ type: 'text', text }],
126
+ stop_reason: 'end_turn',
127
+ stop_sequence: null,
128
+ usage: {
129
+ input_tokens: Math.ceil(prompt.length / 4),
130
+ output_tokens: estimatedTokens,
131
+ },
132
+ };
133
+ resolve({ status: 200, body: JSON.stringify(response), contentType: 'application/json' });
134
+ });
135
+ child.on('error', (err) => {
136
+ resolve({
137
+ status: 502,
138
+ body: JSON.stringify({ type: 'error', error: { type: 'api_error', message: 'Claude CLI not found. Install Claude Code first.' } }),
139
+ contentType: 'application/json',
140
+ });
141
+ });
142
+ });
143
+ }
144
+ catch (err) {
145
+ return {
146
+ status: 400,
147
+ body: JSON.stringify({ type: 'error', error: { type: 'invalid_request_error', message: 'Invalid request body' } }),
148
+ contentType: 'application/json',
149
+ };
150
+ }
151
+ }
63
152
  export async function startProxy(opts = {}) {
64
153
  const port = opts.port ?? DEFAULT_PORT;
65
154
  const verbose = opts.verbose ?? false;
@@ -72,6 +161,7 @@ export async function startProxy(opts = {}) {
72
161
  const cliVersion = detectClaudeVersion();
73
162
  const sdkVersion = detectSdkVersion();
74
163
  const modelOverride = opts.model ? (MODEL_ALIASES[opts.model] ?? opts.model) : null;
164
+ const useCli = opts.cliBackend ?? false;
75
165
  let requestCount = 0;
76
166
  let tokenCostEstimate = 0;
77
167
  const server = createServer(async (req, res) => {
@@ -141,6 +231,17 @@ export async function startProxy(opts = {}) {
141
231
  chunks.push(buf);
142
232
  }
143
233
  const body = Buffer.concat(chunks);
234
+ // CLI backend mode: route through claude --print
235
+ if (useCli && rawPath === '/v1/messages' && req.method === 'POST' && body.length > 0) {
236
+ const cliResult = await handleViaCli(body, modelOverride, verbose);
237
+ requestCount++;
238
+ res.writeHead(cliResult.status, {
239
+ 'Content-Type': cliResult.contentType,
240
+ 'Access-Control-Allow-Origin': CORS_ORIGIN,
241
+ });
242
+ res.end(cliResult.body);
243
+ return;
244
+ }
144
245
  // Override model in request body if --model flag was set
145
246
  let finalBody = body.length > 0 ? body : undefined;
146
247
  if (modelOverride && body.length > 0) {
@@ -266,7 +367,7 @@ export async function startProxy(opts = {}) {
266
367
  process.exit(1);
267
368
  });
268
369
  server.listen(port, LOCALHOST, () => {
269
- const oauthLine = `OAuth: ${status.status} (expires in ${status.expiresIn})`;
370
+ const oauthLine = useCli ? 'Backend: Claude CLI (bypasses rate limits)' : `OAuth: ${status.status} (expires in ${status.expiresIn})`;
270
371
  const modelLine = modelOverride ? `Model: ${modelOverride} (all requests)` : 'Model: passthrough (client decides)';
271
372
  console.log('');
272
373
  console.log(` dario — http://localhost:${port}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "1.0.9",
3
+ "version": "1.1.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": {