@askalf/dario 6.6.2 → 6.6.4
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 +9 -1
- package/dist/doctor-core.d.ts +27 -0
- package/dist/doctor-core.js +92 -0
- package/docs/api-equivalent-spend.md +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -368,7 +368,7 @@ Type `dario` with no arguments for a full-screen control panel: live request str
|
|
|
368
368
|
|
|
369
369
|
<picture>
|
|
370
370
|
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/tui-analytics-dark.svg">
|
|
371
|
-
<img alt="The dario TUI Analytics tab: requests per minute, tokens in and out, thinking tokens, average latency, subscription percentage, a per-model bar chart, per-account rate-limit bars for the 5-hour and 7-day windows, and a billing breakdown." src=".github/readme/tui-analytics-light.svg" width="100%">
|
|
371
|
+
<img alt="The dario TUI Analytics tab: requests per minute, tokens in and out, thinking tokens, average latency, subscription percentage, the lifetime API-equivalent spend from the ledger, a per-model bar chart, per-account rate-limit bars for the 5-hour and 7-day windows, and a billing breakdown." src=".github/readme/tui-analytics-light.svg" width="100%">
|
|
372
372
|
</picture>
|
|
373
373
|
|
|
374
374
|
<sub>Both screenshots are rendered from the real TUI against a fixture proxy by <a href="scripts/readme/tui.mjs"><code>scripts/readme/tui.mjs</code></a>, so a layout change shows up here instead of rotting a mock-up. The numbers are illustrative; the pixels are not.</sub>
|
|
@@ -663,6 +663,14 @@ Two easy ways to help beyond code: **star the repo**, the clearest signal this i
|
|
|
663
663
|
| [@anupamme](https://github.com/anupamme) | Refresh-lock ownership by server-issued lock id ([#1059](https://github.com/askalf/dario/pull/1059)) |
|
|
664
664
|
| [@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)) |
|
|
665
665
|
|
|
666
|
+
### Sponsors
|
|
667
|
+
|
|
668
|
+
<!-- sponsors:start -->
|
|
669
|
+
dario is funded by its users through [GitHub Sponsors](https://github.com/sponsors/askalf) — the live-test seats it is checked against before every release are the biggest line item. Sponsors at $25/month and up are listed here.
|
|
670
|
+
<!-- sponsors:end -->
|
|
671
|
+
|
|
672
|
+
<sub>This block and the thank-you in each release's notes come from <a href="scripts/sponsors.mjs"><code>scripts/sponsors.mjs</code></a>, which reads the public sponsor list; <code>sponsors-readme.yml</code> opens a PR when it changes. Private sponsors are never named.</sub>
|
|
673
|
+
|
|
666
674
|
## Disclaimers
|
|
667
675
|
|
|
668
676
|
**dario is an independent, unofficial, third-party project.** Not affiliated with, endorsed by, or sponsored by Anthropic, OpenAI, or any vendor referenced here. Provided as-is, no warranty. You are solely responsible for compliance with your subscription's terms, the security of your credentials, and the content you send through the proxy. Not for safety-critical, regulated, or production environments without your own review. Full text: [DISCLAIMER.md](DISCLAIMER.md).
|
package/dist/doctor-core.d.ts
CHANGED
|
@@ -52,6 +52,33 @@ export declare function continuationReadiness(input: {
|
|
|
52
52
|
status: CheckStatus;
|
|
53
53
|
detail: string;
|
|
54
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Ledger readiness (v6.6.4). The ledger is a file the proxy writes a few
|
|
57
|
+
* seconds after each request; the number it feeds (`dario usage`, the TUI's
|
|
58
|
+
* API-equivalent row) only ever moves if that write lands. A bind mount that
|
|
59
|
+
* came up read-only, a home directory the proxy cannot create, a file a
|
|
60
|
+
* root-owned rig left behind — each of these looks exactly like "no traffic"
|
|
61
|
+
* from the outside. This row says whether the file can be written, when it
|
|
62
|
+
* last was, and what it holds. File and configuration only: the proxy's own
|
|
63
|
+
* unflushed records are not visible here.
|
|
64
|
+
*/
|
|
65
|
+
export declare function ledgerReadiness(input: {
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
path: string;
|
|
68
|
+
exists: boolean;
|
|
69
|
+
writable: boolean;
|
|
70
|
+
/** Why the file could not be parsed, when it exists and could not be. */
|
|
71
|
+
parseError?: string;
|
|
72
|
+
/** Epoch ms of the file's `updated` stamp, when it parsed. */
|
|
73
|
+
updatedAtMs?: number;
|
|
74
|
+
requests?: number;
|
|
75
|
+
apiEquivalentCost?: number;
|
|
76
|
+
since?: string;
|
|
77
|
+
nowMs?: number;
|
|
78
|
+
}): {
|
|
79
|
+
status: CheckStatus;
|
|
80
|
+
detail: string;
|
|
81
|
+
};
|
|
55
82
|
export declare function failoverReadiness(input: {
|
|
56
83
|
chain: readonly string[];
|
|
57
84
|
codexAccounts: number;
|
package/dist/doctor-core.js
CHANGED
|
@@ -64,6 +64,50 @@ export function continuationReadiness(input) {
|
|
|
64
64
|
: 'the chain has nowhere to go for a second hop (see Failover)'),
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Ledger readiness (v6.6.4). The ledger is a file the proxy writes a few
|
|
69
|
+
* seconds after each request; the number it feeds (`dario usage`, the TUI's
|
|
70
|
+
* API-equivalent row) only ever moves if that write lands. A bind mount that
|
|
71
|
+
* came up read-only, a home directory the proxy cannot create, a file a
|
|
72
|
+
* root-owned rig left behind — each of these looks exactly like "no traffic"
|
|
73
|
+
* from the outside. This row says whether the file can be written, when it
|
|
74
|
+
* last was, and what it holds. File and configuration only: the proxy's own
|
|
75
|
+
* unflushed records are not visible here.
|
|
76
|
+
*/
|
|
77
|
+
export function ledgerReadiness(input) {
|
|
78
|
+
if (!input.enabled) {
|
|
79
|
+
return { status: 'info', detail: 'off — no lifetime API-equivalent numbers (unset DARIO_LEDGER / drop --no-ledger)' };
|
|
80
|
+
}
|
|
81
|
+
if (!input.exists) {
|
|
82
|
+
return input.writable
|
|
83
|
+
? { status: 'ok', detail: `no file yet at ${input.path} — it appears after the first request through the proxy` }
|
|
84
|
+
: { status: 'warn', detail: `cannot write ${input.path} — the ledger will never record anything (check the directory's owner and mount)` };
|
|
85
|
+
}
|
|
86
|
+
if (input.parseError) {
|
|
87
|
+
return { status: 'warn', detail: `${input.path} is unreadable (${input.parseError}) — the proxy moves it aside and starts fresh on its next start; the history in it is lost` };
|
|
88
|
+
}
|
|
89
|
+
const now = input.nowMs ?? Date.now();
|
|
90
|
+
const age = typeof input.updatedAtMs === 'number' ? Math.max(0, now - input.updatedAtMs) : null;
|
|
91
|
+
const ageText = age === null ? 'last write unknown' : `last write ${describeAge(age)} ago`;
|
|
92
|
+
const usd = typeof input.apiEquivalentCost === 'number' ? `$${input.apiEquivalentCost >= 100 ? Math.round(input.apiEquivalentCost).toLocaleString('en-US') : input.apiEquivalentCost.toFixed(2)}` : '$?';
|
|
93
|
+
const body = `${(input.requests ?? 0).toLocaleString('en-US')} requests since ${(input.since ?? '').slice(0, 10) || '?'}, ${usd} API-equivalent; ${ageText}`;
|
|
94
|
+
if (!input.writable) {
|
|
95
|
+
return { status: 'warn', detail: `${body} — file is read-only now, so nothing since then is being saved (${input.path})` };
|
|
96
|
+
}
|
|
97
|
+
return { status: 'ok', detail: `${body} (${input.path})` };
|
|
98
|
+
}
|
|
99
|
+
function describeAge(ms) {
|
|
100
|
+
const s = Math.round(ms / 1000);
|
|
101
|
+
if (s < 60)
|
|
102
|
+
return `${s}s`;
|
|
103
|
+
const m = Math.round(s / 60);
|
|
104
|
+
if (m < 60)
|
|
105
|
+
return `${m}m`;
|
|
106
|
+
const h = Math.round(m / 60);
|
|
107
|
+
if (h < 48)
|
|
108
|
+
return `${h}h`;
|
|
109
|
+
return `${Math.round(h / 24)}d`;
|
|
110
|
+
}
|
|
67
111
|
export function failoverReadiness(input) {
|
|
68
112
|
const { chain, codexAccounts, backends } = input;
|
|
69
113
|
const hasCodex = codexAccounts > 0;
|
|
@@ -1200,6 +1244,54 @@ export async function runChecks(opts = {}) {
|
|
|
1200
1244
|
catch (err) {
|
|
1201
1245
|
checks.push({ status: 'warn', label: 'Failover', detail: `check failed: ${err.message}` });
|
|
1202
1246
|
}
|
|
1247
|
+
// ---- Ledger (v6.6.4) — see ledgerReadiness() for the why. The default
|
|
1248
|
+
// port's file; DARIO_LEDGER_PATH is honoured like the proxy honours it.
|
|
1249
|
+
try {
|
|
1250
|
+
const { resolveLedgerPath, ledgerDisabledByEnv, readLedgerFile, summarizeLedger } = await import('./ledger.js');
|
|
1251
|
+
const { access, constants } = await import('node:fs/promises');
|
|
1252
|
+
const path = resolveLedgerPath(3456);
|
|
1253
|
+
const enabled = !ledgerDisabledByEnv();
|
|
1254
|
+
let exists = false;
|
|
1255
|
+
let writable = false;
|
|
1256
|
+
try {
|
|
1257
|
+
await access(path, constants.F_OK);
|
|
1258
|
+
exists = true;
|
|
1259
|
+
}
|
|
1260
|
+
catch { /* no file yet */ }
|
|
1261
|
+
// The proxy creates missing directories on its first flush, so test the
|
|
1262
|
+
// nearest ancestor that exists — a fresh install has no ~/.dario yet.
|
|
1263
|
+
let probe = exists ? path : dirname(path);
|
|
1264
|
+
while (!exists) {
|
|
1265
|
+
try {
|
|
1266
|
+
await access(probe, constants.F_OK);
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
1269
|
+
catch { /* climb */ }
|
|
1270
|
+
const up = dirname(probe);
|
|
1271
|
+
if (up === probe)
|
|
1272
|
+
break;
|
|
1273
|
+
probe = up;
|
|
1274
|
+
}
|
|
1275
|
+
try {
|
|
1276
|
+
await access(probe, constants.W_OK);
|
|
1277
|
+
writable = true;
|
|
1278
|
+
}
|
|
1279
|
+
catch { /* not writable */ }
|
|
1280
|
+
const { file, error } = exists ? await readLedgerFile(path) : { file: null, error: undefined };
|
|
1281
|
+
const summary = file ? summarizeLedger(file, path) : null;
|
|
1282
|
+
const verdict = ledgerReadiness({
|
|
1283
|
+
enabled, path, exists, writable,
|
|
1284
|
+
parseError: error,
|
|
1285
|
+
updatedAtMs: file ? Date.parse(file.updated) : undefined,
|
|
1286
|
+
requests: summary?.requests,
|
|
1287
|
+
apiEquivalentCost: summary?.apiEquivalentCost,
|
|
1288
|
+
since: summary?.since,
|
|
1289
|
+
});
|
|
1290
|
+
checks.push({ status: verdict.status, label: 'Ledger', detail: verdict.detail });
|
|
1291
|
+
}
|
|
1292
|
+
catch (err) {
|
|
1293
|
+
checks.push({ status: 'warn', label: 'Ledger', detail: `check failed: ${err.message}` });
|
|
1294
|
+
}
|
|
1203
1295
|
// ---- CC sub-agent (v3.26, direction #2)
|
|
1204
1296
|
try {
|
|
1205
1297
|
const { loadSubagentStatus } = await import('./subagent.js');
|
|
@@ -60,6 +60,18 @@ is never overwritten in place. Days past 730 roll off the front.
|
|
|
60
60
|
The test suite pins `DARIO_LEDGER_PATH` to a temp directory, so a suite run
|
|
61
61
|
does not add stub traffic to the operator's file.
|
|
62
62
|
|
|
63
|
+
`dario doctor` has a **Ledger** row (6.6.4): whether the default port's file
|
|
64
|
+
(or `DARIO_LEDGER_PATH`) can be written, when it was last written, and what
|
|
65
|
+
it holds. A bind mount that came up read-only, a home the proxy cannot
|
|
66
|
+
create, a file a root-owned rig left behind — from the outside each looks
|
|
67
|
+
exactly like "no traffic", and the number stops moving without a word. The
|
|
68
|
+
row reads the file only; records the proxy has not flushed yet are not in it.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
[ OK ] Ledger 160 requests since 2026-09-12, $1.74 API-equivalent; last write 3s ago (/home/dario/.dario/ledger.json)
|
|
72
|
+
[WARN] Ledger cannot write /home/dario/.dario/ledger.json — the ledger will never record anything (check the directory's owner and mount)
|
|
73
|
+
```
|
|
74
|
+
|
|
63
75
|
## Reading it
|
|
64
76
|
|
|
65
77
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.6.
|
|
3
|
+
"version": "6.6.4",
|
|
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": {
|