@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,388 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pi-unipi/utility — Badge Settings TUI Overlay
|
|
3
|
-
*
|
|
4
|
-
* Interactive settings overlay for badge configuration.
|
|
5
|
-
* Three settings: auto-generate toggle, badge-enabled toggle, generation model selector.
|
|
6
|
-
* Model list loaded from shared model cache (~/.unipi/config/models-cache.json).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Component } from "@earendil-works/pi-tui";
|
|
10
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
11
|
-
import type { CachedModel } from "@pi-unipi/core";
|
|
12
|
-
import { readModelCache, boxInnerWidth } from "@pi-unipi/core";
|
|
13
|
-
import {
|
|
14
|
-
readBadgeSettings,
|
|
15
|
-
writeBadgeSettings,
|
|
16
|
-
type BadgeSettings,
|
|
17
|
-
} from "./badge-settings.js";
|
|
18
|
-
|
|
19
|
-
/** ANSI escape codes */
|
|
20
|
-
const ansi = {
|
|
21
|
-
reset: "\x1b[0m",
|
|
22
|
-
bold: "\x1b[1m",
|
|
23
|
-
dim: "\x1b[2m",
|
|
24
|
-
cyan: "\x1b[36m",
|
|
25
|
-
green: "\x1b[32m",
|
|
26
|
-
yellow: "\x1b[33m",
|
|
27
|
-
red: "\x1b[31m",
|
|
28
|
-
gray: "\x1b[90m",
|
|
29
|
-
white: "\x1b[37m",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/** Toggle symbols */
|
|
33
|
-
const TOGGLE_ON = `${ansi.green}●${ansi.reset}`;
|
|
34
|
-
const TOGGLE_OFF = `${ansi.dim}○${ansi.reset}`;
|
|
35
|
-
|
|
36
|
-
/** Active mode */
|
|
37
|
-
type Mode = "settings" | "model-picker";
|
|
38
|
-
|
|
39
|
-
/** Setting row types */
|
|
40
|
-
interface BooleanSetting {
|
|
41
|
-
type: "boolean";
|
|
42
|
-
key: keyof BadgeSettings;
|
|
43
|
-
label: string;
|
|
44
|
-
description: string;
|
|
45
|
-
getValue: (s: BadgeSettings) => boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface ModelSetting {
|
|
49
|
-
type: "model";
|
|
50
|
-
key: "generationModel";
|
|
51
|
-
label: string;
|
|
52
|
-
description: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
type SettingItem = BooleanSetting | ModelSetting;
|
|
56
|
-
|
|
57
|
-
/** Settings list */
|
|
58
|
-
const SETTINGS: SettingItem[] = [
|
|
59
|
-
{
|
|
60
|
-
type: "boolean",
|
|
61
|
-
key: "autoGen",
|
|
62
|
-
label: "Auto generate",
|
|
63
|
-
description: "Generate session name on first user message",
|
|
64
|
-
getValue: (s) => s.autoGen,
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
type: "boolean",
|
|
68
|
-
key: "badgeEnabled",
|
|
69
|
-
label: "Badge enabled",
|
|
70
|
-
description: "Show the name badge overlay",
|
|
71
|
-
getValue: (s) => s.badgeEnabled,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: "model",
|
|
75
|
-
key: "generationModel",
|
|
76
|
-
label: "Generation model",
|
|
77
|
-
description: "Model to use for badge name generation",
|
|
78
|
-
},
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Badge Settings TUI overlay.
|
|
83
|
-
* Implements the Component interface for use with ctx.ui.custom().
|
|
84
|
-
*/
|
|
85
|
-
export class BadgeSettingsTui implements Component {
|
|
86
|
-
private settings: BadgeSettings;
|
|
87
|
-
private mode: Mode = "settings";
|
|
88
|
-
private selectedIndex = 0;
|
|
89
|
-
private modelScrollOffset = 0;
|
|
90
|
-
private models: CachedModel[] = [];
|
|
91
|
-
|
|
92
|
-
/** Theme reference for rendering (set externally) */
|
|
93
|
-
private _theme: any = null;
|
|
94
|
-
|
|
95
|
-
/** Callback when overlay should close */
|
|
96
|
-
onClose?: () => void;
|
|
97
|
-
|
|
98
|
-
/** Callback to request a re-render */
|
|
99
|
-
requestRender?: () => void;
|
|
100
|
-
|
|
101
|
-
constructor() {
|
|
102
|
-
this.settings = readBadgeSettings();
|
|
103
|
-
this.models = readModelCache();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Set the pi-tui theme for styled rendering.
|
|
108
|
-
*/
|
|
109
|
-
setTheme(theme: any): void {
|
|
110
|
-
this._theme = theme;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Invalidate cached render state.
|
|
115
|
-
*/
|
|
116
|
-
invalidate(): void {
|
|
117
|
-
// No cached state to invalidate
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Handle keyboard input.
|
|
122
|
-
*/
|
|
123
|
-
handleInput(data: string): void {
|
|
124
|
-
if (this.mode === "settings") {
|
|
125
|
-
this.handleSettingsInput(data);
|
|
126
|
-
} else {
|
|
127
|
-
this.handleModelPickerInput(data);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Handle input in settings mode.
|
|
133
|
-
*/
|
|
134
|
-
private handleSettingsInput(data: string): void {
|
|
135
|
-
switch (data) {
|
|
136
|
-
case "\x1b[A": // Up arrow
|
|
137
|
-
case "k":
|
|
138
|
-
this.selectedIndex =
|
|
139
|
-
(this.selectedIndex - 1 + SETTINGS.length) % SETTINGS.length;
|
|
140
|
-
break;
|
|
141
|
-
case "\x1b[B": // Down arrow
|
|
142
|
-
case "j":
|
|
143
|
-
this.selectedIndex = (this.selectedIndex + 1) % SETTINGS.length;
|
|
144
|
-
break;
|
|
145
|
-
case " ": // Space — toggle boolean settings
|
|
146
|
-
this.toggleCurrentSetting();
|
|
147
|
-
break;
|
|
148
|
-
case "\r": // Enter — open model picker or toggle
|
|
149
|
-
if (SETTINGS[this.selectedIndex].type === "model") {
|
|
150
|
-
this.enterModelPicker();
|
|
151
|
-
} else {
|
|
152
|
-
this.toggleCurrentSetting();
|
|
153
|
-
}
|
|
154
|
-
break;
|
|
155
|
-
case "\x1b": // Escape — close and save
|
|
156
|
-
this.save();
|
|
157
|
-
this.onClose?.();
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Handle input in model picker mode.
|
|
164
|
-
*/
|
|
165
|
-
private handleModelPickerInput(data: string): void {
|
|
166
|
-
const allModels = this.getModelList();
|
|
167
|
-
|
|
168
|
-
switch (data) {
|
|
169
|
-
case "\x1b[A": // Up arrow
|
|
170
|
-
case "k":
|
|
171
|
-
this.selectedIndex =
|
|
172
|
-
(this.selectedIndex - 1 + allModels.length) % allModels.length;
|
|
173
|
-
this.adjustModelScroll(allModels.length);
|
|
174
|
-
break;
|
|
175
|
-
case "\x1b[B": // Down arrow
|
|
176
|
-
case "j":
|
|
177
|
-
this.selectedIndex = (this.selectedIndex + 1) % allModels.length;
|
|
178
|
-
this.adjustModelScroll(allModels.length);
|
|
179
|
-
break;
|
|
180
|
-
case "\r": // Enter — select model
|
|
181
|
-
this.selectModel();
|
|
182
|
-
break;
|
|
183
|
-
case "\x1b": // Escape — cancel picker
|
|
184
|
-
this.mode = "settings";
|
|
185
|
-
this.selectedIndex = SETTINGS.findIndex(
|
|
186
|
-
(s) => s.key === "generationModel",
|
|
187
|
-
);
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Toggle the currently selected boolean setting.
|
|
194
|
-
*/
|
|
195
|
-
private toggleCurrentSetting(): void {
|
|
196
|
-
const item = SETTINGS[this.selectedIndex];
|
|
197
|
-
if (item.type !== "boolean") return;
|
|
198
|
-
|
|
199
|
-
const current = item.getValue(this.settings);
|
|
200
|
-
(this.settings as any)[item.key] = !current;
|
|
201
|
-
this.save();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Enter model picker mode.
|
|
206
|
-
*/
|
|
207
|
-
private enterModelPicker(): void {
|
|
208
|
-
this.mode = "model-picker";
|
|
209
|
-
const allModels = this.getModelList();
|
|
210
|
-
const currentModel = this.settings.generationModel;
|
|
211
|
-
this.selectedIndex = allModels.findIndex((m) => m.id === currentModel);
|
|
212
|
-
if (this.selectedIndex < 0) this.selectedIndex = 0;
|
|
213
|
-
this.modelScrollOffset = 0;
|
|
214
|
-
this.adjustModelScroll(allModels.length);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Select the current model in the picker.
|
|
219
|
-
*/
|
|
220
|
-
private selectModel(): void {
|
|
221
|
-
const allModels = this.getModelList();
|
|
222
|
-
const selected = allModels[this.selectedIndex];
|
|
223
|
-
if (selected) {
|
|
224
|
-
this.settings.generationModel = selected.id;
|
|
225
|
-
this.save();
|
|
226
|
-
}
|
|
227
|
-
this.mode = "settings";
|
|
228
|
-
this.selectedIndex = SETTINGS.findIndex((s) => s.key === "generationModel");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Get model list with "inherit" as first entry.
|
|
233
|
-
*/
|
|
234
|
-
private getModelList(): Array<{ id: string; label: string }> {
|
|
235
|
-
const list: Array<{ id: string; label: string }> = [
|
|
236
|
-
{ id: "inherit", label: "inherit (use parent model)" },
|
|
237
|
-
];
|
|
238
|
-
for (const m of this.models) {
|
|
239
|
-
const fullId = `${m.provider}/${m.id}`;
|
|
240
|
-
list.push({
|
|
241
|
-
id: fullId,
|
|
242
|
-
label: m.name ? `${fullId} (${m.name})` : fullId,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
return list;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Adjust scroll offset so the selected item is visible.
|
|
250
|
-
*/
|
|
251
|
-
private adjustModelScroll(totalItems: number): void {
|
|
252
|
-
// Reserve ~10 lines for visible model list
|
|
253
|
-
const visibleLines = 10;
|
|
254
|
-
if (this.selectedIndex < this.modelScrollOffset) {
|
|
255
|
-
this.modelScrollOffset = this.selectedIndex;
|
|
256
|
-
} else if (this.selectedIndex >= this.modelScrollOffset + visibleLines) {
|
|
257
|
-
this.modelScrollOffset = this.selectedIndex - visibleLines + 1;
|
|
258
|
-
}
|
|
259
|
-
// Clamp
|
|
260
|
-
this.modelScrollOffset = Math.max(
|
|
261
|
-
0,
|
|
262
|
-
Math.min(this.modelScrollOffset, totalItems - visibleLines),
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Save settings to disk.
|
|
268
|
-
*/
|
|
269
|
-
private save(): void {
|
|
270
|
-
writeBadgeSettings(this.settings);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Render the overlay.
|
|
275
|
-
*/
|
|
276
|
-
render(width: number): string[] {
|
|
277
|
-
const lines: string[] = [];
|
|
278
|
-
const innerWidth = boxInnerWidth(width);
|
|
279
|
-
|
|
280
|
-
const padVisible = (content: string, targetWidth: number): string => {
|
|
281
|
-
const vw = visibleWidth(content);
|
|
282
|
-
const pad = Math.max(0, targetWidth - vw);
|
|
283
|
-
return content + " ".repeat(pad);
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const add = (s: string) =>
|
|
287
|
-
lines.push(
|
|
288
|
-
`${ansi.cyan}│${ansi.reset}` +
|
|
289
|
-
padVisible(truncateToWidth(s, innerWidth), innerWidth) +
|
|
290
|
-
`${ansi.cyan}│${ansi.reset}`,
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const addEmpty = () =>
|
|
294
|
-
lines.push(
|
|
295
|
-
`${ansi.cyan}│${ansi.reset}` +
|
|
296
|
-
" ".repeat(innerWidth) +
|
|
297
|
-
`${ansi.cyan}│${ansi.reset}`,
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
// Top border
|
|
301
|
-
lines.push(`${ansi.cyan}╭${"─".repeat(innerWidth)}╮${ansi.reset}`);
|
|
302
|
-
|
|
303
|
-
// Header
|
|
304
|
-
add(`${ansi.bold}${ansi.cyan}Badge Settings${ansi.reset}`);
|
|
305
|
-
add(`${ansi.dim}Configure badge generation behavior${ansi.reset}`);
|
|
306
|
-
addEmpty();
|
|
307
|
-
|
|
308
|
-
// Settings list
|
|
309
|
-
for (let i = 0; i < SETTINGS.length; i++) {
|
|
310
|
-
const item = SETTINGS[i];
|
|
311
|
-
const isSelected = i === this.selectedIndex && this.mode === "settings";
|
|
312
|
-
const selector = isSelected ? `${ansi.cyan}▸${ansi.reset}` : " ";
|
|
313
|
-
|
|
314
|
-
if (item.type === "boolean") {
|
|
315
|
-
const value = item.getValue(this.settings);
|
|
316
|
-
const toggle = value ? TOGGLE_ON : TOGGLE_OFF;
|
|
317
|
-
const labelColor = isSelected ? ansi.bold : ansi.dim;
|
|
318
|
-
|
|
319
|
-
add(
|
|
320
|
-
`${selector} ${toggle} ${labelColor}${item.label}${ansi.reset}`,
|
|
321
|
-
);
|
|
322
|
-
add(` ${ansi.gray}${item.description}${ansi.reset}`);
|
|
323
|
-
} else if (item.type === "model") {
|
|
324
|
-
const labelColor = isSelected ? ansi.bold : ansi.dim;
|
|
325
|
-
const modelDisplay = this.settings.generationModel;
|
|
326
|
-
|
|
327
|
-
add(
|
|
328
|
-
`${selector} ${ansi.yellow}⚙${ansi.reset} ${labelColor}${item.label}${ansi.reset}: ${ansi.white}${modelDisplay}${ansi.reset}`,
|
|
329
|
-
);
|
|
330
|
-
add(` ${ansi.gray}${item.description}${ansi.reset}`);
|
|
331
|
-
add(` ${ansi.dim}Enter to select model${ansi.reset}`);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Model picker (inline)
|
|
336
|
-
if (this.mode === "model-picker") {
|
|
337
|
-
addEmpty();
|
|
338
|
-
add(`${ansi.bold}${ansi.cyan}── Available Models ──${ansi.reset}`);
|
|
339
|
-
|
|
340
|
-
const allModels = this.getModelList();
|
|
341
|
-
const visibleLines = 10;
|
|
342
|
-
const start = this.modelScrollOffset;
|
|
343
|
-
const end = Math.min(start + visibleLines, allModels.length);
|
|
344
|
-
|
|
345
|
-
// Scroll indicator up
|
|
346
|
-
if (start > 0) {
|
|
347
|
-
add(` ${ansi.dim}▲ ${start} more above${ansi.reset}`);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
for (let i = start; i < end; i++) {
|
|
351
|
-
const m = allModels[i];
|
|
352
|
-
const isSelected = i === this.selectedIndex;
|
|
353
|
-
const selector = isSelected ? `${ansi.cyan}▸${ansi.reset}` : " ";
|
|
354
|
-
const labelColor = isSelected ? ansi.bold + ansi.white : ansi.dim;
|
|
355
|
-
|
|
356
|
-
add(
|
|
357
|
-
`${selector} ${labelColor}${m.label}${ansi.reset}`,
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Scroll indicator down
|
|
362
|
-
if (end < allModels.length) {
|
|
363
|
-
add(
|
|
364
|
-
` ${ansi.dim}▼ ${allModels.length - end} more below${ansi.reset}`,
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Footer
|
|
370
|
-
addEmpty();
|
|
371
|
-
|
|
372
|
-
if (this.mode === "model-picker") {
|
|
373
|
-
add(
|
|
374
|
-
`${ansi.dim}↑↓ navigate • Enter select • Esc cancel${ansi.reset}`,
|
|
375
|
-
);
|
|
376
|
-
} else {
|
|
377
|
-
add(
|
|
378
|
-
`${ansi.dim}↑↓ navigate • Space toggle • Enter select model • Esc close${ansi.reset}`,
|
|
379
|
-
);
|
|
380
|
-
add(`${ansi.dim}Config: .unipi/config/badge.json${ansi.reset}`);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Bottom border
|
|
384
|
-
lines.push(`${ansi.cyan}╰${"─".repeat(innerWidth)}╯${ansi.reset}`);
|
|
385
|
-
|
|
386
|
-
return lines;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pi-unipi/utility — Badge Settings Manager
|
|
3
|
-
*
|
|
4
|
-
* Thin wrappers over the unified settings manager (util-settings.json).
|
|
5
|
-
* Existing callers continue to work unchanged.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
readUtilSettings,
|
|
10
|
-
writeUtilSettings,
|
|
11
|
-
type BadgeSettingsSection,
|
|
12
|
-
} from "../diff/settings.js";
|
|
13
|
-
|
|
14
|
-
/** Badge settings interface (re-exports BadgeSettingsSection for backward compat) */
|
|
15
|
-
export type BadgeSettings = BadgeSettingsSection;
|
|
16
|
-
|
|
17
|
-
/** Default badge settings for formatBadgeSettings display */
|
|
18
|
-
const BADGE_CONFIG_FILE = ".unipi/config/util-settings.json";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Read badge settings from unified config.
|
|
22
|
-
* Returns defaults if file doesn't exist or is malformed.
|
|
23
|
-
*/
|
|
24
|
-
export function readBadgeSettings(): BadgeSettings {
|
|
25
|
-
return readUtilSettings().badge;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Write badge settings to unified config.
|
|
30
|
-
*/
|
|
31
|
-
export function writeBadgeSettings(settings: BadgeSettings): void {
|
|
32
|
-
const util = readUtilSettings();
|
|
33
|
-
util.badge = settings;
|
|
34
|
-
writeUtilSettings(util);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Update a single badge setting.
|
|
39
|
-
*/
|
|
40
|
-
export function updateBadgeSetting<K extends keyof BadgeSettings>(
|
|
41
|
-
key: K,
|
|
42
|
-
value: BadgeSettings[K],
|
|
43
|
-
): BadgeSettings {
|
|
44
|
-
const settings = readBadgeSettings();
|
|
45
|
-
settings[key] = value;
|
|
46
|
-
writeBadgeSettings(settings);
|
|
47
|
-
return settings;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Format badge settings for display.
|
|
52
|
-
*/
|
|
53
|
-
export function formatBadgeSettings(settings: BadgeSettings): string {
|
|
54
|
-
const toggle = (v: boolean) => (v ? "✓ enabled" : "✗ disabled");
|
|
55
|
-
return [
|
|
56
|
-
"## Badge Settings",
|
|
57
|
-
"",
|
|
58
|
-
`| Setting | Status | Description |`,
|
|
59
|
-
`|---------|--------|-------------|`,
|
|
60
|
-
`| Auto Generate | ${toggle(settings.autoGen)} | Generate name on first message |`,
|
|
61
|
-
`| Badge Enabled | ${toggle(settings.badgeEnabled)} | Show badge overlay |`,
|
|
62
|
-
`| Agent Tool | ${toggle(settings.agentTool)} | Allow agents to call set_session_name |`,
|
|
63
|
-
`| Generation Model | ${settings.generationModel} | Model for badge name generation |`,
|
|
64
|
-
"",
|
|
65
|
-
`Config: \`${BADGE_CONFIG_FILE}\``,
|
|
66
|
-
].join("\n");
|
|
67
|
-
}
|