pi-caveman 1.0.1 → 1.0.3
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 +117 -28
- 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
|
// ---------------------------------------------------------------------------
|
|
@@ -85,12 +101,13 @@ const FIRE_FRAMES = [
|
|
|
85
101
|
];
|
|
86
102
|
|
|
87
103
|
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: "文言文極",
|
|
104
|
+
lite: { frames: FIRE_FRAMES, label: "LITE", interval: 300 },
|
|
105
|
+
full: { frames: FIRE_FRAMES, label: "CAVEMAN", interval: 200 },
|
|
106
|
+
ultra: { frames: FIRE_FRAMES, label: "ULTRA", interval: 100 },
|
|
107
|
+
"wenyan-lite": { frames: FIRE_FRAMES, label: "文言", interval: 300 },
|
|
108
|
+
wenyan: { frames: FIRE_FRAMES, label: "文言文", interval: 200 },
|
|
109
|
+
"wenyan-ultra": { frames: FIRE_FRAMES, label: "文言文極", interval: 100 },
|
|
110
|
+
micro: { frames: FIRE_FRAMES, label: "MICRO", interval: 120 },
|
|
94
111
|
};
|
|
95
112
|
|
|
96
113
|
// ---------------------------------------------------------------------------
|
|
@@ -111,29 +128,37 @@ pleasantries, hedging
|
|
|
111
128
|
Bad: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
|
|
112
129
|
Good: "Bug in auth middleware. Token expiry check use \`<\` not \`<=\`. Fix:"`;
|
|
113
130
|
|
|
114
|
-
const
|
|
131
|
+
const MICRO_PROMPT = `# Token efficiency
|
|
132
|
+
Respond like smart caveman. Cut all filler, keep technical substance.
|
|
133
|
+
- Drop articles (a, an, the), filler (just, really, basically, actually).
|
|
134
|
+
- Drop pleasantries (sure, certainly, happy to).
|
|
135
|
+
- No hedging. Fragments fine. Short synonyms.
|
|
136
|
+
- Technical terms stay exact. Code blocks unchanged.
|
|
137
|
+
- Pattern: [thing] [action] [reason]. [next step].`;
|
|
138
|
+
|
|
139
|
+
const INTENSITY: Record<Exclude<Level, "off" | "micro">, string> = {
|
|
115
140
|
lite: `\
|
|
116
|
-
|
|
141
|
+
No filler/hedging. Keep articles + full sentences. Professional but tight.
|
|
117
142
|
Example: "Your component re-renders because you create a new object reference each render. Wrap it in \`useMemo\`."`,
|
|
118
143
|
|
|
119
144
|
full: `\
|
|
120
|
-
|
|
145
|
+
Drop articles, fragments OK, short synonyms.
|
|
121
146
|
Example: "New object ref each render. Inline object prop = new ref = re-render. Wrap in \`useMemo\`."`,
|
|
122
147
|
|
|
123
148
|
ultra: `\
|
|
124
|
-
|
|
149
|
+
Abbreviate (DB/auth/config/req/res/fn/impl), strip conjunctions, arrows for causality (X → Y).
|
|
125
150
|
Example: "Inline obj prop → new ref → re-render. \`useMemo\`."`,
|
|
126
151
|
|
|
127
152
|
"wenyan-lite": `\
|
|
128
|
-
|
|
153
|
+
Semi-classical Chinese. Grammar intact, filler gone. Technical terms in English.
|
|
129
154
|
Example: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。"`,
|
|
130
155
|
|
|
131
156
|
wenyan: `\
|
|
132
|
-
|
|
157
|
+
Maximum classical terseness. 80-90% character reduction. Technical terms in English.
|
|
133
158
|
Example: "物出新參照,致重繪。useMemo Wrap之。"`,
|
|
134
159
|
|
|
135
160
|
"wenyan-ultra": `\
|
|
136
|
-
|
|
161
|
+
Extreme classical compression. Technical terms in English.
|
|
137
162
|
Example: "新參照→重繪。useMemo Wrap。"`,
|
|
138
163
|
};
|
|
139
164
|
|
|
@@ -151,6 +176,7 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
151
176
|
let config: CavemanConfig = { ...DEFAULT_CONFIG };
|
|
152
177
|
let timer: ReturnType<typeof setInterval> | null = null;
|
|
153
178
|
let frameIndex = 0;
|
|
179
|
+
let isActive = false;
|
|
154
180
|
|
|
155
181
|
// -- Animation helpers --
|
|
156
182
|
|
|
@@ -172,10 +198,17 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
172
198
|
}
|
|
173
199
|
|
|
174
200
|
const anim = ANIMATIONS[level];
|
|
201
|
+
const setFrame = (frame: string) => {
|
|
202
|
+
ctx.ui.setStatus("caveman", frame + " " + theme.fg("muted", "caveman level: ") + theme.fg("text", anim.label));
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
if (!isActive) {
|
|
206
|
+
setFrame(anim.frames[0]!);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
175
209
|
|
|
176
210
|
const renderFrame = () => {
|
|
177
|
-
|
|
178
|
-
ctx.ui.setStatus("caveman", icon + " " + theme.fg("muted", "caveman level: ") + theme.fg("text", anim.label));
|
|
211
|
+
setFrame(anim.frames[frameIndex % anim.frames.length]!);
|
|
179
212
|
frameIndex++;
|
|
180
213
|
};
|
|
181
214
|
|
|
@@ -208,14 +241,30 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
208
241
|
syncStatus(ctx);
|
|
209
242
|
});
|
|
210
243
|
|
|
244
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
245
|
+
isActive = true;
|
|
246
|
+
syncStatus(ctx);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
250
|
+
isActive = false;
|
|
251
|
+
syncStatus(ctx);
|
|
252
|
+
});
|
|
253
|
+
|
|
211
254
|
pi.on("session_shutdown", async () => {
|
|
212
255
|
stopAnimation();
|
|
256
|
+
isActive = false;
|
|
213
257
|
});
|
|
214
258
|
|
|
215
259
|
// -- /caveman command --
|
|
216
260
|
|
|
217
261
|
pi.registerCommand("caveman", {
|
|
218
|
-
description: "Toggle caveman mode, set level, or 'config' to open settings",
|
|
262
|
+
description: "Toggle caveman mode, set level, use stop/off/quit to disable, or 'config' to open settings",
|
|
263
|
+
getArgumentCompletions: (prefix: string) => {
|
|
264
|
+
const normalized = prefix.trim().toLowerCase();
|
|
265
|
+
const items = CAVEMAN_COMMAND_OPTIONS.filter((item) => item.value.startsWith(normalized));
|
|
266
|
+
return items.length > 0 ? items : null;
|
|
267
|
+
},
|
|
219
268
|
handler: async (args, ctx) => {
|
|
220
269
|
const arg = args?.trim().toLowerCase();
|
|
221
270
|
|
|
@@ -227,10 +276,12 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
227
276
|
|
|
228
277
|
if (!arg) {
|
|
229
278
|
level = level === "off" ? "full" : "off";
|
|
279
|
+
} else if (STOP_ALIASES.has(arg)) {
|
|
280
|
+
level = "off";
|
|
230
281
|
} else if (LEVELS.includes(arg as Level)) {
|
|
231
282
|
level = arg as Level;
|
|
232
283
|
} else {
|
|
233
|
-
ctx.ui.notify(`Unknown: "${arg}". Use: ${LEVELS.join(", ")} or config`, "error");
|
|
284
|
+
ctx.ui.notify(`Unknown: "${arg}". Use: ${LEVELS.join(", ")}, stop, quit, or config`, "error");
|
|
234
285
|
return;
|
|
235
286
|
}
|
|
236
287
|
|
|
@@ -268,32 +319,65 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
268
319
|
const container = new Container();
|
|
269
320
|
container.addChild(new Text(theme.fg("accent", theme.bold(" Caveman Config")), 0, 0));
|
|
270
321
|
container.addChild(new Text(theme.fg("dim", " Saved to ~/.pi/agent/caveman.json"), 0, 0));
|
|
322
|
+
container.addChild(new Text(theme.fg("dim", " Default level applies to future sessions."), 0, 0));
|
|
271
323
|
container.addChild(new Text("", 0, 0));
|
|
272
324
|
|
|
325
|
+
const applySettingChange = (id: string, newValue: string) => {
|
|
326
|
+
if (id === "defaultLevel" && LEVELS.includes(newValue as Level)) {
|
|
327
|
+
config.defaultLevel = newValue as Level;
|
|
328
|
+
} else if (id === "showStatus") {
|
|
329
|
+
config.showStatus = newValue === "on";
|
|
330
|
+
}
|
|
331
|
+
saveConfig(config);
|
|
332
|
+
syncStatus(ctx);
|
|
333
|
+
};
|
|
334
|
+
|
|
273
335
|
const settingsList = new SettingsList(
|
|
274
336
|
items,
|
|
275
337
|
Math.min(items.length + 2, 10),
|
|
276
338
|
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
|
-
},
|
|
339
|
+
applySettingChange,
|
|
286
340
|
() => done(undefined),
|
|
287
341
|
);
|
|
288
342
|
|
|
289
343
|
container.addChild(settingsList);
|
|
290
|
-
container.addChild(new Text(theme.fg("dim", "
|
|
344
|
+
container.addChild(new Text(theme.fg("dim", " ←→/hl/tab change • ↑↓/jk move • esc close"), 0, 0));
|
|
345
|
+
|
|
346
|
+
const cycleSelectedValue = (direction: -1 | 1) => {
|
|
347
|
+
const selectedIndex = (settingsList as unknown as { selectedIndex: number }).selectedIndex;
|
|
348
|
+
const item = items[selectedIndex];
|
|
349
|
+
if (!item?.values?.length) return;
|
|
350
|
+
|
|
351
|
+
const currentIndex = item.values.indexOf(item.currentValue);
|
|
352
|
+
const nextIndex = (currentIndex + direction + item.values.length) % item.values.length;
|
|
353
|
+
const newValue = item.values[nextIndex]!;
|
|
354
|
+
item.currentValue = newValue;
|
|
355
|
+
settingsList.updateValue(item.id, newValue);
|
|
356
|
+
applySettingChange(item.id, newValue);
|
|
357
|
+
};
|
|
291
358
|
|
|
292
359
|
return {
|
|
293
360
|
render: (w: number) => container.render(w),
|
|
294
361
|
invalidate: () => container.invalidate(),
|
|
295
362
|
handleInput: (data: string) => {
|
|
363
|
+
if (data === "j") data = "\u001b[B";
|
|
364
|
+
else if (data === "k") data = "\u001b[A";
|
|
365
|
+
else if (data === "h") {
|
|
366
|
+
cycleSelectedValue(-1);
|
|
367
|
+
_tui.requestRender();
|
|
368
|
+
return;
|
|
369
|
+
} else if (data === "l" || data === "\u001b[C" || data === "\t") {
|
|
370
|
+
cycleSelectedValue(1);
|
|
371
|
+
_tui.requestRender();
|
|
372
|
+
return;
|
|
373
|
+
} else if (data === "\u001b[D") {
|
|
374
|
+
cycleSelectedValue(-1);
|
|
375
|
+
_tui.requestRender();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
296
379
|
settingsList.handleInput?.(data);
|
|
380
|
+
_tui.requestRender();
|
|
297
381
|
},
|
|
298
382
|
};
|
|
299
383
|
});
|
|
@@ -303,6 +387,11 @@ export default function caveman(pi: ExtensionAPI) {
|
|
|
303
387
|
|
|
304
388
|
pi.on("before_agent_start", async (event) => {
|
|
305
389
|
if (level === "off") return;
|
|
390
|
+
if (level === "micro") {
|
|
391
|
+
return {
|
|
392
|
+
systemPrompt: `${event.systemPrompt}\n\n${MICRO_PROMPT}`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
306
395
|
return {
|
|
307
396
|
systemPrompt: `${event.systemPrompt}\n\n${BASE}\n\n${INTENSITY[level]}\n\n${SAFETY}`,
|
|
308
397
|
};
|
package/package.json
CHANGED