@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-optimizer",
3
- "version": "1.1.4",
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": "*"
@@ -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 { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
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
- const on = level !== "off" && config.showStatus;
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 state on session load --
156
+ // -- Restore live level from the session log on load --
248
157
 
249
158
  pi.on("session_start", async (_event, ctx) => {
250
- await ensureConfigLoaded();
251
-
252
- let sessionLevel: Level | null = null;
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
- sessionLevel = (entry.data as { level: Level })?.level ?? null;
164
+ level = (entry.data as { level: Level })?.level ?? level;
256
165
  }
257
166
  }
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
-
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
- // -- Subcommand handler (dispatched by the merged /opt router) --
180
+ // -- Overlay value handler (called by the /optimizer overlay) --
278
181
 
279
182
  async function run(
280
- args: string,
183
+ value: string,
281
184
  ctx: ExtensionCommandContext,
282
185
  ): Promise<void> {
283
- const arg = args.trim().toLowerCase();
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 <1|2|3|lite|full|ultra|micro|off|config> — terse output",
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 (/opt <tool> [args]). index.ts wires lifecycle hooks via each
12
- * module, then registers the merged command from their handles.
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
- // merged /opt command dispatches to.
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
- // -- Subcommand handler (dispatched by the merged /opt router) --
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
- args: string,
280
+ value: string,
291
281
  ctx: ExtensionCommandContext,
292
282
  ): 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
283
+ enabled = value === "on";
284
+ saveOptValue("toon", enabled ? "on" : "off");
298
285
 
299
- syncStatus(ctx);
300
- ctx.ui.notify(`JSON/TOON guidance ${enabled ? "on" : "off"}.`, "info");
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 [on|off] — jq+TOON guidance for dense JSON",
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, completeInvocation, parseInvocation } from "./opt.ts";
2
+ import { buildOptHelp, levelBar } from "./opt.ts";
3
3
  import type { OptimizerHandle, OptimizerTool } from "./status.ts";
4
4
 
5
- /** Build a handle set with spy-able run/complete. */
5
+ /** Build a handle set with fixed current values + value lists. */
6
6
  function fakeHandles(): Record<OptimizerTool, OptimizerHandle> {
7
- const mk = (name: OptimizerTool): OptimizerHandle => ({
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("parseInvocation", () => {
25
- it("empty args → empty name", () => {
26
- expect(parseInvocation("")).toEqual({ name: "", rest: "" });
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("single token → name only", () => {
31
- expect(parseInvocation("rtk")).toEqual({ name: "rtk", rest: "" });
32
- expect(parseInvocation(" RTK ")).toEqual({ name: "rtk", rest: "" });
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("splits name from rest", () => {
36
- expect(parseInvocation("caveman ultra")).toEqual({
37
- name: "caveman",
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
- describe("completeInvocation", () => {
48
- it("completes tool names before any space", () => {
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 all tool names for empty prefix", () => {
55
- const handles = fakeHandles();
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's help line", () => {
52
+ it("lists every tool with its current value", () => {
79
53
  const help = buildOptHelp(fakeHandles());
80
- expect(help).toContain("/opt caveman help");
81
- expect(help).toContain("/opt rtk help");
82
- expect(help).toContain("/opt toon help");
83
- expect(help).toContain("/opt ponytail help");
84
- expect(help).toContain("Usage: /opt <tool> [args]");
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
  });