@krtclcdy/tersio 2.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +6 -0
  2. package/package.json +1 -1
  3. package/tersio.js +93 -14
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krtclcdy/tersio",
3
- "version": "2.1.0",
3
+ "version": "2.2.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
@@ -680,7 +680,7 @@ async function runDoctor() {
680
680
  const modeReinforcement = path.join(extDir, 'shared', 'mode-reinforcement.js');
681
681
  const selfPkg = path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'package.json');
682
682
  // 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([
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([
684
684
  execP(OMP_BIN, ['--version']).then((r) => r.stdout.trim(), () => null),
685
685
  fs.readdir(agentDir).catch(() => null),
686
686
  fs.readdir(extDir).catch(() => null),
@@ -697,6 +697,10 @@ async function runDoctor() {
697
697
  readTextIfExists(updaterIndex),
698
698
  readTextIfExists(comboIndex),
699
699
  readTextIfExists(modeReinforcement),
700
+ fs.stat(cavemanRule).catch(() => null),
701
+ fs.stat(rtkBin).catch(() => null),
702
+ fs.stat(ponytailPkg).catch(() => null),
703
+ checkForUpdate(),
700
704
  ]);
701
705
  // Categorized output with a tally; section headers group related probes.
702
706
  const tally = { ok: 0, missing: 0, warn: 0 };
@@ -717,6 +721,10 @@ async function runDoctor() {
717
721
  section('Environment');
718
722
  check('Node', true, process.version);
719
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);
720
728
  console.log(` Home: ${HOME}`);
721
729
  section('Installation');
722
730
  check('OMP agent dir', agentEntries !== null, agentDir);
@@ -725,7 +733,6 @@ async function runDoctor() {
725
733
  check('Shared session bridge', sharedStateText !== null);
726
734
  section('Extensions');
727
735
  check('Caveman extension', cavemanIndexText !== null);
728
- check('Caveman rule.md', cavemanRuleText !== null);
729
736
  check('RTK extension', rtkIndexText !== null);
730
737
  check('Updater extension', updaterIndexText !== null);
731
738
  check('Combo extension', comboIndexText !== null);
@@ -737,22 +744,27 @@ async function runDoctor() {
737
744
  check('Ponytail in config.yml', configText.includes('ponytail') && configText.includes('pi-extension'));
738
745
  check('Combo in config.yml', configText.includes('combo-toggle'));
739
746
  }
740
- check('Self plugin package', selfPkgText !== null);
747
+ check('Self plugin package', selfPkgText !== null, parseJsonObject(selfPkgText)?.version ?? '');
741
748
  const selfDep = PACKAGE_NAME in (parseJsonObject(pluginsPkgRaw)?.dependencies || {});
742
749
  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
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)
754
764
  warnLine('RTK version', 'unavailable — binary may not be executable');
755
765
  }
766
+ const ponytailAge = ponytailMtime ? `updated ${relTime(Date.now() - ponytailMtime.mtimeMs)}` : '';
767
+ check('Ponytail', ponytailPkgText !== null, [parseJsonObject(ponytailPkgText)?.version ?? '', ponytailAge].filter(Boolean).join(', '));
756
768
  const total = tally.ok + tally.missing + tally.warn;
757
769
  console.log(`\n Summary: ${total} checks — ${tally.ok} ok, ${tally.warn} warn, ${tally.missing} missing`);
758
770
  }
@@ -907,6 +919,66 @@ async function runUninstall(options = {}) {
907
919
  }
908
920
  const SCOPE_MAP = { user: '1', project: '2', both: '3' };
909
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
+ }
910
982
  async function runLatestUpdate() {
911
983
  const updateScope = scopeFlag || 'user';
912
984
  if (!Object.hasOwn(SCOPE_MAP, updateScope)) {
@@ -1163,6 +1235,13 @@ async function main() {
1163
1235
  console.log(` Platform: ${process.platform}`);
1164
1236
  console.log(` Arch: ${process.arch}`);
1165
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
+ }
1166
1245
  const scope = await resolveScope();
1167
1246
  // Resolve session defaults: flags > interactive prompt > defaults.
1168
1247
  const profile = await resolveProfile();