@krtclcdy/tersio 2.10.0 → 2.12.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
@@ -97,16 +97,17 @@ Break-even math, balanced preset (caveman full + rtk + ponytail): `⌈overhead
97
97
 
98
98
  | Command | Purpose |
99
99
  |---|---|
100
- | `tersio install` | Install (user scope by default; `--scope project\|both` for more) |
100
+ | `tersio install` | Install (user scope: all OMP sessions) |
101
101
  | `tersio update` | Refresh the CLI, extensions, and add-ons (RTK binary, Caveman rule, Ponytail) |
102
102
  | `tersio reinstall` | Fresh install, preserving the Ponytail package |
103
103
  | `tersio doctor` | Check OMP, extension, Ponytail, and RTK health |
104
104
  | `tersio usage` | Ledger-backed usage + savings report |
105
105
  | `tersio gain` | Open the gain dashboard (`--open`, `--export <file>`, `--port <n>`; serves localhost only) |
106
+ | `tersio reset` | Clear tersio statistics (usage ledger; `--dry-run`, `--yes`) — sessions and RTK history stay intact |
106
107
  | `tersio uninstall` | Remove extensions, registration, and the Ponytail plugin (`--keep-ponytail` keeps Ponytail; `--remove-rtk` also removes the RTK binary) |
107
108
  | `tersio version` | Print version |
108
109
 
109
- Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--scope`, `--combo-default`/`--caveman-default`/`--rtk-default`/`--ponytail-default`. Legacy `--doctor` / `--uninstall` forms still work.
110
+ Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--combo-default`/`--caveman-default`/`--rtk-default`/`--ponytail-default`. Legacy `--doctor` / `--uninstall` forms still work.
110
111
 
111
112
  ## ⌨️ Commands reference
112
113
 
package/cli/banner.js CHANGED
@@ -1,4 +1,4 @@
1
- // cli/banner.ts — mini pixel-scissor welcome (16x10 px -> 5 half-block rows).
1
+ // cli/banner.ts — mini pixel-scissor welcome (16x12 px -> 6 half-block rows).
2
2
  // OMP-style launch moment for interactive runs: clack intro + note box.
3
3
  // TTY-only and skipped on --dry-run, so piped output stays byte-identical.
4
4
  import { intro as clackIntro, note as clackNote } from '@clack/prompts';
@@ -18,6 +18,8 @@ const GRID = [
18
18
  '..LL.LL...RR.RR.',
19
19
  '.LL...LL.RR...RR',
20
20
  '..LL.LL...RR.RR.',
21
+ '..LLLLL...RRRRR.',
22
+ '....L.......R...',
21
23
  ];
22
24
  // [truecolor rgb, ansi256] per class. Left ring red, right ring yellow.
23
25
  const PALETTE = {
@@ -55,7 +57,7 @@ function paint(ka, kb, tier) {
55
57
  return `${fg(ka)}▀\x1b[0m`;
56
58
  return `${fg(kb)}▄\x1b[0m`;
57
59
  }
58
- // Five 16-wide rows. Pure: unit-testable without a TTY.
60
+ // Six 16-wide rows. Pure: unit-testable without a TTY.
59
61
  function bannerLines(tier = bannerTier()) {
60
62
  const out = [];
61
63
  for (let r = 0; r < GRID.length; r += 2) {
@@ -77,6 +79,6 @@ function printWelcome() {
77
79
  return;
78
80
  shown = true;
79
81
  clackIntro(`✂ tersio v${PACKAGE_VERSION}`);
80
- clackNote(`${bannerLines().join('\n')}\n\n /tersio combo medium toggle all three\n /tersio caveman full terse replies\n /tersio rtk on compact shell output`, 'cut token bloat');
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');
81
83
  }
82
84
  export { bannerLines, bannerTier, printWelcome };
package/cli/common.js CHANGED
@@ -46,9 +46,9 @@ function flagValue(name) {
46
46
  return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
47
47
  }
48
48
  // --- CLI flags ---
49
- const COMMANDS = { install: true, update: true, reinstall: true, doctor: true, uninstall: true, usage: true, gain: true, version: true, help: true };
49
+ const COMMANDS = { install: true, update: true, reinstall: true, doctor: true, uninstall: true, usage: true, gain: true, reset: true, version: true, help: true };
50
50
  const args = process.argv.slice(2);
51
- const commandArg = args.find((arg, index) => !arg.startsWith('-') && args[index - 1] !== '--scope');
51
+ const commandArg = args.find((arg) => !arg.startsWith('-'));
52
52
  const command = commandArg?.toLowerCase() || null;
53
53
  const unknownCommand = command !== null && !COMMANDS[command];
54
54
  const install = command === 'install';
@@ -63,6 +63,7 @@ const verbose = args.includes('--verbose');
63
63
  const doctor = command === 'doctor' || args.includes('--doctor');
64
64
  const usage = command === 'usage' || args.includes('--usage');
65
65
  const gain = command === 'gain';
66
+ const reset = command === 'reset';
66
67
  const dashboardPort = Number.parseInt(flagValue('--port') ?? '', 10) || 0;
67
68
  const dashboardOpen = args.includes('--open');
68
69
  const dashboardExport = flagValue('--export') ?? null;
@@ -70,7 +71,13 @@ const uninstall = command === 'uninstall' || args.includes('--uninstall');
70
71
  const removePonytail = args.includes('--remove-ponytail');
71
72
  const keepPonytail = args.includes('--keep-ponytail');
72
73
  const removeRtk = args.includes('--remove-rtk');
73
- const scopeFlag = flagValue('--scope')?.toLowerCase() ?? null;
74
+ // Project scope was removed: tersio installs user-level (all OMP sessions)
75
+ // only. A bare `--scope user` still parses (old scripts); anything else fails.
76
+ const legacyScope = flagValue('--scope')?.toLowerCase() ?? null;
77
+ if (legacyScope !== null && legacyScope !== 'user') {
78
+ console.error(`[fail] Invalid --scope: ${legacyScope}. Project scope was removed; tersio installs user-level only.`);
79
+ process.exit(1);
80
+ }
74
81
  // --- Profile selection (session-start mode defaults) ---
75
82
  const comboDefaultFlag = parseEnum(flagValue('--combo-default'), COMBO_DEFAULTS, '--combo-default');
76
83
  const cavemanDefaultFlag = parseEnum(flagValue('--caveman-default'), CAVEMAN_DEFAULTS, '--caveman-default');
@@ -238,7 +245,6 @@ async function readPluginsPackage(pkgPath) {
238
245
  const pkg = parseJsonObject(await readTextIfExists(pkgPath)) ?? {};
239
246
  return { ...pkg, name: pkg.name || 'omp-plugins', private: true, dependencies: pkg.dependencies || {} };
240
247
  }
241
- const SCOPE_MAP = { user: '1', project: '2', both: '3' };
242
248
  function relTime(ageMs) {
243
249
  const mins = Math.floor(ageMs / 60000);
244
250
  if (mins < 1)
@@ -254,4 +260,4 @@ function relTime(ageMs) {
254
260
  const weeks = Math.floor(days / 7);
255
261
  return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
256
262
  }
257
- export { IS_WINDOWS, RTK_BINARY_NAME, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, BUN_BIN_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_BIN, PACKAGE_VERSION, CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS, RTK_DEFAULTS, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, unknownCommand, install, update, reinstall, showVersion, showHelp, applyUpdate, dryRun, yes, verbose, doctor, uninstall, usage, gain, dashboardPort, dashboardOpen, dashboardExport, removePonytail, keepPonytail, removeRtk, scopeFlag, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, rtkDefaultFlag, profileFlagsGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, EXTENSIONS_KEY_RE, writeConfigLines, ensureExtensionInConfig, ensureExtensionAfterConfigEntry, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, SCOPE_MAP, relTime, };
263
+ export { IS_WINDOWS, RTK_BINARY_NAME, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, BUN_BIN_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_BIN, PACKAGE_VERSION, CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS, RTK_DEFAULTS, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, unknownCommand, install, update, reinstall, showVersion, showHelp, applyUpdate, dryRun, yes, verbose, doctor, uninstall, usage, gain, reset, dashboardPort, dashboardOpen, dashboardExport, removePonytail, keepPonytail, removeRtk, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, rtkDefaultFlag, profileFlagsGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, EXTENSIONS_KEY_RE, writeConfigLines, ensureExtensionInConfig, ensureExtensionAfterConfigEntry, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, relTime, };
package/cli/dashboard.js CHANGED
@@ -8,7 +8,7 @@ import { promises as fs } from 'node:fs';
8
8
  import http from 'node:http';
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
- import { readUsage } from '../extensions/shared/usage-ledger.js';
11
+ import { clearUsageLedger, readUsage } from '../extensions/shared/usage-ledger.js';
12
12
  import { summarizeUsage } from './usage.js';
13
13
  import { withInteractiveSpinner } from './interactive.js';
14
14
  const DASH_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'dashboard');
@@ -50,6 +50,12 @@ async function runDashboard(options) {
50
50
  }
51
51
  const html = await withInteractiveSpinner('Loading dashboard template', () => readSegment(TEMPLATE));
52
52
  const server = http.createServer(async (req, res) => {
53
+ if (req.url === '/reset' && req.method === 'POST') {
54
+ const rows = clearUsageLedger();
55
+ res.writeHead(200, { 'Content-Type': 'application/json' });
56
+ res.end(JSON.stringify({ ok: true, rows }));
57
+ return;
58
+ }
53
59
  if (req.url === '/data.json') {
54
60
  res.writeHead(200, { 'Content-Type': 'application/json' });
55
61
  res.end(dataJson());
package/cli/doctor.js CHANGED
@@ -3,7 +3,8 @@ import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
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';
5
5
  import { runInteractivePhase } from './interactive.js';
6
- import { ledgerPath, readUsage } from '../extensions/shared/usage-ledger.js';
6
+ import { ledgerPath, readUsage, sessionsDir } from '../extensions/shared/usage-ledger.js';
7
+ import { readRtkGain, rtkDbPath } from '../extensions/shared/rtk-gain.js';
7
8
  import { checkForUpdate } from './update.js';
8
9
  import { readTextIfExists } from '../extensions/lib/utils.js';
9
10
  async function runDoctor() {
@@ -133,6 +134,12 @@ async function runDoctor() {
133
134
  section('Usage');
134
135
  const usageRows = readUsage();
135
136
  check('Usage ledger', usageRows.length > 0, usageRows.length ? `${usageRows.length} rows · ${ledgerPath()}` : ledgerPath());
137
+ section('Records');
138
+ console.log(` Usage ledger (tersio-owned, tersio reset clears): ${ledgerPath()} · ${usageRows.length} rows`);
139
+ console.log(` Session transcripts (host-owned, never touched): ${sessionsDir()}`);
140
+ const rtkDb = rtkDbPath();
141
+ const rtk = readRtkGain();
142
+ console.log(` RTK history (rtk-owned, never touched): ${rtkDb}${rtk.commands ? ` · ${rtk.commands} commands` : ''}`);
136
143
  section('Add-ons');
137
144
  const ruleAge = ruleMtime ? `updated ${relTime(Date.now() - ruleMtime.mtimeMs)}` : '';
138
145
  check('Caveman rule', cavemanRuleText !== null, ruleAge);
package/cli/install.js CHANGED
@@ -3,12 +3,13 @@ import { promises as fs } from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
- import { BUN_BIN_DIR, COMBO_PRESET_MODES, EXTENSIONS_KEY_RE, HOME, IS_WINDOWS, OMP_AGENT_DIR, OMP_PLUGINS_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, SCOPE_MAP, applyUpdate, cavemanDefaultFlag, comboDefaultFlag, command, dryRun, install, ponytailDefaultFlag, profileFlagsGiven, reinstall, rtkDefaultFlag, scopeFlag, verbose, yes, dashboardExport, dashboardPort, debug, ensureExtensionAfterConfigEntry, ensureExtensionInConfig, ensurePonytailConfigValue, execP, parseJsonObject, readPluginsPackage, writeIfChanged, } from './common.js';
7
- import { ask, askInteractiveChoice, askInteractiveConfirm, closeRL, execNetwork, tty, withInteractiveSpinner, } from './interactive.js';
6
+ import { BUN_BIN_DIR, COMBO_PRESET_MODES, EXTENSIONS_KEY_RE, HOME, IS_WINDOWS, OMP_AGENT_DIR, OMP_PLUGINS_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, applyUpdate, cavemanDefaultFlag, comboDefaultFlag, command, dryRun, install, ponytailDefaultFlag, profileFlagsGiven, reinstall, rtkDefaultFlag, verbose, yes, dashboardExport, dashboardPort, debug, ensureExtensionAfterConfigEntry, ensureExtensionInConfig, ensurePonytailConfigValue, execP, parseJsonObject, readPluginsPackage, writeIfChanged, } from './common.js';
7
+ import { askInteractiveChoice, askInteractiveConfirm, closeRL, execNetwork, tty, withInteractiveSpinner, } from './interactive.js';
8
8
  import { printWelcome } from './banner.js';
9
9
  import { checkForUpdate, runLatestUpdate } from './update.js';
10
10
  import { runUninstall } from './uninstall.js';
11
11
  import { runDoctor } from './doctor.js';
12
+ import { runReset } from './reset.js';
12
13
  import { runUsage } from './usage.js';
13
14
  import { runDashboard } from './dashboard.js';
14
15
  import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, fetchJson, findFile, httpsGet, httpsDownload, parseChecksum, readTextIfExists, rtkPlatformSpec, sha256File, } from '../extensions/lib/utils.js';
@@ -24,6 +25,9 @@ const MODE_REINFORCEMENT_INDEX = path.join(EXT_DIR, 'shared', 'mode-reinforcemen
24
25
  const SHARED_TYPES = path.join(EXT_DIR, 'shared', 'types.js');
25
26
  const LIB_UTILS = path.join(EXT_DIR, 'lib', 'utils.js');
26
27
  const SHARED_PLUGIN_SETTINGS = path.join(EXT_DIR, 'shared', 'plugin-settings.js');
28
+ const SHARED_USAGE_LEDGER = path.join(EXT_DIR, 'shared', 'usage-ledger.js');
29
+ const SHARED_PRICING = path.join(EXT_DIR, 'shared', 'pricing.js');
30
+ const SHARED_CARBON = path.join(EXT_DIR, 'shared', 'carbon.js');
27
31
  const PONYTAIL_GITHUB_SPEC = 'github:DietrichGebert/ponytail';
28
32
  const PONYTAIL_NPM_SPEC = '@dietrichgebert/ponytail@latest';
29
33
  async function stepPonytail(pluginsDir, userDir, options) {
@@ -344,6 +348,9 @@ async function stepSharedSessionState(extDir, options) {
344
348
  [SHARED_TYPES, path.join('shared', 'types.js')],
345
349
  [LIB_UTILS, path.join('lib', 'utils.js')],
346
350
  [SHARED_PLUGIN_SETTINGS, path.join('shared', 'plugin-settings.js')],
351
+ [SHARED_USAGE_LEDGER, path.join('shared', 'usage-ledger.js')],
352
+ [SHARED_PRICING, path.join('shared', 'pricing.js')],
353
+ [SHARED_CARBON, path.join('shared', 'carbon.js')],
347
354
  ], 'shared/session-state.js', options);
348
355
  }
349
356
  async function stepModeReinforcement(extDir, ponytailExtPath, options) {
@@ -357,7 +364,7 @@ async function stepRtkSession(extDir, options) {
357
364
  console.log('\n[4/8] Installing RTK session extension...');
358
365
  await copySources(extDir, [[RTK_SESSION_INDEX, path.join('rtk-session', 'index.js')]], 'rtk-session/index.js', options);
359
366
  }
360
- // One rule fetch serves user- and project-level installs; dry runs stay offline
367
+ // One rule fetch serves the install; dry runs stay offline
361
368
  // and fall back to the bundled rule to preview its destination.
362
369
  async function fetchCavemanRule(options) {
363
370
  if (options.dryRun)
@@ -412,52 +419,12 @@ function defaultProfile() {
412
419
  ponytailDefault: 'off',
413
420
  };
414
421
  }
415
- // Determine install scope: reinstall > flag > non-interactive default > prompt.
416
- async function resolveScope() {
417
- if (reinstall) {
418
- console.log(' Scope: user (reinstall)');
419
- return '1';
420
- }
421
- if (scopeFlag) {
422
- const scope = SCOPE_MAP[scopeFlag];
423
- if (!scope) {
424
- console.log(` [fail] Invalid --scope: ${scopeFlag}. Use: user, project, both`);
425
- closeRL();
426
- process.exit(1);
427
- }
428
- console.log(` Scope: ${scopeFlag}`);
429
- return scope;
430
- }
431
- if (install || yes) {
432
- console.log(` Scope: user (${install ? 'install default' : '--scope omitted, defaulting to user with --yes'})`);
433
- return '1';
434
- }
435
- if (tty()) {
436
- const choice = await askInteractiveChoice('Install scope', [
437
- { value: '1', label: 'User-level', hint: 'all OMP sessions' },
438
- { value: '2', label: 'Project-level', hint: 'this repo only' },
439
- { value: '3', label: 'Both' },
440
- ], '1');
441
- if (choice.status === 'selected')
442
- return choice.value;
443
- closeRL();
444
- process.exit(130);
445
- }
446
- console.log('\nInstall scope:');
447
- console.log(' 1) User-level (all OMP sessions)');
448
- console.log(' 2) Project-level (this repo only)');
449
- console.log(' 3) Both');
450
- while (true) {
451
- const answer = (await ask('\nChoose [1-3] (default 1): ')).trim() || '1';
452
- if (answer === '1' || answer === '2' || answer === '3')
453
- return answer;
454
- console.log(` [fail] Invalid scope: ${answer}. Choose 1, 2, or 3.`);
455
- }
456
- }
422
+ // Install is always user-level (all OMP sessions). Project scope was removed:
423
+ // session extensions must live in ~/.omp/agent/extensions to load.
457
424
  async function resolveProfile() {
458
425
  const profile = defaultProfile();
459
426
  // Single interactive prompt: the Combo preset implies all three modes.
460
- // Numbered menu, same pattern as the scope prompt — no typing preset names.
427
+ // Numbered menu — no typing preset names.
461
428
  // Only for a real user at a terminal, only when no default flags were
462
429
  // given, and never for --apply-update runs.
463
430
  if (tty() && !profileFlagsGiven && !applyUpdate && (install || reinstall)) {
@@ -567,12 +534,12 @@ async function runCommandMenu() {
567
534
  }
568
535
  updatePromptDone = true;
569
536
  const choice = await askInteractiveChoice('Tersio — what next?', [
570
- { value: 'install', label: 'Install add-ons', hint: 'user/project scope + combo defaults' },
571
- { value: 'check', label: 'Check for updates', hint: 'compare installed vs latest release' },
572
- { value: 'update', label: 'Update everything', hint: 'CLI plus all ai-addons' },
537
+ { value: 'install', label: 'Install add-ons', hint: 'user scope + combo defaults' },
538
+ { value: 'check', label: 'Check for updates', hint: 'check CLI version, then update ai-addons' },
573
539
  { value: 'doctor', label: 'Doctor', hint: 'verify the installation' },
574
540
  { value: 'usage', label: 'Usage', hint: 'token usage and savings report' },
575
541
  { value: 'gain', label: 'Gain dashboard', hint: 'open the report in your browser' },
542
+ { value: 'reset', label: 'Reset statistics', hint: 'clear the tersio usage ledger' },
576
543
  { value: 'uninstall', label: 'Uninstall', hint: 'remove tersio' },
577
544
  ], 'install');
578
545
  if (choice.status !== 'selected') {
@@ -584,6 +551,8 @@ async function runCommandMenu() {
584
551
  await runInstall();
585
552
  break;
586
553
  case 'check': {
554
+ // One bound flow: report the CLI version, then offer the ai-addons
555
+ // refresh in the same pass — no redundant second "update" option.
587
556
  const latest = await checkForUpdate(true);
588
557
  if (typeof latest === 'string')
589
558
  console.log(` [update] tersio ${latest} available (installed ${PACKAGE_VERSION})`);
@@ -591,13 +560,20 @@ async function runCommandMenu() {
591
560
  console.log(` [warn] could not reach the npm registry — run \`tersio update\` to retry`);
592
561
  else
593
562
  console.log(` [ok] tersio ${PACKAGE_VERSION} is the latest`);
563
+ const go = await askInteractiveConfirm('Update ai-addons now (RTK, Caveman rule, Ponytail)?');
564
+ if (go.status === 'confirmed' && go.value) {
565
+ await runLatestUpdate();
566
+ }
567
+ else if (go.status === 'cancelled') {
568
+ closeRL();
569
+ process.exit(130);
570
+ }
571
+ else {
572
+ console.log(` staying on ${PACKAGE_VERSION} — run \`tersio update\` anytime`);
573
+ }
594
574
  closeRL();
595
575
  break;
596
576
  }
597
- case 'update':
598
- await runLatestUpdate();
599
- closeRL();
600
- break;
601
577
  case 'doctor':
602
578
  await runDoctor();
603
579
  closeRL();
@@ -610,6 +586,10 @@ async function runCommandMenu() {
610
586
  await runDashboard({ port: dashboardPort, open: true, exportFile: dashboardExport });
611
587
  closeRL();
612
588
  break;
589
+ case 'reset':
590
+ await runReset();
591
+ closeRL();
592
+ break;
613
593
  case 'uninstall':
614
594
  await runUninstall();
615
595
  closeRL();
@@ -617,7 +597,10 @@ async function runCommandMenu() {
617
597
  }
618
598
  }
619
599
  async function runInstall() {
620
- if (command === null && tty() && !yes) {
600
+ // Menu-driven installs must fall through: bare `tersio` re-enters here
601
+ // with command === null after the picker, and without this guard the
602
+ // choice loops straight back into runCommandMenu() forever.
603
+ if (command === null && tty() && !yes && !updatePromptDone) {
621
604
  await runCommandMenu();
622
605
  return;
623
606
  }
@@ -657,16 +640,15 @@ async function runInstall() {
657
640
  }
658
641
  }
659
642
  }
660
- const scope = await resolveScope();
643
+ console.log(' Scope: user (all OMP sessions)');
661
644
  // Resolve session defaults: flags > interactive prompt > defaults.
662
645
  const profile = await resolveProfile();
663
646
  const userDir = OMP_AGENT_DIR;
664
647
  const userExtDir = path.join(userDir, 'extensions');
665
- const projectExtDir = path.join(process.cwd(), '.omp', 'extensions');
666
648
  // apply-update is `tersio update`'s payload run: treat it like reinstall so
667
649
  // the add-ons refresh too — Ponytail package via npm, self plugin, RTK
668
650
  // binary (always re-downloaded), and the Caveman rule (always re-fetched).
669
- const options = { dryRun, verbose, yes, scope, reinstall: reinstall || applyUpdate };
651
+ const options = { dryRun, verbose, yes, reinstall: reinstall || applyUpdate };
670
652
  // Check prerequisites
671
653
  console.log('\nPrerequisites:');
672
654
  try {
@@ -677,32 +659,20 @@ async function runInstall() {
677
659
  console.log(' [fail] omp not found — ensure it\'s installed');
678
660
  }
679
661
  const cavemanRule = await fetchCavemanRule(options);
680
- if (scope === '1' || scope === '3') {
681
- console.log('\n--- User-level install ---');
682
- await stepSharedSessionState(userExtDir, options);
683
- await stepPonytail(OMP_PLUGINS_DIR, userDir, options);
684
- const selfPlugin = await stepSelfPlugin(OMP_PLUGINS_DIR, options);
685
- const ponytailExtPath = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
686
- await stepRtk(BUN_BIN_DIR, options);
687
- await stepRtkSession(userExtDir, options);
688
- await stepCaveman(userExtDir, cavemanRule, options);
689
- await stepCombo(userExtDir, options);
690
- await stepTersioCommands(userExtDir, options);
691
- await stepModeReinforcement(userExtDir, ponytailExtPath, options);
692
- await stepUpdater(userExtDir, options);
693
- if (selfPlugin)
694
- await writePluginSettings(profile, options);
695
- await validateConfigExtensions(userDir, options);
696
- }
697
- if (scope === '2' || scope === '3') {
698
- console.log('\n--- Project-level install ---');
699
- await stepSharedSessionState(projectExtDir, options);
700
- await stepRtkSession(projectExtDir, options);
701
- await stepCaveman(projectExtDir, cavemanRule, options);
702
- await stepTersioCommands(projectExtDir, options);
703
- await stepUpdater(projectExtDir, options);
704
- console.log(' [note] Ponytail, RTK binary, and Combo toggle require user-level (global) install');
705
- }
662
+ console.log('\n--- User-level install ---');
663
+ await stepSharedSessionState(userExtDir, options);
664
+ await stepPonytail(OMP_PLUGINS_DIR, userDir, options);
665
+ const selfPlugin = await stepSelfPlugin(OMP_PLUGINS_DIR, options);
666
+ const ponytailExtPath = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
667
+ await stepRtk(BUN_BIN_DIR, options);
668
+ await stepRtkSession(userExtDir, options);
669
+ await stepCaveman(userExtDir, cavemanRule, options);
670
+ await stepCombo(userExtDir, options);
671
+ await stepTersioCommands(userExtDir, options);
672
+ await stepModeReinforcement(userExtDir, ponytailExtPath, options);
673
+ await stepUpdater(userExtDir, options);
674
+ if (selfPlugin)
675
+ await writePluginSettings(profile, options);
706
676
  console.log('\n=== Installation complete ===');
707
677
  console.log('\nNext steps:');
708
678
  console.log(' 1. Restart OMP');
package/cli/reset.js ADDED
@@ -0,0 +1,51 @@
1
+ // cli/reset.ts — clear tersio-owned statistics (usage ledger).
2
+ // Session transcripts (~/.omp/agent/sessions) and the RTK database are
3
+ // host/tool-owned and are never touched; doctor shows where they live.
4
+ import { cancel as clackCancel, confirm as clackConfirm } from '@clack/prompts';
5
+ import { dryRun, yes } from './common.js';
6
+ import { ask, closeRL, tty } from './interactive.js';
7
+ import { clearUsageLedger, ledgerPath, readUsage } from '../extensions/shared/usage-ledger.js';
8
+ import { sessionsDir } from '../extensions/shared/usage-ledger.js';
9
+ import { rtkDbPath } from '../extensions/shared/rtk-gain.js';
10
+ async function runReset() {
11
+ console.log('\n=== Tersio Reset ===\n');
12
+ const rows = readUsage().length;
13
+ console.log(`Will remove: ${rows} usage rows · ${ledgerPath()}`);
14
+ console.log(`Left intact: sessions ${sessionsDir()}, rtk ${rtkDbPath()}`);
15
+ if (dryRun) {
16
+ console.log('[dry-run] ledger kept.');
17
+ return true;
18
+ }
19
+ if (rows === 0) {
20
+ console.log('Nothing to reset — usage ledger is already empty.');
21
+ return true;
22
+ }
23
+ if (!yes) {
24
+ if (tty()) {
25
+ closeRL();
26
+ const confirmedChoice = await clackConfirm({ message: 'Clear the usage ledger?', initialValue: false });
27
+ if (typeof confirmedChoice !== 'boolean') {
28
+ clackCancel('Aborted.');
29
+ closeRL();
30
+ return false;
31
+ }
32
+ if (!confirmedChoice) {
33
+ console.log('Aborted.');
34
+ closeRL();
35
+ return false;
36
+ }
37
+ }
38
+ else {
39
+ const answer = await ask('\nProceed? [y/N]: ');
40
+ if (!answer.toLowerCase().startsWith('y')) {
41
+ console.log('Aborted.');
42
+ closeRL();
43
+ return false;
44
+ }
45
+ }
46
+ }
47
+ const cleared = clearUsageLedger();
48
+ console.log(`[ok] reset — removed ${cleared} usage rows`);
49
+ return true;
50
+ }
51
+ export { runReset };
package/cli/uninstall.js CHANGED
@@ -168,5 +168,4 @@ async function runUninstall(options = {}) {
168
168
  console.log('\nDone. Restart OMP for changes to take effect.');
169
169
  return true;
170
170
  }
171
- const SCOPE_MAP = { user: '1', project: '2', both: '3' };
172
171
  export { runUninstall };
package/cli/update.js CHANGED
@@ -2,7 +2,7 @@
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, SCOPE_MAP, dryRun, execP, parseJsonObject, scopeFlag, verbose, } from './common.js';
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';
6
6
  import { execNetwork } from './interactive.js';
7
7
  import { readTextIfExists } from '../extensions/lib/utils.js';
8
8
  import { refreshPrices } from '../extensions/shared/pricing.js';
@@ -127,13 +127,7 @@ function planLine(name, current, latest) {
127
127
  console.log(` ${name}: ${current ?? 'missing'} → ${latest}`);
128
128
  }
129
129
  async function runLatestUpdate() {
130
- const updateScope = scopeFlag || 'user';
131
- if (!Object.hasOwn(SCOPE_MAP, updateScope)) {
132
- console.error(`[fail] Invalid --scope: ${updateScope}. Use: user, project, both`);
133
- process.exitCode = 1;
134
- return;
135
- }
136
- const forwardedArgs = ['--yes', '--scope', updateScope];
130
+ const forwardedArgs = ['--yes'];
137
131
  if (dryRun)
138
132
  forwardedArgs.push('--dry-run');
139
133
  if (verbose)
package/cli/usage.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // cli/usage.ts — ledger + session-token usage report, tokscale-style.
2
- import { co2GramsFor, energyWhFor, importSessionTokens, priceFor, readUsage, usdCost, } from '../extensions/shared/usage-ledger.js';
3
- import { readRtkGain } from '../extensions/shared/rtk-gain.js';
2
+ import { co2GramsFor, energyWhFor, importSessionTokens, ledgerPath, priceFor, readUsage, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
3
+ import { readRtkGain, rtkDbPath } from '../extensions/shared/rtk-gain.js';
4
4
  import { withInteractiveSpinner } from './interactive.js';
5
5
  import { PACKAGE_VERSION } from './common.js';
6
6
  export function summarizeUsage(rows) {
@@ -43,6 +43,7 @@ export function summarizeUsage(rows) {
43
43
  byDay: session.byDay,
44
44
  byDayModel: session.byDayModel,
45
45
  byTool,
46
+ recent: session.recent,
46
47
  rtkGain: readRtkGain(),
47
48
  usd,
48
49
  priced,
@@ -51,6 +52,7 @@ export function summarizeUsage(rows) {
51
52
  co2g,
52
53
  energyWh,
53
54
  version: PACKAGE_VERSION,
55
+ paths: { ledger: ledgerPath(), sessions: sessionsDir(), rtk: rtkDbPath() },
54
56
  };
55
57
  }
56
58
  function fmt(n) {