@monotykamary/pi-better-grok 0.3.0 → 0.3.2
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/index.ts +59 -10
- package/package.json +1 -1
- package/src/usage-controller.ts +20 -4
- package/src/usage.ts +41 -6
package/index.ts
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
* cli-chat-proxy.grok.com identity-first billing surface using pi's native
|
|
8
8
|
* xAI OAuth credential.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type ExtensionAPI,
|
|
12
|
+
type ExtensionContext,
|
|
13
|
+
type Theme,
|
|
14
|
+
type ThemeColor,
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
16
|
import { Container, SettingsList, type SettingsListTheme } from "@earendil-works/pi-tui";
|
|
12
17
|
import { CONFIG_BASENAME, STATUS_KEY } from "./src/identity.ts";
|
|
13
18
|
import {
|
|
@@ -56,6 +61,8 @@ import {
|
|
|
56
61
|
parseUsageSnapshot,
|
|
57
62
|
requestGrokUsage,
|
|
58
63
|
UsageError,
|
|
64
|
+
type UsageSegment,
|
|
65
|
+
type UsageSeverity,
|
|
59
66
|
type UsageSnapshot,
|
|
60
67
|
} from "./src/usage.ts";
|
|
61
68
|
import {
|
|
@@ -85,6 +92,46 @@ const requireSettingsListTheme = (): SettingsListTheme => {
|
|
|
85
92
|
return loadedSettingsListTheme;
|
|
86
93
|
};
|
|
87
94
|
|
|
95
|
+
const USAGE_SEVERITY_COLORS: Record<UsageSeverity, ThemeColor> = {
|
|
96
|
+
ok: "success",
|
|
97
|
+
warning: "warning",
|
|
98
|
+
critical: "error",
|
|
99
|
+
muted: "dim",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Keep a coloured segment on one line. Unlike sanitizeStatusText this leaves the
|
|
104
|
+
* spacing shared by neighbouring segments ("5h: " + "90%") intact.
|
|
105
|
+
*/
|
|
106
|
+
function flattenSegment(text: string): string {
|
|
107
|
+
return text.replace(/[\r\n\t]+/g, " ");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Colour a usage line segment by segment: the percentage carries the severity
|
|
112
|
+
* colour while the label, countdown and banked resets stay dim.
|
|
113
|
+
*/
|
|
114
|
+
function colorizeUsageSegments(segments: UsageSegment[], theme: Theme): string {
|
|
115
|
+
return segments
|
|
116
|
+
.map((segment) =>
|
|
117
|
+
theme.fg(USAGE_SEVERITY_COLORS[segment.severity], flattenSegment(segment.text)),
|
|
118
|
+
)
|
|
119
|
+
.join("");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Pair the fast-mode segment with the usage segments for the status widget. */
|
|
123
|
+
function statusWidgetParts(
|
|
124
|
+
fast: string | undefined,
|
|
125
|
+
usage: UsageSegment[] | undefined,
|
|
126
|
+
): UsageSegment[] | undefined {
|
|
127
|
+
const parts: UsageSegment[] = fast ? [{ text: fast, severity: "muted" }] : [];
|
|
128
|
+
if (usage?.length) {
|
|
129
|
+
if (parts.length > 0) parts.push({ text: " · ", severity: "muted" });
|
|
130
|
+
parts.push(...usage);
|
|
131
|
+
}
|
|
132
|
+
return parts.length > 0 ? parts : undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
88
135
|
class DynamicBorder {
|
|
89
136
|
readonly #color: (text: string) => string;
|
|
90
137
|
|
|
@@ -548,8 +595,10 @@ export default function betterGrok(pi: ExtensionAPI): void {
|
|
|
548
595
|
parts.push(contextText);
|
|
549
596
|
|
|
550
597
|
const cfg = config(ctx);
|
|
551
|
-
const
|
|
552
|
-
const usageLine =
|
|
598
|
+
const usageStatusSegments = usageController.statusSegments(ctx, cfg, usingSubscription);
|
|
599
|
+
const usageLine = usageStatusSegments
|
|
600
|
+
? colorizeUsageSegments(usageStatusSegments, theme)
|
|
601
|
+
: undefined;
|
|
553
602
|
|
|
554
603
|
let statsLeft = parts.join(" ");
|
|
555
604
|
let statsLeftWidth = visibleWidth(statsLeft);
|
|
@@ -630,22 +679,22 @@ export default function betterGrok(pi: ExtensionAPI): void {
|
|
|
630
679
|
statusInstalled = text !== undefined;
|
|
631
680
|
}
|
|
632
681
|
|
|
633
|
-
function setStatusWidget(ctx: ExtensionContext,
|
|
634
|
-
if (!
|
|
682
|
+
function setStatusWidget(ctx: ExtensionContext, parts: UsageSegment[] | undefined): void {
|
|
683
|
+
if (!parts && !statusWidgetInstalled) return;
|
|
635
684
|
ctx.ui.setWidget(
|
|
636
685
|
STATUS_KEY,
|
|
637
|
-
|
|
686
|
+
parts
|
|
638
687
|
? (_tui, theme) => ({
|
|
639
688
|
invalidate() {},
|
|
640
689
|
render(width: number): string[] {
|
|
641
|
-
const line =
|
|
690
|
+
const line = colorizeUsageSegments(parts, theme);
|
|
642
691
|
return [truncateToWidth(line, width, theme.fg("dim", "..."))];
|
|
643
692
|
},
|
|
644
693
|
})
|
|
645
694
|
: undefined,
|
|
646
695
|
{ placement: "belowEditor" },
|
|
647
696
|
);
|
|
648
|
-
statusWidgetInstalled =
|
|
697
|
+
statusWidgetInstalled = parts !== undefined;
|
|
649
698
|
}
|
|
650
699
|
|
|
651
700
|
function updateFooter(ctx: ExtensionContext): void {
|
|
@@ -678,8 +727,8 @@ export default function betterGrok(pi: ExtensionAPI): void {
|
|
|
678
727
|
}
|
|
679
728
|
|
|
680
729
|
const fast = fastController.statusSegment(ctx, cfg);
|
|
681
|
-
const usage = usageController.
|
|
682
|
-
setStatusWidget(ctx,
|
|
730
|
+
const usage = usageController.statusSegments(ctx, cfg);
|
|
731
|
+
setStatusWidget(ctx, statusWidgetParts(fast, usage));
|
|
683
732
|
}
|
|
684
733
|
|
|
685
734
|
pi.on("session_start", (_event, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monotykamary/pi-better-grok",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Improve Grok/xAI in pi with fast mode, subscription usage stats, banked reset redemption, multiprovider pools, footer polish, and settings — mirroring pi-better-openai.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"footer",
|
package/src/usage-controller.ts
CHANGED
|
@@ -3,7 +3,13 @@ import type { ResolvedConfig } from "./config.ts";
|
|
|
3
3
|
import { isXaiProvider, readPiStoredOAuthToken } from "./grok-auth.ts";
|
|
4
4
|
import { sanitizeDiagnosticError } from "./format.ts";
|
|
5
5
|
import { currentModelKey } from "./fast-controller.ts";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type UsageSegment,
|
|
8
|
+
type UsageSnapshot,
|
|
9
|
+
formatUsageDetail,
|
|
10
|
+
formatUsageSnapshot,
|
|
11
|
+
usageSegments,
|
|
12
|
+
} from "./usage.ts";
|
|
7
13
|
|
|
8
14
|
export function isGrokSubscriptionModel(
|
|
9
15
|
ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
|
|
@@ -77,22 +83,32 @@ export class UsageController {
|
|
|
77
83
|
return this.usageSnapshot;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
|
|
86
|
+
statusSegments(
|
|
81
87
|
ctx: ExtensionContext,
|
|
82
88
|
cfg = this.getConfig(ctx),
|
|
83
89
|
isUsingOAuth?: boolean,
|
|
84
|
-
):
|
|
90
|
+
): UsageSegment[] | undefined {
|
|
85
91
|
return this.usageSnapshot &&
|
|
86
92
|
!this.usageError &&
|
|
87
93
|
cfg.usage.enabled &&
|
|
88
94
|
isGrokSubscriptionModel(ctx, cfg, isUsingOAuth)
|
|
89
|
-
?
|
|
95
|
+
? usageSegments(this.usageSnapshot, {
|
|
90
96
|
...cfg.usage,
|
|
91
97
|
bankedResets: this.getBankedResets?.() ?? null,
|
|
92
98
|
})
|
|
93
99
|
: undefined;
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
statusLine(
|
|
103
|
+
ctx: ExtensionContext,
|
|
104
|
+
cfg = this.getConfig(ctx),
|
|
105
|
+
isUsingOAuth?: boolean,
|
|
106
|
+
): string | undefined {
|
|
107
|
+
return this.statusSegments(ctx, cfg, isUsingOAuth)
|
|
108
|
+
?.map((segment) => segment.text)
|
|
109
|
+
.join("");
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
formatStatus(ctx: ExtensionContext): string {
|
|
97
113
|
const cfg = this.getConfig(ctx);
|
|
98
114
|
if (!cfg.usage.enabled) return "Usage display is disabled.";
|
package/src/usage.ts
CHANGED
|
@@ -241,24 +241,59 @@ export function formatBankedResetsSuffix(count: number | null | undefined): stri
|
|
|
241
241
|
return `${count} banked reset${count === 1 ? "" : "s"}`;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
export
|
|
244
|
+
export type UsageSeverity = "ok" | "warning" | "critical" | "muted";
|
|
245
|
+
|
|
246
|
+
export type UsageSegment = {
|
|
247
|
+
text: string;
|
|
248
|
+
severity: UsageSeverity;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/** Remaining budget at or below these thresholds turns the percentage amber/red. */
|
|
252
|
+
const WARNING_LEFT_PERCENT = 30;
|
|
253
|
+
const CRITICAL_LEFT_PERCENT = 10;
|
|
254
|
+
|
|
255
|
+
export function severityForLeftPercent(percent: number | null): UsageSeverity {
|
|
256
|
+
if (percent === null) return "muted";
|
|
257
|
+
if (percent <= CRITICAL_LEFT_PERCENT) return "critical";
|
|
258
|
+
if (percent <= WARNING_LEFT_PERCENT) return "warning";
|
|
259
|
+
return "ok";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function usageSegments(
|
|
245
263
|
snapshot: UsageSnapshot,
|
|
246
264
|
options: UsageStatusOptions,
|
|
247
265
|
now = Date.now(),
|
|
248
|
-
):
|
|
266
|
+
): UsageSegment[] {
|
|
249
267
|
const used = snapshot.creditUsagePercent;
|
|
250
268
|
const left = used === null ? null : clampPercent(100 - used);
|
|
251
|
-
const
|
|
269
|
+
const segments: UsageSegment[] = [
|
|
270
|
+
{ text: "Usage: ", severity: "muted" },
|
|
271
|
+
{ text: formatPercent(left), severity: severityForLeftPercent(left) },
|
|
272
|
+
{ text: " left", severity: "muted" },
|
|
273
|
+
];
|
|
252
274
|
if (options.showResetTimes) {
|
|
253
275
|
const seconds = periodSecondsLeft(snapshot, now);
|
|
254
276
|
const countdown = formatResetCountdown(seconds);
|
|
255
277
|
const clock = formatResetClock(seconds, now);
|
|
256
|
-
if (countdown && clock)
|
|
278
|
+
if (countdown && clock) {
|
|
279
|
+
segments.push({ text: ` · ↺ ${countdown} - ${clock}`, severity: "muted" });
|
|
280
|
+
}
|
|
257
281
|
}
|
|
258
282
|
const banked =
|
|
259
283
|
options.showBankedResets === false ? null : formatBankedResetsSuffix(options.bankedResets);
|
|
260
|
-
if (banked)
|
|
261
|
-
return
|
|
284
|
+
if (banked) segments.push({ text: ` · ${banked}`, severity: "muted" });
|
|
285
|
+
return segments;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Flat text form of {@link usageSegments}, used for status lines and notifications. */
|
|
289
|
+
export function formatUsageSnapshot(
|
|
290
|
+
snapshot: UsageSnapshot,
|
|
291
|
+
options: UsageStatusOptions,
|
|
292
|
+
now = Date.now(),
|
|
293
|
+
): string {
|
|
294
|
+
return usageSegments(snapshot, options, now)
|
|
295
|
+
.map((segment) => segment.text)
|
|
296
|
+
.join("");
|
|
262
297
|
}
|
|
263
298
|
|
|
264
299
|
export function formatUsageDetail(snapshot: UsageSnapshot): string {
|