@krtclcdy/tersio 2.1.0 → 2.3.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.
Files changed (3) hide show
  1. package/README.md +7 -8
  2. package/package.json +1 -1
  3. package/tersio.js +105 -15
package/README.md CHANGED
@@ -11,24 +11,23 @@ Terse replies, compact shell output, and minimal code decisions for [Oh My Pi (O
11
11
 
12
12
  ## Install
13
13
 
14
- OMP plugin (recommended — updates and feature flags handled by OMP):
14
+ **Default** (macOS/Linux/WSL) — one line. Installs the CLI via npm, then runs the main installer (scope + Combo preset menus) in the same pass:
15
15
 
16
16
  ```bash
17
- omp plugin install @krtclcdy/tersio
17
+ curl -fsSL https://github.com/KurutoDenzeru/tersio/releases/latest/download/install.sh | sh
18
18
  ```
19
19
 
20
- Subset only:
20
+ Prefer OMP-managed updates and feature flags? Install as an OMP plugin instead:
21
21
 
22
22
  ```bash
23
- omp plugin install '@krtclcdy/tersio[caveman,ponytail]'
24
- omp plugin features @krtclcdy/tersio --disable rtk
23
+ omp plugin install @krtclcdy/tersio
25
24
  ```
26
25
 
27
- npm CLI (installs add-ons into your OMP home):
26
+ Subset only:
28
27
 
29
28
  ```bash
30
- npm install -g @krtclcdy/tersio@latest
31
- tersio install
29
+ omp plugin install '@krtclcdy/tersio[caveman,ponytail]'
30
+ omp plugin features @krtclcdy/tersio --disable rtk
32
31
  ```
33
32
 
34
33
  Then restart OMP and enable a preset:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krtclcdy/tersio",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "✂️ Token-saving OMP add-ons: terse Caveman replies, compact RTK shell output, lean Ponytail code calls — one-command combo presets.",
5
5
  "keywords": [
6
6
  "omp",
package/tersio.js CHANGED
@@ -112,7 +112,18 @@ function debug(...a) {
112
112
  const RL = readline.createInterface({ input: process.stdin, output: process.stdout });
113
113
  let rlOpen = true;
114
114
  function ask(q) {
115
- return new Promise((resolve) => RL.question(q, resolve));
115
+ return new Promise((resolve) => {
116
+ if (!rlOpen) {
117
+ resolve('');
118
+ return;
119
+ }
120
+ try {
121
+ RL.question(q, resolve);
122
+ }
123
+ catch {
124
+ resolve('');
125
+ }
126
+ });
116
127
  }
117
128
  function closeRL() { if (rlOpen) {
118
129
  RL.close();
@@ -680,7 +691,7 @@ async function runDoctor() {
680
691
  const modeReinforcement = path.join(extDir, 'shared', 'mode-reinforcement.js');
681
692
  const selfPkg = path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'package.json');
682
693
  // Independent probes run concurrently; logs below keep fixed order.
683
- const [ompVersion, agentEntries, extEntries, sharedStateText, configText, ponytailPkgText, ponytailExtText, pluginsPkgRaw, selfPkgText, rtkBinText, cavemanIndexText, cavemanRuleText, rtkIndexText, updaterIndexText, comboIndexText, modeReinforcementText,] = await Promise.all([
694
+ const [ompVersion, agentEntries, extEntries, sharedStateText, configText, ponytailPkgText, ponytailExtText, pluginsPkgRaw, selfPkgText, rtkBinText, cavemanIndexText, cavemanRuleText, rtkIndexText, updaterIndexText, comboIndexText, modeReinforcementText, ruleMtime, rtkMtime, ponytailMtime, updateVersion,] = await Promise.all([
684
695
  execP(OMP_BIN, ['--version']).then((r) => r.stdout.trim(), () => null),
685
696
  fs.readdir(agentDir).catch(() => null),
686
697
  fs.readdir(extDir).catch(() => null),
@@ -697,6 +708,10 @@ async function runDoctor() {
697
708
  readTextIfExists(updaterIndex),
698
709
  readTextIfExists(comboIndex),
699
710
  readTextIfExists(modeReinforcement),
711
+ fs.stat(cavemanRule).catch(() => null),
712
+ fs.stat(rtkBin).catch(() => null),
713
+ fs.stat(ponytailPkg).catch(() => null),
714
+ checkForUpdate(),
700
715
  ]);
701
716
  // Categorized output with a tally; section headers group related probes.
702
717
  const tally = { ok: 0, missing: 0, warn: 0 };
@@ -717,6 +732,10 @@ async function runDoctor() {
717
732
  section('Environment');
718
733
  check('Node', true, process.version);
719
734
  check('OMP CLI', ompVersion !== null, ompVersion ?? '');
735
+ if (updateVersion)
736
+ warnLine('Tersio CLI', `${updateVersion} available — run tersio update`);
737
+ else
738
+ check('Tersio CLI', true, PACKAGE_VERSION);
720
739
  console.log(` Home: ${HOME}`);
721
740
  section('Installation');
722
741
  check('OMP agent dir', agentEntries !== null, agentDir);
@@ -725,7 +744,6 @@ async function runDoctor() {
725
744
  check('Shared session bridge', sharedStateText !== null);
726
745
  section('Extensions');
727
746
  check('Caveman extension', cavemanIndexText !== null);
728
- check('Caveman rule.md', cavemanRuleText !== null);
729
747
  check('RTK extension', rtkIndexText !== null);
730
748
  check('Updater extension', updaterIndexText !== null);
731
749
  check('Combo extension', comboIndexText !== null);
@@ -737,22 +755,27 @@ async function runDoctor() {
737
755
  check('Ponytail in config.yml', configText.includes('ponytail') && configText.includes('pi-extension'));
738
756
  check('Combo in config.yml', configText.includes('combo-toggle'));
739
757
  }
740
- check('Self plugin package', selfPkgText !== null);
758
+ check('Self plugin package', selfPkgText !== null, parseJsonObject(selfPkgText)?.version ?? '');
741
759
  const selfDep = PACKAGE_NAME in (parseJsonObject(pluginsPkgRaw)?.dependencies || {});
742
760
  check('Self plugin in plugins/package.json', selfDep);
743
- section('RTK');
744
- check('RTK binary', rtkBinText !== null, rtkBin);
745
- if (rtkBinText !== null) {
746
- // rtk may exit non-zero while still printing its version — accept either stream.
747
- const version = await execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => r.stdout.trim() || r.stderr.trim() || null, (e) => {
748
- const err = e;
749
- return err.stdout?.trim() || err.stderr?.trim() || null;
750
- });
751
- if (version)
752
- check('RTK version', true, version);
753
- else
761
+ section('Add-ons');
762
+ const ruleAge = ruleMtime ? `updated ${relTime(Date.now() - ruleMtime.mtimeMs)}` : '';
763
+ check('Caveman rule', cavemanRuleText !== null, ruleAge);
764
+ const rtkVersion = rtkBinText === null ? null : await execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => r.stdout.trim() || r.stderr.trim() || null, (e) => {
765
+ const err = e;
766
+ return err.stdout?.trim() || err.stderr?.trim() || null;
767
+ });
768
+ const rtkAge = rtkMtime ? `updated ${relTime(Date.now() - rtkMtime.mtimeMs)}` : '';
769
+ if (rtkBinText === null)
770
+ check('RTK binary', false, rtkBin);
771
+ else {
772
+ const bits = [rtkVersion, rtkAge].filter(Boolean).join(', ');
773
+ check('RTK binary', true, bits);
774
+ if (!rtkVersion)
754
775
  warnLine('RTK version', 'unavailable — binary may not be executable');
755
776
  }
777
+ const ponytailAge = ponytailMtime ? `updated ${relTime(Date.now() - ponytailMtime.mtimeMs)}` : '';
778
+ check('Ponytail', ponytailPkgText !== null, [parseJsonObject(ponytailPkgText)?.version ?? '', ponytailAge].filter(Boolean).join(', '));
756
779
  const total = tally.ok + tally.missing + tally.warn;
757
780
  console.log(`\n Summary: ${total} checks — ${tally.ok} ok, ${tally.warn} warn, ${tally.missing} missing`);
758
781
  }
@@ -907,6 +930,66 @@ async function runUninstall(options = {}) {
907
930
  }
908
931
  const SCOPE_MAP = { user: '1', project: '2', both: '3' };
909
932
  const COMBO_MENU = { '1': 'off', '2': 'medium', '3': 'balanced', '4': 'max' };
933
+ // --- Update check (cached; used by the bare-invocation banner and doctor) ---
934
+ const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
935
+ function newerThan(a, b) {
936
+ const pa = a.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
937
+ const pb = b.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
938
+ for (let i = 0; i < 3; i++) {
939
+ if ((pa[i] || 0) !== (pb[i] || 0))
940
+ return (pa[i] || 0) > (pb[i] || 0);
941
+ }
942
+ return false;
943
+ }
944
+ async function latestPublishedVersion() {
945
+ try {
946
+ const args = IS_WINDOWS
947
+ ? ['/d', '/s', '/c', 'npm', 'view', PACKAGE_NAME, 'version', '--prefer-online']
948
+ : ['view', PACKAGE_NAME, 'version', '--prefer-online'];
949
+ const r = await execP(IS_WINDOWS ? process.env.ComSpec || 'cmd.exe' : 'npm', args, {
950
+ timeout: 4000,
951
+ maxBuffer: 1024 * 1024,
952
+ windowsHide: true,
953
+ shell: false,
954
+ });
955
+ return r.stdout.trim() || null;
956
+ }
957
+ catch {
958
+ return null;
959
+ }
960
+ }
961
+ // Returns a newer published version, or null. Reads the registry at most once
962
+ // per TTL (cached under OMP_PLUGINS_DIR) and stays silent on failure.
963
+ async function checkForUpdate() {
964
+ const cachePath = path.join(OMP_PLUGINS_DIR, 'tersio-update-check.json');
965
+ const cached = parseJsonObject(await readTextIfExists(cachePath));
966
+ const cacheFresh = typeof cached?.lastCheck === 'number' && Date.now() - cached.lastCheck < UPDATE_CHECK_TTL_MS;
967
+ if (cacheFresh && cached?.latest)
968
+ return newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : null;
969
+ const latest = await latestPublishedVersion();
970
+ if (latest) {
971
+ await fs.mkdir(OMP_PLUGINS_DIR, { recursive: true }).catch(() => { });
972
+ await fs.writeFile(cachePath, JSON.stringify({ latest, lastCheck: Date.now() }) + '\n', 'utf8').catch(() => { });
973
+ return newerThan(latest, PACKAGE_VERSION) ? latest : null;
974
+ }
975
+ // Registry unreachable: fall back to a stale cache rather than staying silent.
976
+ return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : null;
977
+ }
978
+ function relTime(ageMs) {
979
+ const mins = Math.floor(ageMs / 60000);
980
+ if (mins < 1)
981
+ return 'just now';
982
+ if (mins < 60)
983
+ return `${mins} min${mins === 1 ? '' : 's'} ago`;
984
+ const hours = Math.floor(mins / 60);
985
+ if (hours < 24)
986
+ return `${hours} hour${hours === 1 ? '' : 's'} ago`;
987
+ const days = Math.floor(hours / 24);
988
+ if (days < 14)
989
+ return `${days} day${days === 1 ? '' : 's'} ago`;
990
+ const weeks = Math.floor(days / 7);
991
+ return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
992
+ }
910
993
  async function runLatestUpdate() {
911
994
  const updateScope = scopeFlag || 'user';
912
995
  if (!Object.hasOwn(SCOPE_MAP, updateScope)) {
@@ -1163,6 +1246,13 @@ async function main() {
1163
1246
  console.log(` Platform: ${process.platform}`);
1164
1247
  console.log(` Arch: ${process.arch}`);
1165
1248
  console.log(` Home: ${HOME}`);
1249
+ // Remind humans a newer release exists; silent for scripts (no TTY) and for
1250
+ // the apply-update payload, which is itself an update run.
1251
+ if (tty() && !applyUpdate && command !== 'uninstall') {
1252
+ const newer = await checkForUpdate();
1253
+ if (newer)
1254
+ console.log(`\n [update] tersio ${newer} available (installed ${PACKAGE_VERSION}) — run \`tersio update\``);
1255
+ }
1166
1256
  const scope = await resolveScope();
1167
1257
  // Resolve session defaults: flags > interactive prompt > defaults.
1168
1258
  const profile = await resolveProfile();