@askalf/dario 4.8.103 → 4.8.105

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.
@@ -118,7 +118,18 @@ export declare class Analytics extends EventEmitter {
118
118
  private computeStats;
119
119
  private perAccountStats;
120
120
  private perModelStats;
121
- private utilizationTrend;
121
+ /**
122
+ * The most recent rate-limit snapshot in the window — current 5h / 7d
123
+ * utilization (0–1) as of the last request. The Analytics tab's rate-limit
124
+ * gauge reads this; an empty window reads 0/0. Mirrors `perAccountStats`'
125
+ * `last.util*` "current" semantics.
126
+ *
127
+ * Replaces the old per-5-min-bucket `utilizationTrend` array: the TUI gauge
128
+ * (the only consumer of `summary.utilization`) reads `.lastUtil5h` /
129
+ * `.lastUtil7d`, which on the array shape were `undefined` → rendered NaN%.
130
+ * See #600.
131
+ */
132
+ private currentUtilization;
122
133
  private predict;
123
134
  }
124
135
  interface PerAccountStat {
@@ -164,12 +175,11 @@ export interface AnalyticsSummary {
164
175
  };
165
176
  perAccount: Record<string, PerAccountStat>;
166
177
  perModel: Record<string, PerModelStat>;
167
- utilization: Array<{
168
- timestamp: number;
169
- avgUtil5h: number;
170
- avgUtil7d: number;
171
- requests: number;
172
- }>;
178
+ /** Current 5h / 7d rate-limit utilization (0–1) as of the last request. */
179
+ utilization: {
180
+ lastUtil5h: number;
181
+ lastUtil7d: number;
182
+ };
173
183
  predictions: {
174
184
  estimatedExhaustionMinutes: number | null;
175
185
  tokenBurnRate: number;
package/dist/analytics.js CHANGED
@@ -175,7 +175,7 @@ export class Analytics extends EventEmitter {
175
175
  },
176
176
  perAccount: this.perAccountStats(recent),
177
177
  perModel: this.perModelStats(recent),
178
- utilization: this.utilizationTrend(recent),
178
+ utilization: this.currentUtilization(recent),
179
179
  predictions: this.predict(recent),
180
180
  };
181
181
  }
@@ -267,29 +267,22 @@ export class Analytics extends EventEmitter {
267
267
  }
268
268
  return result;
269
269
  }
270
- utilizationTrend(records) {
270
+ /**
271
+ * The most recent rate-limit snapshot in the window — current 5h / 7d
272
+ * utilization (0–1) as of the last request. The Analytics tab's rate-limit
273
+ * gauge reads this; an empty window reads 0/0. Mirrors `perAccountStats`'
274
+ * `last.util*` "current" semantics.
275
+ *
276
+ * Replaces the old per-5-min-bucket `utilizationTrend` array: the TUI gauge
277
+ * (the only consumer of `summary.utilization`) reads `.lastUtil5h` /
278
+ * `.lastUtil7d`, which on the array shape were `undefined` → rendered NaN%.
279
+ * See #600.
280
+ */
281
+ currentUtilization(records) {
271
282
  if (records.length === 0)
272
- return [];
273
- const bucketMs = 5 * 60_000;
274
- const buckets = new Map();
275
- for (const r of records) {
276
- const key = Math.floor(r.timestamp / bucketMs) * bucketMs;
277
- const existing = buckets.get(key);
278
- if (existing) {
279
- existing.push(r);
280
- }
281
- else {
282
- buckets.set(key, [r]);
283
- }
284
- }
285
- return [...buckets.entries()]
286
- .sort(([a], [b]) => a - b)
287
- .map(([ts, recs]) => ({
288
- timestamp: ts,
289
- avgUtil5h: Math.round(recs.reduce((s, r) => s + r.util5h, 0) / recs.length * 100) / 100,
290
- avgUtil7d: Math.round(recs.reduce((s, r) => s + r.util7d, 0) / recs.length * 100) / 100,
291
- requests: recs.length,
292
- }));
283
+ return { lastUtil5h: 0, lastUtil7d: 0 };
284
+ const last = records[records.length - 1];
285
+ return { lastUtil5h: last.util5h, lastUtil7d: last.util7d };
293
286
  }
294
287
  predict(records) {
295
288
  if (records.length < 3) {
@@ -37,6 +37,12 @@ interface SummaryShape {
37
37
  lastUtil5h: number;
38
38
  lastUtil7d: number;
39
39
  };
40
+ perAccount: Record<string, {
41
+ requests: number;
42
+ currentUtil5h: number;
43
+ currentUtil7d: number;
44
+ lastClaim: string;
45
+ }>;
40
46
  }
41
47
  export interface AnalyticsState {
42
48
  summary: SummaryShape | null;
@@ -78,7 +78,7 @@ export const AnalyticsTab = {
78
78
  lines.push(' ' + renderKvRow('Tokens out', formatNumber(s.window.totalOutputTokens), w - 4));
79
79
  lines.push(' ' + renderKvRow('Thinking tokens', formatNumber(s.window.totalThinkingTokens), w - 4));
80
80
  lines.push(' ' + renderKvRow('Avg latency', `${Math.round(s.window.avgLatencyMs)}ms`, w - 4));
81
- lines.push(' ' + renderKvRow('Subscription %', `${(s.window.subscriptionPercent * 100).toFixed(0)}%`, w - 4));
81
+ lines.push(' ' + renderKvRow('Subscription %', `${s.window.subscriptionPercent.toFixed(0)}%`, w - 4));
82
82
  // ── Per-model bars ─────────────────────────────────────────
83
83
  const models = Object.entries(s.perModel).sort((a, b) => b[1].requests - a[1].requests);
84
84
  if (models.length > 0) {
@@ -94,14 +94,33 @@ export const AnalyticsTab = {
94
94
  }
95
95
  }
96
96
  // ── Rate-limit ────────────────────────────────────────────
97
+ // Each account hits its OWN 5h/7d windows, so with >1 account an
98
+ // aggregate gauge is misleading (#600) — show one row per account, the
99
+ // bar tracking the binding constraint (max of 5h/7d = closest to a limit).
97
100
  lines.push('');
98
- lines.push(' ' + brand('Rate-limit'));
99
- lines.push(' ' + pad('5h', 6) +
100
- fg('cyan', progressBar(s.utilization.lastUtil5h, barWidth)) +
101
- ' ' + dim(`${(s.utilization.lastUtil5h * 100).toFixed(0)}%`));
102
- lines.push(' ' + pad('7d', 6) +
103
- fg('cyan', progressBar(s.utilization.lastUtil7d, barWidth)) +
104
- ' ' + dim(`${(s.utilization.lastUtil7d * 100).toFixed(0)}%`));
101
+ const accts = s.perAccount ? Object.entries(s.perAccount) : [];
102
+ if (accts.length > 1) {
103
+ lines.push(' ' + brand('Rate-limit') + dim(' (per account)'));
104
+ const acctBarWidth = Math.max(8, Math.min(20, w - 48));
105
+ for (const [alias, a] of accts.sort((x, y) => y[1].requests - x[1].requests)) {
106
+ const u5 = a.currentUtil5h ?? 0;
107
+ const u7 = a.currentUtil7d ?? 0;
108
+ const peak = Math.max(u5, u7);
109
+ lines.push(' ' + pad(alias, 14) +
110
+ fg(peak >= 0.9 ? 'red' : 'cyan', progressBar(peak, acctBarWidth)) +
111
+ ' ' + dim(`5h ${(u5 * 100).toFixed(0)}%`.padEnd(8)) +
112
+ dim(`7d ${(u7 * 100).toFixed(0)}%`));
113
+ }
114
+ }
115
+ else {
116
+ lines.push(' ' + brand('Rate-limit'));
117
+ lines.push(' ' + pad('5h', 6) +
118
+ fg('cyan', progressBar(s.utilization.lastUtil5h, barWidth)) +
119
+ ' ' + dim(`${(s.utilization.lastUtil5h * 100).toFixed(0)}%`));
120
+ lines.push(' ' + pad('7d', 6) +
121
+ fg('cyan', progressBar(s.utilization.lastUtil7d, barWidth)) +
122
+ ' ' + dim(`${(s.utilization.lastUtil7d * 100).toFixed(0)}%`));
123
+ }
105
124
  // Overage bucket (v4.1, dario#288). Count of requests that landed in
106
125
  // the overage bucket within the rolling window. Empty bar in normal
107
126
  // operation; non-zero count renders in red. Hard zero IS the success
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "4.8.103",
3
+ "version": "4.8.105",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {