@xynogen/pix-optimizer 1.1.16 → 1.1.17

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/README.md CHANGED
@@ -44,7 +44,7 @@ Switch the style live from the **`/optimizer` overlay** — a fifth `icons` row
44
44
  at the bottom cycles `nerd → unicode → ascii` with `←→`; the choice persists to
45
45
  `~/.pi/agent/optimizer.json`.
46
46
 
47
- Icons follow the **global** `pix-pretty` mode — set via `/pretty` or `PRETTY_ICONS`
47
+ Icons follow the **global** `pix-pretty` mode — set via `/pix` or `PRETTY_ICONS`
48
48
  env var. The optimizer no longer has its own toggle.
49
49
 
50
50
  ## Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-optimizer",
3
- "version": "1.1.16",
3
+ "version": "1.1.17",
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",
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it, mock } from "bun:test";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ import { canExecute } from "./capability.ts";
4
+
5
+ function executor(impl: ExtensionAPI["exec"]): Pick<ExtensionAPI, "exec"> {
6
+ return { exec: impl };
7
+ }
8
+
9
+ describe("canExecute", () => {
10
+ it("runs the requested capability probe without a shell", async () => {
11
+ const exec = mock(async () => ({
12
+ stdout: "jq-1.7",
13
+ stderr: "",
14
+ code: 0,
15
+ killed: false,
16
+ }));
17
+
18
+ expect(await canExecute(executor(exec), "jq", ["--version"])).toBe(true);
19
+ expect(exec).toHaveBeenCalledWith("jq", ["--version"], { timeout: 3000 });
20
+ });
21
+
22
+ it("supports a custom timeout", async () => {
23
+ const exec = mock(async () => ({ stdout: "2.3.0", stderr: "", code: 0, killed: false }));
24
+
25
+ expect(await canExecute(executor(exec), "toon", ["--version"], 1000)).toBe(true);
26
+ expect(exec).toHaveBeenCalledWith("toon", ["--version"], { timeout: 1000 });
27
+ });
28
+
29
+ it("returns false for a non-zero exit", async () => {
30
+ const exec = mock(async () => ({ stdout: "", stderr: "failed", code: 1, killed: false }));
31
+
32
+ expect(await canExecute(executor(exec), "jq", ["--version"])).toBe(false);
33
+ });
34
+
35
+ it("returns false when the executable cannot be spawned", async () => {
36
+ const exec = mock(async () => {
37
+ throw new Error("ENOENT");
38
+ });
39
+
40
+ expect(await canExecute(executor(exec), "toon", ["--version"])).toBe(false);
41
+ });
42
+ });
@@ -0,0 +1,16 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
3
+ /** Test whether a command can perform a harmless operation through Pi's executor. */
4
+ export async function canExecute(
5
+ pi: Pick<ExtensionAPI, "exec">,
6
+ command: string,
7
+ args: string[],
8
+ timeout = 3000,
9
+ ): Promise<boolean> {
10
+ try {
11
+ const result = await pi.exec(command, args, { timeout });
12
+ return result.code === 0;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
package/src/index.ts CHANGED
@@ -23,7 +23,7 @@ import { type OptimizerHandle, OptimizerStatus, type OptimizerTool } from "./sta
23
23
  import { filterModelWarnings } from "./tool-result-filter.ts";
24
24
 
25
25
  export default function optimizer(pi: ExtensionAPI) {
26
- // Icons follow the global pix-pretty mode (set via /pretty); no local state.
26
+ // Icons follow the global pix-pretty mode (set via /pix); no local state.
27
27
  const status = new OptimizerStatus();
28
28
 
29
29
  // Each module registers its own lifecycle hooks and returns a handle the
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 { canExecute } from "./capability.ts";
25
26
  import { loadOptValue, saveOptValue } from "./persist.ts";
26
27
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
27
28
 
@@ -241,12 +242,10 @@ export function json(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle
241
242
 
242
243
  // Probe once on first JSON-relevant prompt.
243
244
  if (jqAvailable === null || toonAvailable === null) {
244
- const [jqRes, toonRes] = await Promise.all([
245
- pi.exec("which", ["jq"], { timeout: 1000 }).catch(() => ({ code: 1 })),
246
- pi.exec("which", ["toon"], { timeout: 1000 }).catch(() => ({ code: 1 })),
245
+ [jqAvailable, toonAvailable] = await Promise.all([
246
+ canExecute(pi, "jq", ["--version"]),
247
+ canExecute(pi, "toon", ["--version"]),
247
248
  ]);
248
- jqAvailable = jqRes.code === 0;
249
- toonAvailable = toonRes.code === 0;
250
249
  if (!jqAvailable)
251
250
  ctx.ui.notify(
252
251
  "jq not found — JSON/TOON guidance disabled. Install: sudo apt install jq or brew install jq",
package/src/rtk.test.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { describe, expect, it } from "bun:test";
1
+ import { describe, expect, it, mock } from "bun:test";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
3
  import {
3
4
  applyRtkRewrite,
4
5
  type BashCallEvent,
5
6
  buildSudoBlockReason,
6
7
  detectSudoSegments,
8
+ probeRtkAvailability,
7
9
  rewriteChain,
8
10
  splitChain,
9
11
  } from "./rtk.ts";
@@ -13,6 +15,34 @@ function bashEvent(command: string): BashCallEvent {
13
15
  return { toolName: "bash", input: { command } };
14
16
  }
15
17
 
18
+ describe("probeRtkAvailability", () => {
19
+ it("probes rtk directly and accepts a successful version check", async () => {
20
+ const exec = mock(async () => ({
21
+ stdout: "rtk 0.1.0",
22
+ stderr: "",
23
+ code: 0,
24
+ killed: false,
25
+ }));
26
+
27
+ expect(await probeRtkAvailability({ exec } as Pick<ExtensionAPI, "exec">)).toBe(true);
28
+ expect(exec).toHaveBeenCalledWith("rtk", ["--version"], { timeout: 3000 });
29
+ });
30
+
31
+ it("rejects an unsuccessful version check", async () => {
32
+ const exec = mock(async () => ({ stdout: "", stderr: "missing", code: 1, killed: false }));
33
+
34
+ expect(await probeRtkAvailability({ exec } as Pick<ExtensionAPI, "exec">)).toBe(false);
35
+ });
36
+
37
+ it("treats a spawn failure as unavailable", async () => {
38
+ const exec = mock(async () => {
39
+ throw new Error("ENOENT");
40
+ });
41
+
42
+ expect(await probeRtkAvailability({ exec } as Pick<ExtensionAPI, "exec">)).toBe(false);
43
+ });
44
+ });
45
+
16
46
  describe("splitChain", () => {
17
47
  it("returns single segment for plain command", () => {
18
48
  expect(splitChain("git status")).toEqual(["git status"]);
package/src/rtk.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  ExtensionCommandContext,
12
12
  ExtensionContext,
13
13
  } from "@earendil-works/pi-coding-agent";
14
+ import { canExecute } from "./capability.ts";
14
15
  import { loadOptValue, saveOptValue } from "./persist.ts";
15
16
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
16
17
 
@@ -136,7 +137,11 @@ const RTK_COMMANDS = new Set([
136
137
  interface RtkStatus {
137
138
  available: boolean;
138
139
  checkedAt: number;
139
- path?: string;
140
+ }
141
+
142
+ /** Probe the command we actually use instead of relying on a platform-specific locator. */
143
+ export function probeRtkAvailability(pi: Pick<ExtensionAPI, "exec">): Promise<boolean> {
144
+ return canExecute(pi, "rtk", ["--version"]);
140
145
  }
141
146
 
142
147
  /**
@@ -242,25 +247,12 @@ export function rtk(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle
242
247
  return rtkStatus;
243
248
  }
244
249
 
245
- try {
246
- const result = await pi.exec("which", ["rtk"], { timeout: 1000 });
247
- if (result.code === 0 && result.stdout?.trim()) {
248
- rtkStatus = {
249
- available: true,
250
- checkedAt: Date.now(),
251
- path: result.stdout.trim(),
252
- };
253
- warnedMissing = false;
254
- return rtkStatus;
255
- }
256
- } catch (_error) {
257
- // which command failed
258
- }
259
-
250
+ const available = await probeRtkAvailability(pi);
260
251
  rtkStatus = {
261
- available: false,
252
+ available,
262
253
  checkedAt: Date.now(),
263
254
  };
255
+ if (available) warnedMissing = false;
264
256
  return rtkStatus;
265
257
  };
266
258
 
package/src/status.ts CHANGED
@@ -45,7 +45,7 @@ export type OptimizerTool = "caveman" | "rtk" | "toon" | "ponytail";
45
45
 
46
46
  /**
47
47
  * Icons come from the shared pix-pretty catalog and follow the ONE global icon
48
- * mode (set via /pretty). The optimizer no longer owns a mode: each tool maps
48
+ * mode (set via /pix). The optimizer no longer owns a mode: each tool maps
49
49
  * to a semantic catalog key, and the cell renders whatever the active mode
50
50
  * resolves — nerd / unicode / ascii — with no per-package knob.
51
51
  */
@@ -76,7 +76,7 @@ export type Colorize = (color: ThemeColor, text: string) => string;
76
76
  * Build the colored status string for a set of tool states. ALL tool icons are
77
77
  * always shown, in TOOL_ORDER; each is accent-colored when its tool is enabled
78
78
  * and dim when disabled. Glyphs resolve against the active global icon mode
79
- * (see /pretty). `color` applies the theme color (e.g. theme.fg).
79
+ * (see /pix). `color` applies the theme color (e.g. theme.fg).
80
80
  *
81
81
  * Pure + exported for tests (pass a tagging colorizer to assert per-icon color).
82
82
  * A trailing space separates the cell from the next status segment.
@@ -102,7 +102,7 @@ export class OptimizerStatus {
102
102
 
103
103
  constructor() {
104
104
  // The cell is a pushed status (setStatus), so it does NOT auto-refresh
105
- // when /pretty flips the global icon mode. Repaint it on mode change
105
+ // when /pix flips the global icon mode. Repaint it on mode change
106
106
  // using the last-seen ctx (no-op until the cell has painted once).
107
107
  onIconModeChange(() => {
108
108
  if (this.lastCtx) this.paint(this.lastCtx);