@krtclcdy/tersio 2.18.0 → 2.20.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 +2 -2
- package/cli/common.js +21 -3
- package/cli/currency.js +102 -0
- package/cli/dashboard.js +54 -2
- package/cli/doctor.js +9 -7
- package/cli/install.js +35 -101
- package/cli/profile.js +92 -0
- package/cli/reset.js +4 -2
- package/cli/settings.js +122 -0
- package/cli/usage.js +38 -24
- package/dashboard/app.js +297 -70
- package/dashboard/styles.css +58 -1
- package/dashboard/template.html +75 -3
- package/extensions/ai-addons-updater/index.ts +377 -0
- package/extensions/caveman-session/index.ts +161 -0
- package/extensions/combo-toggle/index.ts +217 -0
- package/extensions/lib/utils.ts +162 -0
- package/extensions/rtk-session/index.ts +164 -0
- package/extensions/shared/carbon.ts +123 -0
- package/extensions/shared/mode-reinforcement.ts +29 -0
- package/extensions/shared/plugin-settings.ts +57 -0
- package/extensions/shared/pricing.ts +168 -0
- package/extensions/shared/rtk-gain.ts +105 -0
- package/extensions/shared/session-state.ts +201 -0
- package/extensions/shared/types.ts +85 -0
- package/extensions/shared/usage-ledger.ts +406 -0
- package/extensions/shared/usage-store.ts +325 -0
- package/extensions/tersio-commands/index.ts +110 -0
- package/package.json +36 -24
- package/tersio.js +8 -1
- package/extensions/ai-addons-updater/index.js +0 -331
- package/extensions/caveman-session/index.js +0 -152
- package/extensions/combo-toggle/index.js +0 -165
- package/extensions/lib/utils.js +0 -154
- package/extensions/rtk-session/index.js +0 -148
- package/extensions/shared/carbon.js +0 -88
- package/extensions/shared/mode-reinforcement.js +0 -26
- package/extensions/shared/plugin-settings.js +0 -51
- package/extensions/shared/pricing.js +0 -163
- package/extensions/shared/rtk-gain.js +0 -82
- package/extensions/shared/session-state.js +0 -177
- package/extensions/shared/types.js +0 -3
- package/extensions/shared/usage-ledger.js +0 -333
- package/extensions/tersio-commands/index.js +0 -102
package/cli/reset.js
CHANGED
|
@@ -7,13 +7,14 @@ import { cancel as clackCancel, confirm as clackConfirm } from '@clack/prompts';
|
|
|
7
7
|
import { dryRun, yes } from './common.js';
|
|
8
8
|
import { ask, closeRL, tty } from './interactive.js';
|
|
9
9
|
import { clearUsageLedger, importSessionTokens, ledgerPath, markReset, readUsage, sessionsDir } from '../extensions/shared/usage-ledger.js';
|
|
10
|
+
import { clearUsageDb, usageDbPath } from '../extensions/shared/usage-store.js';
|
|
10
11
|
import { readRtkGain, rtkDbPath } from '../extensions/shared/rtk-gain.js';
|
|
11
12
|
async function runReset() {
|
|
12
13
|
console.log('\n=== Tersio Reset ===\n');
|
|
13
14
|
const rows = readUsage().length;
|
|
14
15
|
const sessions = importSessionTokens();
|
|
15
16
|
const rtk = readRtkGain(1);
|
|
16
|
-
console.log(`Will clear: ${rows} usage ledger rows · ${ledgerPath()}`);
|
|
17
|
+
console.log(`Will clear: ${rows} usage ledger rows · ${ledgerPath()} + usage.db ${usageDbPath()}`);
|
|
17
18
|
console.log(`Will hide (view-level watermark): session-derived statistics (${sessions.messages} messages) · RTK metered rows (${rtk.commands})`);
|
|
18
19
|
console.log(`Left intact on disk: sessions ${sessionsDir()}, rtk ${rtkDbPath()}`);
|
|
19
20
|
if (dryRun) {
|
|
@@ -49,8 +50,9 @@ async function runReset() {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
const cleared = clearUsageLedger();
|
|
53
|
+
const dbCleared = clearUsageDb();
|
|
52
54
|
const ts = markReset();
|
|
53
|
-
console.log(`[ok] reset — removed ${cleared} usage rows; statistics view starts at ${new Date(ts).toISOString()}`);
|
|
55
|
+
console.log(`[ok] reset — removed ${cleared} usage rows${dbCleared ? ' + usage.db' : ''}; statistics view starts at ${new Date(ts).toISOString()}`);
|
|
54
56
|
return true;
|
|
55
57
|
}
|
|
56
58
|
export { runReset };
|
package/cli/settings.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// cli/settings.ts — view/edit session-start defaults (combo/caveman/rtk/ponytail)
|
|
2
|
+
// plus the display-currency default for usage/gain reports.
|
|
3
|
+
// Non-interactive: `tersio settings --combo-default balanced` (plus per-mode
|
|
4
|
+
// override flags and --currency). Interactive: clack selects seeded from the
|
|
5
|
+
// stored profile.
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, OMP_PLUGINS_DIR, PACKAGE_NAME, PONYTAIL_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, dryRun, ponytailDefaultFlag, profileFlagsGiven, rtkDefaultFlag, } from './common.js';
|
|
8
|
+
import { CURRENCY_CODES } from './currency.js';
|
|
9
|
+
import { textTable } from './usage.js';
|
|
10
|
+
import { askInteractiveChoice, closeRL, tty } from './interactive.js';
|
|
11
|
+
import { formatProfile, storedProfile, writePluginSettings } from './profile.js';
|
|
12
|
+
function applyFlags(base) {
|
|
13
|
+
const next = { ...base };
|
|
14
|
+
if (comboDefaultFlag !== undefined)
|
|
15
|
+
next.comboDefault = comboDefaultFlag;
|
|
16
|
+
const preset = COMBO_PRESET_MODES[next.comboDefault] ?? COMBO_PRESET_MODES.off;
|
|
17
|
+
next.cavemanDefault = preset.caveman;
|
|
18
|
+
next.rtkDefault = preset.rtk;
|
|
19
|
+
next.ponytailDefault = preset.ponytail;
|
|
20
|
+
if (cavemanDefaultFlag !== undefined)
|
|
21
|
+
next.cavemanDefault = cavemanDefaultFlag;
|
|
22
|
+
if (rtkDefaultFlag !== undefined)
|
|
23
|
+
next.rtkDefault = rtkDefaultFlag === 'on';
|
|
24
|
+
if (ponytailDefaultFlag !== undefined)
|
|
25
|
+
next.ponytailDefault = ponytailDefaultFlag;
|
|
26
|
+
if (currencyGiven)
|
|
27
|
+
next.currency = currency;
|
|
28
|
+
return next;
|
|
29
|
+
}
|
|
30
|
+
async function askProfile(current) {
|
|
31
|
+
const combo = await askInteractiveChoice('Session-start defaults — Combo preset', [
|
|
32
|
+
{ value: 'off', label: 'off' },
|
|
33
|
+
{ value: 'medium', label: 'medium', hint: 'caveman=lite, rtk=on, ponytail=lite' },
|
|
34
|
+
{ value: 'balanced', label: 'balanced', hint: 'caveman=full, rtk=on, ponytail=full' },
|
|
35
|
+
{ value: 'max', label: 'max', hint: 'caveman=ultra, rtk=on, ponytail=ultra' },
|
|
36
|
+
], current.comboDefault);
|
|
37
|
+
if (combo.status !== 'selected')
|
|
38
|
+
return null;
|
|
39
|
+
const preset = COMBO_PRESET_MODES[combo.value] ?? COMBO_PRESET_MODES.off;
|
|
40
|
+
const next = {
|
|
41
|
+
comboDefault: combo.value,
|
|
42
|
+
cavemanDefault: preset.caveman,
|
|
43
|
+
rtkDefault: preset.rtk,
|
|
44
|
+
ponytailDefault: preset.ponytail,
|
|
45
|
+
currency: current.currency,
|
|
46
|
+
};
|
|
47
|
+
const caveman = await askInteractiveChoice('Caveman default', [...CAVEMAN_DEFAULTS].map((v) => ({
|
|
48
|
+
value: v, label: v,
|
|
49
|
+
})), next.cavemanDefault);
|
|
50
|
+
if (caveman.status !== 'selected')
|
|
51
|
+
return null;
|
|
52
|
+
next.cavemanDefault = caveman.value;
|
|
53
|
+
const rtk = await askInteractiveChoice('RTK default', [
|
|
54
|
+
{ value: 'on', label: 'on' },
|
|
55
|
+
{ value: 'off', label: 'off' },
|
|
56
|
+
], next.rtkDefault ? 'on' : 'off');
|
|
57
|
+
if (rtk.status !== 'selected')
|
|
58
|
+
return null;
|
|
59
|
+
next.rtkDefault = rtk.value === 'on';
|
|
60
|
+
const ponytail = await askInteractiveChoice('Ponytail default', [...PONYTAIL_DEFAULTS].map((v) => ({
|
|
61
|
+
value: v, label: v,
|
|
62
|
+
})), next.ponytailDefault);
|
|
63
|
+
if (ponytail.status !== 'selected')
|
|
64
|
+
return null;
|
|
65
|
+
next.ponytailDefault = ponytail.value;
|
|
66
|
+
const cur = await askInteractiveChoice('Display currency (usage/gain reports)', CURRENCY_CODES.map((v) => ({
|
|
67
|
+
value: v, label: v,
|
|
68
|
+
})), next.currency);
|
|
69
|
+
if (cur.status !== 'selected')
|
|
70
|
+
return null;
|
|
71
|
+
next.currency = cur.value;
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
74
|
+
function printSettingsTable(current) {
|
|
75
|
+
console.log('\n=== Tersio Settings ===');
|
|
76
|
+
// Currency lives here no longer: the gain dashboard owns displaying and
|
|
77
|
+
// persisting it (its picker POSTs to /currency); settings can still set
|
|
78
|
+
// the stored default via --currency or the prompt below.
|
|
79
|
+
const rows = [
|
|
80
|
+
['combo', current.comboDefault, [...COMBO_DEFAULTS].join(' | ')],
|
|
81
|
+
['caveman', current.cavemanDefault, [...CAVEMAN_DEFAULTS].join(' | ')],
|
|
82
|
+
['rtk', current.rtkDefault ? 'on' : 'off', 'on | off'],
|
|
83
|
+
['ponytail', current.ponytailDefault, [...PONYTAIL_DEFAULTS].join(' | ')],
|
|
84
|
+
];
|
|
85
|
+
for (const l of textTable(['Setting', 'Current', 'Valid values'], rows, [false, false, false], 64)) {
|
|
86
|
+
console.log(l);
|
|
87
|
+
}
|
|
88
|
+
console.log(` Stored: ${PACKAGE_NAME} in ${path.join(OMP_PLUGINS_DIR, 'omp-plugins.lock.json')}`);
|
|
89
|
+
}
|
|
90
|
+
async function runSettings() {
|
|
91
|
+
const current = await storedProfile();
|
|
92
|
+
printSettingsTable(current);
|
|
93
|
+
let next;
|
|
94
|
+
if (profileFlagsGiven || currencyGiven) {
|
|
95
|
+
next = applyFlags(current);
|
|
96
|
+
}
|
|
97
|
+
else if (tty()) {
|
|
98
|
+
next = await askProfile(current);
|
|
99
|
+
if (next === null) {
|
|
100
|
+
closeRL();
|
|
101
|
+
process.exit(130);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.log('\n No flags given and no TTY — nothing to change.');
|
|
107
|
+
console.log(' Usage: tersio settings [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|wenyan] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra|review] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--dry-run]');
|
|
108
|
+
closeRL();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (dryRun) {
|
|
112
|
+
console.log(`\n [dry-run] would set defaults: ${formatProfile(next)}`);
|
|
113
|
+
closeRL();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
await writePluginSettings(next, {});
|
|
117
|
+
console.log(` Defaults: ${formatProfile(next)}`);
|
|
118
|
+
console.log(' Applies to fresh sessions only — anything persisted with /combo, /caveman, or /rtk wins.');
|
|
119
|
+
console.log(' Currency applies to usage/gain reports; --currency overrides it per run.');
|
|
120
|
+
closeRL();
|
|
121
|
+
}
|
|
122
|
+
export { runSettings };
|
package/cli/usage.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { co2GramsFor, energyWhFor, importSessionTokens, ledgerPath, priceFor, readResetWatermark, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
|
|
1
|
+
import { co2GramsFor, displayModelId, energyWhFor, importSessionTokens, ledgerPath, priceFor, readResetWatermark, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
|
|
4
2
|
import { readRtkGain } from '../extensions/shared/rtk-gain.js';
|
|
3
|
+
import { readUsageDb, syncUsageDb, usageDbPath } from '../extensions/shared/usage-store.js';
|
|
5
4
|
import { withInteractiveSpinner } from './interactive.js';
|
|
6
|
-
import { PACKAGE_VERSION } from './common.js';
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// to nothing; 2000 groups covers a long history (~1.7k today) while still
|
|
11
|
-
// bounding /data.json.
|
|
5
|
+
import { PACKAGE_VERSION, currency } from './common.js';
|
|
6
|
+
import { formatCurrency } from './currency.js';
|
|
7
|
+
// RTK's history is machine-wide; rows group by command alone. 2000 groups
|
|
8
|
+
// covers a long history while still bounding /data.json.
|
|
12
9
|
const RTK_COMMAND_ROWS = 2000;
|
|
13
10
|
export function summarizeUsage(rows) {
|
|
14
11
|
refreshPricesIfStale();
|
|
@@ -19,7 +16,24 @@ export function summarizeUsage(rows) {
|
|
|
19
16
|
detailCounts[r.detail] = (detailCounts[r.detail] ?? 0) + 1;
|
|
20
17
|
}
|
|
21
18
|
const byDetail = Object.entries(detailCounts).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
22
|
-
|
|
19
|
+
// usage.db is a cache of the same per-message rows: sync best-effort, read
|
|
20
|
+
// stored, fall back to the live parse when the store is missing or stale.
|
|
21
|
+
let synced = false;
|
|
22
|
+
try {
|
|
23
|
+
synced = syncUsageDb();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
synced = false;
|
|
27
|
+
}
|
|
28
|
+
let stored = null;
|
|
29
|
+
try {
|
|
30
|
+
stored = readUsageDb()?.tokens ?? null;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
stored = null;
|
|
34
|
+
}
|
|
35
|
+
const session = stored ?? importSessionTokens();
|
|
36
|
+
const source = stored ? (synced ? 'stored' : 'stored-stale') : 'live';
|
|
23
37
|
const watermark = readResetWatermark();
|
|
24
38
|
let usd = 0;
|
|
25
39
|
let priced = true;
|
|
@@ -67,7 +81,9 @@ export function summarizeUsage(rows) {
|
|
|
67
81
|
co2g,
|
|
68
82
|
energyWh,
|
|
69
83
|
version: PACKAGE_VERSION,
|
|
70
|
-
|
|
84
|
+
currency,
|
|
85
|
+
source,
|
|
86
|
+
paths: { ledger: ledgerPath(), sessions: sessionsDir(), usageDb: usageDbPath() },
|
|
71
87
|
};
|
|
72
88
|
}
|
|
73
89
|
function fmt(n) {
|
|
@@ -97,8 +113,8 @@ function pad(s, n) {
|
|
|
97
113
|
return s.length >= n ? s : s + ' '.repeat(n - s.length);
|
|
98
114
|
}
|
|
99
115
|
// Box-drawing table, plain text (piped-safe, byte-stable). Numeric columns
|
|
100
|
-
// right-align; long cells truncate with an ellipsis.
|
|
101
|
-
function
|
|
116
|
+
// right-align; long cells truncate with an ellipsis. Shared with cli/settings.ts.
|
|
117
|
+
function textTable(headers, rows, right = [], maxW = 32) {
|
|
102
118
|
const cells = [headers, ...rows].map((r) => r.map((c) => (c.length > maxW ? c.slice(0, maxW - 1) + '…' : c)));
|
|
103
119
|
const widths = headers.map((_, i) => Math.max(...cells.map((r) => r[i].length)));
|
|
104
120
|
const line = (l, m, r) => l + widths.map((w) => '─'.repeat(w + 2)).join(m) + r;
|
|
@@ -110,14 +126,14 @@ function table(headers, rows, right = [], maxW = 32) {
|
|
|
110
126
|
return out.map((l) => ' ' + l);
|
|
111
127
|
}
|
|
112
128
|
function printReport(report) {
|
|
113
|
-
console.log(`\n=== Tersio Usage v${report.version} · ${fmt(report.messages)} msgs ===`);
|
|
129
|
+
console.log(`\n=== Tersio Usage v${report.version} · ${fmt(report.messages)} msgs · ${report.source} ===`);
|
|
114
130
|
if (report.empty) {
|
|
115
131
|
console.log(' No ledger rows or session tokens yet — run session commands first.');
|
|
116
132
|
return;
|
|
117
133
|
}
|
|
118
134
|
const t = report.tokens;
|
|
119
135
|
console.log(` TOKENS ${fmt(t.input)} in · ${fmt(t.output)} out · ${fmt(t.cacheRead)} cache read${t.cacheWrite > 0 ? ` · ${fmt(t.cacheWrite)} written` : ''}`);
|
|
120
|
-
console.log(` COST
|
|
136
|
+
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.)`);
|
|
121
137
|
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);
|
|
122
138
|
if (models.length) {
|
|
123
139
|
console.log(' BY MODEL');
|
|
@@ -126,36 +142,34 @@ function printReport(report) {
|
|
|
126
142
|
const mt = b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
127
143
|
const hit = b.input + b.cacheRead ? (b.cacheRead / (b.input + b.cacheRead)) * 100 : 0;
|
|
128
144
|
const usd = report.byModelUsd[model] ?? 0;
|
|
129
|
-
return [model,
|
|
145
|
+
return [displayModelId(model), fmt(b.input), fmt(b.output), `${fmt(b.cacheRead)}/${fmt(b.cacheWrite)}`, formatCurrency(usd, report.currency), `${hit.toFixed(1)}%`, `${bar(mt / top, 8)} ${fmtShort(mt)}`];
|
|
130
146
|
});
|
|
131
|
-
for (const l of
|
|
147
|
+
for (const l of textTable(['Model', 'Input', 'Output', 'Cache r/w', report.currency, 'Hit', 'Share'], mrows, [false, true, true, true, true, true, false])) {
|
|
132
148
|
console.log(l);
|
|
133
149
|
}
|
|
134
150
|
}
|
|
135
|
-
const cmdRows = report.byTool.map(([tool, n]) => ({ name: tool,
|
|
151
|
+
const cmdRows = report.byTool.map(([tool, n]) => ({ name: tool, count: n, saved: null, avgPct: null, avgMs: null }));
|
|
136
152
|
for (const r of report.rtkGain.byCommand) {
|
|
137
|
-
cmdRows.push({ name: r.command,
|
|
153
|
+
cmdRows.push({ name: r.command, count: r.count, saved: r.saved, avgPct: r.avgPct, avgMs: r.avgMs });
|
|
138
154
|
}
|
|
139
155
|
cmdRows.sort((a, b) => b.count - a.count);
|
|
140
156
|
if (cmdRows.length) {
|
|
141
157
|
const g = report.rtkGain;
|
|
142
|
-
const projects = new Set(g.byCommand.map((r) => r.project).filter(Boolean));
|
|
143
158
|
const scope = g.commands
|
|
144
|
-
? ` COMMAND TOOLS ${fmt(
|
|
159
|
+
? ` COMMAND TOOLS ${fmt(cmdRows.length)} commands · ${fmt(g.commands)} runs · ${fmtShort(g.saved)} saved`
|
|
145
160
|
: ' COMMAND TOOLS';
|
|
146
161
|
console.log(scope);
|
|
147
162
|
const top = cmdRows.reduce((m, r) => Math.max(m, r.count), 1);
|
|
148
163
|
const crows = cmdRows.slice(0, 15).map((r, idx) => [
|
|
149
164
|
String(idx + 1),
|
|
150
165
|
r.name,
|
|
151
|
-
r.project ? path.basename(r.project) : '–',
|
|
152
166
|
fmt(r.count),
|
|
153
167
|
r.saved === null ? '–' : fmtShort(r.saved),
|
|
154
168
|
r.avgPct === null ? '–' : `${r.avgPct.toFixed(1)}%`,
|
|
155
169
|
r.avgMs === null ? '–' : fmtMs(r.avgMs),
|
|
156
170
|
bar(r.count / top, 8),
|
|
157
171
|
]);
|
|
158
|
-
for (const l of
|
|
172
|
+
for (const l of textTable(['#', 'Tool/Command', 'Count', 'Saved', 'Avg%', 'Time', 'Impact'], crows, [true, false, true, true, true, true, false], 30)) {
|
|
159
173
|
console.log(l);
|
|
160
174
|
}
|
|
161
175
|
}
|
|
@@ -164,4 +178,4 @@ async function runUsage() {
|
|
|
164
178
|
const report = await withInteractiveSpinner('Reading usage ledger', async () => summarizeUsage(readUsage()));
|
|
165
179
|
printReport(report);
|
|
166
180
|
}
|
|
167
|
-
export { runUsage };
|
|
181
|
+
export { runUsage, textTable };
|