@krtclcdy/tersio 2.11.0 → 2.13.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 +21 -13
- package/cli/common.js +9 -4
- package/cli/doctor.js +7 -1
- package/cli/install.js +54 -78
- package/cli/rtk-wiring.js +58 -0
- package/cli/uninstall.js +7 -3
- package/cli/update.js +2 -8
- package/dashboard/styles.css +10 -1
- package/dashboard/template.html +13 -4
- package/package.json +1 -1
- package/tersio.js +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
# ✂️ Tersio — Token-saving OMP Add-ons
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ Terse replies, compact shell output, and minimal code decisions for [Oh My Pi (O
|
|
|
13
13
|
## ✨ Features
|
|
14
14
|
|
|
15
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.
|
|
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
17
|
- **Ponytail** — Minimal, YAGNI-oriented code decisions, from nudge (`lite`) to requirement-challenger (`ultra`).
|
|
18
18
|
- **Combo** — Toggles all three at once. Presets: `off`, `medium`, `balanced`, `max`.
|
|
19
19
|
- **Updater** — `/tersio check` and `/tersio update` cover Ponytail, RTK, and Caveman in-session, with dry-run and backups.
|
|
@@ -84,30 +84,35 @@ Measured savings against the same workload without the modes. Token counts are r
|
|
|
84
84
|
|
|
85
85
|
| Measured surface (n) | Baseline → Tersio | Δ |
|
|
86
86
|
|---|---|---|
|
|
87
|
-
| Terse reply, caveman lite — BPE tok (3 samples, p50) |
|
|
88
|
-
| Terse reply, caveman full — BPE tok (3 samples, p50) |
|
|
89
|
-
| Terse reply, caveman ultra — BPE tok (3 samples, p50) |
|
|
90
|
-
| Code diff, ponytail
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
| Terse reply, caveman lite — BPE tok (3 samples, p50) | 173 → 69 tok | **−60.1%** |
|
|
88
|
+
| Terse reply, caveman full — BPE tok (3 samples, p50) | 173 → 49 tok | **−71.7%** |
|
|
89
|
+
| Terse reply, caveman ultra — BPE tok (3 samples, p50) | 173 → 25 tok | **−85.5%** |
|
|
90
|
+
| Code diff, ponytail lite — BPE tok (2 tasks) | 326–462 → 57–121 tok | **−73.8…−82.5%** |
|
|
91
|
+
| Code diff, ponytail full — BPE tok (2 tasks) | 326–462 → 55–88 tok | **−81.0…−83.1%** |
|
|
92
|
+
| Code diff, ponytail ultra — BPE tok (2 tasks) | 326–462 → 40–88 tok | **−81.0…−87.7%** |
|
|
93
|
+
| Shell output, rtk — `git status` — BPE tok | 182 → 72 tok | **−60.4%** |
|
|
94
|
+
| Shell output, rtk — repo `grep` — BPE tok | 376 → 274 tok | **−27.1%** |
|
|
95
|
+
| Shell output, rtk — `find` — BPE tok | 205 → 168 tok | **−18.0%** |
|
|
96
|
+
| Shell output, rtk — `git diff` — BPE tok | 12,684 → 8,504 tok | **−33.0%** |
|
|
97
|
+
| Shell output, rtk — `npm test` (passthrough) — BPE tok | 2,597 → 2,577 tok | **−0.8%** |
|
|
98
|
+
|
|
99
|
+
Break-even math, balanced preset (caveman full + rtk + ponytail): a mixed turn costs 941 tok baseline vs 399 with Tersio (0.42×, **−57.6% per turn**). Overhead repays in turn 1 with the bundled floor (464 tok); turn 4 with the real installed Ponytail plugin (v4.9.0, 1,264 tok). RTK concentrates savings where noise lives — hook-wired sessions record **−97.5% on summarized test suites** (rtk `history.db`, 4 runs).
|
|
95
100
|
|
|
96
101
|
## 🖥️ CLI
|
|
97
102
|
|
|
98
103
|
| Command | Purpose |
|
|
99
104
|
|---|---|
|
|
100
|
-
| `tersio install` | Install (user scope
|
|
105
|
+
| `tersio install` | Install (user scope: all OMP sessions) |
|
|
101
106
|
| `tersio update` | Refresh the CLI, extensions, and add-ons (RTK binary, Caveman rule, Ponytail) |
|
|
102
107
|
| `tersio reinstall` | Fresh install, preserving the Ponytail package |
|
|
103
108
|
| `tersio doctor` | Check OMP, extension, Ponytail, and RTK health |
|
|
104
109
|
| `tersio usage` | Ledger-backed usage + savings report |
|
|
105
110
|
| `tersio gain` | Open the gain dashboard (`--open`, `--export <file>`, `--port <n>`; serves localhost only) |
|
|
106
111
|
| `tersio reset` | Clear tersio statistics (usage ledger; `--dry-run`, `--yes`) — sessions and RTK history stay intact |
|
|
107
|
-
| `tersio uninstall` | Remove extensions, registration, and the Ponytail plugin (`--keep-ponytail` keeps Ponytail; `--remove-rtk` also removes the RTK binary) |
|
|
112
|
+
| `tersio uninstall` | Remove extensions, registration, and the Ponytail plugin (`--keep-ponytail` keeps Ponytail; `--remove-rtk` also removes the RTK binary and its `rtk.ts` OMP wiring) |
|
|
108
113
|
| `tersio version` | Print version |
|
|
109
114
|
|
|
110
|
-
Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--
|
|
115
|
+
Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--combo-default`/`--caveman-default`/`--rtk-default`/`--ponytail-default`. Legacy `--doctor` / `--uninstall` forms still work.
|
|
111
116
|
|
|
112
117
|
## ⌨️ Commands reference
|
|
113
118
|
|
|
@@ -132,6 +137,7 @@ Everything runs under one root. Bare `/tersio` prints status.
|
|
|
132
137
|
| What | Path |
|
|
133
138
|
|---|---|
|
|
134
139
|
| Caveman / RTK / Updater / Combo extensions | `~/.omp/agent/extensions/{caveman-session,rtk-session,ai-addons-updater,combo-toggle}/` |
|
|
140
|
+
| RTK OMP wiring (rtk-owned) | `~/.omp/agent/extensions/rtk.ts` — written by the installer via `rtk init -g --agent omp`; auto-loads, no config entry |
|
|
135
141
|
| Ponytail package | `~/.omp/plugins/node_modules/@dietrichgebert/ponytail/` |
|
|
136
142
|
| RTK binary | `~/.bun/bin/rtk` (`rtk.exe` on Windows) |
|
|
137
143
|
| Extension registrations | `~/.omp/agent/config.yml` |
|
|
@@ -146,6 +152,8 @@ User-level installs also register the package in `~/.omp/plugins` (visible in OM
|
|
|
146
152
|
|
|
147
153
|
**RTK missing or not executable:** run `tersio reinstall`, then `tersio doctor`. On Linux/macOS: `chmod +x ~/.bun/bin/rtk`.
|
|
148
154
|
|
|
155
|
+
**RTK commands not metered in the gain dashboard:** check `tersio doctor` — the `RTK OMP wiring (rtk.ts)` row must be ok. Missing: run `rtk init -g --agent omp` (needs rtk ≥ 0.49) and restart OMP. Native tool calls (`read`/`edit`/`eval`) stay unmetered — only bash tool calls pass through rtk.
|
|
156
|
+
|
|
149
157
|
**Checksum warning or failure:** the installer aborts on RTK checksum mismatch but warns and continues when checksum metadata is unavailable; `/tersio update rtk` aborts when metadata is missing.
|
|
150
158
|
|
|
151
159
|
## 🤝🏻 Contributing
|
package/cli/common.js
CHANGED
|
@@ -48,7 +48,7 @@ function flagValue(name) {
|
|
|
48
48
|
// --- CLI flags ---
|
|
49
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';
|
|
@@ -71,7 +71,13 @@ const uninstall = command === 'uninstall' || args.includes('--uninstall');
|
|
|
71
71
|
const removePonytail = args.includes('--remove-ponytail');
|
|
72
72
|
const keepPonytail = args.includes('--keep-ponytail');
|
|
73
73
|
const removeRtk = args.includes('--remove-rtk');
|
|
74
|
-
|
|
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
|
+
}
|
|
75
81
|
// --- Profile selection (session-start mode defaults) ---
|
|
76
82
|
const comboDefaultFlag = parseEnum(flagValue('--combo-default'), COMBO_DEFAULTS, '--combo-default');
|
|
77
83
|
const cavemanDefaultFlag = parseEnum(flagValue('--caveman-default'), CAVEMAN_DEFAULTS, '--caveman-default');
|
|
@@ -239,7 +245,6 @@ async function readPluginsPackage(pkgPath) {
|
|
|
239
245
|
const pkg = parseJsonObject(await readTextIfExists(pkgPath)) ?? {};
|
|
240
246
|
return { ...pkg, name: pkg.name || 'omp-plugins', private: true, dependencies: pkg.dependencies || {} };
|
|
241
247
|
}
|
|
242
|
-
const SCOPE_MAP = { user: '1', project: '2', both: '3' };
|
|
243
248
|
function relTime(ageMs) {
|
|
244
249
|
const mins = Math.floor(ageMs / 60000);
|
|
245
250
|
if (mins < 1)
|
|
@@ -255,4 +260,4 @@ function relTime(ageMs) {
|
|
|
255
260
|
const weeks = Math.floor(days / 7);
|
|
256
261
|
return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
|
|
257
262
|
}
|
|
258
|
-
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,
|
|
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/doctor.js
CHANGED
|
@@ -39,6 +39,7 @@ async function runDoctor() {
|
|
|
39
39
|
pluginsPkgRaw: readTextIfExists(path.join(pluginsDir, 'package.json')),
|
|
40
40
|
selfPkgText: readTextIfExists(selfPkg),
|
|
41
41
|
rtkBinText: readTextIfExists(rtkBin),
|
|
42
|
+
rtkOmpText: readTextIfExists(path.join(extDir, 'rtk.ts')),
|
|
42
43
|
cavemanIndexText: readTextIfExists(cavemanIndex),
|
|
43
44
|
cavemanRuleText: readTextIfExists(cavemanRule),
|
|
44
45
|
rtkIndexText: readTextIfExists(rtkIndex),
|
|
@@ -75,12 +76,13 @@ async function runDoctor() {
|
|
|
75
76
|
probes.pluginsPkgRaw,
|
|
76
77
|
probes.selfPkgText,
|
|
77
78
|
]));
|
|
78
|
-
const [cavemanRuleText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, ponytailMtime] = await runInteractivePhase('Checking add-ons', () => Promise.all([
|
|
79
|
+
const [cavemanRuleText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, rtkOmpText, ponytailMtime] = await runInteractivePhase('Checking add-ons', () => Promise.all([
|
|
79
80
|
probes.cavemanRuleText,
|
|
80
81
|
probes.ruleMtime,
|
|
81
82
|
probes.rtkBinText,
|
|
82
83
|
probes.rtkMtime,
|
|
83
84
|
rtkVersionProbe,
|
|
85
|
+
probes.rtkOmpText,
|
|
84
86
|
probes.ponytailMtime,
|
|
85
87
|
]));
|
|
86
88
|
// Categorized output with a tally; section headers group related probes.
|
|
@@ -151,6 +153,10 @@ async function runDoctor() {
|
|
|
151
153
|
check('RTK binary', true, bits);
|
|
152
154
|
if (!rtkVersion)
|
|
153
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'));
|
|
154
160
|
}
|
|
155
161
|
const ponytailAge = ponytailMtime ? `updated ${relTime(Date.now() - ponytailMtime.mtimeMs)}` : '';
|
|
156
162
|
check('Ponytail', ponytailPkgText !== null, [parseJsonObject(ponytailPkgText)?.version ?? '', ponytailAge].filter(Boolean).join(', '));
|
package/cli/install.js
CHANGED
|
@@ -3,8 +3,8 @@ 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';
|
|
@@ -12,6 +12,7 @@ import { runDoctor } from './doctor.js';
|
|
|
12
12
|
import { runReset } from './reset.js';
|
|
13
13
|
import { runUsage } from './usage.js';
|
|
14
14
|
import { runDashboard } from './dashboard.js';
|
|
15
|
+
import { wireRtkOmp } from './rtk-wiring.js';
|
|
15
16
|
import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, fetchJson, findFile, httpsGet, httpsDownload, parseChecksum, readTextIfExists, rtkPlatformSpec, sha256File, } from '../extensions/lib/utils.js';
|
|
16
17
|
// Paths to extension source files (relative to this script)
|
|
17
18
|
const EXT_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'extensions');
|
|
@@ -25,6 +26,9 @@ const MODE_REINFORCEMENT_INDEX = path.join(EXT_DIR, 'shared', 'mode-reinforcemen
|
|
|
25
26
|
const SHARED_TYPES = path.join(EXT_DIR, 'shared', 'types.js');
|
|
26
27
|
const LIB_UTILS = path.join(EXT_DIR, 'lib', 'utils.js');
|
|
27
28
|
const SHARED_PLUGIN_SETTINGS = path.join(EXT_DIR, 'shared', 'plugin-settings.js');
|
|
29
|
+
const SHARED_USAGE_LEDGER = path.join(EXT_DIR, 'shared', 'usage-ledger.js');
|
|
30
|
+
const SHARED_PRICING = path.join(EXT_DIR, 'shared', 'pricing.js');
|
|
31
|
+
const SHARED_CARBON = path.join(EXT_DIR, 'shared', 'carbon.js');
|
|
28
32
|
const PONYTAIL_GITHUB_SPEC = 'github:DietrichGebert/ponytail';
|
|
29
33
|
const PONYTAIL_NPM_SPEC = '@dietrichgebert/ponytail@latest';
|
|
30
34
|
async function stepPonytail(pluginsDir, userDir, options) {
|
|
@@ -231,6 +235,15 @@ async function verifyRtkArchive(archivePath, assetName, checksumsText) {
|
|
|
231
235
|
function shortError(e) {
|
|
232
236
|
return ((e.stderr) || e.message || '').trim().slice(0, 200);
|
|
233
237
|
}
|
|
238
|
+
async function fileExists(p) {
|
|
239
|
+
try {
|
|
240
|
+
await fs.access(p);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
234
247
|
async function extractRtkArchive(archivePath, extractDir) {
|
|
235
248
|
await fs.mkdir(extractDir, { recursive: true });
|
|
236
249
|
if (archivePath.endsWith('.zip')) {
|
|
@@ -260,6 +273,9 @@ async function extractRtkArchive(archivePath, extractDir) {
|
|
|
260
273
|
}
|
|
261
274
|
async function stepRtk(binDir, options) {
|
|
262
275
|
console.log('\n[3/8] Installing RTK binary...');
|
|
276
|
+
const binDest = path.join(binDir, RTK_BINARY_NAME);
|
|
277
|
+
// Download failure must not skip wiring: a pre-existing rtk binary is
|
|
278
|
+
// exactly as good for the OMP hook, so wire whatever ends up at binDest.
|
|
263
279
|
try {
|
|
264
280
|
const release = await withInteractiveSpinner('Finding latest RTK release', () => fetchJson(RTK_RELEASE_API));
|
|
265
281
|
const triple = resolveRtkTriple();
|
|
@@ -268,7 +284,6 @@ async function stepRtk(binDir, options) {
|
|
|
268
284
|
const asset = findRtkAsset(release, triple);
|
|
269
285
|
if (!asset)
|
|
270
286
|
return;
|
|
271
|
-
const binDest = path.join(binDir, RTK_BINARY_NAME);
|
|
272
287
|
if (options.dryRun) {
|
|
273
288
|
console.log(` [dry-run] would download ${asset.name} from release ${release.tag_name}`);
|
|
274
289
|
console.log(' [dry-run] would verify checksum against checksums.txt');
|
|
@@ -305,7 +320,7 @@ async function stepRtk(binDir, options) {
|
|
|
305
320
|
debug(`chmod 755 ${binDest}`);
|
|
306
321
|
}
|
|
307
322
|
try {
|
|
308
|
-
const v = (await execP(binDest, ['--version'], { timeout:
|
|
323
|
+
const v = (await execP(binDest, ['--version'], { timeout: 30000, shell: false })).stdout.trim();
|
|
309
324
|
console.log(` [ok] ${binDest} → ${v}`);
|
|
310
325
|
}
|
|
311
326
|
catch {
|
|
@@ -320,6 +335,17 @@ async function stepRtk(binDir, options) {
|
|
|
320
335
|
console.log(` [fail] RTK: ${e.message}`);
|
|
321
336
|
console.log(' [hint] Manual: https://github.com/rtk-ai/rtk/releases');
|
|
322
337
|
}
|
|
338
|
+
if (options.dryRun) {
|
|
339
|
+
await wireRtkOmp(binDest, options);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (!(await fileExists(binDest))) {
|
|
343
|
+
console.log(' [skip] no rtk binary to wire — install rtk, then run: rtk init -g --agent omp');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// Binary alone never meters OMP sessions — wire rtk's tool_call
|
|
347
|
+
// extension so bash commands rewrite to rtk and land in history.db.
|
|
348
|
+
await wireRtkOmp(binDest, options);
|
|
323
349
|
}
|
|
324
350
|
// Copy repo source files into the target extension dir. First entry is
|
|
325
351
|
// required (skip label on missing); rest are optional companions.
|
|
@@ -345,6 +371,9 @@ async function stepSharedSessionState(extDir, options) {
|
|
|
345
371
|
[SHARED_TYPES, path.join('shared', 'types.js')],
|
|
346
372
|
[LIB_UTILS, path.join('lib', 'utils.js')],
|
|
347
373
|
[SHARED_PLUGIN_SETTINGS, path.join('shared', 'plugin-settings.js')],
|
|
374
|
+
[SHARED_USAGE_LEDGER, path.join('shared', 'usage-ledger.js')],
|
|
375
|
+
[SHARED_PRICING, path.join('shared', 'pricing.js')],
|
|
376
|
+
[SHARED_CARBON, path.join('shared', 'carbon.js')],
|
|
348
377
|
], 'shared/session-state.js', options);
|
|
349
378
|
}
|
|
350
379
|
async function stepModeReinforcement(extDir, ponytailExtPath, options) {
|
|
@@ -358,7 +387,7 @@ async function stepRtkSession(extDir, options) {
|
|
|
358
387
|
console.log('\n[4/8] Installing RTK session extension...');
|
|
359
388
|
await copySources(extDir, [[RTK_SESSION_INDEX, path.join('rtk-session', 'index.js')]], 'rtk-session/index.js', options);
|
|
360
389
|
}
|
|
361
|
-
// One rule fetch serves
|
|
390
|
+
// One rule fetch serves the install; dry runs stay offline
|
|
362
391
|
// and fall back to the bundled rule to preview its destination.
|
|
363
392
|
async function fetchCavemanRule(options) {
|
|
364
393
|
if (options.dryRun)
|
|
@@ -413,52 +442,12 @@ function defaultProfile() {
|
|
|
413
442
|
ponytailDefault: 'off',
|
|
414
443
|
};
|
|
415
444
|
}
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
if (reinstall) {
|
|
419
|
-
console.log(' Scope: user (reinstall)');
|
|
420
|
-
return '1';
|
|
421
|
-
}
|
|
422
|
-
if (scopeFlag) {
|
|
423
|
-
const scope = SCOPE_MAP[scopeFlag];
|
|
424
|
-
if (!scope) {
|
|
425
|
-
console.log(` [fail] Invalid --scope: ${scopeFlag}. Use: user, project, both`);
|
|
426
|
-
closeRL();
|
|
427
|
-
process.exit(1);
|
|
428
|
-
}
|
|
429
|
-
console.log(` Scope: ${scopeFlag}`);
|
|
430
|
-
return scope;
|
|
431
|
-
}
|
|
432
|
-
if (install || yes) {
|
|
433
|
-
console.log(` Scope: user (${install ? 'install default' : '--scope omitted, defaulting to user with --yes'})`);
|
|
434
|
-
return '1';
|
|
435
|
-
}
|
|
436
|
-
if (tty()) {
|
|
437
|
-
const choice = await askInteractiveChoice('Install scope', [
|
|
438
|
-
{ value: '1', label: 'User-level', hint: 'all OMP sessions' },
|
|
439
|
-
{ value: '2', label: 'Project-level', hint: 'this repo only' },
|
|
440
|
-
{ value: '3', label: 'Both' },
|
|
441
|
-
], '1');
|
|
442
|
-
if (choice.status === 'selected')
|
|
443
|
-
return choice.value;
|
|
444
|
-
closeRL();
|
|
445
|
-
process.exit(130);
|
|
446
|
-
}
|
|
447
|
-
console.log('\nInstall scope:');
|
|
448
|
-
console.log(' 1) User-level (all OMP sessions)');
|
|
449
|
-
console.log(' 2) Project-level (this repo only)');
|
|
450
|
-
console.log(' 3) Both');
|
|
451
|
-
while (true) {
|
|
452
|
-
const answer = (await ask('\nChoose [1-3] (default 1): ')).trim() || '1';
|
|
453
|
-
if (answer === '1' || answer === '2' || answer === '3')
|
|
454
|
-
return answer;
|
|
455
|
-
console.log(` [fail] Invalid scope: ${answer}. Choose 1, 2, or 3.`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
445
|
+
// Install is always user-level (all OMP sessions). Project scope was removed:
|
|
446
|
+
// session extensions must live in ~/.omp/agent/extensions to load.
|
|
458
447
|
async function resolveProfile() {
|
|
459
448
|
const profile = defaultProfile();
|
|
460
449
|
// Single interactive prompt: the Combo preset implies all three modes.
|
|
461
|
-
// Numbered menu
|
|
450
|
+
// Numbered menu — no typing preset names.
|
|
462
451
|
// Only for a real user at a terminal, only when no default flags were
|
|
463
452
|
// given, and never for --apply-update runs.
|
|
464
453
|
if (tty() && !profileFlagsGiven && !applyUpdate && (install || reinstall)) {
|
|
@@ -568,7 +557,7 @@ async function runCommandMenu() {
|
|
|
568
557
|
}
|
|
569
558
|
updatePromptDone = true;
|
|
570
559
|
const choice = await askInteractiveChoice('Tersio — what next?', [
|
|
571
|
-
{ value: 'install', label: 'Install add-ons', hint: 'user
|
|
560
|
+
{ value: 'install', label: 'Install add-ons', hint: 'user scope + combo defaults' },
|
|
572
561
|
{ value: 'check', label: 'Check for updates', hint: 'check CLI version, then update ai-addons' },
|
|
573
562
|
{ value: 'doctor', label: 'Doctor', hint: 'verify the installation' },
|
|
574
563
|
{ value: 'usage', label: 'Usage', hint: 'token usage and savings report' },
|
|
@@ -674,16 +663,15 @@ async function runInstall() {
|
|
|
674
663
|
}
|
|
675
664
|
}
|
|
676
665
|
}
|
|
677
|
-
|
|
666
|
+
console.log(' Scope: user (all OMP sessions)');
|
|
678
667
|
// Resolve session defaults: flags > interactive prompt > defaults.
|
|
679
668
|
const profile = await resolveProfile();
|
|
680
669
|
const userDir = OMP_AGENT_DIR;
|
|
681
670
|
const userExtDir = path.join(userDir, 'extensions');
|
|
682
|
-
const projectExtDir = path.join(process.cwd(), '.omp', 'extensions');
|
|
683
671
|
// apply-update is `tersio update`'s payload run: treat it like reinstall so
|
|
684
672
|
// the add-ons refresh too — Ponytail package via npm, self plugin, RTK
|
|
685
673
|
// binary (always re-downloaded), and the Caveman rule (always re-fetched).
|
|
686
|
-
const options = { dryRun, verbose, yes,
|
|
674
|
+
const options = { dryRun, verbose, yes, reinstall: reinstall || applyUpdate };
|
|
687
675
|
// Check prerequisites
|
|
688
676
|
console.log('\nPrerequisites:');
|
|
689
677
|
try {
|
|
@@ -694,32 +682,20 @@ async function runInstall() {
|
|
|
694
682
|
console.log(' [fail] omp not found — ensure it\'s installed');
|
|
695
683
|
}
|
|
696
684
|
const cavemanRule = await fetchCavemanRule(options);
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
await writePluginSettings(profile, options);
|
|
712
|
-
await validateConfigExtensions(userDir, options);
|
|
713
|
-
}
|
|
714
|
-
if (scope === '2' || scope === '3') {
|
|
715
|
-
console.log('\n--- Project-level install ---');
|
|
716
|
-
await stepSharedSessionState(projectExtDir, options);
|
|
717
|
-
await stepRtkSession(projectExtDir, options);
|
|
718
|
-
await stepCaveman(projectExtDir, cavemanRule, options);
|
|
719
|
-
await stepTersioCommands(projectExtDir, options);
|
|
720
|
-
await stepUpdater(projectExtDir, options);
|
|
721
|
-
console.log(' [note] Ponytail, RTK binary, and Combo toggle require user-level (global) install');
|
|
722
|
-
}
|
|
685
|
+
console.log('\n--- User-level install ---');
|
|
686
|
+
await stepSharedSessionState(userExtDir, options);
|
|
687
|
+
await stepPonytail(OMP_PLUGINS_DIR, userDir, options);
|
|
688
|
+
const selfPlugin = await stepSelfPlugin(OMP_PLUGINS_DIR, options);
|
|
689
|
+
const ponytailExtPath = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
|
|
690
|
+
await stepRtk(BUN_BIN_DIR, options);
|
|
691
|
+
await stepRtkSession(userExtDir, options);
|
|
692
|
+
await stepCaveman(userExtDir, cavemanRule, options);
|
|
693
|
+
await stepCombo(userExtDir, options);
|
|
694
|
+
await stepTersioCommands(userExtDir, options);
|
|
695
|
+
await stepModeReinforcement(userExtDir, ponytailExtPath, options);
|
|
696
|
+
await stepUpdater(userExtDir, options);
|
|
697
|
+
if (selfPlugin)
|
|
698
|
+
await writePluginSettings(profile, options);
|
|
723
699
|
console.log('\n=== Installation complete ===');
|
|
724
700
|
console.log('\nNext steps:');
|
|
725
701
|
console.log(' 1. Restart OMP');
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// cli/rtk-wiring.ts — wire the installed rtk binary into OMP.
|
|
2
|
+
// `rtk init -g --agent omp` writes rtk's tool_call extension to
|
|
3
|
+
// ~/.omp/agent/extensions/rtk.ts; OMP auto-loads it and rewrites bash
|
|
4
|
+
// commands to rtk before execution, so every rewritten call lands in
|
|
5
|
+
// rtk's history.db and shows metered savings in the gain dashboard.
|
|
6
|
+
// Fail-open: a failed wire only downgrades to manual `rtk` prefixing.
|
|
7
|
+
// Deliberately free of cli/common.ts imports (argv side effects) so tests
|
|
8
|
+
// can load it directly.
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
|
+
function shortWiringError(e) {
|
|
11
|
+
const err = e;
|
|
12
|
+
const why = [err.code !== undefined ? `code ${err.code}` : '', err.signal ?? '', err.stderr?.trim(), err.message]
|
|
13
|
+
.filter(Boolean).join(' · ');
|
|
14
|
+
return why.slice(0, 200);
|
|
15
|
+
}
|
|
16
|
+
function execFileP(cmd, args, timeout) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
execFile(cmd, args, { timeout, encoding: 'utf8' }, (err, stdout, stderr) => {
|
|
19
|
+
if (err) {
|
|
20
|
+
err.stderr = String(stderr || '');
|
|
21
|
+
reject(err);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
resolve({ stdout: String(stdout), stderr: String(stderr) });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// Idempotent: rtk rewrites its extension file on every init run, so
|
|
30
|
+
// reinstalling tersio refreshes the wiring for free.
|
|
31
|
+
export async function wireRtkOmp(rtkBin, options = {}) {
|
|
32
|
+
console.log(' Wiring rtk → OMP (bash tool_call rewrite)...');
|
|
33
|
+
if (options.dryRun) {
|
|
34
|
+
console.log(' [dry-run] would run: rtk init -g --agent omp');
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
await execFileP(rtkBin, ['init', '-g', '--agent', 'omp'], 30000);
|
|
39
|
+
console.log(' [ok] rtk OMP extension wired (~/.omp/agent/extensions/rtk.ts)');
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (first) {
|
|
43
|
+
// A freshly written binary can lose its first exec to macOS Gatekeeper
|
|
44
|
+
// verification; give it one settle-and-retry before reporting failure.
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
46
|
+
try {
|
|
47
|
+
await execFileP(rtkBin, ['init', '-g', '--agent', 'omp'], 30000);
|
|
48
|
+
console.log(' [ok] rtk OMP extension wired (~/.omp/agent/extensions/rtk.ts)');
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
void first;
|
|
53
|
+
console.log(` [warn] could not wire rtk → OMP: ${shortWiringError(e)}`);
|
|
54
|
+
console.log(' [hint] Manual: rtk init -g --agent omp (needs rtk >= 0.49)');
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
package/cli/uninstall.js
CHANGED
|
@@ -88,6 +88,7 @@ async function runUninstall(options = {}) {
|
|
|
88
88
|
}
|
|
89
89
|
if (shouldRemoveRtk) {
|
|
90
90
|
console.log(` ${rtkBin}`);
|
|
91
|
+
console.log(` ${path.join(extDir, 'rtk.ts')} (rtk OMP wiring)`);
|
|
91
92
|
}
|
|
92
93
|
if (!confirmed) {
|
|
93
94
|
if (tty()) {
|
|
@@ -162,11 +163,14 @@ async function runUninstall(options = {}) {
|
|
|
162
163
|
if ((await readTextIfExists(path.join(selfPluginDir, 'package.json'))) !== null) {
|
|
163
164
|
await removeUninstallTarget(selfPluginDir, shouldDryRun);
|
|
164
165
|
}
|
|
165
|
-
// Remove RTK binary if requested
|
|
166
|
-
|
|
166
|
+
// Remove RTK binary if requested. rtk.ts is rtk-owned but installed by our
|
|
167
|
+
// wiring step; with the binary gone it would pass through harmlessly, so
|
|
168
|
+
// only drop it on a full rtk removal.
|
|
169
|
+
if (shouldRemoveRtk) {
|
|
167
170
|
await removeUninstallTarget(rtkBin, shouldDryRun, false);
|
|
171
|
+
await removeUninstallTarget(path.join(extDir, 'rtk.ts'), shouldDryRun);
|
|
172
|
+
}
|
|
168
173
|
console.log('\nDone. Restart OMP for changes to take effect.');
|
|
169
174
|
return true;
|
|
170
175
|
}
|
|
171
|
-
const SCOPE_MAP = { user: '1', project: '2', both: '3' };
|
|
172
176
|
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,
|
|
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/dashboard/styles.css
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
--cell-0: rgba(24, 24, 27, .07);
|
|
6
6
|
--cell-1: rgba(4, 120, 87, .3); --cell-2: rgba(4, 120, 87, .55);
|
|
7
7
|
--cell-3: rgba(4, 120, 87, .8); --cell-4: #047857;
|
|
8
|
+
--danger: #dc2626; --danger-border: rgba(220, 38, 38, .35); --danger-soft: rgba(220, 38, 38, .05);
|
|
8
9
|
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.07);
|
|
9
10
|
}
|
|
10
11
|
html[data-theme="light"] { color-scheme: light; }
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
--cell-0: rgba(255, 255, 255, .07);
|
|
18
19
|
--cell-1: rgba(52, 211, 153, .3); --cell-2: rgba(52, 211, 153, .55);
|
|
19
20
|
--cell-3: rgba(52, 211, 153, .8); --cell-4: #34d399;
|
|
21
|
+
--danger: #f87171; --danger-border: rgba(248, 113, 113, .3); --danger-soft: rgba(248, 113, 113, .07);
|
|
20
22
|
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.55);
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
--cell-0: rgba(255, 255, 255, .07);
|
|
37
39
|
--cell-1: rgba(52, 211, 153, .3); --cell-2: rgba(52, 211, 153, .55);
|
|
38
40
|
--cell-3: rgba(52, 211, 153, .8); --cell-4: #34d399;
|
|
41
|
+
--danger: #f87171; --danger-border: rgba(248, 113, 113, .3); --danger-soft: rgba(248, 113, 113, .07);
|
|
39
42
|
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.55);
|
|
40
43
|
}
|
|
41
44
|
.ticker { animation: tick 36s linear infinite; will-change: transform; }
|
|
@@ -172,6 +175,7 @@
|
|
|
172
175
|
}
|
|
173
176
|
/* Empty states (shadcn pattern): dashed well, icon chip, title + hint. */
|
|
174
177
|
.empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 2px; border: 1px dashed var(--line); border-radius: 12px; padding: 32px 16px; }
|
|
178
|
+
.empty.hidden { display: none; } /* beats .empty flex: same-file order, higher specificity than tailwind .hidden */
|
|
175
179
|
.empty-icon { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 999px; background: var(--track); color: var(--dim); margin-bottom: 10px; }
|
|
176
180
|
.empty-title { font-weight: 600; font-size: 14px; color: var(--ink); margin: 0; }
|
|
177
181
|
.empty-desc { font-size: 12px; color: var(--dim); margin: 0; max-width: 340px; }
|
|
@@ -184,11 +188,16 @@
|
|
|
184
188
|
.dlg-desc { font-size: 12px; color: var(--dim); margin: 2px 0 0; }
|
|
185
189
|
.dlg-label { font-size: 11px; text-transform: uppercase; letter-spacing: .14em; color: var(--dim); margin: 18px 0 8px; }
|
|
186
190
|
.dlg-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
191
|
+
.dlg-row + .dlg-row { margin-top: 14px; }
|
|
187
192
|
.dlg-name { font-size: 13px; font-weight: 600; margin: 0; }
|
|
188
193
|
.dlg-hint { font-size: 12px; color: var(--dim); margin: 2px 0 0; }
|
|
189
194
|
.dlg-own { font-weight: 400; font-size: 11px; color: var(--dim); }
|
|
190
195
|
.dlg-paths { margin-top: 12px; display: grid; gap: 8px; }
|
|
191
|
-
.dlg-path { border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; }
|
|
196
|
+
.dlg-path { border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; min-width: 0; overflow: hidden; }
|
|
192
197
|
.dlg-file { font-size: 11px; color: var(--dim); margin: 4px 0 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
193
198
|
#settingsBtn:hover { box-shadow: 0 0 14px var(--accent-soft); border-color: var(--accent); }
|
|
199
|
+
.dlg-danger { margin-top: 16px; border: 1px solid var(--danger-border); background: var(--danger-soft); border-radius: 12px; padding: 12px; }
|
|
200
|
+
.dlg-danger .dlg-label { color: var(--danger); margin: 0 0 10px; }
|
|
201
|
+
#reset { color: var(--danger); }
|
|
202
|
+
#reset:hover { border-color: var(--danger); box-shadow: 0 0 14px var(--danger-border); }
|
|
194
203
|
.dlg .seg button { display: grid; place-items: center; }
|
package/dashboard/template.html
CHANGED
|
@@ -36,9 +36,6 @@
|
|
|
36
36
|
<span id="clock">--:--</span>
|
|
37
37
|
</span>
|
|
38
38
|
<span class="hidden sm:block w-px h-5 shrink-0" style="background: var(--line)" aria-hidden="true"></span>
|
|
39
|
-
<button id="reset" class="btn-push mono flex shrink-0 items-center gap-2 text-xs pl-2.5 pr-3 py-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Reset tersio statistics">
|
|
40
|
-
<i data-lucide="rotate-ccw" class="size-3.5"></i><span class="hidden md:inline" id="resetLabel">Reset</span>
|
|
41
|
-
</button>
|
|
42
39
|
<button id="settingsBtn" class="btn-push flex shrink-0 items-center p-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Open settings">
|
|
43
40
|
<i data-lucide="settings" class="size-4"></i>
|
|
44
41
|
</button>
|
|
@@ -49,7 +46,7 @@
|
|
|
49
46
|
<div class="dlg-head">
|
|
50
47
|
<div class="min-w-0">
|
|
51
48
|
<p class="dlg-title" id="settingsTitle">Settings</p>
|
|
52
|
-
<p class="dlg-desc">Appearance, data
|
|
49
|
+
<p class="dlg-desc">Appearance, data, and where statistics live.</p>
|
|
53
50
|
</div>
|
|
54
51
|
<button id="settingsClose" class="btn-push flex shrink-0 items-center p-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Close settings">
|
|
55
52
|
<i data-lucide="x" class="size-4"></i>
|
|
@@ -91,6 +88,18 @@
|
|
|
91
88
|
<p class="mono dlg-file" id="pathRtk" title="">–</p>
|
|
92
89
|
</div>
|
|
93
90
|
</div>
|
|
91
|
+
<div class="dlg-danger">
|
|
92
|
+
<p class="dlg-label">Danger zone</p>
|
|
93
|
+
<div class="dlg-row">
|
|
94
|
+
<div class="min-w-0">
|
|
95
|
+
<p class="dlg-name">Reset statistics</p>
|
|
96
|
+
<p class="dlg-hint">Clears the usage ledger. Click twice to confirm.</p>
|
|
97
|
+
</div>
|
|
98
|
+
<button id="reset" class="btn-push mono flex shrink-0 items-center gap-2 text-xs pl-2.5 pr-3 py-2 rounded-xl" style="border: 1px solid var(--danger-border)" aria-label="Reset tersio statistics">
|
|
99
|
+
<i data-lucide="rotate-ccw" class="size-3.5"></i><span id="resetLabel">Reset</span>
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
94
103
|
</dialog>
|
|
95
104
|
|
|
96
105
|
<!-- Hero: centered total -->
|
package/package.json
CHANGED
package/tersio.js
CHANGED
|
@@ -15,7 +15,7 @@ function printHelp() {
|
|
|
15
15
|
console.log(`Usage: ${PACKAGE_BIN} [command] [options]
|
|
16
16
|
|
|
17
17
|
Commands:
|
|
18
|
-
install Install the add-ons (user scope
|
|
18
|
+
install Install the add-ons (user scope: all OMP sessions)
|
|
19
19
|
update Refresh the CLI and add-ons (RTK binary, Caveman rule, Ponytail)
|
|
20
20
|
reinstall Clean and reinstall the user-scope add-ons
|
|
21
21
|
doctor Check the current installation
|
|
@@ -27,7 +27,7 @@ Commands:
|
|
|
27
27
|
help Show this help
|
|
28
28
|
|
|
29
29
|
Options:
|
|
30
|
-
--scope user
|
|
30
|
+
--scope user (legacy; accepted and ignored — user scope is the only scope)
|
|
31
31
|
--keep-ponytail (uninstall: keep the Ponytail plugin — removed by default)
|
|
32
32
|
--remove-rtk (uninstall: also remove the RTK binary)
|
|
33
33
|
--combo-default off|medium|balanced|max (implies caveman, rtk, ponytail)
|