@krtclcdy/tersio 2.14.0 → 2.16.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 CHANGED
@@ -10,24 +10,6 @@
10
10
 
11
11
  Terse replies, compact shell output, and minimal code decisions for [Oh My Pi (OMP)](https://github.com/can1357/oh-my-pi) — one-command combo presets.
12
12
 
13
- ## ✨ Features
14
-
15
- - **Caveman** — Shortens replies, keeps technical substance. Modes: `lite`, `full`, `ultra`, `wenyan`.
16
- - **RTK** — Routes noisy shell commands through the RTK binary for compact output; exact bytes bypass it by policy. The installer wires rtk into OMP automatically (`rtk init -g --agent omp`), so bash tool calls rewrite to `rtk` before execution and meter into the gain dashboard.
17
- - **Ponytail** — Minimal, YAGNI-oriented code decisions, from nudge (`lite`) to requirement-challenger (`ultra`).
18
- - **Combo** — Toggles all three at once. Presets: `off`, `medium`, `balanced`, `max`.
19
- - **Updater** — `/tersio check` and `/tersio update` cover Ponytail, RTK, and Caveman in-session, with dry-run and backups.
20
- - **Gain dashboard** — `tersio gain` serves a local-only savings dashboard: cost with currency conversion, activity graph, command tools, and model breakdowns.
21
- - **Usage ledger** — `tersio usage` prints a ledger-backed savings report for the machine, local only.
22
-
23
- ## 🧱 Tech Stack
24
-
25
- - [TypeScript](https://www.typescriptlang.org/) + [Node.js](https://nodejs.org/): CLI and OMP extensions, compiled to dependency-free JS for install.
26
- - Vanilla dashboard: dependency-free HTML/CSS/JS export — no build step, no runtime deps.
27
- - [js-tiktoken](https://github.com/dqbd/tiktoken): Real `o200k_base` BPE token counts for benchmarks (dev only).
28
- - [tokscale](https://github.com/junhoyeo/tokscale): Token-bucket convention (input / output / cache read / cache write) for parsing OMP session transcripts.
29
- - [EcoLogits](https://github.com/genai-impact/ecologits): CO2 model (v0.8.2 port — per-model params, provider grids, served ÷32 amortization).
30
-
31
13
  ## ⚡ Getting Started
32
14
 
33
15
  **Default** (macOS/Linux/WSL) — one line. Installs the CLI via npm, then runs the main installer (scope + Combo preset menus) in the same pass:
@@ -52,10 +34,10 @@ omp plugin features @krtclcdy/tersio --disable rtk
52
34
  Then restart OMP and enable a preset:
53
35
 
54
36
  ```text
55
- /tersio combo medium
37
+ /combo balanced
56
38
  ```
57
39
 
58
- Individual toggles: `/tersio caveman full` · `/tersio rtk on` · `/tersio ponytail full`. Everything starts off until you enable it.
40
+ Individual toggles: `/caveman full` · `/rtk on` · `/ponytail full`. Everything starts off until you enable it.
59
41
 
60
42
  Session-start defaults (prompted during `install`, or flags):
61
43
 
@@ -63,7 +45,7 @@ Session-start defaults (prompted during `install`, or flags):
63
45
  tersio install --combo-default balanced --caveman-default lite --rtk-default on
64
46
  ```
65
47
 
66
- Defaults apply to fresh sessions only — anything persisted with `/tersio combo`, `/tersio caveman`, or `/tersio rtk` wins. Stored as plugin settings (`omp plugin config get @krtclcdy/tersio comboDefault`).
48
+ Defaults apply to fresh sessions only — anything persisted with `/combo`, `/caveman`, or `/rtk` wins. Stored as plugin settings (`omp plugin config get @krtclcdy/tersio comboDefault`).
67
49
 
68
50
  One-off use without installing:
69
51
 
@@ -116,21 +98,21 @@ Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--combo-default`/`--caveman-defa
116
98
 
117
99
  ## ⌨️ Commands reference
118
100
 
119
- Everything runs under one root. Bare `/tersio` prints status.
101
+ Mode switches live on their own commands; bare `/tersio` prints status.
120
102
 
121
103
  | Command | Purpose |
122
104
  |---|---|
123
- | `/tersio combo off\|medium\|balanced\|max\|status` | One preset for all three (start here): medium = lite/lite/on, balanced = full/full/on, max = ultra/ultra/on; per-mode tweaks drop to `custom`. |
124
- | `/tersio caveman lite\|full\|ultra\|wenyan\|off\|status` | Terse replies (substance stays); `ultra` uses fragments only, `wenyan` a compressed classical-Chinese-inspired register. |
125
- | `/tersio rtk on\|off\|status` | Compact shell output via the real `rtk` binary; exact bytes (checksums, patches) bypass it by policy. |
126
- | `/tersio ponytail off\|lite\|full\|ultra\|review\|status` | Minimal code (YAGNI): `lite` nudges, `full` enforces, `ultra` challenges the requirement, `review` audits for over-engineering. |
105
+ | `/combo off\|medium\|balanced\|max\|status` | One preset for all three (start here): medium = lite/lite/on, balanced = full/full/on, max = ultra/ultra/on; per-mode tweaks drop to `custom`. |
106
+ | `/caveman lite\|full\|ultra\|wenyan\|off\|status` | Terse replies (substance stays); `ultra` uses fragments only, `wenyan` a compressed classical-Chinese-inspired register. |
107
+ | `/rtk on\|off\|status` | Compact shell output via the real `rtk` binary; exact bytes (checksums, patches) bypass it by policy. |
108
+ | `/ponytail off\|lite\|full\|ultra\|review\|status` | Minimal code (YAGNI): `lite` nudges, `full` enforces, `ultra` challenges the requirement, `review` audits for over-engineering (upstream command). |
127
109
  | `/tersio status` | Active modes + combo level; state persists, propagates to subagents, and lights the Combo footer indicator. |
128
110
  | `/tersio check` | Add-on version check |
129
111
  | `/tersio update <ponytail\|rtk\|caveman\|all> [--dry-run]` | Update add-ons, preview with dry-run |
130
112
  | `/tersio gain` | Savings summary; the gain dashboard charts `~/.omp/plugins/tersio-usage.jsonl` (`tersio gain --open` serves on 127.0.0.1, `--export` writes one file). |
131
113
  | `/tersio usage` | Ledger report for this machine (rows in `~/.omp/plugins/tersio-usage.jsonl`, local only) |
132
114
  | `/tersio help` | This table |
133
- | `/caveman`, `/rtk`, `/combo`, `/ai-addons` | Legacy aliases, still work; prefer `/tersio`. |
115
+ | `/ai-addons` | Add-on updater alias, still works. |
134
116
 
135
117
  ## 🗂️ Files and backups
136
118
 
package/cli/banner.js CHANGED
@@ -72,13 +72,20 @@ function bannerLines(tier = bannerTier()) {
72
72
  return out;
73
73
  }
74
74
  let shown = false;
75
- // OMP-style welcome: intro line + note box with art and tips.
75
+ // OMP-style welcome: intro line + note box with art left, commands right.
76
76
  // No-op unless interactive (and never on --dry-run).
77
77
  function printWelcome() {
78
78
  if (shown || dryRun || !tty())
79
79
  return;
80
80
  shown = true;
81
81
  clackIntro(`✂ tersio v${PACKAGE_VERSION}`);
82
- clackNote(`${bannerLines().join('\n')}\n\n tersio install set up add-ons\n tersio update refresh CLI + add-ons\n tersio doctor verify the install`, 'cut token bloat');
82
+ const tips = [
83
+ 'tersio install set up add-ons',
84
+ 'tersio update refresh CLI + add-ons',
85
+ 'tersio doctor verify the install',
86
+ ];
87
+ const art = bannerLines();
88
+ const rows = art.map((line, i) => (i >= 1 && i <= 3 ? `${line} ${tips[i - 1]}` : line));
89
+ clackNote(rows.join('\n'));
83
90
  }
84
91
  export { bannerLines, bannerTier, printWelcome };
package/cli/common.js CHANGED
@@ -104,7 +104,8 @@ async function writeIfChanged(dest, content, options = {}) {
104
104
  return false;
105
105
  }
106
106
  if (options.dryRun) {
107
- console.log(` [dry-run] would write ${dest}`);
107
+ if (verbose)
108
+ console.log(` [dry-run] would write ${dest}`);
108
109
  return true;
109
110
  }
110
111
  await fs.mkdir(path.dirname(dest), { recursive: true });
@@ -113,7 +114,8 @@ async function writeIfChanged(dest, content, options = {}) {
113
114
  debug(`${path.basename(dest)} → ${path.basename(dest)}.bak`);
114
115
  }
115
116
  await fs.writeFile(dest, content, 'utf8');
116
- console.log(` [write] ${dest}`);
117
+ if (verbose && !options.quiet)
118
+ console.log(` [write] ${dest}`);
117
119
  return true;
118
120
  }
119
121
  // omp ships 'extensions: null'; appending list items under a null scalar breaks
@@ -127,11 +129,12 @@ function normalizeExtensionsKey(lines) {
127
129
  lines[idx] = 'extensions:';
128
130
  return true;
129
131
  }
130
- async function writeConfigLines(configPath, lines, logMsg) {
132
+ async function writeConfigLines(configPath, lines, logMsg, options = {}) {
131
133
  await fs.mkdir(path.dirname(configPath), { recursive: true });
132
134
  await fs.copyFile(configPath, `${configPath}.bak`).catch(() => { });
133
135
  await fs.writeFile(configPath, lines.join('\n'), 'utf8');
134
- console.log(logMsg);
136
+ if (verbose && !options.quiet)
137
+ console.log(logMsg);
135
138
  }
136
139
  async function ensureExtensionInConfig(configPath, extensionPath, label, options = {}) {
137
140
  const normalizedPath = extensionPath.replace(/\\/g, '/');
@@ -144,7 +147,8 @@ async function ensureExtensionInConfig(configPath, extensionPath, label, options
144
147
  }
145
148
  const extLineIdx = lines.findIndex((l) => EXTENSIONS_KEY_RE.test(l));
146
149
  if (options.dryRun) {
147
- console.log(` [dry-run] would add ${label} to config.yml: ${normalizedPath}`);
150
+ if (verbose && !options.quiet)
151
+ console.log(` [dry-run] would add ${label} to config.yml`);
148
152
  return true;
149
153
  }
150
154
  if (normalizeExtensionsKey(lines)) {
@@ -158,7 +162,7 @@ async function ensureExtensionInConfig(configPath, extensionPath, label, options
158
162
  else {
159
163
  lines.splice(extLineIdx + 1, 0, line);
160
164
  }
161
- await writeConfigLines(configPath, lines, ` [write] Added ${label} to config.yml`);
165
+ await writeConfigLines(configPath, lines, ` [write] Added ${label} to config.yml`, options);
162
166
  return true;
163
167
  }
164
168
  async function ensureExtensionAfterConfigEntry(configPath, extensionPath, afterPath, label, options = {}) {
@@ -172,7 +176,8 @@ async function ensureExtensionAfterConfigEntry(configPath, extensionPath, afterP
172
176
  if (existingIndex !== -1 && afterIndex !== -1 && existingIndex === afterIndex + 1)
173
177
  return false;
174
178
  if (options.dryRun) {
175
- console.log(` [dry-run] would place ${label} after Ponytail in config.yml: ${normalizedPath}`);
179
+ if (verbose && !options.quiet)
180
+ console.log(` [dry-run] would place ${label} after Ponytail in config.yml`);
176
181
  return true;
177
182
  }
178
183
  if (existingIndex !== -1)
@@ -191,7 +196,7 @@ async function ensureExtensionAfterConfigEntry(configPath, extensionPath, afterP
191
196
  lines.splice(extensionsIndex + 1, 0, line);
192
197
  }
193
198
  }
194
- await writeConfigLines(configPath, lines, ` [write] Placed ${label} after Ponytail in config.yml`);
199
+ await writeConfigLines(configPath, lines, ` [write] Placed ${label} after Ponytail in config.yml`, options);
195
200
  return true;
196
201
  }
197
202
  function readPonytailConfig() {
@@ -219,7 +224,8 @@ function parsePonytailConfig(existing) {
219
224
  async function patchPonytailConfig(apply, dryRunNote, writeNote, options = {}) {
220
225
  const config = readPonytailConfig();
221
226
  if (options.dryRun) {
222
- console.log(` [dry-run] ${dryRunNote} in ${config.path}`);
227
+ if (verbose && !options.quiet)
228
+ console.log(` [dry-run] ${dryRunNote}`);
223
229
  return;
224
230
  }
225
231
  const cfg = parsePonytailConfig(await readTextIfExists(config.path));
@@ -229,7 +235,8 @@ async function patchPonytailConfig(apply, dryRunNote, writeNote, options = {}) {
229
235
  }
230
236
  await fs.mkdir(config.dir, { recursive: true });
231
237
  await fs.writeFile(config.path, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
232
- console.log(` [write] ${writeNote} in ${config.path}`);
238
+ if (verbose && !options.quiet)
239
+ console.log(` [write] ${writeNote}`);
233
240
  }
234
241
  async function ensurePonytailConfigValue(key, value, options = {}) {
235
242
  await patchPonytailConfig((cfg) => {
package/cli/doctor.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // cli/doctor.ts — installation health checks.
2
2
  import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
- import { BUN_BIN_DIR, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, execP, parseJsonObject, relTime, } from './common.js';
4
+ import { BUN_BIN_DIR, OMP_AGENT_DIR, OMP_PLUGINS_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, execP, parseJsonObject, relTime, } from './common.js';
5
5
  import { runInteractivePhase } from './interactive.js';
6
6
  import { ledgerPath, readUsage, sessionsDir } from '../extensions/shared/usage-ledger.js';
7
7
  import { readRtkGain, rtkDbPath } from '../extensions/shared/rtk-gain.js';
@@ -56,50 +56,53 @@ async function runDoctor() {
56
56
  const err = e;
57
57
  return err.stdout?.trim() || err.stderr?.trim() || null;
58
58
  }));
59
- const [ompVersion, agentEntries, extEntries, sharedStateText, configText, updateVersion] = await runInteractivePhase('Checking environment and installation', () => Promise.all([
60
- probes.ompVersion,
61
- probes.agentEntries,
62
- probes.extEntries,
63
- probes.sharedStateText,
64
- probes.configText,
65
- probes.updateVersion,
59
+ const [[ompVersion, agentEntries, extEntries, sharedStateText, configText, updateVersion], [cavemanIndexText, rtkIndexText, updaterIndexText, comboIndexText, tersioIndexText, modeReinforcementText, ponytailPkgText, ponytailExtText, pluginsPkgRaw, selfPkgText], [cavemanRuleText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, rtkOmpText, ponytailMtime]] = await runInteractivePhase('Checking installation', () => Promise.all([
60
+ Promise.all([
61
+ probes.ompVersion,
62
+ probes.agentEntries,
63
+ probes.extEntries,
64
+ probes.sharedStateText,
65
+ probes.configText,
66
+ probes.updateVersion,
67
+ ]),
68
+ Promise.all([
69
+ probes.cavemanIndexText,
70
+ probes.rtkIndexText,
71
+ probes.updaterIndexText,
72
+ probes.comboIndexText,
73
+ probes.tersioIndexText,
74
+ probes.modeReinforcementText,
75
+ probes.ponytailPkgText,
76
+ probes.ponytailExtText,
77
+ probes.pluginsPkgRaw,
78
+ probes.selfPkgText,
79
+ ]),
80
+ Promise.all([
81
+ probes.cavemanRuleText,
82
+ probes.ruleMtime,
83
+ probes.rtkBinText,
84
+ probes.rtkMtime,
85
+ rtkVersionProbe,
86
+ probes.rtkOmpText,
87
+ probes.ponytailMtime,
88
+ ]),
66
89
  ]));
67
- const [cavemanIndexText, rtkIndexText, updaterIndexText, comboIndexText, tersioIndexText, modeReinforcementText, ponytailPkgText, ponytailExtText, pluginsPkgRaw, selfPkgText] = await runInteractivePhase('Checking extensions and plugins', () => Promise.all([
68
- probes.cavemanIndexText,
69
- probes.rtkIndexText,
70
- probes.updaterIndexText,
71
- probes.comboIndexText,
72
- probes.tersioIndexText,
73
- probes.modeReinforcementText,
74
- probes.ponytailPkgText,
75
- probes.ponytailExtText,
76
- probes.pluginsPkgRaw,
77
- probes.selfPkgText,
78
- ]));
79
- const [cavemanRuleText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, rtkOmpText, ponytailMtime] = await runInteractivePhase('Checking add-ons', () => Promise.all([
80
- probes.cavemanRuleText,
81
- probes.ruleMtime,
82
- probes.rtkBinText,
83
- probes.rtkMtime,
84
- rtkVersionProbe,
85
- probes.rtkOmpText,
86
- probes.ponytailMtime,
87
- ]));
88
- // Categorized output with a tally; section headers group related probes.
90
+ // Categorized output with a tally; success rows stay quiet (no path echoes)
91
+ // while failures print the expected path or fix so they stay actionable.
89
92
  const tally = { ok: 0, missing: 0, warn: 0 };
90
93
  function check(label, ok, detail = '') {
91
94
  if (ok) {
92
95
  tally.ok++;
93
- console.log(` ${label}: ok${detail ? ` ${detail}` : ''}`);
96
+ console.log(` ✅ ${label}: ok${detail ? ` ${detail}` : ''}`);
94
97
  }
95
98
  else {
96
99
  tally.missing++;
97
- console.log(` ${label}: MISSING${detail ? ` ${detail}` : ''}`);
100
+ console.log(` ❌ ${label}: MISSING${detail ? ` ${detail}` : ''}`);
98
101
  }
99
102
  }
100
103
  function warnLine(label, detail) {
101
104
  tally.warn++;
102
- console.log(` ${label}: warn ${detail}`);
105
+ console.log(` ⚠️ ${label}: warn ${detail}`);
103
106
  }
104
107
  section('Environment');
105
108
  check('Node', true, process.version);
@@ -110,33 +113,27 @@ async function runDoctor() {
110
113
  warnLine('Tersio CLI', `${PACKAGE_VERSION} (version check unreachable)`);
111
114
  else
112
115
  check('Tersio CLI', true, PACKAGE_VERSION);
113
- console.log(` Home: ${HOME}`);
114
116
  section('Installation');
115
- check('OMP agent dir', agentEntries !== null, agentDir);
116
- check('OMP extensions dir', extEntries !== null, extDir);
117
- check('OMP config.yml', configText !== null, configPath);
117
+ check('OMP agent dir', agentEntries !== null, agentEntries === null ? agentDir : '');
118
+ check('OMP extensions dir', extEntries !== null, extEntries === null ? extDir : '');
119
+ check('OMP config.yml', configText !== null, configText === null ? configPath : '');
118
120
  check('Shared session bridge', sharedStateText !== null);
119
- section('Extensions');
121
+ section('Extensions & plugins');
120
122
  check('Caveman extension', cavemanIndexText !== null);
121
123
  check('RTK extension', rtkIndexText !== null);
122
124
  check('Updater extension', updaterIndexText !== null);
123
125
  check('Combo extension', comboIndexText !== null);
124
126
  check('Tersio commands extension', tersioIndexText !== null);
125
127
  check('Mode reinforcement extension', modeReinforcementText !== null);
126
- section('Plugins');
127
- check('Ponytail package', ponytailPkgText !== null);
128
128
  check('Ponytail extension', ponytailExtText !== null);
129
- if (configText) {
130
- check('Ponytail in config.yml', configText.includes('ponytail') && configText.includes('pi-extension'));
131
- check('Combo in config.yml', configText.includes('combo-toggle'));
132
- }
129
+ check('Ponytail in config.yml', (configText ?? '').includes('ponytail') && (configText ?? '').includes('pi-extension'));
130
+ check('Combo in config.yml', (configText ?? '').includes('combo-toggle'));
133
131
  check('Self plugin package', selfPkgText !== null, parseJsonObject(selfPkgText)?.version ?? '');
134
132
  const selfDep = PACKAGE_NAME in (parseJsonObject(pluginsPkgRaw)?.dependencies || {});
135
133
  check('Self plugin in plugins/package.json', selfDep);
136
- section('Usage');
134
+ section('Usage & records');
137
135
  const usageRows = readUsage();
138
- check('Usage ledger', usageRows.length > 0, usageRows.length ? `${usageRows.length} rows · ${ledgerPath()}` : ledgerPath());
139
- section('Records');
136
+ check('Usage ledger', true, usageRows.length ? `${usageRows.length} rows` : 'empty — no records yet');
140
137
  console.log(` Usage ledger (tersio-owned, tersio reset clears): ${ledgerPath()} · ${usageRows.length} rows`);
141
138
  console.log(` Session transcripts (host-owned, never touched): ${sessionsDir()}`);
142
139
  const rtkDb = rtkDbPath();
@@ -146,22 +143,17 @@ async function runDoctor() {
146
143
  const ruleAge = ruleMtime ? `updated ${relTime(Date.now() - ruleMtime.mtimeMs)}` : '';
147
144
  check('Caveman rule', cavemanRuleText !== null, ruleAge);
148
145
  const rtkAge = rtkMtime ? `updated ${relTime(Date.now() - rtkMtime.mtimeMs)}` : '';
149
- if (rtkBinText === null)
150
- check('RTK binary', false, rtkBin);
151
- else {
152
- const bits = [rtkVersion, rtkAge].filter(Boolean).join(', ');
153
- check('RTK binary', true, bits);
154
- if (!rtkVersion)
155
- warnLine('RTK version', 'unavailable — binary may not be executable');
156
- if (rtkOmpText === null)
157
- check('RTK OMP wiring (rtk.ts)', false, `run: rtk init -g --agent omp`);
158
- else
159
- check('RTK OMP wiring (rtk.ts)', true, path.join(extDir, 'rtk.ts'));
160
- }
146
+ check('RTK binary', rtkBinText !== null, rtkBinText === null ? rtkBin : [rtkVersion, rtkAge].filter(Boolean).join(', '));
147
+ if (rtkBinText !== null && !rtkVersion)
148
+ warnLine('RTK version', 'unavailable — binary may not be executable');
149
+ const rtkRegistered = rtkOmpText !== null && (configText ?? '').includes('extensions/rtk.ts');
150
+ check('RTK OMP wiring (rtk.ts)', rtkOmpText !== null, rtkOmpText === null ? 'run: rtk init -g --agent omp' : '');
151
+ if (rtkOmpText !== null && !rtkRegistered)
152
+ warnLine('RTK in config.yml', 'rtk.ts not listed — OMP will not load it; rerun install');
161
153
  const ponytailAge = ponytailMtime ? `updated ${relTime(Date.now() - ponytailMtime.mtimeMs)}` : '';
162
154
  check('Ponytail', ponytailPkgText !== null, [parseJsonObject(ponytailPkgText)?.version ?? '', ponytailAge].filter(Boolean).join(', '));
163
155
  const total = tally.ok + tally.missing + tally.warn;
164
- console.log(`\n Summary: ${total} checks — ${tally.ok} ok, ${tally.warn} warn, ${tally.missing} missing`);
156
+ console.log(`\n Summary: ${total} checks — ✅ ${tally.ok} ok, ⚠️ ${tally.warn} warn, ❌ ${tally.missing} missing`);
165
157
  }
166
158
  function section(name) {
167
159
  console.log(`\n${name}`);