@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.
- package/README.md +23 -9
- package/index.ts +99 -5
- 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
|
|
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
|
|
19
|
-
|
|
20
|
-
| Claude Max
|
|
21
|
-
| OpenAI Codex
|
|
22
|
-
| GitHub Copilot | Premium interactions + chat quotas
|
|
23
|
-
| Google Gemini
|
|
24
|
-
| MiniMax
|
|
25
|
-
| MiniMax CN
|
|
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
|
-
|
|
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
|
-
|
|
1040
|
+
locationBlock,
|
|
947
1041
|
fitFooterSegment(width, modelStr === plainModelStr ? [plainModelStr] : [modelStr, plainModelStr]),
|
|
948
1042
|
fitFooterSegment(width, [
|
|
949
1043
|
renderContextGauge(percentage, theme, ctxUsed, ctxTotal, {
|