@krtclcdy/tersio 2.24.0 → 2.24.1
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 +1 -3
- package/cli/banner.js +2 -3
- package/cli/currency.js +3 -7
- package/cli/dashboard.js +167 -3
- package/cli/doctor-fix.js +15 -12
- package/cli/doctor.js +1 -12
- package/cli/hosts.js +7 -11
- package/cli/install.js +113 -71
- package/cli/interactive.js +36 -2
- package/cli/layer.js +1 -3
- package/cli/manifest.js +1 -4
- package/cli/profile.js +9 -7
- package/cli/reset.js +3 -7
- package/cli/rtk-wiring.js +1 -6
- package/cli/settings.js +3 -8
- package/cli/uninstall.js +43 -24
- package/cli/update.js +7 -8
- package/cli/usage.js +15 -5
- package/dashboard/dist/index.html +18 -18
- package/extensions/ai-addons-updater/index.js +6 -5
- package/extensions/ai-addons-updater/index.ts +7 -6
- package/extensions/caveman-session/index.js +14 -29
- package/extensions/caveman-session/index.ts +15 -30
- package/extensions/combo-toggle/index.js +10 -14
- package/extensions/combo-toggle/index.ts +10 -15
- package/extensions/lib/utils.js +4 -0
- package/extensions/lib/utils.ts +3 -0
- package/extensions/rtk-session/index.js +27 -31
- package/extensions/rtk-session/index.ts +25 -29
- package/extensions/shared/carbon.js +3 -8
- package/extensions/shared/carbon.ts +4 -8
- package/extensions/shared/host.js +1 -11
- package/extensions/shared/host.ts +1 -11
- package/extensions/shared/plugin-settings.js +4 -14
- package/extensions/shared/plugin-settings.ts +4 -14
- package/extensions/shared/pricing.js +4 -6
- package/extensions/shared/pricing.ts +4 -6
- package/extensions/shared/rtk-gain.js +2 -7
- package/extensions/shared/rtk-gain.ts +2 -7
- package/extensions/shared/session-state.js +30 -18
- package/extensions/shared/session-state.ts +33 -16
- package/extensions/shared/usage-ledger.js +138 -17
- package/extensions/shared/usage-ledger.ts +143 -19
- package/extensions/shared/usage-store.js +175 -23
- package/extensions/shared/usage-store.ts +171 -22
- package/extensions/tersio-commands/index.js +1 -5
- package/extensions/tersio-commands/index.ts +1 -5
- package/package.json +6 -7
- package/tersio.js +1 -3
package/cli/uninstall.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 { BUN_BIN_DIR, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, RTK_BINARY_NAME, args, dryRun, keepPonytail, removePonytail, removeRtk, yes, debug, writeConfigLines, } from './common.js';
|
|
5
|
-
import { askInteractiveChoice, closeRL, confirmDestructive, tty } from './interactive.js';
|
|
5
|
+
import { askInteractiveChoice, closeRL, confirmDestructive, tty, sayTagged } from './interactive.js';
|
|
6
6
|
import { detectHosts, hostHint, hostLabel, parseHostArg } from './hosts.js';
|
|
7
7
|
import { findHoistedPackage, piAgentDir, readTextIfExists } from '../extensions/lib/utils.js';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
@@ -22,7 +22,7 @@ const PI_TREE_DIRS = [
|
|
|
22
22
|
// One `pi remove`, for both packages the installer adds there.
|
|
23
23
|
async function piRemove(spec, shouldDryRun) {
|
|
24
24
|
if (shouldDryRun) {
|
|
25
|
-
|
|
25
|
+
sayTagged(` [dry-run] would run: pi remove ${spec}`);
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
try {
|
|
@@ -35,11 +35,11 @@ async function piRemove(spec, shouldDryRun) {
|
|
|
35
35
|
resolve();
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
|
-
|
|
38
|
+
sayTagged(` [ok] removed the pi package ${spec}`);
|
|
39
39
|
}
|
|
40
40
|
catch (e) {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
sayTagged(` [fail] pi remove: ${e.message}`);
|
|
42
|
+
sayTagged(` [hint] Manual: pi remove ${spec}`);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
// Only the skill directories the installer copied, never pi's own skills dir.
|
|
@@ -52,13 +52,24 @@ async function removeCopiedPonytailSkills(shouldDryRun) {
|
|
|
52
52
|
await removeUninstallTarget(path.join(piAgentDir(), 'skills', entry.name), shouldDryRun);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
// pi
|
|
56
|
-
|
|
57
|
-
// route, so neither host keeps a package we installed on its behalf.
|
|
58
|
-
async function removePiLayer(host, shouldDryRun, shouldRemovePonytail) {
|
|
55
|
+
// pi removal delegates to pi remove; always list and confirm first.
|
|
56
|
+
async function removePiLayer(host, shouldDryRun, shouldRemovePonytail, shouldRemoveRtk, confirmed) {
|
|
59
57
|
const targets = PI_TREE_DIRS.map((dir) => path.join(piAgentDir(), 'extensions', dir));
|
|
58
|
+
const rtkWiring = path.join(piAgentDir(), 'extensions', 'rtk.ts');
|
|
60
59
|
if (!host.installed && !host.declared) {
|
|
61
|
-
|
|
60
|
+
sayTagged(` [skip] ${host.label} — nothing installed (${host.installCmd})`);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
console.log('Will remove:');
|
|
64
|
+
for (const t of targets)
|
|
65
|
+
console.log(` ${t}`);
|
|
66
|
+
if (shouldRemovePonytail)
|
|
67
|
+
console.log(` npm:${PONYTAIL_PKG} (pi package)`);
|
|
68
|
+
if (shouldRemoveRtk)
|
|
69
|
+
console.log(` ${rtkWiring} (rtk wiring)`);
|
|
70
|
+
console.log(` ${tersioSettingsFile()} (session defaults)`);
|
|
71
|
+
if (!confirmed && !(await confirmDestructive(`Remove Tersio from ${host.label}?`))) {
|
|
72
|
+
closeRL();
|
|
62
73
|
return false;
|
|
63
74
|
}
|
|
64
75
|
if (host.declared)
|
|
@@ -68,6 +79,12 @@ async function removePiLayer(host, shouldDryRun, shouldRemovePonytail) {
|
|
|
68
79
|
await removeCopiedPonytailSkills(shouldDryRun);
|
|
69
80
|
}
|
|
70
81
|
await Promise.all(targets.map((t) => removeUninstallTarget(t, shouldDryRun)));
|
|
82
|
+
// rtk's wiring is a loose file, not one of the tree dirs, so it survived
|
|
83
|
+
// --remove-rtk on pi while OMP removed its copy.
|
|
84
|
+
if (shouldRemoveRtk) {
|
|
85
|
+
await removeUninstallTarget(rtkWiring, shouldDryRun, false);
|
|
86
|
+
await removeUninstallTarget(path.join(BUN_BIN_DIR, RTK_BINARY_NAME), shouldDryRun, false);
|
|
87
|
+
}
|
|
71
88
|
await clearSessionDefaults(shouldDryRun);
|
|
72
89
|
return true;
|
|
73
90
|
}
|
|
@@ -91,11 +108,11 @@ async function updateJsonFile(filePath, mutate, dryRunNote, writeNote, dryRun) {
|
|
|
91
108
|
if (!mutate(data))
|
|
92
109
|
return;
|
|
93
110
|
if (dryRun) {
|
|
94
|
-
|
|
111
|
+
sayTagged(` [dry-run] ${dryRunNote}`);
|
|
95
112
|
return;
|
|
96
113
|
}
|
|
97
114
|
await fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
98
|
-
|
|
115
|
+
sayTagged(` [write] ${writeNote}`);
|
|
99
116
|
}
|
|
100
117
|
function dropKey(section, key) {
|
|
101
118
|
if (!section || typeof section !== 'object')
|
|
@@ -109,14 +126,14 @@ function dropKey(section, key) {
|
|
|
109
126
|
async function removeUninstallTarget(target, shouldDryRun, recursive = true) {
|
|
110
127
|
try {
|
|
111
128
|
if (shouldDryRun) {
|
|
112
|
-
|
|
129
|
+
sayTagged(` [dry-run] would remove ${target}`);
|
|
113
130
|
return;
|
|
114
131
|
}
|
|
115
132
|
if (recursive)
|
|
116
133
|
await fs.rm(target, { recursive: true, force: true });
|
|
117
134
|
else
|
|
118
135
|
await fs.unlink(target);
|
|
119
|
-
|
|
136
|
+
sayTagged(` [rm] ${target}`);
|
|
120
137
|
}
|
|
121
138
|
catch {
|
|
122
139
|
debug(`Could not remove ${target}`);
|
|
@@ -163,14 +180,14 @@ async function runUninstall(options = {}) {
|
|
|
163
180
|
}
|
|
164
181
|
const selected = detectHosts().find((h) => h.id === host);
|
|
165
182
|
if (host === 'pi') {
|
|
166
|
-
const removed = await removePiLayer(selected, shouldDryRun, shouldRemovePonytail);
|
|
183
|
+
const removed = await removePiLayer(selected, shouldDryRun, shouldRemovePonytail, shouldRemoveRtk, confirmed);
|
|
167
184
|
if (removed)
|
|
168
|
-
console.log('\nDone. Restart pi
|
|
185
|
+
console.log('\nDone. Restart pi, then /combo medium.');
|
|
169
186
|
closeRL();
|
|
170
187
|
return removed;
|
|
171
188
|
}
|
|
172
189
|
if (!selected.installed)
|
|
173
|
-
|
|
190
|
+
sayTagged(` [note] ${selected.label} has no tersio install; removing what is left behind.`);
|
|
174
191
|
const extDir = path.join(OMP_AGENT_DIR, 'extensions');
|
|
175
192
|
const configPath = path.join(OMP_AGENT_DIR, 'config.yml');
|
|
176
193
|
const rtkBin = path.join(BUN_BIN_DIR, RTK_BINARY_NAME);
|
|
@@ -222,23 +239,27 @@ async function runUninstall(options = {}) {
|
|
|
222
239
|
});
|
|
223
240
|
if (lines.length !== before) {
|
|
224
241
|
if (shouldDryRun)
|
|
225
|
-
|
|
242
|
+
sayTagged(` [dry-run] would remove ${before - lines.length} config.yml entries`);
|
|
226
243
|
else
|
|
227
244
|
await writeConfigLines(configPath, lines, ` [write] Updated config.yml (removed ${before - lines.length} entries)`);
|
|
228
245
|
}
|
|
229
246
|
}
|
|
247
|
+
// Cleared last: writeConfigLines above would just make a fresh one.
|
|
248
|
+
for (const backup of [`${configPath}.bak`, path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'extensions', 'caveman-session', 'rule.md.bak')]) {
|
|
249
|
+
await removeUninstallTarget(backup, shouldDryRun, false);
|
|
250
|
+
}
|
|
230
251
|
// Remove the bundled Ponytail copy; its config.yml entry is already filtered.
|
|
231
252
|
if (shouldRemovePonytail) {
|
|
232
253
|
const pluginsPkgPath = path.join(pluginsDir, 'package.json');
|
|
233
254
|
await updateJsonFile(pluginsPkgPath, (data) => dropKey(data.dependencies, '@dietrichgebert/ponytail'), `would remove @dietrichgebert/ponytail from ${pluginsPkgPath}`, 'Removed @dietrichgebert/ponytail from plugins/package.json', shouldDryRun);
|
|
234
255
|
try {
|
|
235
256
|
if (shouldDryRun)
|
|
236
|
-
|
|
257
|
+
sayTagged(` [dry-run] would remove ${ponytailPkgDir}`);
|
|
237
258
|
else {
|
|
238
259
|
await fs.rm(ponytailPkgDir, { recursive: true, force: true });
|
|
239
|
-
|
|
260
|
+
sayTagged(` [rm] ${ponytailPkgDir}`);
|
|
240
261
|
await fs.rm(path.dirname(ponytailPkgDir));
|
|
241
|
-
|
|
262
|
+
sayTagged(` [rm] ${path.dirname(ponytailPkgDir)} (empty scope)`);
|
|
242
263
|
}
|
|
243
264
|
}
|
|
244
265
|
catch {
|
|
@@ -254,9 +275,7 @@ async function runUninstall(options = {}) {
|
|
|
254
275
|
if ((await readTextIfExists(path.join(selfPluginDir, 'package.json'))) !== null) {
|
|
255
276
|
await removeUninstallTarget(selfPluginDir, shouldDryRun);
|
|
256
277
|
}
|
|
257
|
-
// Our rtk wiring goes
|
|
258
|
-
// rewriting bash calls to a package this uninstall just removed. The binary
|
|
259
|
-
// stays unless --remove-rtk, since it is a shared tool on PATH.
|
|
278
|
+
// Our rtk wiring goes too, or OMP keeps rewriting for a removed package.
|
|
260
279
|
await removeUninstallTarget(path.join(extDir, 'rtk.ts'), shouldDryRun);
|
|
261
280
|
if (shouldRemoveRtk)
|
|
262
281
|
await removeUninstallTarget(rtkBin, shouldDryRun, false);
|
package/cli/update.js
CHANGED
|
@@ -3,7 +3,7 @@ import { promises as fs } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { IS_WINDOWS, OMP_PLUGINS_DIR, PACKAGE_BIN, PACKAGE_NAME, PACKAGE_VERSION, dryRun, execP, parseJsonObject, verbose, } from './common.js';
|
|
6
|
-
import { execNetwork } from './interactive.js';
|
|
6
|
+
import { execNetwork, sayTagged } from './interactive.js';
|
|
7
7
|
import { readTextIfExists, resolveRtkBinary, tersioDataPath } from '../extensions/lib/utils.js';
|
|
8
8
|
import { refreshPrices } from '../extensions/shared/pricing.js';
|
|
9
9
|
import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, httpsGet, normalizeRtkVersion, sha256Hex, } from '../extensions/lib/utils.js';
|
|
@@ -140,8 +140,8 @@ async function runLatestUpdate() {
|
|
|
140
140
|
const { stale, status } = planLines(plan);
|
|
141
141
|
if (dryRun) {
|
|
142
142
|
console.log(`tersio update (dry-run):\n ${status.join('\n ')}`);
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
sayTagged(` [dry-run] would run: npm install -g ${PACKAGE_NAME}${target} --no-audit --no-fund --prefer-online`);
|
|
144
|
+
sayTagged(` [dry-run] would delegate: npm exec --yes --prefer-online --package=${PACKAGE_NAME}${target} -- tersio --apply-update ${forwardedArgs.join(' ')}`);
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
147
|
console.log(`Checking for updates:\n ${status.join('\n ')}`);
|
|
@@ -186,8 +186,8 @@ async function runLatestUpdate() {
|
|
|
186
186
|
process.stderr.write(globalResult.stderr);
|
|
187
187
|
}
|
|
188
188
|
catch (e) {
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
sayTagged(` [warn] Global CLI update skipped: ${e.message}`);
|
|
190
|
+
sayTagged(` [hint] Manual: npm install -g ${PACKAGE_NAME}${target} --no-audit --no-fund --prefer-online`);
|
|
191
191
|
}
|
|
192
192
|
const npmArgs = [
|
|
193
193
|
'exec',
|
|
@@ -200,9 +200,8 @@ async function runLatestUpdate() {
|
|
|
200
200
|
...forwardedArgs,
|
|
201
201
|
];
|
|
202
202
|
const npmCommandArgs = IS_WINDOWS ? ['/d', '/s', '/c', 'npm', ...npmArgs] : npmArgs;
|
|
203
|
-
// Inherited stdio, no outer spinner
|
|
204
|
-
//
|
|
205
|
-
// line above and the closing summary below — one voice, no repeats.
|
|
203
|
+
// Inherited stdio, no outer spinner; the delegated installer runs quiet and
|
|
204
|
+
// this parent owns both the plan line and the closing summary.
|
|
206
205
|
try {
|
|
207
206
|
await execInherit(npmCommand, npmCommandArgs);
|
|
208
207
|
console.log(`Done — tersio ${plan.cli ?? PACKAGE_VERSION}. Restart OMP.`);
|
package/cli/usage.js
CHANGED
|
@@ -113,6 +113,7 @@ function bar(frac, width = 12) {
|
|
|
113
113
|
const filled = Math.max(0, Math.min(width, Math.round(frac * width)));
|
|
114
114
|
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
115
115
|
}
|
|
116
|
+
const ROW_CAP = 15;
|
|
116
117
|
function pad(s, n) {
|
|
117
118
|
return s.length >= n ? s : s + ' '.repeat(n - s.length);
|
|
118
119
|
}
|
|
@@ -136,10 +137,15 @@ function printReport(report) {
|
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
138
139
|
const t = report.tokens;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const a = report.rtkAdoption;
|
|
141
|
+
for (const l of textTable(['Metric', 'Value'], [
|
|
142
|
+
['Tokens', `${fmt(t.input)} in · ${fmt(t.output)} out · ${fmt(t.cacheRead)} cache read · ${fmt(t.cacheWrite)} cache write`],
|
|
143
|
+
['Cost', `${formatCurrency(report.usd, report.currency)}${report.priced ? '' : ' (includes default pricing)'} · ~${formatCurrency(report.savedUsd, report.currency)} cache-saved (est.) · ~${report.co2g.toFixed(1)}g CO2 (est.)`],
|
|
144
|
+
['RTK adoption', `${fmt(a.rtkCalls)}/${fmt(a.eligibleCalls)} eligible Bash calls use RTK (${a.adoptionPct.toFixed(1)}%) · ${fmt(a.missedCalls)} missed · recall ${report.rtkRecall.available ? `${report.rtkRecall.mode} (${report.rtkRecall.entries})` : 'unavailable'}`],
|
|
145
|
+
], [false, false], 118))
|
|
146
|
+
console.log(l);
|
|
147
|
+
const allModels = Object.entries(report.byModel).filter(([, b]) => b.input + b.output + b.cacheRead + b.cacheWrite > 0).sort((a, b) => (b[1].input + b[1].output) - (a[1].input + a[1].output));
|
|
148
|
+
const models = allModels.slice(0, ROW_CAP);
|
|
143
149
|
if (models.length) {
|
|
144
150
|
console.log(' BY MODEL');
|
|
145
151
|
const top = models.reduce((m, [, b]) => Math.max(m, b.input + b.output + b.cacheRead + b.cacheWrite), 1);
|
|
@@ -152,6 +158,8 @@ function printReport(report) {
|
|
|
152
158
|
for (const l of textTable(['Model', 'Input', 'Output', 'Cache r/w', report.currency, 'Hit', 'Share'], mrows, [false, true, true, true, true, true, false])) {
|
|
153
159
|
console.log(l);
|
|
154
160
|
}
|
|
161
|
+
if (allModels.length > ROW_CAP)
|
|
162
|
+
console.log(` showing top ${ROW_CAP} of ${fmt(allModels.length)} models — full set in the dashboard`);
|
|
155
163
|
}
|
|
156
164
|
const cmdRows = report.byTool.map(([tool, n]) => ({ name: tool, count: n, saved: null, avgPct: null, avgMs: null }));
|
|
157
165
|
for (const r of report.rtkGain.byCommand) {
|
|
@@ -165,7 +173,7 @@ function printReport(report) {
|
|
|
165
173
|
: ' COMMAND TOOLS';
|
|
166
174
|
console.log(scope);
|
|
167
175
|
const top = cmdRows.reduce((m, r) => Math.max(m, r.count), 1);
|
|
168
|
-
const crows = cmdRows.slice(0,
|
|
176
|
+
const crows = cmdRows.slice(0, ROW_CAP).map((r, idx) => [
|
|
169
177
|
String(idx + 1),
|
|
170
178
|
r.name,
|
|
171
179
|
fmt(r.count),
|
|
@@ -177,6 +185,8 @@ function printReport(report) {
|
|
|
177
185
|
for (const l of textTable(['#', 'Tool/Command', 'Count', 'Saved', 'Avg%', 'Time', 'Impact'], crows, [true, false, true, true, true, true, false], 30)) {
|
|
178
186
|
console.log(l);
|
|
179
187
|
}
|
|
188
|
+
if (cmdRows.length > ROW_CAP)
|
|
189
|
+
console.log(` showing top ${ROW_CAP} of ${fmt(cmdRows.length)} commands — full set in the dashboard`);
|
|
180
190
|
}
|
|
181
191
|
}
|
|
182
192
|
async function runUsage() {
|