@krtclcdy/tersio 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +1 -1
- package/tersio.js +185 -53
package/README.md
CHANGED
|
@@ -11,6 +11,12 @@ Terse replies, compact shell output, and minimal code decisions for [Oh My Pi (O
|
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
+
Quick install (macOS/Linux/WSL — checks npm, then bootstraps the CLI):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
curl -fsSL https://raw.githubusercontent.com/KurutoDenzeru/tersio/main/install.sh | sh
|
|
18
|
+
```
|
|
19
|
+
|
|
14
20
|
OMP plugin (recommended — updates and feature flags handled by OMP):
|
|
15
21
|
|
|
16
22
|
```bash
|
|
@@ -79,7 +85,7 @@ Break-even math, balanced preset (caveman full + rtk + ponytail): `⌈overhead
|
|
|
79
85
|
| Command | Purpose |
|
|
80
86
|
|---|---|
|
|
81
87
|
| `tersio install` | Install (user scope by default; `--scope project\|both` for more) |
|
|
82
|
-
| `tersio update` | Refresh
|
|
88
|
+
| `tersio update` | Refresh the CLI, extensions, and add-ons (RTK binary, Caveman rule, Ponytail) |
|
|
83
89
|
| `tersio reinstall` | Fresh install, preserving the Ponytail package |
|
|
84
90
|
| `tersio doctor` | Check OMP, extension, Ponytail, and RTK health |
|
|
85
91
|
| `tersio uninstall` | Remove extensions, registration, and the Ponytail plugin (`--keep-ponytail` keeps Ponytail; `--remove-rtk` also removes the RTK binary) |
|
package/package.json
CHANGED
package/tersio.js
CHANGED
|
@@ -84,7 +84,7 @@ function printHelp() {
|
|
|
84
84
|
|
|
85
85
|
Commands:
|
|
86
86
|
install Install the add-ons (user scope by default)
|
|
87
|
-
update
|
|
87
|
+
update Refresh the CLI and add-ons (RTK binary, Caveman rule, Ponytail)
|
|
88
88
|
reinstall Clean and reinstall the user-scope add-ons
|
|
89
89
|
doctor Check the current installation
|
|
90
90
|
uninstall Remove the managed extensions
|
|
@@ -661,17 +661,8 @@ async function stepCombo(extDir, options) {
|
|
|
661
661
|
await ensureExtensionInConfig(configPath, dest, 'combo', options);
|
|
662
662
|
}
|
|
663
663
|
// --- Doctor ---
|
|
664
|
-
function presence(label, text) {
|
|
665
|
-
console.log(` ${label}: ${text !== null ? 'installed' : 'MISSING'}`);
|
|
666
|
-
}
|
|
667
|
-
function locationLine(label, ok, loc) {
|
|
668
|
-
console.log(` ${label}: ${ok ? 'ok' : 'MISSING'} ${loc}`);
|
|
669
|
-
}
|
|
670
664
|
async function runDoctor() {
|
|
671
|
-
console.log('\n=== Tersio Doctor
|
|
672
|
-
// Node
|
|
673
|
-
console.log(` Node: ok ${process.version}`);
|
|
674
|
-
// OMP CLI version joins FS probes below; logs keep fixed order.
|
|
665
|
+
console.log('\n=== Tersio Doctor ===');
|
|
675
666
|
// Directories
|
|
676
667
|
const agentDir = OMP_AGENT_DIR;
|
|
677
668
|
const extDir = path.join(agentDir, 'extensions');
|
|
@@ -689,7 +680,7 @@ async function runDoctor() {
|
|
|
689
680
|
const modeReinforcement = path.join(extDir, 'shared', 'mode-reinforcement.js');
|
|
690
681
|
const selfPkg = path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
691
682
|
// Independent probes run concurrently; logs below keep fixed order.
|
|
692
|
-
const [ompVersion, agentEntries, extEntries, sharedStateText, configText, ponytailPkgText, ponytailExtText, pluginsPkgRaw, selfPkgText, rtkBinText, cavemanIndexText, cavemanRuleText, rtkIndexText, updaterIndexText, comboIndexText, modeReinforcementText,] = await Promise.all([
|
|
683
|
+
const [ompVersion, agentEntries, extEntries, sharedStateText, configText, ponytailPkgText, ponytailExtText, pluginsPkgRaw, selfPkgText, rtkBinText, cavemanIndexText, cavemanRuleText, rtkIndexText, updaterIndexText, comboIndexText, modeReinforcementText, ruleMtime, rtkMtime, ponytailMtime, updateVersion,] = await Promise.all([
|
|
693
684
|
execP(OMP_BIN, ['--version']).then((r) => r.stdout.trim(), () => null),
|
|
694
685
|
fs.readdir(agentDir).catch(() => null),
|
|
695
686
|
fs.readdir(extDir).catch(() => null),
|
|
@@ -706,49 +697,79 @@ async function runDoctor() {
|
|
|
706
697
|
readTextIfExists(updaterIndex),
|
|
707
698
|
readTextIfExists(comboIndex),
|
|
708
699
|
readTextIfExists(modeReinforcement),
|
|
700
|
+
fs.stat(cavemanRule).catch(() => null),
|
|
701
|
+
fs.stat(rtkBin).catch(() => null),
|
|
702
|
+
fs.stat(ponytailPkg).catch(() => null),
|
|
703
|
+
checkForUpdate(),
|
|
709
704
|
]);
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
presence('Ponytail package', ponytailPkgText);
|
|
717
|
-
presence('Ponytail extension', ponytailExtText);
|
|
718
|
-
if (configText) {
|
|
719
|
-
const hasPonytailPath = configText.includes('ponytail') && configText.includes('pi-extension');
|
|
720
|
-
console.log(` Ponytail in config.yml: ${hasPonytailPath ? 'registered' : 'MISSING'}`);
|
|
721
|
-
}
|
|
722
|
-
// Self plugin registration (Settings → Plugins listing)
|
|
723
|
-
const selfDep = PACKAGE_NAME in (parseJsonObject(pluginsPkgRaw)?.dependencies || {});
|
|
724
|
-
console.log(` Self plugin in plugins/package.json: ${selfDep ? 'registered' : 'MISSING'}`);
|
|
725
|
-
console.log(` Self plugin package: ${selfPkgText !== null ? 'installed' : 'MISSING'}`);
|
|
726
|
-
// RTK
|
|
727
|
-
const rtkExists = rtkBinText !== null;
|
|
728
|
-
console.log(` RTK binary: ${rtkExists ? 'installed' : 'MISSING'} ${rtkBin}`);
|
|
729
|
-
if (rtkExists) {
|
|
730
|
-
try {
|
|
731
|
-
const v = (await execP(rtkBin, ['--version'], { timeout: 5000 })).stdout.trim();
|
|
732
|
-
console.log(` RTK version: ${v}`);
|
|
705
|
+
// Categorized output with a tally; section headers group related probes.
|
|
706
|
+
const tally = { ok: 0, missing: 0, warn: 0 };
|
|
707
|
+
function check(label, ok, detail = '') {
|
|
708
|
+
if (ok) {
|
|
709
|
+
tally.ok++;
|
|
710
|
+
console.log(` ${label}: ok${detail ? ` ${detail}` : ''}`);
|
|
733
711
|
}
|
|
734
|
-
|
|
735
|
-
|
|
712
|
+
else {
|
|
713
|
+
tally.missing++;
|
|
714
|
+
console.log(` ${label}: MISSING${detail ? ` ${detail}` : ''}`);
|
|
736
715
|
}
|
|
737
716
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
717
|
+
function warnLine(label, detail) {
|
|
718
|
+
tally.warn++;
|
|
719
|
+
console.log(` ${label}: warn ${detail}`);
|
|
720
|
+
}
|
|
721
|
+
section('Environment');
|
|
722
|
+
check('Node', true, process.version);
|
|
723
|
+
check('OMP CLI', ompVersion !== null, ompVersion ?? '');
|
|
724
|
+
if (updateVersion)
|
|
725
|
+
warnLine('Tersio CLI', `${updateVersion} available — run tersio update`);
|
|
726
|
+
else
|
|
727
|
+
check('Tersio CLI', true, PACKAGE_VERSION);
|
|
728
|
+
console.log(` Home: ${HOME}`);
|
|
729
|
+
section('Installation');
|
|
730
|
+
check('OMP agent dir', agentEntries !== null, agentDir);
|
|
731
|
+
check('OMP extensions dir', extEntries !== null, extDir);
|
|
732
|
+
check('OMP config.yml', configText !== null, configPath);
|
|
733
|
+
check('Shared session bridge', sharedStateText !== null);
|
|
734
|
+
section('Extensions');
|
|
735
|
+
check('Caveman extension', cavemanIndexText !== null);
|
|
736
|
+
check('RTK extension', rtkIndexText !== null);
|
|
737
|
+
check('Updater extension', updaterIndexText !== null);
|
|
738
|
+
check('Combo extension', comboIndexText !== null);
|
|
739
|
+
check('Mode reinforcement extension', modeReinforcementText !== null);
|
|
740
|
+
section('Plugins');
|
|
741
|
+
check('Ponytail package', ponytailPkgText !== null);
|
|
742
|
+
check('Ponytail extension', ponytailExtText !== null);
|
|
748
743
|
if (configText) {
|
|
749
|
-
|
|
750
|
-
|
|
744
|
+
check('Ponytail in config.yml', configText.includes('ponytail') && configText.includes('pi-extension'));
|
|
745
|
+
check('Combo in config.yml', configText.includes('combo-toggle'));
|
|
751
746
|
}
|
|
747
|
+
check('Self plugin package', selfPkgText !== null, parseJsonObject(selfPkgText)?.version ?? '');
|
|
748
|
+
const selfDep = PACKAGE_NAME in (parseJsonObject(pluginsPkgRaw)?.dependencies || {});
|
|
749
|
+
check('Self plugin in plugins/package.json', selfDep);
|
|
750
|
+
section('Add-ons');
|
|
751
|
+
const ruleAge = ruleMtime ? `updated ${relTime(Date.now() - ruleMtime.mtimeMs)}` : '';
|
|
752
|
+
check('Caveman rule', cavemanRuleText !== null, ruleAge);
|
|
753
|
+
const rtkVersion = rtkBinText === null ? null : await execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => r.stdout.trim() || r.stderr.trim() || null, (e) => {
|
|
754
|
+
const err = e;
|
|
755
|
+
return err.stdout?.trim() || err.stderr?.trim() || null;
|
|
756
|
+
});
|
|
757
|
+
const rtkAge = rtkMtime ? `updated ${relTime(Date.now() - rtkMtime.mtimeMs)}` : '';
|
|
758
|
+
if (rtkBinText === null)
|
|
759
|
+
check('RTK binary', false, rtkBin);
|
|
760
|
+
else {
|
|
761
|
+
const bits = [rtkVersion, rtkAge].filter(Boolean).join(', ');
|
|
762
|
+
check('RTK binary', true, bits);
|
|
763
|
+
if (!rtkVersion)
|
|
764
|
+
warnLine('RTK version', 'unavailable — binary may not be executable');
|
|
765
|
+
}
|
|
766
|
+
const ponytailAge = ponytailMtime ? `updated ${relTime(Date.now() - ponytailMtime.mtimeMs)}` : '';
|
|
767
|
+
check('Ponytail', ponytailPkgText !== null, [parseJsonObject(ponytailPkgText)?.version ?? '', ponytailAge].filter(Boolean).join(', '));
|
|
768
|
+
const total = tally.ok + tally.missing + tally.warn;
|
|
769
|
+
console.log(`\n Summary: ${total} checks — ${tally.ok} ok, ${tally.warn} warn, ${tally.missing} missing`);
|
|
770
|
+
}
|
|
771
|
+
function section(name) {
|
|
772
|
+
console.log(`\n${name}`);
|
|
752
773
|
}
|
|
753
774
|
// Read-modify-write a JSON file. mutate returns true when it changed
|
|
754
775
|
// something; no change (or unreadable file) means no output at all.
|
|
@@ -897,6 +918,67 @@ async function runUninstall(options = {}) {
|
|
|
897
918
|
return true;
|
|
898
919
|
}
|
|
899
920
|
const SCOPE_MAP = { user: '1', project: '2', both: '3' };
|
|
921
|
+
const COMBO_MENU = { '1': 'off', '2': 'medium', '3': 'balanced', '4': 'max' };
|
|
922
|
+
// --- Update check (cached; used by the bare-invocation banner and doctor) ---
|
|
923
|
+
const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
|
|
924
|
+
function newerThan(a, b) {
|
|
925
|
+
const pa = a.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
|
|
926
|
+
const pb = b.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
|
|
927
|
+
for (let i = 0; i < 3; i++) {
|
|
928
|
+
if ((pa[i] || 0) !== (pb[i] || 0))
|
|
929
|
+
return (pa[i] || 0) > (pb[i] || 0);
|
|
930
|
+
}
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
async function latestPublishedVersion() {
|
|
934
|
+
try {
|
|
935
|
+
const args = IS_WINDOWS
|
|
936
|
+
? ['/d', '/s', '/c', 'npm', 'view', PACKAGE_NAME, 'version', '--prefer-online']
|
|
937
|
+
: ['view', PACKAGE_NAME, 'version', '--prefer-online'];
|
|
938
|
+
const r = await execP(IS_WINDOWS ? process.env.ComSpec || 'cmd.exe' : 'npm', args, {
|
|
939
|
+
timeout: 4000,
|
|
940
|
+
maxBuffer: 1024 * 1024,
|
|
941
|
+
windowsHide: true,
|
|
942
|
+
shell: false,
|
|
943
|
+
});
|
|
944
|
+
return r.stdout.trim() || null;
|
|
945
|
+
}
|
|
946
|
+
catch {
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
// Returns a newer published version, or null. Reads the registry at most once
|
|
951
|
+
// per TTL (cached under OMP_PLUGINS_DIR) and stays silent on failure.
|
|
952
|
+
async function checkForUpdate() {
|
|
953
|
+
const cachePath = path.join(OMP_PLUGINS_DIR, 'tersio-update-check.json');
|
|
954
|
+
const cached = parseJsonObject(await readTextIfExists(cachePath));
|
|
955
|
+
const cacheFresh = typeof cached?.lastCheck === 'number' && Date.now() - cached.lastCheck < UPDATE_CHECK_TTL_MS;
|
|
956
|
+
if (cacheFresh && cached?.latest)
|
|
957
|
+
return newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : null;
|
|
958
|
+
const latest = await latestPublishedVersion();
|
|
959
|
+
if (latest) {
|
|
960
|
+
await fs.mkdir(OMP_PLUGINS_DIR, { recursive: true }).catch(() => { });
|
|
961
|
+
await fs.writeFile(cachePath, JSON.stringify({ latest, lastCheck: Date.now() }) + '\n', 'utf8').catch(() => { });
|
|
962
|
+
return newerThan(latest, PACKAGE_VERSION) ? latest : null;
|
|
963
|
+
}
|
|
964
|
+
// Registry unreachable: fall back to a stale cache rather than staying silent.
|
|
965
|
+
return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : null;
|
|
966
|
+
}
|
|
967
|
+
function relTime(ageMs) {
|
|
968
|
+
const mins = Math.floor(ageMs / 60000);
|
|
969
|
+
if (mins < 1)
|
|
970
|
+
return 'just now';
|
|
971
|
+
if (mins < 60)
|
|
972
|
+
return `${mins} min${mins === 1 ? '' : 's'} ago`;
|
|
973
|
+
const hours = Math.floor(mins / 60);
|
|
974
|
+
if (hours < 24)
|
|
975
|
+
return `${hours} hour${hours === 1 ? '' : 's'} ago`;
|
|
976
|
+
const days = Math.floor(hours / 24);
|
|
977
|
+
if (days < 14)
|
|
978
|
+
return `${days} day${days === 1 ? '' : 's'} ago`;
|
|
979
|
+
const weeks = Math.floor(days / 7);
|
|
980
|
+
return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
|
|
981
|
+
}
|
|
900
982
|
async function runLatestUpdate() {
|
|
901
983
|
const updateScope = scopeFlag || 'user';
|
|
902
984
|
if (!Object.hasOwn(SCOPE_MAP, updateScope)) {
|
|
@@ -923,6 +1005,31 @@ async function runLatestUpdate() {
|
|
|
923
1005
|
const npmCommandArgs = IS_WINDOWS ? ['/d', '/s', '/c', 'npm', ...npmArgs] : npmArgs;
|
|
924
1006
|
console.log('=== Updating Tersio ===');
|
|
925
1007
|
console.log(` Running the latest ${PACKAGE_NAME} installer...\n`);
|
|
1008
|
+
// The npx delegation below only refreshes the OMP-side files; the globally
|
|
1009
|
+
// installed CLI keeps its old version until npm -g runs. Refresh both.
|
|
1010
|
+
if (dryRun) {
|
|
1011
|
+
console.log(` [dry-run] would run: npm install -g ${PACKAGE_NAME}@latest --no-audit --no-fund`);
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
try {
|
|
1015
|
+
const globalArgs = IS_WINDOWS ? ['/d', '/s', '/c', 'npm', 'install', '-g', `${PACKAGE_NAME}@latest`, '--no-audit', '--no-fund'] : ['install', '-g', `${PACKAGE_NAME}@latest`, '--no-audit', '--no-fund'];
|
|
1016
|
+
const globalResult = await execP(npmCommand, globalArgs, {
|
|
1017
|
+
timeout: 300000,
|
|
1018
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1019
|
+
windowsHide: true,
|
|
1020
|
+
shell: false,
|
|
1021
|
+
});
|
|
1022
|
+
if (globalResult.stdout)
|
|
1023
|
+
process.stdout.write(globalResult.stdout);
|
|
1024
|
+
if (globalResult.stderr)
|
|
1025
|
+
process.stderr.write(globalResult.stderr);
|
|
1026
|
+
console.log(` [ok] CLI updated to ${PACKAGE_NAME}@latest`);
|
|
1027
|
+
}
|
|
1028
|
+
catch (e) {
|
|
1029
|
+
console.log(` [warn] Global CLI update skipped: ${e.message}`);
|
|
1030
|
+
console.log(` [hint] Manual: npm install -g ${PACKAGE_NAME}@latest`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
926
1033
|
try {
|
|
927
1034
|
const result = await execP(npmCommand, npmCommandArgs, {
|
|
928
1035
|
timeout: 300000,
|
|
@@ -992,12 +1099,27 @@ async function resolveScope() {
|
|
|
992
1099
|
async function resolveProfile() {
|
|
993
1100
|
const profile = defaultProfile();
|
|
994
1101
|
// Single interactive prompt: the Combo preset implies all three modes.
|
|
1102
|
+
// Numbered menu, same pattern as the scope prompt — no typing preset names.
|
|
995
1103
|
// Only for a real user at a terminal, only when no default flags were
|
|
996
1104
|
// given, and never for --apply-update runs.
|
|
997
1105
|
if (tty() && !profileFlagsGiven && !applyUpdate && (install || reinstall)) {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1106
|
+
console.log(' Session-start defaults — Combo preset:');
|
|
1107
|
+
console.log(' 1) off');
|
|
1108
|
+
console.log(' 2) medium (caveman=lite, rtk=on, ponytail=lite)');
|
|
1109
|
+
console.log(' 3) balanced (caveman=full, rtk=on, ponytail=full)');
|
|
1110
|
+
console.log(' 4) max (caveman=ultra, rtk=on, ponytail=ultra)');
|
|
1111
|
+
for (;;) {
|
|
1112
|
+
const answer = (await ask(' Choose [1-4] (default 1): ')).trim();
|
|
1113
|
+
if (!answer) {
|
|
1114
|
+
profile.comboDefault = 'off';
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
if (COMBO_MENU[answer]) {
|
|
1118
|
+
profile.comboDefault = COMBO_MENU[answer];
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1121
|
+
console.log(` [fail] Invalid choice: ${answer}. Choose 1, 2, 3, or 4.`);
|
|
1122
|
+
}
|
|
1001
1123
|
}
|
|
1002
1124
|
// Explicit flags always win over the Combo preset.
|
|
1003
1125
|
if (comboDefaultFlag !== undefined)
|
|
@@ -1113,13 +1235,23 @@ async function main() {
|
|
|
1113
1235
|
console.log(` Platform: ${process.platform}`);
|
|
1114
1236
|
console.log(` Arch: ${process.arch}`);
|
|
1115
1237
|
console.log(` Home: ${HOME}`);
|
|
1238
|
+
// Remind humans a newer release exists; silent for scripts (no TTY) and for
|
|
1239
|
+
// the apply-update payload, which is itself an update run.
|
|
1240
|
+
if (tty() && !applyUpdate && command !== 'uninstall') {
|
|
1241
|
+
const newer = await checkForUpdate();
|
|
1242
|
+
if (newer)
|
|
1243
|
+
console.log(`\n [update] tersio ${newer} available (installed ${PACKAGE_VERSION}) — run \`tersio update\``);
|
|
1244
|
+
}
|
|
1116
1245
|
const scope = await resolveScope();
|
|
1117
1246
|
// Resolve session defaults: flags > interactive prompt > defaults.
|
|
1118
1247
|
const profile = await resolveProfile();
|
|
1119
1248
|
const userDir = OMP_AGENT_DIR;
|
|
1120
1249
|
const userExtDir = path.join(userDir, 'extensions');
|
|
1121
1250
|
const projectExtDir = path.join(process.cwd(), '.omp', 'extensions');
|
|
1122
|
-
|
|
1251
|
+
// apply-update is `tersio update`'s payload run: treat it like reinstall so
|
|
1252
|
+
// the add-ons refresh too — Ponytail package via npm, self plugin, RTK
|
|
1253
|
+
// binary (always re-downloaded), and the Caveman rule (always re-fetched).
|
|
1254
|
+
const options = { dryRun, verbose, yes, scope, reinstall: reinstall || applyUpdate };
|
|
1123
1255
|
// Check prerequisites
|
|
1124
1256
|
console.log('\nPrerequisites:');
|
|
1125
1257
|
try {
|