@krtclcdy/tersio 2.22.0 → 2.22.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 +14 -9
- package/cli/common.js +4 -4
- package/cli/currency.js +2 -2
- package/cli/dashboard.js +33 -104
- package/cli/install.js +2 -2
- package/cli/profile.js +1 -1
- package/cli/settings.js +5 -5
- package/dashboard/dist/index.html +65 -0
- package/extensions/shared/usage-ledger.js +1 -1
- package/extensions/shared/usage-ledger.ts +1 -1
- package/extensions/tersio-commands/index.js +10 -13
- package/extensions/tersio-commands/index.ts +10 -13
- package/package.json +10 -7
- package/tersio.js +8 -8
- 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/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/README.md
CHANGED
|
@@ -79,21 +79,26 @@ Measured savings against the same workload without the modes. Token counts are r
|
|
|
79
79
|
|
|
80
80
|
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).
|
|
81
81
|
|
|
82
|
-
## 📈
|
|
82
|
+
## 📈 Dashboard
|
|
83
83
|
|
|
84
|
-
`tersio
|
|
84
|
+
`tersio dashboard --open` serves a local dashboard (127.0.0.1 only) that charts your own savings from `~/.tersio/usage.db`. Three views:
|
|
85
85
|
|
|
86
86
|
**Live feed and savings.** Token throughput, cost, savings bento, and recent activity — the top of the dashboard.
|
|
87
87
|
|
|
88
|
-

|
|
89
89
|
|
|
90
90
|
**Top models and recent requests.** Per-model cost breakdown plus the last requests with elapsed time and token speed.
|
|
91
91
|
|
|
92
|
-

|
|
93
93
|
|
|
94
94
|
**Command tools.** Token savings, average rate, and timing per shell tool.
|
|
95
95
|
|
|
96
|
-

|
|
97
|
+
|
|
98
|
+
### Measurement references
|
|
99
|
+
|
|
100
|
+
- **[EcoLogits](https://github.com/mlco2/ecologits)** informs Tersio's local CO₂ and energy estimates. Tersio ports the model to TypeScript; EcoLogits is not a runtime dependency.
|
|
101
|
+
- **[Tokscale](https://github.com/junhoyeo/tokscale)** informs Tersio's Input, Output, Cache Read, and Cache Write accounting from local OMP sessions. Tokscale is a reference, not a runtime dependency.
|
|
97
102
|
|
|
98
103
|
## 🖥️ CLI
|
|
99
104
|
|
|
@@ -104,12 +109,12 @@ Break-even math, balanced preset (caveman full + rtk + ponytail): a mixed turn c
|
|
|
104
109
|
| `tersio reinstall` | Fresh install, preserving the Ponytail package |
|
|
105
110
|
| `tersio doctor` | Check OMP, extension, Ponytail, and RTK health (`--fix` repairs, `--fix=<scope>` one scope, `--dry-run` previews) |
|
|
106
111
|
| `tersio usage` | Ledger-backed usage + savings report |
|
|
107
|
-
| `tersio
|
|
112
|
+
| `tersio dashboard` | Open the Dashboard (`--open`, `--export <file>`, `--port <n>`, `--currency <code>`; serves localhost only) |
|
|
108
113
|
| `tersio reset` | Clear tersio statistics: usage ledger + a watermark that hides pre-reset rows from every derived view (Y/N confirm, `--dry-run`, `--yes`) — session transcripts and RTK history stay intact on disk |
|
|
109
114
|
| `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) |
|
|
110
115
|
| `tersio version` | Print version |
|
|
111
116
|
|
|
112
|
-
Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--combo-default`/`--caveman-default`/`--rtk-default`/`--ponytail-default`, `--currency <code>` (usage
|
|
117
|
+
Flags: `--dry-run`, `--yes`/`-y`, `--verbose`, `--combo-default`/`--caveman-default`/`--rtk-default`/`--ponytail-default`, `--currency <code>` (usage/dashboard display currency; flag wins, then the `tersio settings` default, then USD). Legacy `--doctor` / `--uninstall` forms still work.
|
|
113
118
|
|
|
114
119
|
## ⌨️ Commands reference
|
|
115
120
|
|
|
@@ -124,7 +129,7 @@ Mode switches live on their own commands; bare `/tersio` prints status.
|
|
|
124
129
|
| `/tersio status` | Active modes + combo level; state persists, propagates to subagents, and lights the Combo footer indicator. |
|
|
125
130
|
| `/tersio check` | Add-on version check |
|
|
126
131
|
| `/tersio update <ponytail\|rtk\|caveman\|all> [--dry-run]` | Update add-ons, preview with dry-run |
|
|
127
|
-
| `/tersio
|
|
132
|
+
| `/tersio dashboard` | Open the Dashboard. |
|
|
128
133
|
| `/tersio usage` | Ledger report for this machine (rows in `~/.tersio/usage.db`, local only) |
|
|
129
134
|
| `/tersio help` | This table |
|
|
130
135
|
| `/ai-addons` | Add-on updater alias, still works. |
|
|
@@ -149,7 +154,7 @@ User-level installs also register the package in `~/.omp/plugins` (visible in OM
|
|
|
149
154
|
|
|
150
155
|
**RTK missing or not executable:** run `tersio reinstall`, then `tersio doctor`. On Linux/macOS: `chmod +x ~/.bun/bin/rtk`.
|
|
151
156
|
|
|
152
|
-
**RTK commands not metered in the
|
|
157
|
+
**RTK commands not metered in the 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.
|
|
153
158
|
|
|
154
159
|
**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.
|
|
155
160
|
|
package/cli/common.js
CHANGED
|
@@ -48,7 +48,7 @@ function flagValue(name) {
|
|
|
48
48
|
return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
|
|
49
49
|
}
|
|
50
50
|
// --- CLI flags ---
|
|
51
|
-
const COMMANDS = { install: true, update: true, reinstall: true, doctor: true, uninstall: true, usage: true,
|
|
51
|
+
const COMMANDS = { install: true, update: true, reinstall: true, doctor: true, uninstall: true, usage: true, dashboard: true, reset: true, settings: true, version: true, help: true };
|
|
52
52
|
const args = process.argv.slice(2);
|
|
53
53
|
const commandArg = args.find((arg) => !arg.startsWith('-'));
|
|
54
54
|
const command = commandArg?.toLowerCase() || null;
|
|
@@ -75,13 +75,13 @@ const verbose = args.includes('--verbose');
|
|
|
75
75
|
const doctor = command === 'doctor' || args.includes('--doctor');
|
|
76
76
|
const fix = args.includes('--fix') || args.some((a) => a.startsWith('--fix='));
|
|
77
77
|
const usage = command === 'usage' || args.includes('--usage');
|
|
78
|
-
const
|
|
78
|
+
const dashboard = command === 'dashboard';
|
|
79
79
|
const reset = command === 'reset';
|
|
80
80
|
const settings = command === 'settings';
|
|
81
81
|
const dashboardPort = Number.parseInt(flagValue('--port') ?? '', 10) || 0;
|
|
82
82
|
const dashboardOpen = args.includes('--open');
|
|
83
83
|
const dashboardExport = flagValue('--export') ?? null;
|
|
84
|
-
// Display currency for usage/
|
|
84
|
+
// Display currency for usage/dashboard: --currency flag wins, then the stored
|
|
85
85
|
// plugin default (tersio settings), then USD.
|
|
86
86
|
const currency = parseCurrencyFlag(flagValue('--currency')) ?? readStoredCurrency();
|
|
87
87
|
const currencyGiven = flagValue('--currency') !== undefined;
|
|
@@ -298,4 +298,4 @@ function relTime(ageMs) {
|
|
|
298
298
|
const weeks = Math.floor(days / 7);
|
|
299
299
|
return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
|
|
300
300
|
}
|
|
301
|
-
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, DIAG_SCHEDULES, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, settingArg, unknownCommand, install, update, reinstall, showVersion, showHelp, applyUpdate, args, dryRun, yes, verbose, doctor, fix, uninstall, usage,
|
|
301
|
+
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, DIAG_SCHEDULES, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, settingArg, unknownCommand, install, update, reinstall, showVersion, showHelp, applyUpdate, args, dryRun, yes, verbose, doctor, fix, uninstall, usage, dashboard, reset, settings, dashboardPort, dashboardOpen, dashboardExport, currency, currencyGiven, removePonytail, keepPonytail, removeRtk, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, rtkDefaultFlag, diagScheduleFlag, profileFlagsGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, EXTENSIONS_KEY_RE, writeConfigLines, ensureExtensionInConfig, ensureExtensionAfterConfigEntry, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, relTime, };
|
package/cli/currency.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/currency.ts — display-currency helpers (USD-base, offline snapshot rates).
|
|
2
|
-
// Same 10 currencies, symbols, and snapshot rates as the
|
|
3
|
-
// converter (dashboard/app
|
|
2
|
+
// Same 10 currencies, symbols, and snapshot rates as the dashboard's
|
|
3
|
+
// converter (dashboard/app); the dashboard replaces rates with live
|
|
4
4
|
// frankfurter figures when reachable, the CLI always uses the snapshot.
|
|
5
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
6
|
import os from 'node:os';
|
package/cli/dashboard.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// cli/dashboard.ts —
|
|
2
|
-
// template (no deps, no build) plus a /data.json endpoint from the ledger.
|
|
1
|
+
// cli/dashboard.ts — serves the built shadcn Dashboard with local usage APIs.
|
|
3
2
|
// Binds 127.0.0.1 only; --export writes a file://-ready file instead.
|
|
4
3
|
// Note: no Promise.withResolvers here — engines still allow Node 20.12,
|
|
5
4
|
// which lacks it; the listening server itself keeps the process alive.
|
|
@@ -16,22 +15,13 @@ import { summarizeUsage } from './usage.js';
|
|
|
16
15
|
import { isCurrencyCode } from './currency.js';
|
|
17
16
|
import { BUN_BIN_DIR, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_VERSION, RTK_BINARY_NAME, } from './common.js';
|
|
18
17
|
import { storedProfile, writePluginSettings } from './profile.js';
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const SHARE_JS = path.join(DASH_DIR, 'share.js');
|
|
27
|
-
const BRAND = path.join(DASH_DIR, 'brand.webp');
|
|
28
|
-
// shadcn gain app (gain/). When its production bundle exists the server
|
|
29
|
-
// binds to it; otherwise it falls back to the legacy dashboard/ segments.
|
|
30
|
-
const GAIN_DIST = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'gain', 'dist');
|
|
31
|
-
const GAIN_INDEX = path.join(GAIN_DIST, 'index.html');
|
|
32
|
-
const GAIN_BRAND = path.join(GAIN_DIST, 'brand.webp');
|
|
33
|
-
function useGainBundle() {
|
|
34
|
-
return existsSync(GAIN_INDEX);
|
|
18
|
+
const DASHBOARD_DIST = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'dashboard', 'dist');
|
|
19
|
+
const DASHBOARD_INDEX = path.join(DASHBOARD_DIST, 'index.html');
|
|
20
|
+
const DASHBOARD_BRAND = path.join(DASHBOARD_DIST, 'brand.webp');
|
|
21
|
+
function requireDashboardBundle() {
|
|
22
|
+
if (existsSync(DASHBOARD_INDEX))
|
|
23
|
+
return;
|
|
24
|
+
throw new Error('Dashboard bundle missing. Run `bun run build` before using `tersio dashboard`.');
|
|
35
25
|
}
|
|
36
26
|
function contentType(file) {
|
|
37
27
|
if (file.endsWith('.html'))
|
|
@@ -53,9 +43,9 @@ function contentType(file) {
|
|
|
53
43
|
async function readSegment(file) {
|
|
54
44
|
return fs.readFile(file, 'utf8');
|
|
55
45
|
}
|
|
56
|
-
async function
|
|
57
|
-
const
|
|
58
|
-
return `data:image/webp;base64,${
|
|
46
|
+
async function brandDataUri() {
|
|
47
|
+
const brand = await fs.readFile(DASHBOARD_BRAND);
|
|
48
|
+
return `data:image/webp;base64,${brand.toString('base64')}`;
|
|
59
49
|
}
|
|
60
50
|
function dataJson() {
|
|
61
51
|
return JSON.stringify(summarizeUsage(readUsage()));
|
|
@@ -273,7 +263,7 @@ function readDiagSchedule() {
|
|
|
273
263
|
return readDiagReport()?.schedule ?? 'manual';
|
|
274
264
|
}
|
|
275
265
|
// Persist the dashboard's picker choice so close → reopen keeps it: each
|
|
276
|
-
// `tersio
|
|
266
|
+
// `tersio dashboard` run serves a fresh ephemeral port (a new origin), so the
|
|
277
267
|
// browser's localStorage alone cannot survive a restart. The stored default
|
|
278
268
|
// feeds data.json and `tersio usage` on the next run.
|
|
279
269
|
async function saveDashboardCurrency(raw) {
|
|
@@ -310,45 +300,22 @@ function escapeInline(json) {
|
|
|
310
300
|
return json.replace(/<\/(script)/gi, '<\\/$1');
|
|
311
301
|
}
|
|
312
302
|
async function exportDashboard(exportFile) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
console.log(`[ok] gain exported → ${exportFile}`);
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const [template, css, core, charts, settings, share, legacyIcon] = await Promise.all([
|
|
324
|
-
readSegment(TEMPLATE), readSegment(STYLES), readSegment(CORE_JS), readSegment(CHARTS_JS),
|
|
325
|
-
readSegment(SETTINGS_JS), readSegment(SHARE_JS), faviconDataUri(),
|
|
326
|
-
]);
|
|
327
|
-
const inline = template
|
|
328
|
-
.replace('<link rel="stylesheet" href="styles.css">', () => `<style>\n${css}</style>`)
|
|
329
|
-
.replace('<script src="core.js" defer></script>', () => `<script>\n${core}</script>`)
|
|
330
|
-
.replace('<script src="charts.js" defer></script>', () => `<script>\n${charts}</script>`)
|
|
331
|
-
.replace('<script src="settings.js" defer></script>', () => `<script>\n${settings}</script>`)
|
|
332
|
-
.replace('<script src="share.js" defer></script>', () => `<script>\n${share}</script>`)
|
|
333
|
-
.replace('window.__TERSIO_SNAP = null;', () => `window.__TERSIO_SNAP = ${escapeInline(JSON.stringify({ health: JSON.parse(healthJson()), doctor: getDoctorReport(false) }))};`)
|
|
334
|
-
.replace("fetch('data.json')", () => `Promise.resolve({ json: function () { return ${escapeInline(dataJson())}; } })`)
|
|
335
|
-
// Exported file runs on file:// where load() early-returns before the
|
|
336
|
-
// stub above ever runs, so nothing ever renders. Drop that guard in
|
|
337
|
-
// the export only; template.html keeps it to avoid 404 polling loops.
|
|
338
|
-
.replace("if (window.location.protocol === 'file:') return;", () => `if (false) return;`)
|
|
339
|
-
.replace('href="brand.webp"', () => `href="${legacyIcon}"`)
|
|
340
|
-
.replace(/src="brand.webp"/g, () => `src="${legacyIcon}"`);
|
|
303
|
+
requireDashboardBundle();
|
|
304
|
+
const [bundle, icon] = await Promise.all([readSegment(DASHBOARD_INDEX), brandDataUri()]);
|
|
305
|
+
const inline = bundle
|
|
306
|
+
.replace('window.__TERSIO_SNAP = null;', () => `window.__TERSIO_SNAP = ${escapeInline(JSON.stringify({ data: JSON.parse(dataJson()), health: JSON.parse(healthJson()), doctor: getDoctorReport(false) }))};`)
|
|
307
|
+
.replace(/href="brand\.webp"/g, () => `href="${icon}"`)
|
|
308
|
+
.replace(/src="brand\.webp"/g, () => `src="${icon}"`)
|
|
309
|
+
.replace('fetch("brand.webp")', () => `Promise.resolve({ ok: true, blob: async () => new Blob([atob("${icon.split(',')[1]}")], { type: "image/webp" }) })`);
|
|
341
310
|
await fs.writeFile(exportFile, inline, 'utf8');
|
|
342
|
-
console.log(`[ok]
|
|
311
|
+
console.log(`[ok] Dashboard exported → ${exportFile}`);
|
|
343
312
|
}
|
|
344
313
|
async function runDashboard(options) {
|
|
345
314
|
if (options.exportFile) {
|
|
346
315
|
await exportDashboard(options.exportFile);
|
|
347
316
|
return;
|
|
348
317
|
}
|
|
349
|
-
|
|
350
|
-
? null
|
|
351
|
-
: await withInteractiveSpinner('Loading dashboard template', () => readSegment(TEMPLATE));
|
|
318
|
+
requireDashboardBundle();
|
|
352
319
|
const server = http.createServer(async (req, res) => {
|
|
353
320
|
if (req.url === '/reset' && req.method === 'POST') {
|
|
354
321
|
const rows = clearUsageLedger();
|
|
@@ -419,67 +386,29 @@ async function runDashboard(options) {
|
|
|
419
386
|
res.end(JSON.stringify(getDoctorReport(fresh)));
|
|
420
387
|
return;
|
|
421
388
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
'/settings.js': SETTINGS_JS,
|
|
431
|
-
'/share.js': SHARE_JS,
|
|
432
|
-
};
|
|
433
|
-
if (req.url !== undefined && req.url in scripts) {
|
|
434
|
-
res.writeHead(200, { 'Content-Type': 'text/javascript; charset=utf-8' });
|
|
435
|
-
res.end(await readSegment(scripts[req.url]));
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
if (req.url === '/brand.webp') {
|
|
439
|
-
// Prefer the bundled brand; fall back to the legacy dashboard copy.
|
|
440
|
-
for (const file of [GAIN_BRAND, BRAND]) {
|
|
441
|
-
try {
|
|
442
|
-
const png = await fs.readFile(file);
|
|
443
|
-
res.writeHead(200, { 'Content-Type': 'image/webp' });
|
|
444
|
-
res.end(png);
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
catch { /* try the next copy */ }
|
|
448
|
-
}
|
|
449
|
-
res.writeHead(404);
|
|
450
|
-
res.end();
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (useGainBundle()) {
|
|
454
|
-
// shadcn bundle: index.html at /, sibling files (brand.webp,
|
|
455
|
-
// vite.svg) by path. No path escapes outside gain/dist.
|
|
456
|
-
const name = (req.url ?? '/').split('?')[0];
|
|
457
|
-
const file = path.normalize(path.join(GAIN_DIST, name === '/' ? 'index.html' : name.slice(1)));
|
|
458
|
-
if (file === GAIN_DIST || file.startsWith(GAIN_DIST + path.sep)) {
|
|
459
|
-
try {
|
|
460
|
-
const body = await fs.readFile(file);
|
|
461
|
-
res.writeHead(200, { 'Content-Type': contentType(file) });
|
|
462
|
-
res.end(body);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
catch { /* fall through to index */ }
|
|
389
|
+
const name = (req.url ?? '/').split('?')[0];
|
|
390
|
+
const file = path.normalize(path.join(DASHBOARD_DIST, name === '/' ? 'index.html' : name.slice(1)));
|
|
391
|
+
if (file === DASHBOARD_DIST || file.startsWith(DASHBOARD_DIST + path.sep)) {
|
|
392
|
+
try {
|
|
393
|
+
const body = await fs.readFile(file);
|
|
394
|
+
res.writeHead(200, { 'Content-Type': contentType(file) });
|
|
395
|
+
res.end(body);
|
|
396
|
+
return;
|
|
466
397
|
}
|
|
467
|
-
|
|
468
|
-
res.end(await readSegment(GAIN_INDEX));
|
|
469
|
-
return;
|
|
398
|
+
catch { /* fall through to index */ }
|
|
470
399
|
}
|
|
471
400
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
472
|
-
res.end(
|
|
401
|
+
res.end(await readSegment(DASHBOARD_INDEX));
|
|
473
402
|
});
|
|
474
403
|
server.listen(options.port, '127.0.0.1', () => {
|
|
475
404
|
try {
|
|
476
|
-
process.title = 'tersio
|
|
405
|
+
process.title = 'tersio dashboard';
|
|
477
406
|
}
|
|
478
407
|
catch { /* non-POSIX shells keep node */ }
|
|
479
408
|
const address = server.address();
|
|
480
409
|
const port = typeof address === 'object' && address ? address.port : options.port;
|
|
481
410
|
const url = `http://127.0.0.1:${port}`;
|
|
482
|
-
console.log(`[ok]
|
|
411
|
+
console.log(`[ok] Dashboard live → ${url} (Ctrl-C to stop)`);
|
|
483
412
|
if (options.open)
|
|
484
413
|
openBrowser(url);
|
|
485
414
|
});
|
package/cli/install.js
CHANGED
|
@@ -484,7 +484,7 @@ async function runCommandMenu() {
|
|
|
484
484
|
{ value: 'reinstall', label: 'Reinstall', hint: 'clean and reinstall the add-ons, Ponytail package kept' },
|
|
485
485
|
{ value: 'doctor', label: 'Doctor', hint: 'verify the installation' },
|
|
486
486
|
{ value: 'usage', label: 'Usage', hint: 'token usage and savings report' },
|
|
487
|
-
{ value: '
|
|
487
|
+
{ value: 'dashboard', label: 'Dashboard', hint: 'open the report in your browser' },
|
|
488
488
|
{ value: 'reset', label: 'Reset statistics', hint: 'clear statistics; transcripts and RTK history stay' },
|
|
489
489
|
{ value: 'settings', label: 'Settings', hint: 'defaults: combo, caveman, rtk, ponytail, currency' },
|
|
490
490
|
{ value: 'uninstall', label: 'Uninstall', hint: 'remove tersio' },
|
|
@@ -545,7 +545,7 @@ async function runCommandMenu() {
|
|
|
545
545
|
await runUsage();
|
|
546
546
|
closeRL();
|
|
547
547
|
break;
|
|
548
|
-
case '
|
|
548
|
+
case 'dashboard':
|
|
549
549
|
await runDashboard({ port: dashboardPort, open: true, exportFile: dashboardExport });
|
|
550
550
|
closeRL();
|
|
551
551
|
break;
|
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';
|
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')
|
|
@@ -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 };
|