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.
- package/dist/adapters/codex.js +12 -1
- package/dist/diagnostics/failure.js +45 -0
- package/dist/version.js +1 -1
- package/dist/web/src/App.js +14 -1
- package/dist/web/src/components/FaultBanner.js +6 -0
- package/dist/web/src/components/PaceBar.d.ts +14 -4
- package/dist/web/src/components/PaceBar.js +18 -11
- package/dist/web/src/components/ProviderCard.js +3 -2
- package/dist/web/src/components/ProviderDrawer.js +3 -3
- package/dist/web/src/components/ProviderRow.d.ts +1 -1
- package/dist/web/src/components/ProviderRow.js +6 -2
- package/dist/web/src/state.d.ts +14 -0
- package/dist/web/src/state.js +26 -0
- package/dist/webAssets.js +1 -1
- package/dist/webHtml.js +1 -1
- package/package.json +1 -1
- package/web/dist/assets/{index-BRBtLIOV.css → index-CjNVp2Yx.css} +1 -1
- package/web/dist/assets/index-hA7pT5M5.js +12 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-XVPB92eL.js +0 -12
package/dist/adapters/codex.js
CHANGED
|
@@ -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
|
|
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.
|
|
2
|
+
export const VERSION = "0.0.31";
|
package/dist/web/src/App.js
CHANGED
|
@@ -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
|
-
*
|
|
68
|
-
*
|
|
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
|
|
140
|
-
*
|
|
141
|
-
*
|
|
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
|
|
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}
|
|
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" }) }) }))] }),
|
|
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": "
|
|
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,12 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
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
|
-
|
|
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) })] }));
|
package/dist/web/src/state.d.ts
CHANGED
|
@@ -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`. */
|
package/dist/web/src/state.js
CHANGED
|
@@ -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);
|