quotacap 0.0.29 → 0.0.31

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.
@@ -32,6 +32,11 @@ function parseWeeklyReset(raw, now) {
32
32
  return dt.toISOString();
33
33
  }
34
34
  function parseFiveReset(raw, now) {
35
+ // The 5h reset is bare ("19:38") when it falls today and date-qualified
36
+ // ("02:43 on 15 Sep", weekly shape) otherwise; accept both.
37
+ const qualified = parseWeeklyReset(raw, now);
38
+ if (qualified)
39
+ return qualified;
35
40
  const m = raw.trim().match(/^(\d{1,2}):(\d{2})$/);
36
41
  if (!m)
37
42
  return null;
@@ -102,10 +107,16 @@ export function parseCodexTui(text, now = new Date()) {
102
107
  fiveIso = new Date(now.getTime() + 5 * 3600000).toISOString();
103
108
  const usedPct = 100 - weeklyLeft;
104
109
  const sessionPct = 100 - fiveLeft;
110
+ // The /status panel carries "Account: <email> (<plan>)" above the limits.
111
+ // Vendor casing preserved (grok/muse precedent); unknown when absent.
112
+ let plan = "unknown";
113
+ const account = cleaned.match(/Account:\s*\S+\s*\(([^)]+)\)/i);
114
+ if (account && account[1].trim())
115
+ plan = account[1].trim();
105
116
  const periodStart = new Date(new Date(weeklyIso).getTime() - 7 * 86400000).toISOString();
106
117
  return {
107
118
  provider: "codex",
108
- plan: "unknown",
119
+ plan,
109
120
  usedPct,
110
121
  sessionPct,
111
122
  resetsAt: weeklyIso,
@@ -171,6 +171,51 @@ function matchPrecedence(evidence, errorName, errorCode, text) {
171
171
  if (/\bparse error\b/i.test(text) || /\bsyntax error\b/i.test(text) || /\bfailed to parse\b/i.test(text)) {
172
172
  return { code: "parse_error", phrase: "parse error" };
173
173
  }
174
+ // QuotaCap-authored adapter parse vocabulary (`src/adapters/*.ts` throws).
175
+ // Fixed subjects only: a quoted provider value after "bad … reset" is never
176
+ // echoed, per the sanitization policy.
177
+ if (/\bweekly limit not found\b/i.test(text)) {
178
+ return { code: "parse_error", phrase: "weekly limit not found" };
179
+ }
180
+ if (/\b5h limit not found\b/i.test(text)) {
181
+ return { code: "parse_error", phrase: "5h limit not found" };
182
+ }
183
+ if (/\bweekly percent not found\b/i.test(text)) {
184
+ return { code: "parse_error", phrase: "weekly percent not found" };
185
+ }
186
+ if (/\bweekly usage not found\b/i.test(text)) {
187
+ return { code: "parse_error", phrase: "weekly usage not found" };
188
+ }
189
+ if (/\bcurrent-window usage not found\b/i.test(text)) {
190
+ return { code: "parse_error", phrase: "current-window usage not found" };
191
+ }
192
+ if (/\bbad weekly pct\b/i.test(text)) {
193
+ return { code: "parse_error", phrase: "bad weekly pct" };
194
+ }
195
+ if (/\bbad 5h pct\b/i.test(text)) {
196
+ return { code: "parse_error", phrase: "bad 5h pct" };
197
+ }
198
+ if (/\bbad current pct\b/i.test(text)) {
199
+ return { code: "parse_error", phrase: "bad current pct" };
200
+ }
201
+ if (/\bbad weekly reset\b/i.test(text)) {
202
+ return { code: "parse_error", phrase: "bad weekly reset" };
203
+ }
204
+ if (/\bbad 5h reset\b/i.test(text)) {
205
+ return { code: "parse_error", phrase: "bad 5h reset" };
206
+ }
207
+ if (/\bbad weekly reset_time\b/i.test(text)) {
208
+ return { code: "parse_error", phrase: "bad weekly reset_time" };
209
+ }
210
+ if (/\bstatus is not SUCCESS\b/i.test(text)) {
211
+ return { code: "parse_error", phrase: "status is not SUCCESS" };
212
+ }
213
+ if (/\bno usage groups\b/i.test(text)) {
214
+ return { code: "parse_error", phrase: "no usage groups" };
215
+ }
216
+ if (/\bno weekly bucket\b/i.test(text)) {
217
+ return { code: "parse_error", phrase: "no weekly bucket" };
218
+ }
174
219
  // Order 9: timeout — Remaining explicit timeout or timed out errors
175
220
  if (/\btimed out\b/i.test(text) || /\btimeout\b/i.test(text)) {
176
221
  return { code: "timeout", phrase: "timed out" };
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // generated by scripts/build-embed.mjs — do not edit
2
- export const VERSION = "0.0.29";
2
+ export const VERSION = "0.0.31";
@@ -63,13 +63,26 @@ function App() {
63
63
  useEffect(() => {
64
64
  void load();
65
65
  }, [load]);
66
+ // While the daemon reports a non-idle poll state, keep re-reading until it
67
+ // settles. Without this the pill ("Polling", "Cooling down") sticks forever
68
+ // on the single post-refresh snapshot, which is always mid-cooldown.
69
+ useEffect(() => {
70
+ if (!snapshot || snapshot.runtime.polling === "idle")
71
+ return;
72
+ const t = setInterval(() => void load(), 5000);
73
+ return () => clearInterval(t);
74
+ }, [snapshot, load]);
66
75
  const refresh = useCallback(async () => {
67
76
  setRefreshing(true);
68
77
  setNotice(null);
69
78
  try {
70
79
  const result = await triggerRefresh();
71
- if (result.message)
80
+ if (result.message) {
72
81
  setNotice(result.message);
82
+ }
83
+ else if (result.raw?.cooldown === true) {
84
+ setNotice("Refresh cooling down — showing last readings.");
85
+ }
73
86
  await load();
74
87
  }
75
88
  catch (e) {
@@ -22,6 +22,12 @@ export function repairFor(provider) {
22
22
  case "invalid":
23
23
  return { text: `Check the ${name} CLI output, then re-poll.` };
24
24
  case "provider-failed": {
25
+ const attempt = provider.lastAttempt;
26
+ if (attempt?.summary) {
27
+ const detail = attempt.errorDetail ? `: ${attempt.errorDetail}` : "";
28
+ const action = attempt.action ?? `Check the ${name} CLI, then re-poll.`;
29
+ return { text: `${attempt.summary}${detail}. ${action}` };
30
+ }
25
31
  const failure = failureWords(provider.lastAttempt?.failureCategory ?? null);
26
32
  return { text: `${failure ?? "Provider failed"} — check the ${name} CLI, then re-poll.` };
27
33
  }
@@ -63,11 +63,21 @@ export declare function timeLeft(resetsAt: string, asOfMs: number): string | nul
63
63
  * rows await a fresh window; invalid rows carry a row-level note.
64
64
  */
65
65
  export declare function resetCountdown(provider: ProviderView, asOfMs: number): string;
66
+ export interface ResetCellLines {
67
+ /** Clock line ("Thu 16:53"), or the whole wording when single-line. */
68
+ top: string;
69
+ /** Countdown line ("in 2d 17h"), or null for single-line states. */
70
+ bottom: string | null;
71
+ }
72
+ /**
73
+ * Reset cell for the table view as two stacked lines: the clock on top,
74
+ * the countdown below. Estimated resets mark the timestamp, per the 0.0.25
75
+ * convention. Reset-passed, missing, and unparseable rows stay single-line.
76
+ */
77
+ export declare function resetCellLines(provider: ProviderView, asOfMs: number): ResetCellLines;
66
78
  /**
67
- * Reset cell for the table view: clock time plus countdown, e.g.
68
- * "Tue 10:25 · in 20h 30m". Estimated resets mark the timestamp, per the
69
- * 0.0.25 convention. Falls back to the countdown-only wording for
70
- * reset-passed, missing, and unparseable rows.
79
+ * Single-line reset wording, e.g. "Tue 10:25 · in 20h 30m". Same content
80
+ * as resetCellLines, joined for compact contexts.
71
81
  */
72
82
  export declare function resetCell(provider: ProviderView, asOfMs: number): string;
73
83
  export interface BarGeometry {
@@ -136,28 +136,35 @@ export function resetCountdown(provider, asOfMs) {
136
136
  return `in ${left}${estimated}`;
137
137
  }
138
138
  /**
139
- * Reset cell for the table view: clock time plus countdown, e.g.
140
- * "Tue 10:25 · in 20h 30m". Estimated resets mark the timestamp, per the
141
- * 0.0.25 convention. Falls back to the countdown-only wording for
142
- * reset-passed, missing, and unparseable rows.
139
+ * Reset cell for the table view as two stacked lines: the clock on top,
140
+ * the countdown below. Estimated resets mark the timestamp, per the 0.0.25
141
+ * convention. Reset-passed, missing, and unparseable rows stay single-line.
143
142
  */
144
- export function resetCell(provider, asOfMs) {
143
+ export function resetCellLines(provider, asOfMs) {
145
144
  if (provider.exclusionReason === "reset-passed")
146
- return "awaiting fresh window";
145
+ return { top: "awaiting fresh window", bottom: null };
147
146
  if (!provider.quota)
148
- return "no readings";
147
+ return { top: "no readings", bottom: null };
149
148
  const clock = resetClock(provider.quota.resetsAt);
150
149
  const left = timeLeft(provider.quota.resetsAt, asOfMs);
151
150
  const estimated = isEstimated(provider) ? " (est.)" : "";
152
151
  if (clock && left)
153
- return `${clock}${estimated} · in ${left}`;
152
+ return { top: `${clock}${estimated}`, bottom: `in ${left}` };
154
153
  if (left === null) {
155
154
  const resetMs = Date.parse(provider.quota.resetsAt);
156
155
  if (Number.isFinite(resetMs) && resetMs <= asOfMs)
157
- return "awaiting fresh window";
158
- return "reset unknown";
156
+ return { top: "awaiting fresh window", bottom: null };
157
+ return { top: "reset unknown", bottom: null };
159
158
  }
160
- return "reset unknown";
159
+ return { top: "reset unknown", bottom: null };
160
+ }
161
+ /**
162
+ * Single-line reset wording, e.g. "Tue 10:25 · in 20h 30m". Same content
163
+ * as resetCellLines, joined for compact contexts.
164
+ */
165
+ export function resetCell(provider, asOfMs) {
166
+ const { top, bottom } = resetCellLines(provider, asOfMs);
167
+ return bottom ? `${top} · ${bottom}` : top;
161
168
  }
162
169
  /** Bar geometry from server timestamps and percents. Presentation only. */
163
170
  export function barGeometry(provider, asOfMs) {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ageDuration } from "../state.js";
2
+ import { ageDuration, displayPlan } from "../state.js";
3
3
  import { Badge, PACE_TIPS, PaceBar, isEstimated, paceCells, resetClock, resetCountdown, timeLeft, } from "./PaceBar.js";
4
4
  import { ProviderIcon, providerTint } from "./ProviderIcon.js";
5
5
  function resetsDate(provider) {
@@ -18,10 +18,11 @@ export function ProviderCard({ provider, recommended, asOf, onSelect, }) {
18
18
  const updated = ageDuration(provider.ageMs);
19
19
  const resetsIn = quota ? timeLeft(quota.resetsAt, asOfMs) : null;
20
20
  const name = provider.displayName;
21
+ const plan = displayPlan(quota?.plan);
21
22
  return (_jsxs("article", { "data-testid": `provider-card-${provider.id}`, role: "button", tabIndex: 0, "aria-label": `${name} details`, onClick: () => onSelect(provider.id), onKeyDown: (e) => {
22
23
  if (e.key === "Enter" || e.key === " ") {
23
24
  e.preventDefault();
24
25
  onSelect(provider.id);
25
26
  }
26
- }, className: `pcard ${recommended ? "is-recommended is-top-pick" : ""}`, children: [_jsxs("div", { className: "pcard-head", children: [_jsx("span", { className: "picon", style: providerTint(provider.id), "aria-hidden": "true", children: _jsx(ProviderIcon, { id: provider.id, title: name }) }), _jsxs("div", { className: "who", children: [_jsxs("b", { children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), quota && quota.plan && _jsx("span", { className: "pl", children: quota.plan }), updated && _jsxs("span", { className: "src", children: ["Updated ", updated, " ago"] })] }), _jsx(Badge, { provider: provider })] }), _jsxs("div", { className: "pcard-body", children: [_jsx(PaceBar, { provider: provider, asOf: asOf }), _jsxs("div", { className: "pstats", children: [_jsxs("div", { title: PACE_TIPS.avg, children: [_jsx("span", { className: "l", children: "Avg pace" }), _jsx("span", { className: "v", children: pace.avg })] }), _jsxs("div", { title: PACE_TIPS.recent, children: [_jsx("span", { className: "l", children: "24h pace" }), _jsx("span", { className: "v", children: pace.recent })] }), _jsxs("div", { title: PACE_TIPS.ideal, children: [_jsx("span", { className: "l", children: "Ideal pace" }), _jsx("span", { className: "v", children: advisory ? `${advisory.idealRate.toFixed(1)}%/day` : "—" })] }), _jsxs("div", { children: [_jsx("span", { className: "l", children: "Resets in" }), _jsx("span", { className: "v", children: resetsIn ? `${resetsIn}${isEstimated(provider) ? " (est.)" : ""}` : resetCountdown(provider, asOfMs) })] })] })] }), _jsxs("div", { className: "pcard-foot", children: [_jsx("span", { children: resetsDate(provider) ?? resetCountdown(provider, asOfMs) }), _jsx("span", { className: "rowbtn", "aria-hidden": "true", children: "Inspect" })] })] }));
27
+ }, className: `pcard ${recommended ? "is-recommended is-top-pick" : ""}`, children: [_jsxs("div", { className: "pcard-head", children: [_jsx("span", { className: "picon", style: providerTint(provider.id), "aria-hidden": "true", children: _jsx(ProviderIcon, { id: provider.id, title: name }) }), _jsxs("div", { className: "who", children: [_jsxs("b", { children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), plan && _jsx("span", { className: "pl", children: plan }), updated && _jsxs("span", { className: "src", children: ["Updated ", updated, " ago"] })] }), _jsx(Badge, { provider: provider })] }), _jsxs("div", { className: "pcard-body", children: [_jsx(PaceBar, { provider: provider, asOf: asOf }), _jsxs("div", { className: "pstats", children: [_jsxs("div", { title: PACE_TIPS.avg, children: [_jsx("span", { className: "l", children: "Avg pace" }), _jsx("span", { className: "v", children: pace.avg })] }), _jsxs("div", { title: PACE_TIPS.recent, children: [_jsx("span", { className: "l", children: "24h pace" }), _jsx("span", { className: "v", children: pace.recent })] }), _jsxs("div", { title: PACE_TIPS.ideal, children: [_jsx("span", { className: "l", children: "Ideal pace" }), _jsx("span", { className: "v", children: advisory ? `${advisory.idealRate.toFixed(1)}%/day` : "—" })] }), _jsxs("div", { children: [_jsx("span", { className: "l", children: "Resets in" }), _jsx("span", { className: "v", children: resetsIn ? `${resetsIn}${isEstimated(provider) ? " (est.)" : ""}` : resetCountdown(provider, asOfMs) })] })] })] }), _jsxs("div", { className: "pcard-foot", children: [_jsx("span", { children: resetsDate(provider) ?? resetCountdown(provider, asOfMs) }), _jsx("span", { className: "rowbtn", "aria-hidden": "true", children: "Inspect" })] })] }));
27
28
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
- import { ageDuration, providerSubtitle } from "../state.js";
3
+ import { ageDuration, displayPlan, providerSubtitle, shortWindowLabel } from "../state.js";
4
4
  import { Badge, PaceBar, barGeometry, isEstimated, paceBadge, paceFigures, resetClock, timeLeft, } from "./PaceBar.js";
5
5
  import { ProviderIcon, providerTint } from "./ProviderIcon.js";
6
6
  import { renameProvider } from "../api.js";
@@ -298,7 +298,7 @@ export function ProviderDrawer({ provider, asOf, recommendation, onClose, onRena
298
298
  const why = rankingCopy(provider, recommendation);
299
299
  const record = compactSnapshot(provider, asOfMs);
300
300
  const recordJson = JSON.stringify(record, null, 2);
301
- const planBits = [provider.quota?.plan, sourceLabel(provider.quota?.source)].filter((b) => b && b !== "unknown");
301
+ const planBits = [displayPlan(provider.quota?.plan), sourceLabel(provider.quota?.source)].filter((b) => b && b !== "unknown");
302
302
  const sub = planBits.join(" · ");
303
303
  return (_jsxs(_Fragment, { children: [_jsx("div", { "data-testid": "drawer-scrim", className: "scrim is-open", "aria-hidden": "true", onClick: onClose }), _jsxs("aside", { ref: dialogRef, tabIndex: -1, "data-testid": "provider-drawer", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, className: "drawer is-open", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "drawer-head", children: [_jsx("span", { className: "picon", style: providerTint(provider.id), children: _jsx(ProviderIcon, { id: provider.id, title: model.name }) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsxs("h2", { id: titleId, onClick: !isEditing ? () => setIsEditing(true) : undefined, title: !isEditing ? "Click to rename" : undefined, style: isEditing
304
304
  ? {
@@ -358,7 +358,7 @@ export function ProviderDrawer({ provider, asOf, recommendation, onClose, onRena
358
358
  margin: "0 var(--s4) var(--s3)",
359
359
  fontSize: "var(--t-2)",
360
360
  color: "var(--ink-soft)",
361
- }, children: provider.description })), _jsxs("section", { className: "dsec", "aria-label": "Current window", children: [_jsx("h3", { children: "Current window" }), _jsx("div", { className: "pcard-topline", children: _jsx(Badge, { provider: provider }) }), provider.quota ? (_jsxs(_Fragment, { children: [_jsx(PaceBar, { provider: provider, asOf: asOf }), underline && _jsx("div", { className: "pcard-underline", children: underline })] })) : (_jsx("p", { className: "cell-s", children: "No readings yet." }))] }), model.sessionPct !== undefined && model.sessionPct > 0 && (_jsxs("section", { className: "dsec", "aria-label": "Session", children: [_jsx("h3", { children: "Session" }), _jsx("div", { className: "pcard-topline", children: _jsxs("span", { className: "used", children: [model.sessionPct, "% used"] }) }), _jsx("div", { className: "track", role: "img", "aria-label": `${model.sessionPct} percent of session window used`, children: _jsx("div", { className: "fill", style: {
361
+ }, children: provider.description })), _jsxs("section", { className: "dsec", "aria-label": "Weekly limit", children: [_jsx("h3", { children: "Weekly limit" }), _jsx("div", { className: "pcard-topline", children: _jsx(Badge, { provider: provider }) }), provider.quota ? (_jsxs(_Fragment, { children: [_jsx(PaceBar, { provider: provider, asOf: asOf }), underline && _jsx("div", { className: "pcard-underline", children: underline })] })) : (_jsx("p", { className: "cell-s", children: "No readings yet." }))] }), model.sessionPct !== undefined && model.sessionPct > 0 && (_jsxs("section", { className: "dsec", "aria-label": shortWindowLabel(provider.id) ?? "Session", children: [_jsx("h3", { children: shortWindowLabel(provider.id) ?? "Session" }), _jsx("div", { className: "pcard-topline", children: _jsxs("span", { className: "used", children: [model.sessionPct, "% used"] }) }), _jsx("div", { className: "track", role: "img", "aria-label": `${model.sessionPct} percent of ${(shortWindowLabel(provider.id) ?? "session").toLowerCase()} used`, children: _jsx("div", { className: "fill", style: {
362
362
  width: `${Math.min(100, Math.max(0, model.sessionPct))}%`,
363
363
  background: "var(--fill-ontrack)",
364
364
  ["--fill-edge"]: "var(--edge-ontrack)",
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import type { ProviderView } from "../state.js";
2
+ import { type ProviderView } from "../state.js";
3
3
  export declare function ProviderRow({ provider, recommended, asOf, onSelect, }: {
4
4
  provider: ProviderView;
5
5
  recommended: boolean;
@@ -1,12 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Badge, PaceBar, forecastLine, paceLines, resetCell, } from "./PaceBar.js";
2
+ import { displayPlan, shortWindowLabel } from "../state.js";
3
+ import { Badge, PaceBar, forecastLine, paceLines, resetCellLines, } from "./PaceBar.js";
3
4
  export function ProviderRow({ provider, recommended, asOf, onSelect, }) {
4
5
  const quota = provider.quota;
5
6
  const advisory = provider.advisory;
6
7
  const asOfMs = Date.parse(asOf);
7
8
  const lines = paceLines(advisory);
8
9
  const name = provider.displayName;
9
- return (_jsxs("div", { "data-testid": `provider-row-${provider.id}`, className: `lrow ${recommended ? "is-recommended is-top-pick" : ""}`, onClick: () => onSelect(provider.id), children: [_jsxs("div", { className: "prov", "data-label": "Provider", children: [_jsx("span", { className: "dot", style: { background: `var(--p-${provider.id.split(":")[0]}, var(--surface-raised))` }, "aria-hidden": "true" }), _jsxs("span", { children: [_jsxs("span", { className: "nm", children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), quota?.plan && _jsx("span", { className: "pl", children: quota.plan }), quota?.sessionPct !== undefined && quota.sessionPct !== null && (_jsxs("span", { className: "src", children: ["Session use ", quota.sessionPct, "%"] }))] })] }), _jsx("div", { "data-label": "Used vs elapsed", children: quota ? _jsx(PaceBar, { provider: provider, asOf: asOf }) : "—" }), _jsx("div", { "data-label": "Reset", children: _jsx("span", { className: "cell-b", children: resetCell(provider, asOfMs) }) }), _jsxs("div", { "data-label": "Pace", children: [_jsx(Badge, { provider: provider }), lines.length > 0 && (_jsx("div", { style: { marginTop: 2 }, children: lines.map((l) => (_jsxs("div", { className: "cell-s paceline", title: l.tip, children: [_jsx("span", { children: l.label }), _jsx("span", { className: "cell-b", children: l.value })] }, l.label))) }))] }), _jsx("button", { type: "button", className: "rowbtn", onClick: (e) => {
10
+ const plan = displayPlan(quota?.plan);
11
+ const shortWindow = shortWindowLabel(provider.id);
12
+ const reset = resetCellLines(provider, asOfMs);
13
+ return (_jsxs("div", { "data-testid": `provider-row-${provider.id}`, className: `lrow ${recommended ? "is-recommended is-top-pick" : ""}`, onClick: () => onSelect(provider.id), children: [_jsxs("div", { className: "prov", "data-label": "Provider", children: [_jsx("span", { className: "dot", style: { background: `var(--p-${provider.id.split(":")[0]}, var(--surface-raised))` }, "aria-hidden": "true" }), _jsxs("span", { children: [_jsxs("span", { className: "nm", children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), plan && _jsx("span", { className: "pl", children: plan }), quota?.sessionPct !== undefined && quota.sessionPct !== null && (_jsxs("span", { className: "src", children: [shortWindow ?? "Session", " \u00B7 ", quota.sessionPct, "% used"] }))] })] }), _jsx("div", { "data-label": "Used vs elapsed", children: quota ? _jsx(PaceBar, { provider: provider, asOf: asOf }) : "—" }), _jsxs("div", { "data-label": "Reset", className: "reset-cell", children: [_jsx("span", { className: "cell-b", children: reset.top }), reset.bottom && _jsx("span", { className: "cell-s", children: reset.bottom })] }), _jsxs("div", { "data-label": "Pace", children: [_jsx(Badge, { provider: provider }), lines.length > 0 && (_jsx("div", { style: { marginTop: 2 }, children: lines.map((l) => (_jsxs("div", { className: "cell-s paceline", title: l.tip, children: [_jsx("span", { children: l.label }), _jsx("span", { className: "cell-b", children: l.value })] }, l.label))) }))] }), _jsx("button", { type: "button", className: "rowbtn", onClick: (e) => {
10
14
  e.stopPropagation();
11
15
  onSelect(provider.id);
12
16
  }, children: "Inspect" }), _jsx("span", { className: "sr-only", children: forecastLine(provider) })] }));
@@ -47,6 +47,11 @@ export interface AttemptView {
47
47
  succeededAt: string | null;
48
48
  success: boolean;
49
49
  failureCategory: FailureCategory;
50
+ diagnosticCode?: string | null;
51
+ summary?: string | null;
52
+ action?: string | null;
53
+ errorDetail?: string | null;
54
+ error?: string | null;
50
55
  }
51
56
  export interface ProviderView {
52
57
  id: string;
@@ -107,6 +112,15 @@ export declare function firstRun(s: {
107
112
  * vendor shows. Null when neither is registered.
108
113
  */
109
114
  export declare function providerSubtitle(p: Pick<ProviderView, "displayName" | "vendor" | "harness">): string | null;
115
+ /** Plan for display. Vendors that expose no tier report "unknown", which never renders. */
116
+ export declare function displayPlan(plan: string | null | undefined): string | null;
117
+ /**
118
+ * Short-window display name, mirroring each vendor's own /usage or /status
119
+ * wording: codex, kimi, and agy call it a "5h" or "Five Hour" limit, claude
120
+ * a "Current session" (a 5-hour rolling window), muse a "Current" window.
121
+ * Null when the provider reports no short window (grok, manual).
122
+ */
123
+ export declare function shortWindowLabel(id: string): string | null;
110
124
  /** "read Xm ago" from the last good snapshot's server timestamp. */
111
125
  export declare function ageLabel(asOf: string, nowMs?: number): string;
112
126
  /** Default poll interval; matches `pollMinutes` in `src/config.ts`. */
@@ -44,6 +44,32 @@ export function providerSubtitle(p) {
44
44
  return p.vendor;
45
45
  return [p.harness, p.vendor].filter(Boolean).join(" · ");
46
46
  }
47
+ /** Plan for display. Vendors that expose no tier report "unknown", which never renders. */
48
+ export function displayPlan(plan) {
49
+ if (!plan || plan === "unknown")
50
+ return null;
51
+ return plan;
52
+ }
53
+ /**
54
+ * Short-window display name, mirroring each vendor's own /usage or /status
55
+ * wording: codex, kimi, and agy call it a "5h" or "Five Hour" limit, claude
56
+ * a "Current session" (a 5-hour rolling window), muse a "Current" window.
57
+ * Null when the provider reports no short window (grok, manual).
58
+ */
59
+ export function shortWindowLabel(id) {
60
+ switch (id.split(":")[0]) {
61
+ case "codex":
62
+ case "kimi":
63
+ case "agy":
64
+ return "5-hour limit";
65
+ case "claude":
66
+ return "Current session";
67
+ case "muse":
68
+ return "Current window";
69
+ default:
70
+ return null;
71
+ }
72
+ }
47
73
  /** "read Xm ago" from the last good snapshot's server timestamp. */
48
74
  export function ageLabel(asOf, nowMs = Date.now()) {
49
75
  const diff = nowMs - Date.parse(asOf);