@bacnh85/pi-sub 0.1.9 → 0.1.11

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/README.md CHANGED
@@ -15,13 +15,9 @@ pi install npm:@bacnh85/pi-sub
15
15
  From this repository checkout:
16
16
 
17
17
  ```bash
18
- cd extensions/pi-sub
19
- npm install
20
- cd ../..
21
-
22
- pi install ./extensions/pi-sub
18
+ pi install ./pi-sub
23
19
  # or test directly
24
- pi -e ./extensions/pi-sub
20
+ pi -e ./pi-sub
25
21
  ```
26
22
 
27
23
  ## What it shows
@@ -1,5 +1,4 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { Box, Text } from "@earendil-works/pi-tui";
3
2
  import { createHash } from "node:crypto";
4
3
  import fs from "node:fs/promises";
5
4
  import os from "node:os";
@@ -20,7 +19,6 @@ type ModelLike = { provider?: string; id?: string } | undefined;
20
19
 
21
20
  type UsageApiWindow = {
22
21
  used_percent?: number;
23
- limit_window_seconds?: number;
24
22
  reset_at?: number;
25
23
  };
26
24
 
@@ -46,14 +44,10 @@ type PiAuthEntry = {
46
44
  };
47
45
 
48
46
  interface UsageWindow {
49
- used?: number;
50
- limit?: number;
51
47
  percent?: number;
52
48
  remaining?: number;
53
49
  remainingLabel?: string;
54
50
  resetLabel?: string;
55
- resetsAt?: string | number | Date;
56
- label?: string;
57
51
  }
58
52
 
59
53
  interface SubscriptionAccountSnapshot {
@@ -63,12 +57,10 @@ interface SubscriptionAccountSnapshot {
63
57
  plan?: string;
64
58
  fiveHour?: UsageWindow;
65
59
  weekly?: UsageWindow;
66
- monthly?: UsageWindow;
67
60
  lastActivity?: string;
68
61
  }
69
62
 
70
63
  interface SubscriptionUsageSnapshot {
71
- providerId: string;
72
64
  providerDisplayName: string;
73
65
  accounts: SubscriptionAccountSnapshot[];
74
66
  activeAccount?: SubscriptionAccountSnapshot;
@@ -77,12 +69,11 @@ interface SubscriptionUsageSnapshot {
77
69
  cost?: number;
78
70
  }
79
71
 
80
- interface SubscriptionProviderAdapter {
72
+ type SubscriptionProviderAdapter = {
81
73
  id: string;
82
74
  displayName: string;
83
- isModelSupported(model: ModelLike): boolean;
84
75
  fetchUsage(signal?: AbortSignal): Promise<SubscriptionUsageSnapshot>;
85
- }
76
+ };
86
77
 
87
78
  interface State {
88
79
  model?: ModelLike;
@@ -246,8 +237,6 @@ function usageWindowFromApi(window: UsageApiWindow | undefined): UsageWindow | u
246
237
  remaining,
247
238
  remainingLabel,
248
239
  resetLabel,
249
- resetsAt: window.reset_at,
250
- label: remainingLabel ? `${remaining}% (${remainingLabel})` : `${remaining}%`,
251
240
  };
252
241
  }
253
242
 
@@ -270,7 +259,6 @@ function parseUsageResponse(body: unknown): UsageApiSnapshot | undefined {
270
259
  if (!window || typeof window !== "object" || typeof window.used_percent !== "number") return undefined;
271
260
  return {
272
261
  used_percent: window.used_percent,
273
- limit_window_seconds: typeof window.limit_window_seconds === "number" ? window.limit_window_seconds : undefined,
274
262
  reset_at: typeof window.reset_at === "number" ? window.reset_at : undefined,
275
263
  };
276
264
  };
@@ -289,13 +277,11 @@ async function readPiCodexAuth(): Promise<PiAuthEntry & { accountId: string }> {
289
277
  return { ...entry, accountId };
290
278
  }
291
279
 
292
- async function readOpenCodeGoAuth(): Promise<{ key?: string; accountId?: string; account: SubscriptionAccountSnapshot }> {
280
+ async function readOpenCodeGoAuth(): Promise<SubscriptionAccountSnapshot> {
293
281
  const auth = await readJsonFile<PiAuthFile>(piAuthPath());
294
282
  const entry = auth[OPC_PROVIDER];
295
283
  if (!entry?.key && !entry?.accountId) throw new Error("Missing opencode-go API key or accountId in Pi auth");
296
- const account = authAccountSnapshot("OpenCode Go", entry, { plan: "Go" });
297
- if (typeof entry.key === "string") return { key: entry.key, account };
298
- return { accountId: typeof entry.accountId === "string" ? entry.accountId : undefined, account };
284
+ return authAccountSnapshot("OpenCode Go", entry, { plan: "Go" });
299
285
  }
300
286
 
301
287
  async function readZaiAuth(): Promise<{ key: string; account: SubscriptionAccountSnapshot }> {
@@ -341,7 +327,6 @@ async function fetchCodexUsage(signal?: AbortSignal): Promise<SubscriptionUsageS
341
327
  const usage = await fetchUsageFromPiAuth(entry, signal);
342
328
  activeAccount = mergeUsageIntoAccount(activeAccount, usage);
343
329
  return {
344
- providerId: CODEX_PROVIDER,
345
330
  providerDisplayName: "Codex",
346
331
  accounts: [activeAccount],
347
332
  activeAccount,
@@ -349,7 +334,6 @@ async function fetchCodexUsage(signal?: AbortSignal): Promise<SubscriptionUsageS
349
334
  };
350
335
  } catch (error) {
351
336
  return {
352
- providerId: CODEX_PROVIDER,
353
337
  providerDisplayName: "Codex",
354
338
  accounts: [],
355
339
  fetchedAt: Date.now(),
@@ -360,9 +344,8 @@ async function fetchCodexUsage(signal?: AbortSignal): Promise<SubscriptionUsageS
360
344
 
361
345
  async function fetchOpenCodeGoUsage(_signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
362
346
  try {
363
- const { account } = await readOpenCodeGoAuth();
347
+ const account = await readOpenCodeGoAuth();
364
348
  return {
365
- providerId: OPC_PROVIDER,
366
349
  providerDisplayName: "OpenCode Go",
367
350
  accounts: [account],
368
351
  activeAccount: account,
@@ -370,7 +353,6 @@ async function fetchOpenCodeGoUsage(_signal?: AbortSignal): Promise<Subscription
370
353
  };
371
354
  } catch (error) {
372
355
  return {
373
- providerId: OPC_PROVIDER,
374
356
  providerDisplayName: "OpenCode Go",
375
357
  accounts: [],
376
358
  fetchedAt: Date.now(),
@@ -418,8 +400,6 @@ function zaiLimitToUsageWindow(limit: ZaiLimitEntry): UsageWindow | undefined {
418
400
  remaining,
419
401
  remainingLabel,
420
402
  resetLabel,
421
- resetsAt: limit.nextResetTime,
422
- label: remainingLabel ? `${remaining}% (${remainingLabel})` : `${remaining}%`,
423
403
  };
424
404
  }
425
405
 
@@ -473,7 +453,6 @@ async function fetchZaiUsage(signal?: AbortSignal): Promise<SubscriptionUsageSna
473
453
  };
474
454
 
475
455
  return {
476
- providerId: ZAI_PROVIDER,
477
456
  providerDisplayName: "Z.ai",
478
457
  accounts: [account],
479
458
  activeAccount: account,
@@ -481,7 +460,6 @@ async function fetchZaiUsage(signal?: AbortSignal): Promise<SubscriptionUsageSna
481
460
  };
482
461
  } catch (error) {
483
462
  return {
484
- providerId: ZAI_PROVIDER,
485
463
  providerDisplayName: "Z.ai",
486
464
  accounts: [],
487
465
  fetchedAt: Date.now(),
@@ -490,31 +468,11 @@ async function fetchZaiUsage(signal?: AbortSignal): Promise<SubscriptionUsageSna
490
468
  }
491
469
  }
492
470
 
493
- const codexAdapter: SubscriptionProviderAdapter = {
494
- id: CODEX_PROVIDER,
495
- displayName: "Codex",
496
- isModelSupported: isCodexModel,
497
- fetchUsage: fetchCodexUsage,
498
- };
499
-
500
- const openCodeGoAdapter: SubscriptionProviderAdapter = {
501
- id: OPC_PROVIDER,
502
- displayName: "OpenCode Go",
503
- isModelSupported: isOpenCodeGoModel,
504
- fetchUsage: fetchOpenCodeGoUsage,
505
- };
506
-
507
- const zaiAdapter: SubscriptionProviderAdapter = {
508
- id: ZAI_PROVIDER,
509
- displayName: "Z.ai",
510
- isModelSupported: isZaiModel,
511
- fetchUsage: fetchZaiUsage,
512
- };
513
-
514
- const adapters = [codexAdapter, openCodeGoAdapter, zaiAdapter];
515
-
516
471
  function supportedAdapter(model: ModelLike): SubscriptionProviderAdapter | undefined {
517
- return adapters.find((adapter) => adapter.isModelSupported(model));
472
+ if (isCodexModel(model)) return { id: CODEX_PROVIDER, displayName: "Codex", fetchUsage: fetchCodexUsage };
473
+ if (isOpenCodeGoModel(model)) return { id: OPC_PROVIDER, displayName: "OpenCode Go", fetchUsage: fetchOpenCodeGoUsage };
474
+ if (isZaiModel(model)) return { id: ZAI_PROVIDER, displayName: "Z.ai", fetchUsage: fetchZaiUsage };
475
+ return undefined;
518
476
  }
519
477
 
520
478
  function formatRemaining(window: UsageWindow | undefined): string {
@@ -525,20 +483,10 @@ function formatRemaining(window: UsageWindow | undefined): string {
525
483
  return "?";
526
484
  }
527
485
 
528
- function formatWindow(window: UsageWindow | undefined, _compact = true): string {
529
- if (!window) return "?";
530
- if (window.percent !== undefined && window.resetLabel) return `${window.percent}% (${window.resetLabel})`;
531
- if (window.label) return window.label;
532
- if (window.percent !== undefined) return `${window.percent}%`;
533
- if (window.used !== undefined && window.limit !== undefined) return `${window.used}/${window.limit}`;
534
- return "?";
535
- }
536
-
537
486
  function minRemaining(account: SubscriptionAccountSnapshot | undefined): number {
538
487
  const values: number[] = [];
539
488
  if (account?.fiveHour?.remaining !== undefined) values.push(account.fiveHour.remaining);
540
489
  if (account?.weekly?.remaining !== undefined) values.push(account.weekly.remaining);
541
- if (account?.monthly?.remaining !== undefined) values.push(account.monthly.remaining);
542
490
  if (values.length === 0) return 100;
543
491
  return Math.min(...values);
544
492
  }
@@ -548,7 +496,6 @@ function windowSegments(account: SubscriptionAccountSnapshot | undefined): strin
548
496
  const segments: string[] = [];
549
497
  if (account.fiveHour) segments.push(`R:${formatRemaining(account.fiveHour)}`);
550
498
  if (account.weekly) segments.push(`W:${formatRemaining(account.weekly)}`);
551
- if (account.monthly) segments.push(`M:${formatRemaining(account.monthly)}`);
552
499
  return segments;
553
500
  }
554
501
 
@@ -688,12 +635,8 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
688
635
 
689
636
  const hasFiveHour = snapshot.accounts.some((a) => a.fiveHour);
690
637
  const hasWeekly = snapshot.accounts.some((a) => a.weekly);
691
- const hasMonthly = snapshot.accounts.some((a) => a.monthly);
692
-
693
638
  if (hasFiveHour) columns.push({ key: "five", label: "ROLLING", get: (a) => formatRemaining(a.fiveHour) });
694
639
  if (hasWeekly) columns.push({ key: "weekly", label: "WEEKLY", get: (a) => formatRemaining(a.weekly) });
695
- if (hasMonthly) columns.push({ key: "monthly", label: "MONTHLY", get: (a) => formatRemaining(a.monthly) });
696
-
697
640
  const rows = snapshot.accounts.map((account) => ({
698
641
  active: account.isActive ? "*" : " ",
699
642
  snapshot: account,
@@ -714,7 +657,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
714
657
 
715
658
  const costLine = snapshot.cost !== undefined ? `\nSession cost: $${snapshot.cost.toFixed(2)}` : "";
716
659
  const lines = [`Provider: ${snapshot.providerDisplayName} · Model: ${state.model?.id ?? "unknown-model"} · Fetched: ${new Date(snapshot.fetchedAt).toLocaleTimeString()}${costLine}`, "", header, sep, ...body];
717
- if (!hasFiveHour && !hasWeekly && !hasMonthly) {
660
+ if (!hasFiveHour && !hasWeekly) {
718
661
  lines.push("", `${snapshot.providerDisplayName} does not expose usage windows.`);
719
662
  }
720
663
  return lines.join("\n");
@@ -723,12 +666,6 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
723
666
  export default function (pi: ExtensionAPI) {
724
667
  const state: State = { lastRefreshAt: 0, refreshGeneration: 0 };
725
668
 
726
- pi.registerMessageRenderer(MESSAGE_TYPE, (message, _options, theme) => {
727
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
728
- box.addChild(new Text(String(message.content ?? ""), 0, 0));
729
- return box;
730
- });
731
-
732
669
  pi.on("session_start", async (_event, ctx) => {
733
670
  updateActiveAdapter(ctx, state, ctx.model);
734
671
  if (state.adapter) void refreshUsage(ctx, state, true);
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-sub",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Pi extension showing subscription usage for supported model providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
- "homepage": "https://github.com/bacnh85/skills#readme",
10
+ "homepage": "https://github.com/bacnh85/pi-extensions#readme",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/bacnh85/skills.git",
14
- "directory": "extensions/pi-sub"
13
+ "url": "git+https://github.com/bacnh85/pi-extensions.git",
14
+ "directory": "pi-sub"
15
15
  },
16
16
  "bugs": {
17
- "url": "https://github.com/bacnh85/skills/issues"
17
+ "url": "https://github.com/bacnh85/pi-extensions/issues"
18
18
  },
19
19
  "keywords": [
20
20
  "pi-package",
@@ -26,18 +26,18 @@
26
26
  ],
27
27
  "files": [
28
28
  "README.md",
29
- "index.ts"
29
+ "extensions/index.ts",
30
+ "extensions/package.json"
30
31
  ],
31
32
  "pi": {
32
33
  "extensions": [
33
- "./index.ts"
34
+ "./extensions/index.ts"
34
35
  ]
35
36
  },
36
37
  "engines": {
37
38
  "node": ">=20.3.0"
38
39
  },
39
40
  "peerDependencies": {
40
- "@earendil-works/pi-coding-agent": "*",
41
- "@earendil-works/pi-tui": "*"
41
+ "@earendil-works/pi-coding-agent": "*"
42
42
  }
43
43
  }