pi-caveman 1.0.2 → 1.0.4
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 +15 -15
- package/extensions/caveman.ts +135 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,10 @@ pi install git:github.com/jonjonrankin/pi-caveman
|
|
|
45
45
|
/caveman wenyan-lite Semi-classical Chinese
|
|
46
46
|
/caveman wenyan Full 文言文
|
|
47
47
|
/caveman wenyan-ultra Extreme 文言文
|
|
48
|
+
/caveman micro Experimental prompt-minimized mode
|
|
48
49
|
/caveman off Disable
|
|
50
|
+
/caveman stop Disable (alias)
|
|
51
|
+
/caveman quit Disable (alias)
|
|
49
52
|
```
|
|
50
53
|
|
|
51
54
|
### Settings
|
|
@@ -63,24 +66,19 @@ Settings are saved to `~/.pi/agent/caveman.json` and persist across all sessions
|
|
|
63
66
|
|
|
64
67
|
### Status Bar
|
|
65
68
|
|
|
66
|
-
When active, an animated campfire flickers in the footer using colored braille characters
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
⠠⠄ → ⠔⠂ → ⠊⠑ → ... colored campfire embers (speed varies by level)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Colors cycle through red → orange → yellow → white-hot → ember using ANSI 256-color. Speed scales with intensity: 300ms for lite, 200ms for full, 100ms for ultra.
|
|
69
|
+
When active, a status bar displays caveman level and an animated campfire flickers in the footer using colored braille characters. This can be disabled in the `/caveman config` menu.
|
|
73
70
|
|
|
74
71
|
## Levels
|
|
75
72
|
|
|
76
|
-
| Level
|
|
77
|
-
|
|
78
|
-
| **Lite**
|
|
79
|
-
| **Full**
|
|
80
|
-
| **Ultra**
|
|
81
|
-
| **文言文 Lite**
|
|
82
|
-
| **文言文**
|
|
83
|
-
| **文言文 Ultra**
|
|
73
|
+
| Level | Style | Example |
|
|
74
|
+
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
75
|
+
| **Lite** | No filler. Full sentences. Professional but tight. | "Your component re-renders because you create a new object reference each render." |
|
|
76
|
+
| **Full** | Drop articles, fragments OK. Classic caveman. | "New object ref each render. Wrap in `useMemo`." |
|
|
77
|
+
| **Ultra** | Abbreviations, arrows, maximum compression. | "Inline obj prop → new ref → re-render. `useMemo`." |
|
|
78
|
+
| **文言文 Lite** | Semi-classical Chinese, grammar intact. | "組件頻重繪,以每繪新生對象參照故。" |
|
|
79
|
+
| **文言文** | Full classical terseness. | "物出新參照,致重繪。useMemo Wrap之。" |
|
|
80
|
+
| **文言文 Ultra** | Extreme classical compression. | "新參照→重繪。useMemo Wrap。" |
|
|
81
|
+
| **Micro** _(experimental)_ | Minimal prompt that reduces size of caveman prompt itself. Drops filler, pleasantries, hedging, keeps technical substance. | "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:" |
|
|
84
82
|
|
|
85
83
|
## How It Works
|
|
86
84
|
|
|
@@ -90,6 +88,8 @@ The extension hooks `before_agent_start` to append caveman communication rules t
|
|
|
90
88
|
|
|
91
89
|
Based on [caveman](https://github.com/JuliusBrussee/caveman) by [Julius Brussee](https://github.com/JuliusBrussee).
|
|
92
90
|
|
|
91
|
+
`micro` mode prompt based on [caveman-micro](https://github.com/kuba-guzik/caveman-micro) by [Kuba Guzik](https://github.com/kuba-guzik).
|
|
92
|
+
|
|
93
93
|
## License
|
|
94
94
|
|
|
95
95
|
MIT
|
package/extensions/caveman.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Commands:
|
|
8
8
|
* /caveman [level] Toggle caveman mode or set intensity
|
|
9
|
+
* /caveman stop Disable caveman mode (aliases: off, quit)
|
|
9
10
|
* /caveman config Open settings dialog (default level, status bar toggle)
|
|
10
11
|
*/
|
|
11
12
|
|
|
@@ -20,9 +21,24 @@ import { Container, type SettingItem, SettingsList, Text } from "@mariozechner/p
|
|
|
20
21
|
// Levels
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
|
|
23
|
-
const LEVELS = ["off", "lite", "full", "ultra", "wenyan-lite", "wenyan", "wenyan-ultra"] as const;
|
|
24
|
+
const LEVELS = ["off", "lite", "full", "ultra", "wenyan-lite", "wenyan", "wenyan-ultra", "micro"] as const;
|
|
25
|
+
const STOP_ALIASES = new Set(["off", "stop", "quit"]);
|
|
24
26
|
type Level = (typeof LEVELS)[number];
|
|
25
27
|
|
|
28
|
+
const CAVEMAN_COMMAND_OPTIONS = [
|
|
29
|
+
{ value: "lite", label: "lite", description: "Professional, no fluff" },
|
|
30
|
+
{ value: "full", label: "full", description: "Classic caveman" },
|
|
31
|
+
{ value: "ultra", label: "ultra", description: "Maximum compression" },
|
|
32
|
+
{ value: "wenyan-lite", label: "wenyan-lite", description: "Semi-classical Chinese" },
|
|
33
|
+
{ value: "wenyan", label: "wenyan", description: "Full 文言文" },
|
|
34
|
+
{ value: "wenyan-ultra", label: "wenyan-ultra", description: "Extreme 文言文" },
|
|
35
|
+
{ value: "micro", label: "micro", description: "Experimental prompt-minimized mode" },
|
|
36
|
+
{ value: "off", label: "off", description: "Disable caveman mode" },
|
|
37
|
+
{ value: "stop", label: "stop", description: "Disable caveman mode" },
|
|
38
|
+
{ value: "quit", label: "quit", description: "Disable caveman mode" },
|
|
39
|
+
{ value: "config", label: "config", description: "Open settings dialog" },
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
26
42
|
// ---------------------------------------------------------------------------
|
|
27
43
|
// Persistent config (survives across sessions)
|
|
28
44
|
// ---------------------------------------------------------------------------
|
|
@@ -35,7 +51,8 @@ interface CavemanConfig {
|
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
const CONFIG_PATH = join(homedir(), ".pi", "agent", "caveman.json");
|
|
38
|
-
const DEFAULT_CONFIG: CavemanConfig = { defaultLevel: "
|
|
54
|
+
const DEFAULT_CONFIG: CavemanConfig = { defaultLevel: "full", showStatus: true };
|
|
55
|
+
let saveConfigQueue: Promise<void> = Promise.resolve();
|
|
39
56
|
|
|
40
57
|
async function loadConfig(): Promise<CavemanConfig> {
|
|
41
58
|
try {
|
|
@@ -51,8 +68,12 @@ async function loadConfig(): Promise<CavemanConfig> {
|
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
async function saveConfig(config: CavemanConfig): Promise<void> {
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
const snapshot = JSON.stringify(config, null, 2) + "\n";
|
|
72
|
+
saveConfigQueue = saveConfigQueue.then(async () => {
|
|
73
|
+
await mkdir(join(homedir(), ".pi", "agent"), { recursive: true });
|
|
74
|
+
await writeFile(CONFIG_PATH, snapshot, "utf8");
|
|
75
|
+
});
|
|
76
|
+
return saveConfigQueue;
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
// ---------------------------------------------------------------------------
|
|
@@ -85,12 +106,13 @@ const FIRE_FRAMES = [
|
|
|
85
106
|
];
|
|
86
107
|
|
|
87
108
|
const ANIMATIONS: Record<Exclude<Level, "off">, Animation> = {
|
|
88
|
-
lite:
|
|
89
|
-
full:
|
|
90
|
-
ultra:
|
|
91
|
-
"wenyan-lite": { frames: FIRE_FRAMES, label: "文言",
|
|
92
|
-
wenyan:
|
|
93
|
-
"wenyan-ultra":{ frames: FIRE_FRAMES, label: "文言文極",
|
|
109
|
+
lite: { frames: FIRE_FRAMES, label: "LITE", interval: 300 },
|
|
110
|
+
full: { frames: FIRE_FRAMES, label: "FULL", interval: 200 },
|
|
111
|
+
ultra: { frames: FIRE_FRAMES, label: "ULTRA", interval: 100 },
|
|
112
|
+
"wenyan-lite": { frames: FIRE_FRAMES, label: "文言", interval: 300 },
|
|
113
|
+
wenyan: { frames: FIRE_FRAMES, label: "文言文", interval: 200 },
|
|
114
|
+
"wenyan-ultra": { frames: FIRE_FRAMES, label: "文言文極", interval: 100 },
|
|
115
|
+
micro: { frames: FIRE_FRAMES, label: "MICRO", interval: 120 },
|
|
94
116
|
};
|
|
95
117
|
|
|
96
118
|
// ---------------------------------------------------------------------------
|
|
@@ -111,7 +133,15 @@ pleasantries, hedging
|
|
|
111
133
|
Bad: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
|
|
112
134
|
Good: "Bug in auth middleware. Token expiry check use \`<\` not \`<=\`. Fix:"`;
|
|
113
135
|
|
|
114
|
-
const
|
|
136
|
+
const MICRO_PROMPT = `# Token efficiency
|
|
137
|
+
Respond like smart caveman. Cut all filler, keep technical substance.
|
|
138
|
+
- Drop articles (a, an, the), filler (just, really, basically, actually).
|
|
139
|
+
- Drop pleasantries (sure, certainly, happy to).
|
|
140
|
+
- No hedging. Fragments fine. Short synonyms.
|
|
141
|
+
- Technical terms stay exact. Code blocks unchanged.
|
|
142
|
+
- Pattern: [thing] [action] [reason]. [next step].`;
|
|
143
|
+
|
|
144
|
+
const INTENSITY: Record<Exclude<Level, "off" | "micro">, string> = {
|
|
115
145
|
lite: `\
|
|
116
146
|
No filler/hedging. Keep articles + full sentences. Professional but tight.
|
|
117
147
|
Example: "Your component re-renders because you create a new object reference each render. Wrap it in \`useMemo\`."`,
|
|
@@ -151,6 +181,20 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
151
181
|
let config: CavemanConfig = { ...DEFAULT_CONFIG };
|
|
152
182
|
let timer: ReturnType<typeof setInterval> | null = null;
|
|
153
183
|
let frameIndex = 0;
|
|
184
|
+
let isActive = false;
|
|
185
|
+
let configLoadPromise: Promise<void> | null = null;
|
|
186
|
+
|
|
187
|
+
const ensureConfigLoaded = async () => {
|
|
188
|
+
if (!configLoadPromise) {
|
|
189
|
+
configLoadPromise = (async () => {
|
|
190
|
+
config = await loadConfig();
|
|
191
|
+
if (level === "off" && config.defaultLevel !== "off") {
|
|
192
|
+
level = config.defaultLevel;
|
|
193
|
+
}
|
|
194
|
+
})();
|
|
195
|
+
}
|
|
196
|
+
await configLoadPromise;
|
|
197
|
+
};
|
|
154
198
|
|
|
155
199
|
// -- Animation helpers --
|
|
156
200
|
|
|
@@ -172,10 +216,17 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
172
216
|
}
|
|
173
217
|
|
|
174
218
|
const anim = ANIMATIONS[level];
|
|
219
|
+
const setFrame = (frame: string) => {
|
|
220
|
+
ctx.ui.setStatus("caveman", frame + " " + theme.fg("muted", "caveman level: ") + theme.fg("text", anim.label));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (!isActive) {
|
|
224
|
+
setFrame(anim.frames[0]!);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
175
227
|
|
|
176
228
|
const renderFrame = () => {
|
|
177
|
-
|
|
178
|
-
ctx.ui.setStatus("caveman", icon + " " + theme.fg("muted", "caveman level: ") + theme.fg("text", anim.label));
|
|
229
|
+
setFrame(anim.frames[frameIndex % anim.frames.length]!);
|
|
179
230
|
frameIndex++;
|
|
180
231
|
};
|
|
181
232
|
|
|
@@ -186,7 +237,7 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
186
237
|
// -- Restore state on session load --
|
|
187
238
|
|
|
188
239
|
pi.on("session_start", async (_event, ctx) => {
|
|
189
|
-
|
|
240
|
+
await ensureConfigLoaded();
|
|
190
241
|
|
|
191
242
|
// Check for session-level override first (resuming a session)
|
|
192
243
|
let sessionLevel: Level | null = null;
|
|
@@ -208,14 +259,30 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
208
259
|
syncStatus(ctx);
|
|
209
260
|
});
|
|
210
261
|
|
|
262
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
263
|
+
isActive = true;
|
|
264
|
+
syncStatus(ctx);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
268
|
+
isActive = false;
|
|
269
|
+
syncStatus(ctx);
|
|
270
|
+
});
|
|
271
|
+
|
|
211
272
|
pi.on("session_shutdown", async () => {
|
|
212
273
|
stopAnimation();
|
|
274
|
+
isActive = false;
|
|
213
275
|
});
|
|
214
276
|
|
|
215
277
|
// -- /caveman command --
|
|
216
278
|
|
|
217
279
|
pi.registerCommand("caveman", {
|
|
218
|
-
description: "Toggle caveman mode, set level, or 'config' to open settings",
|
|
280
|
+
description: "Toggle caveman mode, set level, use stop/off/quit to disable, or 'config' to open settings",
|
|
281
|
+
getArgumentCompletions: (prefix: string) => {
|
|
282
|
+
const normalized = prefix.trim().toLowerCase();
|
|
283
|
+
const items = CAVEMAN_COMMAND_OPTIONS.filter((item) => item.value.startsWith(normalized));
|
|
284
|
+
return items.length > 0 ? items : null;
|
|
285
|
+
},
|
|
219
286
|
handler: async (args, ctx) => {
|
|
220
287
|
const arg = args?.trim().toLowerCase();
|
|
221
288
|
|
|
@@ -227,10 +294,12 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
227
294
|
|
|
228
295
|
if (!arg) {
|
|
229
296
|
level = level === "off" ? "full" : "off";
|
|
297
|
+
} else if (STOP_ALIASES.has(arg)) {
|
|
298
|
+
level = "off";
|
|
230
299
|
} else if (LEVELS.includes(arg as Level)) {
|
|
231
300
|
level = arg as Level;
|
|
232
301
|
} else {
|
|
233
|
-
ctx.ui.notify(`Unknown: "${arg}". Use: ${LEVELS.join(", ")} or config`, "error");
|
|
302
|
+
ctx.ui.notify(`Unknown: "${arg}". Use: ${LEVELS.join(", ")}, stop, quit, or config`, "error");
|
|
234
303
|
return;
|
|
235
304
|
}
|
|
236
305
|
|
|
@@ -247,7 +316,7 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
247
316
|
// -- /caveman config: interactive SettingsList --
|
|
248
317
|
|
|
249
318
|
async function openConfig(ctx: ExtensionContext) {
|
|
250
|
-
|
|
319
|
+
await ensureConfigLoaded();
|
|
251
320
|
|
|
252
321
|
await ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
253
322
|
const items: SettingItem[] = [
|
|
@@ -268,32 +337,65 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
268
337
|
const container = new Container();
|
|
269
338
|
container.addChild(new Text(theme.fg("accent", theme.bold(" Caveman Config")), 0, 0));
|
|
270
339
|
container.addChild(new Text(theme.fg("dim", " Saved to ~/.pi/agent/caveman.json"), 0, 0));
|
|
340
|
+
container.addChild(new Text(theme.fg("dim", " Default level applies to future sessions."), 0, 0));
|
|
271
341
|
container.addChild(new Text("", 0, 0));
|
|
272
342
|
|
|
343
|
+
const applySettingChange = (id: string, newValue: string) => {
|
|
344
|
+
if (id === "defaultLevel" && LEVELS.includes(newValue as Level)) {
|
|
345
|
+
config.defaultLevel = newValue as Level;
|
|
346
|
+
} else if (id === "showStatus") {
|
|
347
|
+
config.showStatus = newValue === "on";
|
|
348
|
+
}
|
|
349
|
+
saveConfig(config);
|
|
350
|
+
syncStatus(ctx);
|
|
351
|
+
};
|
|
352
|
+
|
|
273
353
|
const settingsList = new SettingsList(
|
|
274
354
|
items,
|
|
275
355
|
Math.min(items.length + 2, 10),
|
|
276
356
|
getSettingsListTheme(),
|
|
277
|
-
|
|
278
|
-
if (id === "defaultLevel" && LEVELS.includes(newValue as Level)) {
|
|
279
|
-
config.defaultLevel = newValue as Level;
|
|
280
|
-
} else if (id === "showStatus") {
|
|
281
|
-
config.showStatus = newValue === "on";
|
|
282
|
-
}
|
|
283
|
-
saveConfig(config);
|
|
284
|
-
syncStatus(ctx);
|
|
285
|
-
},
|
|
357
|
+
applySettingChange,
|
|
286
358
|
() => done(undefined),
|
|
287
359
|
);
|
|
288
360
|
|
|
289
361
|
container.addChild(settingsList);
|
|
290
|
-
container.addChild(new Text(theme.fg("dim", "
|
|
362
|
+
container.addChild(new Text(theme.fg("dim", " ←→/hl/tab change • ↑↓/jk move • esc close"), 0, 0));
|
|
363
|
+
|
|
364
|
+
const cycleSelectedValue = (direction: -1 | 1) => {
|
|
365
|
+
const selectedIndex = (settingsList as unknown as { selectedIndex: number }).selectedIndex;
|
|
366
|
+
const item = items[selectedIndex];
|
|
367
|
+
if (!item?.values?.length) return;
|
|
368
|
+
|
|
369
|
+
const currentIndex = item.values.indexOf(item.currentValue);
|
|
370
|
+
const nextIndex = (currentIndex + direction + item.values.length) % item.values.length;
|
|
371
|
+
const newValue = item.values[nextIndex]!;
|
|
372
|
+
item.currentValue = newValue;
|
|
373
|
+
settingsList.updateValue(item.id, newValue);
|
|
374
|
+
applySettingChange(item.id, newValue);
|
|
375
|
+
};
|
|
291
376
|
|
|
292
377
|
return {
|
|
293
378
|
render: (w: number) => container.render(w),
|
|
294
379
|
invalidate: () => container.invalidate(),
|
|
295
380
|
handleInput: (data: string) => {
|
|
381
|
+
if (data === "j") data = "\u001b[B";
|
|
382
|
+
else if (data === "k") data = "\u001b[A";
|
|
383
|
+
else if (data === "h") {
|
|
384
|
+
cycleSelectedValue(-1);
|
|
385
|
+
_tui.requestRender();
|
|
386
|
+
return;
|
|
387
|
+
} else if (data === "l" || data === "\u001b[C" || data === "\t") {
|
|
388
|
+
cycleSelectedValue(1);
|
|
389
|
+
_tui.requestRender();
|
|
390
|
+
return;
|
|
391
|
+
} else if (data === "\u001b[D") {
|
|
392
|
+
cycleSelectedValue(-1);
|
|
393
|
+
_tui.requestRender();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
296
397
|
settingsList.handleInput?.(data);
|
|
398
|
+
_tui.requestRender();
|
|
297
399
|
},
|
|
298
400
|
};
|
|
299
401
|
});
|
|
@@ -302,7 +404,13 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
302
404
|
// -- Inject caveman rules into system prompt --
|
|
303
405
|
|
|
304
406
|
pi.on("before_agent_start", async (event) => {
|
|
407
|
+
await ensureConfigLoaded();
|
|
305
408
|
if (level === "off") return;
|
|
409
|
+
if (level === "micro") {
|
|
410
|
+
return {
|
|
411
|
+
systemPrompt: `${event.systemPrompt}\n\n${MICRO_PROMPT}`,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
306
414
|
return {
|
|
307
415
|
systemPrompt: `${event.systemPrompt}\n\n${BASE}\n\n${INTENSITY[level]}\n\n${SAFETY}`,
|
|
308
416
|
};
|
package/package.json
CHANGED