@askalf/dario 3.4.0 → 3.4.3
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 +158 -139
- package/dist/cc-oauth-detect.d.ts +30 -14
- package/dist/cc-oauth-detect.js +62 -72
- package/dist/cc-template-data.json +1 -1
- package/dist/cc-template.d.ts +4 -4
- package/dist/cc-template.js +66 -31
- package/dist/cli.js +19 -5
- package/dist/proxy.d.ts +1 -1
- package/dist/proxy.js +72 -292
- package/package.json +4 -2
package/dist/cc-oauth-detect.js
CHANGED
|
@@ -5,20 +5,32 @@
|
|
|
5
5
|
* (client_id, authorize URL, token URL, scopes). Eliminates the need to
|
|
6
6
|
* hardcode values that Anthropic rotates between CC releases.
|
|
7
7
|
*
|
|
8
|
-
* CC ships
|
|
8
|
+
* CC ships three OAuth config factories in one binary (dev/staging/prod),
|
|
9
|
+
* selected at runtime by an environment switch that is hardcoded to "prod"
|
|
10
|
+
* in shipped builds. Only the PROD block is live; "local" and "staging"
|
|
11
|
+
* are dead code paths.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* PROD block (the one we want):
|
|
14
|
+
* BASE_API_URL: "https://api.anthropic.com"
|
|
15
|
+
* CLAUDE_AI_AUTHORIZE_URL: "https://claude.com/cai/oauth/authorize"
|
|
16
|
+
* TOKEN_URL: "https://platform.claude.com/v1/oauth/token"
|
|
17
|
+
* CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
|
|
18
|
+
* OAUTH_FILE_SUFFIX: ""
|
|
13
19
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
20
|
+
* LOCAL block (dead code in shipped builds — CC pointing at localhost:8000
|
|
21
|
+
* etc. as its own dev stack, NOT about "client uses a localhost callback"):
|
|
22
|
+
* BASE_API_URL: "http://localhost:8000"
|
|
23
|
+
* CLIENT_ID: "22422756-60c9-4084-8eb7-27705fd5cf9a"
|
|
24
|
+
* OAUTH_FILE_SUFFIX: "-local-oauth"
|
|
17
25
|
*
|
|
18
|
-
*
|
|
26
|
+
* Dario uses CC's own automatic OAuth flow — the prod client is registered
|
|
27
|
+
* with `http://localhost:${port}/callback` exactly as dario sends. (The
|
|
28
|
+
* "MANUAL_REDIRECT_URL" on platform.claude.com is only used when dario's
|
|
29
|
+
* local HTTP server can't bind a port; dario never hits that path.)
|
|
19
30
|
*
|
|
20
|
-
* Results are cached per-binary-hash at ~/.dario/cc-oauth-cache.json so
|
|
21
|
-
* startup only re-scans when the user upgrades Claude Code.
|
|
31
|
+
* Results are cached per-binary-hash at ~/.dario/cc-oauth-cache-v2.json so
|
|
32
|
+
* startup only re-scans when the user upgrades Claude Code. The -v2 suffix
|
|
33
|
+
* invalidates the v3.4.0-v3.4.2 caches that held the wrong (dev) client_id.
|
|
22
34
|
*/
|
|
23
35
|
import { readFile, writeFile, mkdir, stat, open as openFile } from 'node:fs/promises';
|
|
24
36
|
import { existsSync } from 'node:fs';
|
|
@@ -26,15 +38,18 @@ import { homedir, platform } from 'node:os';
|
|
|
26
38
|
import { join, dirname } from 'node:path';
|
|
27
39
|
import { createHash } from 'node:crypto';
|
|
28
40
|
// Last-resort fallback if CC binary can't be found or scanned.
|
|
29
|
-
// These values are the
|
|
41
|
+
// These values are the CC v2.1.104 PROD OAuth config, extracted from
|
|
42
|
+
// the `nh$` object in the shipped binary.
|
|
30
43
|
const FALLBACK = {
|
|
31
|
-
clientId: '
|
|
44
|
+
clientId: '9d1c250a-e61b-44d9-88ed-5944d1962f5e',
|
|
32
45
|
authorizeUrl: 'https://claude.com/cai/oauth/authorize',
|
|
33
46
|
tokenUrl: 'https://platform.claude.com/v1/oauth/token',
|
|
34
|
-
scopes: 'user:profile user:inference user:sessions:claude_code user:mcp_servers',
|
|
47
|
+
scopes: 'user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload',
|
|
35
48
|
source: 'fallback',
|
|
36
49
|
};
|
|
37
|
-
|
|
50
|
+
// -v2 suffix invalidates the v3.4.0-v3.4.2 cache that pinned the wrong
|
|
51
|
+
// (dev) client_id extracted from the dead-code -local-oauth block.
|
|
52
|
+
const CACHE_PATH = join(homedir(), '.dario', 'cc-oauth-cache-v2.json');
|
|
38
53
|
function candidatePaths() {
|
|
39
54
|
const home = homedir();
|
|
40
55
|
if (platform() === 'win32') {
|
|
@@ -88,72 +103,47 @@ async function fingerprintBinary(path) {
|
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
/**
|
|
91
|
-
* Scan binary bytes for the
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
106
|
+
* Scan binary bytes for the PROD OAuth config block.
|
|
107
|
+
*
|
|
108
|
+
* Anchors on `BASE_API_URL:"https://api.anthropic.com"` — this literal
|
|
109
|
+
* only appears inside the prod config object (`nh$`). The LOCAL-dev block
|
|
110
|
+
* uses `http://localhost:8000` for the same key, and there's no staging
|
|
111
|
+
* block present in shipped builds. Once we find the anchor, the CLIENT_ID,
|
|
112
|
+
* CLAUDE_AI_AUTHORIZE_URL, TOKEN_URL, and scopes all live within a ~1.5KB
|
|
113
|
+
* window after it.
|
|
95
114
|
*/
|
|
96
115
|
export function scanBinaryForOAuthConfig(buf) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// just `-local-oauth` bytes, but the config object serializes as
|
|
100
|
-
// `OAUTH_FILE_SUFFIX:"-local-oauth"` with the key+quote prefix, which is
|
|
101
|
-
// stable across minified CC builds.
|
|
102
|
-
const anchor = Buffer.from('OAUTH_FILE_SUFFIX:"-local-oauth"');
|
|
103
|
-
let anchorIdx = buf.indexOf(anchor);
|
|
104
|
-
// Fallback anchor — some builds may tokenize differently.
|
|
105
|
-
if (anchorIdx === -1) {
|
|
106
|
-
const looseAnchor = Buffer.from('"-local-oauth"');
|
|
107
|
-
anchorIdx = buf.indexOf(looseAnchor);
|
|
108
|
-
}
|
|
116
|
+
const anchor = Buffer.from('BASE_API_URL:"https://api.anthropic.com"');
|
|
117
|
+
const anchorIdx = buf.indexOf(anchor);
|
|
109
118
|
if (anchorIdx === -1)
|
|
110
119
|
return null;
|
|
111
|
-
// The
|
|
112
|
-
//
|
|
113
|
-
const windowStart =
|
|
114
|
-
const windowEnd = Math.min(buf.length, anchorIdx +
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (!lastCid)
|
|
120
|
+
// The prod config object is laid out roughly as one line of minified JS.
|
|
121
|
+
// Take a generous window to be safe across minifier differences.
|
|
122
|
+
const windowStart = anchorIdx;
|
|
123
|
+
const windowEnd = Math.min(buf.length, anchorIdx + 2048);
|
|
124
|
+
const prodBlock = buf.slice(windowStart, windowEnd).toString('latin1');
|
|
125
|
+
const cidMatch = /CLIENT_ID\s*:\s*"([0-9a-f-]{36})"/i.exec(prodBlock);
|
|
126
|
+
if (!cidMatch || !cidMatch[1])
|
|
127
|
+
return null;
|
|
128
|
+
const clientId = cidMatch[1];
|
|
129
|
+
// Defensive: if we somehow matched the dev client_id, reject — the
|
|
130
|
+
// anchor should have put us in the prod block, but this guards against
|
|
131
|
+
// the block being laid out in an unexpected order across builds.
|
|
132
|
+
if (clientId === '22422756-60c9-4084-8eb7-27705fd5cf9a')
|
|
125
133
|
return null;
|
|
126
|
-
const clientId = lastCid;
|
|
127
|
-
// Authorize URL: CLAUDE_AI_AUTHORIZE_URL appears once in the binary.
|
|
128
|
-
const authAnchor = Buffer.from('CLAUDE_AI_AUTHORIZE_URL');
|
|
129
|
-
const authIdx = buf.indexOf(authAnchor);
|
|
130
134
|
let authorizeUrl = FALLBACK.authorizeUrl;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (m && m[1])
|
|
135
|
-
authorizeUrl = m[1];
|
|
136
|
-
}
|
|
137
|
-
// Token URL: TOKEN_URL — look for the one under platform.claude.com/.../oauth/token
|
|
138
|
-
const tokenAnchor = Buffer.from('TOKEN_URL');
|
|
139
|
-
let searchFrom = 0;
|
|
135
|
+
const authMatch = /CLAUDE_AI_AUTHORIZE_URL\s*:\s*"([^"]+)"/.exec(prodBlock);
|
|
136
|
+
if (authMatch && authMatch[1])
|
|
137
|
+
authorizeUrl = authMatch[1];
|
|
140
138
|
let tokenUrl = FALLBACK.tokenUrl;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (m && m[1]) {
|
|
148
|
-
tokenUrl = m[1];
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
searchFrom = idx + tokenAnchor.length;
|
|
152
|
-
}
|
|
153
|
-
// Scopes: contiguous quoted string of "user:X user:Y user:Z ..."
|
|
154
|
-
// Search for an anchor like "user:profile " which is the first scope.
|
|
155
|
-
const scopeAnchor = Buffer.from('"user:profile ');
|
|
139
|
+
const tokenMatch = /TOKEN_URL\s*:\s*"(https:\/\/[^"]*\/oauth\/token[^"]*)"/.exec(prodBlock);
|
|
140
|
+
if (tokenMatch && tokenMatch[1])
|
|
141
|
+
tokenUrl = tokenMatch[1];
|
|
142
|
+
// Scopes live in a separate array-of-strings block elsewhere in the
|
|
143
|
+
// binary, not inside the prod config object itself. Search globally
|
|
144
|
+
// for the first quoted `user:profile ...` run.
|
|
156
145
|
let scopes = FALLBACK.scopes;
|
|
146
|
+
const scopeAnchor = Buffer.from('"user:profile ');
|
|
157
147
|
const scopeIdx = buf.indexOf(scopeAnchor);
|
|
158
148
|
if (scopeIdx !== -1) {
|
|
159
149
|
const w = buf.slice(scopeIdx, Math.min(buf.length, scopeIdx + 512)).toString('latin1');
|
package/dist/cc-template.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude Code request template
|
|
2
|
+
* Claude Code request template.
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions, system prompt, and request structure are loaded from
|
|
5
|
-
* cc-template-data.json
|
|
6
|
-
*
|
|
5
|
+
* cc-template-data.json and sent verbatim — this gives byte-level fidelity
|
|
6
|
+
* with the shape of a real Claude Code request.
|
|
7
7
|
*/
|
|
8
|
-
/** CC's exact tool definitions — loaded from
|
|
8
|
+
/** CC's exact tool definitions — loaded from the template JSON. */
|
|
9
9
|
export declare const CC_TOOL_DEFINITIONS: {
|
|
10
10
|
name: string;
|
|
11
11
|
description: string;
|
package/dist/cc-template.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude Code request template
|
|
2
|
+
* Claude Code request template.
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions, system prompt, and request structure are loaded from
|
|
5
|
-
* cc-template-data.json
|
|
6
|
-
*
|
|
5
|
+
* cc-template-data.json and sent verbatim — this gives byte-level fidelity
|
|
6
|
+
* with the shape of a real Claude Code request.
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync } from 'node:fs';
|
|
9
9
|
import { join, dirname } from 'node:path';
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
// Load template data at module init — fail fast if missing
|
|
13
13
|
const TEMPLATE = JSON.parse(readFileSync(join(__dirname, 'cc-template-data.json'), 'utf-8'));
|
|
14
|
-
/** CC's exact tool definitions — loaded from
|
|
14
|
+
/** CC's exact tool definitions — loaded from the template JSON. */
|
|
15
15
|
export const CC_TOOL_DEFINITIONS = TEMPLATE.tools;
|
|
16
16
|
/** CC's static system prompt (~25KB). */
|
|
17
17
|
export const CC_SYSTEM_PROMPT = TEMPLATE.system_prompt;
|
|
@@ -46,6 +46,16 @@ const TOOL_MAP = {
|
|
|
46
46
|
browse: { ccTool: 'WebFetch', translateArgs: (a) => ({ url: a.url || '' }) },
|
|
47
47
|
notebook: { ccTool: 'NotebookEdit' },
|
|
48
48
|
notebook_edit: { ccTool: 'NotebookEdit' },
|
|
49
|
+
// Additional client tool mappings
|
|
50
|
+
browser: { ccTool: 'WebFetch', translateArgs: (a) => ({ url: String(a.url || '') }) },
|
|
51
|
+
message: { ccTool: 'AskUserQuestion', translateArgs: (a) => ({ question: String(a.message || a.content || '') }) },
|
|
52
|
+
todo_read: { ccTool: 'TodoWrite', translateArgs: () => ({ todos: [] }) },
|
|
53
|
+
todo_write: { ccTool: 'TodoWrite', translateArgs: (a) => ({ todos: a.todos || [] }) },
|
|
54
|
+
notebook_read: { ccTool: 'NotebookEdit', translateArgs: (a) => ({ notebook_path: String(a.notebook_path || a.path || '') }) },
|
|
55
|
+
enter_plan_mode: { ccTool: 'EnterPlanMode' },
|
|
56
|
+
exit_plan_mode: { ccTool: 'ExitPlanMode' },
|
|
57
|
+
enter_worktree: { ccTool: 'EnterWorktree', translateArgs: (a) => ({ path: a.path }) },
|
|
58
|
+
exit_worktree: { ccTool: 'ExitWorktree' },
|
|
49
59
|
};
|
|
50
60
|
/**
|
|
51
61
|
* Build a CC-template request from a client request.
|
|
@@ -79,34 +89,47 @@ export function buildCCRequest(clientBody, billingTag, cache1h, identity, opts =
|
|
|
79
89
|
const activeToolMap = new Map();
|
|
80
90
|
const unmappedTools = [];
|
|
81
91
|
if (clientTools && !opts.preserveTools) {
|
|
92
|
+
// Two passes so the unmapped-tool distributor can avoid colliding with
|
|
93
|
+
// CC tools the client already uses directly. Without this, a client
|
|
94
|
+
// sending both `WebSearch` and some unmapped tool like `memory_get`
|
|
95
|
+
// could have both forward-map to `WebSearch`, and the reverse map would
|
|
96
|
+
// then rewrite real `WebSearch` responses to the collided client name.
|
|
97
|
+
const claimedCC = new Set();
|
|
82
98
|
for (const tool of clientTools) {
|
|
83
99
|
const name = (tool.name || '').toLowerCase();
|
|
84
100
|
const mapping = TOOL_MAP[name];
|
|
85
101
|
if (mapping) {
|
|
86
102
|
activeToolMap.set(tool.name, mapping);
|
|
103
|
+
claimedCC.add(mapping.ccTool);
|
|
87
104
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
}
|
|
106
|
+
const CC_FALLBACK_TOOLS = ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'];
|
|
107
|
+
for (const tool of clientTools) {
|
|
108
|
+
const name = (tool.name || '').toLowerCase();
|
|
109
|
+
if (TOOL_MAP[name])
|
|
110
|
+
continue;
|
|
111
|
+
unmappedTools.push(tool.name);
|
|
112
|
+
// Exclude CC tools the client already uses so we never create a
|
|
113
|
+
// two-client-names-to-one-CC-tool collision. If every fallback is
|
|
114
|
+
// claimed (rare: client already uses 6+ CC tools), fall back to the
|
|
115
|
+
// full pool and accept the ambiguity.
|
|
116
|
+
const pool = CC_FALLBACK_TOOLS.filter(t => !claimedCC.has(t));
|
|
117
|
+
const fallbackPool = pool.length > 0 ? pool : CC_FALLBACK_TOOLS;
|
|
118
|
+
const fallbackTool = fallbackPool[(unmappedTools.length - 1) % fallbackPool.length];
|
|
119
|
+
activeToolMap.set(tool.name, {
|
|
120
|
+
ccTool: fallbackTool,
|
|
121
|
+
translateArgs: (a) => {
|
|
122
|
+
switch (fallbackTool) {
|
|
123
|
+
case 'Bash': return { command: `echo "${JSON.stringify(a).slice(0, 200)}"` };
|
|
124
|
+
case 'Read': return { file_path: String(a.path || a.file || a.url || '/tmp/output') };
|
|
125
|
+
case 'Grep': return { pattern: String(a.query || a.pattern || a.search || '.'), path: '.' };
|
|
126
|
+
case 'Glob': return { pattern: String(a.pattern || a.glob || '*') };
|
|
127
|
+
case 'WebSearch': return { query: String(a.query || a.q || a.search || '') };
|
|
128
|
+
case 'WebFetch': return { url: String(a.url || a.uri || '') };
|
|
129
|
+
default: return a;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
});
|
|
110
133
|
}
|
|
111
134
|
}
|
|
112
135
|
// ── Remap tool_use and tool_result references in message history ──
|
|
@@ -182,7 +205,7 @@ export function buildCCRequest(clientBody, billingTag, cache1h, identity, opts =
|
|
|
182
205
|
systemText = systemText.replace(pattern, '');
|
|
183
206
|
}
|
|
184
207
|
// ── Build the CC request from template ──
|
|
185
|
-
// Key order matches CC v2.1.104
|
|
208
|
+
// Key order matches CC v2.1.104 exactly:
|
|
186
209
|
// model, messages, system, tools, metadata, max_tokens, thinking, context_management, output_config, stream
|
|
187
210
|
//
|
|
188
211
|
// System prompt structure (3 blocks, matching real CC):
|
|
@@ -232,14 +255,26 @@ export function reverseMapResponse(responseBody, toolMap) {
|
|
|
232
255
|
if (toolMap.size === 0)
|
|
233
256
|
return responseBody;
|
|
234
257
|
let result = responseBody;
|
|
235
|
-
// Build reverse map: CC tool name → original client tool name
|
|
258
|
+
// Build reverse map: CC tool name → original client tool name.
|
|
259
|
+
// Two passes so identity mappings (client sent a tool with the real CC
|
|
260
|
+
// name) claim their CC slot first and can never be overwritten by a
|
|
261
|
+
// non-identity entry. Without this, a collision between a direct
|
|
262
|
+
// `WebSearch` and an unmapped-tool fallback landing on `WebSearch` could
|
|
263
|
+
// rewrite the real search response to the wrong client name.
|
|
236
264
|
const reverseMap = new Map();
|
|
265
|
+
const identityClaimed = new Set();
|
|
237
266
|
for (const [clientName, mapping] of toolMap) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
reverseMap.set(mapping.ccTool, clientName);
|
|
267
|
+
if (clientName.toLowerCase() === mapping.ccTool.toLowerCase()) {
|
|
268
|
+
identityClaimed.add(mapping.ccTool);
|
|
241
269
|
}
|
|
242
270
|
}
|
|
271
|
+
for (const [clientName, mapping] of toolMap) {
|
|
272
|
+
if (clientName.toLowerCase() === mapping.ccTool.toLowerCase())
|
|
273
|
+
continue;
|
|
274
|
+
if (identityClaimed.has(mapping.ccTool))
|
|
275
|
+
continue;
|
|
276
|
+
reverseMap.set(mapping.ccTool, clientName);
|
|
277
|
+
}
|
|
243
278
|
for (const [ccName, clientName] of reverseMap) {
|
|
244
279
|
result = result.replace(new RegExp(`"name"\\s*:\\s*"${ccName}"`, 'g'), `"name":"${clientName}"`);
|
|
245
280
|
}
|
package/dist/cli.js
CHANGED
|
@@ -125,13 +125,22 @@ async function proxy() {
|
|
|
125
125
|
console.error('[dario] Invalid port. Must be 1-65535.');
|
|
126
126
|
process.exit(1);
|
|
127
127
|
}
|
|
128
|
+
// Bind address — accepts --host=<addr>; falls through to DARIO_HOST env
|
|
129
|
+
// var or the default of 127.0.0.1 inside startProxy. The sanity check
|
|
130
|
+
// here only rejects obviously bad shapes; real address validation
|
|
131
|
+
// happens when the OS tries to bind.
|
|
132
|
+
const hostArg = args.find(a => a.startsWith('--host='));
|
|
133
|
+
const host = hostArg ? hostArg.split('=')[1] : undefined;
|
|
134
|
+
if (host !== undefined && !/^[a-zA-Z0-9._:-]+$/.test(host)) {
|
|
135
|
+
console.error('[dario] Invalid --host. Must be an IP address or hostname.');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
128
138
|
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
129
|
-
const cliBackend = args.includes('--cli');
|
|
130
139
|
const passthrough = args.includes('--passthrough') || args.includes('--thin');
|
|
131
140
|
const preserveTools = args.includes('--preserve-tools') || args.includes('--keep-tools');
|
|
132
141
|
const modelArg = args.find(a => a.startsWith('--model='));
|
|
133
142
|
const model = modelArg ? modelArg.split('=')[1] : undefined;
|
|
134
|
-
await startProxy({ port, verbose, model,
|
|
143
|
+
await startProxy({ port, host, verbose, model, passthrough, preserveTools });
|
|
135
144
|
}
|
|
136
145
|
async function help() {
|
|
137
146
|
console.log(`
|
|
@@ -149,15 +158,20 @@ async function help() {
|
|
|
149
158
|
Shortcuts: opus, sonnet, haiku
|
|
150
159
|
Full IDs: claude-opus-4-6, claude-sonnet-4-6
|
|
151
160
|
Default: passthrough (client decides)
|
|
152
|
-
--
|
|
153
|
-
--passthrough Thin proxy — OAuth swap only, no injection
|
|
161
|
+
--passthrough, --thin Thin proxy — OAuth swap only, no injection
|
|
154
162
|
--preserve-tools Keep client tool schemas (for agents with custom tools)
|
|
155
163
|
--port=PORT Port to listen on (default: 3456)
|
|
164
|
+
--host=ADDRESS Address to bind to (default: 127.0.0.1)
|
|
165
|
+
Use 0.0.0.0 to accept connections from other machines.
|
|
166
|
+
Alternatively set DARIO_HOST env var.
|
|
167
|
+
When binding non-loopback, also set DARIO_API_KEY
|
|
168
|
+
so unauthenticated LAN hosts can't proxy through
|
|
169
|
+
your OAuth subscription.
|
|
156
170
|
--verbose, -v Log all requests
|
|
157
171
|
|
|
158
172
|
Quick start:
|
|
159
173
|
dario login # auto-detects Claude Code credentials
|
|
160
|
-
dario proxy
|
|
174
|
+
dario proxy --model=opus # or: dario proxy --passthrough
|
|
161
175
|
|
|
162
176
|
Then point any Anthropic SDK at http://localhost:3456:
|
|
163
177
|
export ANTHROPIC_BASE_URL=http://localhost:3456
|