@askalf/dario 6.0.35 → 6.0.37

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/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,40 +970,8 @@ async function accountsListLive() {
924
970
  }
925
971
  if (!payload || payload.mode !== 'pool' || !Array.isArray(payload.accounts))
926
972
  return false;
927
- const seats = payload.accounts;
928
- const now = Date.now();
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
- // The one-word next step (dario#1244): a parked seat wants nothing from
946
- // the operator; an auth-failure streak wants a re-grant.
947
- const next = s.action === 'regrant' ? 'next: re-grant this seat (dario accounts remove + add)'
948
- : s.action === 'wait' ? 'next: nothing, it comes back on its own'
949
- : null;
950
- const facts = [
951
- `served ${s.requestCount}`,
952
- `429s ${s.rejectedCount}`,
953
- ...(next ? [next] : []),
954
- s.organizationId ? `org ${s.organizationId.slice(0, 8)}…` : 'org not yet observed',
955
- ...(s.sharesWindowWith.length > 0 ? [`shares its window with ${s.sharesWindowWith.join(', ')}`] : []),
956
- ];
957
- console.log(` ${''.padEnd(20)} ${facts.join(' · ')}`);
958
- console.log(` ${''.padEnd(20)} ${describeGrantAge(grantAge(s.grantedAt ?? undefined, now))}`);
959
- }
960
- console.log('');
973
+ for (const line of formatLiveAccountsListing(payload, port, Date.now()))
974
+ console.log(line);
961
975
  return true;
962
976
  }
963
977
  async function accounts() {
package/dist/version.js CHANGED
@@ -1,28 +1,42 @@
1
1
  /**
2
- * dario's own package version, read once from the bundled package.json.
2
+ * dario's own package version, bound at module load.
3
3
  *
4
4
  * Surfaced on `/status` and `/health` (#640) so a headless operator can confirm
5
5
  * an auto-update actually rolled the running proxy — `curl /health | jq .version`
6
6
  * beats exec-ing into the container to read package.json.
7
+ *
8
+ * WHY AT MODULE LOAD, NOT ON FIRST CALL. This used to read package.json lazily
9
+ * the first time someone asked, and cache that. `npm i -g` rewrites
10
+ * package.json under the running install without touching the process, so a
11
+ * proxy that had not served `/status` before an upgrade answered its first one
12
+ * with the NEW version while still executing the OLD code — precisely the
13
+ * opposite of what the field exists to report. It cost the reporter on #1244 a
14
+ * round trip: `/status` read 6.0.34 while `GET /admin/accounts` was still
15
+ * emitting the 6.0.33 field set, so the advice he had been given ("upgrade,
16
+ * then read `organization_id`") looked already done.
17
+ *
18
+ * Reading at import binds the value to the process. proxy.ts imports this at
19
+ * startup, so `/status` reports the build that is actually answering until it
20
+ * restarts — the only claim the field can honestly make.
7
21
  */
8
22
  import { readFileSync } from 'node:fs';
9
23
  import { join, dirname } from 'node:path';
10
24
  import { fileURLToPath } from 'node:url';
11
- let cached = null;
12
- export function darioVersion() {
13
- if (cached !== null)
14
- return cached;
15
- let v = 'unknown';
25
+ function readVersion() {
16
26
  try {
17
27
  // dist/version.js → ../package.json (same layout the MCP server + CLI use).
18
28
  const here = dirname(fileURLToPath(import.meta.url));
19
29
  const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf-8'));
20
30
  if (typeof pkg.version === 'string')
21
- v = pkg.version;
31
+ return pkg.version;
22
32
  }
23
33
  catch {
24
- // package.json missing/malformed — keep 'unknown', never throw.
34
+ // package.json missing/malformed — report 'unknown', never throw.
25
35
  }
26
- cached = v;
27
- return v;
36
+ return 'unknown';
37
+ }
38
+ /** Read once, at import. See the note above for why not on first call. */
39
+ const VERSION = readVersion();
40
+ export function darioVersion() {
41
+ return VERSION;
28
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "6.0.35",
3
+ "version": "6.0.37",
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": {