@krtclcdy/tersio 2.22.1 → 2.24.0

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.
Files changed (59) hide show
  1. package/README.md +62 -46
  2. package/cli/agent-hosts.js +42 -0
  3. package/cli/agents.js +266 -0
  4. package/cli/common.js +55 -55
  5. package/cli/currency.js +8 -12
  6. package/cli/dashboard.js +76 -49
  7. package/cli/doctor-fix.js +61 -44
  8. package/cli/doctor.js +66 -44
  9. package/cli/hosts.js +118 -0
  10. package/cli/install.js +187 -143
  11. package/cli/interactive.js +27 -1
  12. package/cli/layer.js +24 -0
  13. package/cli/manifest.js +36 -0
  14. package/cli/omp-layer.js +51 -0
  15. package/cli/pi-layer.js +37 -0
  16. package/cli/pi-wiring.js +92 -0
  17. package/cli/profile.js +45 -46
  18. package/cli/reset.js +3 -26
  19. package/cli/rtk-wiring.js +10 -15
  20. package/cli/settings.js +1 -1
  21. package/cli/uninstall.js +135 -44
  22. package/cli/update.js +24 -30
  23. package/cli/usage.js +6 -1
  24. package/dashboard/dist/index.html +16 -16
  25. package/extensions/ai-addons-updater/index.js +36 -25
  26. package/extensions/ai-addons-updater/index.ts +36 -24
  27. package/extensions/caveman-session/index.js +23 -18
  28. package/extensions/caveman-session/index.ts +23 -18
  29. package/extensions/caveman-session/rule.md +90 -0
  30. package/extensions/combo-toggle/index.js +63 -41
  31. package/extensions/combo-toggle/index.ts +61 -48
  32. package/extensions/lib/utils.js +56 -13
  33. package/extensions/lib/utils.ts +55 -13
  34. package/extensions/rtk-session/index.js +20 -19
  35. package/extensions/rtk-session/index.ts +18 -24
  36. package/extensions/shared/carbon.js +5 -8
  37. package/extensions/shared/carbon.ts +5 -8
  38. package/extensions/shared/host.js +68 -0
  39. package/extensions/shared/host.ts +104 -0
  40. package/extensions/shared/omp-prompt.js +12 -0
  41. package/extensions/shared/plugin-settings.js +63 -16
  42. package/extensions/shared/plugin-settings.ts +67 -17
  43. package/extensions/shared/pricing.js +26 -15
  44. package/extensions/shared/pricing.ts +28 -16
  45. package/extensions/shared/rtk-gain.js +13 -24
  46. package/extensions/shared/rtk-gain.ts +14 -25
  47. package/extensions/shared/session-state.js +26 -28
  48. package/extensions/shared/session-state.ts +26 -29
  49. package/extensions/shared/types.ts +15 -3
  50. package/extensions/shared/usage-ledger.js +136 -38
  51. package/extensions/shared/usage-ledger.ts +149 -52
  52. package/extensions/shared/usage-store.js +14 -12
  53. package/extensions/shared/usage-store.ts +16 -14
  54. package/extensions/tersio-commands/index.js +2 -1
  55. package/extensions/tersio-commands/index.ts +2 -1
  56. package/package.json +15 -7
  57. package/tersio.js +6 -6
  58. package/extensions/shared/mode-reinforcement.js +0 -26
  59. package/extensions/shared/mode-reinforcement.ts +0 -29
@@ -1,21 +1,26 @@
1
- // OMP extension: /ai-addons manual updater for Ponytail, RTK, Caveman.
2
- // Built-in Node modules only. Default off; registers a single slash command.
3
- // ponytail: `skipped: none` — semantics match one-liner: fetch + compare + run install.
4
- // rtk: `skipped: signature verification` — checksums.txt ships only SHA256 of release assets; add sigchain when upstream publishes a signing key.
5
- // caveman: `skipped: none` — exactly the ask: write rule.md, report old/new hash.
1
+ // /ai-addons manual updater. Node built-ins only. RTK ships SHA256 checksums
2
+ // with no signature, so checksum-only is the best available.
6
3
  import { execFileSync } from 'node:child_process';
7
4
  import { createHash } from 'node:crypto';
8
5
  import fs from 'node:fs/promises';
9
6
  import os from 'node:os';
10
7
  import path from 'node:path';
11
- import { CAVEMAN_REMOTE_RULE as CAVEMAN_REMOTE, RTK_RELEASE_API, fetchJson, findFile, httpsGet, httpsDownload, sha256Hex, parseChecksum, normalizeRtkVersion, readTextIfExists, rtkPlatformSpec, } from '../lib/utils.js';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { setExtensionLabel } from '../shared/host.js';
10
+ import { CAVEMAN_REMOTE_RULE as CAVEMAN_REMOTE, RTK_RELEASE_API, fetchJson, findFile, findHoistedPackage, httpsGet, httpsDownload, isPiProcess, sha256Hex, parseChecksum, normalizeRtkVersion, readTextIfExists, rtkPlatformSpec, resolveRtkBinary, } from '../lib/utils.js';
12
11
  const IS_WINDOWS = process.platform === 'win32';
13
- const HOME = os.homedir();
12
+ const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
14
13
  const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
15
- const PONYTAIL_LOCAL = path.join(HOME, '.omp', 'plugins', 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
16
- const RTK_BINARY = path.join(HOME, '.bun', 'bin', IS_WINDOWS ? 'rtk.exe' : 'rtk');
17
- const CAVEMAN_LOCAL = path.join(HOME, '.omp', 'agent', 'extensions', 'caveman-session', 'rule.md');
18
- const RELOAD_MSG = 'Reminder: restart OMP (or reload extensions) for updates to take effect.';
14
+ const RTK_BINARY = resolveRtkBinary();
15
+ // Beside this file, not a host path: that is the rule.md /caveman reads.
16
+ const CAVEMAN_LOCAL = path.join(EXTENSION_DIR, '..', 'caveman-session', 'rule.md');
17
+ const HOST_NAME = isPiProcess() ? 'Pi' : 'OMP';
18
+ const RELOAD_MSG = `Reminder: restart ${HOST_NAME} (or reload extensions) for updates to take effect.`;
19
+ // Ponytail is a hoisted dependency, so the installed copy is found by walking
20
+ // up rather than by naming a host directory.
21
+ function ponytailLocal() {
22
+ return findHoistedPackage('@dietrichgebert/ponytail', EXTENSION_DIR, 'package.json');
23
+ }
19
24
  function notify(ctx, msg, level) {
20
25
  ctx?.ui?.notify?.(String(msg), level);
21
26
  }
@@ -29,16 +34,25 @@ function shortHash(text) {
29
34
  function checkFailed(name, e) {
30
35
  return { text: `${name} check failed: ${e.message}`, level: 'warning' };
31
36
  }
32
- // Single error-handling source for the three probes; messages unchanged.
33
37
  function runCheck(name, probe) {
34
38
  return probe().then((text) => ({ text, level: 'info' }), (e) => checkFailed(name, e));
35
39
  }
36
- // Check: no mutation. Probes run concurrently via checkAddons below.
40
+ // A missing file is "not installed"; an unreadable one is a reportable fault.
41
+ function parsePackageVersion(raw) {
42
+ if (!raw)
43
+ return null;
44
+ try {
45
+ return JSON.parse(raw).version ?? null;
46
+ }
47
+ catch {
48
+ throw new Error('ponytail package.json is not readable JSON');
49
+ }
50
+ }
37
51
  function checkPonytail() {
38
52
  return runCheck('Ponytail', async () => {
39
53
  const remoteJson = await fetchJson(PONYTAIL_REMOTE);
40
- const localRaw = await readTextIfExists(PONYTAIL_LOCAL);
41
- const localVer = localRaw ? JSON.parse(localRaw).version ?? null : null;
54
+ const localRaw = await readTextIfExists(ponytailLocal() ?? '');
55
+ const localVer = parsePackageVersion(localRaw);
42
56
  const remoteVer = remoteJson.version;
43
57
  const status = !localVer ? 'not installed'
44
58
  : localVer === remoteVer ? 'up to date'
@@ -55,6 +69,8 @@ function checkRtk() {
55
69
  const latestTag = release.tag_name || null;
56
70
  let localVer = null;
57
71
  try {
72
+ if (!RTK_BINARY)
73
+ throw new Error('rtk not found in PATH');
58
74
  const out = execFileSync(RTK_BINARY, ['--version'], { encoding: 'utf8', windowsHide: true, shell: false, timeout: 10000 }) || '';
59
75
  if (out)
60
76
  localVer = out.trim().split(/\r?\n/)[0];
@@ -118,13 +134,10 @@ async function checkAddons(ctx) {
118
134
  return lines.join('\n');
119
135
  }
120
136
  async function updatePonytail(pi, ctx, dryRun = false) {
121
- // Bundled with tersio: no separate package to refresh. `tersio update`
122
- // pulls the bundled copy with the CLI.
137
+ // Bundled with tersio: `tersio update` pulls the copy with the CLI.
123
138
  let localVer = null;
124
139
  try {
125
- const raw = await readTextIfExists(PONYTAIL_LOCAL);
126
- if (raw)
127
- localVer = JSON.parse(raw).version ?? null;
140
+ localVer = parsePackageVersion(await readTextIfExists(ponytailLocal() ?? ''));
128
141
  }
129
142
  catch {
130
143
  localVer = null;
@@ -146,7 +159,8 @@ async function updateRtk(ctx, dryRun = false) {
146
159
  }
147
160
  const tag = release.tag_name || 'unknown';
148
161
  const assets = Array.isArray(release.assets) ? release.assets : [];
149
- // Cross-platform asset selection (mirrors installer stepRtk)
162
+ if (!RTK_BINARY)
163
+ return report(ctx, 'RTK: executable not found in PATH', 'warning');
150
164
  const PLATFORM = process.platform;
151
165
  const ARCH = process.arch;
152
166
  const spec = rtkPlatformSpec(PLATFORM, ARCH);
@@ -175,7 +189,6 @@ async function updateRtk(ctx, dryRun = false) {
175
189
  httpsDownload(asset.browser_download_url, archivePath),
176
190
  httpsDownload(checksAsset.browser_download_url, checksPath),
177
191
  ]);
178
- // Verify SHA256 against checksums.txt
179
192
  const checks = await fs.readFile(checksPath, 'utf8');
180
193
  const expected = parseChecksum(checks, asset.name);
181
194
  if (!expected) {
@@ -189,7 +202,6 @@ async function updateRtk(ctx, dryRun = false) {
189
202
  return report(ctx, m, 'warning');
190
203
  }
191
204
  notify(ctx, 'RTK: checksum verified.', 'info');
192
- // Extract by archive format
193
205
  const extractDir = path.join(tmp, 'extracted');
194
206
  await fs.mkdir(extractDir, { recursive: true });
195
207
  if (asset.name.endsWith('.zip')) {
@@ -221,7 +233,6 @@ async function updateRtk(ctx, dryRun = false) {
221
233
  const backupPath = `${RTK_BINARY}.bak`;
222
234
  const backedUp = await fs.copyFile(RTK_BINARY, backupPath).then(() => true, () => false);
223
235
  await fs.copyFile(rtkExtracted, RTK_BINARY);
224
- // Set executable bit on Unix
225
236
  if (!IS_WINDOWS) {
226
237
  await fs.chmod(RTK_BINARY, 0o755);
227
238
  }
@@ -289,7 +300,7 @@ async function updateCaveman(ctx, dryRun = false) {
289
300
  }
290
301
  }
291
302
  export default function aiAddonsUpdaterExtension(pi) {
292
- pi.setLabel?.('AI add-ons updater');
303
+ setExtensionLabel(pi, 'AI add-ons updater');
293
304
  pi.registerCommand?.('ai-addons', {
294
305
  description: 'Check or update AI add-ons (ponytail/rtk/caveman/all). Usage: /ai-addons <check|status|update ponytail|rtk|caveman|all> [--dry-run]',
295
306
  handler: async (args, ctx) => {
@@ -1,39 +1,47 @@
1
- // OMP extension: /ai-addons manual updater for Ponytail, RTK, Caveman.
2
- // Built-in Node modules only. Default off; registers a single slash command.
3
- // ponytail: `skipped: none` — semantics match one-liner: fetch + compare + run install.
4
- // rtk: `skipped: signature verification` — checksums.txt ships only SHA256 of release assets; add sigchain when upstream publishes a signing key.
5
- // caveman: `skipped: none` — exactly the ask: write rule.md, report old/new hash.
1
+ // /ai-addons manual updater. Node built-ins only. RTK ships SHA256 checksums
2
+ // with no signature, so checksum-only is the best available.
6
3
 
7
4
  import { execFileSync } from 'node:child_process';
8
5
  import { createHash } from 'node:crypto';
9
6
  import fs from 'node:fs/promises';
10
7
  import os from 'node:os';
11
8
  import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
12
10
 
11
+ import { setExtensionLabel } from '../shared/host.ts';
13
12
  import {
14
13
  CAVEMAN_REMOTE_RULE as CAVEMAN_REMOTE,
15
14
  RTK_RELEASE_API,
16
15
  RtkRelease,
17
16
  fetchJson,
18
17
  findFile,
18
+ findHoistedPackage,
19
19
  httpsGet,
20
20
  httpsDownload,
21
+ isPiProcess,
21
22
  sha256Hex,
22
23
  parseChecksum,
23
24
  normalizeRtkVersion,
24
25
  readTextIfExists,
25
26
  rtkPlatformSpec,
27
+ resolveRtkBinary,
26
28
  } from '../lib/utils.ts';
27
29
 
28
30
  const IS_WINDOWS = process.platform === 'win32';
29
- const HOME = os.homedir();
31
+ const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
30
32
 
31
33
  const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
32
- const PONYTAIL_LOCAL = path.join(HOME, '.omp', 'plugins', 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
33
- const RTK_BINARY = path.join(HOME, '.bun', 'bin', IS_WINDOWS ? 'rtk.exe' : 'rtk');
34
- const CAVEMAN_LOCAL = path.join(HOME, '.omp', 'agent', 'extensions', 'caveman-session', 'rule.md');
35
-
36
- const RELOAD_MSG = 'Reminder: restart OMP (or reload extensions) for updates to take effect.';
34
+ const RTK_BINARY = resolveRtkBinary();
35
+ // Beside this file, not a host path: that is the rule.md /caveman reads.
36
+ const CAVEMAN_LOCAL = path.join(EXTENSION_DIR, '..', 'caveman-session', 'rule.md');
37
+ const HOST_NAME = isPiProcess() ? 'Pi' : 'OMP';
38
+ const RELOAD_MSG = `Reminder: restart ${HOST_NAME} (or reload extensions) for updates to take effect.`;
39
+
40
+ // Ponytail is a hoisted dependency, so the installed copy is found by walking
41
+ // up rather than by naming a host directory.
42
+ function ponytailLocal(): string | null {
43
+ return findHoistedPackage('@dietrichgebert/ponytail', EXTENSION_DIR, 'package.json');
44
+ }
37
45
 
38
46
  // --- Types ---
39
47
 
@@ -75,17 +83,25 @@ function checkFailed(name: string, e: unknown): AddonStatus {
75
83
  return { text: `${name} check failed: ${(e as Error).message}`, level: 'warning' };
76
84
  }
77
85
 
78
- // Single error-handling source for the three probes; messages unchanged.
79
86
  function runCheck(name: string, probe: () => Promise<string>): Promise<AddonStatus> {
80
87
  return probe().then((text) => ({ text, level: 'info' as const }), (e) => checkFailed(name, e));
81
88
  }
82
89
 
83
- // Check: no mutation. Probes run concurrently via checkAddons below.
90
+ // A missing file is "not installed"; an unreadable one is a reportable fault.
91
+ function parsePackageVersion(raw: string | null): string | null {
92
+ if (!raw) return null;
93
+ try {
94
+ return (JSON.parse(raw) as { version?: string }).version ?? null;
95
+ } catch {
96
+ throw new Error('ponytail package.json is not readable JSON');
97
+ }
98
+ }
99
+
84
100
  function checkPonytail(): Promise<AddonStatus> {
85
101
  return runCheck('Ponytail', async () => {
86
102
  const remoteJson = await fetchJson<{ version?: string }>(PONYTAIL_REMOTE);
87
- const localRaw = await readTextIfExists(PONYTAIL_LOCAL);
88
- const localVer = localRaw ? (JSON.parse(localRaw) as { version?: string }).version ?? null : null;
103
+ const localRaw = await readTextIfExists(ponytailLocal() ?? '');
104
+ const localVer = parsePackageVersion(localRaw);
89
105
  const remoteVer = remoteJson.version;
90
106
  const status = !localVer ? 'not installed'
91
107
  : localVer === remoteVer ? 'up to date'
@@ -104,6 +120,7 @@ function checkRtk(): Promise<AddonStatus> {
104
120
  const latestTag = release.tag_name || null;
105
121
  let localVer: string | null = null;
106
122
  try {
123
+ if (!RTK_BINARY) throw new Error('rtk not found in PATH');
107
124
  const out = execFileSync(RTK_BINARY, ['--version'], { encoding: 'utf8', windowsHide: true, shell: false, timeout: 10000 }) || '';
108
125
  if (out) localVer = out.trim().split(/\r?\n/)[0];
109
126
  } catch { localVer = null; }
@@ -164,12 +181,10 @@ async function checkAddons(ctx: AddonUpdaterCtx): Promise<string> {
164
181
  }
165
182
 
166
183
  async function updatePonytail(pi: AddonUpdaterPi, ctx: AddonUpdaterCtx, dryRun = false): Promise<string> {
167
- // Bundled with tersio: no separate package to refresh. `tersio update`
168
- // pulls the bundled copy with the CLI.
184
+ // Bundled with tersio: `tersio update` pulls the copy with the CLI.
169
185
  let localVer: string | null = null;
170
186
  try {
171
- const raw = await readTextIfExists(PONYTAIL_LOCAL);
172
- if (raw) localVer = (JSON.parse(raw) as { version?: string }).version ?? null;
187
+ localVer = parsePackageVersion(await readTextIfExists(ponytailLocal() ?? ''));
173
188
  } catch { localVer = null; }
174
189
  void pi;
175
190
  const m = dryRun
@@ -188,8 +203,8 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
188
203
  }
189
204
  const tag = release.tag_name || 'unknown';
190
205
  const assets = Array.isArray(release.assets) ? release.assets : [];
206
+ if (!RTK_BINARY) return report(ctx, 'RTK: executable not found in PATH', 'warning');
191
207
 
192
- // Cross-platform asset selection (mirrors installer stepRtk)
193
208
  const PLATFORM = process.platform;
194
209
  const ARCH = process.arch;
195
210
  const spec = rtkPlatformSpec(PLATFORM, ARCH);
@@ -222,7 +237,6 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
222
237
  httpsDownload(asset.browser_download_url, archivePath),
223
238
  httpsDownload(checksAsset.browser_download_url, checksPath),
224
239
  ]);
225
- // Verify SHA256 against checksums.txt
226
240
  const checks = await fs.readFile(checksPath, 'utf8');
227
241
  const expected = parseChecksum(checks, asset.name);
228
242
  if (!expected) {
@@ -237,7 +251,6 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
237
251
  }
238
252
  notify(ctx, 'RTK: checksum verified.', 'info');
239
253
 
240
- // Extract by archive format
241
254
  const extractDir = path.join(tmp, 'extracted');
242
255
  await fs.mkdir(extractDir, { recursive: true });
243
256
 
@@ -269,7 +282,6 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
269
282
 
270
283
  await fs.copyFile(rtkExtracted, RTK_BINARY);
271
284
 
272
- // Set executable bit on Unix
273
285
  if (!IS_WINDOWS) {
274
286
  await fs.chmod(RTK_BINARY, 0o755);
275
287
  }
@@ -331,7 +343,7 @@ async function updateCaveman(ctx: AddonUpdaterCtx, dryRun = false): Promise<stri
331
343
  }
332
344
 
333
345
  export default function aiAddonsUpdaterExtension(pi: AddonUpdaterPi): void {
334
- pi.setLabel?.('AI add-ons updater');
346
+ setExtensionLabel(pi, 'AI add-ons updater');
335
347
 
336
348
  pi.registerCommand?.('ai-addons', {
337
349
  description: 'Check or update AI add-ons (ponytail/rtk/caveman/all). Usage: /ai-addons <check|status|update ponytail|rtk|caveman|all> [--dry-run]',
@@ -1,20 +1,25 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { activeModesSummary, asPromptArray, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode } from '../shared/session-state.js';
3
+ import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode } from '../shared/session-state.js';
4
4
  import { dirname, join } from 'node:path';
5
+ import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
5
6
  import { readCavemanDefault } from '../shared/plugin-settings.js';
6
7
  const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
7
8
  const RULE_PATH = join(CAVERN_DIR, 'rule.md');
8
- const FALLBACK_FULL_RULE = `Respond terse like smart caveman. All technical substance stay. Only fluff die.
9
+ const FALLBACK_FULL_RULE = `Caveman full active for this session.
10
+ Respond terse like smart caveman. Keep all technical substance. Drop filler, pleasantries, and hedging.
9
11
 
10
12
  Rules:
11
- - Drop articles (a/an/the), filler (just/really/basically), pleasantries, hedging.
12
- - Fragments OK. Short synonyms. Technical terms exact. Code unchanged.
13
- - Pattern: [thing] [action] [reason]. [next step].
14
- - Not: 'Sure! I'd be happy to help you with that.'
15
- - Yes: 'Bug in auth middleware. Fix:'
13
+ - Drop articles where meaning stays clear. Keep fragments and short sentences.
14
+ - Keep technical terms, code, commands, paths, API names, numbers, units, and errors exact.
15
+ - Never invent abbreviations or causal arrows. Do not add words to sound caveman.
16
+ - Use ASD-STE100 Simplified Technical English. Keep one idea per sentence, target 20 words, active voice, and consistent terms.
17
+ - Make no tool-call narration. Use no decorative tables or emoji.
18
+ - Preserve the user's reply language. Compress style, not language.
19
+ - Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or requests to clarify.
20
+ - Write code, comments, commits, docs, issues, pull requests, and third-party messages in normal prose.
16
21
 
17
- Auto-clarity: drop caveman for security warnings, irreversible actions, or when user seems confused. Resume after.`;
22
+ Default: **full**. Switch: \`/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off\`. Stop: "stop caveman" or "normal mode".`;
18
23
  // ponytail: synchronous read each full-mode injection; ceiling = small file, cold session start. Upgrade path: cache file contents + mtime, invalidate on change.
19
24
  function readFullRule() {
20
25
  try {
@@ -30,9 +35,10 @@ const INSTRUCTIONS = {
30
35
  Respond concise. Drop pleasantries, filler, and hedging. Keep complete technical substance. Code, commands, paths, errors, commits, and PR text stay normal/exact.`,
31
36
  full: () => `Caveman full active for this session.\n${readFullRule()}`,
32
37
  ultra: `Caveman ultra active for this session.
33
- Maximum terse prose. Fragments preferred. No pleasantries, no tour, no recap unless needed. Keep all technical substance exact. Code, commands, commits, PR text, paths, and errors stay normal/exact. Drop caveman for security warnings, irreversible actions, or user confusion.`,
34
- wenyan: `Caveman wenyan active for this session.
35
- Use ultra-terse classical-Chinese-style prose only where it preserves clarity for the user. Keep technical terms, code, commands, commits, PR text, paths, and errors exact. If clarity would suffer, use caveman full instead.`,
38
+ Maximum terse prose. Strip conjunctions only when meaning stays clear. State each fact once. Use no invented abbreviations or causal arrows. Keep code, commands, commits, PR text, paths, API names, numbers, units, and errors exact. Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or user confusion.`,
39
+ 'wenyan-lite': `Caveman wenyan-lite active for this session. Use semi-classical terse prose. Keep grammar structure, user language, technical terms, code, commands, paths, API names, numbers, units, and errors exact. Drop caveman when clarity or safety needs normal prose.`,
40
+ 'wenyan-full': `Caveman wenyan-full active for this session. Use maximum classical terseness where meaning stays clear. Use classical sentence patterns, verbs before objects, and optional omitted subjects. Keep user language, technical terms, code, commands, paths, API names, numbers, units, and errors exact. Drop caveman when clarity or safety needs normal prose.`,
41
+ 'wenyan-ultra': `Caveman wenyan-ultra active for this session. Use extreme classical abbreviation while keeping a classical Chinese feel and meaning clear. Keep user language, technical terms, code, commands, paths, API names, numbers, units, and errors exact. Drop caveman when clarity or safety needs normal prose.`,
36
42
  };
37
43
  function resolveMode(entries, fallback = DEFAULT_MODE) {
38
44
  return lastCustomValue(entries, 'caveman-mode', (data) => normalizeMode('caveman', data?.mode)) ?? fallback;
@@ -72,7 +78,7 @@ export default function cavemanSessionExtension(pi) {
72
78
  ctx?.ui?.notify?.(msg, 'info');
73
79
  return true;
74
80
  }
75
- pi.setLabel?.('Caveman session toggle');
81
+ setExtensionLabel(pi, 'Caveman session toggle');
76
82
  // Live mirror: a /tersio or /combo switch publishes shared state — adopt
77
83
  // it at once so the next turn injects the new mode with no session reload.
78
84
  // Stable identity, so the bridge set dedupes across re-inits.
@@ -97,11 +103,11 @@ export default function cavemanSessionExtension(pi) {
97
103
  return;
98
104
  }
99
105
  if (!setMode(arg, ctx)) {
100
- ctx?.ui?.notify?.('Usage: /caveman [lite|full|ultra|wenyan|off|status]', 'warning');
106
+ ctx?.ui?.notify?.('Usage: /caveman [lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off|status]', 'warning');
101
107
  }
102
108
  },
103
109
  });
104
- pi.on('input', async (event) => {
110
+ onHostEvent(pi, 'input', async (event) => {
105
111
  if (event?.source === 'extension')
106
112
  return;
107
113
  if (currentMode !== 'off' && isOffCommand(event?.text))
@@ -117,14 +123,14 @@ export default function cavemanSessionExtension(pi) {
117
123
  // Persisted session state wins; a fresh session falls back to the
118
124
  // installer/user-configured default (off unless configured).
119
125
  const persisted = resolveMode(entries, '');
120
- currentMode = persisted || readCavemanDefault();
126
+ currentMode = persisted || normalizeMode('caveman', readCavemanDefault()) || DEFAULT_MODE;
121
127
  syncStatus(ctx);
122
128
  }
123
129
  pi.on('session_start', async (_event, ctx) => {
124
130
  restoreMode(ctx);
125
131
  ctx?.ui?.notify?.(`Caveman loaded: ${currentMode}`, 'info');
126
132
  });
127
- pi.on('session_branch', async (_event, ctx) => {
133
+ onHostEvent(pi, 'session_branch', async (_event, ctx) => {
128
134
  restoreMode(ctx);
129
135
  });
130
136
  pi.on('session_tree', async (_event, ctx) => {
@@ -146,7 +152,6 @@ export default function cavemanSessionExtension(pi) {
146
152
  if (!def)
147
153
  return;
148
154
  const instruction = typeof def === 'function' ? def() : def;
149
- const base = asPromptArray(event.systemPrompt);
150
- return { systemPrompt: [...base, instruction] };
155
+ return injectPromptText(pi, event, instruction);
151
156
  });
152
157
  }
@@ -1,23 +1,28 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { activeModesSummary, asPromptArray, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode } from '../shared/session-state.ts';
3
+ import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode } from '../shared/session-state.ts';
4
4
  import { dirname, join } from 'node:path';
5
+ import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.ts';
5
6
  import { readCavemanDefault } from '../shared/plugin-settings.ts';
6
7
  import type { ExtensionApi, ExtensionCtx, InputEvent, SessionEntry, SystemPromptEvent } from '../shared/types.ts';
7
8
 
8
9
  const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
9
10
  const RULE_PATH = join(CAVERN_DIR, 'rule.md');
10
11
 
11
- const FALLBACK_FULL_RULE = `Respond terse like smart caveman. All technical substance stay. Only fluff die.
12
+ const FALLBACK_FULL_RULE = `Caveman full active for this session.
13
+ Respond terse like smart caveman. Keep all technical substance. Drop filler, pleasantries, and hedging.
12
14
 
13
15
  Rules:
14
- - Drop articles (a/an/the), filler (just/really/basically), pleasantries, hedging.
15
- - Fragments OK. Short synonyms. Technical terms exact. Code unchanged.
16
- - Pattern: [thing] [action] [reason]. [next step].
17
- - Not: 'Sure! I'd be happy to help you with that.'
18
- - Yes: 'Bug in auth middleware. Fix:'
16
+ - Drop articles where meaning stays clear. Keep fragments and short sentences.
17
+ - Keep technical terms, code, commands, paths, API names, numbers, units, and errors exact.
18
+ - Never invent abbreviations or causal arrows. Do not add words to sound caveman.
19
+ - Use ASD-STE100 Simplified Technical English. Keep one idea per sentence, target 20 words, active voice, and consistent terms.
20
+ - Make no tool-call narration. Use no decorative tables or emoji.
21
+ - Preserve the user's reply language. Compress style, not language.
22
+ - Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or requests to clarify.
23
+ - Write code, comments, commits, docs, issues, pull requests, and third-party messages in normal prose.
19
24
 
20
- Auto-clarity: drop caveman for security warnings, irreversible actions, or when user seems confused. Resume after.`;
25
+ Default: **full**. Switch: \`/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off\`. Stop: "stop caveman" or "normal mode".`;
21
26
 
22
27
  // ponytail: synchronous read each full-mode injection; ceiling = small file, cold session start. Upgrade path: cache file contents + mtime, invalidate on change.
23
28
  function readFullRule(): string {
@@ -30,9 +35,10 @@ const INSTRUCTIONS: Record<string, string | (() => string)> = {
30
35
  Respond concise. Drop pleasantries, filler, and hedging. Keep complete technical substance. Code, commands, paths, errors, commits, and PR text stay normal/exact.`,
31
36
  full: () => `Caveman full active for this session.\n${readFullRule()}`,
32
37
  ultra: `Caveman ultra active for this session.
33
- Maximum terse prose. Fragments preferred. No pleasantries, no tour, no recap unless needed. Keep all technical substance exact. Code, commands, commits, PR text, paths, and errors stay normal/exact. Drop caveman for security warnings, irreversible actions, or user confusion.`,
34
- wenyan: `Caveman wenyan active for this session.
35
- Use ultra-terse classical-Chinese-style prose only where it preserves clarity for the user. Keep technical terms, code, commands, commits, PR text, paths, and errors exact. If clarity would suffer, use caveman full instead.`,
38
+ Maximum terse prose. Strip conjunctions only when meaning stays clear. State each fact once. Use no invented abbreviations or causal arrows. Keep code, commands, commits, PR text, paths, API names, numbers, units, and errors exact. Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or user confusion.`,
39
+ 'wenyan-lite': `Caveman wenyan-lite active for this session. Use semi-classical terse prose. Keep grammar structure, user language, technical terms, code, commands, paths, API names, numbers, units, and errors exact. Drop caveman when clarity or safety needs normal prose.`,
40
+ 'wenyan-full': `Caveman wenyan-full active for this session. Use maximum classical terseness where meaning stays clear. Use classical sentence patterns, verbs before objects, and optional omitted subjects. Keep user language, technical terms, code, commands, paths, API names, numbers, units, and errors exact. Drop caveman when clarity or safety needs normal prose.`,
41
+ 'wenyan-ultra': `Caveman wenyan-ultra active for this session. Use extreme classical abbreviation while keeping a classical Chinese feel and meaning clear. Keep user language, technical terms, code, commands, paths, API names, numbers, units, and errors exact. Drop caveman when clarity or safety needs normal prose.`,
36
42
  };
37
43
 
38
44
  function resolveMode(entries: SessionEntry[] | null | undefined, fallback: string = DEFAULT_MODE): string {
@@ -76,7 +82,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
76
82
  return true;
77
83
  }
78
84
 
79
- pi.setLabel?.('Caveman session toggle');
85
+ setExtensionLabel(pi, 'Caveman session toggle');
80
86
 
81
87
  // Live mirror: a /tersio or /combo switch publishes shared state — adopt
82
88
  // it at once so the next turn injects the new mode with no session reload.
@@ -102,12 +108,12 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
102
108
  return;
103
109
  }
104
110
  if (!setMode(arg, ctx)) {
105
- ctx?.ui?.notify?.('Usage: /caveman [lite|full|ultra|wenyan|off|status]', 'warning');
111
+ ctx?.ui?.notify?.('Usage: /caveman [lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off|status]', 'warning');
106
112
  }
107
113
  },
108
114
  });
109
115
 
110
- pi.on<InputEvent>('input', async (event) => {
116
+ onHostEvent<InputEvent>(pi, 'input', async (event) => {
111
117
  if (event?.source === 'extension') return;
112
118
  if (currentMode !== 'off' && isOffCommand(event?.text)) setMode('off');
113
119
  });
@@ -122,7 +128,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
122
128
  // Persisted session state wins; a fresh session falls back to the
123
129
  // installer/user-configured default (off unless configured).
124
130
  const persisted = resolveMode(entries, '');
125
- currentMode = persisted || readCavemanDefault();
131
+ currentMode = persisted || normalizeMode('caveman', readCavemanDefault()) || DEFAULT_MODE;
126
132
  syncStatus(ctx);
127
133
  }
128
134
 
@@ -131,7 +137,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
131
137
  ctx?.ui?.notify?.(`Caveman loaded: ${currentMode}`, 'info');
132
138
  });
133
139
 
134
- pi.on('session_branch', async (_event, ctx) => {
140
+ onHostEvent(pi, 'session_branch', async (_event, ctx) => {
135
141
  restoreMode(ctx);
136
142
  });
137
143
 
@@ -155,7 +161,6 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
155
161
  const def = INSTRUCTIONS[mode];
156
162
  if (!def) return;
157
163
  const instruction = typeof def === 'function' ? def() : def;
158
- const base = asPromptArray(event.systemPrompt);
159
- return { systemPrompt: [...base, instruction] };
164
+ return injectPromptText(pi, event, instruction);
160
165
  });
161
166
  }
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: caveman
3
+ description: >
4
+ Ultra-compressed communication mode that cuts output tokens while keeping
5
+ technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for
6
+ /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".
7
+ ---
8
+
9
+ Respond terse like smart caveman. All technical substance stay. Only fluff die.
10
+
11
+ ## Persistence
12
+
13
+ Default style for this whole session, every response, until user say "stop caveman" or "normal mode". Keep terse on long sessions no filler drift.
14
+
15
+ Default: **full**. Switch: `/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off`.
16
+
17
+ ## Rules
18
+
19
+ 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"). No tool-call narration, no decorative tables/emoji, no dumping long raw error logs unless asked quote shortest decisive line. Standard well-known tech acronyms OK (DB/API/HTTP); never invent new abbreviations (cfg/impl/req/res/fn) tokenizer split them same as full word: zero token saved, reader still decode. Full word cheaper AND clearer. No causal arrows (→) either own token, save nothing. Technical terms exact. Code blocks unchanged. Errors quoted exact.
20
+
21
+ Never drop not/never/no/only/except flip meaning worse than any token saved. Numbers, units exact.
22
+
23
+ Never ADD word to sound caveman. Compression only style never grow output. No inserted pronoun or copula to fake broken grammar: "when it not" cost one token more than "when not" and say same thing. Keep correct verb form when correct form cost same "sees" one token, "see" one token, so mangle buy nothing and read worse. Same rule as abbreviations and arrows: if caveman phrasing not shorter than plain phrasing, use plain.
24
+
25
+ Clarity register: mix ASD-STE100 Simplified Technical English into caveman, always. One idea per sentence. Sentence short, target 20 words max. Active voice. Present tense where true. One word one meaning: same term for same thing every time, no synonym rotation. Instruction = imperative: "Run X", not "X should be run". Noun cluster 3 words max. Pronoun only with one clear referent, else repeat noun. Caveman cut filler; STE keep what make meaning unambiguous. Conflict between them → clarity win.
26
+
27
+ Tool calls: fire direct. No preamble, plan, or progress note before or between calls. After result: next call direct or final answer never announce next call. Text before call only to clarify, warn security/irreversible, or resolve ambiguity.
28
+
29
+ Follow explicit reply-language instructions from the user or project. Otherwise preserve the user's dominant language. Never switch because of example text or multilingual context elsewhere. Compress the style, not the language. Every emitted line in that language openings, pre-tool status lines, all not just final reply. ALWAYS keep technical terms, code, API names, CLI commands, commit-type keywords (feat/fix/...), and exact error strings verbatim unless user explicitly ask for translation.
30
+
31
+ 'Drop articles' = article languages only. Where small markers carry case/role (particles, postpositions), keep them grammar, not filler; compress politeness/filler instead.
32
+
33
+ Answer directly in this style. Skip "caveman mode on", "me caveman think", "Caveman:" prefix or recap redundant with the reply itself. No normal answer plus caveman duplicate. User ask what mode is → say so plainly.
34
+
35
+ Pattern: `[thing] [action] [reason]. [next step].`
36
+
37
+ Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
38
+ Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
39
+
40
+ ## Intensity
41
+
42
+ | Level | What change |
43
+ |-------|------------|
44
+ | **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight |
45
+ | **full** | Drop articles, fragments OK, short synonyms. Classic caveman. No tool-call narration, no decorative tables/emoji, no long raw error-log dumps unless asked. Standard acronyms OK; no invented abbreviations |
46
+ | **ultra** | Strip conjunctions when cause-then-effect stay unambiguous. One word when one word enough. State each fact once. NO prose abbreviations (cfg/impl/req/res/fn/auth), NO arrows (X → Y) measured zero token saving under tokenizer, cost decode clarity. Code symbols, function names, API names, error strings: never touch |
47
+ | **wenyan-lite** | Semi-classical. Drop filler/hedging but keep grammar structure, classical register |
48
+ | **wenyan-full** | Maximum classical terseness. Fully 文言文. 80-90% character reduction chars, not tokens. Classical sentence patterns, verbs precede objects, subjects often omitted, classical particles (之/乃/為/其) |
49
+ | **wenyan-ultra** | Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse |
50
+
51
+ Example "Why React component re-render?"
52
+ - lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
53
+ - full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
54
+ - ultra: "Inline obj prop, new ref, re-render. `useMemo`."
55
+ - wenyan-lite: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。"
56
+ - wenyan-full: "每繪新生對象參照,故重繪;以 useMemo 包之則免。"
57
+ - wenyan-ultra: "新參照則重繪。useMemo 包之。"
58
+
59
+ Example "Explain database connection pooling."
60
+ - lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead."
61
+ - full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead."
62
+ - ultra: "Pool reuse open DB connections. No per-request handshake."
63
+ - wenyan-full: "池蓄已開之連,不逐請而新開,省握手之費。"
64
+ - wenyan-ultra: "池蓄連,免逐請新開,省握手。"
65
+
66
+ Classical chars = wenyan modes only. Never swap a word to a classical char to shrink at non-wenyan levels.
67
+
68
+ ## Auto-Clarity
69
+
70
+ Drop caveman when:
71
+ - Security warnings
72
+ - Irreversible action confirmations
73
+ - Multi-step sequences where fragment order or omitted conjunctions risk misread
74
+ - Compression itself creates technical ambiguity (e.g., `"migrate table drop column backup first"` order unclear without articles/conjunctions)
75
+ - User asks to clarify or repeats question
76
+
77
+ Resume caveman after clear part done.
78
+
79
+ Example shows FORMAT only write warning in session language, not example's.
80
+
81
+ Example destructive op:
82
+ > **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
83
+ > ```sql
84
+ > DROP TABLE users;
85
+ > ```
86
+ > Caveman resume. Verify backup exist first.
87
+
88
+ ## Boundaries
89
+
90
+ Persisted outside chat: write normal prose code, comments, commits, docs, issue/PR/MR/defect/ticket/bug-report text, memory files, third-party messages (/caveman-compress exempt). "Open a defect" or "file a bug" mean the same as "open issue": body go to other humans, so body normal English. "stop caveman" or "normal mode": revert. Level persist until changed or session end.