@xynogen/pix-toolbox 0.1.6 → 0.1.8

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-toolbox",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Pi tool — gated tool toggle UI (/toolbox)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -41,7 +41,7 @@ describe("buildRows", () => {
41
41
 
42
42
  test("flags MCP tools", () => {
43
43
  const rows = buildRows([mcpToolInfo("ctx_search")]);
44
- expect(rows[0].mcp).toBe(true);
44
+ expect((rows[0] as ToolRow).mcp).toBe(true);
45
45
  });
46
46
 
47
47
  test("empty input returns empty", () => {
@@ -53,12 +53,7 @@ describe("buildRows", () => {
53
53
 
54
54
  describe("parseTargets", () => {
55
55
  test("splits on commas, spaces, newlines", () => {
56
- expect(parseTargets("ls, find grep\nfetch")).toEqual([
57
- "ls",
58
- "find",
59
- "grep",
60
- "fetch",
61
- ]);
56
+ expect(parseTargets("ls, find grep\nfetch")).toEqual(["ls", "find", "grep", "fetch"]);
62
57
  });
63
58
 
64
59
  test("dedupes", () => {
@@ -202,9 +197,7 @@ function makeHost(toolNames: string[]) {
202
197
  },
203
198
  emit(ev: string, payload: unknown, ctx?: unknown) {
204
199
  return Promise.all(
205
- (handlers[ev] ?? []).map((f) =>
206
- (f as (...args: unknown[]) => unknown)(payload, ctx),
207
- ),
200
+ (handlers[ev] ?? []).map((f) => (f as (...args: unknown[]) => unknown)(payload, ctx)),
208
201
  );
209
202
  },
210
203
  getAllTools() {
@@ -226,18 +219,13 @@ function makeHost(toolNames: string[]) {
226
219
  },
227
220
  appendEntry() {},
228
221
  registerTool() {},
229
- registerCommand(
230
- name: string,
231
- def: { handler: (...args: unknown[]) => unknown },
232
- ) {
222
+ registerCommand(name: string, def: { handler: (...args: unknown[]) => unknown }) {
233
223
  commands.push({ name, handler: def.handler });
234
224
  },
235
225
  } as never;
236
226
  const emit = (ev: string, payload: unknown, ctx?: unknown) =>
237
227
  Promise.all(
238
- (handlers[ev] ?? []).map((f) =>
239
- (f as (...args: unknown[]) => unknown)(payload, ctx),
240
- ),
228
+ (handlers[ev] ?? []).map((f) => (f as (...args: unknown[]) => unknown)(payload, ctx)),
241
229
  );
242
230
  return {
243
231
  pi,
@@ -260,6 +248,7 @@ function makeCtx() {
260
248
  }
261
249
 
262
250
  describe("/toolbox command", () => {
251
+ type Note = { text: string; level?: string };
263
252
  const ALL = ["read", "write", "bash", "grep", "find"];
264
253
 
265
254
  async function boot() {
@@ -281,28 +270,31 @@ describe("/toolbox command", () => {
281
270
  await host.command("toolbox")?.handler("", ctx);
282
271
  expect(notes.length).toBe(1);
283
272
  // only non-core tools shown, all start active
284
- expect(notes[0].text).toContain("✓ active grep");
285
- expect(notes[0].text).toContain("✓ active find");
273
+ const n0 = notes[0] as Note;
274
+ expect(n0.text).toContain("✓ active grep");
275
+ expect(n0.text).toContain("✓ active find");
286
276
  // core tools excluded from toolbox
287
- expect(notes[0].text).not.toContain(" read");
288
- expect(notes[0].text).not.toContain(" bash");
277
+ expect(n0.text).not.toContain(" read");
278
+ expect(n0.text).not.toContain(" bash");
289
279
  });
290
280
 
291
281
  test("/toolbox list shows non-core tools with status", async () => {
292
282
  const host = await boot();
293
283
  const { ctx, notes } = makeCtx();
294
284
  await host.command("toolbox")?.handler("list", ctx);
295
- expect(notes[0].text).toContain("grep");
296
- expect(notes[0].text).toContain("find");
297
- expect(notes[0].text).not.toContain(" bash");
285
+ const ln0 = notes[0] as Note;
286
+ expect(ln0.text).toContain("grep");
287
+ expect(ln0.text).toContain("find");
288
+ expect(ln0.text).not.toContain(" bash");
298
289
  });
299
290
 
300
291
  test("/toolbox list <query> filters", async () => {
301
292
  const host = await boot();
302
293
  const { ctx, notes } = makeCtx();
303
294
  await host.command("toolbox")?.handler("list fin", ctx);
304
- expect(notes[0].text).toContain("find");
305
- expect(notes[0].text).not.toContain("✓ active read");
295
+ const fn0 = notes[0] as Note;
296
+ expect(fn0.text).toContain("find");
297
+ expect(fn0.text).not.toContain("✓ active read");
306
298
  });
307
299
 
308
300
  test("opens interactive picker when ctx.ui.custom exists", async () => {
package/src/toolbox.ts CHANGED
@@ -39,12 +39,7 @@ import { frameLines, modalWidth } from "@xynogen/pix-pretty/modal-frame";
39
39
  // ─── Constants ──────────────────────────────────────────────────────────────
40
40
 
41
41
  /** Tools that can never be disabled — always prompt-visible. */
42
- export const CORE_TOOLS: ReadonlySet<string> = new Set([
43
- "bash",
44
- "edit",
45
- "read",
46
- "write",
47
- ]);
42
+ export const CORE_TOOLS: ReadonlySet<string> = new Set(["bash", "edit", "read", "write"]);
48
43
 
49
44
  // ─── Types ──────────────────────────────────────────────────────────────────
50
45
 
@@ -134,14 +129,10 @@ export function toggleTool(
134
129
 
135
130
  if (action === "enable") {
136
131
  const did = ops.onActivate(name);
137
- return did
138
- ? `Enabled ${name} — now prompt-visible.`
139
- : `${name} is already active.`;
132
+ return did ? `Enabled ${name} — now prompt-visible.` : `${name} is already active.`;
140
133
  }
141
134
  const did = ops.onDeactivate(name);
142
- return did
143
- ? `Disabled ${name} — hidden from prompt.`
144
- : `${name} is already gated.`;
135
+ return did ? `Disabled ${name} — hidden from prompt.` : `${name} is already gated.`;
145
136
  }
146
137
 
147
138
  // ─── Persistence ───────────────────────────────────────────────────────────
@@ -173,11 +164,7 @@ function createState(pi: ExtensionAPI) {
173
164
  try {
174
165
  const sp = getStatePath();
175
166
  mkdirSync(dirname(sp), { recursive: true });
176
- writeFileSync(
177
- sp,
178
- JSON.stringify({ enabledTools: [...enabledTools] }, null, 2),
179
- "utf-8",
180
- );
167
+ writeFileSync(sp, JSON.stringify({ enabledTools: [...enabledTools] }, null, 2), "utf-8");
181
168
  } catch (err) {
182
169
  console.warn("toolbox: file persist failed:", err);
183
170
  }
@@ -203,9 +190,7 @@ function createState(pi: ExtensionAPI) {
203
190
  const fileSaved = loadFromFile();
204
191
  if (fileSaved) {
205
192
  const validNames = new Set((pi.getAllTools() ?? []).map((t) => t.name));
206
- enabledTools = new Set(
207
- fileSaved.filter((n) => validNames.has(n) || CORE_TOOLS.has(n)),
208
- );
193
+ enabledTools = new Set(fileSaved.filter((n) => validNames.has(n) || CORE_TOOLS.has(n)));
209
194
  for (const ct of CORE_TOOLS) enabledTools.add(ct);
210
195
  initialized = true;
211
196
  apply();
@@ -233,9 +218,7 @@ function createState(pi: ExtensionAPI) {
233
218
 
234
219
  if (saved) {
235
220
  const validNames = new Set((pi.getAllTools() ?? []).map((t) => t.name));
236
- enabledTools = new Set(
237
- saved.filter((n) => validNames.has(n) || CORE_TOOLS.has(n)),
238
- );
221
+ enabledTools = new Set(saved.filter((n) => validNames.has(n) || CORE_TOOLS.has(n)));
239
222
  for (const ct of CORE_TOOLS) enabledTools.add(ct);
240
223
  } else {
241
224
  const names = (pi.getAllTools() ?? []).map((t) => t.name);
@@ -351,34 +334,24 @@ export default function registerToolbox(pi: ExtensionAPI): void {
351
334
  };
352
335
  }): Promise<void> {
353
336
  await ctx.ui.custom<null>(
354
- (
355
- tui: TUI,
356
- theme: Theme,
357
- _kb: KeybindingsManager,
358
- done: (r: null) => void,
359
- ) => {
337
+ (tui: TUI, theme: Theme, _kb: KeybindingsManager, done: (r: null) => void) => {
360
338
  const accent = "accent";
361
339
  const mute = (s: string) => theme.fg("muted", s);
362
340
 
363
341
  type RowState = "active" | "gated";
364
- const stateOf = (name: string): RowState =>
365
- ops.isActive(name) ? "active" : "gated";
342
+ const stateOf = (name: string): RowState => (ops.isActive(name) ? "active" : "gated");
366
343
 
367
344
  const labelFor = (r: ToolRow): string => {
368
345
  const active = stateOf(r.name) === "active";
369
346
  const marker = active ? " " : theme.fg("warning", "#");
370
- const name = active
371
- ? theme.fg("success", r.name)
372
- : theme.fg("dim", r.name);
347
+ const name = active ? theme.fg("success", r.name) : theme.fg("dim", r.name);
373
348
  const kind = mute(`[${r.mcp ? "MCP" : "tool"}]`);
374
349
  return `${marker} ${name} ${kind}`;
375
350
  };
376
351
 
377
352
  const descFor = (r: ToolRow): string => {
378
353
  const active = stateOf(r.name) === "active";
379
- const tag = active
380
- ? theme.fg("success", "active")
381
- : theme.fg("warning", "gated");
354
+ const tag = active ? theme.fg("success", "active") : theme.fg("warning", "gated");
382
355
  return `${tag} ${mute("·")} ${r.description || "(no description)"}`;
383
356
  };
384
357
 
@@ -394,10 +367,7 @@ export default function registerToolbox(pi: ExtensionAPI): void {
394
367
  };
395
368
 
396
369
  const allItems = rows.map(toItem);
397
- const widest = allItems.reduce(
398
- (w, it) => Math.max(w, visibleWidth(it.label)),
399
- 0,
400
- );
370
+ const widest = allItems.reduce((w, it) => Math.max(w, visibleWidth(it.label)), 0);
401
371
 
402
372
  const list = new SelectList(
403
373
  allItems,
@@ -480,10 +450,7 @@ export default function registerToolbox(pi: ExtensionAPI): void {
480
450
  ];
481
451
  if (statusText) lines.push(statusText);
482
452
  lines.push(
483
- theme.fg(
484
- "dim",
485
- "↑↓ navigate · e enable · d disable · space toggle · esc close",
486
- ),
453
+ theme.fg("dim", "↑↓ navigate · e enable · d disable · space toggle · esc close"),
487
454
  );
488
455
  return frameLines({
489
456
  width: mw,
@@ -499,16 +466,10 @@ export default function registerToolbox(pi: ExtensionAPI): void {
499
466
  handleInput(data: string) {
500
467
  if (matchesKey(data, Key.up) || matchesKey(data, Key.down)) {
501
468
  list.handleInput?.(data);
502
- } else if (
503
- matchesKey(data, Key.enter) ||
504
- matchesKey(data, Key.escape)
505
- ) {
469
+ } else if (matchesKey(data, Key.enter) || matchesKey(data, Key.escape)) {
506
470
  done(null);
507
471
  return;
508
- } else if (
509
- matchesKey(data, Key.space) ||
510
- matchesKey(data, Key.tab)
511
- ) {
472
+ } else if (matchesKey(data, Key.space) || matchesKey(data, Key.tab)) {
512
473
  flipSelected();
513
474
  } else {
514
475
  const printable = decodeKittyPrintable(data);
@@ -553,9 +514,7 @@ export default function registerToolbox(pi: ExtensionAPI): void {
553
514
  return;
554
515
  }
555
516
  const rows = getRows();
556
- const msg = targets
557
- .map((t) => toggleTool(verb, t, rows, ops))
558
- .join("\n");
517
+ const msg = targets.map((t) => toggleTool(verb, t, rows, ops)).join("\n");
559
518
  ctx.ui.notify(msg, "info");
560
519
  return;
561
520
  }