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