@hk_net/pi-usage-bars 0.3.0 → 0.4.1
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/CHANGELOG.md +35 -0
- package/README.md +41 -5
- package/docs/financial-metrics-plan.md +75 -0
- package/docs/releasing.md +84 -0
- package/extensions/usage-bars/core.ts +628 -13
- package/extensions/usage-bars/index.ts +170 -22
- package/package.json +12 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Quota, balance, and spend indicators for providers supported by current Pi releases. */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
DynamicBorder,
|
|
@@ -22,9 +22,16 @@ import {
|
|
|
22
22
|
fetchAllUsages,
|
|
23
23
|
fetchClaudeUsageWithFallback,
|
|
24
24
|
fetchCodexUsage,
|
|
25
|
+
fetchDeepSeekBalance,
|
|
26
|
+
fetchKimiUsage,
|
|
27
|
+
fetchMiniMaxUsage,
|
|
28
|
+
fetchMoonshotBalance,
|
|
29
|
+
fetchOpenRouterUsage,
|
|
25
30
|
fetchZaiUsage,
|
|
26
31
|
providerToPiProviderId,
|
|
27
32
|
resolveUsageEndpoints,
|
|
33
|
+
type AccountBalance,
|
|
34
|
+
type AccountSpend,
|
|
28
35
|
type ProviderKey,
|
|
29
36
|
type UsageByProvider,
|
|
30
37
|
type UsageData,
|
|
@@ -33,15 +40,63 @@ import {
|
|
|
33
40
|
|
|
34
41
|
const POLL_INTERVAL_MS = 2 * 60 * 1000;
|
|
35
42
|
const STATUS_KEY = "usage-bars";
|
|
36
|
-
const PROVIDERS: readonly ProviderKey[] = [
|
|
43
|
+
const PROVIDERS: readonly ProviderKey[] = [
|
|
44
|
+
"codex",
|
|
45
|
+
"claude",
|
|
46
|
+
"zai",
|
|
47
|
+
"zai-cn",
|
|
48
|
+
"kimi",
|
|
49
|
+
"minimax",
|
|
50
|
+
"minimax-cn",
|
|
51
|
+
"openrouter",
|
|
52
|
+
"deepseek",
|
|
53
|
+
"moonshot",
|
|
54
|
+
"moonshot-cn",
|
|
55
|
+
];
|
|
37
56
|
|
|
38
57
|
const PROVIDER_LABELS: Record<ProviderKey, string> = {
|
|
39
58
|
codex: "Codex",
|
|
40
59
|
claude: "Claude",
|
|
41
60
|
zai: "ZAI Coding Plan (Global)",
|
|
42
61
|
"zai-cn": "ZAI Coding Plan (China)",
|
|
62
|
+
kimi: "Kimi For Coding",
|
|
63
|
+
minimax: "MiniMax Coding Plan (Global)",
|
|
64
|
+
"minimax-cn": "MiniMax Coding Plan (China)",
|
|
65
|
+
openrouter: "OpenRouter",
|
|
66
|
+
deepseek: "DeepSeek",
|
|
67
|
+
moonshot: "Moonshot/Kimi API (Global)",
|
|
68
|
+
"moonshot-cn": "Moonshot/Kimi API (China)",
|
|
43
69
|
};
|
|
44
70
|
|
|
71
|
+
function formatFinancialAmount(amount: number, unit: string): string {
|
|
72
|
+
if (/^[A-Z]{3}$/.test(unit)) {
|
|
73
|
+
return new Intl.NumberFormat("en-US", {
|
|
74
|
+
style: "currency",
|
|
75
|
+
currency: unit,
|
|
76
|
+
minimumFractionDigits: 2,
|
|
77
|
+
maximumFractionDigits: 5,
|
|
78
|
+
}).format(amount);
|
|
79
|
+
}
|
|
80
|
+
const formatted = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(amount);
|
|
81
|
+
return `${formatted} ${unit}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatAccountBalance(balance: AccountBalance): string {
|
|
85
|
+
return `${balance.label} · ${formatFinancialAmount(balance.amount, balance.unit)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatAccountSpend(spend: AccountSpend): string {
|
|
89
|
+
const values = [
|
|
90
|
+
spend.daily === undefined ? undefined : `today ${formatFinancialAmount(spend.daily, spend.unit)}`,
|
|
91
|
+
spend.weekly === undefined ? undefined : `week ${formatFinancialAmount(spend.weekly, spend.unit)}`,
|
|
92
|
+
spend.monthly === undefined ? undefined : `month ${formatFinancialAmount(spend.monthly, spend.unit)}`,
|
|
93
|
+
].filter((value): value is string => Boolean(value));
|
|
94
|
+
if (values.length === 0 && spend.lifetime !== undefined) {
|
|
95
|
+
values.push(`lifetime ${formatFinancialAmount(spend.lifetime, spend.unit)}`);
|
|
96
|
+
}
|
|
97
|
+
return `Spent · ${values.join(" · ")}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
45
100
|
interface SubscriptionItem {
|
|
46
101
|
name: string;
|
|
47
102
|
provider: ProviderKey;
|
|
@@ -135,12 +190,12 @@ class UsageSelectorComponent extends Container implements Focusable {
|
|
|
135
190
|
|
|
136
191
|
private updateHint(): void {
|
|
137
192
|
if (this.hint === "loading") {
|
|
138
|
-
this.hintText.setText(this.theme.fg("dim", "Fetching
|
|
193
|
+
this.hintText.setText(this.theme.fg("dim", "Fetching quota, balance, and spend from configured providers…"));
|
|
139
194
|
} else if (this.hint === "error") {
|
|
140
195
|
this.hintText.setText(this.theme.fg("error", "Failed to fetch usage data"));
|
|
141
196
|
} else {
|
|
142
197
|
this.hintText.setText(
|
|
143
|
-
this.theme.fg("muted", "Only showing configured
|
|
198
|
+
this.theme.fg("muted", "Only showing configured usage providers. ") +
|
|
144
199
|
this.theme.fg("dim", "✓ = active provider"),
|
|
145
200
|
);
|
|
146
201
|
}
|
|
@@ -197,19 +252,51 @@ class UsageSelectorComponent extends Container implements Focusable {
|
|
|
197
252
|
const weeklyReset = item.data.weeklyResetsIn
|
|
198
253
|
? theme.fg("dim", ` resets in ${item.data.weeklyResetsIn}`)
|
|
199
254
|
: "";
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
255
|
+
const sessionLabel = (item.data.sessionLabel ?? "Session").slice(0, 9).padEnd(10);
|
|
256
|
+
const weeklyLabel = (item.data.weeklyLabel ?? "Weekly").slice(0, 9).padEnd(10);
|
|
257
|
+
|
|
258
|
+
if (!item.data.quotaHidden) {
|
|
259
|
+
if (!item.data.sessionHidden) {
|
|
260
|
+
this.listContainer.addChild(new Text(
|
|
261
|
+
indent + theme.fg("muted", sessionLabel) + this.renderBar(session) + " " +
|
|
262
|
+
theme.fg(colorForPercent(session), `${session}%`.padStart(4)) + sessionReset,
|
|
263
|
+
0,
|
|
264
|
+
0,
|
|
265
|
+
));
|
|
266
|
+
}
|
|
267
|
+
if (!item.data.weeklyHidden) {
|
|
268
|
+
this.listContainer.addChild(new Text(
|
|
269
|
+
indent + theme.fg("muted", weeklyLabel) + this.renderBar(weekly) + " " +
|
|
270
|
+
theme.fg(colorForPercent(weekly), `${weekly}%`.padStart(4)) + weeklyReset,
|
|
271
|
+
0,
|
|
272
|
+
0,
|
|
273
|
+
));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (item.data.accountBalance) {
|
|
277
|
+
this.listContainer.addChild(new Text(
|
|
278
|
+
indent + theme.fg("muted", formatAccountBalance(item.data.accountBalance)),
|
|
279
|
+
0,
|
|
280
|
+
0,
|
|
281
|
+
));
|
|
282
|
+
}
|
|
283
|
+
for (const balance of item.data.accountBalanceDetails ?? []) {
|
|
284
|
+
this.listContainer.addChild(new Text(
|
|
285
|
+
indent + theme.fg("dim", formatAccountBalance(balance)),
|
|
286
|
+
0,
|
|
287
|
+
0,
|
|
288
|
+
));
|
|
289
|
+
}
|
|
290
|
+
if (item.data.accountSpend) {
|
|
291
|
+
this.listContainer.addChild(new Text(
|
|
292
|
+
indent + theme.fg("muted", formatAccountSpend(item.data.accountSpend)),
|
|
293
|
+
0,
|
|
294
|
+
0,
|
|
295
|
+
));
|
|
296
|
+
}
|
|
297
|
+
if (item.data.notice) {
|
|
298
|
+
this.listContainer.addChild(new Text(indent + theme.fg("muted", item.data.notice), 0, 0));
|
|
299
|
+
}
|
|
213
300
|
|
|
214
301
|
if (typeof item.data.extraSpend === "number" && typeof item.data.extraLimit === "number") {
|
|
215
302
|
this.listContainer.addChild(new Text(
|
|
@@ -300,6 +387,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
300
387
|
claude: null,
|
|
301
388
|
zai: null,
|
|
302
389
|
"zai-cn": null,
|
|
390
|
+
kimi: null,
|
|
391
|
+
minimax: null,
|
|
392
|
+
"minimax-cn": null,
|
|
393
|
+
openrouter: null,
|
|
394
|
+
deepseek: null,
|
|
395
|
+
moonshot: null,
|
|
396
|
+
"moonshot-cn": null,
|
|
303
397
|
activeProvider: null,
|
|
304
398
|
available: {},
|
|
305
399
|
};
|
|
@@ -343,15 +437,50 @@ export default function (pi: ExtensionAPI): void {
|
|
|
343
437
|
ctx.ui.setStatus(STATUS_KEY, theme.fg("warning", `${label} usage unavailable (${data.error})`));
|
|
344
438
|
return;
|
|
345
439
|
}
|
|
440
|
+
if (data.quotaHidden) {
|
|
441
|
+
const financial = [
|
|
442
|
+
data.accountBalance ? formatAccountBalance(data.accountBalance) : undefined,
|
|
443
|
+
data.accountSpend?.monthly === undefined
|
|
444
|
+
? undefined
|
|
445
|
+
: `Month · ${formatFinancialAmount(data.accountSpend.monthly, data.accountSpend.unit)}`,
|
|
446
|
+
].filter((value): value is string => Boolean(value));
|
|
447
|
+
const summary = financial.length > 0 ? financial.join(" · ") : data.notice;
|
|
448
|
+
ctx.ui.setStatus(
|
|
449
|
+
STATUS_KEY,
|
|
450
|
+
summary ? theme.fg("dim", `${label} `) + theme.fg("muted", summary) : undefined,
|
|
451
|
+
);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
346
454
|
|
|
347
455
|
const session = clampPercent(data.session);
|
|
348
456
|
const weekly = clampPercent(data.weekly);
|
|
457
|
+
const sessionPrefix = data.sessionLabel === "5-hour"
|
|
458
|
+
? "5h "
|
|
459
|
+
: data.sessionLabel === "Interval"
|
|
460
|
+
? "I "
|
|
461
|
+
: data.sessionLabel === "Key limit"
|
|
462
|
+
? "L "
|
|
463
|
+
: "S ";
|
|
464
|
+
const quotaLanes: string[] = [];
|
|
465
|
+
if (!data.sessionHidden) {
|
|
466
|
+
quotaLanes.push(
|
|
467
|
+
theme.fg("muted", sessionPrefix) + renderBar(theme, session) + " " + renderPercent(theme, session) +
|
|
468
|
+
(data.sessionResetsIn ? theme.fg("dim", ` ⟳ ${data.sessionResetsIn}`) : ""),
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
if (!data.weeklyHidden) {
|
|
472
|
+
quotaLanes.push(
|
|
473
|
+
theme.fg("muted", "W ") + renderBar(theme, weekly) + " " + renderPercent(theme, weekly) +
|
|
474
|
+
(data.weeklyResetsIn ? theme.fg("dim", ` ⟳ ${data.weeklyResetsIn}`) : ""),
|
|
475
|
+
);
|
|
476
|
+
}
|
|
349
477
|
const status =
|
|
350
478
|
theme.fg("dim", `${label} `) +
|
|
351
|
-
|
|
352
|
-
(data.
|
|
353
|
-
|
|
354
|
-
|
|
479
|
+
quotaLanes.join(" ") +
|
|
480
|
+
(data.accountBalance ? theme.fg("muted", ` · ${formatAccountBalance(data.accountBalance)}`) : "") +
|
|
481
|
+
(data.accountSpend?.monthly === undefined
|
|
482
|
+
? ""
|
|
483
|
+
: theme.fg("muted", ` · Month ${formatFinancialAmount(data.accountSpend.monthly, data.accountSpend.unit)}`)) +
|
|
355
484
|
(data.stale ? theme.fg("warning", " stale") : "") +
|
|
356
485
|
(data.warning && !data.stale ? theme.fg("warning", " ⚠") : "");
|
|
357
486
|
ctx.ui.setStatus(STATUS_KEY, status);
|
|
@@ -404,6 +533,25 @@ export default function (pi: ExtensionAPI): void {
|
|
|
404
533
|
if (provider === "claude") state.claude = await fetchClaudeUsageWithFallback(credential.token, { signal });
|
|
405
534
|
if (provider === "zai") state.zai = await fetchZaiUsage(credential.token, "zai", { endpoints, signal });
|
|
406
535
|
if (provider === "zai-cn") state["zai-cn"] = await fetchZaiUsage(credential.token, "zai-cn", { endpoints, signal });
|
|
536
|
+
if (provider === "kimi") state.kimi = await fetchKimiUsage(credential.token, { endpoints, signal });
|
|
537
|
+
if (provider === "minimax") {
|
|
538
|
+
state.minimax = await fetchMiniMaxUsage(credential.token, "minimax", { endpoints, signal });
|
|
539
|
+
}
|
|
540
|
+
if (provider === "minimax-cn") {
|
|
541
|
+
state["minimax-cn"] = await fetchMiniMaxUsage(credential.token, "minimax-cn", { endpoints, signal });
|
|
542
|
+
}
|
|
543
|
+
if (provider === "openrouter") {
|
|
544
|
+
state.openrouter = await fetchOpenRouterUsage(credential.token, { endpoints, signal });
|
|
545
|
+
}
|
|
546
|
+
if (provider === "deepseek") {
|
|
547
|
+
state.deepseek = await fetchDeepSeekBalance(credential.token, { endpoints, signal });
|
|
548
|
+
}
|
|
549
|
+
if (provider === "moonshot") {
|
|
550
|
+
state.moonshot = await fetchMoonshotBalance(credential.token, "moonshot", { endpoints, signal });
|
|
551
|
+
}
|
|
552
|
+
if (provider === "moonshot-cn") {
|
|
553
|
+
state["moonshot-cn"] = await fetchMoonshotBalance(credential.token, "moonshot-cn", { endpoints, signal });
|
|
554
|
+
}
|
|
407
555
|
}
|
|
408
556
|
|
|
409
557
|
async function runPoll(): Promise<void> {
|
|
@@ -493,7 +641,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
493
641
|
});
|
|
494
642
|
|
|
495
643
|
pi.registerCommand("usage", {
|
|
496
|
-
description: "Show
|
|
644
|
+
description: "Show quota, balance, and spend for configured providers",
|
|
497
645
|
handler: async (_args, ctx) => {
|
|
498
646
|
currentContext = ctx;
|
|
499
647
|
updateProviderFrom(ctx.model);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hk_net/pi-usage-bars",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Quota, balance, and spend indicators for current Pi providers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi",
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
"usage",
|
|
10
10
|
"codex",
|
|
11
11
|
"anthropic",
|
|
12
|
-
"zai"
|
|
12
|
+
"zai",
|
|
13
|
+
"kimi",
|
|
14
|
+
"minimax",
|
|
15
|
+
"openrouter",
|
|
16
|
+
"deepseek",
|
|
17
|
+
"moonshot"
|
|
13
18
|
],
|
|
14
19
|
"license": "MIT",
|
|
15
20
|
"repository": {
|
|
@@ -27,6 +32,7 @@
|
|
|
27
32
|
"type": "module",
|
|
28
33
|
"files": [
|
|
29
34
|
"extensions",
|
|
35
|
+
"docs",
|
|
30
36
|
"README.md",
|
|
31
37
|
"CHANGELOG.md",
|
|
32
38
|
"LICENSE"
|
|
@@ -34,8 +40,7 @@
|
|
|
34
40
|
"scripts": {
|
|
35
41
|
"test": "bun test tests",
|
|
36
42
|
"typecheck": "tsc --noEmit",
|
|
37
|
-
"
|
|
38
|
-
"check": "npm run typecheck && npm test && npm run smoke",
|
|
43
|
+
"check": "npm run typecheck && npm test",
|
|
39
44
|
"prepublishOnly": "npm run check"
|
|
40
45
|
},
|
|
41
46
|
"engines": {
|
|
@@ -46,8 +51,8 @@
|
|
|
46
51
|
"@earendil-works/pi-tui": ">=0.81.1"
|
|
47
52
|
},
|
|
48
53
|
"devDependencies": {
|
|
49
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
50
|
-
"@earendil-works/pi-tui": "0.
|
|
54
|
+
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
55
|
+
"@earendil-works/pi-tui": "0.83.0",
|
|
51
56
|
"@types/bun": "1.3.14",
|
|
52
57
|
"@types/node": "24.13.3",
|
|
53
58
|
"typescript": "5.9.3"
|