@xynogen/pix-optimizer 1.1.18 → 1.1.20

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.18",
3
+ "version": "1.1.20",
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",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "src",
12
+ "!src/**/*.test.ts",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],
package/src/caveman.ts CHANGED
@@ -5,12 +5,12 @@
5
5
  * called by index.ts alongside rtk(pi).
6
6
  */
7
7
 
8
- import type {
9
- ExtensionAPI,
10
- ExtensionCommandContext,
11
- ExtensionContext,
12
- } from "@earendil-works/pi-coding-agent";
13
- import { loadOptValue, saveOptValue } from "./persist.ts";
8
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
+ import {
10
+ createMode,
11
+ resolveLevel as resolveLevelGeneric,
12
+ toggleLevel as toggleLevelGeneric,
13
+ } from "./mode.ts";
14
14
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
15
15
 
16
16
  // ── Levels ────────────────────────────────────────────────────────────────────
@@ -97,11 +97,7 @@ export function buildPrompt(level: Level): string {
97
97
  * Handles stop aliases (stop/quit → "off") and valid level names.
98
98
  */
99
99
  export function resolveLevel(arg: string): Level | null {
100
- const a = arg.trim().toLowerCase();
101
- if (STOP_ALIASES.has(a)) return "off";
102
- if (LEVEL_NUMBERS[a]) return LEVEL_NUMBERS[a];
103
- if (LEVELS.includes(a as Level)) return a as Level;
104
- return null;
100
+ return resolveLevelGeneric(arg, LEVELS, LEVEL_NUMBERS, STOP_ALIASES);
105
101
  }
106
102
 
107
103
  /**
@@ -127,74 +123,18 @@ export function buildHelp(current: Level): string {
127
123
  * Toggle: off → full, anything else → off.
128
124
  */
129
125
  export function toggleLevel(current: Level): Level {
130
- return current === "off" ? "full" : "off";
126
+ return toggleLevelGeneric(current);
131
127
  }
132
128
 
133
129
  // ── Pi extension ────────────────────────────────────────────────────────────
134
130
 
135
131
  export function caveman(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
136
- let level: Level = "off";
137
-
138
- // -- Status: report into the shared optimizer indicator. --
139
-
140
- function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
141
- status.set("caveman", level !== "off", ctx);
142
- }
143
-
144
- // Inject caveman prompt via before_agent_start
145
- pi.on("before_agent_start", async (event, _ctx) => {
146
- const prompt = buildPrompt(level);
147
- if (!prompt) return undefined;
148
- const existing = event.systemPrompt ?? "";
149
- return { systemPrompt: `${prompt}\n\n${existing}` };
150
- });
151
-
152
- // -- Restore live level from the session log on load --
153
-
154
- pi.on("session_start", async (_event, ctx) => {
155
- // Session log first (survives in-session branch nav), then disk (survives
156
- // a full quit/restart). Disk wins when present so a new session restores
157
- // the last chosen level instead of defaulting to off.
158
- for (const entry of ctx.sessionManager.getEntries()) {
159
- if (entry.type === "custom" && entry.customType === "caveman-level") {
160
- level = (entry.data as { level: Level })?.level ?? level;
161
- }
162
- }
163
- const saved = loadOptValue("caveman");
164
- if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
165
- syncStatus(ctx);
166
- });
167
-
168
- pi.on("agent_start", async (_event, ctx) => {
169
- syncStatus(ctx);
170
- });
171
- pi.on("agent_end", async (_event, ctx) => {
172
- syncStatus(ctx);
173
- });
174
- pi.on("session_shutdown", async () => {});
175
-
176
- // -- Overlay value handler (called by the /optimizer overlay) --
177
-
178
- async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
179
- const resolved = resolveLevel(value);
180
- if (resolved === null) return;
181
- level = resolved;
182
-
183
- pi.appendEntry("caveman-level", { level });
184
- saveOptValue("caveman", level);
185
- syncStatus(ctx);
186
-
187
- ctx.ui.notify(
188
- level === "off" ? "Caveman mode off." : `Caveman: ${STATUS_LABELS[level]}`,
189
- "info",
190
- );
191
- }
192
-
193
- return {
132
+ return createMode(pi, status, {
194
133
  name: "caveman",
195
134
  help: "caveman — terse output",
196
- values: LEVELS,
197
- current: () => level,
198
- run,
199
- };
135
+ levels: LEVELS,
136
+ buildPrompt,
137
+ resolve: resolveLevel,
138
+ notify: (level) => (level === "off" ? "Caveman mode off." : `Caveman: ${STATUS_LABELS[level]}`),
139
+ });
200
140
  }
package/src/mode.ts ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * mode.ts — shared plumbing for prompt-injection optimizer modes.
3
+ *
4
+ * caveman.ts and ponytail.ts are the same *kind* of tool: a level enum, a
5
+ * system-prompt fragment injected via before_agent_start, and identical
6
+ * persistence/status wiring. Only the prompt/help/label *content* differs —
7
+ * that stays in each module. This file owns the generic level resolution and
8
+ * the extension factory both call.
9
+ */
10
+
11
+ import type {
12
+ ExtensionAPI,
13
+ ExtensionCommandContext,
14
+ ExtensionContext,
15
+ } from "@earendil-works/pi-coding-agent";
16
+ import { loadOptValue, saveOptValue } from "./persist.ts";
17
+ import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
18
+
19
+ /**
20
+ * Resolve a raw command arg to a level, or null if unrecognised.
21
+ * Handles stop aliases (stop/quit → "off"), numeric shortcuts, and level names.
22
+ */
23
+ export function resolveLevel<L extends string>(
24
+ arg: string,
25
+ levels: readonly L[],
26
+ levelNumbers: Record<string, L>,
27
+ stopAliases: ReadonlySet<string>,
28
+ ): L | null {
29
+ const a = arg.trim().toLowerCase();
30
+ if (stopAliases.has(a)) return "off" as L;
31
+ if (levelNumbers[a]) return levelNumbers[a];
32
+ if (levels.includes(a as L)) return a as L;
33
+ return null;
34
+ }
35
+
36
+ /** Toggle: off → full, anything else → off. */
37
+ export function toggleLevel<L extends string>(current: L): L {
38
+ return (current === "off" ? "full" : "off") as L;
39
+ }
40
+
41
+ export interface ModeConfig<L extends string> {
42
+ /** Status-registry key + persistence key + `${name}-level` custom entry. */
43
+ name: OptimizerTool;
44
+ /** One-line overlay summary. */
45
+ help: string;
46
+ /** Cyclable values in display order. */
47
+ levels: readonly L[];
48
+ /** Build the system-prompt fragment for a level ("" when off). */
49
+ buildPrompt(level: L): string;
50
+ /** Resolve an overlay/command value to a level (each module supplies its enum). */
51
+ resolve(value: string): L | null;
52
+ /** Notification shown after a successful change. */
53
+ notify(level: L): string;
54
+ }
55
+
56
+ /**
57
+ * Build the shared extension: prompt injection + session restore + status +
58
+ * overlay handle. Identical across every prompt-mode tool.
59
+ */
60
+ export function createMode<L extends string>(
61
+ pi: ExtensionAPI,
62
+ status: OptimizerStatus,
63
+ config: ModeConfig<L>,
64
+ ): OptimizerHandle {
65
+ const { name, levels, buildPrompt } = config;
66
+ const customType = `${name}-level`;
67
+ let level = "off" as L;
68
+
69
+ function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
70
+ status.set(name, level !== "off", ctx);
71
+ }
72
+
73
+ pi.on("before_agent_start", async (event, _ctx) => {
74
+ const prompt = buildPrompt(level);
75
+ if (!prompt) return undefined;
76
+ const existing = event.systemPrompt ?? "";
77
+ return { systemPrompt: `${prompt}\n\n${existing}` };
78
+ });
79
+
80
+ pi.on("session_start", async (_event, ctx) => {
81
+ // Session log first (survives in-session branch nav), then disk (survives
82
+ // a full quit/restart). Disk wins when present.
83
+ for (const entry of ctx.sessionManager.getEntries()) {
84
+ if (entry.type === "custom" && entry.customType === customType) {
85
+ level = ((entry.data as { level: L })?.level ?? level) as L;
86
+ }
87
+ }
88
+ const saved = loadOptValue(name);
89
+ if (saved && levels.includes(saved as L)) level = saved as L;
90
+ syncStatus(ctx);
91
+ });
92
+
93
+ pi.on("agent_start", async (_event, ctx) => syncStatus(ctx));
94
+ pi.on("agent_end", async (_event, ctx) => syncStatus(ctx));
95
+ pi.on("session_shutdown", async () => {});
96
+
97
+ async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
98
+ const resolved = config.resolve(value);
99
+ if (resolved === null) return;
100
+ level = resolved;
101
+
102
+ pi.appendEntry(customType, { level });
103
+ saveOptValue(name, level);
104
+ syncStatus(ctx);
105
+
106
+ ctx.ui.notify(config.notify(level), "info");
107
+ }
108
+
109
+ return {
110
+ name,
111
+ help: config.help,
112
+ values: levels,
113
+ current: () => level,
114
+ run,
115
+ };
116
+ }
package/src/opt.ts CHANGED
@@ -15,7 +15,13 @@
15
15
  */
16
16
 
17
17
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
- import { frameLines } from "@xynogen/pix-pretty/modal-frame";
18
+ import { type KeybindingsManager, matchesKey } from "@earendil-works/pi-tui";
19
+ import {
20
+ frameModal,
21
+ MIN_MODAL_HEIGHT,
22
+ ModalPager,
23
+ terminalModalHeight,
24
+ } from "@xynogen/pix-pretty/modal-frame";
19
25
  import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
20
26
  import { toolIcon } from "./status.ts";
21
27
 
@@ -113,12 +119,13 @@ export function registerOptCommand(
113
119
 
114
120
  await ui.custom<null>(
115
121
  (
116
- tui: { requestRender(): void },
122
+ tui: { requestRender(): void; terminal?: { rows?: number } },
117
123
  theme: typeof ui.theme,
118
- _kb: unknown,
124
+ kb: KeybindingsManager,
119
125
  done: (v: null) => void,
120
126
  ) => {
121
127
  let selected = 0;
128
+ const pager = new ModalPager();
122
129
 
123
130
  const cycle = (direction: -1 | 1) => {
124
131
  const tool = TOOL_ORDER[selected] as OptimizerTool;
@@ -130,6 +137,7 @@ export function registerOptCommand(
130
137
 
131
138
  const move = (direction: -1 | 1) => {
132
139
  selected = (selected + direction + TOOL_ORDER.length) % TOOL_ORDER.length;
140
+ pager.followSelection();
133
141
  };
134
142
 
135
143
  return {
@@ -148,30 +156,46 @@ export function registerOptCommand(
148
156
  const bar = theme.fg(on ? "accent" : "dim", levelBar(cur, handles[tool].values));
149
157
  return `${cursor} ${glyph} ${name} ${value} ${bar}`;
150
158
  });
151
- const lines = [
152
- theme.fg("accent", theme.bold("󱎫 Optimizer")),
153
- "",
154
- ...rows,
155
- "",
156
- theme.fg("dim", helpSummary(handles[TOOL_ORDER[selected] as OptimizerTool].help)),
157
- "",
158
- theme.fg("dim", "←→ change · ↑↓ move · esc close"),
159
- ];
160
- return frameLines({
159
+ const result = frameModal({
161
160
  width: boxW,
162
- lines,
161
+ maxHeight: terminalModalHeight(tui.terminal?.rows),
162
+ minHeight: MIN_MODAL_HEIGHT,
163
+ header: [theme.fg("accent", theme.bold("󱎫 Optimizer")), ""],
164
+ body: rows,
165
+ footer: [
166
+ "",
167
+ theme.fg("dim", helpSummary(handles[TOOL_ORDER[selected] as OptimizerTool].help)),
168
+ "",
169
+ theme.fg("dim", "←→ change · ↑↓ move · PgUp/PgDn inspect · esc close"),
170
+ ],
171
+ bodyOffset: pager.bodyOffset,
172
+ selectedBodyLine: pager.selectedLine(selected),
163
173
  color: (s) => theme.fg("accent", s),
164
174
  bg: (s) => theme.bg("customMessageBg", s),
165
175
  });
176
+ pager.sync(result);
177
+ return result.lines;
166
178
  },
167
179
  invalidate: () => {},
168
180
  handleInput: (data: string) => {
169
- if (data === "k" || data === "\u001b[A") move(-1);
170
- else if (data === "j" || data === "\u001b[B") move(1);
171
- else if (data === "h" || data === "\u001b[D") cycle(-1);
172
- else if (data === "l" || data === "\u001b[C" || data === " " || data === "\r")
181
+ if (pager.handleInput(data, kb)) {
182
+ tui.requestRender();
183
+ return;
184
+ }
185
+ // matchesKey handles both legacy bytes and Kitty CSI-u encodings
186
+ // for letters and special keys alike — raw string compares like
187
+ // `data === "k"` silently fail under the Kitty keyboard protocol.
188
+ if (matchesKey(data, "k") || matchesKey(data, "up")) move(-1);
189
+ else if (matchesKey(data, "j") || matchesKey(data, "down")) move(1);
190
+ else if (matchesKey(data, "h") || matchesKey(data, "left")) cycle(-1);
191
+ else if (
192
+ matchesKey(data, "l") ||
193
+ matchesKey(data, "right") ||
194
+ matchesKey(data, "space") ||
195
+ matchesKey(data, "enter")
196
+ )
173
197
  cycle(1);
174
- else if (data === "\u001b" || data === "q") {
198
+ else if (matchesKey(data, "escape") || matchesKey(data, "q")) {
175
199
  done(null);
176
200
  return;
177
201
  } else return;
package/src/ponytail.ts CHANGED
@@ -9,12 +9,12 @@
9
9
  * skill. We inject it as a system-prompt fragment — no external hooks/files.
10
10
  */
11
11
 
12
- import type {
13
- ExtensionAPI,
14
- ExtensionCommandContext,
15
- ExtensionContext,
16
- } from "@earendil-works/pi-coding-agent";
17
- import { loadOptValue, saveOptValue } from "./persist.ts";
12
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import {
14
+ createMode,
15
+ resolveLevel as resolveLevelGeneric,
16
+ toggleLevel as toggleLevelGeneric,
17
+ } from "./mode.ts";
18
18
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
19
19
 
20
20
  // ── Levels ────────────────────────────────────────────────────────────────────
@@ -102,11 +102,7 @@ export function buildPrompt(level: Level): string {
102
102
  * Handles stop aliases (stop/quit → "off") and valid level names.
103
103
  */
104
104
  export function resolveLevel(arg: string): Level | null {
105
- const a = arg.trim().toLowerCase();
106
- if (STOP_ALIASES.has(a)) return "off";
107
- if (LEVEL_NUMBERS[a]) return LEVEL_NUMBERS[a];
108
- if (LEVELS.includes(a as Level)) return a as Level;
109
- return null;
105
+ return resolveLevelGeneric(arg, LEVELS, LEVEL_NUMBERS, STOP_ALIASES);
110
106
  }
111
107
 
112
108
  /**
@@ -131,73 +127,19 @@ export function buildHelp(current: Level): string {
131
127
  * Toggle: off → full, anything else → off.
132
128
  */
133
129
  export function toggleLevel(current: Level): Level {
134
- return current === "off" ? "full" : "off";
130
+ return toggleLevelGeneric(current);
135
131
  }
136
132
 
137
133
  // ── Pi extension ────────────────────────────────────────────────────────────
138
134
 
139
135
  export function ponytail(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
140
- let level: Level = "off";
141
-
142
- // -- Status: report into the shared optimizer indicator. --
143
-
144
- function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
145
- status.set("ponytail", level !== "off", ctx);
146
- }
147
-
148
- // Inject ponytail prompt via before_agent_start
149
- pi.on("before_agent_start", async (event, _ctx) => {
150
- const prompt = buildPrompt(level);
151
- if (!prompt) return undefined;
152
- const existing = event.systemPrompt ?? "";
153
- return { systemPrompt: `${prompt}\n\n${existing}` };
154
- });
155
-
156
- // -- Restore live level from the session log on load --
157
-
158
- pi.on("session_start", async (_event, ctx) => {
159
- // Session log first (in-session branch nav), then disk (survives a full
160
- // quit/restart). Disk wins when present.
161
- for (const entry of ctx.sessionManager.getEntries()) {
162
- if (entry.type === "custom" && entry.customType === "ponytail-level") {
163
- level = (entry.data as { level: Level })?.level ?? level;
164
- }
165
- }
166
- const saved = loadOptValue("ponytail");
167
- if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
168
- syncStatus(ctx);
169
- });
170
-
171
- pi.on("agent_start", async (_event, ctx) => {
172
- syncStatus(ctx);
173
- });
174
- pi.on("agent_end", async (_event, ctx) => {
175
- syncStatus(ctx);
176
- });
177
- pi.on("session_shutdown", async () => {});
178
-
179
- // -- Overlay value handler (called by the /optimizer overlay) --
180
-
181
- async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
182
- const resolved = resolveLevel(value);
183
- if (resolved === null) return;
184
- level = resolved;
185
-
186
- pi.appendEntry("ponytail-level", { level });
187
- saveOptValue("ponytail", level);
188
- syncStatus(ctx);
189
-
190
- ctx.ui.notify(
191
- level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
192
- "info",
193
- );
194
- }
195
-
196
- return {
136
+ return createMode(pi, status, {
197
137
  name: "ponytail",
198
138
  help: "ponytail — lazy senior dev (minimal code)",
199
- values: LEVELS,
200
- current: () => level,
201
- run,
202
- };
139
+ levels: LEVELS,
140
+ buildPrompt,
141
+ resolve: resolveLevel,
142
+ notify: (level) =>
143
+ level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
144
+ });
203
145
  }
@@ -1,42 +0,0 @@
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
- });