@ogulcancelik/pi-minimal-footer 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 +2 -1
- package/index.ts +89 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,9 +21,10 @@ Minimal footer for [pi](https://github.com/earendil-works/pi) that replaces the
|
|
|
21
21
|
| OpenAI Codex | Primary + secondary rolling windows |
|
|
22
22
|
| GitHub Copilot | Premium interactions + chat quotas |
|
|
23
23
|
| Google Gemini | Pro + Flash remaining quotas |
|
|
24
|
-
| MiniMax | 5h + weekly rolling windows (Token Plan
|
|
24
|
+
| MiniMax | 5h + weekly rolling windows (Token Plan, credit-based) |
|
|
25
25
|
| MiniMax CN | Same as MiniMax, China endpoint |
|
|
26
26
|
| Kimi Coding | 5h + weekly rolling windows (Plan) |
|
|
27
|
+
| OpenCode Go | 5h + weekly + monthly usage windows |
|
|
27
28
|
|
|
28
29
|
## Install
|
|
29
30
|
|
package/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Custom footer with context gauge + subscription usage bars.
|
|
5
5
|
* Auto-detects provider from current model and shows relevant usage.
|
|
6
6
|
*
|
|
7
|
-
* Supports: Claude Max, Codex, Copilot, Gemini
|
|
7
|
+
* Supports: Claude Max, Codex, Copilot, Gemini, MiniMax Token Plan, Kimi Coding
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -267,6 +267,10 @@ function getKimiToken(): string | undefined {
|
|
|
267
267
|
return getApiKey("kimi-coding", "KIMI_API_KEY");
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
function getOpencodeToken(): string | undefined {
|
|
271
|
+
return getApiKey("opencode-go", "OPENCODE_API_KEY");
|
|
272
|
+
}
|
|
273
|
+
|
|
270
274
|
// ============ Time Formatting ============
|
|
271
275
|
|
|
272
276
|
function formatResetTime(date: Date): string {
|
|
@@ -544,10 +548,12 @@ async function fetchGeminiUsage(): Promise<UsageSnapshot> {
|
|
|
544
548
|
async function fetchMinimaxUsage(provider: "minimax" | "minimax-cn"): Promise<UsageSnapshot> {
|
|
545
549
|
const token = getMinimaxToken(provider);
|
|
546
550
|
const providerLabel = provider === "minimax-cn" ? "MiniMax CN" : "MiniMax";
|
|
551
|
+
// Docs-recommended Token Plan endpoint. The legacy /coding_plan/remains path
|
|
552
|
+
// still works but the response field names differ from the current UI.
|
|
547
553
|
const endpoint =
|
|
548
554
|
provider === "minimax-cn"
|
|
549
|
-
? "https://api.minimaxi.com/v1/
|
|
550
|
-
: "https://api.minimax.io/v1/
|
|
555
|
+
? "https://api.minimaxi.com/v1/token_plan/remains"
|
|
556
|
+
: "https://api.minimax.io/v1/token_plan/remains";
|
|
551
557
|
|
|
552
558
|
if (!token) {
|
|
553
559
|
return { provider: providerLabel, windows: [], error: "no-auth", fetchedAt: Date.now() };
|
|
@@ -578,9 +584,16 @@ async function fetchMinimaxUsage(provider: "minimax" | "minimax-cn"): Promise<Us
|
|
|
578
584
|
}
|
|
579
585
|
|
|
580
586
|
const remains = Array.isArray(data?.model_remains) ? data.model_remains : [];
|
|
587
|
+
// Token Plan returns one bucket per capability (general = text/code, video, etc.).
|
|
588
|
+
// Prefer the active "general" bucket since M-series chat models land there.
|
|
589
|
+
// status === 1 = window is active/limiting, 3 = inactive (no usage). Fall back
|
|
590
|
+
// to the first active bucket, then the first bucket of any kind.
|
|
581
591
|
const textBucket =
|
|
582
|
-
remains.find(
|
|
583
|
-
|
|
592
|
+
remains.find(
|
|
593
|
+
(entry: any) => entry?.model_name === "general" && Number(entry?.current_interval_status) === 1
|
|
594
|
+
) ||
|
|
595
|
+
remains.find((entry: any) => entry?.model_name === "general") ||
|
|
596
|
+
remains.find((entry: any) => Number(entry?.current_interval_status) === 1) ||
|
|
584
597
|
remains[0];
|
|
585
598
|
|
|
586
599
|
if (!textBucket) {
|
|
@@ -589,12 +602,12 @@ async function fetchMinimaxUsage(provider: "minimax" | "minimax-cn"): Promise<Us
|
|
|
589
602
|
|
|
590
603
|
const windows: RateWindow[] = [];
|
|
591
604
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const usedPercent = clampPercent(
|
|
605
|
+
// Source of truth: *_remaining_percent (the *_total_count / *_usage_count
|
|
606
|
+
// fields are zeroed in the credit-based model and cannot be used to compute
|
|
607
|
+
// a fraction). The UI usage bar maps 100 - remainingPercent -> used.
|
|
608
|
+
const intervalRemaining = Number(textBucket.current_interval_remaining_percent);
|
|
609
|
+
if (Number.isFinite(intervalRemaining)) {
|
|
610
|
+
const usedPercent = clampPercent(100 - intervalRemaining);
|
|
598
611
|
const resetDate = textBucket.end_time ? new Date(Number(textBucket.end_time)) : undefined;
|
|
599
612
|
const durationMs =
|
|
600
613
|
textBucket.start_time && textBucket.end_time
|
|
@@ -607,13 +620,12 @@ async function fetchMinimaxUsage(provider: "minimax" | "minimax-cn"): Promise<Us
|
|
|
607
620
|
});
|
|
608
621
|
}
|
|
609
622
|
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const resetDate = textBucket.weekly_end_time ? new Date(Number(textBucket.weekly_end_time)) : undefined;
|
|
623
|
+
const weeklyRemaining = Number(textBucket.current_weekly_remaining_percent);
|
|
624
|
+
if (Number.isFinite(weeklyRemaining)) {
|
|
625
|
+
const usedPercent = clampPercent(100 - weeklyRemaining);
|
|
626
|
+
const resetDate = textBucket.weekly_end_time
|
|
627
|
+
? new Date(Number(textBucket.weekly_end_time))
|
|
628
|
+
: undefined;
|
|
617
629
|
const durationMs =
|
|
618
630
|
textBucket.weekly_start_time && textBucket.weekly_end_time
|
|
619
631
|
? Number(textBucket.weekly_end_time) - Number(textBucket.weekly_start_time)
|
|
@@ -625,6 +637,10 @@ async function fetchMinimaxUsage(provider: "minimax" | "minimax-cn"): Promise<Us
|
|
|
625
637
|
});
|
|
626
638
|
}
|
|
627
639
|
|
|
640
|
+
if (windows.length === 0) {
|
|
641
|
+
return { provider: providerLabel, windows: [], error: "no-usage-data", fetchedAt: Date.now() };
|
|
642
|
+
}
|
|
643
|
+
|
|
628
644
|
return { provider: providerLabel, windows, fetchedAt: Date.now() };
|
|
629
645
|
} catch (e) {
|
|
630
646
|
return { provider: providerLabel, windows: [], error: String(e), fetchedAt: Date.now() };
|
|
@@ -694,6 +710,58 @@ async function fetchKimiUsage(): Promise<UsageSnapshot> {
|
|
|
694
710
|
}
|
|
695
711
|
}
|
|
696
712
|
|
|
713
|
+
async function fetchOpencodeGoUsage(): Promise<UsageSnapshot> {
|
|
714
|
+
const providerLabel = "OpenCode Go";
|
|
715
|
+
const token = getOpencodeToken();
|
|
716
|
+
if (!token) {
|
|
717
|
+
return { provider: providerLabel, windows: [], error: "no-auth", fetchedAt: Date.now() };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
try {
|
|
721
|
+
const res = await fetchWithTimeout("https://opencode.ai/zen/go/v1/usage", {
|
|
722
|
+
method: "GET",
|
|
723
|
+
headers: {
|
|
724
|
+
Authorization: `Bearer ${token}`,
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
if (!res.ok) {
|
|
729
|
+
return { provider: providerLabel, windows: [], error: `HTTP ${res.status}`, fetchedAt: Date.now() };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const data = (await res.json()) as any;
|
|
733
|
+
const windows: RateWindow[] = [];
|
|
734
|
+
|
|
735
|
+
if (data.rollingUsage) {
|
|
736
|
+
const usedPercent = clampPercent(data.rollingUsage.usagePercent ?? 0);
|
|
737
|
+
const resetsIn = Number.isFinite(data.rollingUsage.resetInSec)
|
|
738
|
+
? formatResetTime(new Date(Date.now() + data.rollingUsage.resetInSec * 1000))
|
|
739
|
+
: undefined;
|
|
740
|
+
windows.push({ label: "5h", usedPercent, resetsIn });
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (data.weeklyUsage) {
|
|
744
|
+
const usedPercent = clampPercent(data.weeklyUsage.usagePercent ?? 0);
|
|
745
|
+
const resetsIn = Number.isFinite(data.weeklyUsage.resetInSec)
|
|
746
|
+
? formatResetTime(new Date(Date.now() + data.weeklyUsage.resetInSec * 1000))
|
|
747
|
+
: undefined;
|
|
748
|
+
windows.push({ label: "Week", usedPercent, resetsIn });
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (data.monthlyUsage) {
|
|
752
|
+
const usedPercent = clampPercent(data.monthlyUsage.usagePercent ?? 0);
|
|
753
|
+
const resetsIn = Number.isFinite(data.monthlyUsage.resetInSec)
|
|
754
|
+
? formatResetTime(new Date(Date.now() + data.monthlyUsage.resetInSec * 1000))
|
|
755
|
+
: undefined;
|
|
756
|
+
windows.push({ label: "Month", usedPercent, resetsIn });
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return { provider: providerLabel, windows, fetchedAt: Date.now() };
|
|
760
|
+
} catch (e) {
|
|
761
|
+
return { provider: providerLabel, windows: [], error: String(e), fetchedAt: Date.now() };
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
697
765
|
// ============ Provider Detection ============
|
|
698
766
|
|
|
699
767
|
// Map pi provider names to our internal usage provider keys
|
|
@@ -705,6 +773,7 @@ const PROVIDER_MAP: Record<string, string> = {
|
|
|
705
773
|
minimax: "minimax", // MiniMax Token Plan / Coding Plan
|
|
706
774
|
"minimax-cn": "minimax-cn", // MiniMax China plan
|
|
707
775
|
"kimi-coding": "kimi-coding", // Kimi plan
|
|
776
|
+
"opencode-go": "opencode-go", // OpenCode Go plan
|
|
708
777
|
};
|
|
709
778
|
|
|
710
779
|
function detectProvider(modelProvider: string): string | null {
|
|
@@ -727,6 +796,8 @@ async function fetchUsageForProvider(provider: string): Promise<UsageSnapshot> {
|
|
|
727
796
|
return fetchMinimaxUsage("minimax-cn");
|
|
728
797
|
case "kimi-coding":
|
|
729
798
|
return fetchKimiUsage();
|
|
799
|
+
case "opencode-go":
|
|
800
|
+
return fetchOpencodeGoUsage();
|
|
730
801
|
default:
|
|
731
802
|
return { provider: "Unknown", windows: [], error: "unknown-provider", fetchedAt: Date.now() };
|
|
732
803
|
}
|