@krtclcdy/tersio 2.25.5 → 2.25.7
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 +42 -38
- package/dashboard/dist/index.html +15 -15
- package/dist/bench/code-out/pfull-0/debounce.js +13 -0
- package/dist/bench/code-out/pfull-0/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-0/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-1/debounce.js +13 -0
- package/dist/bench/code-out/pfull-1/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-1/groupBy.js +14 -0
- package/dist/bench/code-out/pfull-2/debounce.js +13 -0
- package/dist/bench/code-out/pfull-2/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-2/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-3/debounce.js +12 -0
- package/dist/bench/code-out/pfull-3/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-3/groupBy.js +7 -0
- package/dist/bench/code-out/pfull-4/debounce.js +10 -0
- package/dist/bench/code-out/pfull-4/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-4/groupBy.js +14 -0
- package/dist/bench/code-out/plite-0/debounce.js +13 -0
- package/dist/bench/code-out/plite-0/deepClone.js +18 -0
- package/dist/bench/code-out/plite-0/groupBy.js +15 -0
- package/dist/bench/code-out/plite-1/debounce.js +13 -0
- package/dist/bench/code-out/plite-1/deepClone.js +17 -0
- package/dist/bench/code-out/plite-1/groupBy.js +11 -0
- package/dist/bench/code-out/plite-2/debounce.js +12 -0
- package/dist/bench/code-out/plite-2/deepClone.js +17 -0
- package/dist/bench/code-out/plite-2/groupBy.js +14 -0
- package/dist/bench/code-out/plite-3/debounce.js +12 -0
- package/dist/bench/code-out/plite-3/deepClone.js +17 -0
- package/dist/bench/code-out/plite-3/groupBy.js +14 -0
- package/dist/bench/code-out/plite-4/debounce.js +12 -0
- package/dist/bench/code-out/plite-4/deepClone.js +17 -0
- package/dist/bench/code-out/plite-4/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-0/debounce.js +12 -0
- package/dist/bench/code-out/pultra-0/deepClone.js +20 -0
- package/dist/bench/code-out/pultra-0/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-1/debounce.js +10 -0
- package/dist/bench/code-out/pultra-1/deepClone.js +17 -0
- package/dist/bench/code-out/pultra-1/groupBy.js +7 -0
- package/dist/bench/code-out/pultra-2/debounce.js +13 -0
- package/dist/bench/code-out/pultra-2/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-2/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-3/debounce.js +13 -0
- package/dist/bench/code-out/pultra-3/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-3/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-4/debounce.js +13 -0
- package/dist/bench/code-out/pultra-4/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-4/groupBy.js +15 -0
- package/dist/bench/code-out/run-0/debounce.js +12 -0
- package/dist/bench/code-out/run-0/deepClone.js +17 -0
- package/dist/bench/code-out/run-0/groupBy.js +14 -0
- package/dist/bench/code-out/run-1/debounce.js +13 -0
- package/dist/bench/code-out/run-1/deepClone.js +15 -0
- package/dist/bench/code-out/run-1/groupBy.js +13 -0
- package/dist/bench/code-out/run-2/debounce.js +12 -0
- package/dist/bench/code-out/run-2/deepClone.js +18 -0
- package/dist/bench/code-out/run-2/groupBy.js +13 -0
- package/dist/bench/code-out/run-3/debounce.js +12 -0
- package/dist/bench/code-out/run-3/deepClone.js +18 -0
- package/dist/bench/code-out/run-3/groupBy.js +14 -0
- package/dist/bench/code-out/run-4/debounce.js +12 -0
- package/dist/bench/code-out/run-4/deepClone.js +16 -0
- package/dist/bench/code-out/run-4/groupBy.js +13 -0
- package/dist/cli/backups.js +7 -14
- package/dist/cli/banner.js +3 -3
- package/dist/cli/common.js +29 -13
- package/dist/cli/currency.js +1 -1
- package/dist/cli/dashboard.js +66 -35
- package/dist/cli/doctor-fix.js +204 -19
- package/dist/cli/doctor.js +263 -40
- package/dist/cli/hosts.js +25 -6
- package/dist/cli/install.js +150 -49
- package/dist/cli/interactive.js +3 -3
- package/dist/cli/manifest.js +109 -9
- package/dist/cli/profile.js +13 -8
- package/dist/cli/rtk-wiring.js +1 -1
- package/dist/cli/settings.js +20 -5
- package/dist/cli/uninstall.js +4 -4
- package/dist/cli/update.js +6 -7
- package/dist/cli/usage.js +6 -9
- package/dist/extensions/caveman/index.js +10 -4
- package/dist/extensions/caveman/rule-lite.md +77 -0
- package/dist/extensions/caveman/rule-megacave-lite.md +59 -0
- package/dist/extensions/caveman/rule-megacave-ultra.md +59 -0
- package/dist/extensions/caveman/rule-megacave.md +5 -3
- package/dist/extensions/caveman/rule-ultra.md +4 -4
- package/dist/extensions/caveman/rule.md +55 -45
- package/dist/extensions/combo/index.js +85 -25
- package/dist/extensions/commands/index.js +1 -1
- package/dist/extensions/pstack/index.js +121 -0
- package/dist/extensions/pstack/rule.md +141 -0
- package/dist/extensions/rtk/filter.js +0 -1
- package/dist/extensions/rtk/session.js +20 -12
- package/dist/extensions/server.js +90 -6
- package/dist/extensions/shared/agent-stats.js +214 -278
- package/dist/extensions/shared/brand-marks.js +33 -34
- package/dist/extensions/shared/carbon.js +1 -1
- package/dist/extensions/shared/plugin-settings.js +11 -6
- package/dist/extensions/shared/pricing.js +8 -16
- package/dist/extensions/shared/rtk-eligible.js +84 -0
- package/dist/extensions/shared/rtk-gain.js +5 -10
- package/dist/extensions/shared/session-state.js +15 -10
- package/dist/extensions/shared/sqlite.js +162 -0
- package/dist/extensions/shared/status.js +8 -9
- package/dist/extensions/shared/types.js +1 -1
- package/dist/extensions/shared/usage-ledger.js +155 -61
- package/dist/extensions/shared/usage-store.js +111 -52
- package/dist/extensions/shared/utils.js +5 -5
- package/dist/extensions/timing/index.js +31 -0
- package/dist/extensions/updater/index.js +3 -6
- package/dist/tersio.js +8 -3
- package/extensions/caveman/index.ts +10 -4
- 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-megacave.md +5 -3
- package/extensions/caveman/rule-ultra.md +4 -4
- package/extensions/caveman/rule.md +55 -45
- package/extensions/combo/index.ts +86 -23
- package/extensions/commands/index.ts +1 -1
- package/extensions/pstack/index.ts +130 -0
- 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 +141 -0
- 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 +0 -1
- package/extensions/rtk/session.ts +19 -11
- package/extensions/server.ts +72 -7
- package/extensions/shared/agent-stats.ts +226 -278
- package/extensions/shared/brand-marks.ts +34 -34
- package/extensions/shared/carbon.ts +1 -1
- package/extensions/shared/plugin-settings.ts +12 -6
- package/extensions/shared/pricing.ts +10 -26
- package/extensions/shared/rtk-eligible.ts +83 -0
- package/extensions/shared/rtk-gain.ts +5 -10
- package/extensions/shared/session-state.ts +18 -13
- package/extensions/shared/sqlite.ts +162 -0
- package/extensions/shared/status.ts +9 -9
- package/extensions/shared/types.ts +2 -1
- package/extensions/shared/usage-ledger.ts +157 -66
- package/extensions/shared/usage-store.ts +120 -52
- package/extensions/shared/utils.ts +5 -5
- package/extensions/timing/index.ts +44 -0
- package/extensions/updater/index.ts +3 -8
- package/package.json +21 -9
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, cavemanDefaultFlag, comboDefaultFlag, command, dryRun, ponytailDefaultFlag, profileFlagsGiven, rtkDefaultFlag, verbose, yes, dashboardExport, dashboardPort, currency, debug, ensurePonytailConfigValue, execP, parseJsonObject, readPluginsPackage, writeIfChanged, } from './common.js';
|
|
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';
|
|
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';
|
|
@@ -17,7 +17,7 @@ import { CAVEMAN_REMOTE_ULTRA, CAVEMAN_REMOTE_MEGACAVE, RTK_RELEASE_API, fetchJs
|
|
|
17
17
|
import { formatCliStatus, storedProfile, storedProfileSync, writePluginSettings } from './profile.js';
|
|
18
18
|
import { runSettings } from './settings.js';
|
|
19
19
|
import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
|
|
20
|
-
import { filesUnder, rootFiles, sourcePath } from './manifest.js';
|
|
20
|
+
import { PI_RTK_MANIFEST, PI_PONYTAIL_FILES, PONYTAIL_BUNDLE_FILES, filesUnder, rootFiles, sourcePath, syncPluginManifest, syncTreeFiles } from './manifest.js';
|
|
21
21
|
import { detectHosts, hostHint, hostLabel, parseHostArg, piTersioSource } from './hosts.js';
|
|
22
22
|
async function stepPonytail(pluginsDir, options) {
|
|
23
23
|
if (!options.quiet)
|
|
@@ -25,22 +25,44 @@ async function stepPonytail(pluginsDir, options) {
|
|
|
25
25
|
await fs.mkdir(pluginsDir, { recursive: true });
|
|
26
26
|
const pkgPath = path.join(pluginsDir, 'package.json');
|
|
27
27
|
const pkg = await readPluginsPackage(pkgPath);
|
|
28
|
-
//
|
|
28
|
+
// npm resolves the *published* tersio, so a plain dependency entry lets npm nest the range's version.
|
|
29
29
|
let migrated = false;
|
|
30
|
-
|
|
30
|
+
const localSpec = await bundledPonytailSpec();
|
|
31
|
+
const declared = pkg.dependencies['@dietrichgebert/ponytail'];
|
|
32
|
+
// Node resolves the nested copy first, so a dependency entry alone cannot hold the local version.
|
|
33
|
+
const rawOverrides = typeof pkg.overrides === 'object' && pkg.overrides !== null ? { ...pkg.overrides } : {};
|
|
34
|
+
const overrideNeeded = localSpec !== null && rawOverrides['@dietrichgebert/ponytail'] !== localSpec;
|
|
35
|
+
if (localSpec !== null && declared !== localSpec) {
|
|
36
|
+
pkg.dependencies['@dietrichgebert/ponytail'] = localSpec;
|
|
37
|
+
migrated = true;
|
|
38
|
+
}
|
|
39
|
+
else if (localSpec === null && declared) {
|
|
31
40
|
delete pkg.dependencies['@dietrichgebert/ponytail'];
|
|
32
41
|
migrated = true;
|
|
33
42
|
}
|
|
43
|
+
if (localSpec === null) {
|
|
44
|
+
// No bundled spec means no tersio-owned row, so an override would resurrect the dependency.
|
|
45
|
+
delete rawOverrides['@dietrichgebert/ponytail'];
|
|
46
|
+
}
|
|
47
|
+
else if (overrideNeeded) {
|
|
48
|
+
rawOverrides['@dietrichgebert/ponytail'] = localSpec;
|
|
49
|
+
}
|
|
50
|
+
if (Object.keys(rawOverrides).length === 0)
|
|
51
|
+
delete pkg.overrides;
|
|
52
|
+
else if (JSON.stringify(rawOverrides) !== JSON.stringify(typeof pkg.overrides === 'object' && pkg.overrides !== null ? pkg.overrides : {})) {
|
|
53
|
+
pkg.overrides = rawOverrides;
|
|
54
|
+
migrated = true;
|
|
55
|
+
}
|
|
34
56
|
if (options.dryRun) {
|
|
35
57
|
if (verbose && !options.quiet)
|
|
36
58
|
sayTagged(` [dry-run] would write ${pkgPath}`);
|
|
37
59
|
if (migrated && verbose && !options.quiet)
|
|
38
|
-
sayTagged(` [dry-run] would
|
|
60
|
+
sayTagged(` [dry-run] would set @dietrichgebert/ponytail → ${localSpec ?? 'this tree\x27s range'} (bundled with tersio)`);
|
|
39
61
|
}
|
|
40
62
|
else if (migrated) {
|
|
41
63
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
42
64
|
if (!options.quiet)
|
|
43
|
-
console.log(
|
|
65
|
+
console.log(` [migrate] set @dietrichgebert/ponytail → ${pkg.dependencies['@dietrichgebert/ponytail'] ?? 'this tree\x27s range'} (bundled with tersio)`);
|
|
44
66
|
}
|
|
45
67
|
const ponytailExtPath = path.join(pluginsDir, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
|
|
46
68
|
const probeExt = async () => (await readTextIfExists(ponytailExtPath)) !== null;
|
|
@@ -50,7 +72,7 @@ async function stepPonytail(pluginsDir, options) {
|
|
|
50
72
|
debug('Bundled Ponytail pi-extension already installed; skipping network refresh');
|
|
51
73
|
}
|
|
52
74
|
else if (options.dryRun) {
|
|
53
|
-
//
|
|
75
|
+
// Pretend the extension is present so the wiring below still runs, without touching the network.
|
|
54
76
|
if (verbose && !options.quiet)
|
|
55
77
|
console.log(' [dry-run] would run: npm install --no-audit --no-fund (in plugins dir)');
|
|
56
78
|
ponytailExtExists = true;
|
|
@@ -163,7 +185,7 @@ async function downloadRtkChecksums(release) {
|
|
|
163
185
|
return null;
|
|
164
186
|
}
|
|
165
187
|
}
|
|
166
|
-
// Fails closed:
|
|
188
|
+
// Fails closed: an unverifiable RTK binary runs with the user's privileges. `--allow-unverified` opts out.
|
|
167
189
|
async function verifyRtkArchive(archivePath, assetName, checksumsText) {
|
|
168
190
|
const unverified = (reason) => {
|
|
169
191
|
if (allowUnverified) {
|
|
@@ -229,11 +251,7 @@ async function extractRtkArchive(archivePath, extractDir) {
|
|
|
229
251
|
sayTagged(` [fail] Unknown archive format: ${archivePath}`);
|
|
230
252
|
return false;
|
|
231
253
|
}
|
|
232
|
-
//
|
|
233
|
-
// OMP plugin manifest, which omp merges with the extension dir it discovers by default, so a
|
|
234
|
-
// copy left in the agent tree loads a second time. `caveman-session` was never written here
|
|
235
|
-
// after the plugin registration shipped, so an old install holds a set that cannot resolve
|
|
236
|
-
// its own rule bodies.
|
|
254
|
+
// A rule body found in a retired copy still resolves and loads twice, so drop the agent tree's extras.
|
|
237
255
|
const RETIRED_OMP_AGENT_DIRS = [
|
|
238
256
|
'caveman-session',
|
|
239
257
|
'rtk-session',
|
|
@@ -289,7 +307,7 @@ async function stepRtk(binDir, options, target = 'omp') {
|
|
|
289
307
|
}
|
|
290
308
|
if (!options.quiet)
|
|
291
309
|
console.log(` RTK — download binary and wire into ${hostName}`);
|
|
292
|
-
// A failed download must not skip wiring: a pre-existing binary serves the host hook
|
|
310
|
+
// A failed download must not skip wiring: a pre-existing binary serves the host hook. Dry runs stay offline.
|
|
293
311
|
if (options.dryRun) {
|
|
294
312
|
if (verbose && !options.quiet)
|
|
295
313
|
sayTagged(` [dry-run] would download rtk binary and install to ${binDest}`);
|
|
@@ -300,7 +318,7 @@ async function stepRtk(binDir, options, target = 'omp') {
|
|
|
300
318
|
const triple = resolveRtkTriple();
|
|
301
319
|
if (!triple)
|
|
302
320
|
return;
|
|
303
|
-
// One task with streamed sub-steps
|
|
321
|
+
// One task with streamed sub-steps; non-TTY keeps the plain lines each step already prints.
|
|
304
322
|
await withInteractiveTask('Installing RTK', async (update) => {
|
|
305
323
|
const release = await fetchJson(RTK_RELEASE_API);
|
|
306
324
|
const asset = findRtkAsset(release, triple);
|
|
@@ -359,7 +377,7 @@ async function stepRtk(binDir, options, target = 'omp') {
|
|
|
359
377
|
// Wire rtk's tool_call extension: the binary alone never meters a session.
|
|
360
378
|
await bind(binDest);
|
|
361
379
|
}
|
|
362
|
-
//
|
|
380
|
+
// First entry is required; the rest are optional companions.
|
|
363
381
|
async function copySources(extDir, files, skipLabel, options) {
|
|
364
382
|
const src = await readTextIfExists(files[0][0]);
|
|
365
383
|
if (!src) {
|
|
@@ -393,10 +411,12 @@ async function stepRtkFilter(extDir, options) {
|
|
|
393
411
|
console.log(' RTK filter — install built-in tool output filtering');
|
|
394
412
|
await copySources(extDir, filesUnder('rtk'), 'rtk/filter.ts', options);
|
|
395
413
|
}
|
|
396
|
-
// rule.md
|
|
397
|
-
// rule.md is a deliberate fork of upstream skills/caveman/SKILL.md; a blind sync would drop the mode switch line and the STE100 rules.
|
|
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.
|
|
398
415
|
const CAVEMAN_RULES = [
|
|
399
416
|
['rule.md', null],
|
|
417
|
+
['rule-lite.md', null],
|
|
418
|
+
['rule-megacave-lite.md', null],
|
|
419
|
+
['rule-megacave-ultra.md', null],
|
|
400
420
|
['rule-ultra.md', CAVEMAN_REMOTE_ULTRA],
|
|
401
421
|
['rule-megacave.md', CAVEMAN_REMOTE_MEGACAVE],
|
|
402
422
|
];
|
|
@@ -449,17 +469,26 @@ async function stepTersioCommands(extDir, options) {
|
|
|
449
469
|
async function stepUpdater(extDir, options) {
|
|
450
470
|
await copySources(extDir, filesUnder('updater'), 'updater/index.ts', options);
|
|
451
471
|
}
|
|
472
|
+
// omp loads no OpenCode entry, but its package ships one: the repair writes it, so the install does too.
|
|
473
|
+
async function stepPluginEntry(extDir, options) {
|
|
474
|
+
await copySources(extDir, rootFiles(), 'server.ts', options);
|
|
475
|
+
}
|
|
476
|
+
// pi records no first-token latency, so this stamps one into the row the dashboard reads.
|
|
477
|
+
async function stepTiming(extDir, options) {
|
|
478
|
+
await copySources(extDir, filesUnder('timing'), 'timing/index.ts', options);
|
|
479
|
+
}
|
|
452
480
|
async function stepCombo(extDir, options) {
|
|
453
481
|
if (!options.quiet)
|
|
454
482
|
console.log(' Combo — install preset switch');
|
|
455
483
|
await copySources(extDir, filesUnder('combo'), 'combo/index.ts', options);
|
|
456
484
|
}
|
|
457
|
-
// The
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
'
|
|
462
|
-
|
|
485
|
+
// The body is a Tersio fork of an upstream skill, so the install copies it and never fetches it.
|
|
486
|
+
async function stepPstack(extDir, options) {
|
|
487
|
+
if (!options.quiet)
|
|
488
|
+
console.log(' pstack — install the rigor mode');
|
|
489
|
+
await copySources(extDir, filesUnder('pstack'), 'pstack/index.ts', options);
|
|
490
|
+
}
|
|
491
|
+
// The tree ships no node_modules, so the ruleset rides along as files. Both hosts need the same six.
|
|
463
492
|
async function stepPonytailBundle(treeDir, options) {
|
|
464
493
|
const root = findHoistedPackage('@dietrichgebert/ponytail', findPackageRoot(path.dirname(fileURLToPath(import.meta.url))));
|
|
465
494
|
if (!root) {
|
|
@@ -487,11 +516,15 @@ async function stepOpencode(options) {
|
|
|
487
516
|
const dir = path.join(HOME, '.config', 'opencode', 'plugins', 'tersio');
|
|
488
517
|
if (!options.quiet)
|
|
489
518
|
console.log(` OpenCode — write the plugin tree (${dir})`);
|
|
519
|
+
// Retire the `-session`/`-toggle` leftovers first: a rule body found in one still resolves.
|
|
520
|
+
await stepRetireAgentCopies(dir, options);
|
|
490
521
|
await stepSharedSessionState(dir, options);
|
|
491
522
|
await copySources(dir, filesUnder('rtk'), 'rtk/session.ts', options);
|
|
492
523
|
await stepRtkFilter(dir, options);
|
|
524
|
+
await stepTiming(dir, options);
|
|
493
525
|
await copySources(dir, filesUnder('caveman'), 'caveman/index.ts', options);
|
|
494
526
|
await stepCombo(dir, options);
|
|
527
|
+
await stepPstack(dir, options);
|
|
495
528
|
await copySources(dir, filesUnder('commands'), 'commands/index.ts', options);
|
|
496
529
|
await stepUpdater(dir, options);
|
|
497
530
|
await stepPonytailBundle(dir, options);
|
|
@@ -515,7 +548,8 @@ async function stepOpencode(options) {
|
|
|
515
548
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
516
549
|
await fs.rm(path.join(HOME, '.config', 'opencode', 'plugins', 'rtk.ts'), { force: true }).catch(() => { });
|
|
517
550
|
}
|
|
518
|
-
|
|
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 });
|
|
519
553
|
}
|
|
520
554
|
async function resolveProfile(opts = {}) {
|
|
521
555
|
// Seed from the stored defaults so flag-less runs keep them.
|
|
@@ -547,6 +581,7 @@ async function resolveProfile(opts = {}) {
|
|
|
547
581
|
profile.cavemanDefault = preset.caveman;
|
|
548
582
|
profile.rtkDefault = preset.rtk;
|
|
549
583
|
profile.ponytailDefault = preset.ponytail;
|
|
584
|
+
profile.pstackDefault = preset.pstack;
|
|
550
585
|
}
|
|
551
586
|
if (cavemanDefaultFlag !== undefined)
|
|
552
587
|
profile.cavemanDefault = cavemanDefaultFlag;
|
|
@@ -554,8 +589,10 @@ async function resolveProfile(opts = {}) {
|
|
|
554
589
|
profile.rtkDefault = rtkDefaultFlag === 'on';
|
|
555
590
|
if (ponytailDefaultFlag !== undefined)
|
|
556
591
|
profile.ponytailDefault = ponytailDefaultFlag;
|
|
592
|
+
if (pstackDefaultFlag !== undefined)
|
|
593
|
+
profile.pstackDefault = pstackDefaultFlag;
|
|
557
594
|
if ((!opts.quiet && verbose) || dryRun)
|
|
558
|
-
console.log(` Defaults: combo=${profile.comboDefault} (caveman=${profile.cavemanDefault} · rtk=${profile.rtkDefault ? 'on' : 'off'} · ponytail=${profile.ponytailDefault})`);
|
|
595
|
+
console.log(` Defaults: combo=${profile.comboDefault} (caveman=${profile.cavemanDefault} · rtk=${profile.rtkDefault ? 'on' : 'off'} · ponytail=${profile.ponytailDefault} · pstack=${profile.pstackDefault})`);
|
|
559
596
|
return profile;
|
|
560
597
|
}
|
|
561
598
|
let updatePromptDone = false;
|
|
@@ -580,6 +617,7 @@ async function runCommandMenu() {
|
|
|
580
617
|
updatePromptDone = true;
|
|
581
618
|
const choice = await askInteractiveChoice(`Tersio — what next?\n${formatCliStatus(storedProfileSync())}`, [
|
|
582
619
|
{ value: 'install', label: 'Install add-ons', hint: 'user scope + combo defaults' },
|
|
620
|
+
{ value: 'reinstall', label: 'Reinstall', hint: 're-install Tersio for the ai agents, then verify' },
|
|
583
621
|
{ value: 'update', label: 'Update', hint: 'CLI version check, then refresh add-ons (RTK, Caveman rule, Ponytail)' },
|
|
584
622
|
{ value: 'doctor', label: 'Doctor', hint: 'verify the installation' },
|
|
585
623
|
{ value: 'settings', label: 'Settings', hint: 'default combo mode, currency, diagnosis schedule' },
|
|
@@ -596,8 +634,11 @@ async function runCommandMenu() {
|
|
|
596
634
|
case 'install':
|
|
597
635
|
await runInstall();
|
|
598
636
|
break;
|
|
637
|
+
case 'reinstall':
|
|
638
|
+
await runReinstall();
|
|
639
|
+
break;
|
|
599
640
|
case 'update': {
|
|
600
|
-
//
|
|
641
|
+
// Version check already ran above, so report it and offer the refresh here — no second "update" quiz.
|
|
601
642
|
if (typeof newer === 'string')
|
|
602
643
|
console.log(` tersio ${newer} available (installed ${PACKAGE_VERSION})`);
|
|
603
644
|
else
|
|
@@ -657,9 +698,57 @@ async function runCommandMenu() {
|
|
|
657
698
|
break;
|
|
658
699
|
}
|
|
659
700
|
}
|
|
660
|
-
// Which host this run targets. --host pins it;
|
|
701
|
+
// Which host this run targets. --host pins it; scripts and --yes keep the default of Oh My Pi.
|
|
661
702
|
let targetHost = 'omp';
|
|
662
|
-
//
|
|
703
|
+
// A reinstall re-runs the npm fast path and the Ponytail probe instead of reusing what is on disk.
|
|
704
|
+
let forceReinstall = false;
|
|
705
|
+
/** Reinstall one agent: pick it from what is installed now, then run the full install with a refresh. */
|
|
706
|
+
async function runReinstall() {
|
|
707
|
+
let pinned;
|
|
708
|
+
try {
|
|
709
|
+
pinned = parseHostArg(args);
|
|
710
|
+
}
|
|
711
|
+
catch (e) {
|
|
712
|
+
console.error(`[fail] ${e.message}`);
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
const installed = detectHosts().filter((host) => host.installed);
|
|
716
|
+
let host;
|
|
717
|
+
if (pinned)
|
|
718
|
+
host = pinned;
|
|
719
|
+
else if (installed.length === 0) {
|
|
720
|
+
console.log(' Nothing to reinstall — no agent has Tersio installed.\n Run `tersio install`, or pick it from the menu.');
|
|
721
|
+
closeRL();
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
else if (installed.length === 1 || !tty() || yes || dryRun) {
|
|
725
|
+
host = installed[0].id;
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
const options = installed.map((entry) => ({
|
|
729
|
+
value: entry.id,
|
|
730
|
+
label: hostLabel(entry),
|
|
731
|
+
hint: hostHint(entry),
|
|
732
|
+
}));
|
|
733
|
+
const picked = await askInteractiveChoice('Reinstall Tersio into which agent?', options, installed[0].id);
|
|
734
|
+
if (picked.status !== 'selected') {
|
|
735
|
+
closeRL();
|
|
736
|
+
process.exit(130);
|
|
737
|
+
}
|
|
738
|
+
host = picked.value;
|
|
739
|
+
}
|
|
740
|
+
forceReinstall = true;
|
|
741
|
+
printWelcome();
|
|
742
|
+
console.log(`Reinstall — Tersio v${PACKAGE_VERSION} into ${hostLabel(hostEntry(host))}`);
|
|
743
|
+
console.log(` ${resolvedHostPath(host)}`);
|
|
744
|
+
await runInstall(host);
|
|
745
|
+
if (!dryRun) {
|
|
746
|
+
const summary = await runDoctor();
|
|
747
|
+
if (summary.missing + summary.warn === 0)
|
|
748
|
+
console.log(`\n Reinstall verified — ${hostLabel(hostEntry(host))} is healthy.`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
// Hosts the menu advertises but cannot install yet; listed disabled so the roadmap stays visible.
|
|
663
752
|
const SOON_HOSTS = [
|
|
664
753
|
{ value: 'claude', label: 'Claude Code', hint: 'Coming soon' },
|
|
665
754
|
{ value: 'codex', label: 'Codex', hint: 'Coming soon' },
|
|
@@ -684,7 +773,19 @@ async function chooseHost(title, only) {
|
|
|
684
773
|
function hostEntry(id) {
|
|
685
774
|
return detectHosts().find((host) => host.id === id);
|
|
686
775
|
}
|
|
687
|
-
|
|
776
|
+
/** pi discovers a subdirectory only through an index file or a package.json with `pi.extensions`, so rtk/ needs one or both files stay dead. */
|
|
777
|
+
async function stepPiRtkManifest(extDir, options) {
|
|
778
|
+
await syncTreeFiles(extDir, [PI_RTK_MANIFEST], options);
|
|
779
|
+
}
|
|
780
|
+
/** The directory a host's install writes, for the plan line a reinstall prints. */
|
|
781
|
+
function resolvedHostPath(host) {
|
|
782
|
+
if (host === 'pi')
|
|
783
|
+
return path.join(piAgentDir(), 'extensions');
|
|
784
|
+
if (host === 'opencode')
|
|
785
|
+
return path.join(HOME, '.config', 'opencode', 'plugins', 'tersio');
|
|
786
|
+
return path.join(OMP_PLUGINS_DIR, 'node_modules', ...PACKAGE_NAME.split('/'), 'extensions');
|
|
787
|
+
}
|
|
788
|
+
// pi auto-discovers `<agent-dir>/extensions/**/index.ts`, so this route writes the same tree and registers nothing.
|
|
688
789
|
async function stepPiLayer(options) {
|
|
689
790
|
const agentDir = piAgentDir();
|
|
690
791
|
const extDir = path.join(agentDir, 'extensions');
|
|
@@ -697,20 +798,16 @@ async function stepPiLayer(options) {
|
|
|
697
798
|
// OMP loads rtk/session.ts from its plugin manifest, so only the pi tree copies it.
|
|
698
799
|
await copySources(extDir, filesUnder('rtk'), 'rtk/session.ts', options);
|
|
699
800
|
await stepRtkFilter(extDir, options);
|
|
801
|
+
await stepPiRtkManifest(extDir, options);
|
|
802
|
+
await stepTiming(extDir, options);
|
|
700
803
|
await stepCaveman(extDir, options);
|
|
701
804
|
await stepCombo(extDir, options);
|
|
805
|
+
await stepPstack(extDir, options);
|
|
702
806
|
await stepTersioCommands(extDir, options);
|
|
703
807
|
await stepUpdater(extDir, options);
|
|
704
808
|
await stepPonytailForPi(agentDir, options);
|
|
705
809
|
}
|
|
706
810
|
// A local dir, not an npm spec: pi only version-checks npm entries, so this avoids the update nag.
|
|
707
|
-
const PI_PONYTAIL_FILES = [
|
|
708
|
-
'pi-extension/index.js',
|
|
709
|
-
'hooks/ponytail-instructions.js',
|
|
710
|
-
'hooks/ponytail-config.js',
|
|
711
|
-
...['ponytail', 'ponytail-audit', 'ponytail-debt', 'ponytail-gain', 'ponytail-help', 'ponytail-review']
|
|
712
|
-
.map((skill) => `skills/${skill}/SKILL.md`),
|
|
713
|
-
];
|
|
714
811
|
const PI_PONYTAIL_PACKAGE = `${JSON.stringify({ name: 'ponytail', version: '0.0.0', private: true, pi: { extensions: ['./pi-extension/index.js'], skills: ['./skills'] } }, null, 2)}\n`;
|
|
715
812
|
async function stepPonytailForPi(agentDir, options) {
|
|
716
813
|
if (!options.quiet)
|
|
@@ -736,8 +833,7 @@ async function stepPonytailForPi(agentDir, options) {
|
|
|
736
833
|
}
|
|
737
834
|
if (options.dryRun)
|
|
738
835
|
return;
|
|
739
|
-
// Only when pi still declares the npm spec: with nothing to remove, `pi remove` still
|
|
740
|
-
// shells out to npm, which warns on stderr and exits non-zero.
|
|
836
|
+
// Only when pi still declares the npm spec: with nothing to remove, `pi remove` still warns and exits non-zero.
|
|
741
837
|
const declared = await piDeclaredPackages(agentDir);
|
|
742
838
|
if (declared.some(isNpmPonytailPackage)) {
|
|
743
839
|
try {
|
|
@@ -783,8 +879,7 @@ async function registerPiLocalPonytail(agentDir, dest, options) {
|
|
|
783
879
|
function comboHint(host, comboDefault) {
|
|
784
880
|
return comboDefault === 'off' ? `\nDone — restart ${host}.` : `\nDone — restart ${host}, then /combo ${comboDefault}.`;
|
|
785
881
|
}
|
|
786
|
-
async function runInstall() {
|
|
787
|
-
// Without this guard a picked command re-enters here with command === null and loops straight back into the picker.
|
|
882
|
+
async function runInstall(forcedHost) {
|
|
788
883
|
if (command === null && tty() && !yes && !updatePromptDone) {
|
|
789
884
|
await runCommandMenu();
|
|
790
885
|
return;
|
|
@@ -798,7 +893,9 @@ async function runInstall() {
|
|
|
798
893
|
console.error(`[fail] ${e.message}`);
|
|
799
894
|
process.exit(1);
|
|
800
895
|
}
|
|
801
|
-
if (
|
|
896
|
+
if (forcedHost)
|
|
897
|
+
targetHost = forcedHost;
|
|
898
|
+
else if (pinned)
|
|
802
899
|
targetHost = pinned;
|
|
803
900
|
else if (tty() && !yes && !dryRun && !applyUpdate && command !== 'uninstall') {
|
|
804
901
|
targetHost = await chooseHost('Install Tersio into which agent?');
|
|
@@ -816,7 +913,7 @@ async function runInstall() {
|
|
|
816
913
|
if (tty() && !applyUpdate && command !== 'uninstall') {
|
|
817
914
|
const newer = await checkForUpdate();
|
|
818
915
|
if (typeof newer === 'string') {
|
|
819
|
-
// Update pending: offer it here
|
|
916
|
+
// Update pending: offer it here; yes runs the full update and stops.
|
|
820
917
|
if (command === null && !dryRun && !yes && !updatePromptDone) {
|
|
821
918
|
const answer = await askInteractiveConfirm(`tersio ${newer} is available (installed ${PACKAGE_VERSION}). Install it now?`);
|
|
822
919
|
if (answer.status === 'confirmed' && answer.value) {
|
|
@@ -840,7 +937,7 @@ async function runInstall() {
|
|
|
840
937
|
const userDir = OMP_AGENT_DIR;
|
|
841
938
|
const userExtDir = path.join(userDir, 'extensions');
|
|
842
939
|
// apply-update refreshes every add-on like a reinstall.
|
|
843
|
-
const installOptions = { dryRun, verbose, yes, reinstall: applyUpdate, quiet };
|
|
940
|
+
const installOptions = { dryRun, verbose, yes, reinstall: applyUpdate || forceReinstall, quiet };
|
|
844
941
|
// Point of no return: disk writes start here; pipes stay unattended.
|
|
845
942
|
if (!yes && !dryRun && !applyUpdate && tty()) {
|
|
846
943
|
const target = hostEntry(targetHost);
|
|
@@ -905,10 +1002,7 @@ async function runInstall() {
|
|
|
905
1002
|
await capture('self-plugin', async () => { selfPlugin = await stepSelfPlugin(OMP_PLUGINS_DIR, installOptions); });
|
|
906
1003
|
await capture('ponytail', () => stepPonytail(OMP_PLUGINS_DIR, installOptions));
|
|
907
1004
|
await capture('rtk', () => stepRtk(BUN_BIN_DIR, installOptions));
|
|
908
|
-
//
|
|
909
|
-
// every mode extension. A dry run previews that same destination. A registration that
|
|
910
|
-
// failed is the one fallback: the agent tree then carries the tree so an offline install
|
|
911
|
-
// still loads its add-ons, and the retired copies stay untouched.
|
|
1005
|
+
// The plugin manifest owns every mode extension; only a failed registration falls back to the agent tree.
|
|
912
1006
|
const pluginExtDir = path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions');
|
|
913
1007
|
const ownsTree = selfPlugin || dryRun;
|
|
914
1008
|
const extDir = ownsTree ? pluginExtDir : userExtDir;
|
|
@@ -917,10 +1011,17 @@ async function runInstall() {
|
|
|
917
1011
|
await capture('shared', () => stepSharedSessionState(extDir, installOptions));
|
|
918
1012
|
await capture('rtk session', () => stepRtkSession(extDir, installOptions));
|
|
919
1013
|
await capture('rtk filter', () => stepRtkFilter(extDir, installOptions));
|
|
1014
|
+
await capture('timing', () => stepTiming(extDir, installOptions));
|
|
920
1015
|
await capture('caveman', () => stepCaveman(extDir, installOptions));
|
|
921
1016
|
await capture('combo', () => stepCombo(extDir, installOptions));
|
|
1017
|
+
await capture('pstack', () => stepPstack(extDir, installOptions));
|
|
922
1018
|
await capture('commands', () => stepTersioCommands(extDir, installOptions));
|
|
923
1019
|
await capture('updater', () => stepUpdater(extDir, installOptions));
|
|
1020
|
+
// One package, one file set: the repair writes every tree file here, so the install does too.
|
|
1021
|
+
if (ownsTree)
|
|
1022
|
+
await capture('plugin entry', () => stepPluginEntry(extDir, installOptions));
|
|
1023
|
+
if (ownsTree)
|
|
1024
|
+
await capture('plugin manifest', () => syncPluginManifest(path.dirname(pluginExtDir), installOptions));
|
|
924
1025
|
if (ownsTree)
|
|
925
1026
|
await capture('retired agent copies', () => stepRetireAgentCopies(userExtDir, installOptions));
|
|
926
1027
|
if (selfPlugin)
|
|
@@ -934,4 +1035,4 @@ async function runInstall() {
|
|
|
934
1035
|
}
|
|
935
1036
|
closeRL();
|
|
936
1037
|
}
|
|
937
|
-
export { runInstall, verifyRtkArchive };
|
|
1038
|
+
export { runInstall, runReinstall, verifyRtkArchive };
|
package/dist/cli/interactive.js
CHANGED
|
@@ -30,7 +30,7 @@ let spinnerDepth = 0;
|
|
|
30
30
|
async function withInteractiveSpinner(message, work) {
|
|
31
31
|
if (!tty() || spinnerDepth > 0 || process.env.CI === 'true')
|
|
32
32
|
return work(() => { });
|
|
33
|
-
// Clack manages stdin itself. Close the legacy question interface
|
|
33
|
+
// Clack manages stdin itself. Close the legacy question interface first; non-interactive callers skip it.
|
|
34
34
|
closeRL();
|
|
35
35
|
const active = clackSpinner({ indicator: 'timer' });
|
|
36
36
|
active.start(message);
|
|
@@ -73,7 +73,7 @@ async function askInteractiveConfirm(message, initialValue = true) {
|
|
|
73
73
|
finally {
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
// Confirm a destructive run: a Clack dialog at a terminal, a plain prompt in a pipe
|
|
76
|
+
// Confirm a destructive run: a Clack dialog at a terminal, a plain prompt in a pipe. False means declined or aborted.
|
|
77
77
|
async function confirmDestructive(message) {
|
|
78
78
|
if (tty()) {
|
|
79
79
|
closeRL();
|
|
@@ -98,7 +98,7 @@ async function confirmDestructive(message) {
|
|
|
98
98
|
}
|
|
99
99
|
return true;
|
|
100
100
|
}
|
|
101
|
-
// Run collecting work under one TTY-only Clack task
|
|
101
|
+
// Run collecting work under one TTY-only Clack task; callers print after it, so output stays out of the spinner.
|
|
102
102
|
async function runInteractivePhase(title, collect) {
|
|
103
103
|
if (!tty())
|
|
104
104
|
return collect();
|
package/dist/cli/manifest.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
// Single list of install writes, shared by installer, doctor, and tests.
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { findPackageRoot, readTextIfExists } from '../extensions/shared/utils.js';
|
|
5
6
|
import { writeIfChanged } from './common.js';
|
|
6
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();
|
|
7
26
|
/** Every file in the tree, as paths relative to the extensions directory. */
|
|
8
27
|
export const TREE_FILES = [
|
|
9
28
|
'shared/host.ts',
|
|
@@ -12,15 +31,24 @@ export const TREE_FILES = [
|
|
|
12
31
|
'shared/types.ts',
|
|
13
32
|
'shared/plugin-settings.ts',
|
|
14
33
|
'shared/usage-ledger.ts',
|
|
34
|
+
'shared/sqlite.ts',
|
|
15
35
|
'shared/pricing.ts',
|
|
16
36
|
'shared/carbon.ts',
|
|
17
37
|
'shared/utils.ts',
|
|
38
|
+
'shared/rtk-eligible.ts',
|
|
18
39
|
'caveman/index.ts',
|
|
19
40
|
'caveman/rule.md',
|
|
41
|
+
'caveman/rule-lite.md',
|
|
42
|
+
'caveman/rule-megacave-lite.md',
|
|
43
|
+
'caveman/rule-megacave-ultra.md',
|
|
20
44
|
'caveman/rule-ultra.md',
|
|
21
45
|
'caveman/rule-megacave.md',
|
|
46
|
+
'pstack/index.ts',
|
|
47
|
+
'pstack/rule.md',
|
|
48
|
+
...TREE_PSTACK_FILES,
|
|
22
49
|
'rtk/session.ts',
|
|
23
50
|
'rtk/filter.ts',
|
|
51
|
+
'timing/index.ts',
|
|
24
52
|
'combo/index.ts',
|
|
25
53
|
'commands/index.ts',
|
|
26
54
|
'updater/index.ts',
|
|
@@ -42,24 +70,96 @@ export function filesUnder(dir) {
|
|
|
42
70
|
export function rootFiles() {
|
|
43
71
|
return TREE_FILES.filter((file) => !file.includes('/')).map((file) => [sourcePath(file), file]);
|
|
44
72
|
}
|
|
45
|
-
/** The OpenCode plugin entry
|
|
73
|
+
/** The OpenCode plugin entry: imports `@opencode/plugin`, absent from omp and pi loaders, so it ships there alone. */
|
|
46
74
|
export const OPENCODE_ENTRY = 'server.ts';
|
|
75
|
+
/** pi's rtk entry manifest, generated rather than copied: no other host loads rtk that way. */
|
|
76
|
+
export const PI_RTK_MANIFEST = 'rtk/package.json';
|
|
77
|
+
/** pi's rtk entry manifest, generated rather than copied: no other host loads rtk that way. */
|
|
78
|
+
export const PI_RTK_MANIFEST_BODY = `${JSON.stringify({ pi: { extensions: ['./session.ts', './filter.ts'] } }, null, 2)}\n`;
|
|
79
|
+
/** Files the install generates, keyed by tree path, so one repair path handles copied and generated alike. */
|
|
80
|
+
export const GENERATED_TREE_FILES = {
|
|
81
|
+
[PI_RTK_MANIFEST]: PI_RTK_MANIFEST_BODY,
|
|
82
|
+
};
|
|
47
83
|
/** The files one host's extension tree holds. The package tree holds all of them. */
|
|
48
84
|
export function treeFilesFor(id) {
|
|
49
|
-
|
|
85
|
+
if (id === 'opencode')
|
|
86
|
+
return TREE_FILES;
|
|
87
|
+
const files = TREE_FILES.filter((file) => file !== OPENCODE_ENTRY);
|
|
88
|
+
return id === 'pi' ? [...files, PI_RTK_MANIFEST] : files;
|
|
89
|
+
}
|
|
90
|
+
/** Obsolete install debris; explicit, not a sweep, because these trees are shared with tersio's other steps. */
|
|
91
|
+
const OBSOLETE_DIRS = ['caveman-session', 'rtk-filter', 'rtk-session', 'combo-toggle', 'tersio-commands', 'ai-addons-updater', 'lib', 'opencode'];
|
|
92
|
+
/** Debris test for a tree-relative path; one definition shared by detection and repair. */
|
|
93
|
+
export function isObsoleteTreeFile(rel, id) {
|
|
94
|
+
if (rel.endsWith('.bak') || rel === '.DS_Store')
|
|
95
|
+
return true;
|
|
96
|
+
if (OBSOLETE_DIRS.includes(rel.split('/')[0]))
|
|
97
|
+
return true;
|
|
98
|
+
return rel === 'server.ts' && id !== 'opencode';
|
|
99
|
+
}
|
|
100
|
+
/** A tree file's text: copied files come from the repo, generated ones from their own definition. */
|
|
101
|
+
export function treeFileText(file) {
|
|
102
|
+
const generated = GENERATED_TREE_FILES[file];
|
|
103
|
+
if (generated !== undefined)
|
|
104
|
+
return generated;
|
|
105
|
+
try {
|
|
106
|
+
return readFileSync(sourcePath(file), 'utf8');
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
50
111
|
}
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
112
|
+
/** The ponytail ruleset this build ships, copied to every host that cannot install the package. */
|
|
113
|
+
export const PONYTAIL_SKILL_NAMES = ['ponytail', 'ponytail-audit', 'ponytail-debt', 'ponytail-gain', 'ponytail-help', 'ponytail-review'];
|
|
114
|
+
/** The OpenCode bundle: no node_modules there, so the ruleset rides along as files. */
|
|
115
|
+
export const PONYTAIL_BUNDLE_FILES = [
|
|
116
|
+
'hooks/ponytail-instructions.js',
|
|
117
|
+
'hooks/ponytail-config.js',
|
|
118
|
+
...PONYTAIL_SKILL_NAMES.map((skill) => `skills/${skill}/SKILL.md`),
|
|
119
|
+
];
|
|
120
|
+
/** pi's local package: the same ruleset, plus the extension that injects it. */
|
|
121
|
+
export const PI_PONYTAIL_FILES = ['pi-extension/index.js', ...PONYTAIL_BUNDLE_FILES];
|
|
122
|
+
/** The shipped package manifest, one level above the extension tree. */
|
|
123
|
+
const MANIFEST_PATH = path.join(path.dirname(EXT_DIR), 'package.json');
|
|
124
|
+
/** The `omp` block a host reads for what to load: extensions, features, and the settings schema. */
|
|
125
|
+
export function shippedOmpManifest() {
|
|
126
|
+
try {
|
|
127
|
+
const pkg = JSON.parse(readFileSync(MANIFEST_PATH, 'utf8'));
|
|
128
|
+
return typeof pkg.omp === 'object' && pkg.omp !== null ? pkg.omp : null;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
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.
|
|
135
|
+
export async function syncPluginManifest(pluginDir, options = {}) {
|
|
136
|
+
const omp = shippedOmpManifest();
|
|
137
|
+
if (omp === null)
|
|
138
|
+
return;
|
|
139
|
+
const pkgPath = path.join(pluginDir, 'package.json');
|
|
140
|
+
let pkg;
|
|
141
|
+
try {
|
|
142
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (JSON.stringify(pkg.omp) === JSON.stringify(omp))
|
|
148
|
+
return;
|
|
149
|
+
pkg.omp = omp;
|
|
150
|
+
await writeIfChanged(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, options);
|
|
151
|
+
}
|
|
152
|
+
/** Copy bundled files into `dest`; misses are reported and skipped, `skip` keeps newer fetched rule bodies. */
|
|
58
153
|
export async function syncTreeFiles(dest, files, options, skip) {
|
|
59
154
|
const missing = [];
|
|
60
155
|
for (const file of files) {
|
|
61
156
|
if (skip?.(file))
|
|
62
157
|
continue;
|
|
158
|
+
const generated = GENERATED_TREE_FILES[file];
|
|
159
|
+
if (generated !== undefined) {
|
|
160
|
+
await writeIfChanged(path.join(dest, ...file.split('/')), generated, options);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
63
163
|
const from = sourcePath(file);
|
|
64
164
|
const text = await readTextIfExists(from);
|
|
65
165
|
if (text === null) {
|