quotacap 0.0.30 → 0.0.32

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.
@@ -22,7 +22,7 @@ function parseGroup(g, provider, rawJsonString, now) {
22
22
  throw new Error("agy: bad weekly reset_time");
23
23
  const resetsAt = resetDate.toISOString();
24
24
  const periodStart = new Date(resetDate.getTime() - 7 * 86400000).toISOString();
25
- const fiveHour = g.buckets.find((b) => b.window === "5h");
25
+ const fiveHour = g.buckets.find((b) => b.window === "5h" || b.id?.includes("5h") || /5[- ]?h|five[- ]?hour/i.test(b.name ?? ""));
26
26
  const sessionPct = fiveHour
27
27
  ? usedPctFromRemaining(fiveHour.remaining_fraction) ?? undefined
28
28
  : undefined;
@@ -1,8 +1,9 @@
1
1
  import { trackedExecFile } from "../runtime/spawn.js";
2
2
  import { parseResetText } from "./parse.js";
3
3
  export function parseClaudeUsage(result, now = new Date()) {
4
- const sessionMatch = result.match(/Current session:\s+(\d+)% used[^·]*·\s*resets\s+([^\n(]+?)\s*\(/);
5
- const weeklyMatch = result.match(/Current week \(all models\):\s+(\d+)% used[^·]*·\s*resets\s+([^\n(]+?)\s*\(/);
4
+ const sessionMatch = result.match(/Current session:\s+(\d+)%\s+used/i);
5
+ const weeklyMatch = result.match(/Current week \(all models\):\s+(\d+)%\s+used/i) ??
6
+ result.match(/Current week[^\d%]*(\d+)%\s+used/i);
6
7
  const usedPct = weeklyMatch ? parseInt(weeklyMatch[1], 10) : 0;
7
8
  const sessionPct = sessionMatch ? parseInt(sessionMatch[1], 10) : undefined;
8
9
  let resetsAt = parseResetText(result, now);
@@ -107,10 +107,16 @@ export function parseCodexTui(text, now = new Date()) {
107
107
  fiveIso = new Date(now.getTime() + 5 * 3600000).toISOString();
108
108
  const usedPct = 100 - weeklyLeft;
109
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();
110
116
  const periodStart = new Date(new Date(weeklyIso).getTime() - 7 * 86400000).toISOString();
111
117
  return {
112
118
  provider: "codex",
113
- plan: "unknown",
119
+ plan,
114
120
  usedPct,
115
121
  sessionPct,
116
122
  resetsAt: weeklyIso,
@@ -10,22 +10,33 @@ export function parseKimiTui(text, now = new Date()) {
10
10
  if (!w)
11
11
  throw new Error("kimi: weekly limit not found in TUI output");
12
12
  const f = cleaned.match(fiveRe);
13
- if (!f)
13
+ let sessionPct = null;
14
+ let fiveRaw = null;
15
+ if (f) {
16
+ sessionPct = parseInt(f[1], 10);
17
+ fiveRaw = f[2].trim();
18
+ }
19
+ else {
20
+ const fallback = cleaned.match(/5h limit[^\d%]*(\d+)%\s+used/i);
21
+ if (fallback)
22
+ sessionPct = parseInt(fallback[1], 10);
23
+ }
24
+ if (sessionPct === null)
14
25
  throw new Error("kimi: 5h limit not found in TUI output");
15
26
  const usedPct = parseInt(w[1], 10);
16
- const sessionPct = parseInt(f[1], 10);
17
27
  if (!Number.isFinite(usedPct) || usedPct < 0 || usedPct > 100)
18
28
  throw new Error("kimi: bad weekly pct");
19
29
  if (!Number.isFinite(sessionPct) || sessionPct < 0 || sessionPct > 100)
20
30
  throw new Error("kimi: bad 5h pct");
21
31
  const weeklyRaw = w[2].trim();
22
- const fiveRaw = f[2].trim();
23
32
  const weeklyIso = parseResetText(`resets ${weeklyRaw}`, now);
24
33
  if (!weeklyIso)
25
34
  throw new Error(`kimi: bad weekly reset "${weeklyRaw}"`);
26
- const fiveIso = parseResetText(`resets ${fiveRaw}`, now);
27
- if (!fiveIso)
28
- throw new Error(`kimi: bad 5h reset "${fiveRaw}"`);
35
+ if (fiveRaw) {
36
+ const fiveIso = parseResetText(`resets ${fiveRaw}`, now);
37
+ if (!fiveIso)
38
+ throw new Error(`kimi: bad 5h reset "${fiveRaw}"`);
39
+ }
29
40
  let plan = "unknown";
30
41
  const paren = cleaned.match(/Weekly limit\s*\(([^)]+)\)/i);
31
42
  if (paren) {
@@ -5,10 +5,18 @@ export function parseManualUsage(provider, text, now = new Date()) {
5
5
  const parsedReset = parseResetText(text, now);
6
6
  const resetsAt = parsedReset ?? new Date(now.getTime() + 3 * 86400000).toISOString();
7
7
  const periodStart = (parsedReset ? new Date(new Date(resetsAt).getTime() - 7 * 86400000) : new Date(now.getTime() - 7 * 86400000)).toISOString();
8
+ let sessionPct;
9
+ const sm = text.match(/(?:5h|5-hour|session|current)[^\d%\n]*(\d+)%\s*used/i);
10
+ if (sm) {
11
+ const v = parseInt(sm[1], 10);
12
+ if (Number.isFinite(v) && v >= 0 && v <= 100)
13
+ sessionPct = v;
14
+ }
8
15
  return {
9
16
  provider,
10
17
  plan: "unknown",
11
18
  usedPct,
19
+ sessionPct,
12
20
  resetsAt,
13
21
  periodStart,
14
22
  source: "manual",
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.30";
2
+ export const VERSION = "0.0.32";
@@ -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) {
@@ -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 !== null && Number.isFinite(model.sessionPct) && (_jsxs("section", { className: "dsec", "aria-label": shortWindowLabel(provider.id) ?? "5h Limit", children: [_jsx("h3", { children: shortWindowLabel(provider.id) ?? "5h Limit" }), _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) ?? "5h limit").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 ?? "5h Limit", " \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) })] }));
@@ -112,6 +112,14 @@ export declare function firstRun(s: {
112
112
  * vendor shows. Null when neither is registered.
113
113
  */
114
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 ("5h Limit"). Codex, Kimi, and Agy report a 5h limit;
119
+ * Claude (current session) and Muse (current window) report an equivalent 5-hour
120
+ * rolling window. Returns null when the provider reports no short window (grok, manual).
121
+ */
122
+ export declare function shortWindowLabel(id: string): string | null;
115
123
  /** "read Xm ago" from the last good snapshot's server timestamp. */
116
124
  export declare function ageLabel(asOf: string, nowMs?: number): string;
117
125
  /** Default poll interval; matches `pollMinutes` in `src/config.ts`. */
@@ -44,6 +44,29 @@ 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 ("5h Limit"). Codex, Kimi, and Agy report a 5h limit;
55
+ * Claude (current session) and Muse (current window) report an equivalent 5-hour
56
+ * rolling window. Returns null when the provider reports no short window (grok, manual).
57
+ */
58
+ export function shortWindowLabel(id) {
59
+ switch (id.split(":")[0]) {
60
+ case "codex":
61
+ case "kimi":
62
+ case "agy":
63
+ case "claude":
64
+ case "muse":
65
+ return "5h Limit";
66
+ default:
67
+ return null;
68
+ }
69
+ }
47
70
  /** "read Xm ago" from the last good snapshot's server timestamp. */
48
71
  export function ageLabel(asOf, nowMs = Date.now()) {
49
72
  const diff = nowMs - Date.parse(asOf);