pi-opencode-go-provider 1.1.0 → 1.1.1
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 +5 -1
- package/config.ts +7 -0
- package/glyphs.ts +57 -0
- package/index.ts +30 -7
- package/package.json +1 -1
- package/tests/glyphs.test.ts +76 -0
- package/usage.ts +16 -10
package/README.md
CHANGED
|
@@ -139,6 +139,7 @@ so that trailing segment appears only if a response ever carries a
|
|
|
139
139
|
/opencode-go-usage off # hide the widget (persisted)
|
|
140
140
|
/opencode-go-usage on # show it again
|
|
141
141
|
/opencode-go-usage debug # config, last fetch/error, endpoint
|
|
142
|
+
/opencode-go-usage glyphs auto|unicode|ascii # footer glyph set (persisted)
|
|
142
143
|
```
|
|
143
144
|
|
|
144
145
|
Polling runs every 60 seconds, plus after every turn and whenever the selected
|
|
@@ -151,7 +152,8 @@ model changes. Settings are read from `~/.pi/agent/opencode-go-provider.json`:
|
|
|
151
152
|
"refreshIntervalMs": 60000,
|
|
152
153
|
"showOnlyOnProvider": true,
|
|
153
154
|
"showResetTimes": true,
|
|
154
|
-
"placement": "belowEditor"
|
|
155
|
+
"placement": "belowEditor",
|
|
156
|
+
"glyphs": "auto"
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
```
|
|
@@ -159,6 +161,8 @@ model changes. Settings are read from `~/.pi/agent/opencode-go-provider.json`:
|
|
|
159
161
|
`placement` accepts `belowEditor` (default) or `aboveEditor`. Every key is
|
|
160
162
|
optional; `/opencode-go-usage on|off` writes only `enabled`.
|
|
161
163
|
|
|
164
|
+
`glyphs` accepts `auto` (default), `unicode`, or `ascii`. Older mintty/Cygwin builds measure ambiguous-width codepoints (the `·` separators, the `↺` reset marker) with their own cell-width tables, which can shift the row and desync pi’s renderer. `auto` switches the widget to ASCII equivalents on those terminals and the widget never paints the terminal’s last column; an explicit `unicode` is clamped to ASCII for widget content there (the status line is not row-budgeted and keeps the choice).
|
|
165
|
+
|
|
162
166
|
### Pooled accounts
|
|
163
167
|
|
|
164
168
|
When [pi-multiprovider](https://github.com/monotykamary/pi-multiprovider)
|
package/config.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { GLYPH_MODES, type GlyphMode } from "./glyphs.ts";
|
|
12
13
|
|
|
13
14
|
export const PROVIDER_ID = "opencode-go";
|
|
14
15
|
export const CONFIG_BASENAME = "opencode-go-provider.json";
|
|
@@ -26,6 +27,8 @@ export interface UsageConfig {
|
|
|
26
27
|
showOnlyOnProvider: boolean;
|
|
27
28
|
/** Include reset countdowns in the widget line. */
|
|
28
29
|
showResetTimes: boolean;
|
|
30
|
+
/** Footer glyph set. "auto" degrades to ASCII on legacy terminals (mintty/Cygwin). */
|
|
31
|
+
glyphs: GlyphMode;
|
|
29
32
|
placement: UsagePlacement;
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -35,6 +38,7 @@ export const DEFAULT_USAGE_CONFIG: UsageConfig = {
|
|
|
35
38
|
showOnlyOnProvider: true,
|
|
36
39
|
showResetTimes: true,
|
|
37
40
|
placement: "belowEditor",
|
|
41
|
+
glyphs: "auto",
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
export const MIN_USAGE_REFRESH_MS = 15_000;
|
|
@@ -90,6 +94,9 @@ export function readUsageConfig(env: NodeJS.ProcessEnv = process.env): UsageConf
|
|
|
90
94
|
if (typeof file.refreshIntervalMs === "number") {
|
|
91
95
|
config.refreshIntervalMs = clampRefreshInterval(file.refreshIntervalMs);
|
|
92
96
|
}
|
|
97
|
+
if (GLYPH_MODES.includes(file.glyphs as GlyphMode)) {
|
|
98
|
+
config.glyphs = file.glyphs as GlyphMode;
|
|
99
|
+
}
|
|
93
100
|
if (USAGE_PLACEMENTS.includes(file.placement as UsagePlacement)) {
|
|
94
101
|
config.placement = file.placement as UsagePlacement;
|
|
95
102
|
}
|
package/glyphs.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer glyph policy for the usage widget.
|
|
3
|
+
*
|
|
4
|
+
* Older mintty/Cygwin builds measure emoji and ambiguous-width codepoints with
|
|
5
|
+
* their own cell-width tables, which can disagree with pi's. The widget line is
|
|
6
|
+
* truncated to (now) width - 1 and never padded to the edge, but a glyph the
|
|
7
|
+
* terminal measures wider than expected still shifts everything after it and
|
|
8
|
+
* can desync pi's differential renderer. "auto" swaps the footer glyphs for
|
|
9
|
+
* ASCII on detected legacy terminals; "unicode"/"ascii" force a set.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type GlyphMode = "auto" | "unicode" | "ascii";
|
|
13
|
+
|
|
14
|
+
export interface GlyphSet {
|
|
15
|
+
/** Separator between usage atoms. */
|
|
16
|
+
sep: string;
|
|
17
|
+
/** Reset-countdown prefix. */
|
|
18
|
+
reset: string;
|
|
19
|
+
/** Progress-bar fill / remainder (command output). */
|
|
20
|
+
barFilled: string;
|
|
21
|
+
barHollow: string;
|
|
22
|
+
/** Truncation marker. */
|
|
23
|
+
ellipsis: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const UNICODE_GLYPHS: GlyphSet = { sep: "\u00b7", reset: "\u21ba", barFilled: "\u2588", barHollow: "\u2591", ellipsis: "\u2026" };
|
|
27
|
+
export const ASCII_GLYPHS: GlyphSet = { sep: "-", reset: "~", barFilled: "#", barHollow: "-", ellipsis: "..." };
|
|
28
|
+
|
|
29
|
+
export const GLYPH_MODES: GlyphMode[] = ["auto", "unicode", "ascii"];
|
|
30
|
+
|
|
31
|
+
/** True for terminals whose cell-width tables are known to disagree with the
|
|
32
|
+
* width math in format.ts (older mintty/Cygwin builds). */
|
|
33
|
+
export function detectLegacyTerminal(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
34
|
+
const termProgram = env.TERM_PROGRAM ?? "";
|
|
35
|
+
const term = env.TERM ?? "";
|
|
36
|
+
return (
|
|
37
|
+
termProgram === "mintty" ||
|
|
38
|
+
termProgram === "cygwin" ||
|
|
39
|
+
termProgram === "msys" ||
|
|
40
|
+
term.startsWith("cygwin") ||
|
|
41
|
+
term.startsWith("msys")
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveGlyphSet(mode: GlyphMode, env: NodeJS.ProcessEnv = process.env): GlyphSet {
|
|
46
|
+
if (mode === "unicode") return UNICODE_GLYPHS;
|
|
47
|
+
if (mode === "ascii") return ASCII_GLYPHS;
|
|
48
|
+
return detectLegacyTerminal(env) ? ASCII_GLYPHS : UNICODE_GLYPHS;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Widget-safe variant. The widget renders inside the editor's row budget, so
|
|
52
|
+
* an over-wide glyph there can desync pi's renderer; status fallback text is
|
|
53
|
+
* not row-budgeted and keeps an explicit choice. */
|
|
54
|
+
export function resolveWidgetGlyphSet(mode: GlyphMode, env: NodeJS.ProcessEnv = process.env): GlyphSet {
|
|
55
|
+
if (mode === "unicode" && detectLegacyTerminal(env)) return ASCII_GLYPHS;
|
|
56
|
+
return resolveGlyphSet(mode, env);
|
|
57
|
+
}
|
package/index.ts
CHANGED
|
@@ -29,8 +29,9 @@ import {
|
|
|
29
29
|
type ModelRegistry,
|
|
30
30
|
type ThemeColor,
|
|
31
31
|
} from "@earendil-works/pi-coding-agent";
|
|
32
|
-
import { USAGE_WIDGET_KEY, readUsageConfig, writeUsageConfig } from "./config.ts";
|
|
32
|
+
import { USAGE_WIDGET_KEY, readUsageConfig, writeUsageConfig, type UsageConfig } from "./config.ts";
|
|
33
33
|
import { sanitizeStatusText, truncateToWidth } from "./format.ts";
|
|
34
|
+
import { resolveGlyphSet, resolveWidgetGlyphSet, type GlyphSet } from "./glyphs.ts";
|
|
34
35
|
import { usageSegments, type UsageSegment, type UsageSeverity } from "./usage.ts";
|
|
35
36
|
import { UsageController } from "./usage-controller.ts";
|
|
36
37
|
import {
|
|
@@ -426,6 +427,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
426
427
|
|
|
427
428
|
let usageConfig = readUsageConfig();
|
|
428
429
|
let usageWidgetInstalled = false;
|
|
430
|
+
let usageClampNotified = false;
|
|
429
431
|
const usageController = new UsageController(() => usageConfig, updateUsageWidget);
|
|
430
432
|
let usageContext: ExtensionContext | undefined;
|
|
431
433
|
|
|
@@ -463,17 +465,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
463
465
|
muted: "dim",
|
|
464
466
|
};
|
|
465
467
|
|
|
466
|
-
function usageSegmentsFor(ctx: ExtensionContext): UsageSegment[] | undefined {
|
|
468
|
+
function usageSegmentsFor(ctx: ExtensionContext, glyphs: GlyphSet): UsageSegment[] | undefined {
|
|
467
469
|
const snapshot = usageController.snapshot;
|
|
468
470
|
if (!snapshot || !usageConfig.enabled || !usageController.isEligible(ctx)) return undefined;
|
|
469
|
-
const segments = usageSegments(snapshot, { showResetTimes: usageConfig.showResetTimes });
|
|
470
|
-
if (usageController.isStale()) segments.push({ text:
|
|
471
|
+
const segments = usageSegments(snapshot, { showResetTimes: usageConfig.showResetTimes, glyphs });
|
|
472
|
+
if (usageController.isStale()) segments.push({ text: ` ${glyphs.sep} stale`, severity: "warning" });
|
|
471
473
|
return segments;
|
|
472
474
|
}
|
|
473
475
|
|
|
474
476
|
function updateUsageWidget(ctx: ExtensionContext): void {
|
|
475
477
|
try {
|
|
476
|
-
|
|
478
|
+
// Legacy terminals measure the footer glyphs with their own tables; widget
|
|
479
|
+
// content clamps to ASCII there, the status fallback keeps the choice.
|
|
480
|
+
const glyphs = resolveGlyphSet(usageConfig.glyphs);
|
|
481
|
+
const widgetGlyphs = resolveWidgetGlyphSet(usageConfig.glyphs);
|
|
482
|
+
if (widgetGlyphs !== glyphs && !usageClampNotified) {
|
|
483
|
+
usageClampNotified = true;
|
|
484
|
+
ctx.ui.notify("OpenCode Go: widget glyphs stay ASCII on this terminal — unicode glyphs overflow legacy mintty/Cygwin cell widths. The status line is unaffected.", "info");
|
|
485
|
+
}
|
|
486
|
+
const segments = usageSegmentsFor(ctx, widgetGlyphs);
|
|
477
487
|
if (!segments) {
|
|
478
488
|
if (usageWidgetInstalled) {
|
|
479
489
|
ctx.ui.setWidget(USAGE_WIDGET_KEY, undefined);
|
|
@@ -494,14 +504,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
494
504
|
const line = segments
|
|
495
505
|
.map((segment) => theme.fg(USAGE_SEVERITY_COLORS[segment.severity], segment.text))
|
|
496
506
|
.join("");
|
|
497
|
-
|
|
507
|
+
// Budget width - 1: never paint the terminal’s last column (a
|
|
508
|
+
// pending wrap there desyncs pi’s renderer on legacy terminals).
|
|
509
|
+
const w = Math.max(1, width - 1);
|
|
510
|
+
return [truncateToWidth(line, w, theme.fg("dim", widgetGlyphs.ellipsis))];
|
|
498
511
|
},
|
|
499
512
|
}),
|
|
500
513
|
{ placement: usageConfig.placement },
|
|
501
514
|
);
|
|
502
515
|
} else {
|
|
503
516
|
ctx.ui.setWidget(USAGE_WIDGET_KEY, undefined);
|
|
504
|
-
ctx
|
|
517
|
+
const barSegments = usageSegmentsFor(ctx, glyphs) ?? segments;
|
|
518
|
+
ctx.ui.setStatus(USAGE_WIDGET_KEY, sanitizeStatusText(barSegments.map((s) => s.text).join("")));
|
|
505
519
|
}
|
|
506
520
|
usageWidgetInstalled = true;
|
|
507
521
|
} catch {
|
|
@@ -513,6 +527,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
513
527
|
description: "Show, refresh, or toggle the OpenCode Go 5h / 7d / 30d usage widget",
|
|
514
528
|
handler: async (args, ctx) => {
|
|
515
529
|
const action = args.trim().toLowerCase();
|
|
530
|
+
if (action === "glyphs auto" || action === "glyphs unicode" || action === "glyphs ascii") {
|
|
531
|
+
const glyphs = action.slice("glyphs ".length) as UsageConfig["glyphs"];
|
|
532
|
+
usageConfig = { ...usageConfig, glyphs };
|
|
533
|
+
const persisted = writeUsageConfig({ glyphs });
|
|
534
|
+
updateUsageWidget(ctx);
|
|
535
|
+
const suffix = persisted ? "" : " for this session (could not write the config file)";
|
|
536
|
+
ctx.ui.notify(`OpenCode Go footer glyphs: ${glyphs}${suffix}.`, "info");
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
516
539
|
if (action === "on" || action === "off") {
|
|
517
540
|
const enabled = action === "on";
|
|
518
541
|
usageConfig = { ...usageConfig, enabled };
|
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glyph policy: legacy terminals (mintty/Cygwin) get an ASCII footer so an
|
|
3
|
+
* over-wide glyph cannot shift the widget line and desync pi's renderer.
|
|
4
|
+
*/
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
import { DEFAULT_USAGE_CONFIG } from "../config.ts";
|
|
8
|
+
import { ASCII_GLYPHS, UNICODE_GLYPHS, detectLegacyTerminal, resolveGlyphSet, resolveWidgetGlyphSet } from "../glyphs.ts";
|
|
9
|
+
import { formatBar, parseUsageSnapshot, usageSegments } from "../usage.ts";
|
|
10
|
+
import { truncateToWidth } from "../format.ts";
|
|
11
|
+
|
|
12
|
+
const mintty = { TERM_PROGRAM: "mintty", TERM: "xterm" } as NodeJS.ProcessEnv;
|
|
13
|
+
const cygwin = { TERM: "cygwin" } as NodeJS.ProcessEnv;
|
|
14
|
+
const wt = { TERM_PROGRAM: "Windows_Terminal", TERM: "xterm-256color" } as NodeJS.ProcessEnv;
|
|
15
|
+
const isAscii = (value: string) => [...value].every((char) => char.charCodeAt(0) < 128);
|
|
16
|
+
|
|
17
|
+
test("detects legacy terminals", () => {
|
|
18
|
+
assert.equal(detectLegacyTerminal(mintty), true);
|
|
19
|
+
assert.equal(detectLegacyTerminal(cygwin), true);
|
|
20
|
+
assert.equal(detectLegacyTerminal(wt), false);
|
|
21
|
+
assert.equal(detectLegacyTerminal({} as NodeJS.ProcessEnv), false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("auto degrades on legacy terminals; explicit modes win for the status line", () => {
|
|
25
|
+
assert.equal(resolveGlyphSet("auto", mintty), ASCII_GLYPHS);
|
|
26
|
+
assert.equal(resolveGlyphSet("auto", wt), UNICODE_GLYPHS);
|
|
27
|
+
assert.equal(resolveGlyphSet("unicode", mintty), UNICODE_GLYPHS);
|
|
28
|
+
assert.equal(resolveGlyphSet("ascii", wt), ASCII_GLYPHS);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("widget content clamps an explicit unicode choice on legacy terminals", () => {
|
|
32
|
+
assert.equal(resolveWidgetGlyphSet("unicode", mintty), ASCII_GLYPHS);
|
|
33
|
+
assert.equal(resolveWidgetGlyphSet("unicode", wt), UNICODE_GLYPHS);
|
|
34
|
+
assert.equal(resolveWidgetGlyphSet("auto", mintty), ASCII_GLYPHS);
|
|
35
|
+
assert.equal(resolveWidgetGlyphSet("ascii", wt), ASCII_GLYPHS);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("the default config is auto", () => {
|
|
39
|
+
assert.equal(DEFAULT_USAGE_CONFIG.glyphs, "auto");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("the ASCII set is pure ASCII and renders an ASCII usage line", () => {
|
|
43
|
+
assert.equal(isAscii(Object.values(ASCII_GLYPHS).join("")), true);
|
|
44
|
+
assert.equal(isAscii(Object.values(UNICODE_GLYPHS).join("")), false);
|
|
45
|
+
|
|
46
|
+
// Built through the real parser so the fixture cannot drift from the wire shape.
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const snapshot = parseUsageSnapshot(
|
|
49
|
+
{
|
|
50
|
+
usage: {
|
|
51
|
+
rolling: { status: "ok", percent: 37, resetsAt: new Date(now + 3_600_000).toISOString() },
|
|
52
|
+
weekly: { status: "ok", percent: 59, resetsAt: new Date(now + 86_400_000).toISOString() },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
now,
|
|
56
|
+
);
|
|
57
|
+
assert.ok(snapshot);
|
|
58
|
+
const asciiLine = usageSegments(snapshot, { showResetTimes: true, glyphs: ASCII_GLYPHS }, now)
|
|
59
|
+
.map((segment) => segment.text)
|
|
60
|
+
.join("");
|
|
61
|
+
const unicodeLine = usageSegments(snapshot, { showResetTimes: true }, now)
|
|
62
|
+
.map((segment) => segment.text)
|
|
63
|
+
.join("");
|
|
64
|
+
assert.equal(isAscii(asciiLine), true);
|
|
65
|
+
assert.equal(asciiLine.includes("5h: 63% - 7d: 41%"), true);
|
|
66
|
+
assert.equal(unicodeLine.includes("\u00b7"), true);
|
|
67
|
+
assert.equal(unicodeLine.includes("\u21ba"), true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("bars and truncation take the ASCII glyphs too", () => {
|
|
71
|
+
assert.equal(formatBar(50, 4, ASCII_GLYPHS), "##--");
|
|
72
|
+
assert.equal(formatBar(50, 4), "\u2588\u2588\u2591\u2591");
|
|
73
|
+
const cut = truncateToWidth("abcdefghij", 5, ASCII_GLYPHS.ellipsis);
|
|
74
|
+
assert.equal(cut, "ab...");
|
|
75
|
+
assert.equal(isAscii(cut), true);
|
|
76
|
+
});
|
package/usage.ts
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
* 30d ↺ 20d0h
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
import { UNICODE_GLYPHS, type GlyphSet } from "./glyphs.ts";
|
|
28
|
+
|
|
27
29
|
export const USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
28
30
|
|
|
29
31
|
/** Published Go budget per window; informational only, not sent by the API. */
|
|
@@ -66,6 +68,8 @@ export interface UsageSegment {
|
|
|
66
68
|
export interface UsageFormatOptions {
|
|
67
69
|
showResetTimes: boolean;
|
|
68
70
|
showBankedResets?: boolean;
|
|
71
|
+
/** Glyph set for separators and reset markers; defaults to unicode. */
|
|
72
|
+
glyphs?: GlyphSet;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export const USAGE_WINDOW_LABELS: Record<UsageWindowKey, string> = {
|
|
@@ -256,9 +260,10 @@ function formatCompactReset(
|
|
|
256
260
|
label: string | undefined,
|
|
257
261
|
resetAt: number | null,
|
|
258
262
|
now: number,
|
|
263
|
+
glyphs: GlyphSet = UNICODE_GLYPHS,
|
|
259
264
|
): string | null {
|
|
260
265
|
if (resetAt === null) return null;
|
|
261
|
-
return `${label ? `${label} ` : ""}
|
|
266
|
+
return `${label ? `${label} ` : ""}${glyphs.reset} ${formatCountdown(resetAt - now)}`;
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
/** "3 banked resets", or null when the count is absent or zero. */
|
|
@@ -285,11 +290,12 @@ export function usageSegments(
|
|
|
285
290
|
options: UsageFormatOptions,
|
|
286
291
|
now = Date.now(),
|
|
287
292
|
): UsageSegment[] {
|
|
293
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
288
294
|
const windows = snapshot.windows;
|
|
289
295
|
const labelled = windows.length > 1;
|
|
290
296
|
const segments: UsageSegment[] = [{ text: "Usage: ", severity: "muted" }];
|
|
291
297
|
windows.forEach((window, index) => {
|
|
292
|
-
if (index > 0) segments.push({ text:
|
|
298
|
+
if (index > 0) segments.push({ text: ` ${glyphs.sep} `, severity: "muted" });
|
|
293
299
|
segments.push({ text: `${window.label}: `, severity: "muted" });
|
|
294
300
|
segments.push({
|
|
295
301
|
text: formatPercent(window.remainingPercent),
|
|
@@ -298,13 +304,13 @@ export function usageSegments(
|
|
|
298
304
|
});
|
|
299
305
|
if (options.showResetTimes) {
|
|
300
306
|
for (const window of windows) {
|
|
301
|
-
const reset = formatCompactReset(labelled ? window.label : undefined, window.resetsAt, now);
|
|
302
|
-
if (reset) segments.push({ text: `
|
|
307
|
+
const reset = formatCompactReset(labelled ? window.label : undefined, window.resetsAt, now, glyphs);
|
|
308
|
+
if (reset) segments.push({ text: ` ${glyphs.sep} ${reset}`, severity: "muted" });
|
|
303
309
|
}
|
|
304
310
|
}
|
|
305
311
|
if (options.showBankedResets !== false) {
|
|
306
312
|
const banked = formatBankedResetsSuffix(snapshot.bankedResets);
|
|
307
|
-
if (banked) segments.push({ text: `
|
|
313
|
+
if (banked) segments.push({ text: ` ${glyphs.sep} ${banked}`, severity: "muted" });
|
|
308
314
|
}
|
|
309
315
|
return segments;
|
|
310
316
|
}
|
|
@@ -320,13 +326,13 @@ export function formatUsageLine(
|
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
/** Progress bar: 20 cells, filled by the remaining percentage. */
|
|
323
|
-
export function formatBar(percent: number, width = 20): string {
|
|
329
|
+
export function formatBar(percent: number, width = 20, glyphs: GlyphSet = UNICODE_GLYPHS): string {
|
|
324
330
|
const filled = Math.round((clampPercent(percent) / 100) * width);
|
|
325
|
-
return `${
|
|
331
|
+
return `${glyphs.barFilled.repeat(filled)}${glyphs.barHollow.repeat(width - filled)}`;
|
|
326
332
|
}
|
|
327
333
|
|
|
328
334
|
/** Multi-line breakdown with bars, used by the command output. */
|
|
329
|
-
export function formatUsageDetail(snapshot: UsageSnapshot, now = Date.now()): string[] {
|
|
335
|
+
export function formatUsageDetail(snapshot: UsageSnapshot, now = Date.now(), glyphs: GlyphSet = UNICODE_GLYPHS): string[] {
|
|
330
336
|
return snapshot.windows.map((window) => {
|
|
331
337
|
const clock = window.resetsAt === null
|
|
332
338
|
? null
|
|
@@ -334,8 +340,8 @@ export function formatUsageDetail(snapshot: UsageSnapshot, now = Date.now()): st
|
|
|
334
340
|
const reset =
|
|
335
341
|
window.resetsAt === null || clock === null
|
|
336
342
|
? ""
|
|
337
|
-
: `
|
|
343
|
+
: ` ${glyphs.reset} ${formatCountdown(window.resetsAt - now)} - ${clock}`;
|
|
338
344
|
const limited = window.status === "rate-limited" ? " RATE LIMITED" : "";
|
|
339
|
-
return `${window.label.padEnd(3)} ${formatBar(window.remainingPercent)} ${formatPercent(window.remainingPercent).padStart(4)} left${reset}${limited}`;
|
|
345
|
+
return `${window.label.padEnd(3)} ${formatBar(window.remainingPercent, 20, glyphs)} ${formatPercent(window.remainingPercent).padStart(4)} left${reset}${limited}`;
|
|
340
346
|
});
|
|
341
347
|
}
|