@krtclcdy/tersio 2.25.4 → 2.25.5
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 +13 -30
- package/dashboard/dist/index.html +18 -16
- package/dist/cli/backups.js +194 -0
- package/dist/cli/common.js +2 -2
- package/dist/cli/dashboard.js +125 -98
- package/dist/cli/doctor-fix.js +41 -44
- package/dist/cli/doctor.js +69 -7
- package/dist/cli/hosts.js +4 -4
- package/dist/cli/install.js +71 -32
- package/dist/cli/manifest.js +45 -17
- package/dist/cli/profile.js +1 -1
- package/dist/cli/uninstall.js +21 -5
- package/dist/cli/update.js +3 -3
- package/dist/cli/usage.js +75 -4
- package/dist/extensions/{combo-toggle → combo}/index.js +1 -1
- package/dist/extensions/{tersio-commands → commands}/index.js +1 -1
- package/dist/extensions/{rtk-filter/index.js → rtk/filter.js} +1 -1
- package/dist/extensions/{rtk-session/index.js → rtk/session.js} +1 -1
- package/dist/extensions/{opencode/server.js → server.js} +8 -8
- package/dist/extensions/shared/agent-stats.js +1262 -0
- package/dist/extensions/shared/brand-marks.js +154 -0
- package/dist/extensions/shared/plugin-settings.js +1 -1
- package/dist/extensions/shared/pricing.js +91 -27
- package/dist/extensions/shared/usage-ledger.js +79 -17
- package/dist/extensions/shared/usage-store.js +106 -22
- package/dist/extensions/{lib → shared}/utils.js +1 -1
- package/dist/extensions/{ai-addons-updater → updater}/index.js +13 -3
- package/dist/probe-catalog.js +48 -0
- package/dist/probe-catalog188.js +44 -0
- package/dist/tersio.js +2 -2
- package/extensions/{combo-toggle → combo}/index.ts +1 -1
- package/extensions/{tersio-commands → commands}/index.ts +1 -1
- package/extensions/{rtk-filter/index.ts → rtk/filter.ts} +1 -1
- package/extensions/{rtk-session/index.ts → rtk/session.ts} +1 -1
- package/extensions/{opencode/server.ts → server.ts} +9 -9
- package/extensions/shared/agent-stats.ts +1657 -0
- package/extensions/shared/brand-marks.ts +163 -0
- package/extensions/shared/plugin-settings.ts +1 -1
- package/extensions/shared/pricing.ts +107 -28
- package/extensions/shared/usage-ledger.ts +94 -19
- package/extensions/shared/usage-store.ts +106 -22
- package/extensions/{lib → shared}/utils.ts +1 -1
- package/extensions/{ai-addons-updater → updater}/index.ts +15 -4
- package/package.json +13 -13
- /package/dist/extensions/{caveman-session → caveman}/index.js +0 -0
- /package/dist/extensions/{caveman-session → caveman}/rule-megacave.md +0 -0
- /package/dist/extensions/{caveman-session → caveman}/rule-ultra.md +0 -0
- /package/dist/extensions/{caveman-session → caveman}/rule.md +0 -0
- /package/extensions/{caveman-session → caveman}/index.ts +0 -0
- /package/extensions/{caveman-session → caveman}/rule-megacave.md +0 -0
- /package/extensions/{caveman-session → caveman}/rule-ultra.md +0 -0
- /package/extensions/{caveman-session → caveman}/rule.md +0 -0
package/dist/cli/usage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/usage.ts — ledger + session-token usage report, tokscale-style.
|
|
2
|
-
import { co2GramsFor, displayModelId, energyWhFor, foldModelsByLabel, importSessionTokens, ledgerPath, priceFor, readResetWatermark, readRtkAdoption, readRtkRecallDiagnostics, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
|
|
3
|
-
import { resolveRtkBinary } from '../extensions/
|
|
2
|
+
import { PRICE_TIERS_FIRM, co2GramsFor, displayModelId, energyWhFor, foldModelsByLabel, importSessionTokens, ledgerPath, priceFor, pricesOverridePath, readResetWatermark, readRtkAdoption, readRtkRecallDiagnostics, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
|
|
3
|
+
import { resolveRtkBinary } from '../extensions/shared/utils.js';
|
|
4
4
|
import { readRtkGain } from '../extensions/shared/rtk-gain.js';
|
|
5
5
|
import { readUsageDb, reparseGuardReport, syncUsageDb, usageDbPath } from '../extensions/shared/usage-store.js';
|
|
6
6
|
import { withInteractiveSpinner } from './interactive.js';
|
|
@@ -39,11 +39,13 @@ export function summarizeUsage(rows) {
|
|
|
39
39
|
const source = stored ? (synced ? 'stored' : 'stored-stale') : 'live';
|
|
40
40
|
const watermark = readResetWatermark();
|
|
41
41
|
let usd = 0;
|
|
42
|
+
let estimatedUsd = 0;
|
|
42
43
|
let priced = true;
|
|
43
44
|
let savedUsd = 0;
|
|
44
45
|
const byModelUsd = {};
|
|
45
46
|
const byModelBucketUsd = {};
|
|
46
47
|
const byHost = {};
|
|
48
|
+
const byAgent = {};
|
|
47
49
|
const byDayModel = {};
|
|
48
50
|
let co2g = 0;
|
|
49
51
|
let energyWh = 0;
|
|
@@ -55,9 +57,26 @@ export function summarizeUsage(rows) {
|
|
|
55
57
|
};
|
|
56
58
|
// Price by exact key first (gateways can differ), then fold every map under the display label.
|
|
57
59
|
const folded = foldModelsByLabel(session.byModel, session.byModelMessages);
|
|
60
|
+
const priceTiers = {};
|
|
61
|
+
// Named in the loop so the list is built in one pass, then sorted by token volume below.
|
|
62
|
+
const weakModels = [];
|
|
63
|
+
const measuredModels = [];
|
|
58
64
|
for (const [model, t] of Object.entries(session.byModel)) {
|
|
59
|
-
|
|
65
|
+
// A model is measured only when the host priced every one of its messages; a partly measured
|
|
66
|
+
// model would read its unpriced messages as zero, which is the one error worse than a guess.
|
|
67
|
+
const covered = (session.byModelCostMessages[model] ?? 0) >= (session.byModelMessages[model] ?? 0);
|
|
68
|
+
const measured = covered ? session.byModelMeasured[model] : undefined;
|
|
69
|
+
const c = usdCost(t, model, measured);
|
|
60
70
|
usd += c.usd;
|
|
71
|
+
priceTiers[c.tier] = (priceTiers[c.tier] ?? 0) + 1;
|
|
72
|
+
if (c.tier === 'measured')
|
|
73
|
+
measuredModels.push(model);
|
|
74
|
+
// A tier that is not firm is an estimate, so it is kept out of the firm total rather than
|
|
75
|
+
// summed beside a measured cost that would make the whole figure read as a bill.
|
|
76
|
+
if (!PRICE_TIERS_FIRM.includes(c.tier)) {
|
|
77
|
+
estimatedUsd += c.usd;
|
|
78
|
+
weakModels.push(model);
|
|
79
|
+
}
|
|
61
80
|
const label = displayModelId(model);
|
|
62
81
|
byModelUsd[label] = (byModelUsd[label] ?? 0) + c.usd;
|
|
63
82
|
byModelBucketUsd[label] ??= { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
@@ -67,6 +86,11 @@ export function summarizeUsage(rows) {
|
|
|
67
86
|
byHost[label][host] ??= { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
68
87
|
addTok(byHost[label][host], hb);
|
|
69
88
|
}
|
|
89
|
+
for (const [agent, ab] of Object.entries(session.byAgent[model] ?? {})) {
|
|
90
|
+
byAgent[agent] ??= { tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, msgs: 0 };
|
|
91
|
+
addTok(byAgent[agent].tokens, ab);
|
|
92
|
+
byAgent[agent].msgs += session.byAgentMessages[model]?.[agent] ?? 0;
|
|
93
|
+
}
|
|
70
94
|
co2g += co2GramsFor(model, t.output);
|
|
71
95
|
energyWh += energyWhFor(model, t.output);
|
|
72
96
|
if (!c.priced)
|
|
@@ -81,6 +105,10 @@ export function summarizeUsage(rows) {
|
|
|
81
105
|
}
|
|
82
106
|
}
|
|
83
107
|
const byTool = Object.entries(session.byTool).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
108
|
+
// Heaviest first, then capped to five: the line is guidance, not a dump of the model table.
|
|
109
|
+
const weakKeys = weakModels
|
|
110
|
+
.toSorted((a, b) => tokenTotal(session.byModel[b]) - tokenTotal(session.byModel[a]))
|
|
111
|
+
.slice(0, 5);
|
|
84
112
|
return {
|
|
85
113
|
total: rows.length,
|
|
86
114
|
byKind,
|
|
@@ -94,6 +122,7 @@ export function summarizeUsage(rows) {
|
|
|
94
122
|
byModelBucketUsd,
|
|
95
123
|
byModelMessages: folded.byModelMessages,
|
|
96
124
|
byHost,
|
|
125
|
+
byAgent,
|
|
97
126
|
byDay: session.byDay,
|
|
98
127
|
byDayModel,
|
|
99
128
|
byTool,
|
|
@@ -106,6 +135,10 @@ export function summarizeUsage(rows) {
|
|
|
106
135
|
rtkRecall: readRtkRecallDiagnostics(resolveRtkBinary()),
|
|
107
136
|
usd,
|
|
108
137
|
priced,
|
|
138
|
+
estimatedUsd,
|
|
139
|
+
priceTiers,
|
|
140
|
+
weakModels: weakKeys,
|
|
141
|
+
measuredModels,
|
|
109
142
|
savedUsd,
|
|
110
143
|
costMeasured: session.costMeasured,
|
|
111
144
|
co2g,
|
|
@@ -119,6 +152,10 @@ export function summarizeUsage(rows) {
|
|
|
119
152
|
function fmt(n) {
|
|
120
153
|
return n.toLocaleString('en-US');
|
|
121
154
|
}
|
|
155
|
+
/** Every billed bucket, so a model sorts by how much of the estimate it carries. */
|
|
156
|
+
function tokenTotal(t) {
|
|
157
|
+
return t.input + t.output + t.cacheRead + t.cacheWrite;
|
|
158
|
+
}
|
|
122
159
|
function fmtShort(n) {
|
|
123
160
|
if (n >= 1e12)
|
|
124
161
|
return (n / 1e12).toFixed(1) + 'T';
|
|
@@ -163,12 +200,34 @@ function printReport(report) {
|
|
|
163
200
|
}
|
|
164
201
|
const t = report.tokens;
|
|
165
202
|
const a = report.rtkAdoption;
|
|
203
|
+
// A tier that is not firm is named, so a figure that is partly a guess never reads as a bill.
|
|
204
|
+
const weak = ['tail', 'default'].reduce((sum, tier) => sum + (report.priceTiers[tier] ?? 0), 0);
|
|
205
|
+
const overridden = report.priceTiers.override ?? 0;
|
|
206
|
+
// The count stays in the cell; the names go on their own line below, because the cell is a
|
|
207
|
+
// fixed 118 columns and a list of model ids would be truncated to nothing.
|
|
208
|
+
const listed = report.weakModels.map((m) => displayModelId(m));
|
|
209
|
+
const measuredCount = report.measuredModels.length;
|
|
210
|
+
const costNote = weak > 0
|
|
211
|
+
? ` · ${weak} model${weak === 1 ? '' : 's'} priced from another provider or the default table — add them to ${pricesOverridePath()}`
|
|
212
|
+
: overridden > 0
|
|
213
|
+
? ` · ${overridden} model${overridden === 1 ? '' : 's'} priced from your override file`
|
|
214
|
+
: measuredCount > 0
|
|
215
|
+
? ` · ${measuredCount} model${measuredCount === 1 ? '' : 's'} priced by the host`
|
|
216
|
+
: '';
|
|
166
217
|
for (const l of textTable(['Metric', 'Value'], [
|
|
167
218
|
['Tokens', `${fmt(t.input)} in · ${fmt(t.output)} out · ${fmt(t.cacheRead)} cache read · ${fmt(t.cacheWrite)} cache write`],
|
|
168
|
-
['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.)`],
|
|
219
|
+
['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.)${costNote}`],
|
|
169
220
|
['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'}`],
|
|
170
221
|
], [false, false], 118))
|
|
171
222
|
console.log(l);
|
|
223
|
+
if (measuredCount > 0) {
|
|
224
|
+
// Named, because a measured figure is only worth trusting when a reader sees whose it is.
|
|
225
|
+
console.log(` Priced by the host: ${measuredCount} model${measuredCount === 1 ? '' : 's'}`);
|
|
226
|
+
}
|
|
227
|
+
if (weak > 0) {
|
|
228
|
+
// Heaviest first, one per host spelling, so the operator sees exactly what to write.
|
|
229
|
+
console.log(` Unpriced (heaviest first): ${listed.join(', ')}`);
|
|
230
|
+
}
|
|
172
231
|
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));
|
|
173
232
|
const models = allModels.slice(0, ROW_CAP);
|
|
174
233
|
if (models.length) {
|
|
@@ -186,6 +245,18 @@ function printReport(report) {
|
|
|
186
245
|
if (allModels.length > ROW_CAP)
|
|
187
246
|
console.log(` showing top ${ROW_CAP} of ${fmt(allModels.length)} models — full set in the dashboard`);
|
|
188
247
|
}
|
|
248
|
+
const agentKinds = Object.entries(report.byAgent).filter(([, per]) => per.msgs > 0).toSorted((x, y) => y[1].msgs - x[1].msgs);
|
|
249
|
+
if (agentKinds.length > 1) {
|
|
250
|
+
console.log(' BY AGENT');
|
|
251
|
+
const top = agentKinds.reduce((m, [, per]) => Math.max(m, per.tokens.input + per.tokens.output + per.tokens.cacheRead + per.tokens.cacheWrite), 1);
|
|
252
|
+
const arows = agentKinds.map(([kind, per]) => {
|
|
253
|
+
const total = per.tokens.input + per.tokens.output + per.tokens.cacheRead + per.tokens.cacheWrite;
|
|
254
|
+
return [kind === 'subagent' ? 'Subagents' : 'Main agent', fmt(per.msgs), fmt(per.tokens.input), fmt(per.tokens.output), fmt(per.tokens.cacheRead), `${bar(total / top, 8)} ${fmtShort(total)}`];
|
|
255
|
+
});
|
|
256
|
+
for (const line of textTable(['Agent', 'Msgs', 'Input', 'Output', 'Cache read', 'Share'], arows, [false, true, true, true, true, false])) {
|
|
257
|
+
console.log(line);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
189
260
|
const cmdRows = report.byTool.map(([tool, n]) => ({ name: tool, count: n, saved: null, avgPct: null, avgMs: null }));
|
|
190
261
|
for (const r of report.rtkGain.byCommand) {
|
|
191
262
|
cmdRows.push({ name: r.command, count: r.count, saved: r.saved, avgPct: r.avgPct, avgMs: r.avgMs });
|
|
@@ -14,7 +14,7 @@ import { activeModesSummary, COMBO_LEVELS, getSharedComboState, hasModeState, no
|
|
|
14
14
|
import { hostSelect, injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
|
|
15
15
|
import { announceStatus } from '../shared/status.js';
|
|
16
16
|
import { isComboSetupComplete, readCavemanDefault, readComboDefault, readPluginSettings, readPonytailDefault, readRtkDefault, saveComboSetup, } from '../shared/plugin-settings.js';
|
|
17
|
-
import { findHoistedPackage, isPiProcess } from '../
|
|
17
|
+
import { findHoistedPackage, isPiProcess } from '../shared/utils.js';
|
|
18
18
|
const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const PONYTAIL_FALLBACK_INTENSITY = {
|
|
20
20
|
lite: 'Prefer the simplest correct solution.',
|
|
@@ -5,7 +5,7 @@ import { setExtensionLabel } from '../shared/host.js';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { appendUsage, readUsage } from '../shared/usage-ledger.js';
|
|
8
|
-
import { checkAddonsSummary, runAddonUpdate } from '../
|
|
8
|
+
import { checkAddonsSummary, runAddonUpdate } from '../updater/index.js';
|
|
9
9
|
const HELP = [
|
|
10
10
|
'/tersio status — active modes + combo level',
|
|
11
11
|
'/tersio check — add-on version check',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { resolveRtkBinary } from '../
|
|
5
|
+
import { resolveRtkBinary } from '../shared/utils.js';
|
|
6
6
|
import { readRtkDefault } from '../shared/plugin-settings.js';
|
|
7
7
|
import { getSharedComboState, lastCustomValue, sessionEntries } from '../shared/session-state.js';
|
|
8
8
|
/** Tool name to `rtk pipe --filter` name. Exported so the OpenCode host reuses one map. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getSharedComboState, hasModeState, isOmpSubagentPrompt, lastCustomValue, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, systemPromptIncludes } from '../shared/session-state.js';
|
|
2
2
|
import { injectPromptText, onHostEvent, setExtensionLabel, stringArrayToolParams } from '../shared/host.js';
|
|
3
3
|
import { readRtkDefault } from '../shared/plugin-settings.js';
|
|
4
|
-
import { resolveRtkBinary } from '../
|
|
4
|
+
import { resolveRtkBinary } from '../shared/utils.js';
|
|
5
5
|
import { announceStatus } from '../shared/status.js';
|
|
6
6
|
const DEFAULT_ENABLED = false;
|
|
7
7
|
function asBoolean(value) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// OpenCode plugin entry: maps its plugin API onto the shared ExtensionApi the mode extensions consume.
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
|
-
import cavemanSessionExtension from '
|
|
4
|
-
import rtkSessionExtension from '
|
|
5
|
-
import rtkFilterExtension, { FILTER_BY_TOOL, MIN_FILTER_BYTES, filterThroughRtk, filterableText, rtkActive } from '
|
|
6
|
-
import comboToggleExtension from '
|
|
7
|
-
import tersioCommandsExtension from '
|
|
8
|
-
import { getSharedComboState } from '
|
|
9
|
-
import { formatStatus } from '
|
|
10
|
-
import { resolveRtkBinary } from '
|
|
3
|
+
import cavemanSessionExtension from './caveman/index.js';
|
|
4
|
+
import rtkSessionExtension from './rtk/session.js';
|
|
5
|
+
import rtkFilterExtension, { FILTER_BY_TOOL, MIN_FILTER_BYTES, filterThroughRtk, filterableText, rtkActive } from './rtk/filter.js';
|
|
6
|
+
import comboToggleExtension from './combo/index.js';
|
|
7
|
+
import tersioCommandsExtension from './commands/index.js';
|
|
8
|
+
import { getSharedComboState } from './shared/session-state.js';
|
|
9
|
+
import { formatStatus } from './shared/status.js';
|
|
10
|
+
import { resolveRtkBinary } from './shared/utils.js';
|
|
11
11
|
// The npm name, so the plugin list reads the same as the OMP and pi installs.
|
|
12
12
|
const plugin = {
|
|
13
13
|
id: '@krtclcdy/tersio',
|