@krtclcdy/tersio 2.25.1 → 2.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dashboard/dist/index.html +1 -1
- package/{cli → dist/cli}/common.js +4 -3
- package/{cli → dist/cli}/dashboard.js +30 -13
- package/{cli → dist/cli}/doctor-fix.js +19 -30
- package/{cli → dist/cli}/doctor.js +19 -6
- package/dist/cli/headroom-install.js +266 -0
- package/dist/cli/headroom.js +492 -0
- package/{cli → dist/cli}/install.js +116 -71
- package/{cli → dist/cli}/interactive.js +2 -2
- package/{cli → dist/cli}/manifest.js +4 -1
- package/{cli → dist/cli}/rtk-wiring.js +5 -2
- package/{cli → dist/cli}/settings.js +5 -23
- package/{cli → dist/cli}/uninstall.js +23 -11
- package/{cli → dist/cli}/update.js +10 -8
- package/{extensions → dist/extensions}/ai-addons-updater/index.js +60 -44
- package/{extensions → dist/extensions}/caveman-session/index.js +11 -11
- package/dist/extensions/caveman-session/rule-megacave.md +63 -0
- package/dist/extensions/caveman-session/rule-ultra.md +63 -0
- package/dist/extensions/caveman-session/rule.md +79 -0
- package/{extensions → dist/extensions}/combo-toggle/index.js +37 -27
- package/dist/extensions/headroom-session/index.js +93 -0
- package/{extensions → dist/extensions}/lib/utils.js +27 -1
- package/{extensions → dist/extensions}/opencode/server.js +33 -3
- package/dist/extensions/shared/headroom.js +131 -0
- package/{extensions → dist/extensions}/shared/plugin-settings.js +1 -1
- package/{extensions → dist/extensions}/shared/session-state.js +14 -2
- package/{extensions → dist/extensions}/shared/status.js +7 -1
- package/{tersio.js → dist/tersio.js} +1 -1
- package/extensions/ai-addons-updater/index.ts +52 -40
- package/extensions/caveman-session/index.ts +10 -11
- package/extensions/caveman-session/rule-megacave.md +63 -0
- package/extensions/caveman-session/rule-ultra.md +63 -0
- package/extensions/caveman-session/rule.md +4 -15
- package/extensions/combo-toggle/index.ts +35 -27
- package/extensions/lib/utils.ts +23 -1
- package/extensions/opencode/server.ts +29 -3
- package/extensions/shared/plugin-settings.ts +1 -1
- package/extensions/shared/session-state.ts +15 -2
- package/extensions/shared/status.ts +8 -1
- package/package.json +17 -15
- package/extensions/opencode/index.js +0 -104
- /package/{cli → dist/cli}/banner.js +0 -0
- /package/{cli → dist/cli}/currency.js +0 -0
- /package/{cli → dist/cli}/hosts.js +0 -0
- /package/{cli → dist/cli}/profile.js +0 -0
- /package/{cli → dist/cli}/reset.js +0 -0
- /package/{cli → dist/cli}/usage.js +0 -0
- /package/{extensions → dist/extensions}/rtk-session/index.js +0 -0
- /package/{extensions → dist/extensions}/shared/carbon.js +0 -0
- /package/{extensions → dist/extensions}/shared/host.js +0 -0
- /package/{extensions → dist/extensions}/shared/pricing.js +0 -0
- /package/{extensions → dist/extensions}/shared/rtk-gain.js +0 -0
- /package/{extensions → dist/extensions}/shared/types.js +0 -0
- /package/{extensions → dist/extensions}/shared/usage-ledger.js +0 -0
- /package/{extensions → dist/extensions}/shared/usage-store.js +0 -0
- /package/{extensions → dist/extensions}/tersio-commands/index.js +0 -0
|
@@ -13,7 +13,7 @@ import { runReset } from './reset.js';
|
|
|
13
13
|
import { runUsage } from './usage.js';
|
|
14
14
|
import { runDashboard } from './dashboard.js';
|
|
15
15
|
import { wireRtkOmp, wireRtkOpencode, wireRtkPi } from './rtk-wiring.js';
|
|
16
|
-
import {
|
|
16
|
+
import { CAVEMAN_REMOTE_ULTRA, CAVEMAN_REMOTE_MEGACAVE, RTK_RELEASE_API, fetchJson, findFile, findHoistedPackage, httpsGet, findPackageRoot, httpsDownload, parseChecksum, piAgentDir, readTextIfExists, resolveRtkBinary, ruleBodyProblem, rtkPlatformSpec, sha256File, } from '../extensions/lib/utils.js';
|
|
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';
|
|
@@ -356,41 +356,53 @@ async function stepRtkSession(extDir, options) {
|
|
|
356
356
|
console.log(' RTK session — install session mode');
|
|
357
357
|
await copySources(extDir, filesUnder('rtk-session'), 'rtk-session/index.ts', options);
|
|
358
358
|
}
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
catch (e) {
|
|
368
|
-
if (bundled !== null) {
|
|
369
|
-
debug(`Using bundled Caveman rule: ${e.message}`);
|
|
370
|
-
return bundled;
|
|
371
|
-
}
|
|
372
|
-
sayTagged(` [warn] Could not fetch caveman rule: ${e.message}`);
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
async function stepCaveman(extDir, rule, options) {
|
|
359
|
+
// rule.md ships with the package; the other bodies track upstream skills.
|
|
360
|
+
// 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.
|
|
361
|
+
const CAVEMAN_RULES = [
|
|
362
|
+
['rule.md', null],
|
|
363
|
+
['rule-ultra.md', CAVEMAN_REMOTE_ULTRA],
|
|
364
|
+
['rule-megacave.md', CAVEMAN_REMOTE_MEGACAVE],
|
|
365
|
+
];
|
|
366
|
+
async function stepCaveman(extDir, options) {
|
|
377
367
|
if (!options.quiet)
|
|
378
|
-
console.log(' Caveman —
|
|
368
|
+
console.log(' Caveman — install session mode and rule bodies');
|
|
379
369
|
const cavemanDir = path.join(extDir, 'caveman-session');
|
|
380
370
|
if (!options.dryRun)
|
|
381
371
|
await fs.mkdir(cavemanDir, { recursive: true });
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
372
|
+
for (const [name, remote] of CAVEMAN_RULES) {
|
|
373
|
+
const bundled = await readTextIfExists(sourcePath(`caveman-session/${name}`));
|
|
374
|
+
let rule = bundled;
|
|
375
|
+
if (remote && !options.dryRun) {
|
|
376
|
+
try {
|
|
377
|
+
const fetched = await withInteractiveSpinner(`Fetching Caveman ${name}`, () => httpsGet(remote));
|
|
378
|
+
const problem = ruleBodyProblem(fetched);
|
|
379
|
+
if (problem && bundled !== null) {
|
|
380
|
+
sayTagged(` [warn] ${name} fetch rejected (${problem}) — keeping the bundled body`);
|
|
381
|
+
rule = bundled;
|
|
382
|
+
}
|
|
383
|
+
else
|
|
384
|
+
rule = fetched;
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
if (bundled === null)
|
|
388
|
+
sayTagged(` [warn] Could not fetch caveman ${name}: ${e.message}`);
|
|
389
|
+
else
|
|
390
|
+
debug(`Using bundled Caveman ${name}: ${e.message}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (rule === null) {
|
|
394
|
+
if (remote)
|
|
395
|
+
sayTagged(` [hint] Manual: ${remote}`);
|
|
396
|
+
console.log(` [skip] Caveman ${name} unavailable`);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
await writeIfChanged(path.join(cavemanDir, name), rule, options);
|
|
400
|
+
}
|
|
392
401
|
}
|
|
393
402
|
await copySources(extDir, filesUnder('caveman-session').filter(([, to]) => to.endsWith('index.ts')), 'caveman-session/index.ts', options);
|
|
403
|
+
// Renamed body: drop the stale copy an older install left behind.
|
|
404
|
+
if (!options.dryRun)
|
|
405
|
+
await fs.rm(path.join(cavemanDir, 'rule-wenyan.md'), { force: true });
|
|
394
406
|
}
|
|
395
407
|
async function stepTersioCommands(extDir, options) {
|
|
396
408
|
if (!options.quiet)
|
|
@@ -412,7 +424,7 @@ const PONYTAIL_BUNDLE_FILES = [
|
|
|
412
424
|
'skills/ponytail/SKILL.md',
|
|
413
425
|
];
|
|
414
426
|
async function stepPonytailBundle(treeDir, options) {
|
|
415
|
-
const root = findHoistedPackage('@dietrichgebert/ponytail', path.dirname(fileURLToPath(import.meta.url)));
|
|
427
|
+
const root = findHoistedPackage('@dietrichgebert/ponytail', findPackageRoot(path.dirname(fileURLToPath(import.meta.url))));
|
|
416
428
|
if (!root) {
|
|
417
429
|
if (!options.quiet)
|
|
418
430
|
console.log(' [skip] Ponytail package not found next to the CLI — OpenCode keeps the built-in text');
|
|
@@ -459,18 +471,22 @@ async function stepOpencode(options) {
|
|
|
459
471
|
return;
|
|
460
472
|
}
|
|
461
473
|
const plugins = Array.isArray(config.plugins) ? [...config.plugins] : [];
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
474
|
+
// The tree ships the rtk rewrite, so its own plugin entry is redundant.
|
|
475
|
+
const kept = plugins.filter((p) => typeof p !== 'string' || !p.endsWith('/opencode/plugins/rtk.ts'));
|
|
476
|
+
if (!kept.includes(dir))
|
|
477
|
+
kept.push(dir);
|
|
478
|
+
config.plugins = kept;
|
|
479
|
+
if (!options.dryRun) {
|
|
467
480
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
481
|
+
await fs.rm(path.join(HOME, '.config', 'opencode', 'plugins', 'rtk.ts'), { force: true }).catch(() => { });
|
|
482
|
+
}
|
|
468
483
|
await writeIfChanged(configPath, `${JSON.stringify(config, null, 2)}\n`, options);
|
|
469
484
|
}
|
|
470
485
|
async function resolveProfile(opts = {}) {
|
|
471
486
|
// Seed from the stored defaults so flag-less runs keep them.
|
|
472
487
|
const profile = await storedProfile();
|
|
473
|
-
//
|
|
488
|
+
// The chosen preset owns all three modes.
|
|
489
|
+
let comboChosen = comboDefaultFlag !== undefined;
|
|
474
490
|
if (tty() && !profileFlagsGiven && !applyUpdate) {
|
|
475
491
|
const choice = await askInteractiveChoice('Session-start defaults — Combo preset', [
|
|
476
492
|
{ value: 'off', label: 'off' },
|
|
@@ -478,8 +494,10 @@ async function resolveProfile(opts = {}) {
|
|
|
478
494
|
{ value: 'balanced', label: 'balanced', hint: 'caveman=full, rtk=on, ponytail=full' },
|
|
479
495
|
{ value: 'max', label: 'max', hint: 'caveman=ultra, rtk=on, ponytail=ultra' },
|
|
480
496
|
], profile.comboDefault);
|
|
481
|
-
if (choice.status === 'selected')
|
|
497
|
+
if (choice.status === 'selected') {
|
|
482
498
|
profile.comboDefault = choice.value;
|
|
499
|
+
comboChosen = true;
|
|
500
|
+
}
|
|
483
501
|
else {
|
|
484
502
|
closeRL();
|
|
485
503
|
process.exit(130);
|
|
@@ -488,10 +506,13 @@ async function resolveProfile(opts = {}) {
|
|
|
488
506
|
// Explicit flags always win over the Combo preset.
|
|
489
507
|
if (comboDefaultFlag !== undefined)
|
|
490
508
|
profile.comboDefault = comboDefaultFlag;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
509
|
+
// A stored per-mode choice survives installs and updates; only a chosen preset rewrites it.
|
|
510
|
+
if (comboChosen) {
|
|
511
|
+
const preset = COMBO_PRESET_MODES[profile.comboDefault] ?? COMBO_PRESET_MODES.off;
|
|
512
|
+
profile.cavemanDefault = preset.caveman;
|
|
513
|
+
profile.rtkDefault = preset.rtk;
|
|
514
|
+
profile.ponytailDefault = preset.ponytail;
|
|
515
|
+
}
|
|
495
516
|
if (cavemanDefaultFlag !== undefined)
|
|
496
517
|
profile.cavemanDefault = cavemanDefaultFlag;
|
|
497
518
|
if (rtkDefaultFlag !== undefined)
|
|
@@ -506,7 +527,8 @@ let updatePromptDone = false;
|
|
|
506
527
|
// Bare `tersio` at a terminal picks a command; scripts and flags install.
|
|
507
528
|
async function runCommandMenu() {
|
|
508
529
|
printWelcome();
|
|
509
|
-
|
|
530
|
+
// Interactive launches always check; scripts use the cached verdict.
|
|
531
|
+
const newer = await checkForUpdate(true);
|
|
510
532
|
if (typeof newer === 'string' && !dryRun) {
|
|
511
533
|
const answer = await askInteractiveConfirm(`tersio ${newer} is available (installed ${PACKAGE_VERSION}). Install it now?`);
|
|
512
534
|
if (answer.status === 'confirmed' && answer.value) {
|
|
@@ -636,50 +658,74 @@ async function stepPiLayer(options) {
|
|
|
636
658
|
sayTagged(` [note] pi also has a tersio package (${declared}) — remove one, or both copies load: pi remove npm:${PACKAGE_NAME}`);
|
|
637
659
|
if (!options.quiet)
|
|
638
660
|
console.log(` Pi — write the extension tree (${extDir})`);
|
|
639
|
-
const cavemanRule = await fetchCavemanRule(options);
|
|
640
661
|
await stepSharedSessionState(extDir, options);
|
|
641
662
|
// OMP loads rtk-session from its plugin manifest, so only the pi tree copies it.
|
|
642
663
|
await copySources(extDir, filesUnder('rtk-session'), 'rtk-session/index.ts', options);
|
|
643
|
-
await stepCaveman(extDir,
|
|
664
|
+
await stepCaveman(extDir, options);
|
|
644
665
|
await stepCombo(extDir, options);
|
|
645
666
|
await stepTersioCommands(extDir, options);
|
|
646
667
|
await stepUpdater(extDir, options);
|
|
647
668
|
await stepPonytailForPi(agentDir, options);
|
|
648
669
|
}
|
|
649
|
-
//
|
|
670
|
+
// A local dir, not an npm spec: pi only version-checks npm entries, so this avoids the update nag.
|
|
671
|
+
const PI_PONYTAIL_FILES = [
|
|
672
|
+
'pi-extension/index.js',
|
|
673
|
+
'hooks/ponytail-instructions.js',
|
|
674
|
+
'hooks/ponytail-config.js',
|
|
675
|
+
...['ponytail', 'ponytail-audit', 'ponytail-debt', 'ponytail-gain', 'ponytail-help', 'ponytail-review']
|
|
676
|
+
.map((skill) => `skills/${skill}/SKILL.md`),
|
|
677
|
+
];
|
|
678
|
+
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`;
|
|
650
679
|
async function stepPonytailForPi(agentDir, options) {
|
|
651
680
|
if (!options.quiet)
|
|
652
|
-
console.log(' Ponytail —
|
|
653
|
-
|
|
681
|
+
console.log(' Ponytail — write the local pi package');
|
|
682
|
+
const root = findHoistedPackage('@dietrichgebert/ponytail', findPackageRoot(path.dirname(fileURLToPath(import.meta.url))));
|
|
683
|
+
const dest = path.join(agentDir, 'ponytail');
|
|
684
|
+
if (!root) {
|
|
685
|
+
console.log(' [skip] Ponytail package not found next to the CLI');
|
|
686
|
+
console.log(' [hint] The /ponytail command will not work; run: tersio update --host pi');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
await writeIfChanged(path.join(dest, 'package.json'), PI_PONYTAIL_PACKAGE, options);
|
|
690
|
+
for (const rel of PI_PONYTAIL_FILES) {
|
|
691
|
+
let text;
|
|
654
692
|
try {
|
|
655
|
-
await
|
|
656
|
-
return;
|
|
693
|
+
text = await fs.readFile(path.join(root, ...rel.split('/')), 'utf8');
|
|
657
694
|
}
|
|
658
|
-
catch
|
|
659
|
-
sayTagged(` [fail]
|
|
695
|
+
catch {
|
|
696
|
+
sayTagged(` [fail] Ponytail file missing: ${rel}`);
|
|
697
|
+
return;
|
|
660
698
|
}
|
|
699
|
+
await writeIfChanged(path.join(dest, ...rel.split('/')), text, options);
|
|
661
700
|
}
|
|
662
|
-
|
|
663
|
-
if (!skills) {
|
|
664
|
-
console.log(' [skip] Ponytail package not found next to the CLI — skills not copied');
|
|
665
|
-
console.log(' [hint] The /ponytail command will not work; run: pi install npm:@dietrichgebert/ponytail');
|
|
701
|
+
if (options.dryRun)
|
|
666
702
|
return;
|
|
703
|
+
try {
|
|
704
|
+
await execNetwork('Removing the pi npm copy of Ponytail', 'pi', ['remove', 'npm:@dietrichgebert/ponytail'], { timeout: 120000 });
|
|
667
705
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (verbose && !options.quiet)
|
|
671
|
-
sayTagged(` [dry-run] would copy Ponytail skills to ${dest}`);
|
|
672
|
-
return;
|
|
706
|
+
catch (e) {
|
|
707
|
+
sayTagged(` [warn] pi remove ponytail: ${shortError(e)}`);
|
|
673
708
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
709
|
+
await registerPiLocalPonytail(agentDir, dest, options);
|
|
710
|
+
}
|
|
711
|
+
// pi lists package sources in settings; swap the npm spec for the local dir.
|
|
712
|
+
async function registerPiLocalPonytail(agentDir, dest, options) {
|
|
713
|
+
const settingsPath = path.join(agentDir, 'settings.json');
|
|
714
|
+
let settings;
|
|
715
|
+
try {
|
|
716
|
+
settings = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return;
|
|
682
720
|
}
|
|
721
|
+
const packages = (Array.isArray(settings.packages) ? settings.packages : []);
|
|
722
|
+
const kept = packages.filter((p) => typeof p !== 'string' || !p.startsWith('npm:@dietrichgebert/ponytail'));
|
|
723
|
+
if (!kept.includes(dest))
|
|
724
|
+
kept.push(dest);
|
|
725
|
+
settings.packages = kept;
|
|
726
|
+
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
|
|
727
|
+
if (!options.quiet)
|
|
728
|
+
sayTagged(` [write] pi package source → ${dest}`);
|
|
683
729
|
}
|
|
684
730
|
async function runInstall() {
|
|
685
731
|
// Without this guard a picked command re-enters here with command === null and loops straight back into the picker.
|
|
@@ -799,14 +845,13 @@ async function runInstall() {
|
|
|
799
845
|
closeRL();
|
|
800
846
|
return;
|
|
801
847
|
}
|
|
802
|
-
const cavemanRule = await fetchCavemanRule(installOptions);
|
|
803
848
|
await capture('shared', () => stepSharedSessionState(userExtDir, installOptions));
|
|
804
849
|
let selfPlugin = false;
|
|
805
850
|
await capture('self-plugin', async () => { selfPlugin = await stepSelfPlugin(OMP_PLUGINS_DIR, installOptions); });
|
|
806
851
|
await capture('ponytail', () => stepPonytail(OMP_PLUGINS_DIR, installOptions));
|
|
807
852
|
await capture('rtk', () => stepRtk(BUN_BIN_DIR, installOptions));
|
|
808
853
|
await capture('rtk session', () => stepRtkSession(userExtDir, installOptions));
|
|
809
|
-
await capture('caveman', () => stepCaveman(path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions'),
|
|
854
|
+
await capture('caveman', () => stepCaveman(path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions'), installOptions));
|
|
810
855
|
await capture('combo', () => stepCombo(userExtDir, installOptions));
|
|
811
856
|
await capture('commands', () => stepTersioCommands(userExtDir, installOptions));
|
|
812
857
|
await capture('updater', () => stepUpdater(userExtDir, installOptions));
|
|
@@ -26,9 +26,9 @@ function tty() {
|
|
|
26
26
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
27
27
|
}
|
|
28
28
|
let spinnerDepth = 0;
|
|
29
|
-
//
|
|
29
|
+
// Clack's spinner stop writes an extra newline under CI, shifting later output by a row.
|
|
30
30
|
async function withInteractiveSpinner(message, work) {
|
|
31
|
-
if (!tty() || spinnerDepth > 0)
|
|
31
|
+
if (!tty() || spinnerDepth > 0 || process.env.CI === 'true')
|
|
32
32
|
return work(() => { });
|
|
33
33
|
// Clack manages stdin itself. Close the legacy question interface before its first use; non-interactive callers never reach this branch.
|
|
34
34
|
closeRL();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Single list of install writes, shared by installer, doctor, and tests.
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
4
|
+
import { findPackageRoot } from '../extensions/lib/utils.js';
|
|
5
|
+
export const EXT_DIR = path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), 'extensions');
|
|
5
6
|
/** Root entrypoint OpenCode resolves for the installed tree; re-exports the versioned entry beside it. Built by join so the path never reads as a relative import (repo-integrity scans literals). */
|
|
6
7
|
const SERVER_SPEC = `./${['opencode', 'server.ts'].join('/')}`;
|
|
7
8
|
export const OPENCODE_SERVER_SHIM = `// Generated by \`tersio install --host opencode\`: OpenCode resolves <pluginDir>/server.ts.
|
|
@@ -20,6 +21,8 @@ export const TREE_FILES = [
|
|
|
20
21
|
'lib/utils.ts',
|
|
21
22
|
'caveman-session/index.ts',
|
|
22
23
|
'caveman-session/rule.md',
|
|
24
|
+
'caveman-session/rule-ultra.md',
|
|
25
|
+
'caveman-session/rule-megacave.md',
|
|
23
26
|
'rtk-session/index.ts',
|
|
24
27
|
'combo-toggle/index.ts',
|
|
25
28
|
'tersio-commands/index.ts',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// OMP loads only listed extensions, so register rtk's path; fail-open.
|
|
2
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
+
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';
|
|
@@ -47,8 +47,11 @@ export async function wireRtkOmp(rtkBin, options = {}) {
|
|
|
47
47
|
await ensureRtkInConfig(options);
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
50
|
+
// The Tersio plugin does the same rewrite, so OpenCode needs no rtk plugin of its own.
|
|
50
51
|
export async function wireRtkOpencode(rtkBin, options = {}) {
|
|
51
|
-
|
|
52
|
+
if (!options.dryRun && !options.quiet)
|
|
53
|
+
console.log(' RTK rewrite runs inside the Tersio plugin — no separate entry');
|
|
54
|
+
return existsSync(rtkBin);
|
|
52
55
|
}
|
|
53
56
|
export async function wireRtkPi(rtkBin, options = {}) {
|
|
54
57
|
return runWire(rtkBin, { host: 'pi', mechanism: 'bash tool_call', initArgs: ['init', '-g', '--agent', 'pi'], hint: 'rtk init -g --agent pi' }, options);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/settings.ts — view/edit session-start defaults and the display currency.
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, PONYTAIL_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, diagScheduleFlag, dryRun, ponytailDefaultFlag, profileFlagsGiven, rtkDefaultFlag, settingArg, subagentMarkerFlags, subagentMarkersGiven, } from './common.js';
|
|
3
|
+
import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
|
|
4
4
|
import { CURRENCY_CODES } from './currency.js';
|
|
5
5
|
import { textTable } from './usage.js';
|
|
6
6
|
import { ask, askInteractiveChoice, closeRL, tty } from './interactive.js';
|
|
@@ -45,25 +45,6 @@ async function askProfile(current) {
|
|
|
45
45
|
backupSchedule: current.backupSchedule,
|
|
46
46
|
subagentMarkers: current.subagentMarkers,
|
|
47
47
|
};
|
|
48
|
-
const caveman = await askInteractiveChoice('Caveman default', [...CAVEMAN_DEFAULTS].map((v) => ({
|
|
49
|
-
value: v, label: v,
|
|
50
|
-
})), next.cavemanDefault);
|
|
51
|
-
if (caveman.status !== 'selected')
|
|
52
|
-
return null;
|
|
53
|
-
next.cavemanDefault = caveman.value;
|
|
54
|
-
const rtk = await askInteractiveChoice('RTK default', [
|
|
55
|
-
{ value: 'on', label: 'on' },
|
|
56
|
-
{ value: 'off', label: 'off' },
|
|
57
|
-
], next.rtkDefault ? 'on' : 'off');
|
|
58
|
-
if (rtk.status !== 'selected')
|
|
59
|
-
return null;
|
|
60
|
-
next.rtkDefault = rtk.value === 'on';
|
|
61
|
-
const ponytail = await askInteractiveChoice('Ponytail default', [...PONYTAIL_DEFAULTS].map((v) => ({
|
|
62
|
-
value: v, label: v,
|
|
63
|
-
})), next.ponytailDefault);
|
|
64
|
-
if (ponytail.status !== 'selected')
|
|
65
|
-
return null;
|
|
66
|
-
next.ponytailDefault = ponytail.value;
|
|
67
48
|
const cur = await askInteractiveChoice('Display currency (usage/dashboard reports)', CURRENCY_CODES.map((v) => ({
|
|
68
49
|
value: v, label: v,
|
|
69
50
|
})), next.currency);
|
|
@@ -103,7 +84,7 @@ async function askDiagSchedule(current) {
|
|
|
103
84
|
}
|
|
104
85
|
const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'currency', 'diagnosis', 'markers'];
|
|
105
86
|
function settingsUsage() {
|
|
106
|
-
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|
|
|
87
|
+
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|megacave-lite|megacave-full|megacave-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--subagent-marker <text>]... [--dry-run]');
|
|
107
88
|
}
|
|
108
89
|
// Single-setting jump: `tersio settings diagnosis` prompts only that value instead of walking the whole chain. Returns false when aborted.
|
|
109
90
|
async function runSingleSetting(name, current, nextDiag) {
|
|
@@ -198,7 +179,8 @@ function printSettingsTable(current) {
|
|
|
198
179
|
for (const l of textTable(['Setting', 'Current', 'Valid values'], rows, [false, false, false], 64)) {
|
|
199
180
|
console.log(l);
|
|
200
181
|
}
|
|
201
|
-
console.log(` Stored: ${
|
|
182
|
+
console.log(` Stored: ${tersioSettingsFile()}`);
|
|
183
|
+
console.log(` Combo ${current.comboDefault} sets caveman/rtk/ponytail. Tune one: tersio settings <name>`);
|
|
202
184
|
}
|
|
203
185
|
async function runSettings() {
|
|
204
186
|
const current = await storedProfile();
|
|
@@ -4,8 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { BUN_BIN_DIR, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, RTK_BINARY_NAME, args, dryRun, keepPonytail, removePonytail, removeRtk, yes, debug, writeConfigLines, } from './common.js';
|
|
5
5
|
import { askInteractiveChoice, closeRL, confirmDestructive, tty, sayTagged } from './interactive.js';
|
|
6
6
|
import { detectHosts, hostHint, hostLabel, parseHostArg } from './hosts.js';
|
|
7
|
-
import {
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { piAgentDir, readTextIfExists } from '../extensions/lib/utils.js';
|
|
9
8
|
import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
|
|
10
9
|
// The pi tree mirrors the OMP one. Ponytail and rtk stay: a package and a shared binary are not ours to delete.
|
|
11
10
|
const PONYTAIL_PKG = '@dietrichgebert/ponytail';
|
|
@@ -42,20 +41,30 @@ async function piRemove(spec, shouldDryRun) {
|
|
|
42
41
|
sayTagged(` [hint] Manual: pi remove ${spec}`);
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
|
-
//
|
|
44
|
+
// The installer writes a local package dir; drop it and its settings entry.
|
|
46
45
|
async function removeCopiedPonytailSkills(shouldDryRun) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
46
|
+
const agentDir = piAgentDir();
|
|
47
|
+
const settingsPath = path.join(agentDir, 'settings.json');
|
|
48
|
+
try {
|
|
49
|
+
const settings = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
|
|
50
|
+
if (!Array.isArray(settings.packages))
|
|
51
|
+
return;
|
|
52
|
+
const kept = settings.packages.filter((p) => p !== path.join(agentDir, 'ponytail'));
|
|
53
|
+
if (kept.length === settings.packages.length)
|
|
54
|
+
return;
|
|
55
|
+
if (shouldDryRun)
|
|
56
|
+
sayTagged(` [dry-run] would drop the local ponytail entry from ${settingsPath}`);
|
|
57
|
+
else
|
|
58
|
+
await fs.writeFile(settingsPath, `${JSON.stringify({ ...settings, packages: kept }, null, 2)}\n`, 'utf8');
|
|
53
59
|
}
|
|
60
|
+
catch { /* no settings file, nothing to clean */ }
|
|
61
|
+
await removeUninstallTarget(path.join(agentDir, 'ponytail'), shouldDryRun);
|
|
54
62
|
}
|
|
55
63
|
// pi removal delegates to pi remove; always list and confirm first.
|
|
56
64
|
async function removePiLayer(host, shouldDryRun, shouldRemovePonytail, shouldRemoveRtk, confirmed) {
|
|
57
65
|
const targets = PI_TREE_DIRS.map((dir) => path.join(piAgentDir(), 'extensions', dir));
|
|
58
66
|
const rtkWiring = path.join(piAgentDir(), 'extensions', 'rtk.ts');
|
|
67
|
+
const piPonytail = path.join(piAgentDir(), 'ponytail');
|
|
59
68
|
if (!host.installed && !host.declared) {
|
|
60
69
|
sayTagged(` [skip] ${host.label} — nothing installed (${host.installCmd})`);
|
|
61
70
|
return false;
|
|
@@ -63,8 +72,10 @@ async function removePiLayer(host, shouldDryRun, shouldRemovePonytail, shouldRem
|
|
|
63
72
|
console.log('Will remove:');
|
|
64
73
|
for (const t of targets)
|
|
65
74
|
console.log(` ${t}`);
|
|
66
|
-
if (shouldRemovePonytail)
|
|
75
|
+
if (shouldRemovePonytail) {
|
|
67
76
|
console.log(` npm:${PONYTAIL_PKG} (pi package)`);
|
|
77
|
+
console.log(` ${piPonytail} (local Ponytail package)`);
|
|
78
|
+
}
|
|
68
79
|
if (shouldRemoveRtk)
|
|
69
80
|
console.log(` ${rtkWiring} (rtk wiring)`);
|
|
70
81
|
console.log(` ${tersioSettingsFile()} (session defaults)`);
|
|
@@ -278,7 +289,8 @@ async function runUninstall(options = {}) {
|
|
|
278
289
|
}
|
|
279
290
|
}
|
|
280
291
|
// Cleared last: writeConfigLines above would just make a fresh one.
|
|
281
|
-
|
|
292
|
+
const cavemanDir = path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'extensions', 'caveman-session');
|
|
293
|
+
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')]) {
|
|
282
294
|
await removeUninstallTarget(backup, shouldDryRun, false);
|
|
283
295
|
}
|
|
284
296
|
// Remove the bundled Ponytail copy; its config.yml entry is already filtered.
|
|
@@ -6,7 +6,7 @@ import { IS_WINDOWS, OMP_PLUGINS_DIR, PACKAGE_BIN, PACKAGE_NAME, PACKAGE_VERSION
|
|
|
6
6
|
import { execNetwork, sayTagged } from './interactive.js';
|
|
7
7
|
import { readTextIfExists, resolveRtkBinary, tersioDataPath } from '../extensions/lib/utils.js';
|
|
8
8
|
import { refreshPrices } from '../extensions/shared/pricing.js';
|
|
9
|
-
import {
|
|
9
|
+
import { CAVEMAN_REMOTE_ULTRA, CAVEMAN_REMOTE_MEGACAVE, RTK_RELEASE_API, httpsGet, normalizeRtkVersion, sha256Hex, } from '../extensions/lib/utils.js';
|
|
10
10
|
import { parseHostArg } from './hosts.js';
|
|
11
11
|
const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
|
|
12
12
|
function newerThan(a, b) {
|
|
@@ -78,20 +78,23 @@ async function execInherit(cmd, args) {
|
|
|
78
78
|
async function probeUpdatePlan(cliLatest) {
|
|
79
79
|
const rtkBin = resolveRtkBinary();
|
|
80
80
|
const ponytailPkg = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
81
|
-
|
|
81
|
+
// rule.md ships with the release; only the tracked bodies can go stale.
|
|
82
|
+
const tersioRule = (name) => path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions', 'caveman-session', name);
|
|
82
83
|
const controller = new AbortController();
|
|
83
|
-
const [rtkLocal, rtkRelease,
|
|
84
|
+
const [rtkLocal, rtkRelease, ruleLocal, ruleRemote, ponytailLocalText] = await Promise.all([
|
|
84
85
|
settle(rtkBin ? execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => normalizeRtkVersion(r.stdout.trim() || r.stderr.trim()) || null) : Promise.resolve(null), 6000),
|
|
85
86
|
settle(httpsGet(RTK_RELEASE_API, { signal: controller.signal }).then((text) => normalizeRtkVersion(JSON.parse(text).tag_name) || null), 1500),
|
|
86
|
-
readTextIfExists(tersioRule),
|
|
87
|
-
|
|
87
|
+
Promise.all([readTextIfExists(tersioRule('rule-ultra.md')), readTextIfExists(tersioRule('rule-megacave.md'))])
|
|
88
|
+
.then((texts) => (texts.every(Boolean) ? texts.map((t) => sha256Hex(t).slice(0, 8)).join('+') : null)),
|
|
89
|
+
settle(Promise.all([httpsGet(CAVEMAN_REMOTE_ULTRA, { signal: controller.signal }), httpsGet(CAVEMAN_REMOTE_MEGACAVE, { signal: controller.signal })])
|
|
90
|
+
.then((texts) => texts.map((t) => sha256Hex(t).slice(0, 8)).join('+')), 1500),
|
|
88
91
|
readTextIfExists(ponytailPkg),
|
|
89
92
|
]);
|
|
90
93
|
controller.abort();
|
|
91
94
|
return {
|
|
92
95
|
cli: cliLatest,
|
|
93
96
|
rtk: [rtkLocal, rtkRelease],
|
|
94
|
-
rule: [
|
|
97
|
+
rule: [ruleLocal, ruleRemote],
|
|
95
98
|
ponytail: ponytailLocalText ? parseJsonObject(ponytailLocalText)?.version ?? null : null,
|
|
96
99
|
};
|
|
97
100
|
}
|
|
@@ -211,8 +214,7 @@ async function runLatestUpdate() {
|
|
|
211
214
|
// Inherited stdio, no outer spinner; the delegated installer runs quiet and this parent owns both the plan line and the closing summary.
|
|
212
215
|
try {
|
|
213
216
|
await execInherit(npmCommand, npmCommandArgs);
|
|
214
|
-
|
|
215
|
-
console.log(`Done — tersio ${plan.cli ?? PACKAGE_VERSION}. Restart ${hostName}.`);
|
|
217
|
+
console.log(`Done — tersio ${plan.cli ?? PACKAGE_VERSION} and add-ons updated.`);
|
|
216
218
|
if (!dryRun) {
|
|
217
219
|
try {
|
|
218
220
|
await refreshPrices();
|