@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 +3 -2
- package/cli/banner.js +5 -3
- package/cli/common.js +11 -5
- package/cli/dashboard.js +7 -1
- package/cli/doctor.js +8 -1
- package/cli/install.js +53 -83
- package/cli/reset.js +51 -0
- package/cli/uninstall.js +0 -1
- package/cli/update.js +2 -8
- package/cli/usage.js +4 -2
- package/dashboard/app.js +141 -20
- package/dashboard/styles.css +46 -8
- package/dashboard/template.html +116 -24
- package/extensions/caveman-session/index.js +12 -1
- package/extensions/combo-toggle/index.js +3 -3
- package/extensions/rtk-session/index.js +8 -1
- package/extensions/shared/session-state.js +10 -3
- package/extensions/shared/usage-ledger.js +18 -1
- package/extensions/tersio-commands/index.js +5 -10
- package/package.json +1 -1
- package/tersio.js +10 -3
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
|
|
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`, `--
|
|
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 (
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
7
|
-
import {
|
|
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
|
|
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
|
-
//
|
|
416
|
-
|
|
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
|
|
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
|
|
571
|
-
{ value: 'check', label: 'Check for updates', hint: '
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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
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,
|
|
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
|
|
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) {
|