@askalf/dario 3.31.21 → 3.32.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 +8 -2
- package/dist/cc-oauth-detect.js +10 -0
- package/dist/cc-template.d.ts +25 -0
- package/dist/cc-template.js +96 -8
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +269 -3
- package/dist/doctor.js +69 -0
- package/dist/mcp/tools.d.ts +28 -0
- package/dist/mcp/tools.js +104 -0
- package/dist/proxy.d.ts +67 -0
- package/dist/proxy.js +191 -10
- package/dist/runtime-fingerprint.d.ts +26 -0
- package/dist/runtime-fingerprint.js +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -281,7 +281,9 @@ Dario's built-in `TOOL_MAP` carries **~66 schema-verified entries** covering the
|
|
|
281
281
|
| OpenClaw | `exec`, `process`, `web_search`, `web_fetch`, `browser`, `message` |
|
|
282
282
|
| Hermes Agent (Nous Research) | `terminal`, `process`, `read_file`, `write_file`, `patch`, `search_files`, `web_search`, `web_extract`, `todo` mapped directly. Hermes-specific tools (`browser_*`, `vision_analyze`, `image_generate`, `skill_*`, `memory`, `session_search`, `cronjob`, `send_message`, `ha_*`, `mixture_of_agents`, `delegate_task`, `execute_code`, `text_to_speech`) have no CC equivalent and auto-preserve through the identity detector (`You are Hermes Agent` or `created by Nous Research` in the system prompt flips dario into preserve-tools for Hermes sessions automatically — v3.30.13). Also consider `--max-tokens=client` so Hermes's 64k/128k per-model caps survive dario's outbound pin. |
|
|
283
283
|
|
|
284
|
-
Text-tool clients (Cline / Kilo Code / Roo Code and forks) are auto-detected via system-prompt identity markers and automatically flipped into preserve-tools mode, because mixing CC's `tools` array with their XML protocol makes the model emit `<function_calls><invoke>` that their parsers can't read. If you run dario specifically for wire-level fidelity and would rather pick `--preserve-tools` yourself, `--no-auto-detect` (v3.20.1, aka `--no-auto-preserve`) disables the heuristic — explicit operator choice then wins.
|
|
284
|
+
Text-tool clients (Cline / Kilo Code / Roo Code and forks) are auto-detected via system-prompt identity markers and automatically flipped into preserve-tools mode, because mixing CC's `tools` array with their XML protocol makes the model emit `<function_calls><invoke>` that their parsers can't read. The same identity path also catches `arnie` (askalf's portable IT-troubleshooting CLI) — its tool names overlap with `TOOL_MAP` but its schemas diverge, so identity match → preserve-tools is the only correct routing. If you run dario specifically for wire-level fidelity and would rather pick `--preserve-tools` yourself, `--no-auto-detect` (v3.20.1, aka `--no-auto-preserve`) disables the heuristic — explicit operator choice then wins.
|
|
285
|
+
|
|
286
|
+
Beyond the identity path, dario falls back to a **structural** check: when a request carries 3+ tools and ≥80% of them aren't in `TOOL_MAP`, that's a custom client whose tool surface has effectively no overlap with CC's, and round-robin remap onto CC fallback slots silently corrupts the calls. The structural fallback flips those requests to preserve-tools too, with `client: 'unknown-non-cc'` in the request log. This catches in-house agents and OpenClaw derivatives that we haven't added an explicit pattern for, without needing per-client maintenance. `--no-auto-detect` disables both paths.
|
|
285
287
|
|
|
286
288
|
If your agent's tool names aren't pre-mapped and its tools carry fields CC's schema doesn't have, there are two escape hatches: **`--preserve-tools`** (forward your schema verbatim, lose the CC wire shape) or **`--hybrid-tools`** (keep the CC wire shape, fill request-context fields from headers). See [Custom tool schemas](#custom-tool-schemas).
|
|
287
289
|
|
|
@@ -336,7 +338,8 @@ A version marker (`<!-- dario-sub-agent-version: X -->`) embedded in the markdow
|
|
|
336
338
|
|---|---|
|
|
337
339
|
| `dario login [--manual]` | Log in to the Claude backend. Detects CC credentials or runs its own OAuth flow. `--manual` (v3.20) mirrors CC's code-paste flow for SSH / container setups without a browser. |
|
|
338
340
|
| `dario proxy` | Start the local API proxy on port 3456 |
|
|
339
|
-
| `dario doctor [--probe] [--auth-check] [--json]` | Aggregated health report — dario / Node / runtime-TLS / CC binary + compat / template + drift / OAuth / pool / backends / sub-agent. `--probe` (v3.31.7) hits the live `claude.ai/oauth/authorize` endpoint and surfaces the verdict, so scope-policy drift is catchable from a user's machine (not just CI). `--auth-check` (v3.31.9) opens a one-shot `x-api-key` listener and classifies whatever a client actually sends (match / mismatch / no-auth / timeout), with only redacted previews in output. `--json` (v3.31.8) emits structured output for claude-bridge's `/status`, deepdive's health probes, and CI scrapers. |
|
|
341
|
+
| `dario doctor [--probe] [--auth-check] [--json] [--bun-bootstrap]` | Aggregated health report — dario / Node / runtime-TLS / CC binary + compat / template + drift / **per-request overhead** / OAuth / pool + **pool routing** (next account in rotation when 2+ loaded) / backends / sub-agent. `--probe` (v3.31.7) hits the live `claude.ai/oauth/authorize` endpoint and surfaces the verdict, so scope-policy drift is catchable from a user's machine (not just CI). `--auth-check` (v3.31.9) opens a one-shot `x-api-key` listener and classifies whatever a client actually sends (match / mismatch / no-auth / timeout), with only redacted previews in output. `--json` (v3.31.8) emits structured output for claude-bridge's `/status`, deepdive's health probes, and CI scrapers. `--bun-bootstrap` runs the canonical bun.sh installer when the runtime/TLS check is warning that Bun isn't on PATH. |
|
|
342
|
+
| `dario usage [--port=N] [--json]` | Burn-rate summary of the running proxy's traffic over the last 60 minutes: requests, input/output tokens, avg latency, error rate, subscription % vs. extra-usage, estimated API-equivalent cost, plus per-account breakdown when pool mode is active. Hits `/analytics` on the local proxy. When the proxy isn't reachable, prints a hint pointing at `dario doctor --usage` (the one-off rate-limit probe). `--json` emits the raw `/analytics` payload for status bars / CI dashboards. Also exposed as the `usage` tool in `dario mcp`. |
|
|
340
343
|
| `dario config [--json]` | Prints the effective dario configuration with credentials redacted. Complementary to `doctor` — doctor answers *is it working?*, config answers *what IS it?* (v3.31.10) |
|
|
341
344
|
| `dario upgrade` | Safe wrapper over `npm install -g @askalf/dario@latest` — probes npm for the `@latest` version first (3s timeout, 60s cache), refuses to run if already on latest, fails with a clear hint if npm is missing. (v3.31.10) |
|
|
342
345
|
| `dario status` | Show Claude backend OAuth token health and expiry |
|
|
@@ -357,11 +360,14 @@ A version marker (`<!-- dario-sub-agent-version: X -->`) embedded in the markdow
|
|
|
357
360
|
| `--preserve-tools` / `--keep-tools` | Keep client tool schemas instead of remapping to CC's. Required for clients whose tools have fields CC doesn't — see [Custom tool schemas](#custom-tool-schemas). Auto-enabled for Cline / Kilo Code / Roo Code and forks (detected via system-prompt identity markers). | off (auto for text-tool clients) |
|
|
358
361
|
| `--no-auto-detect` / `--no-auto-preserve` | Disable the text-tool-client detector so the CC wire shape stays intact on Cline/Kilo/Roo prompts (v3.20.1, dario#40). Explicit `--preserve-tools` still wins. | off |
|
|
359
362
|
| `--hybrid-tools` / `--context-inject` | Remap to CC tools **and** inject request-context values (`sessionId`, `requestId`, `channelId`, `userId`, `timestamp`) into client-declared fields CC's schema doesn't carry. See [Hybrid tool mode](#hybrid-tool-mode). | off |
|
|
363
|
+
| `--merge-tools` / `--append-tools` | **EXPERIMENTAL.** Send CC's canonical tools first, append the client's custom tools after (deduped by name, case-insensitive). Model can call either side; tool calls flow back unchanged. Mutually exclusive with `--preserve-tools` and `--hybrid-tools`. Anthropic's billing classifier may flip routing on the appended suffix — validate with `--verbose` and watch the `billing: <bucket>` line on the first 1-2 requests before relying on it. | off |
|
|
360
364
|
| `--model=<name>` | Force a model. Shortcuts (`opus`, `sonnet`, `haiku`), full IDs (`claude-opus-4-7`), or a **provider prefix** (`openai:gpt-4o`, `groq:llama-3.3-70b`, `claude:opus`, `local:qwen-coder`) to force the backend server-wide. | passthrough |
|
|
361
365
|
| `--port=<n>` | Port to listen on | `3456` |
|
|
362
366
|
| `--host=<addr>` / `DARIO_HOST` | Bind address. Use `0.0.0.0` for LAN, or a specific IP (e.g. a Tailscale interface). When non-loopback, also set `DARIO_API_KEY`. | `127.0.0.1` |
|
|
363
367
|
| `--verbose` / `-v` | Log every request (one line per request — method + path + billing bucket) | off |
|
|
364
368
|
| `--verbose=2` / `-vv` / `DARIO_LOG_BODIES=1` | Also dump the outbound request body (redacted: bearer tokens, `sk-ant-*` keys, JWTs stripped; capped at 8KB). For wire-level client-compat debugging. | off |
|
|
369
|
+
| `--log-file=<path>` / `DARIO_LOG_FILE` | Append one JSON-ND record per completed request to PATH. Useful for backgrounded proxies where stdout is unobserved (where `--verbose` can't help). Field set: `ts`, `req`, `method`, `path`, `model`, `status`, `latency_ms`, `in_tokens`, `out_tokens`, `cache_read`, `cache_create`, `claim`, `bucket`, `account`, `client`, `preserve_tools`, `stream`, plus `reject` / `error` on failure paths. Secrets scrubbed via the same redactor that `--verbose-bodies` uses; no request bodies. | off |
|
|
370
|
+
| `--passthrough-betas=<csv>` / `DARIO_PASSTHROUGH_BETAS` | Beta flags ALWAYS forwarded upstream regardless of CC's captured set or the client's `anthropic-beta` header. Bypasses the billable-beta filter (so `extended-cache-ttl-*` survives if you opt in). Per-account rejection cache still applies — a pinned flag the upstream 400's gets dropped on retry rather than re-sent forever. Use when you know a beta works on your account but isn't in the captured template, or when client traffic should be force-augmented. Empty flag value (`--passthrough-betas=`) clears the env-default. | off |
|
|
365
371
|
| `--strict-tls` / `DARIO_STRICT_TLS=1` | Refuse to start proxy mode unless runtime classifies as `bun-match` — i.e. the TLS ClientHello matches CC's. See [Wire-fidelity axes](#wire-fidelity-axes). (v3.23) | off |
|
|
366
372
|
| `--pace-min=<ms>` / `DARIO_PACE_MIN_MS` | Minimum inter-request gap in ms. Replaces the legacy hardcoded 500 ms. (v3.24) | `500` |
|
|
367
373
|
| `--pace-jitter=<ms>` / `DARIO_PACE_JITTER_MS` | Uniform random jitter added to each gap. Dissolves the minimum-inter-arrival observable edge. (v3.24) | `0` |
|
package/dist/cc-oauth-detect.js
CHANGED
|
@@ -116,6 +116,12 @@ function candidatePaths() {
|
|
|
116
116
|
const home = homedir();
|
|
117
117
|
if (platform() === 'win32') {
|
|
118
118
|
return [
|
|
119
|
+
// CC v2.x ships a Bun-compiled standalone exe under bin/.
|
|
120
|
+
// Earlier (v1.x) layouts used cli.js / cli.mjs at the package
|
|
121
|
+
// root. Both are kept in the search list so we work across the
|
|
122
|
+
// upgrade without forcing every user onto a fresh capture.
|
|
123
|
+
join(home, 'AppData', 'Roaming', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude.exe'),
|
|
124
|
+
join(home, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude.exe'),
|
|
119
125
|
join(home, '.local', 'bin', 'claude.exe'),
|
|
120
126
|
join(home, 'AppData', 'Roaming', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
121
127
|
join(home, 'AppData', 'Roaming', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.mjs'),
|
|
@@ -124,6 +130,10 @@ function candidatePaths() {
|
|
|
124
130
|
];
|
|
125
131
|
}
|
|
126
132
|
return [
|
|
133
|
+
// v2.x bin/claude precompiled exe — checked before legacy cli.js/.mjs.
|
|
134
|
+
'/usr/local/lib/node_modules/@anthropic-ai/claude-code/bin/claude',
|
|
135
|
+
'/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/bin/claude',
|
|
136
|
+
join(home, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude'),
|
|
127
137
|
join(home, '.local', 'bin', 'claude'),
|
|
128
138
|
'/usr/local/bin/claude',
|
|
129
139
|
'/opt/homebrew/bin/claude',
|
package/dist/cc-template.d.ts
CHANGED
|
@@ -120,6 +120,30 @@ export declare function scrubFrameworkIdentifiers(text: string): string;
|
|
|
120
120
|
* Reported via @vmvarg4 on X after the v3.30.5 marketing push.
|
|
121
121
|
*/
|
|
122
122
|
export declare function detectTextToolClient(systemText: string): string | null;
|
|
123
|
+
/**
|
|
124
|
+
* Structural fallback for non-CC clients that the identity-string
|
|
125
|
+
* detector doesn't recognize. When the operator hands us 3+ tools and
|
|
126
|
+
* ≥80% of them don't appear in TOOL_MAP, we're looking at a custom
|
|
127
|
+
* client whose tool surface has effectively no overlap with CC's.
|
|
128
|
+
* Default-mode round-robin onto CC fallback slots silently corrupts
|
|
129
|
+
* those calls (the client gets back a Glob/Read/Bash response shape
|
|
130
|
+
* its own tool can't parse).
|
|
131
|
+
*
|
|
132
|
+
* Returns 'unknown-non-cc' for that case so buildCCRequest can flip
|
|
133
|
+
* to preserve-tools — the only correct routing for a tool surface
|
|
134
|
+
* dario doesn't understand. Unlike the identity-string detector, this
|
|
135
|
+
* catches future clients we haven't added an explicit pattern for
|
|
136
|
+
* (in-house agents, OpenClaw derivatives, etc.) without needing
|
|
137
|
+
* per-client maintenance.
|
|
138
|
+
*
|
|
139
|
+
* Threshold reasoning:
|
|
140
|
+
* - len < 3: too few tools to be confident; let the existing detector
|
|
141
|
+
* decide. Single-purpose bridges and partial loads land here.
|
|
142
|
+
* - 80% unmapped: leaves room for a non-CC client that legitimately
|
|
143
|
+
* reuses 1-2 of TOOL_MAP's bash/grep/read aliases. 100% would miss
|
|
144
|
+
* those; 50% would catch Cline forks that use 4 mapped + 4 custom.
|
|
145
|
+
*/
|
|
146
|
+
export declare function detectNonCCByTools(clientTools: Array<Record<string, unknown>> | undefined): string | null;
|
|
123
147
|
/**
|
|
124
148
|
* Flatten an Anthropic-shaped `system` field (string or array of text
|
|
125
149
|
* blocks) to a single joined string. Skips the billing-tag block so
|
|
@@ -218,6 +242,7 @@ export declare function buildCCRequest(clientBody: Record<string, unknown>, bill
|
|
|
218
242
|
}, opts?: {
|
|
219
243
|
preserveTools?: boolean;
|
|
220
244
|
hybridTools?: boolean;
|
|
245
|
+
mergeTools?: boolean;
|
|
221
246
|
noAutoDetect?: boolean;
|
|
222
247
|
effort?: EffortValue;
|
|
223
248
|
maxTokens?: number | 'client';
|
package/dist/cc-template.js
CHANGED
|
@@ -245,6 +245,16 @@ export function detectTextToolClient(systemText) {
|
|
|
245
245
|
return 'hermes';
|
|
246
246
|
if (/\bcreated by Nous Research\b/.test(systemText))
|
|
247
247
|
return 'hermes';
|
|
248
|
+
// arnie (askalf) — IT-troubleshooting CLI built on the Anthropic SDK.
|
|
249
|
+
// Identity line is stable across versions ("You are Arnie, a portable
|
|
250
|
+
// IT tech troubleshooting assistant ..."). Tool *names* (shell, read_file,
|
|
251
|
+
// grep, ...) overlap with TOOL_MAP so structural fallback won't catch it,
|
|
252
|
+
// but the *schemas* diverge from CC's (arnie's shell takes {cmd, timeout_s,
|
|
253
|
+
// working_directory}; CC's Bash takes {command, description}) so default
|
|
254
|
+
// round-robin remap silently corrupts the calls. Identity match → auto
|
|
255
|
+
// preserve-tools is the only correct routing.
|
|
256
|
+
if (/\bYou are Arnie\b/.test(systemText))
|
|
257
|
+
return 'arnie';
|
|
248
258
|
// Protocol-signature fallback — unique to the Cline family and its
|
|
249
259
|
// forks; survives a forked system prompt that edited the identity
|
|
250
260
|
// string out but kept the tool protocol intact.
|
|
@@ -256,6 +266,43 @@ export function detectTextToolClient(systemText) {
|
|
|
256
266
|
return 'cline-like';
|
|
257
267
|
return null;
|
|
258
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Structural fallback for non-CC clients that the identity-string
|
|
271
|
+
* detector doesn't recognize. When the operator hands us 3+ tools and
|
|
272
|
+
* ≥80% of them don't appear in TOOL_MAP, we're looking at a custom
|
|
273
|
+
* client whose tool surface has effectively no overlap with CC's.
|
|
274
|
+
* Default-mode round-robin onto CC fallback slots silently corrupts
|
|
275
|
+
* those calls (the client gets back a Glob/Read/Bash response shape
|
|
276
|
+
* its own tool can't parse).
|
|
277
|
+
*
|
|
278
|
+
* Returns 'unknown-non-cc' for that case so buildCCRequest can flip
|
|
279
|
+
* to preserve-tools — the only correct routing for a tool surface
|
|
280
|
+
* dario doesn't understand. Unlike the identity-string detector, this
|
|
281
|
+
* catches future clients we haven't added an explicit pattern for
|
|
282
|
+
* (in-house agents, OpenClaw derivatives, etc.) without needing
|
|
283
|
+
* per-client maintenance.
|
|
284
|
+
*
|
|
285
|
+
* Threshold reasoning:
|
|
286
|
+
* - len < 3: too few tools to be confident; let the existing detector
|
|
287
|
+
* decide. Single-purpose bridges and partial loads land here.
|
|
288
|
+
* - 80% unmapped: leaves room for a non-CC client that legitimately
|
|
289
|
+
* reuses 1-2 of TOOL_MAP's bash/grep/read aliases. 100% would miss
|
|
290
|
+
* those; 50% would catch Cline forks that use 4 mapped + 4 custom.
|
|
291
|
+
*/
|
|
292
|
+
export function detectNonCCByTools(clientTools) {
|
|
293
|
+
if (!clientTools || clientTools.length < 3)
|
|
294
|
+
return null;
|
|
295
|
+
let unmapped = 0;
|
|
296
|
+
for (const tool of clientTools) {
|
|
297
|
+
const name = (tool.name || '').toLowerCase();
|
|
298
|
+
if (!TOOL_MAP[name])
|
|
299
|
+
unmapped++;
|
|
300
|
+
}
|
|
301
|
+
if (unmapped / clientTools.length >= 0.8) {
|
|
302
|
+
return 'unknown-non-cc';
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
259
306
|
/**
|
|
260
307
|
* Flatten an Anthropic-shaped `system` field (string or array of text
|
|
261
308
|
* blocks) to a single joined string. Skips the billing-tag block so
|
|
@@ -789,8 +836,8 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
789
836
|
// Cline / Kilo Code / Roo Code (and forks) ship an XML tool-invocation
|
|
790
837
|
// protocol in the system prompt. Peek at it before scrubbing so the
|
|
791
838
|
// brand name is still present, decide whether to auto-switch into
|
|
792
|
-
// preserve-tools behavior below. Explicit --hybrid-tools
|
|
793
|
-
// heuristic (operator opt-in wins). dario#40.
|
|
839
|
+
// preserve-tools behavior below. Explicit --hybrid-tools / --merge-tools
|
|
840
|
+
// outrank the heuristic (operator opt-in wins). dario#40.
|
|
794
841
|
//
|
|
795
842
|
// `noAutoDetect` skips the detector entirely — operators who want the
|
|
796
843
|
// full CC fingerprint restored (tools array included) even when their
|
|
@@ -799,9 +846,22 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
799
846
|
const rawSystemForDetection = extractSystemText(clientBody);
|
|
800
847
|
const detectedClient = opts.noAutoDetect
|
|
801
848
|
? undefined
|
|
802
|
-
: (detectTextToolClient(rawSystemForDetection)
|
|
803
|
-
|
|
849
|
+
: (detectTextToolClient(rawSystemForDetection)
|
|
850
|
+
?? detectNonCCByTools(clientTools)
|
|
851
|
+
?? undefined);
|
|
852
|
+
const autoPreserve = Boolean(detectedClient) && !opts.hybridTools && !opts.mergeTools;
|
|
804
853
|
const effectivePreserveTools = Boolean(opts.preserveTools) || autoPreserve;
|
|
854
|
+
// Merge mode is the third tool-routing axis. Wire shape: CC's canonical
|
|
855
|
+
// tool array is sent first (so the fingerprint axis "tools[]" still
|
|
856
|
+
// matches CC's wire footprint), and the client's tools are appended
|
|
857
|
+
// after — deduped by name, case-insensitive. The model sees the union
|
|
858
|
+
// and may call either side; tool calls flow back unchanged because we
|
|
859
|
+
// skip the reverse-map (any rewriting would be lossy in both directions).
|
|
860
|
+
//
|
|
861
|
+
// Mutually exclusive with preserveTools and hybridTools — three flags
|
|
862
|
+
// would mean three different bodies; the operator must pick one. The
|
|
863
|
+
// proxy CLI enforces the mutex at startup, this just respects it.
|
|
864
|
+
const effectiveMergeTools = Boolean(opts.mergeTools) && !effectivePreserveTools && !opts.hybridTools;
|
|
805
865
|
// ── Strip thinking from history ──
|
|
806
866
|
for (const msg of messages) {
|
|
807
867
|
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
@@ -844,7 +904,7 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
844
904
|
// the fingerprint risk on their own account.
|
|
845
905
|
const activeToolMap = new Map();
|
|
846
906
|
const unmappedTools = [];
|
|
847
|
-
if (clientTools && !effectivePreserveTools) {
|
|
907
|
+
if (clientTools && !effectivePreserveTools && !effectiveMergeTools) {
|
|
848
908
|
// Two passes so the unmapped-tool distributor can avoid colliding with
|
|
849
909
|
// CC tools the client already uses directly. Without this, a client
|
|
850
910
|
// sending both `WebSearch` and some unmapped tool like `memory_get`
|
|
@@ -1027,10 +1087,38 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
1027
1087
|
],
|
|
1028
1088
|
};
|
|
1029
1089
|
// Tools come before metadata in CC's key order.
|
|
1030
|
-
// preserveTools mode: pass client tools through unchanged (better for
|
|
1031
|
-
// agents with custom schemas, but loses the CC tool fingerprint).
|
|
1090
|
+
// - preserveTools mode: pass client tools through unchanged (better for
|
|
1091
|
+
// real agents with custom schemas, but loses the CC tool fingerprint).
|
|
1092
|
+
// - mergeTools mode: send CC's canonical tools FIRST then append the
|
|
1093
|
+
// client's tools, deduped by name (case-insensitive). The model sees
|
|
1094
|
+
// the union; tool calls flow back unchanged because activeToolMap is
|
|
1095
|
+
// empty in this branch. Trade-off documented in the README: the
|
|
1096
|
+
// wire-shape "tools[]" axis still contains CC's array as a prefix,
|
|
1097
|
+
// but the suffix is operator-supplied custom shapes — Anthropic's
|
|
1098
|
+
// classifier may flip routing on the difference. Verify locally
|
|
1099
|
+
// before relying on it.
|
|
1032
1100
|
if (clientTools && clientTools.length > 0) {
|
|
1033
|
-
|
|
1101
|
+
if (effectivePreserveTools) {
|
|
1102
|
+
ccRequest.tools = clientTools;
|
|
1103
|
+
}
|
|
1104
|
+
else if (effectiveMergeTools) {
|
|
1105
|
+
const ccNames = new Set(CC_TOOL_DEFINITIONS.map((t) => t.name.toLowerCase()));
|
|
1106
|
+
const appended = clientTools.filter((t) => {
|
|
1107
|
+
const name = t.name?.toLowerCase();
|
|
1108
|
+
return name !== undefined && !ccNames.has(name);
|
|
1109
|
+
});
|
|
1110
|
+
ccRequest.tools = [...CC_TOOL_DEFINITIONS, ...appended];
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
ccRequest.tools = CC_TOOL_DEFINITIONS;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
else if (effectiveMergeTools) {
|
|
1117
|
+
// Operator opted into merge but the client sent no tools. Still
|
|
1118
|
+
// emit the CC base array — that preserves the fingerprint shape
|
|
1119
|
+
// (zero-tools requests are themselves a divergence from CC's
|
|
1120
|
+
// wire footprint).
|
|
1121
|
+
ccRequest.tools = CC_TOOL_DEFINITIONS;
|
|
1034
1122
|
}
|
|
1035
1123
|
// Metadata
|
|
1036
1124
|
ccRequest.metadata = {
|
package/dist/cli.d.ts
CHANGED
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
* dario logout — Remove saved credentials
|
|
11
11
|
*/
|
|
12
12
|
import { type EffortValue } from './cc-template.js';
|
|
13
|
+
/**
|
|
14
|
+
* Parse `--passthrough-betas=<csv>` (or the env-var fallback) into a
|
|
15
|
+
* deduped, trimmed list. The CLI flag wins over the env var when both
|
|
16
|
+
* are set — that's the convention every other dario flag uses.
|
|
17
|
+
*
|
|
18
|
+
* Edge cases:
|
|
19
|
+
* - `--passthrough-betas=` (explicit empty) → returns []. The
|
|
20
|
+
* operator typed an empty value; this is the documented "clear the
|
|
21
|
+
* env-default, run with no pinned betas" override.
|
|
22
|
+
* - flag missing entirely → falls back to envVar.
|
|
23
|
+
* - empty entries / whitespace-only entries / duplicates are dropped.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parsePassthroughBetasFlag(args: string[], envVar: string | undefined): string[];
|
|
13
26
|
/**
|
|
14
27
|
* Parse `--max-tokens=<N|client>` + `DARIO_MAX_TOKENS` env (dario#88).
|
|
15
28
|
* Numeric values pin; `client` (case-insensitive) = passthrough client's
|
package/dist/cli.js
CHANGED
|
@@ -181,8 +181,19 @@ async function proxy() {
|
|
|
181
181
|
const passthrough = args.includes('--passthrough') || args.includes('--thin');
|
|
182
182
|
const preserveTools = args.includes('--preserve-tools') || args.includes('--keep-tools');
|
|
183
183
|
const hybridTools = args.includes('--hybrid-tools') || args.includes('--context-inject');
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
const mergeTools = args.includes('--merge-tools') || args.includes('--append-tools');
|
|
185
|
+
// The three modes shape the outbound `tools` array differently;
|
|
186
|
+
// combining any two would mean two different bodies. Caught here so
|
|
187
|
+
// the operator gets a clear error instead of one flag silently
|
|
188
|
+
// winning. startProxy enforces the same mutex defensively.
|
|
189
|
+
const toolModeCount = [preserveTools, hybridTools, mergeTools].filter(Boolean).length;
|
|
190
|
+
if (toolModeCount > 1) {
|
|
191
|
+
const picked = [
|
|
192
|
+
preserveTools && '--preserve-tools',
|
|
193
|
+
hybridTools && '--hybrid-tools',
|
|
194
|
+
mergeTools && '--merge-tools',
|
|
195
|
+
].filter(Boolean).join(', ');
|
|
196
|
+
console.error(`[dario] tool-routing flags are mutually exclusive. Pick one (got: ${picked}).`);
|
|
186
197
|
process.exit(1);
|
|
187
198
|
}
|
|
188
199
|
// Opt-out for v3.19.3's text-tool-client auto-detection. Operators who
|
|
@@ -268,6 +279,17 @@ async function proxy() {
|
|
|
268
279
|
// the server side, so passing through a too-high value returns a clean
|
|
269
280
|
// 400 rather than silently accepting beyond-model-max.
|
|
270
281
|
const maxTokens = resolveMaxTokensFlag(args, process.env['DARIO_MAX_TOKENS']);
|
|
282
|
+
// --log-file <path> — append a one-line JSON record per completed
|
|
283
|
+
// request. Useful for backgrounded proxies where stdout is unobserved.
|
|
284
|
+
// Falls back to DARIO_LOG_FILE; off by default. Path is opened with
|
|
285
|
+
// append mode so multiple proxy restarts share a rolling history.
|
|
286
|
+
const logFile = parseLogFileFlag(args) ?? process.env['DARIO_LOG_FILE'] ?? undefined;
|
|
287
|
+
// --passthrough-betas=name1,name2 — operator-pinned beta allow-list.
|
|
288
|
+
// Names listed here are always forwarded to Anthropic regardless of
|
|
289
|
+
// CC's captured set or the client's own beta header; bypasses the
|
|
290
|
+
// billable-filter. Empty values are dropped. Falls back to
|
|
291
|
+
// DARIO_PASSTHROUGH_BETAS env var.
|
|
292
|
+
const passthroughBetas = parsePassthroughBetasFlag(args, process.env['DARIO_PASSTHROUGH_BETAS']);
|
|
271
293
|
// Non-loopback bind without DARIO_API_KEY turns dario into an open
|
|
272
294
|
// OAuth-subscription relay for anyone on the reachable network. Refuse
|
|
273
295
|
// to start rather than rely on the operator to read the startup banner.
|
|
@@ -287,7 +309,56 @@ async function proxy() {
|
|
|
287
309
|
console.error(`[dario] Override (not recommended): pass --unsafe-no-auth if you have out-of-band network controls and accept the risk.`);
|
|
288
310
|
process.exit(1);
|
|
289
311
|
}
|
|
290
|
-
await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs, drainOnClose, sessionIdleRotateMs, sessionRotateJitterMs, sessionMaxAgeMs, sessionPerClient, preserveOrchestrationTags, noLiveCapture, strictTemplate, maxConcurrent, maxQueued, queueTimeoutMs, effort, maxTokens });
|
|
312
|
+
await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools, mergeTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs, drainOnClose, sessionIdleRotateMs, sessionRotateJitterMs, sessionMaxAgeMs, sessionPerClient, preserveOrchestrationTags, noLiveCapture, strictTemplate, maxConcurrent, maxQueued, queueTimeoutMs, effort, maxTokens, logFile, passthroughBetas });
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Parse `--passthrough-betas=<csv>` (or the env-var fallback) into a
|
|
316
|
+
* deduped, trimmed list. The CLI flag wins over the env var when both
|
|
317
|
+
* are set — that's the convention every other dario flag uses.
|
|
318
|
+
*
|
|
319
|
+
* Edge cases:
|
|
320
|
+
* - `--passthrough-betas=` (explicit empty) → returns []. The
|
|
321
|
+
* operator typed an empty value; this is the documented "clear the
|
|
322
|
+
* env-default, run with no pinned betas" override.
|
|
323
|
+
* - flag missing entirely → falls back to envVar.
|
|
324
|
+
* - empty entries / whitespace-only entries / duplicates are dropped.
|
|
325
|
+
*/
|
|
326
|
+
export function parsePassthroughBetasFlag(args, envVar) {
|
|
327
|
+
const eqArg = args.find((a) => a.startsWith('--passthrough-betas='));
|
|
328
|
+
// When the flag is present at all (even with an empty value), it owns
|
|
329
|
+
// the result. Only fall back to the env var when the flag is absent.
|
|
330
|
+
const raw = eqArg !== undefined ? eqArg.slice('--passthrough-betas='.length) : envVar;
|
|
331
|
+
if (!raw)
|
|
332
|
+
return [];
|
|
333
|
+
const seen = new Set();
|
|
334
|
+
const out = [];
|
|
335
|
+
for (const piece of raw.split(',')) {
|
|
336
|
+
const trimmed = piece.trim();
|
|
337
|
+
if (trimmed.length > 0 && !seen.has(trimmed)) {
|
|
338
|
+
seen.add(trimmed);
|
|
339
|
+
out.push(trimmed);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Parse `--log-file=<path>` or `--log-file <path>`. Returns the path
|
|
346
|
+
* string when present, undefined otherwise. An empty path (e.g.
|
|
347
|
+
* `--log-file=`) is treated as unset so the env-var fallback can apply.
|
|
348
|
+
*/
|
|
349
|
+
function parseLogFileFlag(args) {
|
|
350
|
+
const eqArg = args.find(a => a.startsWith('--log-file='));
|
|
351
|
+
if (eqArg) {
|
|
352
|
+
const value = eqArg.slice('--log-file='.length);
|
|
353
|
+
return value.length > 0 ? value : undefined;
|
|
354
|
+
}
|
|
355
|
+
const idx = args.indexOf('--log-file');
|
|
356
|
+
if (idx >= 0 && idx + 1 < args.length) {
|
|
357
|
+
const value = args[idx + 1];
|
|
358
|
+
if (value && !value.startsWith('-'))
|
|
359
|
+
return value;
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
291
362
|
}
|
|
292
363
|
/**
|
|
293
364
|
* Parse `--max-tokens=<N|client>` + `DARIO_MAX_TOKENS` env (dario#88).
|
|
@@ -671,11 +742,30 @@ async function help() {
|
|
|
671
742
|
DARIO_API_KEY — with redacted previews and
|
|
672
743
|
a targeted diagnosis (dario#97 class). Use
|
|
673
744
|
--timeout-ms=N to adjust the 30s default.
|
|
745
|
+
dario doctor --bun-bootstrap
|
|
746
|
+
One-shot Bun installer. Closes the gap
|
|
747
|
+
between "doctor warned about Node-only TLS
|
|
748
|
+
fingerprint" and "Bun on PATH" without
|
|
749
|
+
copy-pasting a curl-to-shell line. Skips
|
|
750
|
+
when Bun is already installed. Pure
|
|
751
|
+
delegation to the official installer at
|
|
752
|
+
bun.com — dario does not vendor or pin a
|
|
753
|
+
Bun version.
|
|
674
754
|
dario config Print the effective configuration (port,
|
|
675
755
|
host, DARIO_API_KEY state, OAuth status,
|
|
676
756
|
pool, backends, paths) with credentials
|
|
677
757
|
redacted. Safe to paste into bug reports.
|
|
678
758
|
--json for structured output.
|
|
759
|
+
dario usage Burn-rate summary of the running proxy's
|
|
760
|
+
traffic (last 60 min): requests, token
|
|
761
|
+
totals, subscription % vs. extra-usage,
|
|
762
|
+
per-account rotation if pool mode is on.
|
|
763
|
+
Hits /analytics on the local proxy. Works
|
|
764
|
+
only when proxy is running; for a one-off
|
|
765
|
+
rate-limit snapshot from Anthropic, see
|
|
766
|
+
\`dario doctor --usage\`. --port=N to target
|
|
767
|
+
a non-default port; --json for the raw
|
|
768
|
+
/analytics payload.
|
|
679
769
|
dario upgrade npm install -g @askalf/dario@latest with a
|
|
680
770
|
pre-flight current-vs-latest check.
|
|
681
771
|
|
|
@@ -691,6 +781,14 @@ async function help() {
|
|
|
691
781
|
Loses subscription routing; use for custom agents
|
|
692
782
|
--hybrid-tools Remap to CC tools, inject sessionId/requestId/etc.
|
|
693
783
|
Keeps subscription routing for custom agents
|
|
784
|
+
--merge-tools Send CC's canonical tools first, append the
|
|
785
|
+
client's custom tools after (deduped by name).
|
|
786
|
+
Model can call either; tool calls flow back
|
|
787
|
+
unchanged. EXPERIMENTAL — Anthropic's billing
|
|
788
|
+
classifier may flip routing on the appended
|
|
789
|
+
tail. Validate with --verbose on the first
|
|
790
|
+
1-2 requests. Mutually exclusive with
|
|
791
|
+
--preserve-tools and --hybrid-tools.
|
|
694
792
|
--no-auto-detect Disable Cline/Kilo/Roo auto-preserve-tools
|
|
695
793
|
(v3.19.3 behavior). Keeps CC fingerprint
|
|
696
794
|
intact even when a text-tool client is
|
|
@@ -801,6 +899,20 @@ async function help() {
|
|
|
801
899
|
--verbose, -v Log all requests
|
|
802
900
|
--verbose=2, -vv Also dump redacted request bodies
|
|
803
901
|
(env: DARIO_LOG_BODIES=1)
|
|
902
|
+
--log-file=PATH Append one JSON-ND record per completed
|
|
903
|
+
request to PATH. Useful for backgrounded
|
|
904
|
+
proxies where stdout is unobserved (where
|
|
905
|
+
--verbose can't help). Secrets scrubbed,
|
|
906
|
+
no request bodies. Env: DARIO_LOG_FILE.
|
|
907
|
+
--passthrough-betas=CSV Beta flags to ALWAYS forward upstream
|
|
908
|
+
regardless of CC's captured set or the
|
|
909
|
+
client's anthropic-beta header. Bypasses
|
|
910
|
+
the billable-beta filter. Per-account
|
|
911
|
+
rejection cache still applies (so a flag
|
|
912
|
+
upstream 400's gets dropped, not retried
|
|
913
|
+
forever). Use when you know a beta works
|
|
914
|
+
on your account but isn't in the captured
|
|
915
|
+
template. Env: DARIO_PASSTHROUGH_BETAS.
|
|
804
916
|
|
|
805
917
|
Quick start:
|
|
806
918
|
dario login # auto-detects Claude Code credentials
|
|
@@ -982,6 +1094,53 @@ async function doctor() {
|
|
|
982
1094
|
const usage = args.includes('--usage');
|
|
983
1095
|
const asJson = args.includes('--json');
|
|
984
1096
|
const authCheck = args.includes('--auth-check');
|
|
1097
|
+
const bunBoot = args.includes('--bun-bootstrap');
|
|
1098
|
+
if (bunBoot) {
|
|
1099
|
+
// One-shot Bun installer. Closes the gap between "doctor warned
|
|
1100
|
+
// about Node-only TLS fingerprint" and "Bun is on PATH" without
|
|
1101
|
+
// making the user copy-paste a curl line from the README.
|
|
1102
|
+
// Probe first so we don't reinstall on a Bun-already-present host.
|
|
1103
|
+
const { probeBunVersion, bunBootstrap } = await import('./runtime-fingerprint.js');
|
|
1104
|
+
console.log('');
|
|
1105
|
+
console.log(' dario — Bun bootstrap');
|
|
1106
|
+
console.log(' ─────────────────────');
|
|
1107
|
+
console.log('');
|
|
1108
|
+
const existing = probeBunVersion();
|
|
1109
|
+
if (existing) {
|
|
1110
|
+
console.log(` Bun v${existing} already on PATH — nothing to install.`);
|
|
1111
|
+
console.log(' If dario is still running on Node, the auto-relaunch was bypassed (DARIO_NO_BUN set,');
|
|
1112
|
+
console.log(' or invoked through a wrapper that strips it). Re-run \`dario proxy\` directly.');
|
|
1113
|
+
console.log('');
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
console.log(' Bun is not on PATH. Running the official upstream installer:');
|
|
1117
|
+
console.log('');
|
|
1118
|
+
const result = await bunBootstrap();
|
|
1119
|
+
console.log('');
|
|
1120
|
+
if (result.exitCode === 0) {
|
|
1121
|
+
// Probe again — installer may write into a directory that the
|
|
1122
|
+
// current shell doesn't have on PATH yet (typical: ~/.bun/bin
|
|
1123
|
+
// appended to a profile that hasn't reloaded). We can't fix that
|
|
1124
|
+
// for the running shell; just call it out so the user knows what
|
|
1125
|
+
// to do next.
|
|
1126
|
+
const after = probeBunVersion();
|
|
1127
|
+
if (after) {
|
|
1128
|
+
console.log(` Bun v${after} installed. Re-run \`dario proxy\` to auto-relaunch under it.`);
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
console.log(' Installer reported success, but \`bun --version\` still fails from this shell.');
|
|
1132
|
+
console.log(' Open a new terminal (or source the profile the installer touched), then re-run');
|
|
1133
|
+
console.log(' \`dario doctor\` to confirm.');
|
|
1134
|
+
}
|
|
1135
|
+
console.log('');
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
console.error(` Installer exited with code ${result.exitCode}.`);
|
|
1139
|
+
console.error(` Manual fallback: ${result.runner}`);
|
|
1140
|
+
console.error(' Or visit https://bun.com for platform-specific instructions.');
|
|
1141
|
+
console.error('');
|
|
1142
|
+
process.exit(result.exitCode);
|
|
1143
|
+
}
|
|
985
1144
|
if (authCheck) {
|
|
986
1145
|
console.log('');
|
|
987
1146
|
console.log(' dario — Auth Check');
|
|
@@ -1133,6 +1292,112 @@ async function upgrade() {
|
|
|
1133
1292
|
console.log(' Upgrade complete. Run `dario --version` to confirm.');
|
|
1134
1293
|
console.log('');
|
|
1135
1294
|
}
|
|
1295
|
+
/**
|
|
1296
|
+
* `dario usage` — focused burn-rate summary of the running proxy's
|
|
1297
|
+
* traffic. Hits `/analytics` on the local proxy (default port 3456,
|
|
1298
|
+
* overridable with --port=N or DARIO_USAGE_PORT) and prints a
|
|
1299
|
+
* human-readable digest: requests in the last hour, token totals,
|
|
1300
|
+
* subscription % vs. extra-usage, per-account rotation if pool mode
|
|
1301
|
+
* is active.
|
|
1302
|
+
*
|
|
1303
|
+
* When the proxy isn't running on the expected port, prints a hint
|
|
1304
|
+
* pointing at `dario doctor --usage` (which fires a Haiku rate-limit
|
|
1305
|
+
* probe directly to Anthropic — different purpose, but the closest
|
|
1306
|
+
* substitute when there's no live proxy traffic to summarize).
|
|
1307
|
+
*
|
|
1308
|
+
* --json mode emits the raw /analytics payload for machine consumption
|
|
1309
|
+
* (CI dashboards, status bars, the MCP `usage` tool that wraps this).
|
|
1310
|
+
*/
|
|
1311
|
+
async function usage() {
|
|
1312
|
+
const portArg = args.find(a => a.startsWith('--port='));
|
|
1313
|
+
const port = portArg
|
|
1314
|
+
? parseInt(portArg.split('=')[1], 10)
|
|
1315
|
+
: process.env['DARIO_USAGE_PORT']
|
|
1316
|
+
? parseInt(process.env['DARIO_USAGE_PORT'], 10)
|
|
1317
|
+
: 3456;
|
|
1318
|
+
const asJson = args.includes('--json');
|
|
1319
|
+
const url = `http://127.0.0.1:${port}/analytics`;
|
|
1320
|
+
let payload = null;
|
|
1321
|
+
let connectError = null;
|
|
1322
|
+
try {
|
|
1323
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(3000) });
|
|
1324
|
+
if (!res.ok) {
|
|
1325
|
+
connectError = `proxy responded ${res.status}`;
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
payload = await res.json();
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
catch (err) {
|
|
1332
|
+
connectError = err instanceof Error ? err.message : String(err);
|
|
1333
|
+
}
|
|
1334
|
+
if (asJson) {
|
|
1335
|
+
if (payload) {
|
|
1336
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
process.stdout.write(JSON.stringify({ error: 'proxy not reachable', port, detail: connectError }, null, 2) + '\n');
|
|
1340
|
+
process.exit(1);
|
|
1341
|
+
}
|
|
1342
|
+
console.log('');
|
|
1343
|
+
console.log(' dario — Usage');
|
|
1344
|
+
console.log(' ─────────────');
|
|
1345
|
+
console.log('');
|
|
1346
|
+
if (!payload) {
|
|
1347
|
+
console.log(` Proxy not reachable on http://127.0.0.1:${port} (${connectError ?? 'no response'}).`);
|
|
1348
|
+
console.log(' `dario usage` summarizes traffic from a running proxy (live history).');
|
|
1349
|
+
console.log(' For a one-off rate-limit snapshot from Anthropic, run:');
|
|
1350
|
+
console.log('');
|
|
1351
|
+
console.log(' dario doctor --usage');
|
|
1352
|
+
console.log('');
|
|
1353
|
+
console.log(' Costs ~1 subscription request; works without a running proxy.');
|
|
1354
|
+
console.log('');
|
|
1355
|
+
process.exit(1);
|
|
1356
|
+
}
|
|
1357
|
+
// Pool mode response shape:
|
|
1358
|
+
// { window: { minutes, requests, ...stats }, allTime: {...},
|
|
1359
|
+
// perAccount, perModel, utilization, predictions }
|
|
1360
|
+
// Single-account mode response shape:
|
|
1361
|
+
// { mode: 'single-account', note: '...' }
|
|
1362
|
+
if (payload.mode === 'single-account') {
|
|
1363
|
+
console.log(' Mode: single-account');
|
|
1364
|
+
console.log('');
|
|
1365
|
+
console.log(` ${payload.note}`);
|
|
1366
|
+
console.log('');
|
|
1367
|
+
console.log(' For a live snapshot of your subscription rate limit, run:');
|
|
1368
|
+
console.log(' dario doctor --usage');
|
|
1369
|
+
console.log('');
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const win = payload.window;
|
|
1373
|
+
const allTime = payload.allTime;
|
|
1374
|
+
const perAccount = payload.perAccount;
|
|
1375
|
+
console.log(' Mode: pool');
|
|
1376
|
+
console.log(` Window: last ${win?.minutes ?? 60} minutes`);
|
|
1377
|
+
console.log('');
|
|
1378
|
+
console.log(` Requests: ${win?.requests ?? 0}` + (allTime ? ` (all-time: ${allTime.requests ?? 0})` : ''));
|
|
1379
|
+
if (win && win.requests > 0) {
|
|
1380
|
+
console.log(` Input tokens: ${(win.totalInputTokens ?? 0).toLocaleString()}`);
|
|
1381
|
+
console.log(` Output tokens: ${(win.totalOutputTokens ?? 0).toLocaleString()}`);
|
|
1382
|
+
console.log(` Avg latency: ${win.avgLatencyMs ?? 0} ms`);
|
|
1383
|
+
if ((win.errorRate ?? 0) > 0) {
|
|
1384
|
+
console.log(` Error rate: ${((win.errorRate ?? 0) * 100).toFixed(1)}%`);
|
|
1385
|
+
}
|
|
1386
|
+
console.log(` Subscription %: ${win.subscriptionPercent ?? 0}%`);
|
|
1387
|
+
if ((win.estimatedCost ?? 0) > 0) {
|
|
1388
|
+
console.log(` Est. cost: $${(win.estimatedCost ?? 0).toFixed(4)} (would-be API cost)`);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (perAccount && Object.keys(perAccount).length > 0) {
|
|
1392
|
+
console.log('');
|
|
1393
|
+
console.log(' Per-account:');
|
|
1394
|
+
const aliasWidth = Math.max(...Object.keys(perAccount).map((a) => a.length));
|
|
1395
|
+
for (const [alias, stats] of Object.entries(perAccount)) {
|
|
1396
|
+
console.log(` ${alias.padEnd(aliasWidth)} ${stats.requests} req${stats.requests === 1 ? '' : 's'} (${stats.subscriptionPercent}% subscription)`);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
console.log('');
|
|
1400
|
+
}
|
|
1136
1401
|
// Main
|
|
1137
1402
|
const commands = {
|
|
1138
1403
|
login,
|
|
@@ -1148,6 +1413,7 @@ const commands = {
|
|
|
1148
1413
|
doctor,
|
|
1149
1414
|
config,
|
|
1150
1415
|
upgrade,
|
|
1416
|
+
usage,
|
|
1151
1417
|
help,
|
|
1152
1418
|
version,
|
|
1153
1419
|
'--help': help,
|