@askalf/dario 6.0.34 → 6.0.36
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 +17 -4
- package/dist/admin-api.d.ts +2 -0
- package/dist/admin-api.js +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.js +48 -28
- package/dist/pool.d.ts +28 -0
- package/dist/pool.js +70 -15
- package/dist/proxy.js +58 -3
- package/dist/upstream-rejection.d.ts +6 -0
- package/dist/upstream-rejection.js +6 -0
- package/docs/drift-monitor.md +2 -1
- package/docs/multi-account-pool.md +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<p><strong>One local endpoint. Every AI tool you own. The subscriptions you already pay for.</strong></p>
|
|
29
29
|
|
|
30
|
-
<sub><code>npm i -g @askalf/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~
|
|
30
|
+
<sub><code>npm i -g @askalf/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~32k lines you can read in a weekend · independent, unofficial, third-party (<a href="DISCLAIMER.md">DISCLAIMER.md</a>)</sub>
|
|
31
31
|
|
|
32
32
|
<sub><a href="#start-in-60-seconds">Start</a> · <a href="#point-your-tools-at-it">Your tools</a> · <a href="#what-it-does-with-a-request">Routing</a> · <a href="#two-plans-one-endpoint">Two plans</a> · <a href="#many-seats-one-endpoint">Pool</a> · <a href="#it-tracks-a-moving-target">Drift</a> · <a href="#trust--transparency">Trust</a> · <a href="#will-my-account-get-suspended">Risk</a> · <a href="#commands">Commands</a> · <a href="#faq">FAQ</a> · <a href="docs/returning.md">Coming back after a while?</a></sub>
|
|
33
33
|
|
|
@@ -373,7 +373,7 @@ Keeping that current is the whole job, and it's automated. These watchers run un
|
|
|
373
373
|
| [`deployed-version-watch`](./.github/workflows/deployed-version-watch.yml) | Publishing is not deploying: is what's running what was last released? |  |
|
|
374
374
|
| [`cc-drift-watcher-liveness`](./.github/workflows/cc-drift-watcher-liveness.yml) | The watcher itself going quiet. Lives on GitHub-hosted infrastructure on purpose, so it survives the failures it watches for. |  |
|
|
375
375
|
|
|
376
|
-
Guarded by a
|
|
376
|
+
Guarded at PR time by [`live-test`](./.github/workflows/live-test.yml), a required check that runs the full suite against a live proxy on a self-hosted runner, plus [`compat-test-self-hosted`](./.github/workflows/compat-test-self-hosted.yml), which replays the compat suite through a passthrough proxy on wire-shape changes. A few changes the watchers caught and shipped fixes for, same day:
|
|
377
377
|
|
|
378
378
|
| Change (no subscriber-facing note) | Effect | dario shipped |
|
|
379
379
|
|---|---|---|
|
|
@@ -405,7 +405,7 @@ The split isn't live, but it was announced once on short notice and could return
|
|
|
405
405
|
|
|
406
406
|
| Signal | Status |
|
|
407
407
|
|---|---|
|
|
408
|
-
| Source | **~
|
|
408
|
+
| Source | **~32k** lines of TypeScript across **68** files, auditable in a weekend. One credential path since v5: the pool. |
|
|
409
409
|
| Dependencies | **0 runtime.** Verify: `npm ls --production` |
|
|
410
410
|
| Provenance | Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions + Sigstore, published with OIDC trusted publishing — no long-lived npm token exists to leak |
|
|
411
411
|
| Scanning | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) on every push and weekly · [ClusterFuzzLite](./.github/workflows/cflite.yml) fuzzes the SSE translator and rejection parsers weekly · [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/askalf/dario) and [Best Practices](https://www.bestpractices.dev/projects/13638) badges above are live |
|
|
@@ -598,19 +598,32 @@ Two easy ways to help beyond code: **star the repo**, the clearest signal this i
|
|
|
598
598
|
|---|---|
|
|
599
599
|
| [@GodsBoy](https://github.com/GodsBoy) | Proxy auth, token redaction, error sanitization ([#2](https://github.com/askalf/dario/pull/2)) |
|
|
600
600
|
| [@belangertrading](https://github.com/belangertrading) | Billing-classification investigation ([#4](https://github.com/askalf/dario/issues/4), [#6](https://github.com/askalf/dario/issues/6), [#7](https://github.com/askalf/dario/issues/7), [#12](https://github.com/askalf/dario/issues/12), [#23](https://github.com/askalf/dario/issues/23)), multi-agent billing FAQ ([#27](https://github.com/askalf/dario/pull/27)) |
|
|
601
|
+
| [@wysie](https://github.com/wysie) | ESM `require` crash in `dario login` ([#15](https://github.com/askalf/dario/issues/15)), OAuth for Max-plan accounts ([#18](https://github.com/askalf/dario/issues/18)) |
|
|
601
602
|
| [@earlvanze](https://github.com/earlvanze) | OpenClaw tool mappings ([#19](https://github.com/askalf/dario/pull/19)), OAuth manual override ([#47](https://github.com/askalf/dario/pull/47)), HTTPS warning ([#53](https://github.com/askalf/dario/pull/53)) |
|
|
603
|
+
| [@nathan-widjaja](https://github.com/nathan-widjaja) | README positioning structure — the promise → who → first use → why-switch spine the page still runs on ([#21](https://github.com/askalf/dario/issues/21)) |
|
|
604
|
+
| [@trinhnvgem](https://github.com/trinhnvgem) | OAuth login failures on first release ([#22](https://github.com/askalf/dario/issues/22)), container and headless callback binding ([#28](https://github.com/askalf/dario/issues/28)) |
|
|
605
|
+
| [@adubkov](https://github.com/adubkov) | The container / headless-SSH case behind the manual OAuth code paste ([#28](https://github.com/askalf/dario/issues/28)) |
|
|
602
606
|
| [@iNicholasBE](https://github.com/iNicholasBE) | macOS keychain credential detection ([#30](https://github.com/askalf/dario/pull/30)) |
|
|
603
607
|
| [@boeingchoco](https://github.com/boeingchoco) | Reverse tool-param translation ([#29](https://github.com/askalf/dario/issues/29)), SSE framing regression catch, hybrid-tool motivation ([#33](https://github.com/askalf/dario/issues/33), [#36](https://github.com/askalf/dario/issues/36)) |
|
|
604
608
|
| [@tetsuco](https://github.com/tetsuco) | Scrubber path corruption ([#35](https://github.com/askalf/dario/issues/35)), OpenClaw reverse-mapping collisions ([#37](https://github.com/askalf/dario/issues/37)), 20x-tier report ([#42](https://github.com/askalf/dario/issues/42)) |
|
|
605
609
|
| [@mikelovatt](https://github.com/mikelovatt) | Silent subscription-drain surfaced via friendly billing buckets ([#34](https://github.com/askalf/dario/issues/34)) |
|
|
606
610
|
| [@ringge](https://github.com/ringge) | `--no-auto-detect` for text-tool auto-preserve ([#40](https://github.com/askalf/dario/issues/40)) |
|
|
611
|
+
| [@rustanacexd](https://github.com/rustanacexd) | Cursor BYOK routing for Claude, and `--effort=max` ([#190](https://github.com/askalf/dario/issues/190)) |
|
|
612
|
+
| [@daimonbot](https://github.com/daimonbot) | Official multi-arch Docker image on GHCR ([#199](https://github.com/askalf/dario/issues/199)) |
|
|
607
613
|
| [@Saik0s](https://github.com/Saik0s) | Wildcard CORS allow-headers, Opus 4.7 catalog entry ([#222](https://github.com/askalf/dario/pull/222)) |
|
|
614
|
+
| [@lwsh123k](https://github.com/lwsh123k) | `cch` anchored to the billing tag instead of first match ([#528](https://github.com/askalf/dario/issues/528)) |
|
|
608
615
|
| [@boredland](https://github.com/boredland) | Time-to-reset in `dario doctor --usage` ([#550](https://github.com/askalf/dario/pull/550)) |
|
|
609
616
|
| [@pnewell](https://github.com/pnewell) | `--preserve-output-format` for structured-output SDKs ([#583](https://github.com/askalf/dario/pull/583)) |
|
|
617
|
+
| [@matteo-rama](https://github.com/matteo-rama) | Headless admin bootstrap ([#599](https://github.com/askalf/dario/issues/599)), Analytics `NaN` and per-account rate-limit rows ([#600](https://github.com/askalf/dario/issues/600)), pool-aware `/status` and `/health` ([#636](https://github.com/askalf/dario/issues/636)), `version` on both ([#640](https://github.com/askalf/dario/issues/640)), Accounts TUI reads the live pool ([#641](https://github.com/askalf/dario/issues/641)) |
|
|
618
|
+
| [@miklisanton](https://github.com/miklisanton) | Mid-session `/model` switch 400 ([#744](https://github.com/askalf/dario/issues/744)), empty-turn guards behind the subagent 400s ([#1033](https://github.com/askalf/dario/issues/1033), [#1117](https://github.com/askalf/dario/issues/1117)) |
|
|
619
|
+
| [@p-i-](https://github.com/p-i-) | Independent wire-fidelity audit with a re-runnable harness — version-blind `bun-match`, and the correction to the packet-identical claim ([#813](https://github.com/askalf/dario/issues/813)) |
|
|
610
620
|
| [@jerzydziewierz](https://github.com/jerzydziewierz) | TUI Config tab clipping and scrolling ([#861](https://github.com/askalf/dario/pull/861)) |
|
|
621
|
+
| [@ramarro123](https://github.com/ramarro123) | Admin bulk re-auth ([#913](https://github.com/askalf/dario/issues/913)), shared state across instances ([#993](https://github.com/askalf/dario/issues/993)), prompt-cache behaviour under litellm ([#1018](https://github.com/askalf/dario/issues/1018)), parked-seat and shared-window reporting ([#1244](https://github.com/askalf/dario/issues/1244)) |
|
|
622
|
+
| [@zytegalaxy](https://github.com/zytegalaxy) | The ChatGPT/Codex engine and `dario add altman` ([#1009](https://github.com/askalf/dario/issues/1009)) |
|
|
611
623
|
| [@chaogebaba](https://github.com/chaogebaba) | Auto-release must never fire from a fork ([#1029](https://github.com/askalf/dario/pull/1029)) |
|
|
624
|
+
| [@robincle](https://github.com/robincle) | Utilisation freshness — `lastObservedAt` / `utilAgeMs` on `/accounts` ([#1032](https://github.com/askalf/dario/issues/1032)) |
|
|
612
625
|
| [@anupamme](https://github.com/anupamme) | Refresh-lock ownership by server-issued lock id ([#1059](https://github.com/askalf/dario/pull/1059)) |
|
|
613
|
-
| [@LiveNathan](https://github.com/LiveNathan) | Never send or stamp empty text blocks ([#1067](https://github.com/askalf/dario/pull/1067)) |
|
|
626
|
+
| [@LiveNathan](https://github.com/LiveNathan) | Never send or stamp empty text blocks ([#1067](https://github.com/askalf/dario/pull/1067)), empty final user turn from CC's stream-interruption retry ([#1092](https://github.com/askalf/dario/issues/1092), as [@NathanLively](https://github.com/NathanLively)) |
|
|
614
627
|
|
|
615
628
|
## Disclaimers
|
|
616
629
|
|
package/dist/admin-api.d.ts
CHANGED
|
@@ -102,6 +102,8 @@ export interface AdminAccountLive {
|
|
|
102
102
|
resetInMs: number | null;
|
|
103
103
|
claim: string;
|
|
104
104
|
status: string;
|
|
105
|
+
/** none · wait · regrant — the operator's next step (dario#1244). */
|
|
106
|
+
action?: 'none' | 'wait' | 'regrant';
|
|
105
107
|
requestCount: number;
|
|
106
108
|
/**
|
|
107
109
|
* Upstream 429s this account answered. `requestCount` counts requests it
|
package/dist/admin-api.js
CHANGED
|
@@ -378,6 +378,7 @@ export async function handleAdminRequest(req, res, urlPath, deps) {
|
|
|
378
378
|
reading_from: l.readingFrom ?? null,
|
|
379
379
|
claim: l.claim,
|
|
380
380
|
status: l.status,
|
|
381
|
+
action: l.action ?? 'none',
|
|
381
382
|
request_count: l.requestCount,
|
|
382
383
|
rejected_count: l.rejectedCount ?? 0,
|
|
383
384
|
last_rejected_at: l.lastRejectedAt ?? null,
|
package/dist/cli.d.ts
CHANGED
|
@@ -105,6 +105,42 @@ export declare function parseBooleanEnv(value: string | undefined): boolean | un
|
|
|
105
105
|
* Set(['thinking','env']) — value "thinking,env" → preserve listed
|
|
106
106
|
*/
|
|
107
107
|
export declare function resolvePreserveOrchestrationTags(args: string[], env: string | undefined): Set<string> | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* The seat shape a running proxy returns from `/accounts`.
|
|
110
|
+
*
|
|
111
|
+
* Everything past `alias` is optional on purpose. A freshly installed CLI
|
|
112
|
+
* routinely queries a proxy still running the PREVIOUS release, whose seats
|
|
113
|
+
* predate whichever field the newest feature added — `sharesWindowWith`,
|
|
114
|
+
* `organizationId` and `grantedAt` all arrived that way (dario#1248 review).
|
|
115
|
+
* The payload is accepted on `mode` + `accounts` alone, so the renderer, not
|
|
116
|
+
* the fetch, is where a missing field would throw. Defaulting each one here
|
|
117
|
+
* keeps `accounts list --live` degrading to a thinner line instead of dying
|
|
118
|
+
* with a TypeError and skipping the on-disk fallback it advertises.
|
|
119
|
+
*/
|
|
120
|
+
export interface LiveSeat {
|
|
121
|
+
alias?: string;
|
|
122
|
+
status?: string;
|
|
123
|
+
action?: 'none' | 'wait' | 'regrant';
|
|
124
|
+
util5h?: number;
|
|
125
|
+
util7d?: number;
|
|
126
|
+
utilAgeMs?: number | null;
|
|
127
|
+
resetInMs?: number | null;
|
|
128
|
+
requestCount?: number;
|
|
129
|
+
rejectedCount?: number;
|
|
130
|
+
organizationId?: string | null;
|
|
131
|
+
sharesWindowWith?: string[];
|
|
132
|
+
grantedAt?: number | null;
|
|
133
|
+
}
|
|
134
|
+
export interface LivePayload {
|
|
135
|
+
mode?: string;
|
|
136
|
+
accounts?: LiveSeat[];
|
|
137
|
+
distinctWindows?: number;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Render the live pool listing. Pure — returns the lines rather than printing
|
|
141
|
+
* them, so a legacy payload can be driven straight through it in a test.
|
|
142
|
+
*/
|
|
143
|
+
export declare function formatLiveAccountsListing(payload: LivePayload, port: number, now: number): string[];
|
|
108
144
|
/**
|
|
109
145
|
* Decide whether this module is being invoked as the CLI entry point or
|
|
110
146
|
* imported as a library. Pure, exported for tests; the file-bottom uses
|
package/dist/cli.js
CHANGED
|
@@ -894,6 +894,52 @@ function parsePositiveIntFlag(prefix) {
|
|
|
894
894
|
}
|
|
895
895
|
return n;
|
|
896
896
|
}
|
|
897
|
+
/**
|
|
898
|
+
* Render the live pool listing. Pure — returns the lines rather than printing
|
|
899
|
+
* them, so a legacy payload can be driven straight through it in a test.
|
|
900
|
+
*/
|
|
901
|
+
export function formatLiveAccountsListing(payload, port, now) {
|
|
902
|
+
const seats = Array.isArray(payload.accounts) ? payload.accounts : [];
|
|
903
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
904
|
+
const pct = (n) => `${Math.round(num(n) * 100)}%`;
|
|
905
|
+
const mins = (ms) => {
|
|
906
|
+
const m = Math.max(1, Math.round(ms / 60_000));
|
|
907
|
+
return m >= 60 ? `${Math.floor(m / 60)}h ${m % 60}m` : `${m}m`;
|
|
908
|
+
};
|
|
909
|
+
const age = (ms) => typeof ms !== 'number' || !Number.isFinite(ms)
|
|
910
|
+
? 'never measured'
|
|
911
|
+
: ms < 60_000 ? `read ${Math.round(ms / 1000)}s ago` : `read ${mins(ms)} ago`;
|
|
912
|
+
const lines = [];
|
|
913
|
+
lines.push('');
|
|
914
|
+
lines.push(` dario — Accounts (live, from http://127.0.0.1:${port})`);
|
|
915
|
+
lines.push(' ────────────────');
|
|
916
|
+
lines.push('');
|
|
917
|
+
const windows = typeof payload.distinctWindows === 'number' ? payload.distinctWindows : seats.length;
|
|
918
|
+
lines.push(` Pool of ${seats.length} (${seats.length === 1 ? '1 seat' : seats.length + ' seats'} on ${windows} distinct window${windows === 1 ? '' : 's'})`);
|
|
919
|
+
lines.push('');
|
|
920
|
+
for (const s of seats) {
|
|
921
|
+
const alias = typeof s.alias === 'string' ? s.alias : '(unnamed)';
|
|
922
|
+
const rawStatus = typeof s.status === 'string' ? s.status : 'unknown';
|
|
923
|
+
const status = rawStatus === 'rejected' && typeof s.resetInMs === 'number' ? `rejected, back in ${mins(s.resetInMs)}` : rawStatus;
|
|
924
|
+
lines.push(` ${alias.padEnd(20)} ${status.padEnd(26)} 5h ${pct(s.util5h).padEnd(6)} 7d ${pct(s.util7d).padEnd(6)} ${age(s.utilAgeMs)}`);
|
|
925
|
+
// The one-word next step (dario#1244): a parked seat wants nothing from
|
|
926
|
+
// the operator; an auth-failure streak wants a re-grant.
|
|
927
|
+
const next = s.action === 'regrant' ? 'next: re-grant this seat (dario accounts remove + add)'
|
|
928
|
+
: s.action === 'wait' ? 'next: nothing, it comes back on its own'
|
|
929
|
+
: null;
|
|
930
|
+
const facts = [
|
|
931
|
+
`served ${num(s.requestCount)}`,
|
|
932
|
+
`429s ${num(s.rejectedCount)}`,
|
|
933
|
+
...(next ? [next] : []),
|
|
934
|
+
typeof s.organizationId === 'string' && s.organizationId ? `org ${s.organizationId.slice(0, 8)}…` : 'org not yet observed',
|
|
935
|
+
...(Array.isArray(s.sharesWindowWith) && s.sharesWindowWith.length > 0 ? [`shares its window with ${s.sharesWindowWith.join(', ')}`] : []),
|
|
936
|
+
];
|
|
937
|
+
lines.push(` ${''.padEnd(20)} ${facts.join(' · ')}`);
|
|
938
|
+
lines.push(` ${''.padEnd(20)} ${describeGrantAge(grantAge(typeof s.grantedAt === 'number' ? s.grantedAt : undefined, now))}`);
|
|
939
|
+
}
|
|
940
|
+
lines.push('');
|
|
941
|
+
return lines;
|
|
942
|
+
}
|
|
897
943
|
/**
|
|
898
944
|
* `dario accounts list --live` — the running proxy's view of the pool
|
|
899
945
|
* (dario#1244): status with its countdown, the reading and its age, 429s
|
|
@@ -924,34 +970,8 @@ async function accountsListLive() {
|
|
|
924
970
|
}
|
|
925
971
|
if (!payload || payload.mode !== 'pool' || !Array.isArray(payload.accounts))
|
|
926
972
|
return false;
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
930
|
-
const mins = (ms) => {
|
|
931
|
-
const m = Math.max(1, Math.round(ms / 60_000));
|
|
932
|
-
return m >= 60 ? `${Math.floor(m / 60)}h ${m % 60}m` : `${m}m`;
|
|
933
|
-
};
|
|
934
|
-
const age = (ms) => ms === null ? 'never measured' : ms < 60_000 ? `read ${Math.round(ms / 1000)}s ago` : `read ${mins(ms)} ago`;
|
|
935
|
-
console.log('');
|
|
936
|
-
console.log(` dario — Accounts (live, from http://127.0.0.1:${port})`);
|
|
937
|
-
console.log(' ────────────────');
|
|
938
|
-
console.log('');
|
|
939
|
-
const windows = payload.distinctWindows ?? seats.length;
|
|
940
|
-
console.log(` Pool of ${seats.length} (${seats.length === 1 ? '1 seat' : seats.length + ' seats'} on ${windows} distinct window${windows === 1 ? '' : 's'})`);
|
|
941
|
-
console.log('');
|
|
942
|
-
for (const s of seats) {
|
|
943
|
-
const status = s.status === 'rejected' && typeof s.resetInMs === 'number' ? `rejected, back in ${mins(s.resetInMs)}` : s.status;
|
|
944
|
-
console.log(` ${s.alias.padEnd(20)} ${status.padEnd(26)} 5h ${pct(s.util5h).padEnd(6)} 7d ${pct(s.util7d).padEnd(6)} ${age(s.utilAgeMs)}`);
|
|
945
|
-
const facts = [
|
|
946
|
-
`served ${s.requestCount}`,
|
|
947
|
-
`429s ${s.rejectedCount}`,
|
|
948
|
-
s.organizationId ? `org ${s.organizationId.slice(0, 8)}…` : 'org not yet observed',
|
|
949
|
-
...(s.sharesWindowWith.length > 0 ? [`shares its window with ${s.sharesWindowWith.join(', ')}`] : []),
|
|
950
|
-
];
|
|
951
|
-
console.log(` ${''.padEnd(20)} ${facts.join(' · ')}`);
|
|
952
|
-
console.log(` ${''.padEnd(20)} ${describeGrantAge(grantAge(s.grantedAt ?? undefined, now))}`);
|
|
953
|
-
}
|
|
954
|
-
console.log('');
|
|
973
|
+
for (const line of formatLiveAccountsListing(payload, port, Date.now()))
|
|
974
|
+
console.log(line);
|
|
955
975
|
return true;
|
|
956
976
|
}
|
|
957
977
|
async function accounts() {
|
package/dist/pool.d.ts
CHANGED
|
@@ -230,6 +230,22 @@ export declare function reportedAccountStatus(account: PoolAccount, now?: number
|
|
|
230
230
|
export declare function accountIneligibility(account: PoolAccount, now?: number): AccountIneligibility | null;
|
|
231
231
|
/** Boolean form of `accountIneligibility` — the router's eligibility filter. */
|
|
232
232
|
export declare function isAccountEligible(account: PoolAccount, now?: number): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* A seat parked on a 429 whose stated window has not rolled yet — the one
|
|
235
|
+
* state the router must never re-probe: the 429 named the reset, the clock
|
|
236
|
+
* has not reached it, and a probe can only 429 again. A rejection with no
|
|
237
|
+
* stated reset is NOT this: with nothing to expire, asking is the only way
|
|
238
|
+
* back, so it stays probeable (dario#1244).
|
|
239
|
+
*/
|
|
240
|
+
export declare function isParkedInLiveWindow(account: PoolAccount, now?: number): boolean;
|
|
241
|
+
/**
|
|
242
|
+
* The operator's next step for one seat, next to `status` on both listings
|
|
243
|
+
* (dario#1244 — "do I have to re-login?" should not need the docs table).
|
|
244
|
+
* `wait`: the seat comes back on its own (a live rate-limit window, or a
|
|
245
|
+
* single auth blip cooling down). `regrant`: an auth-failure streak, which is
|
|
246
|
+
* a dead refresh token. `none`: nothing to do.
|
|
247
|
+
*/
|
|
248
|
+
export declare function accountAction(account: PoolAccount, now?: number): 'none' | 'wait' | 'regrant';
|
|
233
249
|
export interface PoolStatus {
|
|
234
250
|
accounts: number;
|
|
235
251
|
healthy: number;
|
|
@@ -341,6 +357,18 @@ export declare class AccountPool {
|
|
|
341
357
|
* unified-buckets-only headroom — same behavior as before this PR.
|
|
342
358
|
*/
|
|
343
359
|
select(family?: string | null): PoolAccount | null;
|
|
360
|
+
/**
|
|
361
|
+
* When EVERY seat is parked inside a live rate-limit window: the epoch ms
|
|
362
|
+
* the earliest window rolls, i.e. the moment the pool can serve again
|
|
363
|
+
* without a probe. Null otherwise — including a pool mixing parked seats
|
|
364
|
+
* with an auth-cooling or token-expired one, which is not "all seats over
|
|
365
|
+
* their windows" and must not be reported (or cooled) as if it were; those
|
|
366
|
+
* pools stay on the existing unavailable handling (dario#1244, and the
|
|
367
|
+
* review on dario#1254 that caught the mixed case).
|
|
368
|
+
*/
|
|
369
|
+
parkedUntil(now?: number): number | null;
|
|
370
|
+
/** Seats currently parked inside a live window (dario#1244). */
|
|
371
|
+
parkedCount(now?: number): number;
|
|
344
372
|
/**
|
|
345
373
|
* Select with session stickiness. If `stickyKey` is already bound to a
|
|
346
374
|
* healthy account (not rejected, token not near expiry, headroom > 2%),
|
package/dist/pool.js
CHANGED
|
@@ -250,6 +250,31 @@ export function accountIneligibility(account, now = Date.now()) {
|
|
|
250
250
|
export function isAccountEligible(account, now = Date.now()) {
|
|
251
251
|
return accountIneligibility(account, now) === null;
|
|
252
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* A seat parked on a 429 whose stated window has not rolled yet — the one
|
|
255
|
+
* state the router must never re-probe: the 429 named the reset, the clock
|
|
256
|
+
* has not reached it, and a probe can only 429 again. A rejection with no
|
|
257
|
+
* stated reset is NOT this: with nothing to expire, asking is the only way
|
|
258
|
+
* back, so it stays probeable (dario#1244).
|
|
259
|
+
*/
|
|
260
|
+
export function isParkedInLiveWindow(account, now = Date.now()) {
|
|
261
|
+
const rl = account.rateLimit;
|
|
262
|
+
return rl.status === 'rejected' && rl.reset > 0 && rl.reset * 1000 > now;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The operator's next step for one seat, next to `status` on both listings
|
|
266
|
+
* (dario#1244 — "do I have to re-login?" should not need the docs table).
|
|
267
|
+
* `wait`: the seat comes back on its own (a live rate-limit window, or a
|
|
268
|
+
* single auth blip cooling down). `regrant`: an auth-failure streak, which is
|
|
269
|
+
* a dead refresh token. `none`: nothing to do.
|
|
270
|
+
*/
|
|
271
|
+
export function accountAction(account, now = Date.now()) {
|
|
272
|
+
if (isInAuthCooldown(account, now))
|
|
273
|
+
return account.consecutiveAuthFailures >= 2 ? 'regrant' : 'wait';
|
|
274
|
+
if (isParkedInLiveWindow(account, now))
|
|
275
|
+
return 'wait';
|
|
276
|
+
return 'none';
|
|
277
|
+
}
|
|
253
278
|
/**
|
|
254
279
|
* Resolve the pool strategy from an explicit value (CLI flag / config file,
|
|
255
280
|
* already precedence-merged by the caller) with `DARIO_POOL_STRATEGY` as
|
|
@@ -515,20 +540,45 @@ export class AccountPool {
|
|
|
515
540
|
}
|
|
516
541
|
return pickMaxHeadroom(eligible, family);
|
|
517
542
|
}
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
//
|
|
528
|
-
|
|
529
|
-
|
|
543
|
+
// No seat is eligible. A seat parked inside a live window is not
|
|
544
|
+
// re-probed: its 429 named the reset, the clock has not reached it, and a
|
|
545
|
+
// probe there is one upstream round trip that can only 429 again — on the
|
|
546
|
+
// dario#1244 gateway that was 500 probes of one seat inside a single
|
|
547
|
+
// window, `rejectedCount` climbing by one each time and the operator
|
|
548
|
+
// reading it as a seat that needed a re-login. The caller reads
|
|
549
|
+
// `parkedUntil()` and answers the client itself; the seat returns on its
|
|
550
|
+
// own when the window rolls (`rateLimitWindowPassed` makes it eligible
|
|
551
|
+
// again). Auth-cooldown seats are skipped for the same reason: upstream
|
|
552
|
+
// already rejected their tokens.
|
|
553
|
+
//
|
|
554
|
+
// What is left — a rejection with no stated reset (nothing to expire, so
|
|
555
|
+
// asking is the only way back) or an expiring token — is tried least-used
|
|
556
|
+
// first, as before.
|
|
557
|
+
const probeable = all.filter(a => !isInAuthCooldown(a, now) && !isParkedInLiveWindow(a, now));
|
|
558
|
+
if (probeable.length === 0)
|
|
530
559
|
return null;
|
|
531
|
-
return
|
|
560
|
+
return probeable.reduce((a, b) => a.requestCount < b.requestCount ? a : b);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* When EVERY seat is parked inside a live rate-limit window: the epoch ms
|
|
564
|
+
* the earliest window rolls, i.e. the moment the pool can serve again
|
|
565
|
+
* without a probe. Null otherwise — including a pool mixing parked seats
|
|
566
|
+
* with an auth-cooling or token-expired one, which is not "all seats over
|
|
567
|
+
* their windows" and must not be reported (or cooled) as if it were; those
|
|
568
|
+
* pools stay on the existing unavailable handling (dario#1244, and the
|
|
569
|
+
* review on dario#1254 that caught the mixed case).
|
|
570
|
+
*/
|
|
571
|
+
parkedUntil(now = Date.now()) {
|
|
572
|
+
if (this.accounts.size === 0)
|
|
573
|
+
return null;
|
|
574
|
+
const all = [...this.accounts.values()];
|
|
575
|
+
if (!all.every(a => isParkedInLiveWindow(a, now)))
|
|
576
|
+
return null;
|
|
577
|
+
return Math.min(...all.map(a => a.rateLimit.reset * 1000));
|
|
578
|
+
}
|
|
579
|
+
/** Seats currently parked inside a live window (dario#1244). */
|
|
580
|
+
parkedCount(now = Date.now()) {
|
|
581
|
+
return [...this.accounts.values()].filter(a => isParkedInLiveWindow(a, now)).length;
|
|
532
582
|
}
|
|
533
583
|
/**
|
|
534
584
|
* Select with session stickiness. If `stickyKey` is already bound to a
|
|
@@ -645,8 +695,13 @@ export class AccountPool {
|
|
|
645
695
|
}
|
|
646
696
|
return pickMaxHeadroom(eligible, family);
|
|
647
697
|
}
|
|
648
|
-
|
|
649
|
-
|
|
698
|
+
// Mid-flight: the seats a 429 could still hand this request to. A seat
|
|
699
|
+
// parked inside a live window is not one of them — on the dario#1244
|
|
700
|
+
// gateway every request walked all six parked seats, six guaranteed 429s
|
|
701
|
+
// a request. Cool-downs are skipped for the same reason.
|
|
702
|
+
const probeable = candidates.filter(a => !isInAuthCooldown(a, now) && !isParkedInLiveWindow(a, now));
|
|
703
|
+
if (probeable.length > 0) {
|
|
704
|
+
return probeable.reduce((a, b) => a.requestCount < b.requestCount ? a : b);
|
|
650
705
|
}
|
|
651
706
|
return null;
|
|
652
707
|
}
|
package/dist/proxy.js
CHANGED
|
@@ -12,7 +12,7 @@ import { darioVersion } from './version.js';
|
|
|
12
12
|
import { buildCCRequest, applyCcPromptCaching, isGenuineCCClient, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, overlayTemplateHeaderValues, forwardClientCCIdentityHeaders, isMcpToolName, CC_TEMPLATE, effectiveCacheControl, withForced1hBeta } from './cc-template.js';
|
|
13
13
|
import { stampCch, hasCchSeed } from './cch.js';
|
|
14
14
|
import { describeTemplate, detectDrift, checkCCCompat, probeInstalledCCVersion } from './live-fingerprint.js';
|
|
15
|
-
import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, accountIneligibility, reportedAccountStatus, reconcilePoolAccounts, resolvePoolStrategy, utilFreshness, rateLimitWindow, describeRateLimitSnapshot, windowPeers, distinctWindows } from './pool.js';
|
|
15
|
+
import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, accountIneligibility, reportedAccountStatus, reconcilePoolAccounts, resolvePoolStrategy, utilFreshness, rateLimitWindow, describeRateLimitSnapshot, windowPeers, distinctWindows, accountAction } from './pool.js';
|
|
16
16
|
import { PoolSync, DEFAULT_POOL_SYNC_INTERVAL_MS } from './pool-sync.js';
|
|
17
17
|
import { Analytics, billingBucketFromClaim, formatUsageLogLine, SUBSCRIPTION_CLAIMS, consumerFromHeader, consumerFromBody, CONSUMER_HEADER, CODEX_CLAIM } from './analytics.js';
|
|
18
18
|
import { OverageGuard, buildHaltErrorBody } from './overage-guard.js';
|
|
@@ -33,7 +33,7 @@ import { selectPoolFallbackModels } from './pool-fallback-tier.js';
|
|
|
33
33
|
import { RequestQueue, QueueFullError, QueueTimeoutError, DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_QUEUED, DEFAULT_QUEUE_TIMEOUT_MS } from './request-queue.js';
|
|
34
34
|
import { redactSecrets } from './redact.js';
|
|
35
35
|
import { BAKED_BASE_MODELS, withLongContextVariants, buildOpenAIModelsList, getModelCatalog, getCachedBases, resolveAliasAgainst, prewarmModelCatalog, retryModelCatalogNow, isSuspendedModel } from './model-catalog.js';
|
|
36
|
-
import { classifyUpstreamRejection, diagnosticSnippet } from './upstream-rejection.js';
|
|
36
|
+
import { classifyUpstreamRejection, diagnosticSnippet, POOL_PARKED } from './upstream-rejection.js';
|
|
37
37
|
import { ProviderCooldowns, canAttempt, allProvidersCooled, cooldownRetryAfterMs, parseRetryAfterMs, ALL_PROVIDERS_RATE_LIMITED, } from './provider-cooldown.js';
|
|
38
38
|
const ANTHROPIC_API = 'https://api.anthropic.com';
|
|
39
39
|
const DEFAULT_PORT = 3456;
|
|
@@ -1655,6 +1655,9 @@ export async function startProxy(opts = {}) {
|
|
|
1655
1655
|
overlayTemplateHeaderValues(staticHeaders, CC_TEMPLATE.header_values);
|
|
1656
1656
|
}
|
|
1657
1657
|
let requestCount = 0;
|
|
1658
|
+
// dario#1244: the "pool parked" line is logged on the transition into the
|
|
1659
|
+
// state, not on every request that arrives while it holds.
|
|
1660
|
+
let poolParkedAnnounced = false;
|
|
1658
1661
|
const queue = new RequestQueue({
|
|
1659
1662
|
maxConcurrent: opts.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
1660
1663
|
maxQueued: opts.maxQueued ?? DEFAULT_MAX_QUEUED,
|
|
@@ -2150,6 +2153,7 @@ export async function startProxy(opts = {}) {
|
|
|
2150
2153
|
...rateLimitWindow(a.rateLimit, snapNow),
|
|
2151
2154
|
claim: a.rateLimit.claim,
|
|
2152
2155
|
status: reportedAccountStatus(a, snapNow),
|
|
2156
|
+
action: accountAction(a, snapNow),
|
|
2153
2157
|
requestCount: a.requestCount,
|
|
2154
2158
|
rejectedCount: a.rejectedCount,
|
|
2155
2159
|
lastRejectedAt: a.lastRejectedAt ?? null,
|
|
@@ -2258,6 +2262,8 @@ export async function startProxy(opts = {}) {
|
|
|
2258
2262
|
...rateLimitWindow(a.rateLimit, now),
|
|
2259
2263
|
claim: a.rateLimit.claim,
|
|
2260
2264
|
status: reportedAccountStatus(a, now),
|
|
2265
|
+
// The one-word next step: none · wait · regrant (dario#1244).
|
|
2266
|
+
action: accountAction(a, now),
|
|
2261
2267
|
requestCount: a.requestCount,
|
|
2262
2268
|
// 429s answered — the attempts requestCount does not count, so a
|
|
2263
2269
|
// parked seat no longer reads as one that was never called.
|
|
@@ -2625,6 +2631,31 @@ export async function startProxy(opts = {}) {
|
|
|
2625
2631
|
//
|
|
2626
2632
|
// Shared because two callers must agree: the selector, and the dispatch
|
|
2627
2633
|
// below for a request the selector DEFERRED but no provider could serve.
|
|
2634
|
+
/**
|
|
2635
|
+
* Every seat in the pool is parked inside a live window (dario#1244).
|
|
2636
|
+
* The old fallback re-probed the earliest-reset seat on every request:
|
|
2637
|
+
* one upstream round trip per request that could only 429,
|
|
2638
|
+
* `rejectedCount` climbing by one each time — 500 on one seat inside a
|
|
2639
|
+
* single window on the reporter's gateway — and the client waiting on a
|
|
2640
|
+
* verdict dario already held. Answer it here: 429, `retry-after` at the
|
|
2641
|
+
* earliest reset, the marker in the same channel the other rejection
|
|
2642
|
+
* classes use, and nothing sent upstream.
|
|
2643
|
+
*/
|
|
2644
|
+
const writePoolParked = (untilMs) => {
|
|
2645
|
+
const retryAfterSec = Math.max(1, Math.ceil((untilMs - Date.now()) / 1000));
|
|
2646
|
+
res.writeHead(429, {
|
|
2647
|
+
...JSON_HEADERS,
|
|
2648
|
+
'retry-after': String(retryAfterSec),
|
|
2649
|
+
'x-dario-upstream-rejection': POOL_PARKED,
|
|
2650
|
+
});
|
|
2651
|
+
res.end(JSON.stringify({
|
|
2652
|
+
error: {
|
|
2653
|
+
type: 'rate_limit_error',
|
|
2654
|
+
message: `All ${pool.size} pool seat${pool.size === 1 ? '' : 's'} are over their rate-limit windows; the earliest resets in ${retryAfterSec}s. Nothing was sent upstream.`,
|
|
2655
|
+
},
|
|
2656
|
+
reason: POOL_PARKED,
|
|
2657
|
+
}));
|
|
2658
|
+
};
|
|
2628
2659
|
const writePoolUnavailable = () => {
|
|
2629
2660
|
res.writeHead(503, JSON_HEADERS);
|
|
2630
2661
|
res.end(JSON.stringify(pool.size === 0
|
|
@@ -2738,6 +2769,20 @@ export async function startProxy(opts = {}) {
|
|
|
2738
2769
|
return true;
|
|
2739
2770
|
}
|
|
2740
2771
|
poolAccount = pool.select();
|
|
2772
|
+
if (poolAccount)
|
|
2773
|
+
poolParkedAnnounced = false;
|
|
2774
|
+
// Every seat parked inside a live window (dario#1244): cool the
|
|
2775
|
+
// provider to the earliest reset so a fallback chain sees the Claude
|
|
2776
|
+
// half as what it is, say so once, and — unless a fallback is armed —
|
|
2777
|
+
// answer the client here instead of spending a probe that can only 429.
|
|
2778
|
+
const parkedUntil = poolAccount ? null : pool.parkedUntil();
|
|
2779
|
+
if (parkedUntil !== null) {
|
|
2780
|
+
providerCooldowns.note('claude', parkedUntil - Date.now());
|
|
2781
|
+
if (!poolParkedAnnounced) {
|
|
2782
|
+
poolParkedAnnounced = true;
|
|
2783
|
+
console.error(`[dario] #${requestCount} pool parked: all ${pool.size} seats are over their rate-limit windows, earliest resets in ${Math.max(1, Math.ceil((parkedUntil - Date.now()) / 60000))}m — answering 429 locally until then, nothing sent upstream`);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2741
2786
|
if (!poolAccount) {
|
|
2742
2787
|
// Pool-exhausted fallback: when armed, the pool HAS accounts (all
|
|
2743
2788
|
// drained / cooling), and the client speaks OpenAI shape, defer —
|
|
@@ -2763,7 +2808,10 @@ export async function startProxy(opts = {}) {
|
|
|
2763
2808
|
// see, not traffic to quietly re-bill somewhere else.
|
|
2764
2809
|
const fallbackViable = poolFallbackModels.length > 0 && pool.size > 0;
|
|
2765
2810
|
if (!fallbackViable) {
|
|
2766
|
-
|
|
2811
|
+
if (parkedUntil !== null)
|
|
2812
|
+
writePoolParked(parkedUntil);
|
|
2813
|
+
else
|
|
2814
|
+
writePoolUnavailable();
|
|
2767
2815
|
return false;
|
|
2768
2816
|
}
|
|
2769
2817
|
}
|
|
@@ -3295,6 +3343,13 @@ export async function startProxy(opts = {}) {
|
|
|
3295
3343
|
// would fall through to the Claude path with no account and an empty
|
|
3296
3344
|
// bearer token, turning a clean 503 into a confusing upstream 401.
|
|
3297
3345
|
if (!upstreamApiKey && !poolAccount) {
|
|
3346
|
+
// A fallback was armed but nothing could serve, and the pool itself is
|
|
3347
|
+
// parked: the exact reset beats a cool-down estimate (dario#1244).
|
|
3348
|
+
const parkedNow = pool.parkedUntil();
|
|
3349
|
+
if (parkedNow !== null) {
|
|
3350
|
+
writePoolParked(parkedNow);
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3298
3353
|
// A chain where every entry is cooling is a rate limit, not a
|
|
3299
3354
|
// misconfiguration — say so in the machine-readable way, once.
|
|
3300
3355
|
if (allProvidersCooled(poolFallbackModels.length > 0 ? ['codex', 'claude'] : ['claude'], providerCooldowns)) {
|
|
@@ -11,6 +11,12 @@ export interface UpstreamRejection {
|
|
|
11
11
|
* every "this request was not served" verdict.
|
|
12
12
|
*/
|
|
13
13
|
export declare const MODEL_UNROUTABLE = "model_unroutable";
|
|
14
|
+
/**
|
|
15
|
+
* Every seat in the pool is parked inside a live rate-limit window, so dario
|
|
16
|
+
* answered 429 itself, with `retry-after` at the earliest reset, and sent
|
|
17
|
+
* nothing upstream (dario#1244).
|
|
18
|
+
*/
|
|
19
|
+
export declare const POOL_PARKED = "pool_parked";
|
|
14
20
|
/** Classify subscription entitlement failures separately from temporary quota exhaustion. */
|
|
15
21
|
export declare function classifyUpstreamRejection(status: number, body: string): UpstreamRejection;
|
|
16
22
|
/** Operator action paired with the failure class. Never suggest credential churn for billing. */
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* every "this request was not served" verdict.
|
|
7
7
|
*/
|
|
8
8
|
export const MODEL_UNROUTABLE = 'model_unroutable';
|
|
9
|
+
/**
|
|
10
|
+
* Every seat in the pool is parked inside a live rate-limit window, so dario
|
|
11
|
+
* answered 429 itself, with `retry-after` at the earliest reset, and sent
|
|
12
|
+
* nothing upstream (dario#1244).
|
|
13
|
+
*/
|
|
14
|
+
export const POOL_PARKED = 'pool_parked';
|
|
9
15
|
/** Classify subscription entitlement failures separately from temporary quota exhaustion. */
|
|
10
16
|
export function classifyUpstreamRejection(status, body) {
|
|
11
17
|
const normalized = body.toLowerCase();
|
package/docs/drift-monitor.md
CHANGED
|
@@ -113,7 +113,8 @@ is a proof that the tools / system_prompt / beta headers / field orders are
|
|
|
113
113
|
byte-identical at the live version, so only the version string moves — the
|
|
114
114
|
same deterministic-bump risk class `cc-drift-watch.yml` already auto-merges for
|
|
115
115
|
`SUPPORTED_CC_RANGE.maxTested`. Auto-merge still gates on the required checks
|
|
116
|
-
(build ×3,
|
|
116
|
+
(build ×3, live-test, CodeQL, actionlint, validate-package-json); compat runs
|
|
117
|
+
alongside but is not required. A red required check leaves the PR open
|
|
117
118
|
with the bot branch preserved. A shape rebake (exit 2) changes the wire-shape
|
|
118
119
|
contract, so a human reviews compat-test + the diff before merging.
|
|
119
120
|
|
|
@@ -93,7 +93,7 @@ curl http://localhost:3456/analytics # per-account / per-model stats, burn ra
|
|
|
93
93
|
|
|
94
94
|
## Reading a seat's `status`
|
|
95
95
|
|
|
96
|
-
`GET /accounts` (and the admin API's `GET /admin/accounts`, in snake_case) report one `status` per seat. It is the routing verdict, and every value comes with the fields that explain it.
|
|
96
|
+
`GET /accounts` (and the admin API's `GET /admin/accounts`, in snake_case) report one `status` per seat. It is the routing verdict, and every value comes with the fields that explain it. Next to it, `action` is the last column of this table in one word: `none`, `wait` (the seat comes back on its own; `resetInMs` says when) or `regrant` (an auth-failure streak, which is a dead refresh token).
|
|
97
97
|
|
|
98
98
|
| `status` | What it means | What to do |
|
|
99
99
|
|---|---|---|
|
|
@@ -102,6 +102,8 @@ curl http://localhost:3456/analytics # per-account / per-model stats, burn ra
|
|
|
102
102
|
| `unknown` | No current observation: a seat that has served nothing yet, or a rejection whose window has rolled (`resetInMs: 0`) and that nothing has measured since. | Nothing; the next request measures it. |
|
|
103
103
|
| `auth-cooldown` | Upstream answered 401/403 or `invalid_grant`. `consecutiveAuthFailures` tells a blip (1) from a dead refresh token (a streak); the cool-down doubles with the streak, from 1 minute to 30. | A streak means re-grant the seat — `dario accounts remove` + `add`, or the admin login flow under the same alias. A new grant starts the seat fresh: no carried-over cool-down, rejection or identity. See [Refresh-token grant age](#refresh-token-grant-age) for the 28-day wall behind most streaks. |
|
|
104
104
|
|
|
105
|
+
**When every seat is parked.** A pool whose seats are all `rejected` inside live windows does not probe them again: dario answers the request itself with `429`, `retry-after` set to the earliest reset, `x-dario-upstream-rejection: pool_parked`, and nothing sent upstream. One log line marks the transition (`pool parked: all 6 seats are over their rate-limit windows, earliest resets in 21m`). Before 6.0.35 every such request re-probed the earliest-reset seat, so `rejectedCount` on that seat grew by one per request — a seat reading `rejected_count: 500` next to `request_count: 1` was that, not a seat that needed a re-login. With a `--pool-fallback` armed, the request goes to the fallback instead, as before.
|
|
106
|
+
|
|
105
107
|
The proxy logs every parking as it happens, once per window: `rate limited (429) on account "spare": 5h 104%, 7d 25%, claim five_hour, resets in 37m — parked until the window rolls`. The re-probes the all-exhausted fallback makes of an already-parked seat are logged only under `-v`.
|
|
106
108
|
|
|
107
109
|
`dario accounts list --live` prints the same view from the running proxy — status with its countdown, the reading and its age, requests served and 429s answered, the organization, shared windows, grant age — where the plain `dario accounts list` only knows what is on disk.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.36",
|
|
4
4
|
"description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, with automatic failover when one hits its limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|