@krtclcdy/tersio 2.25.6 → 2.25.8
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 +43 -13
- package/dashboard/dist/index.html +1 -1
- package/dist/cli/banner.js +1 -2
- package/dist/cli/common.js +26 -4
- package/dist/cli/dashboard.js +7 -2
- package/dist/cli/doctor-fix.js +12 -7
- package/dist/cli/doctor.js +64 -15
- package/dist/cli/hosts.js +1 -2
- package/dist/cli/install.js +12 -7
- package/dist/cli/manifest.js +25 -5
- package/dist/cli/profile.js +28 -3
- package/dist/cli/rtk-wiring.js +13 -10
- package/dist/cli/uninstall.js +1 -1
- package/dist/cli/usage.js +5 -0
- package/dist/extensions/caveman/index.js +4 -3
- package/dist/extensions/combo/index.js +21 -7
- package/dist/extensions/commands/index.js +7 -2
- package/dist/extensions/pstack/index.js +5 -2
- package/dist/extensions/rtk/filter.js +45 -4
- package/dist/extensions/rtk/session.js +21 -11
- package/dist/extensions/server.js +44 -29
- package/dist/extensions/shared/agent-stats.js +21 -13
- package/dist/extensions/shared/pricing.js +5 -4
- package/dist/extensions/shared/rtk-eligible.js +80 -0
- package/dist/extensions/shared/rtk-gain.js +3 -7
- package/dist/extensions/shared/sqlite.js +2 -13
- package/dist/extensions/shared/status.js +1 -2
- package/dist/extensions/shared/usage-ledger.js +196 -29
- package/dist/extensions/shared/utils.js +33 -0
- package/dist/extensions/updater/index.js +15 -7
- package/extensions/caveman/index.ts +4 -3
- package/extensions/caveman/rule-lite.md +77 -0
- package/extensions/caveman/rule-megacave-lite.md +59 -0
- package/extensions/caveman/rule-megacave-ultra.md +59 -0
- package/extensions/caveman/rule.md +1 -1
- package/extensions/combo/index.ts +22 -7
- package/extensions/commands/index.ts +7 -2
- package/extensions/pstack/index.ts +5 -2
- package/extensions/pstack/playbooks/authoring-a-skill.md +12 -0
- package/extensions/pstack/playbooks/autonomous-run.md +13 -0
- package/extensions/pstack/playbooks/autopilot-full.md +13 -0
- package/extensions/pstack/playbooks/autopilot-stack.md +16 -0
- package/extensions/pstack/playbooks/babysit.md +23 -0
- package/extensions/pstack/playbooks/bug-fix.md +15 -0
- package/extensions/pstack/playbooks/eval.md +25 -0
- package/extensions/pstack/playbooks/feature.md +21 -0
- package/extensions/pstack/playbooks/hillclimb.md +21 -0
- package/extensions/pstack/playbooks/investigation.md +14 -0
- package/extensions/pstack/playbooks/multi-phase-plan.md +155 -0
- package/extensions/pstack/playbooks/opening-a-pr.md +34 -0
- package/extensions/pstack/playbooks/orchestrate.md +111 -0
- package/extensions/pstack/playbooks/pause-safely.md +10 -0
- package/extensions/pstack/playbooks/perf-issue.md +25 -0
- package/extensions/pstack/playbooks/prototype.md +14 -0
- package/extensions/pstack/playbooks/refactoring.md +16 -0
- package/extensions/pstack/playbooks/runtime-forensics.md +11 -0
- package/extensions/pstack/playbooks/session-pickup.md +11 -0
- package/extensions/pstack/playbooks/shipping.md +17 -0
- package/extensions/pstack/playbooks/trace-forensics.md +14 -0
- package/extensions/pstack/playbooks/visual-parity.md +11 -0
- package/extensions/pstack/playbooks/worktree-cleanup.md +12 -0
- package/extensions/pstack/principles/attack-the-premise.md +22 -0
- package/extensions/pstack/principles/boundary-discipline.md +33 -0
- package/extensions/pstack/principles/build-the-lever.md +22 -0
- package/extensions/pstack/principles/encode-lessons-in-structure.md +10 -0
- package/extensions/pstack/principles/exhaust-the-design-space.md +8 -0
- package/extensions/pstack/principles/experience-first.md +18 -0
- package/extensions/pstack/principles/explain-the-number.md +16 -0
- package/extensions/pstack/principles/fix-root-causes.md +23 -0
- package/extensions/pstack/principles/foundational-thinking.md +20 -0
- package/extensions/pstack/principles/guard-the-context-window.md +11 -0
- package/extensions/pstack/principles/laziness-protocol.md +9 -0
- package/extensions/pstack/principles/make-operations-idempotent.md +23 -0
- package/extensions/pstack/principles/migrate-callers-then-delete-legacy-apis.md +21 -0
- package/extensions/pstack/principles/minimize-reader-load.md +17 -0
- package/extensions/pstack/principles/model-the-domain.md +23 -0
- package/extensions/pstack/principles/never-block-on-the-human.md +14 -0
- package/extensions/pstack/principles/outcome-oriented-execution.md +20 -0
- package/extensions/pstack/principles/prove-it-works.md +21 -0
- package/extensions/pstack/principles/redesign-from-first-principles.md +15 -0
- package/extensions/pstack/principles/separate-before-serializing-shared-state.md +15 -0
- package/extensions/pstack/principles/sequence-verifiable-units.md +16 -0
- package/extensions/pstack/principles/subtract-before-you-add.md +17 -0
- package/extensions/pstack/principles/test-behavior-not-implementation.md +20 -0
- package/extensions/pstack/principles/type-system-discipline.md +17 -0
- package/extensions/pstack/references/bugbot-triage.md +142 -0
- package/extensions/pstack/rule.md +88 -12
- package/extensions/pstack/skills/architect/SKILL.md +78 -0
- package/extensions/pstack/skills/architect/references/design-red-flags.md +57 -0
- package/extensions/pstack/skills/architect/references/rationale-template.md +35 -0
- package/extensions/pstack/skills/architect/references/runner-prompt.md +20 -0
- package/extensions/pstack/skills/arena/SKILL.md +47 -0
- package/extensions/pstack/skills/benchmark-checklist/SKILL.md +38 -0
- package/extensions/pstack/skills/blast-radius/SKILL.md +49 -0
- package/extensions/pstack/skills/bro/SKILL.md +6 -0
- package/extensions/pstack/skills/correct/SKILL.md +31 -0
- package/extensions/pstack/skills/figure-it-out/SKILL.md +51 -0
- package/extensions/pstack/skills/how/SKILL.md +35 -0
- package/extensions/pstack/skills/how/references/explainer-prompt.md +55 -0
- package/extensions/pstack/skills/how/references/explorer-prompt.md +35 -0
- package/extensions/pstack/skills/interrogate/SKILL.md +73 -0
- package/extensions/pstack/skills/interrogate/references/code-quality-review.md +47 -0
- package/extensions/pstack/skills/interrogate/references/lead-judgment.md +58 -0
- package/extensions/pstack/skills/interrogate/references/reviewer-prompt.md +62 -0
- package/extensions/pstack/skills/interrogate/references/rubric.md +77 -0
- package/extensions/pstack/skills/no-comments/SKILL.md +23 -0
- package/extensions/pstack/skills/reflect/SKILL.md +66 -0
- package/extensions/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
- package/extensions/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
- package/extensions/pstack/skills/reflect/references/synthesizer.md +56 -0
- package/extensions/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
- package/extensions/pstack/skills/show-me-your-work/SKILL.md +81 -0
- package/extensions/pstack/skills/swarm/SKILL.md +45 -0
- package/extensions/pstack/skills/tdd/SKILL.md +41 -0
- package/extensions/pstack/skills/technical-writing/SKILL.md +105 -0
- package/extensions/pstack/skills/unslop/SKILL.md +61 -0
- package/extensions/pstack/skills/why/SKILL.md +97 -0
- package/extensions/pstack/skills/why/references/epistemics.md +71 -0
- package/extensions/pstack/skills/why/references/investigator-prompt.md +84 -0
- package/extensions/pstack/skills/why/references/source-playbook.md +17 -0
- package/extensions/pstack/skills/why/references/sources/code-archaeology.md +51 -0
- package/extensions/pstack/skills/why/references/sources/databricks.md +70 -0
- package/extensions/pstack/skills/why/references/sources/datadog.md +97 -0
- package/extensions/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
- package/extensions/pstack/skills/why/references/sources/linear.md +48 -0
- package/extensions/pstack/skills/why/references/sources/notion.md +55 -0
- package/extensions/pstack/skills/why/references/sources/sentry.md +66 -0
- package/extensions/pstack/skills/why/references/sources/slack.md +17 -0
- package/extensions/pstack/skills/why/references/synthesizer-prompt.md +129 -0
- package/extensions/rtk/filter.ts +51 -4
- package/extensions/rtk/session.ts +20 -10
- package/extensions/server.ts +31 -23
- package/extensions/shared/agent-stats.ts +21 -12
- package/extensions/shared/pricing.ts +7 -5
- package/extensions/shared/rtk-eligible.ts +79 -0
- package/extensions/shared/rtk-gain.ts +3 -7
- package/extensions/shared/sqlite.ts +2 -13
- package/extensions/shared/status.ts +1 -2
- package/extensions/shared/usage-ledger.ts +198 -32
- package/extensions/shared/utils.ts +29 -0
- package/extensions/timing/index.ts +1 -2
- package/extensions/updater/index.ts +18 -7
- package/package.json +8 -7
- package/dist/extensions/caveman/rule-megacave.md +0 -65
- package/dist/extensions/caveman/rule-ultra.md +0 -63
- package/dist/extensions/caveman/rule.md +0 -89
- package/dist/extensions/pstack/rule.md +0 -65
- package/dist/probe-catalog.js +0 -48
- package/dist/probe-catalog188.js +0 -44
package/dist/cli/banner.js
CHANGED
|
@@ -81,8 +81,7 @@ function printWelcome() {
|
|
|
81
81
|
'tersio update refresh CLI + add-ons',
|
|
82
82
|
'tersio doctor verify the install',
|
|
83
83
|
];
|
|
84
|
-
// Below 26 rows the
|
|
85
|
-
// update prompt and every option, so the tips take one line there.
|
|
84
|
+
// Below 26 rows the tips scroll off the top and slice the art, so they take one line there.
|
|
86
85
|
if (process.stdout.rows < 26) {
|
|
87
86
|
clackNote(` ${tips.join(' · ')}`);
|
|
88
87
|
return;
|
package/dist/cli/common.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/common.ts — shared foundation: CLI flags, paths, exec, file/config helpers.
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
|
-
import { promises as fs } from 'node:fs';
|
|
3
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
@@ -14,6 +14,10 @@ const OMP_AGENT_DIR = path.join(HOME, '.omp', 'agent');
|
|
|
14
14
|
const OMP_PLUGINS_DIR = path.join(HOME, '.omp', 'plugins');
|
|
15
15
|
const BUN_BIN_DIR = path.join(HOME, '.bun', 'bin');
|
|
16
16
|
const OMP_BIN = IS_WINDOWS ? 'omp.cmd' : 'omp';
|
|
17
|
+
// npm and bun are `.cmd` shims on Windows, and CreateProcess spawns neither a bare name nor a shim,
|
|
18
|
+
// so the spawn name itself carries the extension the way `omp.cmd` does.
|
|
19
|
+
const NPM_BIN = IS_WINDOWS ? 'npm.cmd' : 'npm';
|
|
20
|
+
const BUN_BIN = IS_WINDOWS ? 'bun.cmd' : 'bun';
|
|
17
21
|
const PACKAGE_NAME = '@krtclcdy/tersio';
|
|
18
22
|
const PACKAGE_BIN = 'tersio';
|
|
19
23
|
const { version: PACKAGE_VERSION } = createRequire(import.meta.url)(path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), 'package.json'));
|
|
@@ -112,8 +116,26 @@ function debug(...a) {
|
|
|
112
116
|
console.log(' [debug]', ...a);
|
|
113
117
|
}
|
|
114
118
|
const execFileP = promisify(execFile);
|
|
119
|
+
/** Windows has no real `omp`, `npm`, or `pgrep`: `.cmd` shims throw EINVAL when spawned, so a bare name wraps through cmd.exe only when a `<name>.cmd` really sits on PATH. */
|
|
120
|
+
export function shimCommand(cmd, args) {
|
|
121
|
+
if (!IS_WINDOWS)
|
|
122
|
+
return { cmd, args };
|
|
123
|
+
if (/\.(?:cmd|bat)$/i.test(cmd)) {
|
|
124
|
+
return { cmd: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', cmd, ...args] };
|
|
125
|
+
}
|
|
126
|
+
if (hasCmdShim(cmd)) {
|
|
127
|
+
return { cmd: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', cmd, ...args] };
|
|
128
|
+
}
|
|
129
|
+
return { cmd, args };
|
|
130
|
+
}
|
|
131
|
+
function hasCmdShim(cmd) {
|
|
132
|
+
if (cmd.includes('/') || cmd.includes('\\'))
|
|
133
|
+
return false;
|
|
134
|
+
return (process.env.PATH || '').split(path.delimiter).some((dir) => dir && existsSync(path.join(dir, `${cmd}.cmd`)));
|
|
135
|
+
}
|
|
115
136
|
async function execP(cmd, args, opts = {}) {
|
|
116
|
-
|
|
137
|
+
const shim = shimCommand(cmd, args);
|
|
138
|
+
return execFileP(shim.cmd, shim.args, {
|
|
117
139
|
timeout: opts.timeout || 120000,
|
|
118
140
|
encoding: 'utf8',
|
|
119
141
|
...opts,
|
|
@@ -131,7 +153,7 @@ async function writeIfChanged(dest, content, options = {}) {
|
|
|
131
153
|
return true;
|
|
132
154
|
}
|
|
133
155
|
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
134
|
-
if (existing !== null) {
|
|
156
|
+
if (existing !== null && options.backup === true) {
|
|
135
157
|
await fs.copyFile(dest, `${dest}.bak`);
|
|
136
158
|
debug(`${path.basename(dest)} → ${path.basename(dest)}.bak`);
|
|
137
159
|
}
|
|
@@ -316,4 +338,4 @@ function relTime(ageMs) {
|
|
|
316
338
|
const weeks = Math.floor(days / 7);
|
|
317
339
|
return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
|
|
318
340
|
}
|
|
319
|
-
export { IS_WINDOWS, RTK_BINARY_NAME, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, BUN_BIN_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_BIN, PACKAGE_VERSION, CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS, PSTACK_DEFAULTS, RTK_DEFAULTS, DIAG_SCHEDULES, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, settingArg, unknownCommand, install, reinstall, update, showVersion, showHelp, applyUpdate, args, dryRun, yes, verbose, doctor, fix, uninstall, usage, dashboard, reset, settings, allowUnverified, dashboardPort, dashboardOpen, dashboardExport, currency, currencyGiven, removePonytail, keepPonytail, removeRtk, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, pstackDefaultFlag, rtkDefaultFlag, diagScheduleFlag, profileFlagsGiven, subagentMarkerFlags, subagentMarkersGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, writeConfigLines, ensureExtensionInConfig, removeExtensionFromConfig, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, relTime, bundledPonytailSpec, ponytailOverride, };
|
|
341
|
+
export { IS_WINDOWS, RTK_BINARY_NAME, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, BUN_BIN_DIR, OMP_BIN, NPM_BIN, BUN_BIN, PACKAGE_NAME, PACKAGE_BIN, PACKAGE_VERSION, CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS, PSTACK_DEFAULTS, RTK_DEFAULTS, DIAG_SCHEDULES, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, settingArg, unknownCommand, install, reinstall, update, showVersion, showHelp, applyUpdate, args, dryRun, yes, verbose, doctor, fix, uninstall, usage, dashboard, reset, settings, allowUnverified, dashboardPort, dashboardOpen, dashboardExport, currency, currencyGiven, removePonytail, keepPonytail, removeRtk, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, pstackDefaultFlag, rtkDefaultFlag, diagScheduleFlag, profileFlagsGiven, subagentMarkerFlags, subagentMarkersGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, writeConfigLines, ensureExtensionInConfig, removeExtensionFromConfig, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, relTime, bundledPonytailSpec, ponytailOverride, };
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -457,8 +457,13 @@ function readBody(req) {
|
|
|
457
457
|
});
|
|
458
458
|
}
|
|
459
459
|
function openBrowser(url) {
|
|
460
|
-
|
|
461
|
-
|
|
460
|
+
// `start` is a cmd builtin, not an executable: spawning it by name fails with ENOENT.
|
|
461
|
+
const [cmd, args] = process.platform === 'darwin'
|
|
462
|
+
? ['open', [url]]
|
|
463
|
+
: process.platform === 'win32'
|
|
464
|
+
? [process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', 'start', '', url]]
|
|
465
|
+
: ['xdg-open', [url]];
|
|
466
|
+
spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
|
|
462
467
|
}
|
|
463
468
|
// Replacers throughout: session data holds `$'` (shell quoting) and `</script>`, which String.replace would mangle.
|
|
464
469
|
function escapeInline(json) {
|
package/dist/cli/doctor-fix.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, promises as fs } from 'node:fs';
|
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { BUN_BIN_DIR, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, removeExtensionFromConfig, dryRun, verbose, debug, bundledPonytailSpec, execP, ponytailOverride, readPluginsPackage, } from './common.js';
|
|
5
|
+
import { BUN_BIN_DIR, HOME, NPM_BIN, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, removeExtensionFromConfig, dryRun, verbose, debug, bundledPonytailSpec, execP, ponytailOverride, readPluginsPackage, shimCommand, } from './common.js';
|
|
6
6
|
import { execNetwork, sayTagged } from './interactive.js';
|
|
7
7
|
import { wireRtkOmp, wireRtkOpencode, wireRtkPi, ensureRtkInConfig } from './rtk-wiring.js';
|
|
8
8
|
import { RTK_RELEASE_API, fetchJson, findFile, httpsGet, httpsDownload, parseChecksum, readTextIfExists, resolveHostBinary, rtkPlatformSpec, sha256File, } from '../extensions/shared/utils.js';
|
|
@@ -14,18 +14,20 @@ export function serviceOrphanPids() {
|
|
|
14
14
|
const bin = resolveHostBinary('opencode', path.join(HOME, '.opencode', 'bin'));
|
|
15
15
|
if (!bin)
|
|
16
16
|
return [];
|
|
17
|
+
const shim = shimCommand(bin, ['service', 'status']);
|
|
17
18
|
let status = '';
|
|
18
19
|
try {
|
|
19
|
-
status = spawnSync(
|
|
20
|
+
status = spawnSync(shim.cmd, shim.args, { encoding: 'utf8' }).stdout.trim().toLowerCase();
|
|
20
21
|
}
|
|
21
22
|
catch {
|
|
22
23
|
return [];
|
|
23
24
|
}
|
|
24
25
|
if (serviceRunning(status))
|
|
25
26
|
return [];
|
|
27
|
+
const grepShim = shimCommand('pgrep', ['-f', 'opencode serve --service']);
|
|
26
28
|
let out = '';
|
|
27
29
|
try {
|
|
28
|
-
out = spawnSync(
|
|
30
|
+
out = spawnSync(grepShim.cmd, grepShim.args, { encoding: 'utf8' }).stdout ?? '';
|
|
29
31
|
}
|
|
30
32
|
catch {
|
|
31
33
|
return [];
|
|
@@ -38,9 +40,10 @@ export function serviceState() {
|
|
|
38
40
|
const bin = resolveHostBinary('opencode', path.join(HOME, '.opencode', 'bin'));
|
|
39
41
|
if (!bin)
|
|
40
42
|
return null;
|
|
43
|
+
const shim = shimCommand(bin, ['service', 'status']);
|
|
41
44
|
let status = '';
|
|
42
45
|
try {
|
|
43
|
-
status = spawnSync(
|
|
46
|
+
status = spawnSync(shim.cmd, shim.args, { encoding: 'utf8' }).stdout.trim().toLowerCase();
|
|
44
47
|
}
|
|
45
48
|
catch {
|
|
46
49
|
return null;
|
|
@@ -49,7 +52,8 @@ export function serviceState() {
|
|
|
49
52
|
return null;
|
|
50
53
|
let live = false;
|
|
51
54
|
try {
|
|
52
|
-
const
|
|
55
|
+
const grepShim = shimCommand('pgrep', ['-f', 'opencode serve --service']);
|
|
56
|
+
const out = spawnSync(grepShim.cmd, grepShim.args, { encoding: 'utf8' }).stdout ?? '';
|
|
53
57
|
live = out.split(/[\s,]+/).some((line) => /^\d+$/.test(line.trim()));
|
|
54
58
|
}
|
|
55
59
|
catch {
|
|
@@ -365,7 +369,7 @@ async function fixPonytail(pluginsDir) {
|
|
|
365
369
|
return;
|
|
366
370
|
}
|
|
367
371
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
368
|
-
await execNetwork('Restoring bundled Ponytail',
|
|
372
|
+
await execNetwork('Restoring bundled Ponytail', NPM_BIN, ['install', '--no-audit', '--no-fund'], { cwd: pluginsDir, timeout: 180000 }).catch((e) => {
|
|
369
373
|
throw new Error(`bundled ponytail restore failed: ${e.message}`);
|
|
370
374
|
});
|
|
371
375
|
}
|
|
@@ -405,7 +409,8 @@ async function fixOpencodeService() {
|
|
|
405
409
|
sayTagged(' [warn] opencode not found on PATH; start it with `opencode service start`');
|
|
406
410
|
return;
|
|
407
411
|
}
|
|
408
|
-
|
|
412
|
+
const shim = shimCommand(bin, ['service', 'start']);
|
|
413
|
+
await execNetwork('Starting the OpenCode service', shim.cmd, shim.args, { timeout: 60000 }).catch((e) => {
|
|
409
414
|
throw new Error(`service start failed: ${shortError(e)}`);
|
|
410
415
|
});
|
|
411
416
|
}
|
package/dist/cli/doctor.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, statSync, promises as fs } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, PACKAGE_VERSION, args, dryRun, fix, yes, BUN_BIN_DIR, OMP_BIN, execP, parseJsonObject, relTime, } from './common.js';
|
|
5
|
+
import { HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, PACKAGE_VERSION, args, dryRun, fix, yes, BUN_BIN_DIR, OMP_BIN, execP, parseJsonObject, relTime, shimCommand, } from './common.js';
|
|
6
6
|
import { detectHosts, hostExtensionsDir, ompPackageDir, piPonytailPackageDir } from './hosts.js';
|
|
7
|
+
import { GENERATED_TREE_FILES } from './manifest.js';
|
|
7
8
|
import { askInteractiveChoice, askInteractiveConfirm, runInteractivePhase } from './interactive.js';
|
|
8
9
|
import { usageDbPath } from '../extensions/shared/usage-store.js';
|
|
9
10
|
import { pricesCachePath } from '../extensions/shared/pricing.js';
|
|
10
|
-
import { findHoistedPackage, fetchJson, piAgentDir, readTextIfExists, resolveHostBinary, resolveRtkBinary, ruleBodyProblem } from '../extensions/shared/utils.js';
|
|
11
|
+
import { findHoistedPackage, fetchJson, piAgentDir, readTextIfExists, resolveHostBinary, resolveRtkBinary, rtkPlatformSpec, ruleBodyProblem, tersioHome } from '../extensions/shared/utils.js';
|
|
11
12
|
import { sqliteScript, statsDbPath } from '../extensions/shared/agent-stats.js';
|
|
12
13
|
import { OMP_SUBAGENT_MARKER } from '../extensions/shared/session-state.js';
|
|
13
|
-
import { PI_PONYTAIL_FILES, PONYTAIL_BUNDLE_FILES, TREE_FILES, isObsoleteTreeFile, treeFileText, treeFilesFor } from './manifest.js';
|
|
14
|
+
import { PI_PONYTAIL_FILES, PONYTAIL_BUNDLE_FILES, TREE_FILES, TREE_PSTACK_FILES, isObsoleteTreeFile, treeFileText, treeFilesFor } from './manifest.js';
|
|
14
15
|
import { serviceRunning, serviceState } from './doctor-fix.js';
|
|
15
16
|
/** Bytes at the scale a transcript gap reads in: `5.6M`, not `5571992`. */
|
|
16
17
|
function shortBytes(bytes) {
|
|
@@ -42,8 +43,7 @@ async function runDoctor(recheck = false) {
|
|
|
42
43
|
const updaterIndex = path.join(tersioPluginDir, 'extensions', 'updater', 'index.ts');
|
|
43
44
|
// Probes start concurrently and report in fixed order as data settles; each resolves instead of rejecting.
|
|
44
45
|
const hosts = detectHosts();
|
|
45
|
-
//
|
|
46
|
-
// their own copy are compared below. The package root, not the hook's directory, is what the paths hang off.
|
|
46
|
+
// Only hosts with their own copy are compared: omp's copy is the reference. Paths hang off the package root.
|
|
47
47
|
const ponytailLoads = {
|
|
48
48
|
pi: { dir: piPonytailPackageDir(piAgentDir()), files: PI_PONYTAIL_FILES },
|
|
49
49
|
opencode: { dir: path.join(hostExtensionsDir('opencode'), 'ponytail-bundle'), files: PONYTAIL_BUNDLE_FILES },
|
|
@@ -70,9 +70,7 @@ async function runDoctor(recheck = false) {
|
|
|
70
70
|
const treeOrphan = [];
|
|
71
71
|
// The tree a host loads: omp reads the plugin package, every other host its own directory.
|
|
72
72
|
const treeBaseFor = (host) => host.id === 'omp' ? path.join(tersioPluginDir, 'extensions') : hostExtensionsDir(host.id);
|
|
73
|
-
// A tree holding
|
|
74
|
-
// installed. pi auto-discovers every index.ts it finds, so a half-written tree needs reporting,
|
|
75
|
-
// and `detectHosts` reads one as absent: it wants every file or a declared package.
|
|
73
|
+
// A tree holding any file of this build is compared even when the host reads as not installed: pi auto-discovers every index.ts it finds, so a half-written tree needs reporting.
|
|
76
74
|
const treeHoldsOwnFiles = (host) => treeFilesFor(host.id === 'omp' ? 'opencode' : host.id).some((rel) => existsSync(path.join(treeBaseFor(host), ...rel.split('/'))));
|
|
77
75
|
for (const host of hosts.filter((h) => h.installed || treeHoldsOwnFiles(h))) {
|
|
78
76
|
const treeId = host.id === 'omp' ? 'opencode' : host.id;
|
|
@@ -103,6 +101,22 @@ async function runDoctor(recheck = false) {
|
|
|
103
101
|
}
|
|
104
102
|
}
|
|
105
103
|
}
|
|
104
|
+
// A host reading as installed whose tree is short of this build's declared files: the pair loop above cannot see a file that is absent on both sides.
|
|
105
|
+
const unwrittenTreeFiles = async () => {
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const host of hosts.filter((h) => h.installed)) {
|
|
108
|
+
const treeId = host.id === 'omp' ? 'opencode' : host.id;
|
|
109
|
+
const base = treeBaseFor(host);
|
|
110
|
+
for (const rel of treeFilesFor(treeId)) {
|
|
111
|
+
if (existsSync(path.join(base, ...rel.split('/'))))
|
|
112
|
+
continue;
|
|
113
|
+
if (host.id === 'omp' && GENERATED_TREE_FILES[rel] !== undefined)
|
|
114
|
+
continue;
|
|
115
|
+
out.push(`${host.id}/${rel}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
};
|
|
106
120
|
const probes = {
|
|
107
121
|
ompPkgText: readTextIfExists(path.join(ompPackageDir(), 'package.json')),
|
|
108
122
|
configText: readTextIfExists(path.join(OMP_AGENT_DIR, 'config.yml')),
|
|
@@ -110,9 +124,13 @@ async function runDoctor(recheck = false) {
|
|
|
110
124
|
rtkBinText: rtkBin ? readTextIfExists(rtkBin) : Promise.resolve(null),
|
|
111
125
|
cavemanIndexText: readTextIfExists(cavemanIndex),
|
|
112
126
|
cavemanRuleText: readTextIfExists(cavemanRule('rule.md')),
|
|
127
|
+
cavemanLiteText: readTextIfExists(cavemanRule('rule-lite.md')),
|
|
128
|
+
cavemanMegacaveLiteText: readTextIfExists(cavemanRule('rule-megacave-lite.md')),
|
|
129
|
+
cavemanMegacaveUltraText: readTextIfExists(cavemanRule('rule-megacave-ultra.md')),
|
|
113
130
|
cavemanUltraText: readTextIfExists(cavemanRule('rule-ultra.md')),
|
|
114
131
|
cavemanMegacaveText: readTextIfExists(cavemanRule('rule-megacave.md')),
|
|
115
132
|
pstackRuleText: readTextIfExists(pstackRule),
|
|
133
|
+
pstackTreeFiles: Promise.all(TREE_PSTACK_FILES.map((rel) => readTextIfExists(path.join(tersioPluginDir, "extensions", ...rel.split("/"))))),
|
|
116
134
|
rtkIndexText: readTextIfExists(rtkIndex),
|
|
117
135
|
updaterIndexText: readTextIfExists(updaterIndex),
|
|
118
136
|
rtkMtime: rtkBin ? fs.stat(rtkBin).catch(() => null) : Promise.resolve(null),
|
|
@@ -128,7 +146,7 @@ async function runDoctor(recheck = false) {
|
|
|
128
146
|
const opencodeServiceProbe = opencodeBin
|
|
129
147
|
? Promise.resolve(serviceState())
|
|
130
148
|
: Promise.resolve(null);
|
|
131
|
-
const [[ompPkgText, configText], [cavemanIndexText, rtkIndexText, updaterIndexText, ponytailPkgText], [cavemanRuleText, cavemanUltraText, cavemanMegacaveText, pstackRuleText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, ponytailMtime, pricesMtime], [opencodeService]] = await runInteractivePhase('Checking installation', () => Promise.all([
|
|
149
|
+
const [[ompPkgText, configText], [cavemanIndexText, rtkIndexText, updaterIndexText, ponytailPkgText], [cavemanRuleText, cavemanLiteText, cavemanMegacaveLiteText, cavemanMegacaveUltraText, cavemanUltraText, cavemanMegacaveText, pstackRuleText, pstackTreeFiles, ruleMtime, rtkBinText, rtkMtime, rtkVersion, ponytailMtime, pricesMtime], [opencodeService]] = await runInteractivePhase('Checking installation', () => Promise.all([
|
|
132
150
|
Promise.all([
|
|
133
151
|
probes.ompPkgText,
|
|
134
152
|
probes.configText,
|
|
@@ -141,9 +159,13 @@ async function runDoctor(recheck = false) {
|
|
|
141
159
|
]),
|
|
142
160
|
Promise.all([
|
|
143
161
|
probes.cavemanRuleText,
|
|
162
|
+
probes.cavemanLiteText,
|
|
163
|
+
probes.cavemanMegacaveLiteText,
|
|
164
|
+
probes.cavemanMegacaveUltraText,
|
|
144
165
|
probes.cavemanUltraText,
|
|
145
166
|
probes.cavemanMegacaveText,
|
|
146
167
|
probes.pstackRuleText,
|
|
168
|
+
probes.pstackTreeFiles,
|
|
147
169
|
probes.ruleMtime,
|
|
148
170
|
probes.rtkBinText,
|
|
149
171
|
probes.rtkMtime,
|
|
@@ -179,6 +201,18 @@ async function runDoctor(recheck = false) {
|
|
|
179
201
|
function unused(label, detail) {
|
|
180
202
|
console.log(` — ${label}: not installed ${detail}`);
|
|
181
203
|
}
|
|
204
|
+
// A Windows failure names its cause here: several rows below silently degrade when the
|
|
205
|
+
// platform has no rtk build, and a wrong home would move every one of them.
|
|
206
|
+
section('Platform');
|
|
207
|
+
const rtkSpec = rtkPlatformSpec();
|
|
208
|
+
check('Platform', rtkSpec !== null, `${process.platform}/${process.arch}${rtkSpec ? ` · node ${process.version} · rtk ${rtkSpec.triple}` : ''}`);
|
|
209
|
+
if (rtkSpec === null) {
|
|
210
|
+
// Windows on ARM today: rtk publishes no build, so the RTK rows read as not installed.
|
|
211
|
+
console.log(` rtk publishes no build for ${process.platform}/${process.arch}; every RTK row below reads as missing`);
|
|
212
|
+
}
|
|
213
|
+
// Windows resolves data through the profile, so name the resolved home and data dir.
|
|
214
|
+
check('Home', HOME !== '', HOME);
|
|
215
|
+
check('Data', true, path.join(tersioHome(), 'usage.db'));
|
|
182
216
|
section('Hosts');
|
|
183
217
|
for (const host of hosts) {
|
|
184
218
|
// pi can hold a working tree while reading as not installed; use the tree as ground truth.
|
|
@@ -201,8 +235,7 @@ async function runDoctor(recheck = false) {
|
|
|
201
235
|
const kind = host.via === 'tree' ? 'extensions' : 'package';
|
|
202
236
|
check(host.label, true, `${kind} ${PACKAGE_NAME}${host.version ? ` ${host.version}` : ''}`);
|
|
203
237
|
}
|
|
204
|
-
// A healthy service adds no row: the host row already names the install
|
|
205
|
-
// this row earns its place only when the manager's report and the live process disagree.
|
|
238
|
+
// A healthy service adds no row: the host row already names the install, and this row only shows when the manager's report and the live process disagree.
|
|
206
239
|
if (opencodeService) {
|
|
207
240
|
const running = serviceRunning(opencodeService.status);
|
|
208
241
|
if (!running && opencodeService.live) {
|
|
@@ -236,7 +269,7 @@ async function runDoctor(recheck = false) {
|
|
|
236
269
|
warnLine('omp stats index', indexLag);
|
|
237
270
|
section('Add-ons');
|
|
238
271
|
const ruleAge = ruleMtime ? `(updated ${relTime(Date.now() - ruleMtime.mtimeMs)} · ${absDate(ruleMtime.mtimeMs)})` : '';
|
|
239
|
-
const cavemanRules = [['rule.md', cavemanRuleText], ['rule-ultra.md', cavemanUltraText], ['rule-megacave.md', cavemanMegacaveText]];
|
|
272
|
+
const cavemanRules = [['rule.md', cavemanRuleText], ['rule-lite.md', cavemanLiteText], ['rule-megacave-lite.md', cavemanMegacaveLiteText], ['rule-megacave-ultra.md', cavemanMegacaveUltraText], ['rule-ultra.md', cavemanUltraText], ['rule-megacave.md', cavemanMegacaveText]];
|
|
240
273
|
const missingRules = cavemanRules.filter(([, text]) => text === null).map(([name]) => name);
|
|
241
274
|
check('Caveman rule', missingRules.length === 0, missingRules.length ? `missing: ${missingRules.join(', ')}` : ruleAge);
|
|
242
275
|
for (const [name, text] of cavemanRules) {
|
|
@@ -253,6 +286,16 @@ async function runDoctor(recheck = false) {
|
|
|
253
286
|
if (problem)
|
|
254
287
|
warnLine('pstack rule.md', `unusable body (${problem}) — run: tersio install`);
|
|
255
288
|
}
|
|
289
|
+
// The index routes to files, so a tree with the rule but no leaves serves a mode that cannot apply.
|
|
290
|
+
const pstackOnDisk = {};
|
|
291
|
+
TREE_PSTACK_FILES.forEach((rel, i) => {
|
|
292
|
+
if (pstackTreeFiles[i] !== null)
|
|
293
|
+
pstackOnDisk[rel] = true;
|
|
294
|
+
});
|
|
295
|
+
const pstackMissing = TREE_PSTACK_FILES.filter((rel) => !pstackOnDisk[rel]).map((rel) => rel.replace(/^pstack\//, ''));
|
|
296
|
+
if (pstackMissing.length > 0) {
|
|
297
|
+
warnLine('pstack tree', `missing ${pstackMissing.length} file${pstackMissing.length === 1 ? '' : 's'}: ${pstackMissing.slice(0, 6).join(', ')}${pstackMissing.length > 6 ? ` +${pstackMissing.length - 6} more` : ''} — run: tersio install`);
|
|
298
|
+
}
|
|
256
299
|
// A verbatim sentence read out of omp's own prompt, so a re-worded release stops Caveman reaching subagents.
|
|
257
300
|
const subagentGap = subagentMarkerStatus(ompBinary());
|
|
258
301
|
if (subagentGap)
|
|
@@ -270,6 +313,11 @@ async function runDoctor(recheck = false) {
|
|
|
270
313
|
warnLine('Per-host add-on files', `orphaned in ${treeOrphan.length} file${treeOrphan.length === 1 ? '' : 's'}: ${treeOrphan.join(', ')}` +
|
|
271
314
|
` — run: tersio doctor --fix extensions`);
|
|
272
315
|
}
|
|
316
|
+
// A count of pairs compared cannot show a file that is absent from disk and absent from the declaration, so assert every declared file landed.
|
|
317
|
+
const unwritten = await unwrittenTreeFiles();
|
|
318
|
+
if (unwritten.length > 0) {
|
|
319
|
+
check('Add-on tree content', false, `${unwritten.length} declared file${unwritten.length === 1 ? '' : 's'} not written for a host reading as installed: ${unwritten.slice(0, 6).join(', ')}${unwritten.length > 6 ? ` +${unwritten.length - 6} more` : ''} — run: tersio doctor --fix extensions`);
|
|
320
|
+
}
|
|
273
321
|
const rtkAge = rtkMtime ? `(updated ${relTime(Date.now() - rtkMtime.mtimeMs)} · ${absDate(rtkMtime.mtimeMs)})` : '';
|
|
274
322
|
check('RTK binary', rtkBin !== null, rtkBinText === null ? 'not found in PATH' : [rtkVersion, rtkAge].filter(Boolean).join(' '));
|
|
275
323
|
// rtk_run needs a host exec API, which doctor cannot assume.
|
|
@@ -336,8 +384,8 @@ function ompBinary() {
|
|
|
336
384
|
const managed = path.join(BUN_BIN_DIR, OMP_BIN);
|
|
337
385
|
return existsSync(managed) ? managed : null;
|
|
338
386
|
}
|
|
339
|
-
// The rule bodies a per-host tree carries. rule.md
|
|
340
|
-
const CAVEMAN_BODIES = ['rule.md', 'rule-ultra.md', 'rule-megacave.md'];
|
|
387
|
+
// The rule bodies a per-host tree carries. rule.md and rule-lite.md are bundled forks; the others track upstream.
|
|
388
|
+
const CAVEMAN_BODIES = ['rule.md', 'rule-lite.md', 'rule-megacave-lite.md', 'rule-megacave-ultra.md', 'rule-ultra.md', 'rule-megacave.md'];
|
|
341
389
|
/** `"installed < latest"` when npm answers, else null: silence means "no row", not "stale". */
|
|
342
390
|
async function ponytailLatestGap(installedPkgText) {
|
|
343
391
|
const local = parseJsonObject(installedPkgText)?.version;
|
|
@@ -384,7 +432,8 @@ function subagentMarkerStatus(ompBin) {
|
|
|
384
432
|
return null;
|
|
385
433
|
let haystack;
|
|
386
434
|
try {
|
|
387
|
-
|
|
435
|
+
const shim = shimCommand(ompBin, ['--version']);
|
|
436
|
+
haystack = execFileSync(shim.cmd, shim.args, { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
388
437
|
+ readFileSync(ompBin, 'utf8');
|
|
389
438
|
}
|
|
390
439
|
catch {
|
package/dist/cli/hosts.js
CHANGED
|
@@ -86,8 +86,7 @@ function detect(agentDir = piAgentDir()) {
|
|
|
86
86
|
return HOSTS.map((host) => {
|
|
87
87
|
const pkgDir = host.id === 'omp' ? ompPackageDir() : host.id === 'pi' ? piPackageDir(agentDir) : null;
|
|
88
88
|
const tree = hostExtensionsDir(host.id, agentDir);
|
|
89
|
-
// A tree wins:
|
|
90
|
-
// OpenCode keeps no package to fall back to, so a present tree is the install; its drifted files get the per-host warning instead.
|
|
89
|
+
// A tree wins: the installer writes it and it carries no package.json, so its version is ours. OpenCode keeps no package to fall back to, so a present tree is the install and drift gets the per-host warning.
|
|
91
90
|
const via = missingTreeFiles(host.id, agentDir).length === 0 || (host.id === 'opencode' && existsSync(tree))
|
|
92
91
|
? 'tree'
|
|
93
92
|
: pkgDir && existsSync(pkgDir) ? 'package' : null;
|
package/dist/cli/install.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, promises as fs } from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { BUN_BIN_DIR, COMBO_PRESET_MODES, HOME, IS_WINDOWS, OMP_AGENT_DIR, OMP_PLUGINS_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, args, allowUnverified, applyUpdate, bundledPonytailSpec, cavemanDefaultFlag, comboDefaultFlag, command, dryRun, ponytailDefaultFlag, pstackDefaultFlag, profileFlagsGiven, rtkDefaultFlag, verbose, yes, dashboardExport, dashboardPort, currency, debug, ensurePonytailConfigValue, execP, parseJsonObject, readPluginsPackage, writeIfChanged, } from './common.js';
|
|
6
|
+
import { BUN_BIN_DIR, BUN_BIN, COMBO_PRESET_MODES, HOME, IS_WINDOWS, NPM_BIN, OMP_AGENT_DIR, OMP_PLUGINS_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, args, allowUnverified, applyUpdate, bundledPonytailSpec, cavemanDefaultFlag, comboDefaultFlag, command, dryRun, ponytailDefaultFlag, pstackDefaultFlag, profileFlagsGiven, rtkDefaultFlag, verbose, yes, dashboardExport, dashboardPort, currency, debug, ensurePonytailConfigValue, execP, parseJsonObject, readPluginsPackage, writeIfChanged, } from './common.js';
|
|
7
7
|
import { askInteractiveChoice, askInteractiveConfirm, closeRL, execNetwork, tty, withInteractiveSpinner, withInteractiveTask, sayTagged } from './interactive.js';
|
|
8
8
|
import { printWelcome } from './banner.js';
|
|
9
9
|
import { checkForUpdate, runLatestUpdate } from './update.js';
|
|
@@ -80,11 +80,11 @@ async function stepPonytail(pluginsDir, options) {
|
|
|
80
80
|
else {
|
|
81
81
|
// Reinstall only when the self-plugin step did not materialize it.
|
|
82
82
|
try {
|
|
83
|
-
await execNetwork('Installing bundled Ponytail',
|
|
83
|
+
await execNetwork('Installing bundled Ponytail', NPM_BIN, ['install', '--no-audit', '--no-fund'], { cwd: pluginsDir, timeout: 180000 });
|
|
84
84
|
}
|
|
85
85
|
catch {
|
|
86
86
|
try {
|
|
87
|
-
await execNetwork('Installing bundled Ponytail',
|
|
87
|
+
await execNetwork('Installing bundled Ponytail', BUN_BIN, ['install'], { cwd: pluginsDir, timeout: 180000 });
|
|
88
88
|
}
|
|
89
89
|
catch (e) {
|
|
90
90
|
sayTagged(` [fail] Could not install bundled ponytail: ${e.message}`);
|
|
@@ -136,11 +136,11 @@ async function stepSelfPlugin(pluginsDir, options) {
|
|
|
136
136
|
await fs.mkdir(pluginsDir, { recursive: true });
|
|
137
137
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
138
138
|
try {
|
|
139
|
-
await execNetwork('Installing Tersio plugin dependencies',
|
|
139
|
+
await execNetwork('Installing Tersio plugin dependencies', NPM_BIN, ['install', '--no-audit', '--no-fund'], { cwd: pluginsDir, timeout: 180000 });
|
|
140
140
|
}
|
|
141
141
|
catch {
|
|
142
142
|
try {
|
|
143
|
-
await execNetwork('Installing Tersio plugin dependencies',
|
|
143
|
+
await execNetwork('Installing Tersio plugin dependencies', BUN_BIN, ['install'], { cwd: pluginsDir, timeout: 180000 });
|
|
144
144
|
}
|
|
145
145
|
catch (e) {
|
|
146
146
|
sayTagged(` [fail] Could not install ${PACKAGE_NAME} into plugins dir: ${e.message}`);
|
|
@@ -411,9 +411,12 @@ async function stepRtkFilter(extDir, options) {
|
|
|
411
411
|
console.log(' RTK filter — install built-in tool output filtering');
|
|
412
412
|
await copySources(extDir, filesUnder('rtk'), 'rtk/filter.ts', options);
|
|
413
413
|
}
|
|
414
|
-
// rule.md
|
|
414
|
+
// rule.md and rule-lite.md are our forks and always ship bundled; the other two track upstream and keep a usable body when the fetch fails.
|
|
415
415
|
const CAVEMAN_RULES = [
|
|
416
416
|
['rule.md', null],
|
|
417
|
+
['rule-lite.md', null],
|
|
418
|
+
['rule-megacave-lite.md', null],
|
|
419
|
+
['rule-megacave-ultra.md', null],
|
|
417
420
|
['rule-ultra.md', CAVEMAN_REMOTE_ULTRA],
|
|
418
421
|
['rule-megacave.md', CAVEMAN_REMOTE_MEGACAVE],
|
|
419
422
|
];
|
|
@@ -545,7 +548,8 @@ async function stepOpencode(options) {
|
|
|
545
548
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
546
549
|
await fs.rm(path.join(HOME, '.config', 'opencode', 'plugins', 'rtk.ts'), { force: true }).catch(() => { });
|
|
547
550
|
}
|
|
548
|
-
|
|
551
|
+
// The user's own config, so keep the previous copy beside it.
|
|
552
|
+
await writeIfChanged(configPath, `${JSON.stringify(config, null, 2)}\n`, { ...options, backup: true });
|
|
549
553
|
}
|
|
550
554
|
async function resolveProfile(opts = {}) {
|
|
551
555
|
// Seed from the stored defaults so flag-less runs keep them.
|
|
@@ -1007,6 +1011,7 @@ async function runInstall(forcedHost) {
|
|
|
1007
1011
|
await capture('shared', () => stepSharedSessionState(extDir, installOptions));
|
|
1008
1012
|
await capture('rtk session', () => stepRtkSession(extDir, installOptions));
|
|
1009
1013
|
await capture('rtk filter', () => stepRtkFilter(extDir, installOptions));
|
|
1014
|
+
await capture('timing', () => stepTiming(extDir, installOptions));
|
|
1010
1015
|
await capture('caveman', () => stepCaveman(extDir, installOptions));
|
|
1011
1016
|
await capture('combo', () => stepCombo(extDir, installOptions));
|
|
1012
1017
|
await capture('pstack', () => stepPstack(extDir, installOptions));
|
package/dist/cli/manifest.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
// Single list of install writes, shared by installer, doctor, and tests.
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { findPackageRoot, readTextIfExists } from '../extensions/shared/utils.js';
|
|
6
6
|
import { writeIfChanged } from './common.js';
|
|
7
7
|
export const EXT_DIR = path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), 'extensions');
|
|
8
|
+
/** Every pstack leaf, playbook, and skill the index routes to. */
|
|
9
|
+
function pstackTreeFiles() {
|
|
10
|
+
const root = path.join(EXT_DIR, "pstack");
|
|
11
|
+
const out = [];
|
|
12
|
+
const walk = (dir, rel) => {
|
|
13
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
14
|
+
const child = path.join(dir, entry.name);
|
|
15
|
+
const relChild = rel ? `${rel}/${entry.name}` : entry.name;
|
|
16
|
+
if (entry.isDirectory())
|
|
17
|
+
walk(child, relChild);
|
|
18
|
+
else if (entry.name.endsWith(".md"))
|
|
19
|
+
out.push(`pstack/${relChild}`);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
walk(root, "");
|
|
23
|
+
return out.toSorted();
|
|
24
|
+
}
|
|
25
|
+
export const TREE_PSTACK_FILES = pstackTreeFiles();
|
|
8
26
|
/** Every file in the tree, as paths relative to the extensions directory. */
|
|
9
27
|
export const TREE_FILES = [
|
|
10
28
|
'shared/host.ts',
|
|
@@ -17,12 +35,17 @@ export const TREE_FILES = [
|
|
|
17
35
|
'shared/pricing.ts',
|
|
18
36
|
'shared/carbon.ts',
|
|
19
37
|
'shared/utils.ts',
|
|
38
|
+
'shared/rtk-eligible.ts',
|
|
20
39
|
'caveman/index.ts',
|
|
21
40
|
'caveman/rule.md',
|
|
41
|
+
'caveman/rule-lite.md',
|
|
42
|
+
'caveman/rule-megacave-lite.md',
|
|
43
|
+
'caveman/rule-megacave-ultra.md',
|
|
22
44
|
'caveman/rule-ultra.md',
|
|
23
45
|
'caveman/rule-megacave.md',
|
|
24
46
|
'pstack/index.ts',
|
|
25
47
|
'pstack/rule.md',
|
|
48
|
+
...TREE_PSTACK_FILES,
|
|
26
49
|
'rtk/session.ts',
|
|
27
50
|
'rtk/filter.ts',
|
|
28
51
|
'timing/index.ts',
|
|
@@ -108,10 +131,7 @@ export function shippedOmpManifest() {
|
|
|
108
131
|
return null;
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
|
-
|
|
112
|
-
* Write the shipped `omp` block over an installed plugin's manifest. The tree files come from this
|
|
113
|
-
* build, so an entry added here reaches the host only when its manifest names it.
|
|
114
|
-
*/
|
|
134
|
+
// Write the shipped `omp` block over an installed plugin's manifest: an entry added here reaches the host only when its manifest names it.
|
|
115
135
|
export async function syncPluginManifest(pluginDir, options = {}) {
|
|
116
136
|
const omp = shippedOmpManifest();
|
|
117
137
|
if (omp === null)
|
package/dist/cli/profile.js
CHANGED
|
@@ -62,11 +62,12 @@ async function storedProfile() {
|
|
|
62
62
|
return storedProfileSync();
|
|
63
63
|
}
|
|
64
64
|
function legacyOmpLockPath() {
|
|
65
|
-
const
|
|
65
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
66
|
+
const configHome = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
66
67
|
return [
|
|
67
|
-
path.join(
|
|
68
|
+
path.join(home, '.omp', 'plugins', 'omp-plugins.lock.json'),
|
|
68
69
|
path.join(configHome, 'omp', 'plugins', 'omp-plugins.lock.json'),
|
|
69
|
-
].find((p) => existsSync(p)) ?? path.join(
|
|
70
|
+
].find((p) => existsSync(p)) ?? path.join(home, '.omp', 'plugins', 'omp-plugins.lock.json');
|
|
70
71
|
}
|
|
71
72
|
function parseStored(raw) {
|
|
72
73
|
if (!raw)
|
|
@@ -83,6 +84,29 @@ function parseStored(raw) {
|
|
|
83
84
|
return null;
|
|
84
85
|
}
|
|
85
86
|
}
|
|
87
|
+
// Upstream Ponytail's pi extension reads its own config for the session default, so the saved default
|
|
88
|
+
// is mirrored there. Without it a headless pi session starts at upstream's default and never injects.
|
|
89
|
+
function ponytailConfigFile() {
|
|
90
|
+
const base = (process.env.XDG_CONFIG_HOME || '').trim() || path.join(os.homedir(), '.config');
|
|
91
|
+
return path.join(base, 'ponytail', 'config.json');
|
|
92
|
+
}
|
|
93
|
+
async function mirrorPonytailDefault(mode, options) {
|
|
94
|
+
const file = ponytailConfigFile();
|
|
95
|
+
if (options.dryRun) {
|
|
96
|
+
if (verbose && !options.quiet)
|
|
97
|
+
console.log(` [dry-run] would write the Ponytail default to ${file}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const existing = JSON.parse(readTextIfExistsSync(file) ?? '{}');
|
|
102
|
+
if (existing.defaultMode === mode)
|
|
103
|
+
return;
|
|
104
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
105
|
+
await fs.writeFile(file, `${JSON.stringify({ ...existing, defaultMode: mode }, null, 2)}\n`, 'utf8');
|
|
106
|
+
console.log(` [write] Ponytail default in ${file}`);
|
|
107
|
+
}
|
|
108
|
+
catch { /* upstream's config is best-effort; its own default stands */ }
|
|
109
|
+
}
|
|
86
110
|
// Persist profile where every host reads it. `omp plugin config get` no longer reports it; `tersio settings` does.
|
|
87
111
|
async function writePluginSettings(profile, options) {
|
|
88
112
|
const file = tersioSettingsFile();
|
|
@@ -106,6 +130,7 @@ async function writePluginSettings(profile, options) {
|
|
|
106
130
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
107
131
|
await fs.writeFile(file, `${JSON.stringify(values, null, 2)}\n`, 'utf8');
|
|
108
132
|
console.log(` [write] Session defaults in ${file}`);
|
|
133
|
+
await mirrorPonytailDefault(profile.ponytailDefault, options);
|
|
109
134
|
}
|
|
110
135
|
function formatProfile(profile) {
|
|
111
136
|
return `combo=${profile.comboDefault} (caveman=${profile.cavemanDefault} · rtk=${profile.rtkDefault ? 'on' : 'off'} · ponytail=${profile.ponytailDefault} · pstack=${profile.pstackDefault}) · currency=${profile.currency}`;
|
package/dist/cli/rtk-wiring.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, promises as fs } from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { execFile } from 'node:child_process';
|
|
6
|
+
import { shimCommand } from '../extensions/shared/utils.js';
|
|
6
7
|
function debugWire(options, msg) {
|
|
7
8
|
if (!options.quiet)
|
|
8
9
|
console.log(` ${msg}`);
|
|
@@ -14,17 +15,19 @@ function shortWiringError(e) {
|
|
|
14
15
|
return why.slice(0, 200);
|
|
15
16
|
}
|
|
16
17
|
function execFileP(cmd, args, timeout) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
19
|
+
// A `.cmd` shim cannot be spawned directly on Windows, so it runs through the shell.
|
|
20
|
+
const shim = shimCommand(cmd, args);
|
|
21
|
+
execFile(shim.cmd, shim.args, { timeout, encoding: 'utf8' }, (err, stdout, stderr) => {
|
|
22
|
+
if (err) {
|
|
23
|
+
err.stderr = String(stderr || '');
|
|
24
|
+
reject(err);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
resolve({ stdout: String(stdout), stderr: String(stderr) });
|
|
28
|
+
}
|
|
27
29
|
});
|
|
30
|
+
return promise;
|
|
28
31
|
}
|
|
29
32
|
async function runWire(rtkBin, spec, options) {
|
|
30
33
|
if (!options.dryRun)
|
package/dist/cli/uninstall.js
CHANGED
|
@@ -306,7 +306,7 @@ async function runUninstall(options = {}) {
|
|
|
306
306
|
}
|
|
307
307
|
// Cleared last: writeConfigLines above would just make a fresh one.
|
|
308
308
|
const cavemanDir = path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'extensions', 'caveman-session');
|
|
309
|
-
for (const backup of [`${configPath}.bak`, path.join(cavemanDir, 'rule.md.bak'), path.join(cavemanDir, 'rule-ultra.md.bak'), path.join(cavemanDir, 'rule-megacave.md.bak')]) {
|
|
309
|
+
for (const backup of [`${configPath}.bak`, path.join(cavemanDir, 'rule.md.bak'), path.join(cavemanDir, 'rule-lite.md.bak'), path.join(cavemanDir, 'rule-megacave-lite.md.bak'), path.join(cavemanDir, 'rule-megacave-ultra.md.bak'), path.join(cavemanDir, 'rule-ultra.md.bak'), path.join(cavemanDir, 'rule-megacave.md.bak')]) {
|
|
310
310
|
await removeUninstallTarget(backup, shouldDryRun, false);
|
|
311
311
|
}
|
|
312
312
|
// Remove the bundled Ponytail copy; its config.yml entry is already filtered.
|
package/dist/cli/usage.js
CHANGED
|
@@ -215,6 +215,11 @@ function printReport(report) {
|
|
|
215
215
|
['Tokens', `${fmt(t.input)} in · ${fmt(t.output)} out · ${fmt(t.cacheRead)} cache read · ${fmt(t.cacheWrite)} cache write`],
|
|
216
216
|
['Cost', `${formatCurrency(report.usd, report.currency)}${report.priced ? '' : ' (includes default pricing)'} · ~${formatCurrency(report.savedUsd, report.currency)} cache-saved (est.) · ~${report.co2g.toFixed(1)}g CO2 (est.)${costNote}`],
|
|
217
217
|
['RTK adoption', `${fmt(a.rtkCalls)}/${fmt(a.eligibleCalls)} eligible Bash calls use RTK (${a.adoptionPct.toFixed(1)}%) · ${fmt(a.missedCalls)} missed · recall ${report.rtkRecall.available ? `${report.rtkRecall.mode} (${report.rtkRecall.entries})` : 'unavailable'}`],
|
|
218
|
+
// A blended number hides a quiet host, so each host reporting bash calls gets its own row.
|
|
219
|
+
...Object.entries(a.byHost)
|
|
220
|
+
.filter(([, c]) => c.bashCalls > 0)
|
|
221
|
+
.sort((x, y) => y[1].eligibleCalls - x[1].eligibleCalls)
|
|
222
|
+
.map(([name, c]) => [`RTK — ${name}`, `${fmt(c.rtkCalls)}/${fmt(c.eligibleCalls)} eligible calls (${c.adoptionPct.toFixed(1)}%) · ${fmt(c.missedCalls)} missed · ${fmt(c.bashCalls)} bash calls`]),
|
|
218
223
|
], [false, false], 118))
|
|
219
224
|
console.log(l);
|
|
220
225
|
if (measuredCount > 0) {
|
|
@@ -5,9 +5,10 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
|
|
6
6
|
import { announceStatus, formatCavemanStatus } from '../shared/status.js';
|
|
7
7
|
import { readCavemanDefault } from '../shared/plugin-settings.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { ruleDir } from '../shared/utils.js';
|
|
9
|
+
const CAVERN_DIR = ruleDir('caveman', dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
// `rule.md`, `rule-ultra.md`, and `rule-megacave.md` track upstream and are refetched; `lite`, `megacave-lite`, and `megacave-ultra` are our forks and ship bundled.
|
|
11
|
+
const RULE_FILES = { lite: 'rule-lite.md', full: 'rule.md', ultra: 'rule-ultra.md', 'megacave-lite': 'rule-megacave-lite.md', 'megacave-full': 'rule-megacave.md', 'megacave-ultra': 'rule-megacave-ultra.md' };
|
|
11
12
|
const DEFAULT_MODE = 'off';
|
|
12
13
|
// A rule file is immutable for the process, so read each level once; reinstalling needs a restart.
|
|
13
14
|
const instructionCache = new Map();
|