pi-caveman 1.0.2 → 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 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 | Style | Example |
77
- |-------|-------|---------|
78
- | **Lite** | No filler. Full sentences. Professional but tight. | "Your component re-renders because you create a new object reference each render." |
79
- | **Full** | Drop articles, fragments OK. Classic caveman. | "New object ref each render. Wrap in `useMemo`." |
80
- | **Ultra** | Abbreviations, arrows, maximum compression. | "Inline obj prop → new ref → re-render. `useMemo`." |
81
- | **文言文 Lite** | Semi-classical Chinese, grammar intact. | "組件頻重繪,以每繪新生對象參照故。" |
82
- | **文言文** | Full classical terseness. | "物出新參照,致重繪。useMemo Wrap之。" |
83
- | **文言文 Ultra** | Extreme classical compression. | "新參照→重繪。useMemo Wrap。" |
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
@@ -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: { frames: FIRE_FRAMES, label: "LITE", interval: 300 },
89
- full: { frames: FIRE_FRAMES, label: "CAVEMAN", interval: 200 },
90
- ultra: { frames: FIRE_FRAMES, label: "ULTRA", interval: 100 },
91
- "wenyan-lite": { frames: FIRE_FRAMES, label: "文言", interval: 300 },
92
- wenyan: { frames: FIRE_FRAMES, label: "文言文", interval: 200 },
93
- "wenyan-ultra":{ frames: FIRE_FRAMES, label: "文言文極", interval: 100 },
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,7 +128,15 @@ 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 INTENSITY: Record<Exclude<Level, "off">, string> = {
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\`."`,
@@ -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
- const icon = anim.frames[frameIndex % anim.frames.length]!;
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
- (id, newValue) => {
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", " ←→ change • esc close"), 0, 0));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-caveman",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Why use many token when few do trick. Caveman mode for pi — cuts ~75% output tokens while keeping full technical accuracy.",
5
5
  "keywords": [
6
6
  "pi-package",