@pi-unipi/utility 2.6.1 → 2.6.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/package.json +8 -6
- package/src/commands.ts +4 -5
- package/src/herdr-sync.ts +136 -0
- package/src/index.ts +11 -68
- package/src/lifecycle/cleanup.ts +2 -16
- package/src/{diff/settings.ts → settings.ts} +42 -80
- package/src/tools/env.ts +0 -0
- package/src/tui/name-badge-state.ts +18 -2
- package/src/tui/name-badge.ts +5 -4
- package/src/tui/util-settings-tui.ts +41 -224
- package/src/cache/ttl-cache.ts +0 -311
- package/src/diff/highlighter.ts +0 -352
- package/src/diff/parser.ts +0 -191
- package/src/diff/renderer.ts +0 -395
- package/src/diff/theme.ts +0 -316
- package/src/diff/wrapper.ts +0 -339
- package/src/display/capabilities.ts +0 -214
- package/src/display/width.ts +0 -226
- package/src/tools/batch.ts +0 -229
- package/src/tui/badge-settings-tui.ts +0 -388
- package/src/tui/badge-settings.ts +0 -67
- package/src/tui/settings-inspector.ts +0 -303
|
@@ -1,51 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @pi-unipi/utility —
|
|
2
|
+
* @pi-unipi/utility — Settings TUI Overlay
|
|
3
3
|
*
|
|
4
|
-
* Single TUI overlay
|
|
5
|
-
* - Badge: autoGen, badgeEnabled, agentTool, generationModel
|
|
6
|
-
* - Diff Rendering: enabled, theme preset, shikiTheme
|
|
7
|
-
*
|
|
8
|
-
* Replaces badge-settings-tui.ts as the primary settings interface.
|
|
4
|
+
* Single TUI overlay for badge settings.
|
|
9
5
|
*/
|
|
10
6
|
|
|
11
7
|
import type { Component } from "@earendil-works/pi-tui";
|
|
12
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
8
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
9
|
+
import { ansi, TOGGLE_ON, TOGGLE_OFF } from "@pi-unipi/core";
|
|
13
10
|
import type { CachedModel } from "@pi-unipi/core";
|
|
14
11
|
import { readModelCache, boxInnerWidth } from "@pi-unipi/core";
|
|
15
12
|
import {
|
|
16
13
|
readUtilSettings,
|
|
17
14
|
writeUtilSettings,
|
|
18
15
|
type UtilSettings,
|
|
19
|
-
|
|
20
|
-
type DiffSettings,
|
|
21
|
-
} from "../diff/settings.js";
|
|
22
|
-
import { getAllPresets } from "../diff/theme.js";
|
|
16
|
+
} from "../settings.js";
|
|
23
17
|
|
|
24
18
|
/** ANSI escape codes */
|
|
25
|
-
const ansi = {
|
|
26
|
-
reset: "\x1b[0m",
|
|
27
|
-
bold: "\x1b[1m",
|
|
28
|
-
dim: "\x1b[2m",
|
|
29
|
-
cyan: "\x1b[36m",
|
|
30
|
-
green: "\x1b[32m",
|
|
31
|
-
yellow: "\x1b[33m",
|
|
32
|
-
red: "\x1b[31m",
|
|
33
|
-
gray: "\x1b[90m",
|
|
34
|
-
white: "\x1b[37m",
|
|
35
|
-
blue: "\x1b[34m",
|
|
36
|
-
};
|
|
37
19
|
|
|
38
20
|
/** Toggle symbols */
|
|
39
|
-
const TOGGLE_ON = `${ansi.green}●${ansi.reset}`;
|
|
40
|
-
const TOGGLE_OFF = `${ansi.dim}○${ansi.reset}`;
|
|
41
21
|
|
|
42
22
|
/** Active mode */
|
|
43
|
-
type Mode = "settings" | "model-picker"
|
|
23
|
+
type Mode = "settings" | "model-picker";
|
|
44
24
|
|
|
45
25
|
/** Setting row types */
|
|
46
26
|
interface BooleanSetting {
|
|
47
27
|
type: "boolean";
|
|
48
|
-
section: "badge" | "diff";
|
|
49
28
|
key: string;
|
|
50
29
|
label: string;
|
|
51
30
|
description: string;
|
|
@@ -54,11 +33,10 @@ interface BooleanSetting {
|
|
|
54
33
|
|
|
55
34
|
interface PickerSetting {
|
|
56
35
|
type: "picker";
|
|
57
|
-
section: "badge" | "diff";
|
|
58
36
|
key: string;
|
|
59
37
|
label: string;
|
|
60
38
|
description: string;
|
|
61
|
-
pickerType: "model"
|
|
39
|
+
pickerType: "model";
|
|
62
40
|
getValue: (s: UtilSettings) => string;
|
|
63
41
|
}
|
|
64
42
|
|
|
@@ -71,11 +49,9 @@ type SettingItem = BooleanSetting | PickerSetting | SectionHeader;
|
|
|
71
49
|
|
|
72
50
|
/** All settings items */
|
|
73
51
|
const SETTINGS: SettingItem[] = [
|
|
74
|
-
// Badge section
|
|
75
52
|
{ type: "section", label: "Badge" },
|
|
76
53
|
{
|
|
77
54
|
type: "boolean",
|
|
78
|
-
section: "badge",
|
|
79
55
|
key: "autoGen",
|
|
80
56
|
label: "Auto generate",
|
|
81
57
|
description: "Generate session name on first user message",
|
|
@@ -83,7 +59,6 @@ const SETTINGS: SettingItem[] = [
|
|
|
83
59
|
},
|
|
84
60
|
{
|
|
85
61
|
type: "boolean",
|
|
86
|
-
section: "badge",
|
|
87
62
|
key: "badgeEnabled",
|
|
88
63
|
label: "Badge enabled",
|
|
89
64
|
description: "Show the name badge overlay",
|
|
@@ -91,79 +66,30 @@ const SETTINGS: SettingItem[] = [
|
|
|
91
66
|
},
|
|
92
67
|
{
|
|
93
68
|
type: "boolean",
|
|
94
|
-
section: "badge",
|
|
95
69
|
key: "agentTool",
|
|
96
70
|
label: "Agent tool",
|
|
97
71
|
description: "Allow agents to call set_session_name",
|
|
98
72
|
getValue: (s) => s.badge.agentTool,
|
|
99
73
|
},
|
|
74
|
+
{
|
|
75
|
+
type: "boolean",
|
|
76
|
+
key: "herdrSync",
|
|
77
|
+
label: "Herdr sync",
|
|
78
|
+
description: "Sync session name to herdr tab/pane title",
|
|
79
|
+
getValue: (s) => s.badge.herdrSync,
|
|
80
|
+
},
|
|
100
81
|
{
|
|
101
82
|
type: "picker",
|
|
102
|
-
section: "badge",
|
|
103
83
|
key: "generationModel",
|
|
104
84
|
label: "Generation model",
|
|
105
85
|
description: "Model for badge name generation",
|
|
106
86
|
pickerType: "model",
|
|
107
87
|
getValue: (s) => s.badge.generationModel,
|
|
108
88
|
},
|
|
109
|
-
// Diff Rendering section
|
|
110
|
-
{ type: "section", label: "Diff Rendering" },
|
|
111
|
-
{
|
|
112
|
-
type: "boolean",
|
|
113
|
-
section: "diff",
|
|
114
|
-
key: "enabled",
|
|
115
|
-
label: "Enabled",
|
|
116
|
-
description: "Shiki-powered syntax-highlighted diffs",
|
|
117
|
-
getValue: (s) => s.diff.enabled,
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
type: "picker",
|
|
121
|
-
section: "diff",
|
|
122
|
-
key: "theme",
|
|
123
|
-
label: "Theme",
|
|
124
|
-
description: "Diff color preset",
|
|
125
|
-
pickerType: "theme",
|
|
126
|
-
getValue: (s) => s.diff.theme,
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
type: "picker",
|
|
130
|
-
section: "diff",
|
|
131
|
-
key: "shikiTheme",
|
|
132
|
-
label: "Shiki theme",
|
|
133
|
-
description: "Syntax highlighting grammar",
|
|
134
|
-
pickerType: "shiki",
|
|
135
|
-
getValue: (s) => s.diff.shikiTheme,
|
|
136
|
-
},
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
/** List of known Shiki themes */
|
|
140
|
-
const SHIKI_THEMES = [
|
|
141
|
-
"github-dark",
|
|
142
|
-
"github-light",
|
|
143
|
-
"dracula",
|
|
144
|
-
"one-dark-pro",
|
|
145
|
-
"catppuccin-mocha",
|
|
146
|
-
"catppuccin-latte",
|
|
147
|
-
"nord",
|
|
148
|
-
"tokyo-night",
|
|
149
|
-
"tokyo-night-storm",
|
|
150
|
-
"night-owl",
|
|
151
|
-
"material-theme",
|
|
152
|
-
"material-theme-palenight",
|
|
153
|
-
"monokai",
|
|
154
|
-
"solarized-dark",
|
|
155
|
-
"solarized-light",
|
|
156
|
-
"vitesse-dark",
|
|
157
|
-
"vitesse-light",
|
|
158
|
-
"ayu-dark",
|
|
159
|
-
"ayu-mirage",
|
|
160
|
-
"slack-dark",
|
|
161
|
-
"slack-ochin",
|
|
162
89
|
];
|
|
163
90
|
|
|
164
91
|
/**
|
|
165
|
-
*
|
|
166
|
-
* Combines badge and diff settings in a single navigable interface.
|
|
92
|
+
* Settings TUI overlay for badge configuration.
|
|
167
93
|
*/
|
|
168
94
|
export class UtilSettingsTui implements Component {
|
|
169
95
|
private settings: UtilSettings;
|
|
@@ -172,10 +98,7 @@ export class UtilSettingsTui implements Component {
|
|
|
172
98
|
private scrollOffset = 0;
|
|
173
99
|
private models: CachedModel[] = [];
|
|
174
100
|
|
|
175
|
-
/** Callback when overlay should close */
|
|
176
101
|
onClose?: () => void;
|
|
177
|
-
|
|
178
|
-
/** Callback to request a re-render */
|
|
179
102
|
requestRender?: () => void;
|
|
180
103
|
|
|
181
104
|
constructor() {
|
|
@@ -183,16 +106,8 @@ export class UtilSettingsTui implements Component {
|
|
|
183
106
|
this.models = readModelCache();
|
|
184
107
|
}
|
|
185
108
|
|
|
186
|
-
|
|
187
|
-
* Invalidate cached render state.
|
|
188
|
-
*/
|
|
189
|
-
invalidate(): void {
|
|
190
|
-
// No cached state to invalidate
|
|
191
|
-
}
|
|
109
|
+
invalidate(): void {}
|
|
192
110
|
|
|
193
|
-
/**
|
|
194
|
-
* Handle keyboard input.
|
|
195
|
-
*/
|
|
196
111
|
handleInput(data: string): void {
|
|
197
112
|
switch (this.mode) {
|
|
198
113
|
case "settings":
|
|
@@ -201,90 +116,52 @@ export class UtilSettingsTui implements Component {
|
|
|
201
116
|
case "model-picker":
|
|
202
117
|
this.handlePickerInput(data, this.getModelList(), "model");
|
|
203
118
|
break;
|
|
204
|
-
case "theme-picker":
|
|
205
|
-
this.handlePickerInput(data, this.getThemeList(), "theme");
|
|
206
|
-
break;
|
|
207
|
-
case "shiki-picker":
|
|
208
|
-
this.handlePickerInput(data, this.getShikiList(), "shiki");
|
|
209
|
-
break;
|
|
210
119
|
}
|
|
211
120
|
}
|
|
212
121
|
|
|
213
|
-
/**
|
|
214
|
-
* Handle input in settings mode.
|
|
215
|
-
*/
|
|
216
122
|
private handleSettingsInput(data: string): void {
|
|
217
|
-
// Get navigable items (skip section headers)
|
|
218
123
|
const navItems = SETTINGS.filter((s) => s.type !== "section");
|
|
219
124
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
125
|
+
if (matchesKey(data, Key.up) || data === "k") {
|
|
126
|
+
this.selectedIndex = (this.selectedIndex - 1 + navItems.length) % navItems.length;
|
|
127
|
+
} else if (matchesKey(data, Key.down) || data === "j") {
|
|
128
|
+
this.selectedIndex = (this.selectedIndex + 1) % navItems.length;
|
|
129
|
+
} else if (matchesKey(data, Key.space)) {
|
|
130
|
+
this.toggleCurrentSetting();
|
|
131
|
+
} else if (matchesKey(data, Key.enter)) {
|
|
132
|
+
if (navItems[this.selectedIndex]?.type === "picker") {
|
|
133
|
+
this.enterPicker(navItems[this.selectedIndex] as PickerSetting);
|
|
134
|
+
} else {
|
|
230
135
|
this.toggleCurrentSetting();
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
} else {
|
|
236
|
-
this.toggleCurrentSetting();
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
case "\x1b": // Escape — save and close
|
|
240
|
-
this.save();
|
|
241
|
-
this.onClose?.();
|
|
242
|
-
break;
|
|
136
|
+
}
|
|
137
|
+
} else if (matchesKey(data, Key.escape)) {
|
|
138
|
+
this.save();
|
|
139
|
+
this.onClose?.();
|
|
243
140
|
}
|
|
244
141
|
}
|
|
245
142
|
|
|
246
|
-
/**
|
|
247
|
-
* Handle input in picker mode.
|
|
248
|
-
*/
|
|
249
143
|
private handlePickerInput(data: string, items: Array<{ id: string; label: string }>, pickerType: string): void {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
break;
|
|
259
|
-
case "\r": // Enter — select
|
|
260
|
-
this.selectPickerItem(items[this.scrollOffset], pickerType);
|
|
261
|
-
break;
|
|
262
|
-
case "\x1b": // Escape — cancel
|
|
263
|
-
this.mode = "settings";
|
|
264
|
-
break;
|
|
144
|
+
if (matchesKey(data, Key.up) || data === "k") {
|
|
145
|
+
this.scrollOffset = (this.scrollOffset - 1 + items.length) % items.length;
|
|
146
|
+
} else if (matchesKey(data, Key.down) || data === "j") {
|
|
147
|
+
this.scrollOffset = (this.scrollOffset + 1) % items.length;
|
|
148
|
+
} else if (matchesKey(data, Key.enter)) {
|
|
149
|
+
this.selectPickerItem(items[this.scrollOffset], pickerType);
|
|
150
|
+
} else if (matchesKey(data, Key.escape)) {
|
|
151
|
+
this.mode = "settings";
|
|
265
152
|
}
|
|
266
153
|
}
|
|
267
154
|
|
|
268
|
-
/**
|
|
269
|
-
* Toggle the currently selected boolean setting.
|
|
270
|
-
*/
|
|
271
155
|
private toggleCurrentSetting(): void {
|
|
272
156
|
const navItems = SETTINGS.filter((s) => s.type !== "section");
|
|
273
157
|
const item = navItems[this.selectedIndex];
|
|
274
158
|
if (!item || item.type !== "boolean") return;
|
|
275
159
|
|
|
276
160
|
const current = item.getValue(this.settings);
|
|
277
|
-
|
|
278
|
-
(this.settings.badge as any)[item.key] = !current;
|
|
279
|
-
} else {
|
|
280
|
-
(this.settings.diff as any)[item.key] = !current;
|
|
281
|
-
}
|
|
161
|
+
(this.settings.badge as any)[item.key] = !current;
|
|
282
162
|
this.save();
|
|
283
163
|
}
|
|
284
164
|
|
|
285
|
-
/**
|
|
286
|
-
* Enter picker mode for a setting.
|
|
287
|
-
*/
|
|
288
165
|
private enterPicker(item: PickerSetting): void {
|
|
289
166
|
switch (item.pickerType) {
|
|
290
167
|
case "model":
|
|
@@ -292,41 +169,19 @@ export class UtilSettingsTui implements Component {
|
|
|
292
169
|
this.scrollOffset = this.getModelList().findIndex((m) => m.id === this.settings.badge.generationModel);
|
|
293
170
|
if (this.scrollOffset < 0) this.scrollOffset = 0;
|
|
294
171
|
break;
|
|
295
|
-
case "theme":
|
|
296
|
-
this.mode = "theme-picker";
|
|
297
|
-
this.scrollOffset = getAllPresets().findIndex((p) => p.name === this.settings.diff.theme);
|
|
298
|
-
if (this.scrollOffset < 0) this.scrollOffset = 0;
|
|
299
|
-
break;
|
|
300
|
-
case "shiki":
|
|
301
|
-
this.mode = "shiki-picker";
|
|
302
|
-
this.scrollOffset = SHIKI_THEMES.indexOf(this.settings.diff.shikiTheme);
|
|
303
|
-
if (this.scrollOffset < 0) this.scrollOffset = 0;
|
|
304
|
-
break;
|
|
305
172
|
}
|
|
306
173
|
}
|
|
307
174
|
|
|
308
|
-
/**
|
|
309
|
-
* Select an item in a picker.
|
|
310
|
-
*/
|
|
311
175
|
private selectPickerItem(item: { id: string; label: string }, pickerType: string): void {
|
|
312
176
|
switch (pickerType) {
|
|
313
177
|
case "model":
|
|
314
178
|
this.settings.badge.generationModel = item.id;
|
|
315
179
|
break;
|
|
316
|
-
case "theme":
|
|
317
|
-
this.settings.diff.theme = item.id;
|
|
318
|
-
break;
|
|
319
|
-
case "shiki":
|
|
320
|
-
this.settings.diff.shikiTheme = item.id;
|
|
321
|
-
break;
|
|
322
180
|
}
|
|
323
181
|
this.mode = "settings";
|
|
324
182
|
this.save();
|
|
325
183
|
}
|
|
326
184
|
|
|
327
|
-
/**
|
|
328
|
-
* Get model list with "inherit" as first entry.
|
|
329
|
-
*/
|
|
330
185
|
private getModelList(): Array<{ id: string; label: string }> {
|
|
331
186
|
const list: Array<{ id: string; label: string }> = [
|
|
332
187
|
{ id: "inherit", label: "inherit (use parent model)" },
|
|
@@ -341,33 +196,10 @@ export class UtilSettingsTui implements Component {
|
|
|
341
196
|
return list;
|
|
342
197
|
}
|
|
343
198
|
|
|
344
|
-
/**
|
|
345
|
-
* Get diff theme preset list.
|
|
346
|
-
*/
|
|
347
|
-
private getThemeList(): Array<{ id: string; label: string }> {
|
|
348
|
-
return getAllPresets().map((p) => ({
|
|
349
|
-
id: p.name,
|
|
350
|
-
label: `${p.name} — ${p.description}`,
|
|
351
|
-
}));
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Get Shiki theme list.
|
|
356
|
-
*/
|
|
357
|
-
private getShikiList(): Array<{ id: string; label: string }> {
|
|
358
|
-
return SHIKI_THEMES.map((t) => ({ id: t, label: t }));
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Save settings to disk.
|
|
363
|
-
*/
|
|
364
199
|
private save(): void {
|
|
365
200
|
writeUtilSettings(this.settings);
|
|
366
201
|
}
|
|
367
202
|
|
|
368
|
-
/**
|
|
369
|
-
* Render the overlay.
|
|
370
|
-
*/
|
|
371
203
|
render(width: number): string[] {
|
|
372
204
|
const lines: string[] = [];
|
|
373
205
|
const innerWidth = boxInnerWidth(width);
|
|
@@ -392,15 +224,12 @@ export class UtilSettingsTui implements Component {
|
|
|
392
224
|
`${ansi.cyan}│${ansi.reset}`,
|
|
393
225
|
);
|
|
394
226
|
|
|
395
|
-
// Top border
|
|
396
227
|
lines.push(`${ansi.cyan}╭${"─".repeat(innerWidth)}╮${ansi.reset}`);
|
|
397
228
|
|
|
398
|
-
// Header
|
|
399
229
|
add(`${ansi.bold}${ansi.cyan}⚙ Utility Settings${ansi.reset}`);
|
|
400
|
-
add(`${ansi.dim}Configure badge
|
|
230
|
+
add(`${ansi.dim}Configure badge${ansi.reset}`);
|
|
401
231
|
addEmpty();
|
|
402
232
|
|
|
403
|
-
// Settings list
|
|
404
233
|
const navItems = SETTINGS.filter((s) => s.type !== "section");
|
|
405
234
|
let navIndex = 0;
|
|
406
235
|
|
|
@@ -422,9 +251,8 @@ export class UtilSettingsTui implements Component {
|
|
|
422
251
|
add(` ${ansi.gray}${item.description}${ansi.reset}`);
|
|
423
252
|
} else if (item.type === "picker") {
|
|
424
253
|
const value = item.getValue(this.settings);
|
|
425
|
-
const icon = item.pickerType === "model" ? "⚙" : "🎨";
|
|
426
254
|
add(
|
|
427
|
-
`${selector} ${ansi.yellow}
|
|
255
|
+
`${selector} ${ansi.yellow}⚙${ansi.reset} ${labelColor}${item.label}${ansi.reset}: ${ansi.white}${value}${ansi.reset}`,
|
|
428
256
|
);
|
|
429
257
|
add(` ${ansi.gray}${item.description}${ansi.reset}`);
|
|
430
258
|
if (isSelected) {
|
|
@@ -435,7 +263,6 @@ export class UtilSettingsTui implements Component {
|
|
|
435
263
|
navIndex++;
|
|
436
264
|
}
|
|
437
265
|
|
|
438
|
-
// Picker overlay (inline)
|
|
439
266
|
if (this.mode !== "settings") {
|
|
440
267
|
addEmpty();
|
|
441
268
|
let items: Array<{ id: string; label: string }> = [];
|
|
@@ -446,14 +273,6 @@ export class UtilSettingsTui implements Component {
|
|
|
446
273
|
items = this.getModelList();
|
|
447
274
|
title = "Available Models";
|
|
448
275
|
break;
|
|
449
|
-
case "theme-picker":
|
|
450
|
-
items = this.getThemeList();
|
|
451
|
-
title = "Diff Theme Presets";
|
|
452
|
-
break;
|
|
453
|
-
case "shiki-picker":
|
|
454
|
-
items = this.getShikiList();
|
|
455
|
-
title = "Shiki Themes";
|
|
456
|
-
break;
|
|
457
276
|
}
|
|
458
277
|
|
|
459
278
|
add(`${ansi.bold}${ansi.cyan}── ${title} ──${ansi.reset}`);
|
|
@@ -480,7 +299,6 @@ export class UtilSettingsTui implements Component {
|
|
|
480
299
|
}
|
|
481
300
|
}
|
|
482
301
|
|
|
483
|
-
// Footer
|
|
484
302
|
addEmpty();
|
|
485
303
|
|
|
486
304
|
if (this.mode === "settings") {
|
|
@@ -490,7 +308,6 @@ export class UtilSettingsTui implements Component {
|
|
|
490
308
|
add(`${ansi.dim}↑↓ navigate • Enter select • Esc cancel${ansi.reset}`);
|
|
491
309
|
}
|
|
492
310
|
|
|
493
|
-
// Bottom border
|
|
494
311
|
lines.push(`${ansi.cyan}╰${"─".repeat(innerWidth)}╯${ansi.reset}`);
|
|
495
312
|
|
|
496
313
|
return lines;
|