rinco-pi-hud 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 +14 -3
- package/docs/CONTRIBUTING.md +16 -6
- package/docs/README.zh-CN.md +14 -3
- package/docs/footer.md +11 -0
- package/extensions/hud/commands/settings-controller.ts +4 -0
- package/extensions/hud/commands/settings.ts +58 -14
- package/extensions/hud/config/config-counts.ts +6 -5
- package/extensions/hud/config/config.ts +2 -0
- package/extensions/hud/config/normalize.ts +17 -0
- package/extensions/hud/config/persist.ts +29 -0
- package/extensions/hud/config/schema.ts +14 -0
- package/extensions/hud/footer/index.ts +8 -12
- package/extensions/hud/footer/layout.ts +29 -12
- package/extensions/hud/footer/render-labels.ts +10 -7
- package/extensions/hud/index.ts +2 -1
- package/extensions/hud/segments/git.ts +2 -8
- package/extensions/hud/segments/mcp-status.ts +2 -1
- package/extensions/hud/segments/package-version.ts +1 -2
- package/extensions/hud/segments/runtime.ts +3 -3
- package/extensions/hud/segments/skill-activity.ts +5 -1
- package/extensions/hud/state/telemetry.ts +14 -8
- package/extensions/hud/telemetry/codex-usage/app-server-client.ts +6 -2
- package/extensions/hud/telemetry/codex-usage/format.ts +1 -4
- package/extensions/hud/telemetry/codex-usage/safety.ts +7 -7
- package/extensions/hud/telemetry/codex-usage/types.ts +1 -2
- package/extensions/hud/telemetry/token-switch.ts +2 -7
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -70,6 +70,10 @@ The HUD footer is enabled by default. Run `/zentui` to open the interactive sett
|
|
|
70
70
|
/zentui statusline enable
|
|
71
71
|
/zentui statusline disable
|
|
72
72
|
/zentui statusline toggle
|
|
73
|
+
/zentui row project toggle
|
|
74
|
+
/zentui row session disable
|
|
75
|
+
/zentui row activity enable
|
|
76
|
+
/zentui row usage toggle
|
|
73
77
|
/zentui format clear
|
|
74
78
|
/zentui format "$model · $context · $cost · $git_branch( $git_commit) · $session_duration"
|
|
75
79
|
/codex-status
|
|
@@ -121,6 +125,14 @@ Open `/zentui`, then select **Built-in segments** to enable or disable individua
|
|
|
121
125
|
|
|
122
126
|
Existing configs using the former aggregate `tokens`, `toolActivity`, or `agentActivity` switches are migrated automatically. The whole footer can still be controlled with `/zentui statusline enable|disable|toggle`.
|
|
123
127
|
|
|
128
|
+
In the default four-row layout, each semantic row can also be controlled directly:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
/zentui row <project|session|activity|usage> <enable|disable|toggle>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Row switches are persisted in `footerRows`. They do not affect a custom single-line `footerFormat`.
|
|
135
|
+
|
|
124
136
|
### Extension statuses
|
|
125
137
|
|
|
126
138
|
Third-party extensions publish statuses that the HUD footer can display. Configure placement (`left`, `middle`, `right`, `off`) and color mode (`zentui`, `original`) for each extension status key.
|
|
@@ -164,11 +176,10 @@ Check types:
|
|
|
164
176
|
npm run typecheck
|
|
165
177
|
```
|
|
166
178
|
|
|
167
|
-
Verify tests and package contents:
|
|
179
|
+
Verify formatting, types, lint rules, tests, and package contents:
|
|
168
180
|
|
|
169
181
|
```bash
|
|
170
|
-
npm
|
|
171
|
-
npm run typecheck
|
|
182
|
+
npm run verify
|
|
172
183
|
npm run pack:check
|
|
173
184
|
```
|
|
174
185
|
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -39,11 +39,12 @@ Pi loads the TypeScript extension directly, so this project has no separate buil
|
|
|
39
39
|
Before submitting a change, run:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
npm
|
|
43
|
-
npm run typecheck
|
|
42
|
+
npm run verify
|
|
44
43
|
npm run pack:check
|
|
45
44
|
```
|
|
46
45
|
|
|
46
|
+
`npm run verify` checks Prettier formatting, TypeScript types, ESLint rules, and the Vitest suite.
|
|
47
|
+
|
|
47
48
|
Tests live in `tests/`:
|
|
48
49
|
|
|
49
50
|
- `zentui-status.test.ts` — Config counts, MCP status parsing, and extension status helpers.
|
|
@@ -51,6 +52,7 @@ Tests live in `tests/`:
|
|
|
51
52
|
- `footer-layout.test.ts` — Four-group categorized layout, segment wrapping, and narrow-width budget.
|
|
52
53
|
- `footer-groups.test.ts` — Semantic Project, Session, Activity, and Usage segment ordering.
|
|
53
54
|
- `footer-segment-controls.test.ts` — Independent status controls and legacy-config migration.
|
|
55
|
+
- `footer-row-controls.test.ts` — Per-row command parsing, persistence, and configuration defaults.
|
|
54
56
|
- `model-switch-statusline.test.ts` — Model switch statusline integration.
|
|
55
57
|
- `model-usage-refresh.test.ts` — Model usage refresh integration.
|
|
56
58
|
- `token-switch-usage.test.ts` — Token Switch balance formatting.
|
|
@@ -67,10 +69,19 @@ In addition to automated tests, validate HUD behavior in a real Pi TUI across di
|
|
|
67
69
|
|
|
68
70
|
## Code style
|
|
69
71
|
|
|
70
|
-
The project
|
|
72
|
+
The project uses ESLint for code-quality checks and Prettier for deterministic formatting:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm run lint
|
|
76
|
+
npm run lint:fix
|
|
77
|
+
npm run format:check
|
|
78
|
+
npm run format
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Follow the configured conventions:
|
|
71
82
|
|
|
72
83
|
- Use TypeScript ESM and explicit types.
|
|
73
|
-
- Use
|
|
84
|
+
- Use tab indentation, double quotes, and semicolons.
|
|
74
85
|
- Keep extension lifecycle handling clear and release timers and UI state during `session_shutdown`.
|
|
75
86
|
- Do not edit content enclosed by `AUTO-GENERATED` markers manually; regenerate it from the source identified in the marker.
|
|
76
87
|
|
|
@@ -116,8 +127,7 @@ Before opening a pull request, confirm that:
|
|
|
116
127
|
|
|
117
128
|
- [ ] The change has a focused scope and contains no unrelated files.
|
|
118
129
|
- [ ] New behavior or bug fixes include appropriate tests.
|
|
119
|
-
- [ ] `npm
|
|
120
|
-
- [ ] `npm run typecheck` passes.
|
|
130
|
+
- [ ] `npm run verify` passes (format, types, lint, and tests).
|
|
121
131
|
- [ ] `npm run pack:check` passes and the package contains the expected files.
|
|
122
132
|
- [ ] Affected HUD behavior was verified in a real Pi TUI.
|
|
123
133
|
- [ ] Related documentation was synchronized from its source of truth.
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -70,6 +70,10 @@ HUD 脚注默认启用。运行 `/zentui` 打开交互式设置。
|
|
|
70
70
|
/zentui statusline enable
|
|
71
71
|
/zentui statusline disable
|
|
72
72
|
/zentui statusline toggle
|
|
73
|
+
/zentui row project toggle
|
|
74
|
+
/zentui row session disable
|
|
75
|
+
/zentui row activity enable
|
|
76
|
+
/zentui row usage toggle
|
|
73
77
|
/zentui format clear
|
|
74
78
|
/zentui format "$model · $context · $cost · $git_branch( $git_commit) · $session_duration"
|
|
75
79
|
/codex-status
|
|
@@ -121,6 +125,14 @@ HUD 脚注默认启用。运行 `/zentui` 打开交互式设置。
|
|
|
121
125
|
|
|
122
126
|
使用旧版聚合开关 `tokens`、`toolActivity` 或 `agentActivity` 的配置会自动迁移。仍可通过 `/zentui statusline enable|disable|toggle` 控制整个脚注。
|
|
123
127
|
|
|
128
|
+
默认四行布局中的每个语义行也可以通过命令单独控制:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
/zentui row <project|session|activity|usage> <enable|disable|toggle>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
行开关会持久化到 `footerRows`。使用自定义单行 `footerFormat` 时,这些开关不生效。
|
|
135
|
+
|
|
124
136
|
### 扩展状态
|
|
125
137
|
|
|
126
138
|
第三方扩展发布的状态可由 HUD 脚注显示。可为每个扩展状态键配置位置(`left`、`middle`、`right`、`off`)和颜色模式(`zentui`、`original`)。
|
|
@@ -164,11 +176,10 @@ npm test
|
|
|
164
176
|
npm run typecheck
|
|
165
177
|
```
|
|
166
178
|
|
|
167
|
-
|
|
179
|
+
验证格式、类型、Lint、测试与发布内容:
|
|
168
180
|
|
|
169
181
|
```bash
|
|
170
|
-
npm
|
|
171
|
-
npm run typecheck
|
|
182
|
+
npm run verify
|
|
172
183
|
npm run pack:check
|
|
173
184
|
```
|
|
174
185
|
|
package/docs/footer.md
CHANGED
|
@@ -13,6 +13,17 @@ Usage · 35%/200k · ↑ 4.2k · ↓ 1.1k · $ 0.030
|
|
|
13
13
|
|
|
14
14
|
The category column is sized from visible text so every separator aligns. When a row exceeds the terminal width, complete status segments move to an indented continuation line. Only a single segment that is wider than the available content area is truncated. Third-party extension statuses appear on the Activity group in configured `left`, `middle`, then `right` order.
|
|
15
15
|
|
|
16
|
+
Control each default-layout row independently:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
/zentui row project enable
|
|
20
|
+
/zentui row session disable
|
|
21
|
+
/zentui row activity toggle
|
|
22
|
+
/zentui row usage toggle
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The command shape is `/zentui row <project|session|activity|usage> <enable|disable|toggle>`. Values persist under `footerRows`; all four default to `true`. Row switches apply only to the default layout and do not alter a custom single-line `footerFormat`.
|
|
26
|
+
|
|
16
27
|
## Built-in status segments
|
|
17
28
|
|
|
18
29
|
| Key | Default | Group | Description |
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
saveExtensionStatusColorMode,
|
|
7
7
|
saveExtensionStatusPlacement,
|
|
8
8
|
saveFooterFormatPatch,
|
|
9
|
+
saveFooterRowsPatch,
|
|
9
10
|
saveFooterSegmentsPatch,
|
|
10
11
|
saveGitBranchPatch,
|
|
11
12
|
saveIconsModePatch,
|
|
@@ -39,6 +40,9 @@ export function registerSettingsController(pi: ExtensionAPI, deps: SettingsContr
|
|
|
39
40
|
updateConfig(saveUiFeaturesPatch(patch));
|
|
40
41
|
deps.applyConfiguredUi(ctx);
|
|
41
42
|
},
|
|
43
|
+
setFooterRows(patch) {
|
|
44
|
+
updateConfig(saveFooterRowsPatch(patch));
|
|
45
|
+
},
|
|
42
46
|
setFooterSegments(patch) {
|
|
43
47
|
updateConfig(saveFooterSegmentsPatch(patch));
|
|
44
48
|
deps.restartSessionTimer();
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ContextStyle,
|
|
16
16
|
type ExtensionStatusColorMode,
|
|
17
17
|
type ExtensionStatusPlacement,
|
|
18
|
+
type FooterRowsConfig,
|
|
18
19
|
type FooterSegmentsConfig,
|
|
19
20
|
type GitBranchConfig,
|
|
20
21
|
type GitBranchMaxLength,
|
|
@@ -64,20 +65,13 @@ type FeatureSettingId = keyof UiFeaturesConfig;
|
|
|
64
65
|
type FooterSegmentSettingId = keyof FooterSegmentsConfig;
|
|
65
66
|
type SettingsSection = (typeof settingsSections)[number];
|
|
66
67
|
type LayoutSettingId =
|
|
67
|
-
| "
|
|
68
|
-
| "separator"
|
|
69
|
-
| "pathDisplay"
|
|
70
|
-
| "pathDepth"
|
|
71
|
-
| "branchLength"
|
|
72
|
-
| "iconMode";
|
|
68
|
+
"contextStyle" | "separator" | "pathDisplay" | "pathDepth" | "branchLength" | "iconMode";
|
|
73
69
|
|
|
74
70
|
type SettingsCommandDeps = {
|
|
75
71
|
getConfig: () => PolishedTuiConfig;
|
|
76
72
|
setColorSources: (patch: Partial<ColorSourcesConfig>) => void;
|
|
77
|
-
setUiFeatures: (
|
|
78
|
-
|
|
79
|
-
ctx: ExtensionContext,
|
|
80
|
-
) => void;
|
|
73
|
+
setUiFeatures: (patch: Partial<UiFeaturesConfig>, ctx: ExtensionContext) => void;
|
|
74
|
+
setFooterRows: (patch: Partial<FooterRowsConfig>) => void;
|
|
81
75
|
setFooterSegments: (patch: Partial<FooterSegmentsConfig>) => void;
|
|
82
76
|
setFooterFormat: (value: string) => void;
|
|
83
77
|
setIconMode: (mode: IconMode) => void;
|
|
@@ -109,6 +103,13 @@ const featureSettingDescriptions: Record<FeatureSettingId, string> = {
|
|
|
109
103
|
statusLine: "Enable or disable Zentui's custom footer/status line.",
|
|
110
104
|
};
|
|
111
105
|
|
|
106
|
+
const footerRowLabels: Record<keyof FooterRowsConfig, string> = {
|
|
107
|
+
project: "Project row",
|
|
108
|
+
session: "Session row",
|
|
109
|
+
activity: "Activity row",
|
|
110
|
+
usage: "Usage row",
|
|
111
|
+
};
|
|
112
|
+
|
|
112
113
|
const footerSegmentSettingLabels: Record<FooterSegmentSettingId, string> = {
|
|
113
114
|
cwd: "Current directory",
|
|
114
115
|
gitBranch: "Git branch",
|
|
@@ -183,6 +184,11 @@ const directCommandSuggestions = [
|
|
|
183
184
|
"statusline enable",
|
|
184
185
|
"statusline disable",
|
|
185
186
|
"statusline toggle",
|
|
187
|
+
...Object.keys(footerRowLabels).flatMap((row) => [
|
|
188
|
+
`row ${row} enable`,
|
|
189
|
+
`row ${row} disable`,
|
|
190
|
+
`row ${row} toggle`,
|
|
191
|
+
]),
|
|
186
192
|
"format clear",
|
|
187
193
|
"format $cwd on $git_branch $fill $context",
|
|
188
194
|
"format $cwd( on $git_branch)($git_status)$fill($context)( | $cost)",
|
|
@@ -317,7 +323,28 @@ function footerSegmentPatch(
|
|
|
317
323
|
}
|
|
318
324
|
|
|
319
325
|
function usageText(): string {
|
|
320
|
-
return 'Usage: /zentui statusline [enable|disable|toggle] or /zentui format "<template>"';
|
|
326
|
+
return 'Usage: /zentui statusline [enable|disable|toggle], /zentui row [project|session|activity|usage] [enable|disable|toggle], or /zentui format "<template>"';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function isFooterRow(value: string): value is keyof FooterRowsConfig {
|
|
330
|
+
return value === "project" || value === "session" || value === "activity" || value === "usage";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function parseDirectRowCommand(
|
|
334
|
+
args: string,
|
|
335
|
+
config: PolishedTuiConfig,
|
|
336
|
+
): { row: keyof FooterRowsConfig; enabled: boolean } | undefined {
|
|
337
|
+
const normalized = args.trim().toLowerCase().replaceAll(/[_-]+/g, " ");
|
|
338
|
+
const [command, row, action, ...rest] = normalized.split(/\s+/g);
|
|
339
|
+
if (command !== "row" || !row || !isFooterRow(row) || !action || rest.length > 0) {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
if (action !== "enable" && action !== "disable" && action !== "toggle") return undefined;
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
row,
|
|
346
|
+
enabled: action === "toggle" ? !config.footerRows[row] : action === "enable",
|
|
347
|
+
};
|
|
321
348
|
}
|
|
322
349
|
|
|
323
350
|
function parseDirectFeatureCommand(
|
|
@@ -329,9 +356,8 @@ function parseDirectFeatureCommand(
|
|
|
329
356
|
|
|
330
357
|
const words = normalized.split(/\s+/g).filter(Boolean);
|
|
331
358
|
const hasWord = (value: string) => words.includes(value);
|
|
332
|
-
const feature =
|
|
333
|
-
? "statusLine"
|
|
334
|
-
: undefined;
|
|
359
|
+
const feature =
|
|
360
|
+
hasWord("footer") || hasWord("statusline") || hasWord("status") ? "statusLine" : undefined;
|
|
335
361
|
const action = hasWord("toggle")
|
|
336
362
|
? "toggle"
|
|
337
363
|
: hasWord("enable") || hasWord("enabled") || hasWord("on")
|
|
@@ -568,6 +594,24 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
568
594
|
return;
|
|
569
595
|
}
|
|
570
596
|
|
|
597
|
+
const directRowCommand = parseDirectRowCommand(args, deps.getConfig());
|
|
598
|
+
if (directRowCommand) {
|
|
599
|
+
try {
|
|
600
|
+
deps.setFooterRows({ [directRowCommand.row]: directRowCommand.enabled });
|
|
601
|
+
deps.requestRender();
|
|
602
|
+
if (ctx.hasUI) {
|
|
603
|
+
ctx.ui.notify(
|
|
604
|
+
`${footerRowLabels[directRowCommand.row]}: ${featureValue(directRowCommand.enabled)}`,
|
|
605
|
+
"info",
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
} catch (error) {
|
|
609
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
610
|
+
if (ctx.hasUI) ctx.ui.notify(`Could not update Zentui settings: ${message}`, "error");
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
571
615
|
const directCommand = parseDirectFeatureCommand(args, deps.getConfig());
|
|
572
616
|
if (directCommand) {
|
|
573
617
|
try {
|
|
@@ -35,11 +35,12 @@ function getPiAgentDir(): string {
|
|
|
35
35
|
function resolveAgentDir(options: ConfigCountOptions): string {
|
|
36
36
|
let agentDir = "";
|
|
37
37
|
try {
|
|
38
|
-
agentDir =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
agentDir =
|
|
39
|
+
typeof options.agentDir === "string"
|
|
40
|
+
? options.agentDir
|
|
41
|
+
: options.getAgentDir
|
|
42
|
+
? options.getAgentDir()
|
|
43
|
+
: getPiAgentDir();
|
|
43
44
|
} catch {
|
|
44
45
|
return "";
|
|
45
46
|
}
|
|
@@ -19,6 +19,7 @@ export type {
|
|
|
19
19
|
ExtensionStatusColorMode,
|
|
20
20
|
ExtensionStatusesConfig,
|
|
21
21
|
ExtensionStatusPlacement,
|
|
22
|
+
FooterRowsConfig,
|
|
22
23
|
FooterSegmentsConfig,
|
|
23
24
|
GitBranchConfig,
|
|
24
25
|
GitBranchMaxLength,
|
|
@@ -56,6 +57,7 @@ export {
|
|
|
56
57
|
saveExtensionStatusColorMode,
|
|
57
58
|
saveExtensionStatusPlacement,
|
|
58
59
|
saveFooterFormatPatch,
|
|
60
|
+
saveFooterRowsPatch,
|
|
59
61
|
saveFooterSegmentsPatch,
|
|
60
62
|
saveGitBranchPatch,
|
|
61
63
|
saveIconsModePatch,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type ExtensionStatusColorMode,
|
|
26
26
|
type ExtensionStatusesConfig,
|
|
27
27
|
type ExtensionStatusPlacement,
|
|
28
|
+
type FooterRowsConfig,
|
|
28
29
|
type FooterSegmentsConfig,
|
|
29
30
|
type GitBranchConfig,
|
|
30
31
|
type GitBranchMaxLength,
|
|
@@ -218,6 +219,18 @@ function normalizeUiFeatures(record: Record<string, unknown>): UiFeaturesConfig
|
|
|
218
219
|
return { statusLine: booleanValue(record, "statusLine") };
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
function normalizeFooterRows(record: Record<string, unknown>): FooterRowsConfig {
|
|
223
|
+
return {
|
|
224
|
+
project:
|
|
225
|
+
typeof record.project === "boolean" ? record.project : defaultConfig.footerRows.project,
|
|
226
|
+
session:
|
|
227
|
+
typeof record.session === "boolean" ? record.session : defaultConfig.footerRows.session,
|
|
228
|
+
activity:
|
|
229
|
+
typeof record.activity === "boolean" ? record.activity : defaultConfig.footerRows.activity,
|
|
230
|
+
usage: typeof record.usage === "boolean" ? record.usage : defaultConfig.footerRows.usage,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
221
234
|
function normalizeFooterSegments(record: Record<string, unknown>): FooterSegmentsConfig {
|
|
222
235
|
return {
|
|
223
236
|
cwd: footerSegmentValue(record, "cwd"),
|
|
@@ -335,6 +348,9 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
335
348
|
const features = isRecord(config.features)
|
|
336
349
|
? normalizeUiFeatures(config.features as Record<string, unknown>)
|
|
337
350
|
: defaultConfig.features;
|
|
351
|
+
const footerRows = isRecord(config.footerRows)
|
|
352
|
+
? normalizeFooterRows(config.footerRows as Record<string, unknown>)
|
|
353
|
+
: defaultConfig.footerRows;
|
|
338
354
|
const footerSegments = isRecord(config.footerSegments)
|
|
339
355
|
? normalizeFooterSegments(config.footerSegments as Record<string, unknown>)
|
|
340
356
|
: defaultConfig.footerSegments;
|
|
@@ -363,6 +379,7 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
363
379
|
},
|
|
364
380
|
colorSources: { ...colorSources },
|
|
365
381
|
features: { ...features },
|
|
382
|
+
footerRows: { ...footerRows },
|
|
366
383
|
footerSegments: { ...footerSegments },
|
|
367
384
|
gitCommit,
|
|
368
385
|
gitMetrics,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type ContextThresholds,
|
|
25
25
|
type ExtensionStatusColorMode,
|
|
26
26
|
type ExtensionStatusPlacement,
|
|
27
|
+
type FooterRowsConfig,
|
|
27
28
|
type FooterSegmentsConfig,
|
|
28
29
|
type GitBranchConfig,
|
|
29
30
|
type IconMode,
|
|
@@ -41,6 +42,10 @@ function isUiFeatureKey(value: string): value is keyof UiFeaturesConfig {
|
|
|
41
42
|
return value === "statusLine";
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function isFooterRowKey(value: string): value is keyof FooterRowsConfig {
|
|
46
|
+
return value === "project" || value === "session" || value === "activity" || value === "usage";
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig {
|
|
45
50
|
return (
|
|
46
51
|
value === "cwd" ||
|
|
@@ -94,6 +99,15 @@ function validUiFeatureEntries(record: Record<string, unknown>): Partial<UiFeatu
|
|
|
94
99
|
) as Partial<UiFeaturesConfig>;
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
function validFooterRowEntries(record: Record<string, unknown>): Partial<FooterRowsConfig> {
|
|
103
|
+
return Object.fromEntries(
|
|
104
|
+
Object.entries(record).filter((entry): entry is [keyof FooterRowsConfig, boolean] => {
|
|
105
|
+
const [key, value] = entry;
|
|
106
|
+
return isFooterRowKey(key) && typeof value === "boolean";
|
|
107
|
+
}),
|
|
108
|
+
) as Partial<FooterRowsConfig>;
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
function validFooterSegmentEntries(record: Record<string, unknown>): Partial<FooterSegmentsConfig> {
|
|
98
112
|
return Object.fromEntries(
|
|
99
113
|
Object.entries(record).filter((entry): entry is [keyof FooterSegmentsConfig, boolean] => {
|
|
@@ -133,6 +147,21 @@ export function saveUiFeaturesPatch(
|
|
|
133
147
|
});
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
export function saveFooterRowsPatch(
|
|
151
|
+
patch: Partial<FooterRowsConfig>,
|
|
152
|
+
path = configPath,
|
|
153
|
+
): PolishedTuiConfig {
|
|
154
|
+
return mutateConfig(path, (record) => {
|
|
155
|
+
const existing = isRecord(record.footerRows)
|
|
156
|
+
? { ...(record.footerRows as Record<string, unknown>) }
|
|
157
|
+
: {};
|
|
158
|
+
record.footerRows = {
|
|
159
|
+
...existing,
|
|
160
|
+
...validFooterRowEntries(patch),
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
136
165
|
export function saveFooterSegmentsPatch(
|
|
137
166
|
patch: Partial<FooterSegmentsConfig>,
|
|
138
167
|
path = configPath,
|
|
@@ -43,6 +43,13 @@ export type UiFeaturesConfig = {
|
|
|
43
43
|
statusLine: boolean;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
export type FooterRowsConfig = {
|
|
47
|
+
project: boolean;
|
|
48
|
+
session: boolean;
|
|
49
|
+
activity: boolean;
|
|
50
|
+
usage: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
46
53
|
export type FooterSegmentsConfig = {
|
|
47
54
|
cwd: boolean;
|
|
48
55
|
gitBranch: boolean;
|
|
@@ -142,6 +149,7 @@ export type PolishedTuiConfig = {
|
|
|
142
149
|
};
|
|
143
150
|
colorSources: ColorSourcesConfig;
|
|
144
151
|
features: UiFeaturesConfig;
|
|
152
|
+
footerRows: FooterRowsConfig;
|
|
145
153
|
footerSegments: FooterSegmentsConfig;
|
|
146
154
|
gitCommit: GitCommitConfig;
|
|
147
155
|
gitMetrics: GitMetricsConfig;
|
|
@@ -258,6 +266,12 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
258
266
|
features: {
|
|
259
267
|
statusLine: true,
|
|
260
268
|
},
|
|
269
|
+
footerRows: {
|
|
270
|
+
project: true,
|
|
271
|
+
session: true,
|
|
272
|
+
activity: true,
|
|
273
|
+
usage: true,
|
|
274
|
+
},
|
|
261
275
|
footerSegments: {
|
|
262
276
|
cwd: true,
|
|
263
277
|
gitBranch: true,
|
|
@@ -184,11 +184,7 @@ export function installFooter(
|
|
|
184
184
|
return gitStateBlock;
|
|
185
185
|
case "runtime": {
|
|
186
186
|
if (!safeRuntime) return "";
|
|
187
|
-
const symbol = resolveRuntimeSymbol(
|
|
188
|
-
safeRuntime.name,
|
|
189
|
-
safeRuntime.symbol,
|
|
190
|
-
iconMode,
|
|
191
|
-
);
|
|
187
|
+
const symbol = resolveRuntimeSymbol(safeRuntime.name, safeRuntime.symbol, iconMode);
|
|
192
188
|
const label = safeRuntime.version ? `${symbol} ${safeRuntime.version}` : symbol;
|
|
193
189
|
return renderStyleForSource(theme, colorSource, safeRuntime.style, label);
|
|
194
190
|
}
|
|
@@ -263,12 +259,7 @@ export function installFooter(
|
|
|
263
259
|
);
|
|
264
260
|
case "git_tag":
|
|
265
261
|
return config.gitCommit.showTag && safeCommit?.tag
|
|
266
|
-
? renderStyleForSource(
|
|
267
|
-
theme,
|
|
268
|
-
colorSource,
|
|
269
|
-
config.colors.gitCommit,
|
|
270
|
-
safeCommit.tag,
|
|
271
|
-
)
|
|
262
|
+
? renderStyleForSource(theme, colorSource, config.colors.gitCommit, safeCommit.tag)
|
|
272
263
|
: "";
|
|
273
264
|
case "git_metrics":
|
|
274
265
|
return formatGitMetricsSegment(
|
|
@@ -533,7 +524,12 @@ export function installFooter(
|
|
|
533
524
|
time: timeSegment,
|
|
534
525
|
},
|
|
535
526
|
});
|
|
536
|
-
const contents = composeCategorizedFooterRows(
|
|
527
|
+
const contents = composeCategorizedFooterRows(
|
|
528
|
+
groups,
|
|
529
|
+
separator,
|
|
530
|
+
innerWidth,
|
|
531
|
+
config.footerRows,
|
|
532
|
+
);
|
|
537
533
|
return contents.map(frame);
|
|
538
534
|
},
|
|
539
535
|
};
|
|
@@ -7,6 +7,14 @@ export type CategorizedFooterRows = {
|
|
|
7
7
|
usage: string[];
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
export type CategorizedFooterRowVisibility = Record<keyof CategorizedFooterRows, boolean>;
|
|
11
|
+
|
|
12
|
+
const DEFAULT_ROW_VISIBILITY: CategorizedFooterRowVisibility = {
|
|
13
|
+
project: true,
|
|
14
|
+
session: true,
|
|
15
|
+
activity: true,
|
|
16
|
+
usage: true,
|
|
17
|
+
};
|
|
10
18
|
const MIN_WRAPPED_CONTENT_WIDTH = 4;
|
|
11
19
|
|
|
12
20
|
function joinStatusTexts(statusTexts: string[], separator: string): string {
|
|
@@ -45,7 +53,11 @@ function prependStatusArea(base: string, statusText: string, separator: string):
|
|
|
45
53
|
return `${statusText}${separator}${base}`;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
export function composeBuiltInFooterContent(
|
|
56
|
+
export function composeBuiltInFooterContent(
|
|
57
|
+
left: string,
|
|
58
|
+
right: string,
|
|
59
|
+
innerWidth: number,
|
|
60
|
+
): string {
|
|
49
61
|
const leftWidth = visibleWidth(left);
|
|
50
62
|
const rightWidth = visibleWidth(right);
|
|
51
63
|
if (!right) return truncateToWidth(left, innerWidth, "");
|
|
@@ -69,17 +81,11 @@ export function composeBuiltInFooterContent(left: string, right: string, innerWi
|
|
|
69
81
|
let fittedLeft = truncateToWidth(left, leftBudget, "");
|
|
70
82
|
let fittedRight = truncateToWidth(right, rightBudget, "");
|
|
71
83
|
for (let pass = 0; pass < 2; pass += 1) {
|
|
72
|
-
let spare = Math.max(
|
|
73
|
-
0,
|
|
74
|
-
available - visibleWidth(fittedLeft) - visibleWidth(fittedRight),
|
|
75
|
-
);
|
|
84
|
+
let spare = Math.max(0, available - visibleWidth(fittedLeft) - visibleWidth(fittedRight));
|
|
76
85
|
if (spare === 0) break;
|
|
77
86
|
if (visibleWidth(fittedLeft) < leftWidth) {
|
|
78
87
|
fittedLeft = truncateToWidth(left, visibleWidth(fittedLeft) + spare, "");
|
|
79
|
-
spare = Math.max(
|
|
80
|
-
0,
|
|
81
|
-
available - visibleWidth(fittedLeft) - visibleWidth(fittedRight),
|
|
82
|
-
);
|
|
88
|
+
spare = Math.max(0, available - visibleWidth(fittedLeft) - visibleWidth(fittedRight));
|
|
83
89
|
}
|
|
84
90
|
if (spare > 0 && visibleWidth(fittedRight) < rightWidth) {
|
|
85
91
|
fittedRight = truncateToWidth(right, visibleWidth(fittedRight) + spare, "");
|
|
@@ -231,10 +237,21 @@ export function composeCategorizedFooterRows(
|
|
|
231
237
|
rows: CategorizedFooterRows,
|
|
232
238
|
separator: string,
|
|
233
239
|
innerWidth: number,
|
|
240
|
+
visibility: CategorizedFooterRowVisibility = DEFAULT_ROW_VISIBILITY,
|
|
234
241
|
): string[] {
|
|
235
|
-
const groups = [
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
const groups = [
|
|
243
|
+
{ id: "project", parts: rows.project },
|
|
244
|
+
{ id: "session", parts: rows.session },
|
|
245
|
+
{ id: "activity", parts: rows.activity },
|
|
246
|
+
{ id: "usage", parts: rows.usage },
|
|
247
|
+
] as const;
|
|
248
|
+
const visibleGroups = groups.filter(({ id }) => visibility[id]);
|
|
249
|
+
if (visibleGroups.length === 0) return [];
|
|
250
|
+
|
|
251
|
+
const categoryWidth = Math.max(
|
|
252
|
+
...visibleGroups.map(({ parts }) => visibleWidth(categoryLabel(parts))),
|
|
253
|
+
);
|
|
254
|
+
return visibleGroups.flatMap(({ parts }) =>
|
|
238
255
|
composeWrappedCategorizedRow(parts, separator, innerWidth, categoryWidth),
|
|
239
256
|
);
|
|
240
257
|
}
|
|
@@ -27,12 +27,9 @@ export function safeStatusText(value: string | null | undefined, maxLength = 256
|
|
|
27
27
|
export type SanitizedFooterInputs = {
|
|
28
28
|
safeCwd: string;
|
|
29
29
|
safeRuntime:
|
|
30
|
-
|
|
31
|
-
| undefined;
|
|
30
|
+
{ name: string; symbol: string; style: string; version: string | undefined } | undefined;
|
|
32
31
|
safePackageVersion: { ecosystem: string; version: string } | undefined;
|
|
33
|
-
safeCommit:
|
|
34
|
-
| { oid: string | null; detached: boolean; tag: string | null }
|
|
35
|
-
| undefined;
|
|
32
|
+
safeCommit: { oid: string | null; detached: boolean; tag: string | null } | undefined;
|
|
36
33
|
};
|
|
37
34
|
|
|
38
35
|
/** Sanitize the state/ctx fields that flow into the terminal so a malicious
|
|
@@ -178,13 +175,19 @@ export function buildTelemetryLabels(
|
|
|
178
175
|
const usageCategoryLabel = statusStyle(config.colors.contextNormal, "Usage");
|
|
179
176
|
|
|
180
177
|
const sessionName = safeStatusText(state.telemetry.sessionName, 256);
|
|
181
|
-
const sessionNameLabel = statusStyle(
|
|
178
|
+
const sessionNameLabel = statusStyle(
|
|
179
|
+
config.colors.gitBranch,
|
|
180
|
+
sessionName ? `◈ ${sessionName}` : "",
|
|
181
|
+
);
|
|
182
182
|
|
|
183
183
|
const providerId = safeStatusText(ctx.model?.provider, 128);
|
|
184
184
|
const modelId = safeStatusText(ctx.model?.id, 256);
|
|
185
185
|
const modelText = providerId && modelId ? `${providerId}/${modelId}` : modelId || providerId;
|
|
186
186
|
const modelValueLabel = statusStyle(config.colors.runtimePrefix, modelText);
|
|
187
|
-
const modelStatusLabel = statusStyle(
|
|
187
|
+
const modelStatusLabel = statusStyle(
|
|
188
|
+
config.colors.runtimePrefix,
|
|
189
|
+
modelText ? `λ ${modelText}` : "",
|
|
190
|
+
);
|
|
188
191
|
|
|
189
192
|
const thinkingLabel = state.telemetry.modelSupportsReasoning
|
|
190
193
|
? statusStyle(config.colors.extensionStatus, `Thinking level: ${state.telemetry.thinkingLevel}`)
|
package/extensions/hud/index.ts
CHANGED
|
@@ -99,7 +99,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
99
99
|
if (
|
|
100
100
|
!currentConfig.features.statusLine ||
|
|
101
101
|
!(needsWallClock || needsDuration || needsActivityClock)
|
|
102
|
-
)
|
|
102
|
+
)
|
|
103
|
+
return;
|
|
103
104
|
|
|
104
105
|
const timer = setInterval(() => {
|
|
105
106
|
if (!sessionLifecycle.isCurrent()) return;
|
|
@@ -7,11 +7,7 @@ const execFileAsync = promisify(execFile);
|
|
|
7
7
|
const GIT_COMMAND_TIMEOUT_MS = 2_000;
|
|
8
8
|
|
|
9
9
|
export type GitOperationState =
|
|
10
|
-
| "
|
|
11
|
-
| "MERGING"
|
|
12
|
-
| "CHERRY-PICKING"
|
|
13
|
-
| "REVERTING"
|
|
14
|
-
| "BISECTING";
|
|
10
|
+
"REBASING" | "MERGING" | "CHERRY-PICKING" | "REVERTING" | "BISECTING";
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* Starship `git_commit`-style info derived from the existing porcelain probe.
|
|
@@ -57,9 +53,7 @@ export type GitStatusSummary = {
|
|
|
57
53
|
};
|
|
58
54
|
|
|
59
55
|
export type GitReadResult =
|
|
60
|
-
|
|
61
|
-
| { kind: "not_a_repo" }
|
|
62
|
-
| { kind: "error" };
|
|
56
|
+
{ kind: "ok"; status: GitStatusSummary } | { kind: "not_a_repo" } | { kind: "error" };
|
|
63
57
|
|
|
64
58
|
export type GitStatePaths = {
|
|
65
59
|
rebaseMerge?: string;
|
|
@@ -18,7 +18,8 @@ export function parseMcpStatus(text: string | undefined): McpStatus | undefined
|
|
|
18
18
|
const total = Number(enabledMatch ? enabledMatch[1] : legacyMatch![2]);
|
|
19
19
|
const disabled = enabledMatch?.[3] === undefined ? 0 : Number(enabledMatch[3]);
|
|
20
20
|
if (![connected, total, disabled].every(Number.isSafeInteger)) return undefined;
|
|
21
|
-
if (connected < 0 || total < 0 || connected > total || total > 10_000 || disabled > 10_000)
|
|
21
|
+
if (connected < 0 || total < 0 || connected > total || total > 10_000 || disabled > 10_000)
|
|
22
|
+
return undefined;
|
|
22
23
|
return { connected, total };
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -41,8 +41,7 @@ export type PackageVersionResult = {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
export type PackageVersionReadResult =
|
|
44
|
-
|
|
45
|
-
| { kind: "error" };
|
|
44
|
+
{ kind: "ok"; result: PackageVersionResult | null } | { kind: "error" };
|
|
46
45
|
|
|
47
46
|
/**
|
|
48
47
|
* A single manifest source. `kind` selects the lookup mode:
|
|
@@ -103,9 +103,9 @@ function matchesDetection(
|
|
|
103
103
|
if (spec.excludedFiles && hasAnyFile(cwd, spec.excludedFiles)) return false;
|
|
104
104
|
return Boolean(
|
|
105
105
|
(spec.files && hasAnyFile(cwd, spec.files)) ||
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
(spec.folders && hasAnyFolder(cwd, spec.folders)) ||
|
|
107
|
+
(spec.extensions && hasAnyExtension(entries, spec.extensions)) ||
|
|
108
|
+
spec.env?.(env),
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -119,7 +119,11 @@ export class SkillActivityTracker {
|
|
|
119
119
|
}
|
|
120
120
|
continue;
|
|
121
121
|
}
|
|
122
|
-
if (
|
|
122
|
+
if (
|
|
123
|
+
message.role !== "toolResult" ||
|
|
124
|
+
message.toolName !== "read" ||
|
|
125
|
+
message.isError === true
|
|
126
|
+
) {
|
|
123
127
|
continue;
|
|
124
128
|
}
|
|
125
129
|
const id = typeof message.toolCallId === "string" ? message.toolCallId : undefined;
|
|
@@ -46,7 +46,7 @@ export type TelemetryUpdate =
|
|
|
46
46
|
name: string;
|
|
47
47
|
args?: Record<string, unknown> | null;
|
|
48
48
|
at: number;
|
|
49
|
-
|
|
49
|
+
}
|
|
50
50
|
| { type: "tool-result"; toolCallId: string; isError: boolean; at: number }
|
|
51
51
|
| { type: "agent-start"; at: number }
|
|
52
52
|
| { type: "agent-end"; at: number };
|
|
@@ -80,7 +80,9 @@ function normalizeThinkingLevel(value: string | undefined): string {
|
|
|
80
80
|
|
|
81
81
|
export function createTelemetryState(initial: TelemetryInitialValues = {}): TelemetryState {
|
|
82
82
|
return {
|
|
83
|
-
sessionName: initial.sessionName
|
|
83
|
+
sessionName: initial.sessionName
|
|
84
|
+
? sanitizeExternalText(initial.sessionName) || undefined
|
|
85
|
+
: undefined,
|
|
84
86
|
turnIndex: initial.turnIndex ?? 0,
|
|
85
87
|
thinkingLevel: normalizeThinkingLevel(initial.thinkingLevel),
|
|
86
88
|
modelSupportsReasoning: initial.modelSupportsReasoning ?? false,
|
|
@@ -118,11 +120,12 @@ export function findToolTarget(state: TelemetryState, toolCallId: string): strin
|
|
|
118
120
|
|
|
119
121
|
function getToolTarget(args: Record<string, unknown> | null | undefined): string | undefined {
|
|
120
122
|
if (!args) return undefined;
|
|
121
|
-
const value =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
const value =
|
|
124
|
+
typeof args.path === "string"
|
|
125
|
+
? args.path
|
|
126
|
+
: typeof args.filePath === "string"
|
|
127
|
+
? args.filePath
|
|
128
|
+
: undefined;
|
|
126
129
|
return value === undefined ? undefined : sanitizeToolTarget(value);
|
|
127
130
|
}
|
|
128
131
|
|
|
@@ -205,7 +208,10 @@ function finishLatestAgentRun(state: TelemetryState, at: number): TelemetryState
|
|
|
205
208
|
return { ...state, agentRuns };
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
export function updateTelemetryState(
|
|
211
|
+
export function updateTelemetryState(
|
|
212
|
+
state: TelemetryState,
|
|
213
|
+
update: TelemetryUpdate,
|
|
214
|
+
): TelemetryState {
|
|
209
215
|
switch (update.type) {
|
|
210
216
|
case "metadata":
|
|
211
217
|
return updateMetadata(state, update);
|
|
@@ -63,7 +63,8 @@ class CodexAppServerClient {
|
|
|
63
63
|
|
|
64
64
|
start(): Promise<void> {
|
|
65
65
|
if (this.startPromise) return this.startPromise;
|
|
66
|
-
if (this.signal?.aborted)
|
|
66
|
+
if (this.signal?.aborted)
|
|
67
|
+
return Promise.reject(new Error("Codex app-server query was cancelled."));
|
|
67
68
|
this.signal?.addEventListener("abort", this.abortHandler, { once: true });
|
|
68
69
|
|
|
69
70
|
this.startPromise = new Promise((resolve, reject) => {
|
|
@@ -100,7 +101,10 @@ class CodexAppServerClient {
|
|
|
100
101
|
|
|
101
102
|
child.stderr.setEncoding("utf8");
|
|
102
103
|
child.stderr.on("data", (chunk: string) => {
|
|
103
|
-
this.stderr = truncateEnd(
|
|
104
|
+
this.stderr = truncateEnd(
|
|
105
|
+
`${this.stderr}${chunk.slice(0, MAX_ERROR_BODY_CHARS)}`,
|
|
106
|
+
MAX_ERROR_BODY_CHARS,
|
|
107
|
+
);
|
|
104
108
|
});
|
|
105
109
|
child.stderr.on("error", () => undefined);
|
|
106
110
|
child.stdin.on("error", (error) => this.rejectAll(error));
|
|
@@ -205,10 +205,7 @@ function isPrimaryCodexSnapshot(snapshot: NormalizedRateLimitSnapshot): boolean
|
|
|
205
205
|
);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
function formatWindowLine(
|
|
209
|
-
window: NormalizedRateLimitWindow,
|
|
210
|
-
fallback: "5h" | "weekly",
|
|
211
|
-
): string {
|
|
208
|
+
function formatWindowLine(window: NormalizedRateLimitWindow, fallback: "5h" | "weekly"): string {
|
|
212
209
|
const label = `${formatWindowLabel(window, fallback, false)} limit:`;
|
|
213
210
|
return ` ${label.padEnd(LIMIT_VALUE_COLUMN)}${formatWindow(window)}`;
|
|
214
211
|
}
|
|
@@ -8,18 +8,18 @@ const SECRET_QUERY_PATTERN =
|
|
|
8
8
|
/([?&](?:access_token|refresh_token|id_token|session_token|client_secret|api_key|password)=)[^&#\s]+/gi;
|
|
9
9
|
|
|
10
10
|
export function sanitizeCodexText(value: unknown, maxChars = 600): string {
|
|
11
|
-
let text =
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
let text =
|
|
12
|
+
typeof value === "string" ? value : value instanceof Error ? value.message : String(value);
|
|
13
|
+
text = stripVTControlCharacters(text.slice(0, Math.max(maxChars * 4, maxChars))).replace(
|
|
14
|
+
/[\u0000-\u001f\u007f-\u009f]/g,
|
|
15
|
+
"",
|
|
16
|
+
);
|
|
14
17
|
text = text
|
|
15
18
|
.replace(BEARER_PATTERN, "Bearer <redacted>")
|
|
16
19
|
.replace(SECRET_FIELD_PATTERN, "$1<redacted>")
|
|
17
20
|
.replace(COOKIE_PATTERN, "$1<redacted>")
|
|
18
21
|
.replace(SECRET_QUERY_PATTERN, "$1<redacted>");
|
|
19
|
-
return text
|
|
20
|
-
.replace(/\s+/g, " ")
|
|
21
|
-
.trim()
|
|
22
|
-
.slice(0, maxChars);
|
|
22
|
+
return text.replace(/\s+/g, " ").trim().slice(0, maxChars);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function safeCodexErrorMessage(error: unknown): string {
|
|
@@ -17,8 +17,7 @@ export type CachedReport = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export type QueryUsageResult =
|
|
20
|
-
|
|
21
|
-
| { ok: false; errors: UsageQueryError[] };
|
|
20
|
+
{ ok: true; report: CodexUsageReport } | { ok: false; errors: UsageQueryError[] };
|
|
22
21
|
|
|
23
22
|
export type UsageQueryError = {
|
|
24
23
|
source: UsageSource;
|
|
@@ -13,9 +13,7 @@ type QueryTokenSwitchBalanceOptions = {
|
|
|
13
13
|
signal?: AbortSignal;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export function isTokenSwitchModel(
|
|
17
|
-
model: Pick<TokenSwitchModel, "provider"> | undefined,
|
|
18
|
-
): boolean {
|
|
16
|
+
export function isTokenSwitchModel(model: Pick<TokenSwitchModel, "provider"> | undefined): boolean {
|
|
19
17
|
return model?.provider === TOKEN_SWITCH_PROVIDER_ID;
|
|
20
18
|
}
|
|
21
19
|
|
|
@@ -92,10 +90,7 @@ export async function queryTokenSwitchBalance({
|
|
|
92
90
|
};
|
|
93
91
|
|
|
94
92
|
try {
|
|
95
|
-
const subscriptionPayload = await fetchPayload(
|
|
96
|
-
TOKEN_SWITCH_SUBSCRIPTION_URL,
|
|
97
|
-
"subscription",
|
|
98
|
-
);
|
|
93
|
+
const subscriptionPayload = await fetchPayload(TOKEN_SWITCH_SUBSCRIPTION_URL, "subscription");
|
|
99
94
|
const usagePayload = await fetchPayload(TOKEN_SWITCH_USAGE_URL, "usage");
|
|
100
95
|
return parseTokenSwitchBalance(subscriptionPayload, usagePayload);
|
|
101
96
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rinco-pi-hud",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A dynamic HUD footer for Pi, ported from Sakura Zentui Footer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"test": "vitest run",
|
|
31
31
|
"test:watch": "vitest",
|
|
32
32
|
"typecheck": "tsc --noEmit",
|
|
33
|
-
"lint": "
|
|
34
|
-
"lint:fix": "
|
|
35
|
-
"format:check": "
|
|
36
|
-
"format": "
|
|
37
|
-
"verify": "npm run typecheck && npm run lint && npm run test",
|
|
33
|
+
"lint": "eslint extensions tests",
|
|
34
|
+
"lint:fix": "eslint extensions tests --fix",
|
|
35
|
+
"format:check": "prettier --check \"extensions/**/*.ts\" \"tests/**/*.ts\" \"eslint.config.js\" \"prettier.config.js\"",
|
|
36
|
+
"format": "prettier --write \"extensions/**/*.ts\" \"tests/**/*.ts\" \"eslint.config.js\" \"prettier.config.js\"",
|
|
37
|
+
"verify": "npm run format:check && npm run typecheck && npm run lint && npm run test",
|
|
38
38
|
"pack:check": "npm pack --dry-run"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -47,10 +47,14 @@
|
|
|
47
47
|
]
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@biomejs/biome": "^2.5.7",
|
|
51
50
|
"@earendil-works/pi-ai": "^0.83.0",
|
|
51
|
+
"@eslint/js": "^10.0.1",
|
|
52
52
|
"@types/node": "^24.12.4",
|
|
53
|
+
"eslint": "^10.8.1",
|
|
54
|
+
"eslint-config-prettier": "^10.1.8",
|
|
55
|
+
"prettier": "^3.9.6",
|
|
53
56
|
"typescript": "^5.9.3",
|
|
57
|
+
"typescript-eslint": "^8.67.0",
|
|
54
58
|
"vitest": "^4.1.9"
|
|
55
59
|
}
|
|
56
60
|
}
|