@bacnh85/pi-sub 0.1.20 → 0.1.22

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 ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to `pi-sub` will be documented in this file.
4
+
5
+ ## 0.1.22 (2026-07-30)
6
+
7
+ ### Improvements
8
+
9
+ - Patch version bump for release sync and package documentation update.
10
+
11
+ ## 0.1.21 (2026-07-24)
12
+
13
+ ### Features
14
+
15
+ - Added 9router adapter for tokens per second (tok/s) and endpoint display.
16
+ - Cleared stale context on session shutdown to prevent crashes on `/new`.
17
+
18
+ ## 0.1.15 (2026-07-16)
19
+
20
+ ### Features
21
+
22
+ - Added `zai-coding-cn` (Z.ai China / open.bigmodel.cn) usage monitoring.
23
+ - Surfaced Z.ai MCP/month + per-model usage in `/sub` detail view.
24
+
25
+ ## 0.1.10 (2026-07-10)
26
+
27
+ ### Features
28
+
29
+ - Added tok/s (tokens per second) display for response speed tracking.
30
+
31
+ ## 0.1.0 (2026-07-05)
32
+
33
+ ### Features
34
+
35
+ - Initial release of `pi-sub` subscription usage footer extension.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # pi-sub
1
+ # @bacnh85/pi-sub
2
2
 
3
3
  Pi extension that shows subscription usage for the currently selected supported model provider.
4
4
 
@@ -6,20 +6,10 @@ Supports OpenAI Codex (`openai-codex`) with live usage windows from ChatGPT's us
6
6
 
7
7
  ## Install
8
8
 
9
- From npm after the package is published:
10
-
11
9
  ```bash
12
10
  pi install npm:@bacnh85/pi-sub
13
11
  ```
14
12
 
15
- From this repository checkout:
16
-
17
- ```bash
18
- pi install ./pi-sub
19
- # or test directly
20
- pi -e ./pi-sub
21
- ```
22
-
23
13
  ## What it shows
24
14
 
25
15
  ### OpenAI Codex
@@ -61,6 +51,14 @@ The built-in `zai-coding-cn` provider targets the domestic BigModel endpoint (`o
61
51
  (Z.ai (CN) key#1a2b3c4d) R:55%/2H W:80%/3D 42 tok/s
62
52
  ```
63
53
 
54
+ ### 9router
55
+
56
+ 9router is a proxy/router with no upstream usage API, so the footer shows the configured endpoint and last response speed:
57
+
58
+ ```text
59
+ 9router (172.30.55.22:20128) 145 tok/s
60
+ ```
61
+
64
62
  ### Tokens per second
65
63
 
66
64
  `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.
@@ -137,6 +135,10 @@ Refreshes are cached briefly to avoid excessive usage endpoint calls.
137
135
  - 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`.
138
136
  - `pi-sub` redacts auth/token-related errors and never prints credentials.
139
137
 
138
+ ## Changelog
139
+
140
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
141
+
140
142
  ## Design notes
141
143
 
142
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).
@@ -1,5 +1,6 @@
1
1
  import { readStoredCredential, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { createHash } from "node:crypto";
3
+ import fs from "node:fs";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
6
 
@@ -15,6 +16,8 @@ const ZAI_PROVIDER = "zai";
15
16
  const ZAI_USAGE_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
16
17
  const ZAI_CODING_CN_PROVIDER = "zai-coding-cn";
17
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");
18
21
 
19
22
  type ModelLike = { provider?: string; id?: string } | undefined;
20
23
 
@@ -110,6 +113,10 @@ function isZaiCodingCnModel(model: ModelLike): boolean {
110
113
  return (model?.provider?.toLowerCase() ?? "") === ZAI_CODING_CN_PROVIDER;
111
114
  }
112
115
 
116
+ function isNineRouterModel(model: ModelLike): boolean {
117
+ return (model?.provider?.toLowerCase() ?? "") === NINE_ROUTER_PROVIDER;
118
+ }
119
+
113
120
  function piAuthPath(): string {
114
121
  const configDir = process.env.PI_CODING_AGENT_DIR?.trim() || path.join(os.homedir(), ".pi", "agent");
115
122
  return path.join(configDir, "auth.json");
@@ -294,6 +301,21 @@ async function readZaiAuth(providerId: string, label: string): Promise<{ key: st
294
301
  return { key: entry.key, account: authAccountSnapshot(label, entry) };
295
302
  }
296
303
 
304
+ // ponytail: duplicated from pi-9router — no shared config lib, 8 lines, acceptable
305
+ function readNineRouterConfig(): { baseUrl: string; apiKey?: string } | null {
306
+ 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 };
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
318
+
297
319
  async function fetchUsageFromPiAuth(entry: PiAuthEntry, signal?: AbortSignal): Promise<UsageApiSnapshot | undefined> {
298
320
  const accountId = getCodexAccountId(entry) ?? entry.accountId;
299
321
  if (!accountId) throw new Error("Missing openai-codex OAuth entry in Pi auth");
@@ -364,6 +386,31 @@ async function fetchOpenCodeGoUsage(_signal?: AbortSignal): Promise<Subscription
364
386
  }
365
387
  }
366
388
 
389
+ async function fetchNineRouterUsage(_signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
390
+ const cfg = readNineRouterConfig();
391
+ const now = Date.now();
392
+ if (!cfg) {
393
+ return {
394
+ providerDisplayName: "9router",
395
+ accounts: [],
396
+ fetchedAt: now,
397
+ error: "9router not configured — run /login-9router",
398
+ };
399
+ }
400
+ const account: SubscriptionAccountSnapshot = {
401
+ id: cfg.baseUrl,
402
+ isActive: true,
403
+ accountLabel: cfg.baseUrl.replace(/^https?:\/\//, ""),
404
+ lastActivity: "Now",
405
+ };
406
+ return {
407
+ providerDisplayName: "9router",
408
+ accounts: [account],
409
+ activeAccount: account,
410
+ fetchedAt: now,
411
+ };
412
+ }
413
+
367
414
  // ---------------------------------------------------------------------------
368
415
  // Z.ai adapter
369
416
  // ---------------------------------------------------------------------------
@@ -552,6 +599,7 @@ function supportedAdapter(model: ModelLike): SubscriptionProviderAdapter | undef
552
599
  if (isOpenCodeGoModel(model)) return { id: OPC_PROVIDER, displayName: "OpenCode Go", fetchUsage: fetchOpenCodeGoUsage };
553
600
  if (isZaiModel(model)) return { id: ZAI_PROVIDER, displayName: "Z.ai", ...zaiUsageAdapter(ZAI_PROVIDER, ZAI_USAGE_URL, "Z.ai") };
554
601
  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 };
555
603
  return undefined;
556
604
  }
557
605
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-sub",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Pi extension showing subscription usage for supported model providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,6 +26,7 @@
26
26
  ],
27
27
  "files": [
28
28
  "README.md",
29
+ "CHANGELOG.md",
29
30
  "extensions/index.ts",
30
31
  "extensions/package.json"
31
32
  ],