@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 +2 -1
- package/src/caveman.ts +14 -74
- package/src/mode.ts +116 -0
- package/src/opt.ts +43 -19
- package/src/ponytail.ts +15 -73
- package/src/capability.test.ts +0 -42
- package/src/caveman.test.ts +0 -203
- package/src/json.test.ts +0 -167
- package/src/opt.test.ts +0 -56
- package/src/persist.test.ts +0 -56
- package/src/ponytail.test.ts +0 -195
- package/src/rtk.test.ts +0 -277
- package/src/status.test.ts +0 -93
- package/src/tool-result-filter.test.ts +0 -96
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-optimizer",
|
|
3
|
-
"version": "1.1.
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
}
|
package/src/capability.test.ts
DELETED
|
@@ -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
|
-
});
|