@bacnh85/pi-sub 0.1.24 → 0.1.26

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 CHANGED
@@ -1,5 +1,62 @@
1
+ ## 0.1.26 (2026-08-21)
2
+
3
+ - Router adapter: reads `router.baseUrl` from `~/.pi/agent/settings.json`
4
+ (env `ROUTER_BASE_URL`/`NINE_ROUTER_BASE_URL`) now that pi-router moved the
5
+ endpoint URL out of the old `9router-config.json` (which is migrated away on
6
+ pi-router load). Footer shows for both new `router/` and legacy `9router/`
7
+ provider models.
8
+
1
9
  # Changelog
2
10
 
11
+ ## 0.1.29 (2026-08-15)
12
+
13
+ ### Features
14
+
15
+ - **tok/s speed now shown for all providers.** Previously the footer was cleared
16
+ for providers without a subscription adapter (e.g. `ollama`); now the last
17
+ response speed renders as a standalone `145 tok/s` line, and `/sub` reports
18
+ provider/model plus last and session-average speed instead of only
19
+ "tracking inactive".
20
+
21
+ ## 0.1.28 (2026-08-09)
22
+
23
+ ### Improvements
24
+
25
+ - **Command Code monthly balance now shown in the footer.** The footer adds a
26
+ compact `M:$X.XX` segment (monthly credit balance from
27
+ `/alpha/billing/credits`) next to the 5-hour/weekly windows, e.g.
28
+ `(Command Code key#…) R:100%/5H W:100%/7D M:$69.99`. The `/sub` detail view
29
+ keeps the `Monthly: $X remaining` breakdown line.
30
+
31
+ ## 0.1.27 (2026-08-09)
32
+
33
+ ### Features
34
+
35
+ - **Command Code now shows live usage windows.** The adapter fetches
36
+ `https://api.commandcode.ai/alpha/billing/credits` with the same Provider
37
+ API key used for `/provider/v1` models and renders the 5-hour and weekly
38
+ rolling windows (USD used/cap, remaining% + reset countdown) plus a
39
+ `Monthly: $X remaining` balance line — matching what the `cmd /usage` CLI
40
+ shows. Previously the footer showed session cost only.
41
+
42
+ ## 0.1.26 (2026-08-09)
43
+
44
+ ### Features
45
+
46
+ - Added Command Code (`commandcode`) provider support. When a `commandcode`
47
+ model is active, the footer shows the account label plus session cost and
48
+ tok/s, and `/sub` reports the provider. Command Code's Provider API does not
49
+ expose usage windows via its API key (the rolling-window data is
50
+ web-session-cookie only), so live 5h/weekly meters are not shown — behavior
51
+ matches `opencode-go` and `9router`.
52
+
53
+ ## 0.1.25 (2026-08-07)
54
+
55
+ ### Improvements
56
+
57
+ - Widen peer dependency range to support Pi 0.84.0 (`>=0.80.8 <0.85.0`).
58
+ No code changes — verified compatible against the 0.84.0 SDK types.
59
+
3
60
  ## 0.1.24 (2026-08-05)
4
61
 
5
62
  ### Improvements
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Pi extension that shows subscription usage for the currently selected supported model provider.
4
4
 
5
- Supports OpenAI Codex (`openai-codex`) with live usage windows from ChatGPT's usage endpoint, OpenCode Go (`opencode-go`) with session cost tracking, and Z.ai GLM Coding Plan — both the international (`zai`) and China (`zai-coding-cn`, `open.bigmodel.cn`) endpoints — with quota monitoring. Displays a subscription footer status after Pi's built-in status/token usage line.
5
+ Supports OpenAI Codex (`openai-codex`) with live usage windows from ChatGPT's usage endpoint, OpenCode Go (`opencode-go`) with session cost tracking, and Z.ai GLM Coding Plan — both the international (`zai`) and China (`zai-coding-cn`, `open.bigmodel.cn`) endpoints — with quota monitoring. Also tracks Command Code (`commandcode`) 5-hour/weekly windows and monthly credit balance. Displays a subscription footer status after Pi's built-in status/token usage line.
6
6
 
7
7
  ## Install
8
8
 
@@ -51,19 +51,41 @@ The built-in `zai-coding-cn` provider targets the domestic BigModel endpoint (`o
51
51
  (Z.ai (CN) key#1a2b3c4d) R:55%/2H W:80%/3D 42 tok/s
52
52
  ```
53
53
 
54
- ### 9router
54
+ ### Router (pi-router — formerly 9router)
55
55
 
56
- 9router is a proxy/router with no upstream usage API, so the footer shows the configured endpoint and last response speed:
56
+ The router (9router, omniroute, …) is a proxy with no upstream usage API, so the footer shows the configured endpoint and last response speed. The endpoint URL is read from `~/.pi/agent/settings.json` (`router.baseUrl`) with `ROUTER_BASE_URL` env override:
57
57
 
58
58
  ```text
59
- 9router (172.30.55.22:20128) 145 tok/s
59
+ Router (172.30.55.22:20128) 145 tok/s
60
60
  ```
61
61
 
62
+ ### Command Code
63
+
64
+ Command Code exposes live usage windows via its `/alpha/billing/credits` endpoint (same Provider API key used for `/provider/v1` models — no cookies). The footer shows the active account/key label, 5-hour and weekly remaining windows with reset countdowns, and last response speed:
65
+
66
+ ```text
67
+ (Command Code key#1a2b3c4d) R:99%/4H W:99%/6D M:$69.99 42 tok/s
68
+ ```
69
+
70
+ The `M:$X.XX` segment is the monthly credit balance (the plan's remaining
71
+ monthly allowance in USD). The `/sub` detail view also shows a
72
+ `Monthly: $X remaining` line.
73
+
62
74
  ### Tokens per second
63
75
 
64
76
  `pi-sub` tracks each response's tokens-per-second (tok/s) speed by measuring the time from provider request to message completion against the response's output token count. The last response's speed is shown in the footer next to usage data. The `/sub` detail view shows both the last response speed and the session-wide average.
65
77
 
66
- When the current model provider is not supported, `pi-sub` clears its subscription line and does not refresh subscription data.
78
+ The tok/s speed line is shown for **all** providers including ones without a
79
+ subscription adapter, such as `ollama` and other OpenAI-compatible local
80
+ providers. For those, the footer shows only the speed:
81
+
82
+ ```text
83
+ 145 tok/s
84
+ ```
85
+
86
+ and `/sub` reports the provider/model and speed instead of usage windows.
87
+ `pi-sub` still does not refresh subscription data for unsupported providers
88
+ (there is nothing to fetch).
67
89
 
68
90
  ## Commands
69
91
 
@@ -98,6 +120,20 @@ Last response: 42 tok/s · Session avg: 39 tok/s
98
120
  OpenCode Go does not expose usage windows.
99
121
  ```
100
122
 
123
+ For Command Code, `/sub` shows the provider/model, active account/key label, rolling windows, and speed:
124
+
125
+ ```text
126
+ Provider: Command Code · Model: deepseek/deepseek-v4-flash · Fetched: 14:23
127
+ Session cost: $0.05
128
+ Last response: 42 tok/s · Session avg: 39 tok/s
129
+
130
+ ACCOUNT ROLLING WEEKLY LAST ACTIVITY
131
+ -----------------------------------------------------------------
132
+ * Command Code key#1a2b3c4d 99%/4H 99%/6D Now
133
+
134
+ Monthly: $69.99 remaining
135
+ ```
136
+
101
137
  For Z.ai, `/sub` shows the rolling and weekly quota windows and speed:
102
138
 
103
139
  ```text
@@ -130,6 +166,7 @@ Refreshes are cached briefly to avoid excessive usage endpoint calls.
130
166
 
131
167
  - **OpenAI Codex**: Pi auth must contain an `openai-codex` OAuth entry in `~/.pi/agent/auth.json` or `$PI_CODING_AGENT_DIR/auth.json`. The entry must include `access` and `accountId` fields.
132
168
  - **OpenCode Go**: Pi auth must contain an `opencode-go` API key entry (via `/login` or env var). The entry must have a `key` field or an `accountId` field. OpenCode Go/Zen usage windows and Zen balance are not shown because no public API is currently documented for those values.
169
+ - **Command Code**: Pi auth must contain a `commandcode` API key entry (via `/login commandcode` or the `COMMAND_CODE_API_KEY` env var). The entry must have a `key` field or an `accountId` field. Usage is read from `https://api.commandcode.ai/alpha/billing/credits` with the same key; 5-hour and weekly windows plus the monthly credit balance are displayed.
133
170
  - **Z.ai**: Pi auth must contain a `zai` entry in `auth.json` with a `key` field (the same API key used for Z.ai model access via `@czottmann/pi-zai-api`). The Z.ai provider must be registered (e.g., `pi install npm:@czottmann/pi-zai-api`).
134
171
  - **Z.ai Coding Plan (China)**: The built-in `zai-coding-cn` provider targets `https://open.bigmodel.cn/api/coding/paas/v4`. Pi auth must contain a `zai-coding-cn` entry with a `key` field (set via `/login` or the `ZAI_CODING_CN_API_KEY` env var). Quota is read from the BigModel endpoint `https://open.bigmodel.cn/api/monitor/usage/quota/limit`.
135
172
  - For API-key-only providers, account labels come from stored auth metadata (`email`, `label`, `name`, or `accountId`) when available; otherwise `pi-sub` displays a non-secret SHA-256 key fingerprint such as `Z.ai key#1a2b3c4d`.
@@ -141,4 +178,4 @@ See [CHANGELOG.md](CHANGELOG.md) for release history.
141
178
 
142
179
  ## Design notes
143
180
 
144
- The extension is named `pi-sub` rather than `pi-codex-usage` so future subscription providers can be added as separate adapters. Supports OpenAI Codex (live usage API), OpenCode Go (session cost only), and the Z.ai GLM Coding Plan (international `zai` and China `zai-coding-cn`, which share a quota response format and are served by one parameterized adapter).
181
+ The extension is named `pi-sub` rather than `pi-codex-usage` so future subscription providers can be added as separate adapters. Supports OpenAI Codex (live usage API), OpenCode Go (session cost only), the Z.ai GLM Coding Plan (international `zai` and China `zai-coding-cn`, which share a quota response format and are served by one parameterized adapter), and Command Code (live 5-hour/weekly windows + monthly balance via `/alpha/billing/credits`).
@@ -16,8 +16,13 @@ const ZAI_PROVIDER = "zai";
16
16
  const ZAI_USAGE_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
17
17
  const ZAI_CODING_CN_PROVIDER = "zai-coding-cn";
18
18
  const ZAI_CODING_CN_USAGE_URL = "https://open.bigmodel.cn/api/monitor/usage/quota/limit";
19
- const NINE_ROUTER_PROVIDER = "9router";
20
- const NINE_ROUTER_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "9router-config.json");
19
+ const ROUTER_PROVIDER = "router";
20
+ const LEGACY_9ROUTER_PROVIDER = "9router";
21
+ // pi-router (formerly pi-9router): URL lives in settings.json `router.baseUrl`
22
+ // (env override ROUTER_BASE_URL), key in auth.json `router` credential.
23
+ const ROUTER_SETTINGS_PATH = path.join(os.homedir(), ".pi", "agent", "settings.json");
24
+ const COMMAND_CODE_PROVIDER = "commandcode";
25
+ const COMMAND_CODE_USAGE_URL = "https://api.commandcode.ai/alpha/billing/credits";
21
26
 
22
27
  type ModelLike = { provider?: string; id?: string } | undefined;
23
28
 
@@ -59,6 +64,8 @@ interface SubscriptionAccountSnapshot {
59
64
  plan?: string;
60
65
  fiveHour?: UsageWindow;
61
66
  weekly?: UsageWindow;
67
+ // Command Code-only: monthly credit balance in USD (not a rolling window).
68
+ monthlyCredits?: number;
62
69
  // Z.ai-only extras surfaced in the /sub detail view.
63
70
  mcpMonthly?: UsageWindow; // from TIME_LIMIT already present in the quota response
64
71
  usageBreakdown?: string; // per-model / per-tool summary line(s)
@@ -113,8 +120,12 @@ function isZaiCodingCnModel(model: ModelLike): boolean {
113
120
  return (model?.provider?.toLowerCase() ?? "") === ZAI_CODING_CN_PROVIDER;
114
121
  }
115
122
 
116
- function isNineRouterModel(model: ModelLike): boolean {
117
- return (model?.provider?.toLowerCase() ?? "") === NINE_ROUTER_PROVIDER;
123
+ function isRouterModel(model: ModelLike, provider: string = ROUTER_PROVIDER): boolean {
124
+ return (model?.provider?.toLowerCase() ?? "") === provider;
125
+ }
126
+
127
+ function isCommandCodeModel(model: ModelLike): boolean {
128
+ return (model?.provider?.toLowerCase() ?? "") === COMMAND_CODE_PROVIDER;
118
129
  }
119
130
 
120
131
  function piAuthPath(): string {
@@ -301,16 +312,16 @@ async function readZaiAuth(providerId: string, label: string): Promise<{ key: st
301
312
  return { key: entry.key, account: authAccountSnapshot(label, entry) };
302
313
  }
303
314
 
304
- // ponytail: duplicated from pi-9router — no shared config lib, 8 lines, acceptable
305
- function readNineRouterConfig(): { baseUrl: string; apiKey?: string } | null {
315
+ // ponytail: duplicated from pi-router — no shared config lib, ~12 lines, acceptable
316
+ function readRouterConfig(): { baseUrl: string } | null {
306
317
  try {
307
- const exists = (p: string) => { try { return fs.statSync(p).isFile(); } catch { return false; } };
308
- if (!exists(NINE_ROUTER_CONFIG_PATH)) return null;
309
- const data = JSON.parse(fs.readFileSync(NINE_ROUTER_CONFIG_PATH, "utf8")) as Record<string, unknown>;
310
- const baseUrl = process.env.NINE_ROUTER_BASE_URL || (typeof data.baseUrl === "string" ? data.baseUrl : undefined);
311
- const apiKey = process.env.NINE_ROUTER_API_KEY || (typeof data.apiKey === "string" ? data.apiKey : undefined);
312
- if (!baseUrl || typeof baseUrl !== "string") return null;
313
- return { baseUrl, apiKey };
318
+ let baseUrl = process.env.ROUTER_BASE_URL || process.env.NINE_ROUTER_BASE_URL;
319
+ if (!baseUrl && fs.statSync(ROUTER_SETTINGS_PATH).isFile()) {
320
+ const settings = JSON.parse(fs.readFileSync(ROUTER_SETTINGS_PATH, "utf8")) as Record<string, unknown>;
321
+ const router = settings.router as { baseUrl?: unknown } | undefined;
322
+ if (typeof router?.baseUrl === "string" && router.baseUrl) baseUrl = router.baseUrl;
323
+ }
324
+ return baseUrl ? { baseUrl } : null;
314
325
  } catch {
315
326
  return null;
316
327
  }
@@ -340,6 +351,7 @@ function redactedError(error: unknown, provider = "Codex"): string {
340
351
  if (/missing openai-codex/i.test(message)) return "openai-codex auth not found";
341
352
  if (/missing opencode-go/i.test(message)) return "opencode-go auth not found";
342
353
  if (/missing zai/i.test(message)) return "zai auth not found";
354
+ if (/missing commandcode/i.test(message)) return "commandcode auth not found";
343
355
  if (/timed out|timeout|aborted/i.test(message)) return `${provider} usage refresh timed out`;
344
356
  if (/401|403|auth|token|unauthorized|forbidden/i.test(message)) return `${provider} auth unavailable`;
345
357
  return `${provider} usage unavailable`;
@@ -386,15 +398,15 @@ async function fetchOpenCodeGoUsage(_signal?: AbortSignal): Promise<Subscription
386
398
  }
387
399
  }
388
400
 
389
- async function fetchNineRouterUsage(_signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
390
- const cfg = readNineRouterConfig();
401
+ async function fetchRouterUsage(_signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
402
+ const cfg = readRouterConfig();
391
403
  const now = Date.now();
392
404
  if (!cfg) {
393
405
  return {
394
- providerDisplayName: "9router",
406
+ providerDisplayName: "Router",
395
407
  accounts: [],
396
408
  fetchedAt: now,
397
- error: "9router not configured — run /login-9router",
409
+ error: "router not configured — set router.baseUrl in ~/.pi/agent/settings.json",
398
410
  };
399
411
  }
400
412
  const account: SubscriptionAccountSnapshot = {
@@ -404,13 +416,67 @@ async function fetchNineRouterUsage(_signal?: AbortSignal): Promise<Subscription
404
416
  lastActivity: "Now",
405
417
  };
406
418
  return {
407
- providerDisplayName: "9router",
419
+ providerDisplayName: "Router",
408
420
  accounts: [account],
409
421
  activeAccount: account,
410
422
  fetchedAt: now,
411
423
  };
412
424
  }
413
425
 
426
+ // Command Code's /alpha/billing/credits endpoint (auth: same Provider API key
427
+ // as /provider/v1 models) returns live 5-hour and weekly rolling windows plus
428
+ // the monthly credit balance — the same data as the cmd /usage CLI.
429
+ async function fetchCommandCodeUsage(signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
430
+ try {
431
+ const { key: apiKey, account: authAccount } = await readZaiAuth(COMMAND_CODE_PROVIDER, "Command Code");
432
+ const timeoutSignal = AbortSignal.timeout(7_000);
433
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
434
+
435
+ const headers = {
436
+ Accept: "application/json",
437
+ Authorization: `Bearer ${apiKey}`,
438
+ "User-Agent": "pi-sub/0.1.27",
439
+ };
440
+ const response = await fetch(COMMAND_CODE_USAGE_URL, { headers, signal: combinedSignal });
441
+ if (!response.ok) {
442
+ throw new Error(`Command Code usage request failed with HTTP ${response.status}`);
443
+ }
444
+ const body = (await response.json()) as CommandCodeCreditsApiResponse;
445
+
446
+ const credits = body.credits;
447
+ const windowLimits = body.windowLimits;
448
+ const fiveHour = commandCodeWindowToUsageWindow(windowLimits?.fiveHour);
449
+ const weekly = commandCodeWindowToUsageWindow(windowLimits?.weekly);
450
+
451
+ // Monthly allowance is an absolute USD balance, not a rolling window.
452
+ const monthlyCredits = typeof credits?.monthlyCredits === "number" ? credits.monthlyCredits : undefined;
453
+ const monthlyLine = monthlyCredits !== undefined ? `Monthly: $${monthlyCredits.toFixed(2)} remaining` : undefined;
454
+ const breakdown = [monthlyLine].filter((s): s is string => !!s);
455
+
456
+ const account: SubscriptionAccountSnapshot = {
457
+ ...authAccount,
458
+ fiveHour,
459
+ weekly,
460
+ monthlyCredits,
461
+ usageBreakdown: breakdown.length > 0 ? breakdown.join("\n") : undefined,
462
+ };
463
+
464
+ return {
465
+ providerDisplayName: "Command Code",
466
+ accounts: [account],
467
+ activeAccount: account,
468
+ fetchedAt: Date.now(),
469
+ };
470
+ } catch (error) {
471
+ return {
472
+ providerDisplayName: "Command Code",
473
+ accounts: [],
474
+ fetchedAt: Date.now(),
475
+ error: redactedError(error, "Command Code"),
476
+ };
477
+ }
478
+ }
479
+
414
480
  // ---------------------------------------------------------------------------
415
481
  // Z.ai adapter
416
482
  // ---------------------------------------------------------------------------
@@ -438,6 +504,48 @@ interface ZaiUsageApiError {
438
504
  success?: boolean;
439
505
  }
440
506
 
507
+ // ---------------------------------------------------------------------------
508
+ // Command Code adapter
509
+ // ---------------------------------------------------------------------------
510
+ // Live-verified 2026-08-09: GET https://api.commandcode.ai/alpha/billing/credits
511
+ // with the Provider API key (same user_... key as /provider/v1 models) returns
512
+ // USD windows + monthly credit balance. resetAt is epoch milliseconds.
513
+
514
+ interface CommandCodeWindowApi {
515
+ used: number;
516
+ cap: number;
517
+ exceeded?: boolean | null;
518
+ resetAt?: number;
519
+ }
520
+
521
+ interface CommandCodeCreditsApiResponse {
522
+ credits?: {
523
+ monthlyCredits?: number;
524
+ purchasedCredits?: number;
525
+ freeCredits?: number;
526
+ belowThreshold?: boolean;
527
+ };
528
+ windowLimits?: {
529
+ limited?: boolean;
530
+ fiveHour?: CommandCodeWindowApi;
531
+ weekly?: CommandCodeWindowApi;
532
+ };
533
+ }
534
+
535
+ /** Map a Command Code USD window (used/cap in dollars, resetAt in ms) into
536
+ * the shared UsageWindow shape (remaining%, reset labels). */
537
+ function commandCodeWindowToUsageWindow(window: CommandCodeWindowApi | undefined): UsageWindow | undefined {
538
+ if (!window || typeof window.used !== "number" || typeof window.cap !== "number" || window.cap <= 0) return undefined;
539
+ const usedPct = Math.round((window.used / window.cap) * 100);
540
+ const percent = Math.min(100, usedPct);
541
+ const remaining = Math.max(0, 100 - percent);
542
+ // resetAt is epoch ms; format helpers expect seconds.
543
+ const resetAtSec = window.resetAt ? window.resetAt / 1000 : undefined;
544
+ const resetLabel = formatReset(resetAtSec);
545
+ const remainingLabel = formatRemainingTime(resetAtSec);
546
+ return { percent, remaining, remainingLabel, resetLabel };
547
+ }
548
+
441
549
  function zaiLimitToUsageWindow(limit: ZaiLimitEntry): UsageWindow | undefined {
442
550
  if (typeof limit.percentage !== "number") return undefined;
443
551
  const percent = Math.round(limit.percentage);
@@ -599,7 +707,9 @@ function supportedAdapter(model: ModelLike): SubscriptionProviderAdapter | undef
599
707
  if (isOpenCodeGoModel(model)) return { id: OPC_PROVIDER, displayName: "OpenCode Go", fetchUsage: fetchOpenCodeGoUsage };
600
708
  if (isZaiModel(model)) return { id: ZAI_PROVIDER, displayName: "Z.ai", ...zaiUsageAdapter(ZAI_PROVIDER, ZAI_USAGE_URL, "Z.ai") };
601
709
  if (isZaiCodingCnModel(model)) return { id: ZAI_CODING_CN_PROVIDER, displayName: "Z.ai (CN)", ...zaiUsageAdapter(ZAI_CODING_CN_PROVIDER, ZAI_CODING_CN_USAGE_URL, "Z.ai (CN)") };
602
- if (isNineRouterModel(model)) return { id: NINE_ROUTER_PROVIDER, displayName: "9router", fetchUsage: fetchNineRouterUsage };
710
+ if (isRouterModel(model)) return { id: ROUTER_PROVIDER, displayName: "Router", fetchUsage: fetchRouterUsage };
711
+ if (isRouterModel(model, LEGACY_9ROUTER_PROVIDER)) return { id: LEGACY_9ROUTER_PROVIDER, displayName: "9router (legacy)", fetchUsage: fetchRouterUsage };
712
+ if (isCommandCodeModel(model)) return { id: COMMAND_CODE_PROVIDER, displayName: "Command Code", fetchUsage: fetchCommandCodeUsage };
603
713
  return undefined;
604
714
  }
605
715
 
@@ -627,11 +737,12 @@ function windowSegments(account: SubscriptionAccountSnapshot | undefined): strin
627
737
  }
628
738
 
629
739
  function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
740
+ const theme = ctx.ui.theme;
630
741
  if (!state.adapter) {
631
- ctx.ui.setStatus(STATUS_KEY, undefined);
742
+ // Unsupported provider (e.g. Ollama): still show the last response speed.
743
+ ctx.ui.setStatus(STATUS_KEY, state.lastTokPerSec !== undefined ? theme.fg("dim", `${state.lastTokPerSec} tok/s`) : undefined);
632
744
  return;
633
745
  }
634
- const theme = ctx.ui.theme;
635
746
  const snapshot = state.snapshot;
636
747
  let line: string;
637
748
  let color: "dim" | "warning" | "error" = "dim";
@@ -647,6 +758,8 @@ function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
647
758
  const segments = accountPart ? [accountPart, ...windowParts] : [...windowParts];
648
759
  const cost = state.cumulativeCost;
649
760
  const hasWindows = windowParts.length > 0;
761
+ // Command Code monthly balance: compact USD figure, e.g. M:$69.99.
762
+ if (typeof account?.monthlyCredits === "number") segments.push(`M:$${account.monthlyCredits.toFixed(2)}`);
650
763
  if (cost > 0) segments.push(`$${cost.toFixed(2)}`);
651
764
  if (state.lastTokPerSec !== undefined) segments.push(`${state.lastTokPerSec} tok/s`);
652
765
  if (segments.length === 0) {
@@ -736,7 +849,15 @@ function pad(value: string, width: number): string {
736
849
  }
737
850
 
738
851
  function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: State): string {
739
- if (!state.adapter) return `Subscription tracking inactive for current model provider (${state.model?.provider ?? "unknown"}).`;
852
+ if (!state.adapter) {
853
+ const header = `Provider: ${state.model?.provider ?? "unknown"}${state.model?.id ? ` · Model: ${state.model.id}` : ""}`;
854
+ if (state.lastTokPerSec === undefined) return `${header}\nSubscription tracking inactive for this provider.`;
855
+ const tokPerSecLine = `Last response: ${state.lastTokPerSec} tok/s` +
856
+ (state.cumulativeDurationMs > 0
857
+ ? ` · Session avg: ${Math.round(state.cumulativeOutput / (state.cumulativeDurationMs / 1000))} tok/s`
858
+ : "");
859
+ return `${header}\n${tokPerSecLine}`;
860
+ }
740
861
  if (!snapshot) return "Subscription usage has not been loaded yet.";
741
862
  if (snapshot.error) return `${snapshot.providerDisplayName}: ${snapshot.error}`;
742
863
  if (snapshot.accounts.length === 0) {
@@ -820,7 +941,7 @@ export default function (pi: ExtensionAPI) {
820
941
  state.cumulativeDurationMs += elapsed;
821
942
  }
822
943
  }
823
- if (state.adapter) renderSubscriptionLine(ctx, state);
944
+ renderSubscriptionLine(ctx, state);
824
945
  }
825
946
  });
826
947
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-sub",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Pi extension showing subscription usage for supported model providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,6 +39,6 @@
39
39
  "node": ">=20.3.0"
40
40
  },
41
41
  "peerDependencies": {
42
- "@earendil-works/pi-coding-agent": ">=0.80.8 <0.84.0"
42
+ "@earendil-works/pi-coding-agent": ">=0.80.8 <0.85.0"
43
43
  }
44
44
  }