@xynogen/pix-optimizer 1.1.4 → 1.1.7
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 +4 -1
- package/src/caveman.test.ts +0 -37
- package/src/caveman.ts +17 -252
- package/src/index.ts +4 -4
- package/src/json.ts +13 -26
- package/src/opt.test.ts +35 -78
- package/src/opt.ts +180 -73
- package/src/persist.test.ts +56 -0
- package/src/persist.ts +50 -0
- package/src/ponytail.test.ts +0 -41
- package/src/ponytail.ts +17 -244
- package/src/rtk.ts +11 -18
- package/src/status.ts +17 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-optimizer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + jq/TOON JSON compression + ponytail lazy-dev mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -47,6 +47,9 @@
|
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@xynogen/pix-pretty": "*"
|
|
52
|
+
},
|
|
50
53
|
"peerDependencies": {
|
|
51
54
|
"@earendil-works/pi-coding-agent": "*",
|
|
52
55
|
"@earendil-works/pi-tui": "*"
|
package/src/caveman.test.ts
CHANGED
|
@@ -2,11 +2,9 @@ import { describe, expect, it } from "bun:test";
|
|
|
2
2
|
import {
|
|
3
3
|
buildHelp,
|
|
4
4
|
buildPrompt,
|
|
5
|
-
DEFAULT_CONFIG,
|
|
6
5
|
LEVEL_NUMBERS,
|
|
7
6
|
LEVELS,
|
|
8
7
|
type Level,
|
|
9
|
-
parseConfig,
|
|
10
8
|
resolveLevel,
|
|
11
9
|
STATUS_LABELS,
|
|
12
10
|
STOP_ALIASES,
|
|
@@ -203,38 +201,3 @@ describe("buildPrompt", () => {
|
|
|
203
201
|
expect(lite).not.toContain("Abbreviate");
|
|
204
202
|
});
|
|
205
203
|
});
|
|
206
|
-
|
|
207
|
-
// ── parseConfig ───────────────────────────────────────────────────────────────
|
|
208
|
-
|
|
209
|
-
describe("parseConfig", () => {
|
|
210
|
-
it("parses valid config", () => {
|
|
211
|
-
const cfg = parseConfig({ defaultLevel: "ultra", showStatus: false });
|
|
212
|
-
expect(cfg.defaultLevel).toBe("ultra");
|
|
213
|
-
expect(cfg.showStatus).toBe(false);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("falls back to defaults for invalid defaultLevel", () => {
|
|
217
|
-
const cfg = parseConfig({ defaultLevel: "invalid", showStatus: true });
|
|
218
|
-
expect(cfg.defaultLevel).toBe(DEFAULT_CONFIG.defaultLevel);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("falls back to defaults for missing fields", () => {
|
|
222
|
-
const cfg = parseConfig({});
|
|
223
|
-
expect(cfg.defaultLevel).toBe(DEFAULT_CONFIG.defaultLevel);
|
|
224
|
-
expect(cfg.showStatus).toBe(DEFAULT_CONFIG.showStatus);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("falls back to defaults for null/undefined", () => {
|
|
228
|
-
const cfg = parseConfig(null);
|
|
229
|
-
expect(cfg.defaultLevel).toBe(DEFAULT_CONFIG.defaultLevel);
|
|
230
|
-
expect(cfg.showStatus).toBe(DEFAULT_CONFIG.showStatus);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("accepts all valid levels as defaultLevel", () => {
|
|
234
|
-
for (const l of LEVELS) {
|
|
235
|
-
expect(
|
|
236
|
-
parseConfig({ defaultLevel: l, showStatus: true }).defaultLevel,
|
|
237
|
-
).toBe(l);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
});
|
package/src/caveman.ts
CHANGED
|
@@ -5,21 +5,12 @@
|
|
|
5
5
|
* called by index.ts alongside rtk(pi).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
8
|
import type {
|
|
12
9
|
ExtensionAPI,
|
|
13
10
|
ExtensionCommandContext,
|
|
14
11
|
ExtensionContext,
|
|
15
12
|
} from "@earendil-works/pi-coding-agent";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
Container,
|
|
19
|
-
type SettingItem,
|
|
20
|
-
SettingsList,
|
|
21
|
-
Text,
|
|
22
|
-
} from "@earendil-works/pi-tui";
|
|
13
|
+
import { loadOptValue, saveOptValue } from "./persist.ts";
|
|
23
14
|
import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
|
|
24
15
|
|
|
25
16
|
// ── Levels ────────────────────────────────────────────────────────────────────
|
|
@@ -37,24 +28,6 @@ export const LEVEL_NUMBERS: Record<string, Level> = {
|
|
|
37
28
|
"3": "ultra",
|
|
38
29
|
};
|
|
39
30
|
|
|
40
|
-
const CAVEMAN_COMMAND_OPTIONS = [
|
|
41
|
-
{ value: "1", label: "1 (lite)", description: "Professional, no fluff" },
|
|
42
|
-
{ value: "2", label: "2 (full)", description: "Classic caveman" },
|
|
43
|
-
{ value: "3", label: "3 (ultra)", description: "Maximum compression" },
|
|
44
|
-
{ value: "lite", label: "lite", description: "Professional, no fluff" },
|
|
45
|
-
{ value: "full", label: "full", description: "Classic caveman" },
|
|
46
|
-
{ value: "ultra", label: "ultra", description: "Maximum compression" },
|
|
47
|
-
{
|
|
48
|
-
value: "micro",
|
|
49
|
-
label: "micro",
|
|
50
|
-
description: "Experimental prompt-minimized mode",
|
|
51
|
-
},
|
|
52
|
-
{ value: "off", label: "off", description: "Disable caveman mode" },
|
|
53
|
-
{ value: "stop", label: "stop", description: "Disable caveman mode" },
|
|
54
|
-
{ value: "quit", label: "quit", description: "Disable caveman mode" },
|
|
55
|
-
{ value: "config", label: "config", description: "Open settings dialog" },
|
|
56
|
-
] as const;
|
|
57
|
-
|
|
58
31
|
// ── Status labels ─────────────────────────────────────────────────────────────
|
|
59
32
|
|
|
60
33
|
export const STATUS_LABELS: Record<Exclude<Level, "off">, string> = {
|
|
@@ -158,55 +131,6 @@ export function toggleLevel(current: Level): Level {
|
|
|
158
131
|
return current === "off" ? "full" : "off";
|
|
159
132
|
}
|
|
160
133
|
|
|
161
|
-
// ── Config ────────────────────────────────────────────────────────────────────
|
|
162
|
-
|
|
163
|
-
export interface CavemanConfig {
|
|
164
|
-
/** Level to apply on new sessions. "off" means don't auto-enable. */
|
|
165
|
-
defaultLevel: Level;
|
|
166
|
-
/** Whether to show the status bar indicator. */
|
|
167
|
-
showStatus: boolean;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export const DEFAULT_CONFIG: CavemanConfig = {
|
|
171
|
-
defaultLevel: "full",
|
|
172
|
-
showStatus: true,
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const CONFIG_PATH = join(homedir(), ".pi", "agent", "caveman.json");
|
|
176
|
-
|
|
177
|
-
export function parseConfig(raw: unknown): CavemanConfig {
|
|
178
|
-
const parsed = raw as Record<string, unknown>;
|
|
179
|
-
return {
|
|
180
|
-
defaultLevel: LEVELS.includes(parsed?.defaultLevel as Level)
|
|
181
|
-
? (parsed.defaultLevel as Level)
|
|
182
|
-
: DEFAULT_CONFIG.defaultLevel,
|
|
183
|
-
showStatus:
|
|
184
|
-
typeof parsed?.showStatus === "boolean"
|
|
185
|
-
? parsed.showStatus
|
|
186
|
-
: DEFAULT_CONFIG.showStatus,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let _saveQueue: Promise<void> = Promise.resolve();
|
|
191
|
-
|
|
192
|
-
async function loadConfig(): Promise<CavemanConfig> {
|
|
193
|
-
try {
|
|
194
|
-
const raw = await readFile(CONFIG_PATH, "utf8");
|
|
195
|
-
return parseConfig(JSON.parse(raw));
|
|
196
|
-
} catch {
|
|
197
|
-
return { ...DEFAULT_CONFIG };
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async function saveConfig(config: CavemanConfig): Promise<void> {
|
|
202
|
-
const snapshot = `${JSON.stringify(config, null, 2)}\n`;
|
|
203
|
-
_saveQueue = _saveQueue.then(async () => {
|
|
204
|
-
await mkdir(join(homedir(), ".pi", "agent"), { recursive: true });
|
|
205
|
-
await writeFile(CONFIG_PATH, snapshot, "utf8");
|
|
206
|
-
});
|
|
207
|
-
return _saveQueue;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
134
|
// ── Pi extension ────────────────────────────────────────────────────────────
|
|
211
135
|
|
|
212
136
|
export function caveman(
|
|
@@ -214,26 +138,11 @@ export function caveman(
|
|
|
214
138
|
status: OptimizerStatus,
|
|
215
139
|
): OptimizerHandle {
|
|
216
140
|
let level: Level = "off";
|
|
217
|
-
let config: CavemanConfig = { ...DEFAULT_CONFIG };
|
|
218
|
-
let configLoadPromise: Promise<void> | null = null;
|
|
219
|
-
|
|
220
|
-
const ensureConfigLoaded = async () => {
|
|
221
|
-
if (!configLoadPromise) {
|
|
222
|
-
configLoadPromise = (async () => {
|
|
223
|
-
config = await loadConfig();
|
|
224
|
-
if (level === "off" && config.defaultLevel !== "off") {
|
|
225
|
-
level = config.defaultLevel;
|
|
226
|
-
}
|
|
227
|
-
})();
|
|
228
|
-
}
|
|
229
|
-
await configLoadPromise;
|
|
230
|
-
};
|
|
231
141
|
|
|
232
142
|
// -- Status: report into the shared optimizer indicator. --
|
|
233
143
|
|
|
234
144
|
function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
|
|
235
|
-
|
|
236
|
-
status.set("caveman", on, ctx);
|
|
145
|
+
status.set("caveman", level !== "off", ctx);
|
|
237
146
|
}
|
|
238
147
|
|
|
239
148
|
// Inject caveman prompt via before_agent_start
|
|
@@ -244,25 +153,19 @@ export function caveman(
|
|
|
244
153
|
return { systemPrompt: `${prompt}\n\n${existing}` };
|
|
245
154
|
});
|
|
246
155
|
|
|
247
|
-
// -- Restore
|
|
156
|
+
// -- Restore live level from the session log on load --
|
|
248
157
|
|
|
249
158
|
pi.on("session_start", async (_event, ctx) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
159
|
+
// Session log first (survives in-session branch nav), then disk (survives
|
|
160
|
+
// a full quit/restart). Disk wins when present so a new session restores
|
|
161
|
+
// the last chosen level instead of defaulting to off.
|
|
253
162
|
for (const entry of ctx.sessionManager.getEntries()) {
|
|
254
163
|
if (entry.type === "custom" && entry.customType === "caveman-level") {
|
|
255
|
-
|
|
164
|
+
level = (entry.data as { level: Level })?.level ?? level;
|
|
256
165
|
}
|
|
257
166
|
}
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
level = sessionLevel;
|
|
261
|
-
} else if (config.defaultLevel !== "off") {
|
|
262
|
-
level = config.defaultLevel;
|
|
263
|
-
pi.appendEntry("caveman-level", { level });
|
|
264
|
-
}
|
|
265
|
-
|
|
167
|
+
const saved = loadOptValue("caveman");
|
|
168
|
+
if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
|
|
266
169
|
syncStatus(ctx);
|
|
267
170
|
});
|
|
268
171
|
|
|
@@ -274,36 +177,18 @@ export function caveman(
|
|
|
274
177
|
});
|
|
275
178
|
pi.on("session_shutdown", async () => {});
|
|
276
179
|
|
|
277
|
-
// --
|
|
180
|
+
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
278
181
|
|
|
279
182
|
async function run(
|
|
280
|
-
|
|
183
|
+
value: string,
|
|
281
184
|
ctx: ExtensionCommandContext,
|
|
282
185
|
): Promise<void> {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
// No argument → show help
|
|
286
|
-
if (!arg) {
|
|
287
|
-
ctx.ui.notify(buildHelp(level), "info");
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (arg === "config") {
|
|
292
|
-
await openConfig(ctx);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const resolved = resolveLevel(arg);
|
|
297
|
-
if (resolved === null) {
|
|
298
|
-
ctx.ui.notify(
|
|
299
|
-
`Unknown: "${arg}". Use 1/2/3, ${LEVELS.join(", ")}, stop, quit, or config`,
|
|
300
|
-
"error",
|
|
301
|
-
);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
186
|
+
const resolved = resolveLevel(value);
|
|
187
|
+
if (resolved === null) return;
|
|
304
188
|
level = resolved;
|
|
305
189
|
|
|
306
190
|
pi.appendEntry("caveman-level", { level });
|
|
191
|
+
saveOptValue("caveman", level);
|
|
307
192
|
syncStatus(ctx);
|
|
308
193
|
|
|
309
194
|
ctx.ui.notify(
|
|
@@ -314,131 +199,11 @@ export function caveman(
|
|
|
314
199
|
);
|
|
315
200
|
}
|
|
316
201
|
|
|
317
|
-
function complete(prefix: string) {
|
|
318
|
-
const normalized = prefix.trim().toLowerCase();
|
|
319
|
-
const items = CAVEMAN_COMMAND_OPTIONS.filter((item) =>
|
|
320
|
-
item.value.startsWith(normalized),
|
|
321
|
-
);
|
|
322
|
-
return items.length > 0 ? items.map((i) => ({ ...i })) : null;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// -- /caveman config: interactive SettingsList --
|
|
326
|
-
|
|
327
|
-
async function openConfig(ctx: ExtensionContext) {
|
|
328
|
-
await ensureConfigLoaded();
|
|
329
|
-
|
|
330
|
-
await ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
331
|
-
const items: SettingItem[] = [
|
|
332
|
-
{
|
|
333
|
-
id: "defaultLevel",
|
|
334
|
-
label: "Default level for new sessions",
|
|
335
|
-
currentValue: config.defaultLevel,
|
|
336
|
-
values: [...LEVELS],
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
id: "showStatus",
|
|
340
|
-
label: "Show status bar",
|
|
341
|
-
currentValue: config.showStatus ? "on" : "off",
|
|
342
|
-
values: ["on", "off"],
|
|
343
|
-
},
|
|
344
|
-
];
|
|
345
|
-
|
|
346
|
-
const container = new Container();
|
|
347
|
-
container.addChild(
|
|
348
|
-
new Text(theme.fg("accent", theme.bold(" Caveman Config")), 0, 0),
|
|
349
|
-
);
|
|
350
|
-
container.addChild(
|
|
351
|
-
new Text(theme.fg("dim", " Saved to ~/.pi/agent/caveman.json"), 0, 0),
|
|
352
|
-
);
|
|
353
|
-
container.addChild(
|
|
354
|
-
new Text(
|
|
355
|
-
theme.fg("dim", " Default level applies to future sessions."),
|
|
356
|
-
0,
|
|
357
|
-
0,
|
|
358
|
-
),
|
|
359
|
-
);
|
|
360
|
-
container.addChild(new Text("", 0, 0));
|
|
361
|
-
|
|
362
|
-
const applySettingChange = (id: string, newValue: string) => {
|
|
363
|
-
if (id === "defaultLevel" && LEVELS.includes(newValue as Level)) {
|
|
364
|
-
config.defaultLevel = newValue as Level;
|
|
365
|
-
} else if (id === "showStatus") {
|
|
366
|
-
config.showStatus = newValue === "on";
|
|
367
|
-
}
|
|
368
|
-
saveConfig(config);
|
|
369
|
-
syncStatus(ctx);
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const settingsList = new SettingsList(
|
|
373
|
-
items,
|
|
374
|
-
Math.min(items.length + 2, 10),
|
|
375
|
-
getSettingsListTheme(),
|
|
376
|
-
applySettingChange,
|
|
377
|
-
() => done(undefined),
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
container.addChild(settingsList);
|
|
381
|
-
container.addChild(
|
|
382
|
-
new Text(
|
|
383
|
-
theme.fg("dim", " ←→/hl/tab change • ↑↓/jk move • esc close"),
|
|
384
|
-
0,
|
|
385
|
-
0,
|
|
386
|
-
),
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
const cycleSelectedValue = (direction: -1 | 1) => {
|
|
390
|
-
const selectedIndex = (
|
|
391
|
-
settingsList as unknown as { selectedIndex: number }
|
|
392
|
-
).selectedIndex;
|
|
393
|
-
const item = items[selectedIndex];
|
|
394
|
-
if (!item?.values?.length) return;
|
|
395
|
-
const currentIndex = item.values.indexOf(item.currentValue);
|
|
396
|
-
const nextIndex =
|
|
397
|
-
(currentIndex + direction + item.values.length) % item.values.length;
|
|
398
|
-
const newValue = item.values[nextIndex]!;
|
|
399
|
-
item.currentValue = newValue;
|
|
400
|
-
settingsList.updateValue(item.id, newValue);
|
|
401
|
-
applySettingChange(item.id, newValue);
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
return {
|
|
405
|
-
render: (w: number) => container.render(w),
|
|
406
|
-
invalidate: () => container.invalidate(),
|
|
407
|
-
handleInput: (data: string) => {
|
|
408
|
-
if (data === "j") data = "\u001b[B";
|
|
409
|
-
else if (data === "k") data = "\u001b[A";
|
|
410
|
-
else if (data === "h") {
|
|
411
|
-
cycleSelectedValue(-1);
|
|
412
|
-
_tui.requestRender();
|
|
413
|
-
return;
|
|
414
|
-
} else if (data === "l" || data === "\u001b[C" || data === "\t") {
|
|
415
|
-
cycleSelectedValue(1);
|
|
416
|
-
_tui.requestRender();
|
|
417
|
-
return;
|
|
418
|
-
} else if (data === "\u001b[D") {
|
|
419
|
-
cycleSelectedValue(-1);
|
|
420
|
-
_tui.requestRender();
|
|
421
|
-
return;
|
|
422
|
-
} else if (data === "\u001b" || data === "q") {
|
|
423
|
-
done(undefined);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// SettingsList.handleInput now returns void; completion/cancel is
|
|
428
|
-
// signalled through the onChange/onCancel callbacks passed to the
|
|
429
|
-
// constructor (onCancel -> done(undefined)). Just forward input and
|
|
430
|
-
// re-render.
|
|
431
|
-
settingsList.handleInput(data);
|
|
432
|
-
_tui.requestRender();
|
|
433
|
-
},
|
|
434
|
-
};
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
202
|
return {
|
|
439
203
|
name: "caveman",
|
|
440
|
-
help: "caveman
|
|
204
|
+
help: "caveman — terse output",
|
|
205
|
+
values: LEVELS,
|
|
206
|
+
current: () => level,
|
|
441
207
|
run,
|
|
442
|
-
complete,
|
|
443
208
|
};
|
|
444
209
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - ponytail: lazy-senior-dev system prompt (minimal code, YAGNI)
|
|
9
9
|
*
|
|
10
10
|
* They share ONE status-bar cell ( , only enabled tools shown) and ONE
|
|
11
|
-
* command (/
|
|
12
|
-
* module, then registers the
|
|
11
|
+
* command (/optimizer — an interactive overlay). index.ts wires lifecycle hooks
|
|
12
|
+
* via each module, then registers the overlay command from their handles.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -29,7 +29,7 @@ export default function optimizer(pi: ExtensionAPI) {
|
|
|
29
29
|
const status = new OptimizerStatus();
|
|
30
30
|
|
|
31
31
|
// Each module registers its own lifecycle hooks and returns a handle the
|
|
32
|
-
//
|
|
32
|
+
// /optimizer overlay renders + drives.
|
|
33
33
|
const handles: Record<OptimizerTool, OptimizerHandle> = {
|
|
34
34
|
caveman: caveman(pi, status),
|
|
35
35
|
rtk: rtk(pi, status),
|
|
@@ -37,7 +37,7 @@ export default function optimizer(pi: ExtensionAPI) {
|
|
|
37
37
|
ponytail: ponytail(pi, status),
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
registerOptCommand(pi, handles);
|
|
40
|
+
registerOptCommand(pi, handles, status);
|
|
41
41
|
|
|
42
42
|
// Strip model-guidance warnings injected by pi-lens into tool_result content.
|
|
43
43
|
// These strings (BLIND WRITE, THRASHING) are directives for the LLM, not
|
package/src/json.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
ExtensionCommandContext,
|
|
23
23
|
ExtensionContext,
|
|
24
24
|
} from "@earendil-works/pi-coding-agent";
|
|
25
|
+
import { loadOptValue, saveOptValue } from "./persist.ts";
|
|
25
26
|
import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
|
|
26
27
|
|
|
27
28
|
// ── System prompt ───────────────────────────────────────────────────────────
|
|
@@ -225,6 +226,9 @@ export function json(
|
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
pi.on("session_start", async (_event, ctx) => {
|
|
229
|
+
// Restore the user's on/off choice from disk (survives quit/restart).
|
|
230
|
+
const saved = loadOptValue("toon");
|
|
231
|
+
if (saved === "on" || saved === "off") enabled = saved === "on";
|
|
228
232
|
syncStatus(ctx);
|
|
229
233
|
});
|
|
230
234
|
pi.on("agent_start", async (_event, ctx) => {
|
|
@@ -270,41 +274,24 @@ export function json(
|
|
|
270
274
|
return { systemPrompt: `${JSON_SYSTEM_PROMPT}\n\n${existing}` };
|
|
271
275
|
});
|
|
272
276
|
|
|
273
|
-
// --
|
|
274
|
-
|
|
275
|
-
function complete(prefix: string) {
|
|
276
|
-
const items = [
|
|
277
|
-
{ value: "on", label: "on", description: "Force JSON/TOON guidance on" },
|
|
278
|
-
{
|
|
279
|
-
value: "off",
|
|
280
|
-
label: "off",
|
|
281
|
-
description: "Force JSON/TOON guidance off",
|
|
282
|
-
},
|
|
283
|
-
];
|
|
284
|
-
const n = prefix.trim().toLowerCase();
|
|
285
|
-
const filtered = items.filter((i) => i.value.startsWith(n));
|
|
286
|
-
return filtered.length > 0 ? filtered : null;
|
|
287
|
-
}
|
|
277
|
+
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
288
278
|
|
|
289
279
|
async function run(
|
|
290
|
-
|
|
280
|
+
value: string,
|
|
291
281
|
ctx: ExtensionCommandContext,
|
|
292
282
|
): Promise<void> {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (arg === "on") enabled = true;
|
|
296
|
-
else if (arg === "off") enabled = false;
|
|
297
|
-
else enabled = !enabled; // bare toggles
|
|
283
|
+
enabled = value === "on";
|
|
284
|
+
saveOptValue("toon", enabled ? "on" : "off");
|
|
298
285
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
286
|
+
syncStatus(ctx);
|
|
287
|
+
ctx.ui.notify(`JSON/TOON guidance ${enabled ? "on" : "off"}.`, "info");
|
|
302
288
|
}
|
|
303
289
|
|
|
304
290
|
return {
|
|
305
291
|
name: "toon",
|
|
306
|
-
help: "toon
|
|
292
|
+
help: "toon — jq+TOON guidance for dense JSON",
|
|
293
|
+
values: ["off", "on"],
|
|
294
|
+
current: () => (enabled ? "on" : "off"),
|
|
307
295
|
run,
|
|
308
|
-
complete,
|
|
309
296
|
};
|
|
310
297
|
}
|
package/src/opt.test.ts
CHANGED
|
@@ -1,103 +1,60 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { buildOptHelp,
|
|
2
|
+
import { buildOptHelp, levelBar } from "./opt.ts";
|
|
3
3
|
import type { OptimizerHandle, OptimizerTool } from "./status.ts";
|
|
4
4
|
|
|
5
|
-
/** Build a handle set with
|
|
5
|
+
/** Build a handle set with fixed current values + value lists. */
|
|
6
6
|
function fakeHandles(): Record<OptimizerTool, OptimizerHandle> {
|
|
7
|
-
const mk = (
|
|
7
|
+
const mk = (
|
|
8
|
+
name: OptimizerTool,
|
|
9
|
+
current: string,
|
|
10
|
+
values: string[],
|
|
11
|
+
): OptimizerHandle => ({
|
|
8
12
|
name,
|
|
9
|
-
help: `${name} help`,
|
|
13
|
+
help: `${name} — ${name} help`,
|
|
14
|
+
values,
|
|
15
|
+
current: () => current,
|
|
10
16
|
run: () => {},
|
|
11
|
-
complete: (prefix: string) =>
|
|
12
|
-
["on", "off"]
|
|
13
|
-
.filter((v) => v.startsWith(prefix.trim().toLowerCase()))
|
|
14
|
-
.map((v) => ({ value: v, label: v, description: v })),
|
|
15
17
|
});
|
|
16
18
|
return {
|
|
17
|
-
caveman: mk("caveman"),
|
|
18
|
-
rtk: mk("rtk"),
|
|
19
|
-
toon: mk("toon"),
|
|
20
|
-
ponytail: mk("ponytail"),
|
|
19
|
+
caveman: mk("caveman", "full", ["off", "lite", "full", "ultra", "micro"]),
|
|
20
|
+
rtk: mk("rtk", "on", ["off", "on"]),
|
|
21
|
+
toon: mk("toon", "off", ["off", "on"]),
|
|
22
|
+
ponytail: mk("ponytail", "off", ["off", "lite", "full", "ultra"]),
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
describe("
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
expect(parseInvocation(" ")).toEqual({ name: "", rest: "" });
|
|
28
|
-
});
|
|
26
|
+
describe("levelBar", () => {
|
|
27
|
+
const PONYTAIL = ["off", "lite", "full", "ultra"];
|
|
28
|
+
const BINARY = ["off", "on"];
|
|
29
29
|
|
|
30
|
-
it("
|
|
31
|
-
expect(
|
|
32
|
-
expect(
|
|
30
|
+
it("fills segments by value position in a multi-step ladder", () => {
|
|
31
|
+
expect(levelBar("off", PONYTAIL)).toBe("▱▱▱");
|
|
32
|
+
expect(levelBar("lite", PONYTAIL)).toBe("▰▱▱");
|
|
33
|
+
expect(levelBar("full", PONYTAIL)).toBe("▰▰▱");
|
|
34
|
+
expect(levelBar("ultra", PONYTAIL)).toBe("▰▰▰");
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
it("
|
|
36
|
-
expect(
|
|
37
|
-
|
|
38
|
-
rest: "ultra",
|
|
39
|
-
});
|
|
40
|
-
expect(parseInvocation("rtk on extra")).toEqual({
|
|
41
|
-
name: "rtk",
|
|
42
|
-
rest: "on extra",
|
|
43
|
-
});
|
|
37
|
+
it("renders a single segment for binary tools", () => {
|
|
38
|
+
expect(levelBar("off", BINARY)).toBe("▱");
|
|
39
|
+
expect(levelBar("on", BINARY)).toBe("▰");
|
|
44
40
|
});
|
|
45
|
-
});
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const handles = fakeHandles();
|
|
50
|
-
const out = completeInvocation("r", handles);
|
|
51
|
-
expect(out?.map((i) => i.value)).toEqual(["rtk"]);
|
|
42
|
+
it("treats an unknown value as off (no fill)", () => {
|
|
43
|
+
expect(levelBar("???", PONYTAIL)).toBe("▱▱▱");
|
|
52
44
|
});
|
|
53
45
|
|
|
54
|
-
it("returns
|
|
55
|
-
|
|
56
|
-
const out = completeInvocation("", handles);
|
|
57
|
-
expect(out?.map((i) => i.value)).toEqual([
|
|
58
|
-
"caveman",
|
|
59
|
-
"rtk",
|
|
60
|
-
"toon",
|
|
61
|
-
"ponytail",
|
|
62
|
-
]);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("delegates to the tool completer after the name", () => {
|
|
66
|
-
const handles = fakeHandles();
|
|
67
|
-
const out = completeInvocation("rtk o", handles);
|
|
68
|
-
expect(out?.map((i) => i.value)).toEqual(["on", "off"]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("null for unknown tool", () => {
|
|
72
|
-
const handles = fakeHandles();
|
|
73
|
-
expect(completeInvocation("nope x", handles)).toBeNull();
|
|
46
|
+
it("returns empty string when there are no non-off steps", () => {
|
|
47
|
+
expect(levelBar("off", ["off"])).toBe("");
|
|
74
48
|
});
|
|
75
49
|
});
|
|
76
50
|
|
|
77
51
|
describe("buildOptHelp", () => {
|
|
78
|
-
it("lists every tool
|
|
52
|
+
it("lists every tool with its current value", () => {
|
|
79
53
|
const help = buildOptHelp(fakeHandles());
|
|
80
|
-
expect(help).toContain("
|
|
81
|
-
expect(help).toContain("
|
|
82
|
-
expect(help).toContain("
|
|
83
|
-
expect(help).toContain("
|
|
84
|
-
expect(help).toContain("
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe("dispatch via run", () => {
|
|
89
|
-
it("routes rest to the matching handle", () => {
|
|
90
|
-
const handles = fakeHandles();
|
|
91
|
-
let received: string | null = null;
|
|
92
|
-
handles.rtk.run = (args: string) => {
|
|
93
|
-
received = args;
|
|
94
|
-
};
|
|
95
|
-
const { name, rest } = parseInvocation("rtk off");
|
|
96
|
-
expect(name).toBe("rtk");
|
|
97
|
-
void handles[name as OptimizerTool].run(rest, {} as never);
|
|
98
|
-
// `received` is mutated inside the run() callback above; TS narrows it to
|
|
99
|
-
// `null` here since it can't see the side effect, so read it through an
|
|
100
|
-
// untyped cast before asserting.
|
|
101
|
-
expect(received as string | null).toBe("off");
|
|
54
|
+
expect(help).toContain("caveman: full");
|
|
55
|
+
expect(help).toContain("rtk: on");
|
|
56
|
+
expect(help).toContain("toon: off");
|
|
57
|
+
expect(help).toContain("ponytail: off");
|
|
58
|
+
expect(help).toContain("caveman help");
|
|
102
59
|
});
|
|
103
60
|
});
|