@kulapard/pi-caveman 0.4.2 → 0.4.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/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+
12
+ - Caveman mode is now project-scoped: `.pi/caveman-mode.json` in the working
13
+ directory persists the mode across new sessions, while session entries still
14
+ override it for the current session. Falls back to `off` when no state exists.
15
+ - Status bar now appends live context usage (`ctx:XX%`) when a caveman mode is
16
+ active and Pi exposes usage data. Updates on mode changes and at each turn end.
17
+
10
18
  ## [0.4.2] - 2026-06-29
11
19
 
12
20
  ### Changed
@@ -0,0 +1,49 @@
1
+ // Project-scoped persistence for caveman mode.
2
+ // This is a workaround until Pi exposes SettingsManager to extensions
3
+ // (tracked upstream as pi-coding-agent issue #4981).
4
+
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { normalizeMode, type StoredMode } from "./caveman-core.ts";
8
+
9
+ const STATE_DIR = ".pi";
10
+ const STATE_FILE = "caveman-mode.json";
11
+
12
+ function statePath(cwd: string): string {
13
+ return join(cwd, STATE_DIR, STATE_FILE);
14
+ }
15
+
16
+ /**
17
+ * Load the project-scoped caveman mode default. Returns `undefined` when no
18
+ * state file exists or it cannot be read, so the caller can fall back to the
19
+ * session-scoped default (`off`).
20
+ */
21
+ export function loadProjectMode(cwd: string): StoredMode | undefined {
22
+ const path = statePath(cwd);
23
+ if (!existsSync(path)) return undefined;
24
+ try {
25
+ const raw = JSON.parse(readFileSync(path, "utf8"));
26
+ return normalizeMode(raw.mode);
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Persist the caveman mode for this project directory. Returns `true` on
34
+ * success; failures are silent so that session operation is never blocked by
35
+ * disk issues.
36
+ */
37
+ export function saveProjectMode(cwd: string, mode: StoredMode): boolean {
38
+ try {
39
+ const dir = join(cwd, STATE_DIR);
40
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
41
+ writeFileSync(
42
+ statePath(cwd),
43
+ JSON.stringify({ mode, updatedAt: Date.now() }, null, 2),
44
+ );
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
@@ -10,6 +10,7 @@ import {
10
10
  normalizeMode,
11
11
  type StoredMode,
12
12
  } from "./caveman-core.ts";
13
+ import { loadProjectMode, saveProjectMode } from "./caveman-state.ts";
13
14
 
14
15
  const HELP_TEXT = `# Caveman for Pi
15
16
 
@@ -22,15 +23,22 @@ Commands:
22
23
  - /caveman-compress <file> — compress prose file via caveman-compress skill.
23
24
  - /caveman-stats — load stats skill/help.
24
25
 
25
- Mode persists in current Pi session and survives /reload via session state.
26
- Code, commands, API names, file paths, and exact errors stay verbatim.`;
26
+ Mode persists across sessions in the same project via \`.pi/caveman-mode.json\`, and
27
+ survives /reload via session state. Code, commands, API names, file paths, and
28
+ exact errors stay verbatim.`;
27
29
 
28
30
  export default function cavemanExtension(pi: ExtensionAPI) {
29
31
  let mode: StoredMode = "off";
30
32
 
31
33
  function setStatus(ctx?: ExtensionContext) {
32
34
  if (!ctx?.hasUI) return;
33
- ctx.ui.setStatus("caveman", mode === "off" ? undefined : `caveman:${mode}`);
35
+ const usage = ctx.getContextUsage?.();
36
+ const suffix =
37
+ usage?.percent != null ? ` ctx:${Math.round(usage.percent)}%` : "";
38
+ ctx.ui.setStatus(
39
+ "caveman",
40
+ mode === "off" ? undefined : `caveman:${mode}${suffix}`,
41
+ );
34
42
  }
35
43
 
36
44
  function persistMode(nextMode: StoredMode, ctx?: ExtensionContext) {
@@ -40,7 +48,7 @@ export default function cavemanExtension(pi: ExtensionAPI) {
40
48
  }
41
49
 
42
50
  pi.on("session_start", (_event, ctx) => {
43
- mode = "off";
51
+ mode = loadProjectMode(ctx.cwd) ?? "off";
44
52
  for (const entry of ctx.sessionManager.getBranch() as Array<{
45
53
  type?: string;
46
54
  customType?: string;
@@ -73,6 +81,7 @@ export default function cavemanExtension(pi: ExtensionAPI) {
73
81
  return;
74
82
  }
75
83
  persistMode(nextMode, ctx);
84
+ saveProjectMode(ctx.cwd, nextMode);
76
85
  ctx.ui.notify(
77
86
  nextMode === "off" ? "Caveman disabled" : `Caveman ${nextMode} enabled`,
78
87
  "info",
@@ -137,14 +146,22 @@ export default function cavemanExtension(pi: ExtensionAPI) {
137
146
  if (event.source !== "extension") {
138
147
  const text = (event.text ?? "").trim();
139
148
  if (DEACTIVATION_RE.test(text)) {
140
- if (mode !== "off") persistMode("off", ctx);
149
+ if (mode !== "off") {
150
+ persistMode("off", ctx);
151
+ saveProjectMode(ctx.cwd, "off");
152
+ }
141
153
  } else if (mode === "off" && ACTIVATION_RE.test(text)) {
142
154
  persistMode("full", ctx);
155
+ saveProjectMode(ctx.cwd, "full");
143
156
  }
144
157
  }
145
158
  return { action: "continue" as const };
146
159
  });
147
160
 
161
+ pi.on("turn_end", (_event, ctx) => {
162
+ setStatus(ctx);
163
+ });
164
+
148
165
  pi.on("before_agent_start", (event) => {
149
166
  if (mode === "off") return undefined;
150
167
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kulapard/pi-caveman",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Caveman for Pi: ultra-compressed agent output that preserves technical substance. Six intensity modes, slash commands, natural-language activation, and a session statusline.",
5
5
  "type": "module",
6
6
  "license": "MIT",