@llblab/pi-codex-usage 0.5.1 → 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 +3 -3
- package/CHANGELOG.md +9 -0
- package/README.md +13 -2
- package/banner.jpg +0 -0
- package/index.ts +95 -12
- package/package.json +3 -3
- package/banner.png +0 -0
package/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Agent Notes
|
|
2
2
|
|
|
3
|
-
- `Statusline-
|
|
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.
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
|
|
15
15
|
- `Weekly reset countdown`: Append the weekly reset countdown only when the secondary window exposes a reset time.
|
|
16
16
|
- Trigger: Changing reset-time normalization or statusline refresh cadence.
|
|
17
|
-
- Action: Keep `d` labels rounded upward in 144-minute day-tenth steps, `
|
|
17
|
+
- Action: Keep `d` labels rounded upward in 144-minute day-tenth steps above 24h, show 24h..1h labels in upward-rounded 6-minute hour-tenth steps, keep `m`/`s` labels floored, and hold `0s` until a successful quota refresh reports the next window.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.5.2: Sub-Day Reset Countdown And JPEG Banner
|
|
10
|
+
|
|
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.
|
|
12
|
+
- Replaced the package/banner artwork from PNG to JPEG and updated package metadata plus README image references. Impact: the package ships the new compressed banner asset consistently across npm and Pi extension listings.
|
|
13
|
+
|
|
5
14
|
## 0.5.1: Non-Codex Bucket Hotfix
|
|
6
15
|
|
|
7
16
|
- Fixed non-Codex app-server quota buckets so Spark-only or unrelated limits are ignored instead of being displayed as Codex quota.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Minimal zero-configuration Pi extension for showing primary ChatGPT Codex usage limits in the statusline
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-codex-usage`](https://github.com/narumiruna/pi-extensions/tree/main/extensions/pi-codex-usage). It keeps the auth and quota-fetching path, but intentionally narrows the interface to the primary Codex 5-hour and weekly limits.
|
|
8
8
|
|
|
@@ -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
|
|
@@ -48,7 +49,7 @@ codex ██████▀▀▀▀ 6d
|
|
|
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
51
|
|
|
51
|
-
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`, `1.1d`,
|
|
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.
|
|
52
53
|
|
|
53
54
|
Unavailable because Codex auth or subscription quota is not available:
|
|
54
55
|
|
|
@@ -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/banner.jpg
ADDED
|
Binary file
|
package/index.ts
CHANGED
|
@@ -7,11 +7,13 @@ 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;
|
|
13
14
|
const MINUTE_MS = 60 * SECOND_MS;
|
|
14
15
|
const HOUR_MS = 60 * MINUTE_MS;
|
|
16
|
+
const HOUR_TENTH_MS = 6 * MINUTE_MS;
|
|
15
17
|
const DAY_MS = 24 * HOUR_MS;
|
|
16
18
|
const DAY_TENTH_MS = 144 * MINUTE_MS;
|
|
17
19
|
const REFRESH_INTERVAL_MS = 30 * SECOND_MS;
|
|
@@ -20,6 +22,10 @@ const STATUS_KEY = "aa-codex-usage";
|
|
|
20
22
|
const MAX_ERROR_BODY_CHARS = 600;
|
|
21
23
|
const STATUS_LABEL_TEXT = "codex";
|
|
22
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
|
+
];
|
|
23
29
|
const DUAL_BAR_CHARS = [
|
|
24
30
|
"⠀",
|
|
25
31
|
"▘",
|
|
@@ -43,6 +49,19 @@ type UsageSource = "pi-auth" | "codex-app-server";
|
|
|
43
49
|
type TimeoutHandle = ReturnType<typeof setTimeout> & { unref?: () => void };
|
|
44
50
|
type PiModel = NonNullable<ExtensionContext["model"]>;
|
|
45
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
|
+
};
|
|
46
65
|
|
|
47
66
|
type QueryUsageOptions = {
|
|
48
67
|
timeoutMs: number;
|
|
@@ -139,6 +158,26 @@ export default function codexUsage(pi: ExtensionAPI) {
|
|
|
139
158
|
let statuslineCountdownTimer: TimeoutHandle | undefined;
|
|
140
159
|
let statuslineRefreshTimer: TimeoutHandle | undefined;
|
|
141
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
|
+
};
|
|
142
181
|
|
|
143
182
|
const clearStatuslineTimers = () => {
|
|
144
183
|
if (statuslineBlinkTimer) clearTimeout(statuslineBlinkTimer);
|
|
@@ -298,7 +337,10 @@ export default function codexUsage(pi: ExtensionAPI) {
|
|
|
298
337
|
});
|
|
299
338
|
};
|
|
300
339
|
|
|
340
|
+
ensureTelegramStatusLineRegistered();
|
|
341
|
+
|
|
301
342
|
pi.on("session_start", (_event, ctx) => {
|
|
343
|
+
ensureTelegramStatusLineRegistered();
|
|
302
344
|
if (isOpenAICodexModel(ctx.model))
|
|
303
345
|
void refreshCurrentCodexUsageStatusline(ctx, false);
|
|
304
346
|
else clearUsageStatusline(ctx);
|
|
@@ -318,7 +360,11 @@ export default function codexUsage(pi: ExtensionAPI) {
|
|
|
318
360
|
}
|
|
319
361
|
});
|
|
320
362
|
|
|
321
|
-
pi.on("session_shutdown", (_event, ctx) =>
|
|
363
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
364
|
+
clearUsageStatusline(ctx);
|
|
365
|
+
unregisterTelegramStatusLine?.();
|
|
366
|
+
unregisterTelegramStatusLine = undefined;
|
|
367
|
+
});
|
|
322
368
|
}
|
|
323
369
|
|
|
324
370
|
function isOpenAICodexModel(
|
|
@@ -327,6 +373,31 @@ function isOpenAICodexModel(
|
|
|
327
373
|
return model?.provider === CODEX_PROVIDER_ID;
|
|
328
374
|
}
|
|
329
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
|
+
|
|
330
401
|
async function queryUsage(
|
|
331
402
|
ctx: ExtensionContext,
|
|
332
403
|
options: Pick<QueryUsageOptions, "timeoutMs">,
|
|
@@ -790,10 +861,10 @@ export function formatCodexUsageStatusline(
|
|
|
790
861
|
ctx: ExtensionContext,
|
|
791
862
|
_model?: CodexUsageModel,
|
|
792
863
|
): string {
|
|
793
|
-
const
|
|
794
|
-
if (!
|
|
795
|
-
const countdown =
|
|
796
|
-
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 ?? "");
|
|
797
868
|
return countdown
|
|
798
869
|
? `${barText} ${ctx.ui.theme.fg("dim", countdown)}`
|
|
799
870
|
: barText;
|
|
@@ -805,6 +876,16 @@ export function formatCodexUsageBar(
|
|
|
805
876
|
return formatReportBar(report);
|
|
806
877
|
}
|
|
807
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
|
+
|
|
808
889
|
export function formatWeeklyResetCountdown(
|
|
809
890
|
report: CodexUsageReport,
|
|
810
891
|
now = Date.now(),
|
|
@@ -819,11 +900,14 @@ export function formatResetCountdown(
|
|
|
819
900
|
now = Date.now(),
|
|
820
901
|
): string {
|
|
821
902
|
const remainingMs = Math.max(0, resetAt - now);
|
|
822
|
-
if (remainingMs
|
|
903
|
+
if (remainingMs > DAY_MS) {
|
|
823
904
|
const dayTenths = Math.max(10, Math.ceil(remainingMs / DAY_TENTH_MS));
|
|
824
905
|
return `${formatTenths(dayTenths)}d`;
|
|
825
906
|
}
|
|
826
|
-
if (remainingMs >= HOUR_MS)
|
|
907
|
+
if (remainingMs >= HOUR_MS) {
|
|
908
|
+
const hourTenths = Math.max(10, Math.ceil(remainingMs / HOUR_TENTH_MS));
|
|
909
|
+
return `${formatTenths(hourTenths)}h`;
|
|
910
|
+
}
|
|
827
911
|
if (remainingMs >= MINUTE_MS)
|
|
828
912
|
return `${Math.floor(remainingMs / MINUTE_MS)}m`;
|
|
829
913
|
return `${Math.floor(remainingMs / SECOND_MS)}s`;
|
|
@@ -842,15 +926,14 @@ export function nextResetCountdownDelayForRemainingMs(
|
|
|
842
926
|
remainingMs: number,
|
|
843
927
|
): number | undefined {
|
|
844
928
|
if (remainingMs <= 0) return undefined;
|
|
845
|
-
if (remainingMs
|
|
929
|
+
if (remainingMs > DAY_MS) {
|
|
846
930
|
const dayTenths = Math.max(10, Math.ceil(remainingMs / DAY_TENTH_MS));
|
|
847
931
|
return Math.max(1, remainingMs - (dayTenths - 1) * DAY_TENTH_MS);
|
|
848
932
|
}
|
|
849
933
|
if (remainingMs >= HOUR_MS) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
);
|
|
934
|
+
const hourTenths = Math.max(10, Math.ceil(remainingMs / HOUR_TENTH_MS));
|
|
935
|
+
if (hourTenths === 10) return Math.max(1, remainingMs - HOUR_MS + 1);
|
|
936
|
+
return Math.max(1, remainingMs - (hourTenths - 1) * HOUR_TENTH_MS);
|
|
854
937
|
}
|
|
855
938
|
if (remainingMs >= MINUTE_MS) {
|
|
856
939
|
return Math.max(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-codex-usage",
|
|
3
|
-
"version": "0.
|
|
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": [
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"BACKLOG.md",
|
|
39
39
|
"CHANGELOG.md",
|
|
40
40
|
"LICENSE",
|
|
41
|
-
"banner.
|
|
41
|
+
"banner.jpg"
|
|
42
42
|
],
|
|
43
43
|
"pi": {
|
|
44
44
|
"extensions": [
|
|
45
45
|
"./index.ts"
|
|
46
46
|
],
|
|
47
|
-
"image": "https://github.com/llblab/pi-codex-usage/raw/main/banner.
|
|
47
|
+
"image": "https://github.com/llblab/pi-codex-usage/raw/main/banner.jpg"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@earendil-works/pi-agent-core": "*",
|
package/banner.png
DELETED
|
Binary file
|