@krtclcdy/tersio 2.22.1 → 2.24.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 (59) hide show
  1. package/README.md +62 -46
  2. package/cli/agent-hosts.js +42 -0
  3. package/cli/agents.js +266 -0
  4. package/cli/common.js +55 -55
  5. package/cli/currency.js +8 -12
  6. package/cli/dashboard.js +76 -49
  7. package/cli/doctor-fix.js +61 -44
  8. package/cli/doctor.js +66 -44
  9. package/cli/hosts.js +118 -0
  10. package/cli/install.js +187 -143
  11. package/cli/interactive.js +27 -1
  12. package/cli/layer.js +24 -0
  13. package/cli/manifest.js +36 -0
  14. package/cli/omp-layer.js +51 -0
  15. package/cli/pi-layer.js +37 -0
  16. package/cli/pi-wiring.js +92 -0
  17. package/cli/profile.js +45 -46
  18. package/cli/reset.js +3 -26
  19. package/cli/rtk-wiring.js +10 -15
  20. package/cli/settings.js +1 -1
  21. package/cli/uninstall.js +135 -44
  22. package/cli/update.js +24 -30
  23. package/cli/usage.js +6 -1
  24. package/dashboard/dist/index.html +16 -16
  25. package/extensions/ai-addons-updater/index.js +36 -25
  26. package/extensions/ai-addons-updater/index.ts +36 -24
  27. package/extensions/caveman-session/index.js +23 -18
  28. package/extensions/caveman-session/index.ts +23 -18
  29. package/extensions/caveman-session/rule.md +90 -0
  30. package/extensions/combo-toggle/index.js +63 -41
  31. package/extensions/combo-toggle/index.ts +61 -48
  32. package/extensions/lib/utils.js +56 -13
  33. package/extensions/lib/utils.ts +55 -13
  34. package/extensions/rtk-session/index.js +20 -19
  35. package/extensions/rtk-session/index.ts +18 -24
  36. package/extensions/shared/carbon.js +5 -8
  37. package/extensions/shared/carbon.ts +5 -8
  38. package/extensions/shared/host.js +68 -0
  39. package/extensions/shared/host.ts +104 -0
  40. package/extensions/shared/omp-prompt.js +12 -0
  41. package/extensions/shared/plugin-settings.js +63 -16
  42. package/extensions/shared/plugin-settings.ts +67 -17
  43. package/extensions/shared/pricing.js +26 -15
  44. package/extensions/shared/pricing.ts +28 -16
  45. package/extensions/shared/rtk-gain.js +13 -24
  46. package/extensions/shared/rtk-gain.ts +14 -25
  47. package/extensions/shared/session-state.js +26 -28
  48. package/extensions/shared/session-state.ts +26 -29
  49. package/extensions/shared/types.ts +15 -3
  50. package/extensions/shared/usage-ledger.js +136 -38
  51. package/extensions/shared/usage-ledger.ts +149 -52
  52. package/extensions/shared/usage-store.js +14 -12
  53. package/extensions/shared/usage-store.ts +16 -14
  54. package/extensions/tersio-commands/index.js +2 -1
  55. package/extensions/tersio-commands/index.ts +2 -1
  56. package/package.json +15 -7
  57. package/tersio.js +6 -6
  58. package/extensions/shared/mode-reinforcement.js +0 -26
  59. package/extensions/shared/mode-reinforcement.ts +0 -29
package/cli/update.js CHANGED
@@ -2,11 +2,11 @@
2
2
  import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
- import { BUN_BIN_DIR, IS_WINDOWS, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_BIN, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, dryRun, execP, parseJsonObject, verbose, } from './common.js';
5
+ import { IS_WINDOWS, OMP_PLUGINS_DIR, PACKAGE_BIN, PACKAGE_NAME, PACKAGE_VERSION, dryRun, execP, parseJsonObject, verbose, } from './common.js';
6
6
  import { execNetwork } from './interactive.js';
7
- import { readTextIfExists, tersioDataPath } from '../extensions/lib/utils.js';
7
+ import { readTextIfExists, resolveRtkBinary, tersioDataPath } from '../extensions/lib/utils.js';
8
8
  import { refreshPrices } from '../extensions/shared/pricing.js';
9
- import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, fetchJson, httpsGet, normalizeRtkVersion, sha256Hex, } from '../extensions/lib/utils.js';
9
+ import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, httpsGet, normalizeRtkVersion, sha256Hex, } from '../extensions/lib/utils.js';
10
10
  const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
11
11
  function newerThan(a, b) {
12
12
  const pa = a.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
@@ -34,10 +34,8 @@ async function latestPublishedVersion() {
34
34
  return null;
35
35
  }
36
36
  }
37
- // Returns a newer published version, null when up to date, or 'unknown' when
38
- // the registry cannot be reached. Reads the registry at most once per TTL
39
- // (cached under OMP_PLUGINS_DIR) unless force skips the cache — an explicit
40
- // "check for updates" must never trust a stale cache.
37
+ // A newer published version, null when up to date, 'unknown' when the registry
38
+ // is unreachable. Cached for a TTL unless force, which must not trust a cache.
41
39
  async function checkForUpdate(force = false) {
42
40
  const cachePath = tersioDataPath('update-check.json', 'tersio-update-check.json');
43
41
  const cached = parseJsonObject(await readTextIfExists(cachePath));
@@ -50,12 +48,11 @@ async function checkForUpdate(force = false) {
50
48
  await fs.writeFile(cachePath, JSON.stringify({ latest, lastCheck: Date.now() }) + '\n', 'utf8').catch(() => { });
51
49
  return newerThan(latest, PACKAGE_VERSION) ? latest : null;
52
50
  }
53
- // Registry unreachable: a stale cache naming a newer release is still
54
- // actionable; otherwise report unknown so callers never claim "latest".
51
+ // Unreachable registry: a stale cache naming a newer release is still
52
+ // actionable, else report unknown so nobody claims "latest".
55
53
  return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : 'unknown';
56
54
  }
57
- // Race a probe against a timeout; slow or failing probes resolve null so the
58
- // update plan stays best-effort and never blocks the update itself.
55
+ // Race a probe against a timeout; failures resolve null so nothing blocks.
59
56
  async function settle(work, ms) {
60
57
  let timer;
61
58
  try {
@@ -68,9 +65,7 @@ async function settle(work, ms) {
68
65
  clearTimeout(timer);
69
66
  }
70
67
  }
71
- // Run a child with inherited stdio for live output. Used when the child
72
- // renders its own interactive UI (the delegated installer runs Clack
73
- // spinners), which a concurrent outer spinner would corrupt into stray bars.
68
+ // Inherited stdio, so a child spinner cannot corrupt this parent's.
74
69
  async function execInherit(cmd, args) {
75
70
  const code = await new Promise((resolve, reject) => {
76
71
  const child = spawn(cmd, args, { stdio: 'inherit' });
@@ -80,29 +75,30 @@ async function execInherit(cmd, args) {
80
75
  if (code !== 0)
81
76
  throw new Error(`${cmd} exited with code ${code}`);
82
77
  }
83
- // Current → latest per add-on. Every probe is capped and nullable; the plan
84
- // prints `unknown` for anything unreachable and the update proceeds anyway.
78
+ // Current → latest per add-on. Every probe is capped and nullable, and the
79
+ // plan prints `unknown` for anything unreachable.
85
80
  async function probeUpdatePlan(cliLatest) {
86
- const rtkBin = path.join(BUN_BIN_DIR, RTK_BINARY_NAME);
81
+ const rtkBin = resolveRtkBinary();
87
82
  const ponytailPkg = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
88
- const ruleDest = path.join(OMP_AGENT_DIR, 'extensions', 'caveman-session', 'rule.md');
83
+ const tersioRule = path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions', 'caveman-session', 'rule.md');
84
+ const controller = new AbortController();
89
85
  const [rtkLocal, rtkRelease, ruleLocalText, ruleRemoteText, ponytailLocalText] = await Promise.all([
90
- settle(execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => normalizeRtkVersion(r.stdout.trim() || r.stderr.trim()) || null), 6000),
91
- settle(fetchJson(RTK_RELEASE_API).then((r) => normalizeRtkVersion(r.tag_name) || null), 4000),
92
- readTextIfExists(ruleDest),
93
- settle(httpsGet(CAVEMAN_REMOTE_RULE), 4000),
86
+ settle(rtkBin ? execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => normalizeRtkVersion(r.stdout.trim() || r.stderr.trim()) || null) : Promise.resolve(null), 6000),
87
+ settle(httpsGet(RTK_RELEASE_API, { signal: controller.signal }).then((text) => normalizeRtkVersion(JSON.parse(text).tag_name) || null), 1500),
88
+ readTextIfExists(tersioRule),
89
+ settle(httpsGet(CAVEMAN_REMOTE_RULE, { signal: controller.signal }).then((text) => sha256Hex(text).slice(0, 8)), 1500),
94
90
  readTextIfExists(ponytailPkg),
95
91
  ]);
96
- const shortHash = (text) => text === null ? null : sha256Hex(text).slice(0, 8);
92
+ controller.abort();
97
93
  return {
98
94
  cli: cliLatest,
99
95
  rtk: [rtkLocal, rtkRelease],
100
- rule: [shortHash(ruleLocalText), shortHash(ruleRemoteText)],
96
+ rule: [ruleLocalText ? sha256Hex(ruleLocalText).slice(0, 8) : null, ruleRemoteText],
101
97
  ponytail: ponytailLocalText ? parseJsonObject(ponytailLocalText)?.version ?? null : null,
102
98
  };
103
99
  }
104
- // Every add-on prints its status: version jump when stale, "up to date"
105
- // otherwise, honest "unknown" when the probe cannot reach its source.
100
+ // Every add-on prints a version jump when stale, "up to date" otherwise, and
101
+ // "unknown" when its source cannot be reached.
106
102
  function planLines(plan) {
107
103
  const stale = [];
108
104
  const status = [];
@@ -136,12 +132,10 @@ async function runLatestUpdate() {
136
132
  if (verbose)
137
133
  forwardedArgs.push('--verbose');
138
134
  const npmCommand = IS_WINDOWS ? process.env.ComSpec || 'cmd.exe' : 'npm';
139
- // Resolve the target explicitly: `npm view --prefer-online` beats the local
140
- // metadata cache, so a stale `@latest` can never pin an older release.
135
+ // `--prefer-online` beats the local cache, so a stale @latest cannot pin down.
141
136
  const cliLatest = await latestPublishedVersion();
142
137
  const target = cliLatest && /^\d+\.\d+\.\d+$/.test(cliLatest) ? `@${cliLatest}` : '@latest';
143
- // Current → latest per add-on. Best-effort; unreachable probes print as
144
- // unknown and never block the update.
138
+ // Best-effort: unreachable probes print as unknown.
145
139
  const plan = await probeUpdatePlan(cliLatest);
146
140
  const { stale, status } = planLines(plan);
147
141
  if (dryRun) {
package/cli/usage.js CHANGED
@@ -1,4 +1,6 @@
1
- import { co2GramsFor, displayModelId, energyWhFor, importSessionTokens, ledgerPath, priceFor, readResetWatermark, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
1
+ // cli/usage.ts — ledger + session-token usage report, tokscale-style.
2
+ import { co2GramsFor, displayModelId, energyWhFor, importSessionTokens, ledgerPath, priceFor, readResetWatermark, readRtkAdoption, readRtkRecallDiagnostics, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
3
+ import { resolveRtkBinary } from '../extensions/lib/utils.js';
2
4
  import { readRtkGain } from '../extensions/shared/rtk-gain.js';
3
5
  import { readUsageDb, syncUsageDb, usageDbPath } from '../extensions/shared/usage-store.js';
4
6
  import { withInteractiveSpinner } from './interactive.js';
@@ -74,6 +76,8 @@ export function summarizeUsage(rows) {
74
76
  est: usdCost({ input: r.i, output: r.o, cacheRead: r.cr ?? 0, cacheWrite: r.cw ?? 0 }, r.m).usd,
75
77
  })),
76
78
  rtkGain: readRtkGain(RTK_COMMAND_ROWS, watermark || undefined),
79
+ rtkAdoption: readRtkAdoption(),
80
+ rtkRecall: readRtkRecallDiagnostics(resolveRtkBinary()),
77
81
  usd,
78
82
  priced,
79
83
  savedUsd,
@@ -134,6 +138,7 @@ function printReport(report) {
134
138
  const t = report.tokens;
135
139
  console.log(` TOKENS ${fmt(t.input)} in · ${fmt(t.output)} out · ${fmt(t.cacheRead)} cache read · ${fmt(t.cacheWrite)} cache write`);
136
140
  console.log(` COST ${formatCurrency(report.usd, report.currency)}${report.priced ? '' : ' (includes default pricing)'} · ~${formatCurrency(report.savedUsd, report.currency)} cache-saved (est.) · ~${report.co2g.toFixed(1)}g CO2 (est.)`);
141
+ console.log(` RTK ${fmt(report.rtkAdoption.rtkCalls)}/${fmt(report.rtkAdoption.eligibleCalls)} eligible OMP Bash calls use RTK (${report.rtkAdoption.adoptionPct.toFixed(1)}%) · ${fmt(report.rtkAdoption.missedCalls)} missed · recall ${report.rtkRecall.available ? `${report.rtkRecall.mode} (${report.rtkRecall.entries})` : 'unavailable'}`);
137
142
  const models = Object.entries(report.byModel).filter(([, b]) => b.input + b.output + b.cacheRead + b.cacheWrite > 0).sort((a, b) => (b[1].input + b[1].output) - (a[1].input + a[1].output)).slice(0, 8);
138
143
  if (models.length) {
139
144
  console.log(' BY MODEL');