pi-zentui 0.3.3 → 0.5.0
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 +88 -8
- package/extensions/zentui/config.ts +163 -68
- package/extensions/zentui/footer-format.ts +279 -0
- package/extensions/zentui/footer.ts +158 -14
- package/extensions/zentui/format.ts +156 -13
- package/extensions/zentui/git.ts +140 -4
- package/extensions/zentui/icons.ts +225 -0
- package/extensions/zentui/index.ts +48 -11
- package/extensions/zentui/project-state.ts +44 -0
- package/extensions/zentui/runtime.ts +64 -11
- package/extensions/zentui/settings-command.ts +110 -3
- package/extensions/zentui/style.ts +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,8 +21,10 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
21
21
|
- `on branch` — git branch with icon
|
|
22
22
|
- `[!?↑]` — git status indicators (modified, untracked, ahead/behind, stashed, etc.)
|
|
23
23
|
- `via v5.5.0` — runtime detection with version and Starship-style Nerd Font runtime/language modules
|
|
24
|
+
- Optional segments (off by default): `user@host`, current time, OS icon, and session duration
|
|
24
25
|
- Right side shows context usage, token counts, and cost
|
|
25
26
|
- Built-in footer segments can be shown or hidden individually from `/zentui`
|
|
27
|
+
- Fully custom Starship-style layout via a `footerFormat` template string — see [Footer Format Template](#footer-format-template)
|
|
26
28
|
- Third-party Pi extension statuses from `ctx.ui.setStatus()` can be shown on the left,
|
|
27
29
|
middle, or right side, or hidden per status key from `/zentui`
|
|
28
30
|
|
|
@@ -127,7 +129,7 @@ pi install git:github.com/lmilojevicc/pi-zentui
|
|
|
127
129
|
|
|
128
130
|
User config lives at `~/.pi/agent/zentui.json`. The file is optional: missing or invalid known values fall back to Zentui defaults, unknown keys are ignored at runtime, and `/zentui` can patch color-source settings, UI feature toggles, built-in footer segment visibility, and active third-party status placements.
|
|
129
131
|
|
|
130
|
-
The interactive `/zentui` menu is split into
|
|
132
|
+
The interactive `/zentui` menu is split into five sections. Use `Tab` and `Shift+Tab` to switch between `Coloring`, `Features`, `Layout`, `Built-in segments`, and `Extension segments`.
|
|
131
133
|
|
|
132
134
|
Useful slash-command shortcuts:
|
|
133
135
|
|
|
@@ -141,6 +143,8 @@ Useful slash-command shortcuts:
|
|
|
141
143
|
/zentui copy-friendly enable
|
|
142
144
|
/zentui copy-friendly disable
|
|
143
145
|
/zentui copy-friendly toggle
|
|
146
|
+
/zentui format "$cwd on branch $git_branch$git_status using $runtime $fill $context"
|
|
147
|
+
/zentui format clear
|
|
144
148
|
```
|
|
145
149
|
|
|
146
150
|
Default config values — copy this and change any value you want:
|
|
@@ -148,7 +152,14 @@ Default config values — copy this and change any value you want:
|
|
|
148
152
|
```json
|
|
149
153
|
{
|
|
150
154
|
"projectRefreshIntervalMs": 30000,
|
|
155
|
+
"footerFormat": "",
|
|
156
|
+
"contextStyle": "text",
|
|
157
|
+
"contextThresholds": {
|
|
158
|
+
"warning": 70,
|
|
159
|
+
"error": 90
|
|
160
|
+
},
|
|
151
161
|
"icons": {
|
|
162
|
+
"mode": "auto",
|
|
152
163
|
"cwd": "",
|
|
153
164
|
"git": "",
|
|
154
165
|
"ahead": "↑",
|
|
@@ -164,7 +175,10 @@ Default config values — copy this and change any value you want:
|
|
|
164
175
|
"typechanged": "T",
|
|
165
176
|
"cacheHit": "",
|
|
166
177
|
"editorPrompt": "",
|
|
167
|
-
"rail": "│"
|
|
178
|
+
"rail": "│",
|
|
179
|
+
"username": "",
|
|
180
|
+
"time": "",
|
|
181
|
+
"os": ""
|
|
168
182
|
},
|
|
169
183
|
"colors": {
|
|
170
184
|
"cwd": "bold cyan",
|
|
@@ -178,6 +192,10 @@ Default config values — copy this and change any value you want:
|
|
|
178
192
|
"extensionStatus": "bright-black",
|
|
179
193
|
"separator": "bright-black",
|
|
180
194
|
"runtimePrefix": "",
|
|
195
|
+
"sessionDuration": "yellow",
|
|
196
|
+
"username": "bold yellow",
|
|
197
|
+
"time": "bold yellow",
|
|
198
|
+
"os": "bold white",
|
|
181
199
|
"editorAccent": "accent",
|
|
182
200
|
"editorPrompt": "accent",
|
|
183
201
|
"editorBorder": "borderMuted",
|
|
@@ -204,10 +222,15 @@ Default config values — copy this and change any value you want:
|
|
|
204
222
|
"cwd": true,
|
|
205
223
|
"gitBranch": true,
|
|
206
224
|
"gitStatus": true,
|
|
225
|
+
"gitCounts": false,
|
|
207
226
|
"runtime": true,
|
|
208
227
|
"context": true,
|
|
209
228
|
"tokens": true,
|
|
210
|
-
"cost": true
|
|
229
|
+
"cost": true,
|
|
230
|
+
"sessionDuration": false,
|
|
231
|
+
"username": false,
|
|
232
|
+
"time": false,
|
|
233
|
+
"os": false
|
|
211
234
|
},
|
|
212
235
|
"extensionStatuses": {
|
|
213
236
|
"defaultPlacement": "right",
|
|
@@ -217,12 +240,15 @@ Default config values — copy this and change any value you want:
|
|
|
217
240
|
}
|
|
218
241
|
```
|
|
219
242
|
|
|
220
|
-
- Style values can be Starship/terminal strings (`bold purple`, `fg:202`, `#89b4fa`, `bg:blue fg:bright-green`) or Pi theme tokens (`accent`, `borderMuted`, `thinkingHigh`).
|
|
221
|
-
- `projectRefreshIntervalMs`: project status polling interval; `0` disables polling.
|
|
222
|
-
- `
|
|
243
|
+
- Style values can be Starship/terminal strings (`bold purple`, `fg:202`, `#89b` / `#89b4fa`, `bg:blue fg:bright-green`) or Pi theme tokens (`accent`, `borderMuted`, `thinkingHigh`). Short `#rgb` hex values expand to `#rrggbb`.
|
|
244
|
+
- `projectRefreshIntervalMs`: project status polling interval; `0` disables polling. Values `1..4999` clamp up to `5000` (minimum 5s); invalid/non-finite values fall back to `30000`.
|
|
245
|
+
- `contextStyle`: `text` (default), `gauge`, or `text+gauge` for the context segment.
|
|
246
|
+
- `contextThresholds`: `{ warning, error }` percentages (default `70` / `90`) that select contextNormal / contextWarning / contextError colors.
|
|
247
|
+
- `icons`: every shown icon key is configurable; omit any key to use the Zentui default. `icons.mode` is `auto` | `nerd` | `ascii` (default `auto`, same glyphs as nerd). ASCII mode swaps in plain fallbacks for statusline icons and runtime symbols — useful without a Nerd Font. Custom per-icon strings always win over mode defaults. Custom `icons.os` always wins; when left at the mode default, Zentui maps the OS icon by platform. `rail` sets the vertical glyph drawn as the left rail of the active editor frame and previous user messages when `copyFriendly` is disabled (default `│`; any single Unicode vertical or block glyph). `editorPrompt` controls an optional copy-friendly editor prompt glyph; the default is `""` so copy-friendly mode stays rail-free.
|
|
223
248
|
- `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
|
|
224
249
|
- `features`: `editor` enables Zentui's custom editor, selector borders, and previous-message chrome. `statusLine` enables Zentui's custom footer/status line. `copyFriendly` hides editor and previous-message rail glyphs so native terminal selection copies less chrome. All three can be changed from `/zentui` or direct slash-command arguments.
|
|
225
|
-
- `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `runtime`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
|
|
250
|
+
- `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `gitCounts`, `runtime`, `sessionDuration`, `username`, `time`, `os`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
|
|
251
|
+
- `footerFormat`: optional Starship-style template string that fully controls the footer layout. When set, it overrides `footerSegments`. See [Footer Format Template](#footer-format-template) below. The `/zentui` **Layout** tab configures context style and icon mode; set or clear custom formats with `/zentui format`.
|
|
226
252
|
- `extensionStatuses`: controls third-party statuses published by other Pi extensions through `ctx.ui.setStatus()`. `defaultPlacement` and each `placements` value can be `off`, `left`, `middle`, or `right`. The `Extension segments` tab in `/zentui` lists only statuses that are currently active.
|
|
227
253
|
- The shown `editor*` values match the default `theme` source. Omit those keys to keep Zentui's source-aware defaults when switching between `theme` and `terminal`.
|
|
228
254
|
- `editorAccent` styles the active editor rail and previous user-message rail when `features.copyFriendly` is disabled.
|
|
@@ -232,10 +258,64 @@ Default config values — copy this and change any value you want:
|
|
|
232
258
|
|
|
233
259
|
Tip: when using copy-friendly mode, setting Pi's `editorPaddingX` to `1` in `~/.pi/agent/settings.json` keeps a small left gutter without copying a rail glyph.
|
|
234
260
|
|
|
261
|
+
## Footer Format Template
|
|
262
|
+
|
|
263
|
+
For full control, set a Starship-style `footerFormat` template string. It supports `$variable` and `${variable}` tokens, a special `$fill` token that splits the line into left and right zones, and conditional groups `( ... )` that drop entirely when every nested variable is empty. When set, it overrides the built-in `footerSegments` layout; when empty or omitted, the segment layout above is used.
|
|
264
|
+
|
|
265
|
+
A second `$fill` creates a **centered middle zone** — content between the two fills is true-centered (`floor((gap - middle) / 2)`), just like third-party statuses placed `middle`.
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"footerFormat": "$os $username $cwd( on $git_branch)( $git_status)( via $runtime)$fill($context)($sep$tokens)($sep$cost)($sep$time)"
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Center the branch between directory and cost:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"footerFormat": "$cwd $fill $git_branch $fill $cost"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Variables
|
|
282
|
+
|
|
283
|
+
| Token | Aliases | Renders |
|
|
284
|
+
| ------------------- | ------------ | ------------------------------------------------ |
|
|
285
|
+
| `$cwd` | `$directory` | current directory |
|
|
286
|
+
| `$git_branch` | `$branch` | git branch with icon |
|
|
287
|
+
| `$git_status` | `$status` | `[!?↑]` status block |
|
|
288
|
+
| `$git_state` | `$state` | `REBASING` / `MERGING` / … (optional `n/m`) |
|
|
289
|
+
| `$runtime` | | runtime icon + version |
|
|
290
|
+
| `$session_duration` | `$duration` | session running time |
|
|
291
|
+
| `$username` | | `user@host` |
|
|
292
|
+
| `$os` | | operating-system icon |
|
|
293
|
+
| `$time` | | current time `HH:MM` |
|
|
294
|
+
| `$context` | | context usage (text and/or gauge via config) |
|
|
295
|
+
| `$tokens` | | input/output token counts |
|
|
296
|
+
| `$cost` | | session cost |
|
|
297
|
+
| `$sep` | `$separator` | themed ` | ` using `colors.separator` |
|
|
298
|
+
| `$fill` | — | special: splits zones |
|
|
299
|
+
|
|
300
|
+
### `$fill` behavior
|
|
301
|
+
|
|
302
|
+
| `$fill` count | Layout |
|
|
303
|
+
| ------------- | ------------------------------------------------------------------------ |
|
|
304
|
+
| 0 | everything left-aligned |
|
|
305
|
+
| 1 | tokens before → left, tokens after → right |
|
|
306
|
+
| 2 | before first → left, between → **centered middle**, after second → right |
|
|
307
|
+
| 3+ | first two count; extras ignored |
|
|
308
|
+
|
|
309
|
+
- Literal text (`on branch`, `using`, `\|`, spaces) is rendered verbatim — you control all spacing.
|
|
310
|
+
- Each variable renders its core value only (no `on`/`via` prefixes); add those words as literal text.
|
|
311
|
+
- Conditional groups: wrap optional pieces in parentheses, e.g. `$cwd( on $git_branch)($git_status)$fill($context)`. If every `$var` inside a group is empty, the whole group (including its literals) is dropped.
|
|
312
|
+
- Unknown `$variables` render empty.
|
|
313
|
+
- Set or clear at runtime: `/zentui format "<template>"` and `/zentui format clear`.
|
|
314
|
+
|
|
235
315
|
## Requirements
|
|
236
316
|
|
|
237
317
|
- [Pi](https://pi.dev) coding agent 0.79 or newer
|
|
238
|
-
- A [Nerd Font](https://www.nerdfonts.com/) for icons
|
|
318
|
+
- A [Nerd Font](https://www.nerdfonts.com/) for icons (or set `icons.mode` to `"ascii"`)
|
|
239
319
|
|
|
240
320
|
## Development
|
|
241
321
|
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import {
|
|
5
|
+
ICON_GLYPH_KEYS,
|
|
6
|
+
type IconGlyphs,
|
|
7
|
+
type IconMode,
|
|
8
|
+
NERD_DEFAULT_ICONS,
|
|
9
|
+
normalizeIconMode,
|
|
10
|
+
type ResolvedIcons,
|
|
11
|
+
resolveConfiguredIcons,
|
|
12
|
+
} from "./icons";
|
|
4
13
|
import { isSupportedColorSpec } from "./style";
|
|
5
14
|
|
|
6
15
|
export type ColorSpec = string;
|
|
7
16
|
export type ColorSource = "theme" | "terminal";
|
|
17
|
+
export type { IconMode } from "./icons";
|
|
18
|
+
|
|
19
|
+
export type ContextStyle = "text" | "gauge" | "text+gauge";
|
|
20
|
+
|
|
21
|
+
export type ContextThresholds = {
|
|
22
|
+
warning: number;
|
|
23
|
+
error: number;
|
|
24
|
+
};
|
|
8
25
|
|
|
9
26
|
export type ColorSourcesConfig = {
|
|
10
27
|
starship: ColorSource;
|
|
@@ -28,6 +45,9 @@ export type FooterSegmentsConfig = {
|
|
|
28
45
|
tokens: boolean;
|
|
29
46
|
cost: boolean;
|
|
30
47
|
sessionDuration: boolean;
|
|
48
|
+
username: boolean;
|
|
49
|
+
time: boolean;
|
|
50
|
+
os: boolean;
|
|
31
51
|
};
|
|
32
52
|
|
|
33
53
|
export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
|
|
@@ -46,24 +66,10 @@ const MIN_PROJECT_REFRESH_INTERVAL_MS = 5_000;
|
|
|
46
66
|
|
|
47
67
|
export type PolishedTuiConfig = {
|
|
48
68
|
projectRefreshIntervalMs: number;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
behind: string;
|
|
54
|
-
diverged: string;
|
|
55
|
-
conflicted: string;
|
|
56
|
-
untracked: string;
|
|
57
|
-
stashed: string;
|
|
58
|
-
modified: string;
|
|
59
|
-
staged: string;
|
|
60
|
-
renamed: string;
|
|
61
|
-
deleted: string;
|
|
62
|
-
typechanged: string;
|
|
63
|
-
cacheHit: string;
|
|
64
|
-
editorPrompt: string;
|
|
65
|
-
rail: string;
|
|
66
|
-
};
|
|
69
|
+
footerFormat: string;
|
|
70
|
+
contextStyle: ContextStyle;
|
|
71
|
+
contextThresholds: ContextThresholds;
|
|
72
|
+
icons: ResolvedIcons;
|
|
67
73
|
colors: {
|
|
68
74
|
cwd: ColorSpec;
|
|
69
75
|
gitBranch: ColorSpec;
|
|
@@ -77,6 +83,9 @@ export type PolishedTuiConfig = {
|
|
|
77
83
|
runtimePrefix: ColorSpec;
|
|
78
84
|
extensionStatus: ColorSpec;
|
|
79
85
|
sessionDuration: ColorSpec;
|
|
86
|
+
username: ColorSpec;
|
|
87
|
+
time: ColorSpec;
|
|
88
|
+
os: ColorSpec;
|
|
80
89
|
editorAccent?: ColorSpec;
|
|
81
90
|
editorPrompt?: ColorSpec;
|
|
82
91
|
editorBorder?: ColorSpec;
|
|
@@ -95,27 +104,49 @@ export type PolishedTuiConfig = {
|
|
|
95
104
|
extensionStatuses: ExtensionStatusesConfig;
|
|
96
105
|
};
|
|
97
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Canonical footer format variable names. In a `footerFormat` string these
|
|
109
|
+
* are written as `$name` or `${name}`.
|
|
110
|
+
*/
|
|
111
|
+
export const FOOTER_FORMAT_VARIABLES = [
|
|
112
|
+
"cwd",
|
|
113
|
+
"git_branch",
|
|
114
|
+
"git_status",
|
|
115
|
+
"git_state",
|
|
116
|
+
"runtime",
|
|
117
|
+
"session_duration",
|
|
118
|
+
"username",
|
|
119
|
+
"os",
|
|
120
|
+
"time",
|
|
121
|
+
"context",
|
|
122
|
+
"tokens",
|
|
123
|
+
"cost",
|
|
124
|
+
"sep",
|
|
125
|
+
] as const;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Alias → canonical variable name mapping for `footerFormat`.
|
|
129
|
+
* `$fill` is special (not a variable) and handled by the parser.
|
|
130
|
+
*/
|
|
131
|
+
export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
|
|
132
|
+
directory: "cwd",
|
|
133
|
+
branch: "git_branch",
|
|
134
|
+
status: "git_status",
|
|
135
|
+
state: "git_state",
|
|
136
|
+
duration: "session_duration",
|
|
137
|
+
separator: "sep",
|
|
138
|
+
};
|
|
139
|
+
|
|
98
140
|
export const configPath = join(getAgentDir(), "zentui.json");
|
|
99
141
|
|
|
100
142
|
export const defaultConfig: PolishedTuiConfig = {
|
|
101
143
|
projectRefreshIntervalMs: DEFAULT_PROJECT_REFRESH_INTERVAL_MS,
|
|
144
|
+
footerFormat: "",
|
|
145
|
+
contextStyle: "text",
|
|
146
|
+
contextThresholds: { warning: 70, error: 90 },
|
|
102
147
|
icons: {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
ahead: "↑",
|
|
106
|
-
behind: "↓",
|
|
107
|
-
diverged: "⇕",
|
|
108
|
-
conflicted: "=",
|
|
109
|
-
untracked: "?",
|
|
110
|
-
stashed: "$",
|
|
111
|
-
modified: "!",
|
|
112
|
-
staged: "+",
|
|
113
|
-
renamed: "»",
|
|
114
|
-
deleted: "✘",
|
|
115
|
-
typechanged: "T",
|
|
116
|
-
cacheHit: "",
|
|
117
|
-
editorPrompt: "",
|
|
118
|
-
rail: "│",
|
|
148
|
+
mode: "auto",
|
|
149
|
+
...NERD_DEFAULT_ICONS,
|
|
119
150
|
},
|
|
120
151
|
colors: {
|
|
121
152
|
cwd: "bold cyan",
|
|
@@ -130,6 +161,9 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
130
161
|
runtimePrefix: "",
|
|
131
162
|
extensionStatus: "bright-black",
|
|
132
163
|
sessionDuration: "yellow",
|
|
164
|
+
username: "bold yellow",
|
|
165
|
+
time: "bold yellow",
|
|
166
|
+
os: "bold white",
|
|
133
167
|
},
|
|
134
168
|
colorSources: {
|
|
135
169
|
starship: "theme",
|
|
@@ -151,6 +185,9 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
151
185
|
tokens: true,
|
|
152
186
|
cost: true,
|
|
153
187
|
sessionDuration: false,
|
|
188
|
+
username: false,
|
|
189
|
+
time: false,
|
|
190
|
+
os: false,
|
|
154
191
|
},
|
|
155
192
|
extensionStatuses: {
|
|
156
193
|
defaultPlacement: "right",
|
|
@@ -159,24 +196,6 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
159
196
|
},
|
|
160
197
|
};
|
|
161
198
|
|
|
162
|
-
const iconKeys = [
|
|
163
|
-
"cwd",
|
|
164
|
-
"git",
|
|
165
|
-
"ahead",
|
|
166
|
-
"behind",
|
|
167
|
-
"diverged",
|
|
168
|
-
"conflicted",
|
|
169
|
-
"untracked",
|
|
170
|
-
"stashed",
|
|
171
|
-
"modified",
|
|
172
|
-
"staged",
|
|
173
|
-
"renamed",
|
|
174
|
-
"deleted",
|
|
175
|
-
"typechanged",
|
|
176
|
-
"cacheHit",
|
|
177
|
-
"editorPrompt",
|
|
178
|
-
] as const satisfies readonly (keyof PolishedTuiConfig["icons"])[];
|
|
179
|
-
|
|
180
199
|
type ConfigRecord = Record<string, unknown>;
|
|
181
200
|
|
|
182
201
|
function isRecord(value: unknown): value is ConfigRecord {
|
|
@@ -186,17 +205,43 @@ function isRecord(value: unknown): value is ConfigRecord {
|
|
|
186
205
|
function parseProjectRefreshIntervalMs(value: unknown): number {
|
|
187
206
|
if (value === 0) return 0;
|
|
188
207
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
189
|
-
return
|
|
208
|
+
return DEFAULT_PROJECT_REFRESH_INTERVAL_MS;
|
|
190
209
|
}
|
|
191
210
|
|
|
192
211
|
const interval = Math.round(value);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
212
|
+
if (interval <= 0) return 0;
|
|
213
|
+
return Math.max(MIN_PROJECT_REFRESH_INTERVAL_MS, interval);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function clampPercent(value: number): number {
|
|
217
|
+
return Math.max(0, Math.min(100, value));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function parseContextStyle(value: unknown): ContextStyle {
|
|
221
|
+
if (value === "text" || value === "gauge" || value === "text+gauge") return value;
|
|
222
|
+
return defaultConfig.contextStyle;
|
|
196
223
|
}
|
|
197
224
|
|
|
198
|
-
function
|
|
199
|
-
|
|
225
|
+
function parseContextThresholds(value: unknown): ContextThresholds {
|
|
226
|
+
const defaults = defaultConfig.contextThresholds;
|
|
227
|
+
if (!isRecord(value)) return { ...defaults };
|
|
228
|
+
|
|
229
|
+
const warningRaw = value.warning;
|
|
230
|
+
const errorRaw = value.error;
|
|
231
|
+
let warning =
|
|
232
|
+
typeof warningRaw === "number" && Number.isFinite(warningRaw)
|
|
233
|
+
? clampPercent(Math.round(warningRaw))
|
|
234
|
+
: defaults.warning;
|
|
235
|
+
let error =
|
|
236
|
+
typeof errorRaw === "number" && Number.isFinite(errorRaw)
|
|
237
|
+
? clampPercent(Math.round(errorRaw))
|
|
238
|
+
: defaults.error;
|
|
239
|
+
if (error < warning) {
|
|
240
|
+
const swapped = warning;
|
|
241
|
+
warning = error;
|
|
242
|
+
error = swapped;
|
|
243
|
+
}
|
|
244
|
+
return { warning, error };
|
|
200
245
|
}
|
|
201
246
|
|
|
202
247
|
function stringValue(record: Record<string, unknown>, key: string): string | undefined {
|
|
@@ -240,13 +285,13 @@ function definedColors(
|
|
|
240
285
|
) as Partial<PolishedTuiConfig["colors"]>;
|
|
241
286
|
}
|
|
242
287
|
|
|
243
|
-
function
|
|
288
|
+
function normalizeIconOverrides(record: Record<string, unknown>): Partial<IconGlyphs> {
|
|
244
289
|
return Object.fromEntries(
|
|
245
|
-
|
|
290
|
+
ICON_GLYPH_KEYS.flatMap((key) => {
|
|
246
291
|
const value = stringValue(record, key);
|
|
247
292
|
return value === undefined ? [] : [[key, value]];
|
|
248
293
|
}),
|
|
249
|
-
) as Partial<
|
|
294
|
+
) as Partial<IconGlyphs>;
|
|
250
295
|
}
|
|
251
296
|
|
|
252
297
|
function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiConfig["colors"]> {
|
|
@@ -263,6 +308,9 @@ function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiCo
|
|
|
263
308
|
runtimePrefix: colorValue(record, "runtimePrefix"),
|
|
264
309
|
extensionStatus: colorValue(record, "extensionStatus"),
|
|
265
310
|
sessionDuration: colorValue(record, "sessionDuration"),
|
|
311
|
+
username: colorValue(record, "username"),
|
|
312
|
+
time: colorValue(record, "time"),
|
|
313
|
+
os: colorValue(record, "os"),
|
|
266
314
|
editorAccent: colorValue(record, "editorAccent"),
|
|
267
315
|
editorPrompt: colorValue(record, "editorPrompt"),
|
|
268
316
|
editorBorder: colorValue(record, "editorBorder"),
|
|
@@ -304,6 +352,9 @@ function normalizeFooterSegments(record: Record<string, unknown>): FooterSegment
|
|
|
304
352
|
tokens: footerSegmentValue(record, "tokens"),
|
|
305
353
|
cost: footerSegmentValue(record, "cost"),
|
|
306
354
|
sessionDuration: footerSegmentValue(record, "sessionDuration"),
|
|
355
|
+
username: footerSegmentValue(record, "username"),
|
|
356
|
+
time: footerSegmentValue(record, "time"),
|
|
357
|
+
os: footerSegmentValue(record, "os"),
|
|
307
358
|
};
|
|
308
359
|
}
|
|
309
360
|
|
|
@@ -361,7 +412,10 @@ function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig
|
|
|
361
412
|
value === "context" ||
|
|
362
413
|
value === "tokens" ||
|
|
363
414
|
value === "cost" ||
|
|
364
|
-
value === "sessionDuration"
|
|
415
|
+
value === "sessionDuration" ||
|
|
416
|
+
value === "username" ||
|
|
417
|
+
value === "time" ||
|
|
418
|
+
value === "os"
|
|
365
419
|
);
|
|
366
420
|
}
|
|
367
421
|
|
|
@@ -412,7 +466,8 @@ export function ensureConfigExists(): void {
|
|
|
412
466
|
export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
413
467
|
const config = isRecord(parsed) ? parsed : {};
|
|
414
468
|
const iconsRecord = isRecord(config.icons) ? (config.icons as Record<string, unknown>) : {};
|
|
415
|
-
const
|
|
469
|
+
const iconMode = normalizeIconMode(iconsRecord.mode);
|
|
470
|
+
const iconOverrides = normalizeIconOverrides(iconsRecord);
|
|
416
471
|
const colors = isRecord(config.colors)
|
|
417
472
|
? normalizeColors(config.colors as Record<string, unknown>)
|
|
418
473
|
: {};
|
|
@@ -430,11 +485,10 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
430
485
|
: defaultConfig.extensionStatuses;
|
|
431
486
|
return {
|
|
432
487
|
projectRefreshIntervalMs: parseProjectRefreshIntervalMs(config.projectRefreshIntervalMs),
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
},
|
|
488
|
+
footerFormat: stringValue(config, "footerFormat") ?? "",
|
|
489
|
+
contextStyle: parseContextStyle(config.contextStyle),
|
|
490
|
+
contextThresholds: parseContextThresholds(config.contextThresholds),
|
|
491
|
+
icons: resolveConfiguredIcons(iconMode, iconOverrides),
|
|
438
492
|
colors: {
|
|
439
493
|
...defaultConfig.colors,
|
|
440
494
|
...colors,
|
|
@@ -521,6 +575,47 @@ export function saveFooterSegmentsPatch(
|
|
|
521
575
|
return mergeConfig(record);
|
|
522
576
|
}
|
|
523
577
|
|
|
578
|
+
export function saveFooterFormatPatch(value: string, path = configPath): PolishedTuiConfig {
|
|
579
|
+
const record = readConfigRecord(path);
|
|
580
|
+
record.footerFormat = typeof value === "string" ? value : "";
|
|
581
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
582
|
+
return mergeConfig(record);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function saveIconsModePatch(mode: IconMode, path = configPath): PolishedTuiConfig {
|
|
586
|
+
const record = readConfigRecord(path);
|
|
587
|
+
const existing = isRecord(record.icons) ? { ...(record.icons as Record<string, unknown>) } : {};
|
|
588
|
+
record.icons = {
|
|
589
|
+
...existing,
|
|
590
|
+
mode: normalizeIconMode(mode),
|
|
591
|
+
};
|
|
592
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
593
|
+
return mergeConfig(record);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export function saveContextStylePatch(style: ContextStyle, path = configPath): PolishedTuiConfig {
|
|
597
|
+
const record = readConfigRecord(path);
|
|
598
|
+
record.contextStyle = parseContextStyle(style);
|
|
599
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
600
|
+
return mergeConfig(record);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export function saveContextThresholdsPatch(
|
|
604
|
+
thresholds: Partial<ContextThresholds>,
|
|
605
|
+
path = configPath,
|
|
606
|
+
): PolishedTuiConfig {
|
|
607
|
+
const record = readConfigRecord(path);
|
|
608
|
+
const existing = isRecord(record.contextThresholds)
|
|
609
|
+
? { ...(record.contextThresholds as Record<string, unknown>) }
|
|
610
|
+
: {};
|
|
611
|
+
record.contextThresholds = {
|
|
612
|
+
...existing,
|
|
613
|
+
...thresholds,
|
|
614
|
+
};
|
|
615
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
616
|
+
return mergeConfig(record);
|
|
617
|
+
}
|
|
618
|
+
|
|
524
619
|
export function saveExtensionStatusPlacement(
|
|
525
620
|
key: string,
|
|
526
621
|
placement: ExtensionStatusPlacement,
|