@llblab/pi-codex-usage 0.5.2 → 0.6.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/AGENTS.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Agent Notes
2
2
 
3
- - `Statusline-only scope`: Keep this extension zero-configuration and statusline-only.
3
+ - `Statusline-first scope`: Keep this extension zero-configuration and focused on compact status surfaces.
4
4
  - Trigger: Considering commands, menus, persisted settings, or notification output.
5
- - Action: Prefer deleting the surface unless it is required for the optimistic status widget.
5
+ - Action: Prefer deleting the surface unless it is required for the optimistic TUI status widget or the optional `pi-telegram` `/start` status-line mirror.
6
6
 
7
7
  - `Optimistic refresh`: Preserve the last good statusline bar during refresh and transient failures.
8
8
  - Trigger: Updating quota polling or error handling.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.6.0: Telegram Status Integration
6
+
7
+ - 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.
8
+
5
9
  ## 0.5.2: Sub-Day Reset Countdown And JPEG Banner
6
10
 
7
11
  - Refined the weekly reset countdown below 24 hours to use upward-rounded 6-minute hour-tenth steps (`24h`, `23.7h`, `20.1h`, `20h`, `19.9h`, …, `1h`) instead of coarse whole-hour floors. Impact: the statusline gives more useful sub-day reset timing without growing wider than one decimal place.
package/README.md CHANGED
@@ -16,6 +16,7 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
16
16
 
17
17
  - Shows an empty statusline bar immediately, then refreshes every 30 seconds while the active Pi model uses `openai-codex`
18
18
  - Statusline output stays compact, with the `codex` label accented and the quota bar plus weekly reset countdown drawn on a themed background
19
+ - When `pi-telegram` is available, the same compact value appears as `codex: <value>` in the `/start` menu status text for active OpenAI Codex subscription models
19
20
  - Additional returned buckets, including Spark-specific limits, are ignored
20
21
  - Pi OpenAI Codex provider auth is used first
21
22
  - Codex CLI app-server remains available as a fallback
@@ -62,6 +63,16 @@ Runtime failure, such as a network or provider error:
62
63
  codex error
63
64
  ```
64
65
 
66
+ ## Telegram Status Menu
67
+
68
+ If `@llblab/pi-telegram` is loaded with the public status-line provider API, this extension registers an optional `/start` menu status row. The row is shown only while the active model uses the OpenAI Codex subscription provider:
69
+
70
+ ```text
71
+ codex: ██████▀▀▀▀ 6d
72
+ ```
73
+
74
+ The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline. If `pi-telegram` is absent, older, or the active model is not a Codex subscription model, no Telegram row is added.
75
+
65
76
  ## Auth
66
77
 
67
78
  The extension tries usage sources in this order:
package/index.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
 
9
9
  const CODEX_PROVIDER_ID = "openai-codex";
10
+ const CODEX_USAGE_EXTENSION_ID = "@llblab/pi-codex-usage";
10
11
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
11
12
  const DEFAULT_TIMEOUT_MS = 15_000;
12
13
  const SECOND_MS = 1000;
@@ -21,6 +22,10 @@ const STATUS_KEY = "aa-codex-usage";
21
22
  const MAX_ERROR_BODY_CHARS = 600;
22
23
  const STATUS_LABEL_TEXT = "codex";
23
24
  const DUAL_BAR_WIDTH = 10;
25
+ const TELEGRAM_STATUS_IMPORT_SPECIFIERS = [
26
+ "@llblab/pi-telegram/status",
27
+ new URL("../pi-telegram/api/status.ts", import.meta.url).href,
28
+ ];
24
29
  const DUAL_BAR_CHARS = [
25
30
  "⠀",
26
31
  "▘",
@@ -44,6 +49,19 @@ type UsageSource = "pi-auth" | "codex-app-server";
44
49
  type TimeoutHandle = ReturnType<typeof setTimeout> & { unref?: () => void };
45
50
  type PiModel = NonNullable<ExtensionContext["model"]>;
46
51
  export type CodexUsageModel = Pick<PiModel, "id" | "name" | "provider">;
52
+ type CodexUsageTelegramStatusModel = Pick<PiModel, "provider">;
53
+ type TelegramStatusLineProviderResult =
54
+ | { label: string; value: string }
55
+ | undefined;
56
+ type TelegramStatusLineProvider = (ctx: {
57
+ activeModel?: CodexUsageTelegramStatusModel;
58
+ }) => TelegramStatusLineProviderResult;
59
+ type TelegramStatusLineModule = {
60
+ registerTelegramStatusLineProvider?: (
61
+ provider: TelegramStatusLineProvider,
62
+ options: { id: string },
63
+ ) => () => void;
64
+ };
47
65
 
48
66
  type QueryUsageOptions = {
49
67
  timeoutMs: number;
@@ -140,6 +158,26 @@ export default function codexUsage(pi: ExtensionAPI) {
140
158
  let statuslineCountdownTimer: TimeoutHandle | undefined;
141
159
  let statuslineRefreshTimer: TimeoutHandle | undefined;
142
160
  let statuslineRequestId = 0;
161
+ let unregisterTelegramStatusLine: (() => void) | undefined;
162
+ let telegramStatusLineRegistration: Promise<void> | undefined;
163
+
164
+ const ensureTelegramStatusLineRegistered = () => {
165
+ if (unregisterTelegramStatusLine || telegramStatusLineRegistration) return;
166
+ telegramStatusLineRegistration = registerCodexUsageTelegramStatusLine(
167
+ ({ activeModel }) => {
168
+ if (!isOpenAICodexModel(activeModel)) return undefined;
169
+ if (!cache) return undefined;
170
+ const value = formatCodexUsageStatusValue(cache.report);
171
+ return value ? { label: "codex", value } : undefined;
172
+ },
173
+ )
174
+ .then((unregister) => {
175
+ unregisterTelegramStatusLine = unregister;
176
+ })
177
+ .finally(() => {
178
+ telegramStatusLineRegistration = undefined;
179
+ });
180
+ };
143
181
 
144
182
  const clearStatuslineTimers = () => {
145
183
  if (statuslineBlinkTimer) clearTimeout(statuslineBlinkTimer);
@@ -299,7 +337,10 @@ export default function codexUsage(pi: ExtensionAPI) {
299
337
  });
300
338
  };
301
339
 
340
+ ensureTelegramStatusLineRegistered();
341
+
302
342
  pi.on("session_start", (_event, ctx) => {
343
+ ensureTelegramStatusLineRegistered();
303
344
  if (isOpenAICodexModel(ctx.model))
304
345
  void refreshCurrentCodexUsageStatusline(ctx, false);
305
346
  else clearUsageStatusline(ctx);
@@ -319,7 +360,11 @@ export default function codexUsage(pi: ExtensionAPI) {
319
360
  }
320
361
  });
321
362
 
322
- pi.on("session_shutdown", (_event, ctx) => clearUsageStatusline(ctx));
363
+ pi.on("session_shutdown", (_event, ctx) => {
364
+ clearUsageStatusline(ctx);
365
+ unregisterTelegramStatusLine?.();
366
+ unregisterTelegramStatusLine = undefined;
367
+ });
323
368
  }
324
369
 
325
370
  function isOpenAICodexModel(
@@ -328,6 +373,31 @@ function isOpenAICodexModel(
328
373
  return model?.provider === CODEX_PROVIDER_ID;
329
374
  }
330
375
 
376
+ async function importTelegramStatusLineModule(): Promise<
377
+ TelegramStatusLineModule | undefined
378
+ > {
379
+ for (const specifier of TELEGRAM_STATUS_IMPORT_SPECIFIERS) {
380
+ try {
381
+ const imported = (await import(specifier)) as TelegramStatusLineModule;
382
+ if (typeof imported.registerTelegramStatusLineProvider === "function") {
383
+ return imported;
384
+ }
385
+ } catch {
386
+ // pi-telegram is optional; absence just disables the Telegram status line.
387
+ }
388
+ }
389
+ return undefined;
390
+ }
391
+
392
+ async function registerCodexUsageTelegramStatusLine(
393
+ provider: TelegramStatusLineProvider,
394
+ ): Promise<(() => void) | undefined> {
395
+ const telegramStatus = await importTelegramStatusLineModule();
396
+ return telegramStatus?.registerTelegramStatusLineProvider?.(provider, {
397
+ id: CODEX_USAGE_EXTENSION_ID,
398
+ });
399
+ }
400
+
331
401
  async function queryUsage(
332
402
  ctx: ExtensionContext,
333
403
  options: Pick<QueryUsageOptions, "timeoutMs">,
@@ -791,10 +861,10 @@ export function formatCodexUsageStatusline(
791
861
  ctx: ExtensionContext,
792
862
  _model?: CodexUsageModel,
793
863
  ): string {
794
- const bar = formatReportBar(report);
795
- if (!bar) return formatStatuslineText(ctx, "n/a");
796
- const countdown = formatWeeklyResetCountdown(report);
797
- const barText = formatStatuslineBarText(ctx, bar);
864
+ const value = formatCodexUsageStatusValue(report);
865
+ if (!value) return formatStatuslineText(ctx, "n/a");
866
+ const [bar, countdown] = value.split(" ", 2);
867
+ const barText = formatStatuslineBarText(ctx, bar ?? "");
798
868
  return countdown
799
869
  ? `${barText} ${ctx.ui.theme.fg("dim", countdown)}`
800
870
  : barText;
@@ -806,6 +876,16 @@ export function formatCodexUsageBar(
806
876
  return formatReportBar(report);
807
877
  }
808
878
 
879
+ export function formatCodexUsageStatusValue(
880
+ report: CodexUsageReport,
881
+ now = Date.now(),
882
+ ): string | undefined {
883
+ const bar = formatReportBar(report);
884
+ if (!bar) return undefined;
885
+ const countdown = formatWeeklyResetCountdown(report, now);
886
+ return countdown ? `${bar} ${countdown}` : bar;
887
+ }
888
+
809
889
  export function formatWeeklyResetCountdown(
810
890
  report: CodexUsageReport,
811
891
  now = Date.now(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-codex-usage",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Minimal Pi extension that shows primary Codex ChatGPT subscription usage limits.",
6
6
  "keywords": [