@ogulcancelik/pi-minimal-footer 0.1.5 → 0.1.7

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.
Files changed (3) hide show
  1. package/README.md +23 -9
  2. package/index.ts +99 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,21 +8,22 @@ Minimal footer for [pi](https://github.com/badlogic/pi-mono) that replaces the d
8
8
 
9
9
  ## Features
10
10
 
11
- - **Context gauge** — working directory, git branch, model, thinking level, and context window usage with token counts
11
+ - **Context gauge** — optional working directory and git branch, model, thinking level, and context window usage with token counts
12
12
  - **Subscription usage bars** — rolling window quotas with reset timers for supported providers
13
13
  - **Auto-refresh** — fetches usage on startup and model switch, then every 5 minutes
14
14
  - **Git integration** — branch name, dirty state, ahead/behind counts
15
15
 
16
16
  ## Supported providers
17
17
 
18
- | Provider | What it shows |
19
- |----------|---------------|
20
- | Claude Max | 5h + weekly rolling windows |
21
- | OpenAI Codex | Primary + secondary rolling windows |
22
- | GitHub Copilot | Premium interactions + chat quotas |
23
- | Google Gemini | Pro + Flash remaining quotas |
24
- | MiniMax | 5h + weekly rolling windows (Token Plan / Coding Plan) |
25
- | MiniMax CN | Same as MiniMax, China endpoint |
18
+ | Provider | What it shows |
19
+ | -------------- | ------------------------------------------------------ |
20
+ | Claude Max | 5h + weekly rolling windows |
21
+ | OpenAI Codex | Primary + secondary rolling windows |
22
+ | GitHub Copilot | Premium interactions + chat quotas |
23
+ | Google Gemini | Pro + Flash remaining quotas |
24
+ | MiniMax | 5h + weekly rolling windows (Token Plan / Coding Plan) |
25
+ | MiniMax CN | Same as MiniMax, China endpoint |
26
+ | Kimi Coding | 5h + weekly rolling windows (Plan) |
26
27
 
27
28
  ## Install
28
29
 
@@ -30,16 +31,29 @@ Minimal footer for [pi](https://github.com/badlogic/pi-mono) that replaces the d
30
31
  pi install npm:@ogulcancelik/pi-minimal-footer
31
32
  ```
32
33
 
34
+ ## Configuration
35
+
36
+ Environment variables (all optional):
37
+
38
+ | Variable | Description | Default |
39
+ | ------------------------------- | -------------------------------------------------------- | ------- |
40
+ | `PI_MINIMAL_FOOTER_SHOW_CWD` | Show current working directory in footer status line | `1` |
41
+ | `PI_MINIMAL_FOOTER_SHOW_BRANCH` | Show git branch/dirty/ahead/behind in footer status line | `1` |
42
+
43
+ Accepted false values: `0`, `false`, `no`, `off` (case-insensitive).
44
+
33
45
  ## How it works
34
46
 
35
47
  The footer reads context usage from the last assistant message's token counts (free — comes with every LLM response). Subscription usage is fetched from each provider's dedicated quota API using your existing auth tokens from `~/.pi/agent/auth.json` or environment variables.
36
48
 
37
49
  Usage is fetched:
50
+
38
51
  - Once on startup
39
52
  - Immediately on model switch (Ctrl+P)
40
53
  - Every 5 minutes after that
41
54
 
42
55
  Git state is refreshed:
56
+
43
57
  - Once on startup
44
58
  - When pi reports a branch change
45
59
  - At the end of each turn
package/index.ts CHANGED
@@ -42,6 +42,19 @@ interface GitCache {
42
42
  const USAGE_REFRESH_INTERVAL = 5 * 60_000; // 5 minutes
43
43
  const usageCache = new Map<string, UsageSnapshot>(); // keyed by provider
44
44
 
45
+ // ============ Env Flags ============
46
+
47
+ function parseBooleanEnv(value: string | undefined, fallback: boolean): boolean {
48
+ if (value === undefined) return fallback;
49
+
50
+ const normalized = value.trim().toLowerCase();
51
+ if (!normalized) return fallback;
52
+
53
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
54
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
55
+ return fallback;
56
+ }
57
+
45
58
  // ============ Identity Cache ============
46
59
 
47
60
  let codexEmailPrefix: string | null | undefined; // undefined = not yet resolved
@@ -250,6 +263,10 @@ function getMinimaxToken(provider: "minimax" | "minimax-cn"): string | undefined
250
263
  : getApiKey("minimax-cn", "MINIMAX_CN_API_KEY");
251
264
  }
252
265
 
266
+ function getKimiToken(): string | undefined {
267
+ return getApiKey("kimi-coding", "KIMI_API_KEY");
268
+ }
269
+
253
270
  // ============ Time Formatting ============
254
271
 
255
272
  function formatResetTime(date: Date): string {
@@ -614,6 +631,69 @@ async function fetchMinimaxUsage(provider: "minimax" | "minimax-cn"): Promise<Us
614
631
  }
615
632
  }
616
633
 
634
+ async function fetchKimiUsage(): Promise<UsageSnapshot> {
635
+ const token = getKimiToken();
636
+ const endpoint = "https://api.kimi.com/coding/v1/usages";
637
+ if (!token) {
638
+ return { provider: "Kimi Coding", windows: [], error: "no-auth", fetchedAt: Date.now() };
639
+ }
640
+
641
+ try {
642
+ const res = await fetchWithTimeout(endpoint, {
643
+ method: "GET",
644
+ headers: {
645
+ Authorization: `Bearer ${token}`,
646
+ "Content-Type": "application/json",
647
+ },
648
+ });
649
+
650
+ if (!res.ok) {
651
+ return { provider: "Kimi Coding", windows: [], error: `HTTP ${res.status}`, fetchedAt: Date.now() };
652
+ }
653
+
654
+ const data = (await res.json()) as any;
655
+ const windows: RateWindow[] = [];
656
+
657
+ for (const limit of data.limits || []) {
658
+ const windowLimit = Number(limit.detail?.limit) || 0;
659
+ const windowRemaining = Number(limit.detail?.remaining) || 0;
660
+ if (windowLimit > 0) {
661
+ const used = windowLimit - windowRemaining;
662
+ const usedPercent = clampPercent((used / windowLimit) * 100);
663
+ const resetDate = limit.detail?.resetTime ? new Date(limit.detail.resetTime) : undefined;
664
+ const durationMs =
665
+ limit.window?.duration && limit.window?.timeUnit === "TIME_UNIT_MINUTE"
666
+ ? limit.window.duration * 60 * 1000
667
+ : undefined;
668
+
669
+ windows.push({
670
+ label: getWindowLabel(durationMs, "5h"),
671
+ usedPercent,
672
+ resetsIn: resetDate ? formatResetTime(resetDate) : undefined,
673
+ });
674
+ }
675
+ }
676
+
677
+ const weeklyLimit = Number(data.usage?.limit) || 0;
678
+ const weeklyRemaining = Number(data.usage?.remaining) || 0;
679
+ const weeklyResetTime = data.usage?.resetTime;
680
+
681
+ if (weeklyLimit > 0) {
682
+ const used = weeklyLimit - weeklyRemaining;
683
+ const usedPercent = clampPercent((used / weeklyLimit) * 100);
684
+ windows.push({
685
+ label: "Weekly",
686
+ usedPercent,
687
+ resetsIn: weeklyResetTime ? formatResetTime(new Date(weeklyResetTime)) : undefined,
688
+ });
689
+ }
690
+
691
+ return { provider: "Kimi Coding", windows, fetchedAt: Date.now() };
692
+ } catch (e) {
693
+ return { provider: "Kimi Coding", windows: [], error: String(e), fetchedAt: Date.now() };
694
+ }
695
+ }
696
+
617
697
  // ============ Provider Detection ============
618
698
 
619
699
  // Map pi provider names to our internal usage provider keys
@@ -624,6 +704,7 @@ const PROVIDER_MAP: Record<string, string> = {
624
704
  "google-gemini-cli": "gemini", // Gemini CLI subscription
625
705
  minimax: "minimax", // MiniMax Token Plan / Coding Plan
626
706
  "minimax-cn": "minimax-cn", // MiniMax China plan
707
+ "kimi-coding": "kimi-coding", // Kimi plan
627
708
  };
628
709
 
629
710
  function detectProvider(modelProvider: string): string | null {
@@ -644,6 +725,8 @@ async function fetchUsageForProvider(provider: string): Promise<UsageSnapshot> {
644
725
  return fetchMinimaxUsage("minimax");
645
726
  case "minimax-cn":
646
727
  return fetchMinimaxUsage("minimax-cn");
728
+ case "kimi-coding":
729
+ return fetchKimiUsage();
647
730
  default:
648
731
  return { provider: "Unknown", windows: [], error: "unknown-provider", fetchedAt: Date.now() };
649
732
  }
@@ -658,6 +741,10 @@ export default function (pi: ExtensionAPI) {
658
741
  const BAR_FILLED = "━";
659
742
  const BAR_EMPTY = "─";
660
743
 
744
+ // Optional visibility toggles (default: enabled)
745
+ const showCwd = parseBooleanEnv(process.env.PI_MINIMAL_FOOTER_SHOW_CWD, true);
746
+ const showBranch = parseBooleanEnv(process.env.PI_MINIMAL_FOOTER_SHOW_BRANCH, true);
747
+
661
748
  function formatTokenCount(tokens: number): string {
662
749
  if (tokens >= 1_000_000) {
663
750
  const m = tokens / 1_000_000;
@@ -814,7 +901,7 @@ export default function (pi: ExtensionAPI) {
814
901
  let latestUsage: UsageSnapshot | null = null;
815
902
  let activeProvider: string | null = null; // internal provider key for the current model
816
903
  let refreshTimer: ReturnType<typeof setInterval> | null = null;
817
-
904
+
818
905
  // Store tui reference for triggering re-renders from event handlers
819
906
  let tuiRef: { requestRender: () => void } | null = null;
820
907
 
@@ -890,7 +977,7 @@ export default function (pi: ExtensionAPI) {
890
977
 
891
978
  ctx.ui.setFooter((tui: any, theme: any, footerData: any) => {
892
979
  tuiRef = tui;
893
-
980
+
894
981
  const unsub = footerData.onBranchChange(() => {
895
982
  refreshGitFooter();
896
983
  });
@@ -920,7 +1007,7 @@ export default function (pi: ExtensionAPI) {
920
1007
  }
921
1008
 
922
1009
  let branchStr = "";
923
- if (gitCache?.branch) {
1010
+ if (showBranch && gitCache?.branch) {
924
1011
  const branchColor = gitCache.dirty ? "warning" : "success";
925
1012
  branchStr = theme.fg(branchColor, gitCache.branch);
926
1013
  if (gitCache.dirty) branchStr += theme.fg("warning", " *");
@@ -941,9 +1028,16 @@ export default function (pi: ExtensionAPI) {
941
1028
 
942
1029
  const sep = " " + theme.fg("dim", ">") + " ";
943
1030
  const lines: string[] = [];
944
- const pwdStr = theme.fg("accent", pwd);
1031
+
1032
+ const pwdStr = showCwd ? theme.fg("accent", pwd) : "";
1033
+ const locationVariants: string[] = [];
1034
+ if (pwdStr && branchStr) locationVariants.push(pwdStr + sep + branchStr);
1035
+ if (pwdStr) locationVariants.push(pwdStr);
1036
+ if (branchStr) locationVariants.push(branchStr);
1037
+ const locationBlock = locationVariants.length > 0 ? fitFooterSegment(width, locationVariants) : "";
1038
+
945
1039
  const statusBlocks = [
946
- fitFooterSegment(width, branchStr ? [pwdStr + sep + branchStr, pwdStr] : [pwdStr]),
1040
+ locationBlock,
947
1041
  fitFooterSegment(width, modelStr === plainModelStr ? [plainModelStr] : [modelStr, plainModelStr]),
948
1042
  fitFooterSegment(width, [
949
1043
  renderContextGauge(percentage, theme, ctxUsed, ctxTotal, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogulcancelik/pi-minimal-footer",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Minimal footer for pi with context gauge and subscription usage bars",
5
5
  "type": "module",
6
6
  "keywords": [