@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 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).
@@ -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;
@@ -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.2",
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": {