@askalf/dario 6.6.3 → 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.
@@ -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.3",
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": {