@krtclcdy/tersio 2.22.0 → 2.23.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 +38 -30
- package/cli/common.js +43 -39
- package/cli/currency.js +2 -2
- package/cli/dashboard.js +60 -116
- package/cli/doctor-fix.js +35 -29
- package/cli/doctor.js +26 -15
- package/cli/install.js +15 -34
- package/cli/profile.js +2 -1
- package/cli/settings.js +6 -6
- package/cli/update.js +12 -11
- package/cli/usage.js +6 -1
- package/dashboard/dist/index.html +65 -0
- package/extensions/ai-addons-updater/index.js +7 -3
- package/extensions/ai-addons-updater/index.ts +15 -3
- package/extensions/caveman-session/index.js +17 -12
- package/extensions/caveman-session/index.ts +17 -12
- package/extensions/caveman-session/rule.md +90 -0
- package/extensions/combo-toggle/index.js +22 -2
- package/extensions/combo-toggle/index.ts +26 -2
- package/extensions/lib/utils.js +20 -4
- package/extensions/lib/utils.ts +18 -4
- package/extensions/rtk-session/index.js +11 -10
- package/extensions/rtk-session/index.ts +10 -11
- package/extensions/shared/plugin-settings.js +37 -3
- package/extensions/shared/plugin-settings.ts +34 -3
- package/extensions/shared/rtk-gain.js +2 -2
- package/extensions/shared/rtk-gain.ts +3 -3
- package/extensions/shared/session-state.js +5 -3
- package/extensions/shared/session-state.ts +4 -3
- package/extensions/shared/types.ts +5 -0
- package/extensions/shared/usage-ledger.js +114 -4
- package/extensions/shared/usage-ledger.ts +122 -4
- package/extensions/tersio-commands/index.js +10 -13
- package/extensions/tersio-commands/index.ts +10 -13
- package/package.json +21 -12
- package/tersio.js +10 -10
- package/dashboard/charts.js +0 -1208
- package/dashboard/core.js +0 -357
- package/dashboard/settings.js +0 -166
- package/dashboard/share.js +0 -218
- package/dashboard/styles.css +0 -379
- package/dashboard/template.html +0 -515
- package/extensions/shared/mode-reinforcement.ts +0 -29
- package/gain/dist/brand.webp +0 -0
- package/gain/dist/index.html +0 -65
- package/gain/dist/vite.svg +0 -1
- /package/dashboard/{brand.webp → dist/brand.webp} +0 -0
package/cli/doctor-fix.js
CHANGED
|
@@ -2,7 +2,7 @@ import { promises as fs } from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { BUN_BIN_DIR, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, dryRun, verbose, debug,
|
|
5
|
+
import { BUN_BIN_DIR, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, removeExtensionFromConfig, dryRun, verbose, debug, execP, readPluginsPackage, writeIfChanged, } from './common.js';
|
|
6
6
|
import { execNetwork } from './interactive.js';
|
|
7
7
|
import { wireRtkOmp, ensureRtkInConfig } from './rtk-wiring.js';
|
|
8
8
|
import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, fetchJson, findFile, httpsGet, httpsDownload, parseChecksum, readTextIfExists, rtkPlatformSpec, sha256File, } from '../extensions/lib/utils.js';
|
|
@@ -17,33 +17,40 @@ const SOURCES = [
|
|
|
17
17
|
[path.join(EXT_DIR, 'shared', 'pricing.ts'), path.join('shared', 'pricing.ts')],
|
|
18
18
|
[path.join(EXT_DIR, 'shared', 'carbon.ts'), path.join('shared', 'carbon.ts')],
|
|
19
19
|
[path.join(EXT_DIR, 'caveman-session', 'index.ts'), path.join('caveman-session', 'index.ts')],
|
|
20
|
+
[path.join(EXT_DIR, 'caveman-session', 'rule.md'), path.join('caveman-session', 'rule.md')],
|
|
20
21
|
[path.join(EXT_DIR, 'rtk-session', 'index.ts'), path.join('rtk-session', 'index.ts')],
|
|
21
22
|
[path.join(EXT_DIR, 'ai-addons-updater', 'index.ts'), path.join('ai-addons-updater', 'index.ts')],
|
|
22
23
|
[path.join(EXT_DIR, 'combo-toggle', 'index.ts'), path.join('combo-toggle', 'index.ts')],
|
|
23
24
|
[path.join(EXT_DIR, 'tersio-commands', 'index.ts'), path.join('tersio-commands', 'index.ts')],
|
|
24
|
-
[path.join(EXT_DIR, 'shared', 'mode-reinforcement.ts'), path.join('shared', 'mode-reinforcement.ts')],
|
|
25
25
|
];
|
|
26
|
-
async function fixExtensions(
|
|
27
|
-
console.log(' Doctor --fix: restoring extension files');
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
for (
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
async function fixExtensions(pluginsDir) {
|
|
27
|
+
console.log(' Doctor --fix: restoring plugin extension files');
|
|
28
|
+
const tersioPluginDir = path.join(pluginsDir, 'node_modules', '@krtclcdy', 'tersio');
|
|
29
|
+
const pluginExtDir = path.join(tersioPluginDir, 'extensions');
|
|
30
|
+
await fs.mkdir(pluginExtDir, { recursive: true });
|
|
31
|
+
for (const [from, to] of SOURCES) {
|
|
32
|
+
const text = await readTextIfExists(from);
|
|
33
|
+
if (text === null)
|
|
34
|
+
console.log(` [warn] bundled source missing: ${from}`);
|
|
34
35
|
else
|
|
35
|
-
|
|
36
|
+
await writeIfChanged(path.join(pluginExtDir, to), text, { dryRun, verbose });
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
if ((await readTextIfExists(
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
const ruleDest = path.join(pluginExtDir, 'caveman-session', 'rule.md');
|
|
39
|
+
if ((await readTextIfExists(ruleDest)) === null) {
|
|
40
|
+
const bundled = await readTextIfExists(path.join(EXT_DIR, 'caveman-session', 'rule.md'));
|
|
41
|
+
if (bundled !== null) {
|
|
42
|
+
await writeIfChanged(ruleDest, bundled, { dryRun, verbose });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const rule = await Promise.race([
|
|
46
|
+
httpsGet(CAVEMAN_REMOTE_RULE).catch(() => null),
|
|
47
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 8000)),
|
|
48
|
+
]);
|
|
49
|
+
if (rule !== null)
|
|
50
|
+
await writeIfChanged(ruleDest, rule, { dryRun, verbose });
|
|
51
|
+
else
|
|
52
|
+
console.log(' [warn] Caveman rule unreachable and no bundled rule exists');
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
async function fixRegistrations(agentDir, pluginsDir) {
|
|
@@ -58,13 +65,13 @@ async function fixRegistrations(agentDir, pluginsDir) {
|
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
const extDir = path.join(agentDir, 'extensions');
|
|
61
|
-
const ponytailExtPath = path.join(pluginsDir, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
|
|
62
|
-
await ensureExtensionInConfig(configPath, ponytailExtPath, 'ponytail', { dryRun, verbose });
|
|
63
|
-
await ensureExtensionInConfig(configPath, path.join(extDir, 'combo-toggle', 'index.ts'), 'combo', { dryRun, verbose });
|
|
64
68
|
const rtkTs = path.join(extDir, 'rtk.ts');
|
|
65
69
|
if ((await readTextIfExists(rtkTs)) !== null)
|
|
66
70
|
await ensureRtkInConfig({ dryRun, verbose });
|
|
67
|
-
|
|
71
|
+
const ponytailExtPath = path.join(pluginsDir, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
|
|
72
|
+
await removeExtensionFromConfig(configPath, path.join(extDir, 'combo-toggle', 'index.ts'), 'manifest-loaded combo', { dryRun, verbose });
|
|
73
|
+
await removeExtensionFromConfig(configPath, ponytailExtPath, 'manifest-loaded ponytail', { dryRun, verbose });
|
|
74
|
+
await removeExtensionFromConfig(configPath, path.join(extDir, 'shared', 'mode-reinforcement.ts'), 'retired mode reinforcement', { dryRun, verbose });
|
|
68
75
|
const pkgPath = path.join(pluginsDir, 'package.json');
|
|
69
76
|
const pkg = await readPluginsPackage(pkgPath);
|
|
70
77
|
if (!(PACKAGE_NAME in pkg.dependencies)) {
|
|
@@ -127,7 +134,7 @@ async function fixRtk(binDir) {
|
|
|
127
134
|
if (!(await wireRtkOmp(binDest, { dryRun, verbose })))
|
|
128
135
|
throw new Error('rtk wiring failed');
|
|
129
136
|
}
|
|
130
|
-
async function fixPonytail(pluginsDir
|
|
137
|
+
async function fixPonytail(pluginsDir) {
|
|
131
138
|
console.log(' Doctor --fix: restoring bundled Ponytail');
|
|
132
139
|
await fs.mkdir(pluginsDir, { recursive: true });
|
|
133
140
|
const pkgPath = path.join(pluginsDir, 'package.json');
|
|
@@ -147,7 +154,6 @@ async function fixPonytail(pluginsDir, agentDir) {
|
|
|
147
154
|
await execNetwork('Restoring bundled Ponytail', 'npm', ['install', '--no-audit', '--no-fund'], { cwd: pluginsDir, timeout: 180000 }).catch((e) => {
|
|
148
155
|
throw new Error(`bundled ponytail restore failed: ${e.message}`);
|
|
149
156
|
});
|
|
150
|
-
await ensureExtensionInConfig(path.join(agentDir, 'config.yml'), path.join(pluginsDir, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js'), 'ponytail', { dryRun, verbose });
|
|
151
157
|
}
|
|
152
158
|
async function fixCli() {
|
|
153
159
|
console.log(' Doctor --fix: updating CLI + add-ons');
|
|
@@ -171,13 +177,13 @@ async function runDoctorRepairs(targets) {
|
|
|
171
177
|
}
|
|
172
178
|
};
|
|
173
179
|
if (want('extensions'))
|
|
174
|
-
await attempt('extensions', () => fixExtensions(
|
|
180
|
+
await attempt('extensions', () => fixExtensions(OMP_PLUGINS_DIR));
|
|
175
181
|
if (want('registrations'))
|
|
176
182
|
await attempt('registrations', () => fixRegistrations(OMP_AGENT_DIR, OMP_PLUGINS_DIR));
|
|
177
183
|
if (want('rtk'))
|
|
178
184
|
await attempt('rtk', () => fixRtk(BUN_BIN_DIR));
|
|
179
185
|
if (want('ponytail'))
|
|
180
|
-
await attempt('ponytail', () => fixPonytail(OMP_PLUGINS_DIR
|
|
186
|
+
await attempt('ponytail', () => fixPonytail(OMP_PLUGINS_DIR));
|
|
181
187
|
if (want('cli'))
|
|
182
188
|
await attempt('cli', fixCli);
|
|
183
189
|
return failed;
|
package/cli/doctor.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// cli/doctor.ts — installation health checks.
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { OMP_AGENT_DIR, OMP_PLUGINS_DIR, args, dryRun, fix, yes, execP, parseJsonObject, relTime, } from './common.js';
|
|
5
5
|
import { askInteractiveChoice, askInteractiveConfirm, runInteractivePhase } from './interactive.js';
|
|
6
6
|
import { usageDbPath } from '../extensions/shared/usage-store.js';
|
|
7
7
|
import { pricesCachePath } from '../extensions/shared/pricing.js';
|
|
8
|
-
import { readTextIfExists } from '../extensions/lib/utils.js';
|
|
8
|
+
import { readTextIfExists, resolveRtkBinary } from '../extensions/lib/utils.js';
|
|
9
9
|
async function runDoctor(recheck = false) {
|
|
10
10
|
console.log('\n=== Tersio Doctor ===');
|
|
11
11
|
// Directories
|
|
@@ -13,38 +13,39 @@ async function runDoctor(recheck = false) {
|
|
|
13
13
|
const extDir = path.join(agentDir, 'extensions');
|
|
14
14
|
const configPath = path.join(agentDir, 'config.yml');
|
|
15
15
|
const pluginsDir = OMP_PLUGINS_DIR;
|
|
16
|
-
const rtkBin =
|
|
16
|
+
const rtkBin = resolveRtkBinary();
|
|
17
17
|
// Ponytail
|
|
18
18
|
const ponytailPkg = path.join(pluginsDir, 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
19
19
|
const ponytailExt = path.join(pluginsDir, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
20
|
+
const tersioPluginDir = path.join(pluginsDir, 'node_modules', '@krtclcdy', 'tersio');
|
|
21
|
+
const cavemanIndex = path.join(tersioPluginDir, 'extensions', 'caveman-session', 'index.ts');
|
|
22
|
+
const cavemanRule = path.join(tersioPluginDir, 'extensions', 'caveman-session', 'rule.md');
|
|
23
|
+
const rtkIndex = path.join(tersioPluginDir, 'extensions', 'rtk-session', 'index.ts');
|
|
24
|
+
const updaterIndex = path.join(tersioPluginDir, 'extensions', 'ai-addons-updater', 'index.ts');
|
|
24
25
|
// Independent probes start concurrently; sections report in fixed order as
|
|
25
26
|
// their data settles. Every probe resolves instead of rejecting.
|
|
26
27
|
const probes = {
|
|
27
28
|
agentEntries: fs.readdir(agentDir).catch(() => null),
|
|
28
29
|
extEntries: fs.readdir(extDir).catch(() => null),
|
|
29
|
-
sharedStateText: readTextIfExists(path.join(
|
|
30
|
+
sharedStateText: readTextIfExists(path.join(tersioPluginDir, 'extensions', 'shared', 'session-state.ts')),
|
|
30
31
|
configText: readTextIfExists(configPath),
|
|
31
32
|
ponytailPkgText: readTextIfExists(ponytailPkg),
|
|
32
33
|
ponytailExtText: readTextIfExists(ponytailExt),
|
|
33
|
-
rtkBinText: readTextIfExists(rtkBin),
|
|
34
|
+
rtkBinText: rtkBin ? readTextIfExists(rtkBin) : Promise.resolve(null),
|
|
34
35
|
rtkOmpText: readTextIfExists(path.join(extDir, 'rtk.ts')),
|
|
35
36
|
cavemanIndexText: readTextIfExists(cavemanIndex),
|
|
36
37
|
cavemanRuleText: readTextIfExists(cavemanRule),
|
|
37
38
|
rtkIndexText: readTextIfExists(rtkIndex),
|
|
38
39
|
updaterIndexText: readTextIfExists(updaterIndex),
|
|
40
|
+
rtkMtime: rtkBin ? fs.stat(rtkBin).catch(() => null) : Promise.resolve(null),
|
|
39
41
|
ruleMtime: fs.stat(cavemanRule).catch(() => null),
|
|
40
|
-
rtkMtime: fs.stat(rtkBin).catch(() => null),
|
|
41
42
|
ponytailMtime: fs.stat(ponytailPkg).catch(() => null),
|
|
42
43
|
pricesMtime: fs.stat(pricesCachePath()).catch(() => null),
|
|
43
44
|
};
|
|
44
|
-
const rtkVersionProbe =
|
|
45
|
+
const rtkVersionProbe = rtkBin ? execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => r.stdout.trim() || r.stderr.trim() || null, (e) => {
|
|
45
46
|
const err = e;
|
|
46
47
|
return err.stdout?.trim() || err.stderr?.trim() || null;
|
|
47
|
-
}));
|
|
48
|
+
}) : Promise.resolve(null);
|
|
48
49
|
const [[agentEntries, extEntries, sharedStateText, configText], [cavemanIndexText, rtkIndexText, updaterIndexText, ponytailPkgText, ponytailExtText], [cavemanRuleText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, rtkOmpText, ponytailMtime, pricesMtime]] = await runInteractivePhase('Checking installation', () => Promise.all([
|
|
49
50
|
Promise.all([
|
|
50
51
|
probes.agentEntries,
|
|
@@ -101,6 +102,16 @@ async function runDoctor(recheck = false) {
|
|
|
101
102
|
check('OMP config.yml', configText !== null, configText === null ? configPath : '');
|
|
102
103
|
check('Shared session bridge', sharedStateText !== null);
|
|
103
104
|
section('Extensions & plugins');
|
|
105
|
+
const explicitEntries = (configText ?? '').split('\n')
|
|
106
|
+
.map((line) => line.trim().replace(/^-\s*/, '').replace(/^['"]|['"]$/g, ''))
|
|
107
|
+
.filter((line) => line.startsWith('/') || line.startsWith('.'));
|
|
108
|
+
const duplicateExtensions = [...new Set(explicitEntries.filter((entry, index) => explicitEntries.indexOf(entry) !== index))];
|
|
109
|
+
const retiredReinforcement = explicitEntries.filter((entry) => entry.endsWith('/shared/mode-reinforcement.ts')).length;
|
|
110
|
+
check('Unique config registrations', duplicateExtensions.length === 0, duplicateExtensions.join(', '));
|
|
111
|
+
if (retiredReinforcement === 0)
|
|
112
|
+
check('No retired reinforcement', true);
|
|
113
|
+
else
|
|
114
|
+
warnLine('Retired reinforcement registration', `${retiredReinforcement} found; run doctor --fix registrations`);
|
|
104
115
|
check('Caveman extension', cavemanIndexText !== null);
|
|
105
116
|
check('RTK extension', rtkIndexText !== null);
|
|
106
117
|
check('Updater extension', updaterIndexText !== null);
|
|
@@ -113,7 +124,7 @@ async function runDoctor(recheck = false) {
|
|
|
113
124
|
const ruleAge = ruleMtime ? `(updated ${relTime(Date.now() - ruleMtime.mtimeMs)} · ${absDate(ruleMtime.mtimeMs)})` : '';
|
|
114
125
|
check('Caveman rule', cavemanRuleText !== null, ruleAge);
|
|
115
126
|
const rtkAge = rtkMtime ? `(updated ${relTime(Date.now() - rtkMtime.mtimeMs)} · ${absDate(rtkMtime.mtimeMs)})` : '';
|
|
116
|
-
check('RTK binary',
|
|
127
|
+
check('RTK binary', rtkBin !== null, rtkBinText === null ? 'not found in PATH' : [rtkVersion, rtkAge].filter(Boolean).join(' '));
|
|
117
128
|
if (rtkBinText !== null && !rtkVersion)
|
|
118
129
|
warnLine('RTK version', 'unavailable — binary may not be executable');
|
|
119
130
|
const rtkRegistered = rtkOmpText !== null && (configText ?? '').includes('extensions/rtk.ts');
|
|
@@ -164,7 +175,7 @@ async function runDoctorFix(tally) {
|
|
|
164
175
|
if (tally.warn > 0)
|
|
165
176
|
problems.push(`${tally.warn} warn`);
|
|
166
177
|
if (dryRun) {
|
|
167
|
-
console.log(`\n [dry-run] would repair ${problems.join(' + ')} (extensions, config.yml
|
|
178
|
+
console.log(`\n [dry-run] would repair ${problems.join(' + ')} (extensions, stale config.yml entries, rtk wiring, ponytail refresh, CLI update)`);
|
|
168
179
|
return;
|
|
169
180
|
}
|
|
170
181
|
const scoped = fixScope();
|
|
@@ -186,7 +197,7 @@ async function runDoctorFix(tally) {
|
|
|
186
197
|
const picked = await askInteractiveChoice('Doctor — what should I repair?', [
|
|
187
198
|
{ value: 'all', label: 'Fix everything', hint: `${tally.missing + tally.warn} rows` },
|
|
188
199
|
{ value: 'extensions', label: 'Missing extension files', hint: 'copy sources from this CLI' },
|
|
189
|
-
{ value: 'registrations', label: 'config.yml registrations', hint: '
|
|
200
|
+
{ value: 'registrations', label: 'config.yml registrations', hint: 'remove stale entries; keep manifest-owned extensions out' },
|
|
190
201
|
{ value: 'rtk', label: 'RTK binary + wiring', hint: 'download checksum-verified binary, rtk init' },
|
|
191
202
|
{ value: 'ponytail', label: 'Ponytail package', hint: 'bundled reinstall via tersio dep' },
|
|
192
203
|
{ value: 'cli', label: 'CLI update', hint: 'latest tersio + delegated refresh' },
|
package/cli/install.js
CHANGED
|
@@ -3,14 +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, 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,
|
|
6
|
+
import { BUN_BIN_DIR, COMBO_PRESET_MODES, 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, ensurePonytailConfigValue, execP, parseJsonObject, readPluginsPackage, writeIfChanged, } from './common.js';
|
|
7
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
12
|
import { runReset } from './reset.js';
|
|
13
|
-
import { runSettings } from './settings.js';
|
|
14
13
|
import { runUsage } from './usage.js';
|
|
15
14
|
import { runDashboard } from './dashboard.js';
|
|
16
15
|
import { wireRtkOmp } from './rtk-wiring.js';
|
|
@@ -20,18 +19,18 @@ import { storedProfile, writePluginSettings } from './profile.js';
|
|
|
20
19
|
const EXT_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'extensions');
|
|
21
20
|
const SHARED_SESSION_STATE = path.join(EXT_DIR, 'shared', 'session-state.ts');
|
|
22
21
|
const CAVEMAN_INDEX = path.join(EXT_DIR, 'caveman-session', 'index.ts');
|
|
22
|
+
const CAVEMAN_RULE = path.join(EXT_DIR, 'caveman-session', 'rule.md');
|
|
23
23
|
const RTK_SESSION_INDEX = path.join(EXT_DIR, 'rtk-session', 'index.ts');
|
|
24
24
|
const UPDATER_INDEX = path.join(EXT_DIR, 'ai-addons-updater', 'index.ts');
|
|
25
25
|
const COMBO_TOGGLE_INDEX = path.join(EXT_DIR, 'combo-toggle', 'index.ts');
|
|
26
26
|
const TERSIO_COMMANDS_INDEX = path.join(EXT_DIR, 'tersio-commands', 'index.ts');
|
|
27
|
-
const MODE_REINFORCEMENT_INDEX = path.join(EXT_DIR, 'shared', 'mode-reinforcement.ts');
|
|
28
27
|
const SHARED_TYPES = path.join(EXT_DIR, 'shared', 'types.ts');
|
|
29
28
|
const LIB_UTILS = path.join(EXT_DIR, 'lib', 'utils.ts');
|
|
30
29
|
const SHARED_PLUGIN_SETTINGS = path.join(EXT_DIR, 'shared', 'plugin-settings.ts');
|
|
31
30
|
const SHARED_USAGE_LEDGER = path.join(EXT_DIR, 'shared', 'usage-ledger.ts');
|
|
32
31
|
const SHARED_PRICING = path.join(EXT_DIR, 'shared', 'pricing.ts');
|
|
33
32
|
const SHARED_CARBON = path.join(EXT_DIR, 'shared', 'carbon.ts');
|
|
34
|
-
async function stepPonytail(pluginsDir,
|
|
33
|
+
async function stepPonytail(pluginsDir, options) {
|
|
35
34
|
if (!options.quiet)
|
|
36
35
|
console.log(' Ponytail — ensure bundled plugin');
|
|
37
36
|
await fs.mkdir(pluginsDir, { recursive: true });
|
|
@@ -92,12 +91,8 @@ async function stepPonytail(pluginsDir, userDir, options) {
|
|
|
92
91
|
console.log(' [hint] The /ponytail command won\'t work, but ponytail skills will still load');
|
|
93
92
|
}
|
|
94
93
|
else if (!options.dryRun && !options.quiet) {
|
|
95
|
-
|
|
96
|
-
debug('Bundled Ponytail pi-extension found');
|
|
94
|
+
debug('Bundled Ponytail pi-extension found; OMP loads it from the plugin manifest');
|
|
97
95
|
}
|
|
98
|
-
// Still set Ponytail config defaults even without the extension command
|
|
99
|
-
const configPath = path.join(userDir, 'config.yml');
|
|
100
|
-
await ensureExtensionInConfig(configPath, ponytailExtPath, 'ponytail', options);
|
|
101
96
|
await ensurePonytailConfigValue('defaultMode', 'off', options);
|
|
102
97
|
// hideStatus=true keeps the upstream ponytail bar hidden; combo owns the bar.
|
|
103
98
|
await ensurePonytailConfigValue('hideStatus', true, options);
|
|
@@ -355,14 +350,6 @@ async function stepSharedSessionState(extDir, options) {
|
|
|
355
350
|
[SHARED_CARBON, path.join('shared', 'carbon.ts')],
|
|
356
351
|
], 'shared/session-state.js', options);
|
|
357
352
|
}
|
|
358
|
-
async function stepModeReinforcement(extDir, ponytailExtPath, options) {
|
|
359
|
-
if (!options.quiet)
|
|
360
|
-
console.log(' Session helpers — sync shared files');
|
|
361
|
-
const dest = path.join(extDir, 'shared', 'mode-reinforcement.ts');
|
|
362
|
-
if (!await copySources(extDir, [[MODE_REINFORCEMENT_INDEX, path.join('shared', 'mode-reinforcement.ts')]], 'shared/mode-reinforcement.ts', options))
|
|
363
|
-
return;
|
|
364
|
-
await ensureExtensionAfterConfigEntry(path.join(path.dirname(extDir), 'config.yml'), dest, ponytailExtPath, 'mode reinforcement', options);
|
|
365
|
-
}
|
|
366
353
|
async function stepRtkSession(extDir, options) {
|
|
367
354
|
if (!options.quiet)
|
|
368
355
|
console.log(' RTK session — install session mode');
|
|
@@ -371,12 +358,17 @@ async function stepRtkSession(extDir, options) {
|
|
|
371
358
|
// One rule fetch serves the install; dry runs stay offline
|
|
372
359
|
// and fall back to the bundled rule to preview its destination.
|
|
373
360
|
async function fetchCavemanRule(options) {
|
|
361
|
+
const bundled = await readTextIfExists(CAVEMAN_RULE);
|
|
374
362
|
if (options.dryRun)
|
|
375
|
-
return
|
|
363
|
+
return bundled;
|
|
376
364
|
try {
|
|
377
365
|
return await withInteractiveSpinner('Fetching Caveman rule', () => httpsGet(CAVEMAN_REMOTE_RULE));
|
|
378
366
|
}
|
|
379
367
|
catch (e) {
|
|
368
|
+
if (bundled !== null) {
|
|
369
|
+
debug(`Using bundled Caveman rule: ${e.message}`);
|
|
370
|
+
return bundled;
|
|
371
|
+
}
|
|
380
372
|
console.log(` [warn] Could not fetch caveman rule: ${e.message}`);
|
|
381
373
|
return null;
|
|
382
374
|
}
|
|
@@ -411,12 +403,7 @@ async function stepUpdater(extDir, options) {
|
|
|
411
403
|
async function stepCombo(extDir, options) {
|
|
412
404
|
if (!options.quiet)
|
|
413
405
|
console.log(' Combo — install preset switch');
|
|
414
|
-
|
|
415
|
-
if (!await copySources(extDir, [[COMBO_TOGGLE_INDEX, path.join('combo-toggle', 'index.ts')]], 'combo-toggle/index.ts', options))
|
|
416
|
-
return;
|
|
417
|
-
// Auto-register combo in config.yml
|
|
418
|
-
const configPath = path.join(path.dirname(extDir), 'config.yml');
|
|
419
|
-
await ensureExtensionInConfig(configPath, dest, 'combo', options);
|
|
406
|
+
await copySources(extDir, [[COMBO_TOGGLE_INDEX, path.join('combo-toggle', 'index.ts')]], 'combo-toggle/index.ts', options);
|
|
420
407
|
}
|
|
421
408
|
async function resolveProfile(forceReinstall = false, opts = {}) {
|
|
422
409
|
// Seed from the lock file so flag-less update/reinstall runs keep the
|
|
@@ -484,9 +471,8 @@ async function runCommandMenu() {
|
|
|
484
471
|
{ value: 'reinstall', label: 'Reinstall', hint: 'clean and reinstall the add-ons, Ponytail package kept' },
|
|
485
472
|
{ value: 'doctor', label: 'Doctor', hint: 'verify the installation' },
|
|
486
473
|
{ value: 'usage', label: 'Usage', hint: 'token usage and savings report' },
|
|
487
|
-
{ value: '
|
|
474
|
+
{ value: 'dashboard', label: 'Dashboard', hint: 'open the report in your browser' },
|
|
488
475
|
{ value: 'reset', label: 'Reset statistics', hint: 'clear statistics; transcripts and RTK history stay' },
|
|
489
|
-
{ value: 'settings', label: 'Settings', hint: 'defaults: combo, caveman, rtk, ponytail, currency' },
|
|
490
476
|
{ value: 'uninstall', label: 'Uninstall', hint: 'remove tersio' },
|
|
491
477
|
], 'install');
|
|
492
478
|
if (choice.status !== 'selected') {
|
|
@@ -545,7 +531,7 @@ async function runCommandMenu() {
|
|
|
545
531
|
await runUsage();
|
|
546
532
|
closeRL();
|
|
547
533
|
break;
|
|
548
|
-
case '
|
|
534
|
+
case 'dashboard':
|
|
549
535
|
await runDashboard({ port: dashboardPort, open: true, exportFile: dashboardExport });
|
|
550
536
|
closeRL();
|
|
551
537
|
break;
|
|
@@ -553,9 +539,6 @@ async function runCommandMenu() {
|
|
|
553
539
|
await runReset();
|
|
554
540
|
closeRL();
|
|
555
541
|
break;
|
|
556
|
-
case 'settings':
|
|
557
|
-
await runSettings();
|
|
558
|
-
break;
|
|
559
542
|
case 'uninstall':
|
|
560
543
|
await runUninstall();
|
|
561
544
|
closeRL();
|
|
@@ -644,14 +627,12 @@ async function runInstall(overrides = {}) {
|
|
|
644
627
|
await capture('shared', () => stepSharedSessionState(userExtDir, options));
|
|
645
628
|
let selfPlugin = false;
|
|
646
629
|
await capture('self-plugin', async () => { selfPlugin = await stepSelfPlugin(OMP_PLUGINS_DIR, options); });
|
|
647
|
-
await capture('ponytail', () => stepPonytail(OMP_PLUGINS_DIR,
|
|
648
|
-
const ponytailExtPath = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'pi-extension', 'index.js');
|
|
630
|
+
await capture('ponytail', () => stepPonytail(OMP_PLUGINS_DIR, options));
|
|
649
631
|
await capture('rtk', () => stepRtk(BUN_BIN_DIR, options));
|
|
650
632
|
await capture('rtk session', () => stepRtkSession(userExtDir, options));
|
|
651
|
-
await capture('caveman', () => stepCaveman(
|
|
633
|
+
await capture('caveman', () => stepCaveman(path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions'), cavemanRule, options));
|
|
652
634
|
await capture('combo', () => stepCombo(userExtDir, options));
|
|
653
635
|
await capture('commands', () => stepTersioCommands(userExtDir, options));
|
|
654
|
-
await capture('reinforcement', () => stepModeReinforcement(userExtDir, ponytailExtPath, options));
|
|
655
636
|
await capture('updater', () => stepUpdater(userExtDir, options));
|
|
656
637
|
if (selfPlugin)
|
|
657
638
|
await capture('settings', () => writePluginSettings(profile, options));
|
package/cli/profile.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// cli/profile.ts — session-start defaults profile (combo/caveman/rtk/ponytail)
|
|
2
|
-
// plus the display-currency default for usage/
|
|
2
|
+
// plus the display-currency default for usage/dashboard reports.
|
|
3
3
|
// Single source for reading/writing the omp plugin settings lock entry.
|
|
4
4
|
// Extracted from cli/install.ts so both install and settings share it.
|
|
5
5
|
import { promises as fs } from 'node:fs';
|
|
@@ -72,6 +72,7 @@ async function writePluginSettings(profile, options) {
|
|
|
72
72
|
config.settings[PACKAGE_NAME] = {
|
|
73
73
|
...(config.settings[PACKAGE_NAME] || {}),
|
|
74
74
|
comboDefault: profile.comboDefault,
|
|
75
|
+
comboSetupComplete: true,
|
|
75
76
|
cavemanDefault: profile.cavemanDefault,
|
|
76
77
|
rtkDefault: profile.rtkDefault,
|
|
77
78
|
ponytailDefault: profile.ponytailDefault,
|
package/cli/settings.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// cli/settings.ts — view/edit session-start defaults (combo/caveman/rtk/ponytail)
|
|
2
|
-
// plus the display-currency default for usage/
|
|
2
|
+
// plus the display-currency default for usage/dashboard reports.
|
|
3
3
|
// Non-interactive: `tersio settings --combo-default balanced` (plus per-mode
|
|
4
4
|
// override flags and --currency). Interactive: clack selects seeded from the
|
|
5
5
|
// stored profile.
|
|
@@ -64,7 +64,7 @@ async function askProfile(current) {
|
|
|
64
64
|
if (ponytail.status !== 'selected')
|
|
65
65
|
return null;
|
|
66
66
|
next.ponytailDefault = ponytail.value;
|
|
67
|
-
const cur = await askInteractiveChoice('Display currency (usage/
|
|
67
|
+
const cur = await askInteractiveChoice('Display currency (usage/dashboard reports)', CURRENCY_CODES.map((v) => ({
|
|
68
68
|
value: v, label: v,
|
|
69
69
|
})), next.currency);
|
|
70
70
|
if (cur.status !== 'selected')
|
|
@@ -85,7 +85,7 @@ async function askDiagSchedule(current) {
|
|
|
85
85
|
}
|
|
86
86
|
const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'currency', 'diagnosis'];
|
|
87
87
|
function settingsUsage() {
|
|
88
|
-
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|wenyan] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra
|
|
88
|
+
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--dry-run]');
|
|
89
89
|
}
|
|
90
90
|
// Single-setting jump: `tersio settings diagnosis` prompts only that value
|
|
91
91
|
// instead of walking the whole chain. Returns false when aborted.
|
|
@@ -138,7 +138,7 @@ async function runSingleSetting(name, current, nextDiag) {
|
|
|
138
138
|
break;
|
|
139
139
|
}
|
|
140
140
|
case 'currency': {
|
|
141
|
-
const picked = await askInteractiveChoice('Display currency (usage/
|
|
141
|
+
const picked = await askInteractiveChoice('Display currency (usage/dashboard reports)', CURRENCY_CODES.map((v) => ({
|
|
142
142
|
value: v, label: v,
|
|
143
143
|
})), next.currency);
|
|
144
144
|
if (picked.status !== 'selected')
|
|
@@ -162,7 +162,7 @@ async function runSingleSetting(name, current, nextDiag) {
|
|
|
162
162
|
}
|
|
163
163
|
function printSettingsTable(current) {
|
|
164
164
|
console.log('\n=== Tersio Settings ===');
|
|
165
|
-
// Currency lives here no longer: the
|
|
165
|
+
// Currency lives here no longer: the dashboard owns displaying and
|
|
166
166
|
// persisting it (its picker POSTs to /currency); settings can still set
|
|
167
167
|
// the stored default via --currency or the prompt below.
|
|
168
168
|
const rows = [
|
|
@@ -239,7 +239,7 @@ async function runSettings() {
|
|
|
239
239
|
setDiagSchedule(nextDiag);
|
|
240
240
|
console.log(` Defaults: ${formatProfile(next)} · diagnosis=${nextDiag}`);
|
|
241
241
|
console.log(' Applies to fresh sessions only — anything persisted with /combo, /caveman, or /rtk wins.');
|
|
242
|
-
console.log(' Currency applies to usage/
|
|
242
|
+
console.log(' Currency applies to usage/dashboard reports; --currency overrides it per run.');
|
|
243
243
|
closeRL();
|
|
244
244
|
}
|
|
245
245
|
export { runSettings };
|
package/cli/update.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
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 {
|
|
5
|
+
import { IS_WINDOWS, OMP_PLUGINS_DIR, PACKAGE_BIN, PACKAGE_NAME, PACKAGE_VERSION, dryRun, execP, parseJsonObject, verbose, } from './common.js';
|
|
6
6
|
import { execNetwork } from './interactive.js';
|
|
7
|
-
import { readTextIfExists, tersioDataPath } from '../extensions/lib/utils.js';
|
|
7
|
+
import { readTextIfExists, resolveRtkBinary, tersioDataPath } from '../extensions/lib/utils.js';
|
|
8
8
|
import { refreshPrices } from '../extensions/shared/pricing.js';
|
|
9
|
-
import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API,
|
|
9
|
+
import { CAVEMAN_REMOTE_RULE, RTK_RELEASE_API, httpsGet, normalizeRtkVersion, sha256Hex, } from '../extensions/lib/utils.js';
|
|
10
10
|
const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
|
|
11
11
|
function newerThan(a, b) {
|
|
12
12
|
const pa = a.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
|
|
@@ -83,21 +83,22 @@ async function execInherit(cmd, args) {
|
|
|
83
83
|
// Current → latest per add-on. Every probe is capped and nullable; the plan
|
|
84
84
|
// prints `unknown` for anything unreachable and the update proceeds anyway.
|
|
85
85
|
async function probeUpdatePlan(cliLatest) {
|
|
86
|
-
const rtkBin =
|
|
86
|
+
const rtkBin = resolveRtkBinary();
|
|
87
87
|
const ponytailPkg = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
88
|
-
const
|
|
88
|
+
const tersioRule = path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions', 'caveman-session', 'rule.md');
|
|
89
|
+
const controller = new AbortController();
|
|
89
90
|
const [rtkLocal, rtkRelease, ruleLocalText, ruleRemoteText, ponytailLocalText] = await Promise.all([
|
|
90
|
-
settle(execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => normalizeRtkVersion(r.stdout.trim() || r.stderr.trim()) || null), 6000),
|
|
91
|
-
settle(
|
|
92
|
-
readTextIfExists(
|
|
93
|
-
settle(httpsGet(CAVEMAN_REMOTE_RULE),
|
|
91
|
+
settle(rtkBin ? execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => normalizeRtkVersion(r.stdout.trim() || r.stderr.trim()) || null) : Promise.resolve(null), 6000),
|
|
92
|
+
settle(httpsGet(RTK_RELEASE_API, { signal: controller.signal }).then((text) => normalizeRtkVersion(JSON.parse(text).tag_name) || null), 1500),
|
|
93
|
+
readTextIfExists(tersioRule),
|
|
94
|
+
settle(httpsGet(CAVEMAN_REMOTE_RULE, { signal: controller.signal }).then((text) => sha256Hex(text).slice(0, 8)), 1500),
|
|
94
95
|
readTextIfExists(ponytailPkg),
|
|
95
96
|
]);
|
|
96
|
-
|
|
97
|
+
controller.abort();
|
|
97
98
|
return {
|
|
98
99
|
cli: cliLatest,
|
|
99
100
|
rtk: [rtkLocal, rtkRelease],
|
|
100
|
-
rule: [
|
|
101
|
+
rule: [ruleLocalText ? sha256Hex(ruleLocalText).slice(0, 8) : null, ruleRemoteText],
|
|
101
102
|
ponytail: ponytailLocalText ? parseJsonObject(ponytailLocalText)?.version ?? null : null,
|
|
102
103
|
};
|
|
103
104
|
}
|
package/cli/usage.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
// cli/usage.ts — ledger + session-token usage report, tokscale-style.
|
|
2
|
+
import { co2GramsFor, displayModelId, energyWhFor, importSessionTokens, ledgerPath, priceFor, readResetWatermark, readRtkAdoption, readRtkRecallDiagnostics, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
|
|
3
|
+
import { resolveRtkBinary } from '../extensions/lib/utils.js';
|
|
2
4
|
import { readRtkGain } from '../extensions/shared/rtk-gain.js';
|
|
3
5
|
import { readUsageDb, syncUsageDb, usageDbPath } from '../extensions/shared/usage-store.js';
|
|
4
6
|
import { withInteractiveSpinner } from './interactive.js';
|
|
@@ -74,6 +76,8 @@ export function summarizeUsage(rows) {
|
|
|
74
76
|
est: usdCost({ input: r.i, output: r.o, cacheRead: r.cr ?? 0, cacheWrite: r.cw ?? 0 }, r.m).usd,
|
|
75
77
|
})),
|
|
76
78
|
rtkGain: readRtkGain(RTK_COMMAND_ROWS, watermark || undefined),
|
|
79
|
+
rtkAdoption: readRtkAdoption(),
|
|
80
|
+
rtkRecall: readRtkRecallDiagnostics(resolveRtkBinary()),
|
|
77
81
|
usd,
|
|
78
82
|
priced,
|
|
79
83
|
savedUsd,
|
|
@@ -134,6 +138,7 @@ function printReport(report) {
|
|
|
134
138
|
const t = report.tokens;
|
|
135
139
|
console.log(` TOKENS ${fmt(t.input)} in · ${fmt(t.output)} out · ${fmt(t.cacheRead)} cache read · ${fmt(t.cacheWrite)} cache write`);
|
|
136
140
|
console.log(` 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.)`);
|
|
141
|
+
console.log(` RTK ${fmt(report.rtkAdoption.rtkCalls)}/${fmt(report.rtkAdoption.eligibleCalls)} eligible OMP Bash calls use RTK (${report.rtkAdoption.adoptionPct.toFixed(1)}%) · ${fmt(report.rtkAdoption.missedCalls)} missed · recall ${report.rtkRecall.available ? `${report.rtkRecall.mode} (${report.rtkRecall.entries})` : 'unavailable'}`);
|
|
137
142
|
const models = 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)).slice(0, 8);
|
|
138
143
|
if (models.length) {
|
|
139
144
|
console.log(' BY MODEL');
|