@krtclcdy/tersio 2.9.0 → 2.11.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 +54 -36
- package/cli/banner.js +84 -0
- package/cli/common.js +3 -2
- package/cli/dashboard.js +37 -11
- package/cli/doctor.js +8 -1
- package/cli/install.js +27 -7
- package/cli/reset.js +51 -0
- package/cli/update.js +8 -0
- package/cli/usage.js +87 -22
- package/dashboard/app.js +922 -0
- package/dashboard/styles.css +194 -0
- package/dashboard/template.html +305 -0
- package/extensions/caveman-session/index.js +12 -1
- package/extensions/combo-toggle/index.js +3 -3
- package/extensions/rtk-session/index.js +8 -1
- package/extensions/shared/carbon.js +88 -0
- package/extensions/shared/pricing.js +121 -0
- package/extensions/shared/rtk-gain.js +61 -0
- package/extensions/shared/session-state.js +10 -3
- package/extensions/shared/usage-ledger.js +81 -37
- package/extensions/tersio-commands/index.js +5 -10
- package/package.json +2 -3
- package/tersio.js +8 -1
- package/cli/dashboard.html +0 -780
- /package/{cli → dashboard}/brand.webp +0 -0
package/cli/usage.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// cli/usage.ts — ledger + session-token usage report, tokscale-style.
|
|
2
|
-
import {
|
|
2
|
+
import { co2GramsFor, energyWhFor, importSessionTokens, ledgerPath, priceFor, readUsage, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
|
|
3
|
+
import { readRtkGain, rtkDbPath } from '../extensions/shared/rtk-gain.js';
|
|
3
4
|
import { withInteractiveSpinner } from './interactive.js';
|
|
4
5
|
import { PACKAGE_VERSION } from './common.js';
|
|
5
6
|
export function summarizeUsage(rows) {
|
|
@@ -14,9 +15,15 @@ export function summarizeUsage(rows) {
|
|
|
14
15
|
let usd = 0;
|
|
15
16
|
let priced = true;
|
|
16
17
|
let savedUsd = 0;
|
|
18
|
+
const byModelUsd = {};
|
|
19
|
+
let co2g = 0;
|
|
20
|
+
let energyWh = 0;
|
|
17
21
|
for (const [model, t] of Object.entries(session.byModel)) {
|
|
18
22
|
const c = usdCost(t, model);
|
|
19
23
|
usd += c.usd;
|
|
24
|
+
byModelUsd[model] = c.usd;
|
|
25
|
+
co2g += co2GramsFor(model, t.output);
|
|
26
|
+
energyWh += energyWhFor(model, t.output);
|
|
20
27
|
if (!c.priced)
|
|
21
28
|
priced = false;
|
|
22
29
|
savedUsd += (t.cacheRead / 1e6) * priceFor(model).price.input;
|
|
@@ -31,50 +38,108 @@ export function summarizeUsage(rows) {
|
|
|
31
38
|
messages: session.messages,
|
|
32
39
|
tokens: session.totals,
|
|
33
40
|
byModel: session.byModel,
|
|
41
|
+
byModelUsd,
|
|
42
|
+
byModelMessages: session.byModelMessages,
|
|
34
43
|
byDay: session.byDay,
|
|
35
44
|
byDayModel: session.byDayModel,
|
|
36
45
|
byTool,
|
|
46
|
+
recent: session.recent,
|
|
47
|
+
rtkGain: readRtkGain(),
|
|
37
48
|
usd,
|
|
38
49
|
priced,
|
|
39
50
|
savedUsd,
|
|
40
51
|
costMeasured: session.costMeasured,
|
|
41
|
-
co2g
|
|
52
|
+
co2g,
|
|
53
|
+
energyWh,
|
|
42
54
|
version: PACKAGE_VERSION,
|
|
55
|
+
paths: { ledger: ledgerPath(), sessions: sessionsDir(), rtk: rtkDbPath() },
|
|
43
56
|
};
|
|
44
57
|
}
|
|
45
58
|
function fmt(n) {
|
|
46
59
|
return n.toLocaleString('en-US');
|
|
47
60
|
}
|
|
61
|
+
function fmtShort(n) {
|
|
62
|
+
if (n >= 1e12)
|
|
63
|
+
return (n / 1e12).toFixed(1) + 'T';
|
|
64
|
+
if (n >= 1e9)
|
|
65
|
+
return (n / 1e9).toFixed(1) + 'B';
|
|
66
|
+
if (n >= 1e6)
|
|
67
|
+
return (n / 1e6).toFixed(1) + 'M';
|
|
68
|
+
if (n >= 1e3)
|
|
69
|
+
return (n / 1e3).toFixed(1) + 'K';
|
|
70
|
+
return String(Math.round(n));
|
|
71
|
+
}
|
|
72
|
+
function fmtMs(ms) {
|
|
73
|
+
if (ms < 1000)
|
|
74
|
+
return `${Math.round(ms)}ms`;
|
|
75
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
76
|
+
}
|
|
77
|
+
function bar(frac, width = 12) {
|
|
78
|
+
const filled = Math.max(0, Math.min(width, Math.round(frac * width)));
|
|
79
|
+
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
80
|
+
}
|
|
81
|
+
function pad(s, n) {
|
|
82
|
+
return s.length >= n ? s : s + ' '.repeat(n - s.length);
|
|
83
|
+
}
|
|
84
|
+
// Box-drawing table, plain text (piped-safe, byte-stable). Numeric columns
|
|
85
|
+
// right-align; long cells truncate with an ellipsis.
|
|
86
|
+
function table(headers, rows, right = [], maxW = 32) {
|
|
87
|
+
const cells = [headers, ...rows].map((r) => r.map((c) => (c.length > maxW ? c.slice(0, maxW - 1) + '…' : c)));
|
|
88
|
+
const widths = headers.map((_, i) => Math.max(...cells.map((r) => r[i].length)));
|
|
89
|
+
const line = (l, m, r) => l + widths.map((w) => '─'.repeat(w + 2)).join(m) + r;
|
|
90
|
+
const row = (cs) => '│ ' + cs.map((c, i) => (right[i] ? c.padStart(widths[i]) : pad(c, widths[i]))).join(' │ ') + ' │';
|
|
91
|
+
const out = [line('┌', '┬', '┐'), row(cells[0]), line('├', '┼', '┤')];
|
|
92
|
+
for (const r of cells.slice(1))
|
|
93
|
+
out.push(row(r));
|
|
94
|
+
out.push(line('└', '┴', '┘'));
|
|
95
|
+
return out.map((l) => ' ' + l);
|
|
96
|
+
}
|
|
48
97
|
function printReport(report) {
|
|
49
|
-
console.log(
|
|
98
|
+
console.log(`\n=== Tersio Usage v${report.version} · ${fmt(report.messages)} msgs ===`);
|
|
50
99
|
if (report.empty) {
|
|
51
100
|
console.log(' No ledger rows or session tokens yet — run session commands first.');
|
|
52
101
|
return;
|
|
53
102
|
}
|
|
54
103
|
const t = report.tokens;
|
|
55
|
-
console.log(`
|
|
56
|
-
console.log(`
|
|
57
|
-
const models = Object.entries(report.byModel).sort((a, b) => (b[1].input + b[1].output) - (a[1].input + a[1].output)).slice(0,
|
|
104
|
+
console.log(` TOKENS ${fmt(t.input)} in · ${fmt(t.output)} out · ${fmt(t.cacheRead)} cache read${t.cacheWrite > 0 ? ` · ${fmt(t.cacheWrite)} written` : ''}`);
|
|
105
|
+
console.log(` COST $${report.usd.toFixed(2)}${report.priced ? '' : ' (includes default pricing)'} · ~$${report.savedUsd.toFixed(2)} cache-saved (est.) · ~${report.co2g.toFixed(1)}g CO2 (est.)`);
|
|
106
|
+
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);
|
|
58
107
|
if (models.length) {
|
|
59
|
-
console.log('
|
|
60
|
-
|
|
61
|
-
|
|
108
|
+
console.log(' BY MODEL');
|
|
109
|
+
const top = models.reduce((m, [, b]) => Math.max(m, b.input + b.output + b.cacheRead + b.cacheWrite), 1);
|
|
110
|
+
const mrows = models.map(([model, b]) => {
|
|
111
|
+
const mt = b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
112
|
+
const hit = b.input + b.cacheRead ? (b.cacheRead / (b.input + b.cacheRead)) * 100 : 0;
|
|
113
|
+
const usd = report.byModelUsd[model] ?? 0;
|
|
114
|
+
return [model, `in ${fmt(b.input)}`, `out ${fmt(b.output)}`, `${fmt(b.cacheRead)}/${fmt(b.cacheWrite)}`, `$${usd.toFixed(2)}`, `${hit.toFixed(1)}%`, `${bar(mt / top, 8)} ${fmtShort(mt)}`];
|
|
115
|
+
});
|
|
116
|
+
for (const l of table(['Model', 'Input', 'Output', 'Cache r/w', 'USD', 'Hit', 'Share'], mrows, [false, true, true, true, true, true, false])) {
|
|
117
|
+
console.log(l);
|
|
62
118
|
}
|
|
63
119
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.log(` ${n}× ${tool}`);
|
|
68
|
-
}
|
|
120
|
+
const cmdRows = report.byTool.map(([tool, n]) => ({ name: tool, count: n, saved: null, avgPct: null, avgMs: null }));
|
|
121
|
+
for (const r of report.rtkGain.byCommand) {
|
|
122
|
+
cmdRows.push({ name: r.command, count: r.count, saved: r.saved, avgPct: r.avgPct, avgMs: r.avgMs });
|
|
69
123
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
124
|
+
cmdRows.sort((a, b) => b.count - a.count);
|
|
125
|
+
if (cmdRows.length) {
|
|
126
|
+
const g = report.rtkGain;
|
|
127
|
+
const scope = g.commands
|
|
128
|
+
? ` COMMAND TOOLS ${fmt(report.byTool.length)} tools · ${fmt(g.commands)} commands · ${fmtShort(g.saved)} saved`
|
|
129
|
+
: ' COMMAND TOOLS';
|
|
130
|
+
console.log(scope);
|
|
131
|
+
const top = cmdRows.reduce((m, r) => Math.max(m, r.count), 1);
|
|
132
|
+
const crows = cmdRows.slice(0, 15).map((r, idx) => [
|
|
133
|
+
String(idx + 1),
|
|
134
|
+
r.name,
|
|
135
|
+
fmt(r.count),
|
|
136
|
+
r.saved === null ? '–' : fmtShort(r.saved),
|
|
137
|
+
r.avgPct === null ? '–' : `${r.avgPct.toFixed(1)}%`,
|
|
138
|
+
r.avgMs === null ? '–' : fmtMs(r.avgMs),
|
|
139
|
+
bar(r.count / top, 8),
|
|
140
|
+
]);
|
|
141
|
+
for (const l of table(['#', 'Tool/Command', 'Count', 'Saved', 'Avg%', 'Time', 'Impact'], crows, [true, false, true, true, true, true, false], 30)) {
|
|
142
|
+
console.log(l);
|
|
78
143
|
}
|
|
79
144
|
}
|
|
80
145
|
}
|