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.
- package/dist/adapters/agy.js +1 -1
- package/dist/adapters/claude.js +3 -2
- package/dist/adapters/codex.js +7 -1
- package/dist/adapters/kimi.js +17 -6
- package/dist/adapters/manual.js +8 -0
- package/dist/version.js +1 -1
- package/dist/web/src/App.js +14 -1
- 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 +8 -0
- package/dist/web/src/state.js +23 -0
- package/dist/webAssets.js +1 -1
- package/dist/webHtml.js +1 -1
- package/package.json +1 -1
- package/web/dist/assets/index-4-7ERxB2.js +12 -0
- package/web/dist/assets/{index-BRBtLIOV.css → index-CjNVp2Yx.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DnWV59MD.js +0 -12
package/dist/adapters/agy.js
CHANGED
|
@@ -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;
|
package/dist/adapters/claude.js
CHANGED
|
@@ -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+)
|
|
5
|
-
const weeklyMatch = result.match(/Current week \(all models\):\s+(\d+)
|
|
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);
|
package/dist/adapters/codex.js
CHANGED
|
@@ -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
|
|
119
|
+
plan,
|
|
114
120
|
usedPct,
|
|
115
121
|
sessionPct,
|
|
116
122
|
resetsAt: weeklyIso,
|
package/dist/adapters/kimi.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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) {
|
package/dist/adapters/manual.js
CHANGED
|
@@ -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.
|
|
2
|
+
export const VERSION = "0.0.32";
|
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) {
|
|
@@ -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 !== 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,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 ?? "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) })] }));
|
package/dist/web/src/state.d.ts
CHANGED
|
@@ -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`. */
|
package/dist/web/src/state.js
CHANGED
|
@@ -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);
|