@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.
- package/README.md +62 -46
- package/cli/agent-hosts.js +42 -0
- package/cli/agents.js +266 -0
- package/cli/common.js +55 -55
- package/cli/currency.js +8 -12
- package/cli/dashboard.js +76 -49
- package/cli/doctor-fix.js +61 -44
- package/cli/doctor.js +66 -44
- package/cli/hosts.js +118 -0
- package/cli/install.js +187 -143
- package/cli/interactive.js +27 -1
- package/cli/layer.js +24 -0
- package/cli/manifest.js +36 -0
- package/cli/omp-layer.js +51 -0
- package/cli/pi-layer.js +37 -0
- package/cli/pi-wiring.js +92 -0
- package/cli/profile.js +45 -46
- package/cli/reset.js +3 -26
- package/cli/rtk-wiring.js +10 -15
- package/cli/settings.js +1 -1
- package/cli/uninstall.js +135 -44
- package/cli/update.js +24 -30
- package/cli/usage.js +6 -1
- package/dashboard/dist/index.html +16 -16
- package/extensions/ai-addons-updater/index.js +36 -25
- package/extensions/ai-addons-updater/index.ts +36 -24
- package/extensions/caveman-session/index.js +23 -18
- package/extensions/caveman-session/index.ts +23 -18
- package/extensions/caveman-session/rule.md +90 -0
- package/extensions/combo-toggle/index.js +63 -41
- package/extensions/combo-toggle/index.ts +61 -48
- package/extensions/lib/utils.js +56 -13
- package/extensions/lib/utils.ts +55 -13
- package/extensions/rtk-session/index.js +20 -19
- package/extensions/rtk-session/index.ts +18 -24
- package/extensions/shared/carbon.js +5 -8
- package/extensions/shared/carbon.ts +5 -8
- package/extensions/shared/host.js +68 -0
- package/extensions/shared/host.ts +104 -0
- package/extensions/shared/omp-prompt.js +12 -0
- package/extensions/shared/plugin-settings.js +63 -16
- package/extensions/shared/plugin-settings.ts +67 -17
- package/extensions/shared/pricing.js +26 -15
- package/extensions/shared/pricing.ts +28 -16
- package/extensions/shared/rtk-gain.js +13 -24
- package/extensions/shared/rtk-gain.ts +14 -25
- package/extensions/shared/session-state.js +26 -28
- package/extensions/shared/session-state.ts +26 -29
- package/extensions/shared/types.ts +15 -3
- package/extensions/shared/usage-ledger.js +136 -38
- package/extensions/shared/usage-ledger.ts +149 -52
- package/extensions/shared/usage-store.js +14 -12
- package/extensions/shared/usage-store.ts +16 -14
- package/extensions/tersio-commands/index.js +2 -1
- package/extensions/tersio-commands/index.ts +2 -1
- package/package.json +15 -7
- package/tersio.js +6 -6
- package/extensions/shared/mode-reinforcement.js +0 -26
- package/extensions/shared/mode-reinforcement.ts +0 -29
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
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 {
|
|
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
|
|
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
|
|
16
|
-
|
|
17
|
-
const CAVEMAN_LOCAL = path.join(
|
|
18
|
-
const
|
|
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
|
-
//
|
|
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(
|
|
41
|
-
const localVer =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
2
|
-
//
|
|
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
|
|
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
|
|
33
|
-
|
|
34
|
-
const CAVEMAN_LOCAL = path.join(
|
|
35
|
-
|
|
36
|
-
const RELOAD_MSG =
|
|
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
|
-
//
|
|
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(
|
|
88
|
-
const localVer =
|
|
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:
|
|
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
|
-
|
|
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
|
|
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,
|
|
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 = `
|
|
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
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
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
|
-
|
|
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.
|
|
34
|
-
wenyan: `Caveman wenyan active for this session.
|
|
35
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 = `
|
|
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
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
|
-
|
|
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.
|
|
34
|
-
wenyan: `Caveman wenyan active for this session.
|
|
35
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|