@llblab/pi-codex-usage 0.6.0 → 0.7.0

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/BACKLOG.md CHANGED
@@ -1,4 +1,3 @@
1
1
  # Backlog
2
2
 
3
- - [ ] `Runtime smoke test` Verify the extension inside a live Pi session against missing auth, missing subscription, and transient network failure. Usable subscription with weekly reset countdown has been checked against real Codex auth.
4
- - [ ] `Statusline polish` Tune the successful-refresh blink duration if the current 150ms redraw feels too visible or too subtle in the Pi footer.
3
+ No open work.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.7.0: Exhausted Quota Visibility
6
+
7
+ - Added exhausted-quota warning background for the statusline quota bar. Impact: when either the 5-hour or weekly Codex window has 0% remaining, the bar keeps its shape but switches from the selected background to the error background for faster visual detection.
8
+ - Added exhausted-primary countdown display for cases where the 5-hour Codex window has 0% remaining and exposes a reset time. Impact: the statusline keeps the normal ten-cell dual bar and appends `<5-hour-reset>/<weekly-reset>` countdowns, so operators can see both how long to wait for short-window recovery and how much weekly quota remains.
9
+ - Fixed quota-bar rounding so a truly empty segment state is shown only when a quota window has 0% remaining, while any positive sub-5% remainder still renders as one visible step. Impact: the statusline now distinguishes exhausted Codex limits from tiny remaining quota without changing normal round-to-nearest behavior elsewhere.
10
+
5
11
  ## 0.6.0: Telegram Status Integration
6
12
 
7
13
  - Added optional `pi-telegram` status-menu integration through the public Telegram status-line provider API. When `pi-telegram` is available and the active model is an OpenAI Codex subscription model, the `/start` menu status text now includes `codex: <value>` using the same compact quota bar and reset countdown value as the terminal statusline. Impact: Telegram operators can see Codex quota/reset state in the main control menu without any extra configuration, while non-Codex models and missing `pi-telegram` installs stay unchanged.
package/README.md CHANGED
@@ -47,10 +47,18 @@ Normal usage:
47
47
  codex ██████▀▀▀▀ 6d
48
48
  ```
49
49
 
50
- The ten-character bar encodes two twenty-step limits at once: 40 total bits of quota state in 10 terminal cells. Each step is 5%: the top quadrants are the 5-hour limit, and the bottom quadrants are the weekly limit.
50
+ The ten-character bar encodes two twenty-step limits at once: 40 total bits of quota state in 10 terminal cells. Each step is 5%: the top quadrants are the 5-hour limit, and the bottom quadrants are the weekly limit. If either quota window is exhausted, the bar keeps the same shape but switches to the error background color.
51
51
 
52
52
  When the weekly reset time is available, the bar is followed by a countdown. More than a day remains is shown in 144-minute day-tenth steps such as `7d`, `6.9d`, `6.6d`, `5.1d`, `5d`, `3.7d`, `3d`, `2d`, `1.9d`, `1.5d`, and `1.1d`, rounded upward to the next tenth. At 24 hours and below it switches to upward-rounded 6-minute hour-tenth steps such as `24h`, `23.7h`, `20.1h`, `20h`, `19.9h`, `1.4h`, `1.3h`, `1.2h`, `1.1h`, and `1h`. Under an hour it switches to floored minutes, and under a minute to seconds. After the reset timestamp passes, `0s` is held until the next successful quota refresh reports the new weekly window.
53
53
 
54
+ When the 5-hour window is exhausted and exposes its own reset time, the statusline adds the 5-hour reset before the weekly reset:
55
+
56
+ ```text
57
+ codex ▄▄▄▄▄⠀⠀⠀⠀⠀ 5h/7d
58
+ ```
59
+
60
+ The ten-character dual bar stays unchanged. The first countdown is the 5-hour reset, and the second countdown after `/` is the weekly reset.
61
+
54
62
  Unavailable because Codex auth or subscription quota is not available:
55
63
 
56
64
  ```text
package/index.ts CHANGED
@@ -864,7 +864,11 @@ export function formatCodexUsageStatusline(
864
864
  const value = formatCodexUsageStatusValue(report);
865
865
  if (!value) return formatStatuslineText(ctx, "n/a");
866
866
  const [bar, countdown] = value.split(" ", 2);
867
- const barText = formatStatuslineBarText(ctx, bar ?? "");
867
+ const barText = formatStatuslineBarText(
868
+ ctx,
869
+ bar ?? "",
870
+ hasExhaustedQuotaWindow(report) ? "toolErrorBg" : "selectedBg",
871
+ );
868
872
  return countdown
869
873
  ? `${barText} ${ctx.ui.theme.fg("dim", countdown)}`
870
874
  : barText;
@@ -880,8 +884,18 @@ export function formatCodexUsageStatusValue(
880
884
  report: CodexUsageReport,
881
885
  now = Date.now(),
882
886
  ): string | undefined {
883
- const bar = formatReportBar(report);
884
- if (!bar) return undefined;
887
+ const snapshot = selectPrimaryCodexSnapshot(report);
888
+ if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
889
+ const bar = formatDualLimitBar(snapshot.primary, snapshot.secondary);
890
+ if (isQuotaWindowExhausted(snapshot.primary) && snapshot.primary?.resetAt) {
891
+ const primaryCountdown = formatResetCountdown(snapshot.primary.resetAt, now);
892
+ const weeklyCountdown = snapshot.secondary?.resetAt
893
+ ? formatResetCountdown(snapshot.secondary.resetAt, now)
894
+ : undefined;
895
+ return weeklyCountdown
896
+ ? `${bar} ${primaryCountdown}/${weeklyCountdown}`
897
+ : `${bar} ${primaryCountdown}`;
898
+ }
885
899
  const countdown = formatWeeklyResetCountdown(report, now);
886
900
  return countdown ? `${bar} ${countdown}` : bar;
887
901
  }
@@ -917,9 +931,16 @@ export function nextResetCountdownDelayMs(
917
931
  report: CodexUsageReport,
918
932
  now = Date.now(),
919
933
  ): number | undefined {
920
- const resetAt = selectPrimaryCodexSnapshot(report)?.secondary?.resetAt;
921
- if (resetAt === undefined) return undefined;
922
- return nextResetCountdownDelayForRemainingMs(resetAt - now);
934
+ const snapshot = selectPrimaryCodexSnapshot(report);
935
+ const resetTimes = [snapshot?.secondary?.resetAt];
936
+ if (isQuotaWindowExhausted(snapshot?.primary)) {
937
+ resetTimes.push(snapshot?.primary?.resetAt);
938
+ }
939
+ const delays = resetTimes
940
+ .filter((resetAt): resetAt is number => resetAt !== undefined)
941
+ .map((resetAt) => nextResetCountdownDelayForRemainingMs(resetAt - now))
942
+ .filter((delay): delay is number => delay !== undefined);
943
+ return delays.length > 0 ? Math.min(...delays) : undefined;
923
944
  }
924
945
 
925
946
  export function nextResetCountdownDelayForRemainingMs(
@@ -962,9 +983,13 @@ function formatStatuslineText(ctx: ExtensionContext, value: string): string {
962
983
  return `${label} ${ctx.ui.theme.fg("dim", value)}`;
963
984
  }
964
985
 
965
- function formatStatuslineBarText(ctx: ExtensionContext, bar: string): string {
986
+ function formatStatuslineBarText(
987
+ ctx: ExtensionContext,
988
+ bar: string,
989
+ background: "selectedBg" | "toolErrorBg" = "selectedBg",
990
+ ): string {
966
991
  const label = ctx.ui.theme.fg("accent", STATUS_LABEL_TEXT);
967
- const value = ctx.ui.theme.bg("selectedBg", ctx.ui.theme.fg("dim", bar));
992
+ const value = ctx.ui.theme.bg(background, ctx.ui.theme.fg("dim", bar));
968
993
  return `${label} ${value}`;
969
994
  }
970
995
 
@@ -1041,15 +1066,38 @@ function formatDualLimitBar(
1041
1066
 
1042
1067
  function filledTwentieths(
1043
1068
  window: NormalizedRateLimitWindow | undefined,
1069
+ ): number {
1070
+ return filledParts(window, 20);
1071
+ }
1072
+
1073
+ function filledParts(
1074
+ window: NormalizedRateLimitWindow | undefined,
1075
+ totalParts: number,
1044
1076
  ): number {
1045
1077
  if (!window) return 0;
1046
- return Math.round(remainingPercent(window) / 5);
1078
+ const remaining = remainingPercent(window);
1079
+ if (remaining <= 0) return 0;
1080
+ return Math.max(1, Math.round(remaining / (100 / totalParts)));
1047
1081
  }
1048
1082
 
1049
1083
  function remainingPercent(window: NormalizedRateLimitWindow): number {
1050
1084
  return 100 - clampPercent(window.usedPercent);
1051
1085
  }
1052
1086
 
1087
+ function isQuotaWindowExhausted(
1088
+ window: NormalizedRateLimitWindow | undefined,
1089
+ ): boolean {
1090
+ return window !== undefined && remainingPercent(window) <= 0;
1091
+ }
1092
+
1093
+ function hasExhaustedQuotaWindow(report: CodexUsageReport): boolean {
1094
+ const snapshot = selectPrimaryCodexSnapshot(report);
1095
+ return (
1096
+ isQuotaWindowExhausted(snapshot?.primary) ||
1097
+ isQuotaWindowExhausted(snapshot?.secondary)
1098
+ );
1099
+ }
1100
+
1053
1101
  function isPrimaryCodexSnapshot(
1054
1102
  snapshot: NormalizedRateLimitSnapshot,
1055
1103
  ): boolean {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-codex-usage",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Minimal Pi extension that shows primary Codex ChatGPT subscription usage limits.",
6
6
  "keywords": [