Add caveman compressed-output extension and skill
This commit is contained in:
226
extensions/caveman/index.ts
Normal file
226
extensions/caveman/index.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* pi-caveman — Compressed-output mode for Pi coding agent.
|
||||
*
|
||||
* Registers a `/caveman` slash command that toggles caveman-speak
|
||||
* compression levels and persists state across session restarts.
|
||||
*
|
||||
* Usage:
|
||||
* /caveman — toggle between off and full
|
||||
* /caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off|status
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────
|
||||
|
||||
type CavemanLevel = "lite" | "full" | "ultra" | "wenyan-lite" | "wenyan-full" | "wenyan-ultra";
|
||||
|
||||
interface CavemanState {
|
||||
active: boolean;
|
||||
level: CavemanLevel;
|
||||
}
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────────────────
|
||||
|
||||
const DEFAULT_LEVEL: CavemanLevel = "full";
|
||||
|
||||
const STATE_DIR = path.join(
|
||||
process.env.XDG_STATE_HOME ?? path.join(process.env.HOME!, ".local", "state"),
|
||||
"pi-caveman",
|
||||
);
|
||||
|
||||
const STATE_FILE = path.join(STATE_DIR, "caveman-state.json");
|
||||
|
||||
const CAVEMAN_SKILL_PATH = path.join(
|
||||
process.env.HOME!,
|
||||
"ai-assets",
|
||||
"skills",
|
||||
"caveman",
|
||||
"SKILL.md",
|
||||
);
|
||||
|
||||
// ── State helpers ────────────────────────────────────────────────────────
|
||||
|
||||
function readState(): CavemanState {
|
||||
try {
|
||||
const raw = fs.readFileSync(STATE_FILE, "utf-8");
|
||||
const parsed = JSON.parse(raw);
|
||||
return {
|
||||
active: typeof parsed.active === "boolean" ? parsed.active : false,
|
||||
level: isValidLevel(parsed.level) ? parsed.level : DEFAULT_LEVEL,
|
||||
};
|
||||
} catch {
|
||||
return { active: false, level: DEFAULT_LEVEL };
|
||||
}
|
||||
}
|
||||
|
||||
function writeState(state: CavemanState): void {
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
|
||||
}
|
||||
|
||||
function isValidLevel(v: unknown): v is CavemanLevel {
|
||||
return (
|
||||
typeof v === "string" &&
|
||||
["lite", "full", "ultra", "wenyan-lite", "wenyan-full", "wenyan-ultra"].includes(v)
|
||||
);
|
||||
}
|
||||
|
||||
// ── Skill content cache ──────────────────────────────────────────────────
|
||||
|
||||
let cachedSkillContent: string | null = null;
|
||||
|
||||
function getSkillContent(): string {
|
||||
if (cachedSkillContent !== null) return cachedSkillContent;
|
||||
try {
|
||||
cachedSkillContent = fs.readFileSync(CAVEMAN_SKILL_PATH, "utf-8");
|
||||
return cachedSkillContent;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Extension ────────────────────────────────────────────────────────────
|
||||
|
||||
export default function (pi: ExtensionAPI): void {
|
||||
// ── State ──
|
||||
let state = readState();
|
||||
|
||||
function applyMode(): void {
|
||||
writeState(state);
|
||||
if (state.active) {
|
||||
pi.setStatus("caveman", `🦴 caveman:${state.level}`);
|
||||
// Inject caveman instructions into the system prompt each turn
|
||||
pi.on("before_agent_start", async (_event) => {
|
||||
if (!state.active) return;
|
||||
const skill = getSkillContent();
|
||||
if (!skill) return;
|
||||
return {
|
||||
systemPrompt: `${_event.systemPrompt}\n\n---\nCRITICAL OUTPUT MODE: You are in CAVEMAN MODE. Follow these rules:\n\n${skill}`,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Clear any previous status
|
||||
// (constructor-style state means we can't easily unregister)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Footer badge (always shown) ──
|
||||
function updateFooter(): void {
|
||||
if (state.active) {
|
||||
pi.setStatus("caveman", `🦴 caveman:${state.level}`);
|
||||
} else {
|
||||
pi.setStatus("caveman", "");
|
||||
}
|
||||
}
|
||||
|
||||
// ── /caveman command ────────────────────────────────────────────────
|
||||
|
||||
pi.registerCommand("caveman", {
|
||||
description: "Toggle caveman compressed-output mode (lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off|status)",
|
||||
handler: async (args, ctx) => {
|
||||
const arg = args?.trim().toLowerCase() ?? "";
|
||||
|
||||
switch (arg) {
|
||||
case "":
|
||||
// Toggle: off → full, anything → off
|
||||
if (state.active) {
|
||||
state.active = false;
|
||||
ctx.ui.notify("🦴 Caveman: off", "info");
|
||||
} else {
|
||||
state.active = true;
|
||||
state.level = DEFAULT_LEVEL;
|
||||
ctx.ui.notify(`🦴 Caveman: ${state.level}`, "info");
|
||||
}
|
||||
break;
|
||||
|
||||
case "off":
|
||||
case "disable":
|
||||
state.active = false;
|
||||
ctx.ui.notify("🦴 Caveman: off", "info");
|
||||
break;
|
||||
|
||||
case "status":
|
||||
ctx.ui.notify(
|
||||
state.active ? `🦴 Caveman: ${state.level} (active)` : "🦴 Caveman: off",
|
||||
"info",
|
||||
);
|
||||
return; // Don't update footer (already correct)
|
||||
|
||||
case "lite":
|
||||
case "full":
|
||||
case "ultra":
|
||||
case "wenyan-lite":
|
||||
case "wenyan-full":
|
||||
case "wenyan-ultra":
|
||||
state.active = true;
|
||||
state.level = arg as CavemanLevel;
|
||||
ctx.ui.notify(`🦴 Caveman: ${state.level}`, "info");
|
||||
break;
|
||||
|
||||
default:
|
||||
ctx.ui.notify(
|
||||
`🦴 Unknown level: "${arg}". Use: lite, full, ultra, wenyan-lite, wenyan-full, wenyan-ultra, off, status`,
|
||||
"error",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
writeState(state);
|
||||
updateFooter();
|
||||
|
||||
// Inject skill content as a user message when activating
|
||||
if (state.active) {
|
||||
const skill = getSkillContent();
|
||||
if (skill) {
|
||||
pi.sendUserMessage(
|
||||
`/skill:caveman`,
|
||||
{ deliverAs: "nextTurn" },
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ── Startup ─────────────────────────────────────────────────────────
|
||||
|
||||
pi.on("session_start", async () => {
|
||||
// Re-read state (may have changed externally)
|
||||
state = readState();
|
||||
updateFooter();
|
||||
|
||||
// If active, inject skill into agent context
|
||||
if (state.active) {
|
||||
const skill = getSkillContent();
|
||||
if (skill) {
|
||||
pi.appendEntry("caveman-active", { level: state.level });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Inject caveman rules when active ────────────────────────────────
|
||||
|
||||
pi.on("before_agent_start", async (_event) => {
|
||||
// Re-read state in case it changed
|
||||
const current = readState();
|
||||
if (!current.active) return;
|
||||
|
||||
const skill = getSkillContent();
|
||||
if (!skill) return;
|
||||
|
||||
return {
|
||||
systemPrompt: `${_event.systemPrompt}\n\n---\nCRITICAL OUTPUT MODE: You are in CAVEMAN MODE at level "${current.level}". Follow these rules exactly:\n\n${skill}`,
|
||||
};
|
||||
});
|
||||
|
||||
// ── Cleanup on shutdown ─────────────────────────────────────────────
|
||||
|
||||
pi.on("session_shutdown", async () => {
|
||||
writeState(state);
|
||||
});
|
||||
|
||||
// ── Apply initial mode ──────────────────────────────────────────────
|
||||
applyMode();
|
||||
}
|
||||
16
extensions/caveman/package.json
Normal file
16
extensions/caveman/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "pi-caveman",
|
||||
"version": "1.0.0",
|
||||
"description": "Caveman compressed-output mode for Pi coding agent — toggle with /caveman",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "./index.ts",
|
||||
"pi": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mariozechner/pi-coding-agent": "*"
|
||||
}
|
||||
}
|
||||
79
skills/caveman/SKILL.md
Normal file
79
skills/caveman/SKILL.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: caveman
|
||||
description: >
|
||||
Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman
|
||||
while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra,
|
||||
wenyan-lite, wenyan-full, wenyan-ultra.
|
||||
Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens",
|
||||
"be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.
|
||||
---
|
||||
|
||||
Terse like smart caveman. All technical substance stay. Only fluff die.
|
||||
|
||||
## Persistence
|
||||
|
||||
ACTIVE EVERY RESPONSE. No revert after many turns. No filler drift. Still active if unsure.
|
||||
Off only: "stop caveman" / "normal mode".
|
||||
|
||||
Default: **full**. Switch: `/caveman lite|full|ultra`.
|
||||
|
||||
## Rules
|
||||
|
||||
Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries
|
||||
(sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive,
|
||||
fix not "implement a solution for"). Technical terms exact. Code blocks unchanged.
|
||||
Errors quoted exact.
|
||||
|
||||
Pattern: `[thing] [action] [reason]. [next step].`
|
||||
|
||||
Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
|
||||
Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
|
||||
|
||||
## Intensity
|
||||
|
||||
| Level | What change |
|
||||
|-------|------------|
|
||||
| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight |
|
||||
| **full** | Drop articles, fragments OK, short synonyms. Classic caveman |
|
||||
| **ultra** | Abbreviate prose words (DB/auth/config/req/res/fn/impl), strip conjunctions, arrows for causality (X → Y), one word when one word enough. Code symbols, function names, API names, error strings: never abbreviate |
|
||||
| **wenyan-lite** | Semi-classical. Drop filler/hedging but keep grammar structure, classical register |
|
||||
| **wenyan-full** | Maximum classical terseness. Fully 文言文. 80-90% character reduction. Classical sentence patterns, verbs precede objects, subjects often omitted, classical particles (之/乃/為/其) |
|
||||
| **wenyan-ultra** | Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse |
|
||||
|
||||
Example — "Why React component re-render?"
|
||||
- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
|
||||
- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
|
||||
- ultra: "Inline obj prop → new ref → re-render. `useMemo`."
|
||||
- wenyan-lite: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。"
|
||||
- wenyan-full: "物出新參照,致重繪。useMemo Wrap之。"
|
||||
- wenyan-ultra: "新參照→重繪。useMemo Wrap。"
|
||||
|
||||
Example — "Explain database connection pooling."
|
||||
- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead."
|
||||
- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead."
|
||||
- ultra: "Pool = reuse DB conn. Skip handshake → fast under load."
|
||||
- wenyan-full: "池reuse open connection。不每req新開。skip handshake overhead。"
|
||||
- wenyan-ultra: "池reuse conn。skip handshake → fast。"
|
||||
|
||||
## Auto-Clarity
|
||||
|
||||
Drop caveman when:
|
||||
- Security warnings
|
||||
- Irreversible action confirmations
|
||||
- Multi-step sequences where fragment order or omitted conjunctions risk misread
|
||||
- Compression itself creates technical ambiguity (e.g., "migrate table drop column backup first" — order unclear without articles/conjunctions)
|
||||
- User asks to clarify or repeats question
|
||||
|
||||
Resume caveman after clear part done.
|
||||
|
||||
Example — destructive op:
|
||||
> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
|
||||
> ```sql
|
||||
> DROP TABLE users;
|
||||
> ```
|
||||
> Caveman resume. Verify backup exist first.
|
||||
|
||||
## Boundaries
|
||||
|
||||
Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert.
|
||||
Level persist until changed or session end.
|
||||
Reference in New Issue
Block a user