@xynogen/pix-optimizer 1.1.3 → 1.1.6
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 +10 -252
- package/src/index.ts +4 -4
- package/src/json.ts +8 -26
- package/src/opt.test.ts +35 -78
- package/src/opt.ts +180 -73
- package/src/ponytail.test.ts +0 -41
- package/src/ponytail.ts +11 -244
- package/src/rtk.test.ts +5 -0
- package/src/rtk.ts +7 -20
- 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.6",
|
|
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,11 @@
|
|
|
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 { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import {
|
|
18
|
-
Container,
|
|
19
|
-
type SettingItem,
|
|
20
|
-
SettingsList,
|
|
21
|
-
Text,
|
|
22
|
-
} from "@earendil-works/pi-tui";
|
|
23
13
|
import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
|
|
24
14
|
|
|
25
15
|
// ── Levels ────────────────────────────────────────────────────────────────────
|
|
@@ -37,24 +27,6 @@ export const LEVEL_NUMBERS: Record<string, Level> = {
|
|
|
37
27
|
"3": "ultra",
|
|
38
28
|
};
|
|
39
29
|
|
|
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
30
|
// ── Status labels ─────────────────────────────────────────────────────────────
|
|
59
31
|
|
|
60
32
|
export const STATUS_LABELS: Record<Exclude<Level, "off">, string> = {
|
|
@@ -158,55 +130,6 @@ export function toggleLevel(current: Level): Level {
|
|
|
158
130
|
return current === "off" ? "full" : "off";
|
|
159
131
|
}
|
|
160
132
|
|
|
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
133
|
// ── Pi extension ────────────────────────────────────────────────────────────
|
|
211
134
|
|
|
212
135
|
export function caveman(
|
|
@@ -214,26 +137,11 @@ export function caveman(
|
|
|
214
137
|
status: OptimizerStatus,
|
|
215
138
|
): OptimizerHandle {
|
|
216
139
|
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
140
|
|
|
232
141
|
// -- Status: report into the shared optimizer indicator. --
|
|
233
142
|
|
|
234
143
|
function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
|
|
235
|
-
|
|
236
|
-
status.set("caveman", on, ctx);
|
|
144
|
+
status.set("caveman", level !== "off", ctx);
|
|
237
145
|
}
|
|
238
146
|
|
|
239
147
|
// Inject caveman prompt via before_agent_start
|
|
@@ -244,25 +152,14 @@ export function caveman(
|
|
|
244
152
|
return { systemPrompt: `${prompt}\n\n${existing}` };
|
|
245
153
|
});
|
|
246
154
|
|
|
247
|
-
// -- Restore
|
|
155
|
+
// -- Restore live level from the session log on load --
|
|
248
156
|
|
|
249
157
|
pi.on("session_start", async (_event, ctx) => {
|
|
250
|
-
await ensureConfigLoaded();
|
|
251
|
-
|
|
252
|
-
let sessionLevel: Level | null = null;
|
|
253
158
|
for (const entry of ctx.sessionManager.getEntries()) {
|
|
254
159
|
if (entry.type === "custom" && entry.customType === "caveman-level") {
|
|
255
|
-
|
|
160
|
+
level = (entry.data as { level: Level })?.level ?? level;
|
|
256
161
|
}
|
|
257
162
|
}
|
|
258
|
-
|
|
259
|
-
if (sessionLevel !== null) {
|
|
260
|
-
level = sessionLevel;
|
|
261
|
-
} else if (config.defaultLevel !== "off") {
|
|
262
|
-
level = config.defaultLevel;
|
|
263
|
-
pi.appendEntry("caveman-level", { level });
|
|
264
|
-
}
|
|
265
|
-
|
|
266
163
|
syncStatus(ctx);
|
|
267
164
|
});
|
|
268
165
|
|
|
@@ -274,33 +171,14 @@ export function caveman(
|
|
|
274
171
|
});
|
|
275
172
|
pi.on("session_shutdown", async () => {});
|
|
276
173
|
|
|
277
|
-
// --
|
|
174
|
+
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
278
175
|
|
|
279
176
|
async function run(
|
|
280
|
-
|
|
177
|
+
value: string,
|
|
281
178
|
ctx: ExtensionCommandContext,
|
|
282
179
|
): 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
|
-
}
|
|
180
|
+
const resolved = resolveLevel(value);
|
|
181
|
+
if (resolved === null) return;
|
|
304
182
|
level = resolved;
|
|
305
183
|
|
|
306
184
|
pi.appendEntry("caveman-level", { level });
|
|
@@ -314,131 +192,11 @@ export function caveman(
|
|
|
314
192
|
);
|
|
315
193
|
}
|
|
316
194
|
|
|
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
195
|
return {
|
|
439
196
|
name: "caveman",
|
|
440
|
-
help: "caveman
|
|
197
|
+
help: "caveman — terse output",
|
|
198
|
+
values: LEVELS,
|
|
199
|
+
current: () => level,
|
|
441
200
|
run,
|
|
442
|
-
complete,
|
|
443
201
|
};
|
|
444
202
|
}
|
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
|
@@ -270,41 +270,23 @@ export function json(
|
|
|
270
270
|
return { systemPrompt: `${JSON_SYSTEM_PROMPT}\n\n${existing}` };
|
|
271
271
|
});
|
|
272
272
|
|
|
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
|
-
}
|
|
273
|
+
// -- Overlay value handler (called by the /optimizer overlay) --
|
|
288
274
|
|
|
289
275
|
async function run(
|
|
290
|
-
|
|
276
|
+
value: string,
|
|
291
277
|
ctx: ExtensionCommandContext,
|
|
292
278
|
): Promise<void> {
|
|
293
|
-
|
|
294
|
-
const arg = args.trim().toLowerCase();
|
|
295
|
-
if (arg === "on") enabled = true;
|
|
296
|
-
else if (arg === "off") enabled = false;
|
|
297
|
-
else enabled = !enabled; // bare toggles
|
|
279
|
+
enabled = value === "on";
|
|
298
280
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
281
|
+
syncStatus(ctx);
|
|
282
|
+
ctx.ui.notify(`JSON/TOON guidance ${enabled ? "on" : "off"}.`, "info");
|
|
302
283
|
}
|
|
303
284
|
|
|
304
285
|
return {
|
|
305
286
|
name: "toon",
|
|
306
|
-
help: "toon
|
|
287
|
+
help: "toon — jq+TOON guidance for dense JSON",
|
|
288
|
+
values: ["off", "on"],
|
|
289
|
+
current: () => (enabled ? "on" : "off"),
|
|
307
290
|
run,
|
|
308
|
-
complete,
|
|
309
291
|
};
|
|
310
292
|
}
|
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
|
});
|