quotacap 0.0.39 → 0.0.40

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/agy.js +2 -2
  3. package/dist/adapters/claude.js +1 -1
  4. package/dist/adapters/codex.js +2 -2
  5. package/dist/adapters/grok.js +1 -1
  6. package/dist/adapters/kimi.js +2 -2
  7. package/dist/adapters/manual.js +2 -2
  8. package/dist/adapters/muse.js +2 -2
  9. package/dist/adapters/opencode-go.js +30 -2
  10. package/dist/adapters/types.d.ts +17 -2
  11. package/dist/advisory/engine.d.ts +4 -2
  12. package/dist/advisory/engine.js +55 -14
  13. package/dist/advisory/snapshot.js +11 -3
  14. package/dist/advisory/types.d.ts +11 -3
  15. package/dist/cli/models.js +1 -1
  16. package/dist/diagnostics/failure.js +9 -0
  17. package/dist/format/rows.d.ts +6 -1
  18. package/dist/format/rows.js +27 -4
  19. package/dist/format/terminal.js +5 -4
  20. package/dist/http/server.js +3 -3
  21. package/dist/runtime/poll.js +1 -1
  22. package/dist/store/db.js +12 -1
  23. package/dist/store/quotas.js +33 -8
  24. package/dist/version.js +1 -1
  25. package/dist/web/src/components/AdviceDrawer.js +10 -5
  26. package/dist/web/src/components/PaceBar.d.ts +9 -5
  27. package/dist/web/src/components/PaceBar.js +32 -10
  28. package/dist/web/src/components/ProviderCard.js +4 -2
  29. package/dist/web/src/components/ProviderDrawer.d.ts +7 -1
  30. package/dist/web/src/components/ProviderDrawer.js +46 -9
  31. package/dist/web/src/components/ProviderRow.js +4 -4
  32. package/dist/web/src/components/Recommendation.d.ts +5 -2
  33. package/dist/web/src/components/Recommendation.js +8 -4
  34. package/dist/web/src/components/ResetRail.js +3 -2
  35. package/dist/web/src/components/SubscriptionList.d.ts +2 -1
  36. package/dist/web/src/components/SubscriptionList.js +8 -5
  37. package/dist/web/src/components/WindowsRow.d.ts +30 -0
  38. package/dist/web/src/components/WindowsRow.js +68 -0
  39. package/dist/web/src/state.d.ts +12 -0
  40. package/dist/webAssets.js +1 -1
  41. package/dist/webHtml.js +1 -1
  42. package/package.json +1 -1
  43. package/web/dist/assets/index-CsOiHZyD.js +12 -0
  44. package/web/dist/assets/{index-NtT9XTrm.css → index-Dk6l5h27.css} +1 -1
  45. package/web/dist/index.html +2 -2
  46. package/web/dist/assets/index-CD8jaN87.js +0 -12
@@ -4,7 +4,8 @@ export type SortKey = "recommended" | "reset-asc" | "reset-desc" | "used-desc" |
4
4
  export type Density = "cards" | "table";
5
5
  /**
6
6
  * Sort over server fields only. Recommended puts the server pick first, then
7
- * lane members, then the rest in server order, excluded last. Sort is stable.
7
+ * lane members, then the rest in server order, excluded last. A blocked month
8
+ * sorts with the excluded rows. Sort is stable.
8
9
  */
9
10
  export declare function sortProviders(providers: ProviderView[], key: SortKey, recommendation: RecommendationView): ProviderView[];
10
11
  /** Density follows the control at every width — the prototype keeps Cards and Table visible on mobile. */
@@ -7,7 +7,8 @@ import { ProviderRow } from "./ProviderRow.js";
7
7
  import { resetCountdown } from "./PaceBar.js";
8
8
  /**
9
9
  * Sort over server fields only. Recommended puts the server pick first, then
10
- * lane members, then the rest in server order, excluded last. Sort is stable.
10
+ * lane members, then the rest in server order, excluded last. A blocked month
11
+ * sorts with the excluded rows. Sort is stable.
11
12
  */
12
13
  export function sortProviders(providers, key, recommendation) {
13
14
  const list = [...providers];
@@ -21,15 +22,17 @@ export function sortProviders(providers, key, recommendation) {
21
22
  if (!order.has(a.provider))
22
23
  order.set(a.provider, rank++);
23
24
  }
25
+ const blocked = (p) => p.exclusionReason !== null ||
26
+ (p.advisory?.bindingWindow === "monthly" && p.advisory.bindingRemaining === 0);
24
27
  const ranked = list
25
- .filter((p) => p.exclusionReason === null && order.has(p.id))
28
+ .filter((p) => !blocked(p) && order.has(p.id))
26
29
  .sort((a, b) => order.get(a.id) - order.get(b.id));
27
- const rest = list.filter((p) => p.exclusionReason === null && !order.has(p.id));
28
- const excluded = list.filter((p) => p.exclusionReason !== null);
30
+ const rest = list.filter((p) => !blocked(p) && !order.has(p.id));
31
+ const excluded = list.filter(blocked);
29
32
  return [...ranked, ...rest, ...excluded];
30
33
  }
31
34
  const resetMs = (p) => (p.quota ? Date.parse(p.quota.resetsAt) : NaN);
32
- const used = (p) => (p.quota ? p.quota.usedPct : NaN);
35
+ const used = (p) => (p.quota ? p.quota.weeklyPct : NaN);
33
36
  const withInvalidLast = (cmp, value) => list.sort((a, b) => {
34
37
  const va = value(a);
35
38
  const vb = value(b);
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import type { ProviderView } from "../state.js";
3
+ /** One-unit countdown for a window tag: "7d", "4h" or "40m". Null when passed or unparseable. */
4
+ export declare function windowCountdown(resetsAt: string, asOfMs: number): string | null;
5
+ export interface WindowUnit {
6
+ win: "five" | "month";
7
+ tag: "5h" | "Mth";
8
+ cardLabel: string;
9
+ rowLabel: string;
10
+ pct: number;
11
+ /** Presentation-only heat: < 80 none, 80–99 hot, 100 or exhausted full. */
12
+ heat: "" | "hot" | "full";
13
+ /** Tag time outside the pill: "in 7d", or the weekday alone when the month is exhausted. */
14
+ time: string | null;
15
+ /** Expanded card track's reset line. */
16
+ reset: string | null;
17
+ }
18
+ /** Present windows only; a unit with no data is not rendered at all. */
19
+ export declare function windowUnits(provider: ProviderView, asOfMs: number): WindowUnit[];
20
+ /**
21
+ * Collapsed windows line under the weekly bar, with a per-card chevron that
22
+ * expands thin tracks. A real button inside the card's role="button": click
23
+ * stops propagation so it never opens the drawer, and the card's own keydown
24
+ * ignores keys aimed at this button.
25
+ */
26
+ export declare function WindowsRow({ provider, asOf, variant }: {
27
+ provider: ProviderView;
28
+ asOf: string;
29
+ variant: "card" | "row";
30
+ }): React.JSX.Element | null;
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { resetClock, resetWeekday } from "./PaceBar.js";
4
+ const MIN_MS = 60_000;
5
+ const HOUR_MS = 60 * MIN_MS;
6
+ const DAY_MS = 24 * HOUR_MS;
7
+ /** One-unit countdown for a window tag: "7d", "4h" or "40m". Null when passed or unparseable. */
8
+ export function windowCountdown(resetsAt, asOfMs) {
9
+ const diff = Date.parse(resetsAt) - asOfMs;
10
+ if (!Number.isFinite(diff) || diff <= 0)
11
+ return null;
12
+ if (diff >= DAY_MS)
13
+ return `${Math.floor(diff / DAY_MS)}d`;
14
+ if (diff >= HOUR_MS)
15
+ return `${Math.floor(diff / HOUR_MS)}h`;
16
+ return `${Math.max(1, Math.floor(diff / MIN_MS))}m`;
17
+ }
18
+ function heatOf(pct, exhausted) {
19
+ if (exhausted || pct >= 100)
20
+ return "full";
21
+ return pct >= 80 ? "hot" : "";
22
+ }
23
+ /** Present windows only; a unit with no data is not rendered at all. */
24
+ export function windowUnits(provider, asOfMs) {
25
+ const q = provider.quota;
26
+ if (!q)
27
+ return [];
28
+ const units = [];
29
+ if (typeof q.fiveHourPct === "number" && Number.isFinite(q.fiveHourPct)) {
30
+ // No adapter reports a 5h reset, so the 5h tag carries no time.
31
+ units.push({ win: "five", tag: "5h", cardLabel: "5h limit", rowLabel: "5h",
32
+ pct: q.fiveHourPct, heat: heatOf(q.fiveHourPct, false), time: null, reset: null });
33
+ }
34
+ if (q.monthlyKind === "included" && typeof q.monthlyPct === "number" && q.monthlyResetsAt) {
35
+ const exhausted = q.monthlyStatus === "exhausted";
36
+ const left = windowCountdown(q.monthlyResetsAt, asOfMs);
37
+ const clock = resetClock(q.monthlyResetsAt);
38
+ units.push({ win: "month", tag: "Mth", cardLabel: "Monthly", rowLabel: "Month",
39
+ pct: q.monthlyPct, heat: heatOf(q.monthlyPct, exhausted),
40
+ time: exhausted ? resetWeekday(q.monthlyResetsAt) : left ? `in ${left}` : null,
41
+ reset: exhausted ? (clock ? `resets ${clock}` : null) : left ? `in ${left}` : null });
42
+ }
43
+ return units;
44
+ }
45
+ function trackFill(u) {
46
+ const cap = u.pct >= 80 || u.heat === "full";
47
+ return {
48
+ width: `${Math.min(100, Math.max(0, u.pct))}%`,
49
+ background: cap ? "var(--fill-cap)" : "var(--fill-ontrack)",
50
+ ["--fill-edge"]: cap ? "var(--edge-cap)" : "var(--edge-ontrack)",
51
+ };
52
+ }
53
+ /**
54
+ * Collapsed windows line under the weekly bar, with a per-card chevron that
55
+ * expands thin tracks. A real button inside the card's role="button": click
56
+ * stops propagation so it never opens the drawer, and the card's own keydown
57
+ * ignores keys aimed at this button.
58
+ */
59
+ export function WindowsRow({ provider, asOf, variant }) {
60
+ const [open, setOpen] = React.useState(false);
61
+ const units = windowUnits(provider, Date.parse(asOf));
62
+ if (units.length === 0)
63
+ return null;
64
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "wline", "data-testid": `wline-${provider.id}`, "aria-expanded": open, "aria-label": `${provider.displayName} other windows: ${units.map((u) => `${u.tag} ${u.pct}%${u.time ? ` ${u.time}` : ""}`).join(", ")}`, onClick: (e) => {
65
+ e.stopPropagation();
66
+ setOpen((v) => !v);
67
+ }, children: [units.map((u) => (_jsxs("span", { className: "unit", "data-win": u.win, children: [_jsxs("span", { className: u.heat ? `seg ${u.heat}` : "seg", children: [_jsx("span", { className: "k", children: u.tag }), _jsxs("span", { className: "n", children: [u.pct, "%"] })] }), u.time && _jsx("span", { className: "t", children: u.time })] }, u.win))), _jsx("span", { className: "chev", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", children: _jsx("path", { d: "M4 6l4 4 4-4" }) }) })] }), open && (_jsx("div", { className: "windows", "data-testid": `windows-${provider.id}`, children: units.map((u) => (_jsxs("div", { className: "win", "data-win": u.win, children: [_jsx("span", { className: "lab", children: variant === "card" ? u.cardLabel : u.rowLabel }), _jsx("div", { className: "track", children: _jsx("div", { className: "fill", style: trackFill(u) }) }), _jsxs("span", { className: "pct", children: [u.pct, "%"] }), variant === "card" && u.reset && _jsx("span", { className: "reset", children: u.reset })] }, u.win))) }))] }));
68
+ }
@@ -23,6 +23,9 @@ export interface AdvisoryView {
23
23
  status: BurnStatus;
24
24
  wastePct: number | null;
25
25
  urgency: Urgency;
26
+ bindingWindow: "weekly" | "monthly";
27
+ bindingRemaining: number;
28
+ bindingDaysLeft: number;
26
29
  }
27
30
  export interface RecommendationView {
28
31
  use: string;
@@ -30,6 +33,7 @@ export interface RecommendationView {
30
33
  wastePct: number | null;
31
34
  idealRate: number;
32
35
  recommendationBasis: RecommendationBasis;
36
+ bindingWindow: "weekly" | "monthly";
33
37
  alternatives: unknown[];
34
38
  advisories: AdvisoryView[];
35
39
  }
@@ -48,7 +52,15 @@ export interface WindowCloseView {
48
52
  export interface QuotaView {
49
53
  provider: string;
50
54
  plan: string;
55
+ weeklyPct: number;
56
+ fiveHourPct?: number;
57
+ monthlyPct?: number;
58
+ monthlyResetsAt?: string;
59
+ monthlyStatus?: "ok" | "exhausted";
60
+ monthlyKind?: "included";
61
+ /** @deprecated wire alias for weeklyPct. */
51
62
  usedPct: number;
63
+ /** @deprecated wire alias for fiveHourPct. */
52
64
  sessionPct?: number;
53
65
  resetsAt: string;
54
66
  periodStart: string;