@krtclcdy/tersio 2.16.0 → 2.18.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 CHANGED
@@ -1,4 +1,4 @@
1
- ![Banner](/TersioBanner.webp)
1
+ ![Banner](/assets/Banner.webp)
2
2
 
3
3
  # ✂️ Tersio — Token-saving OMP Add-ons
4
4
 
@@ -8,7 +8,7 @@
8
8
  [![Build](https://shieldcn.dev/github/ci/KurutoDenzeru/tersio.svg?variant=branded&size=xs&logo=githubactions&label=Build)](https://github.com/KurutoDenzeru/tersio/actions)
9
9
  [![MIT](https://shieldcn.dev/badge/license-MIT-2563eb.svg?variant=branded&size=xs&logo=opensourceinitiative)](./LICENSE)
10
10
 
11
- Terse replies, compact shell output, and minimal code decisions for [Oh My Pi (OMP)](https://github.com/can1357/oh-my-pi) — one-command combo presets.
11
+ Terse replies, compact shell output, and minimal code decisions for [Oh My Pi (OMP)](https://github.com/can1357/oh-my-pi) — one-command combo presets.
12
12
 
13
13
  ## ⚡ Getting Started
14
14
 
@@ -80,6 +80,22 @@ Measured savings against the same workload without the modes. Token counts are r
80
80
 
81
81
  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).
82
82
 
83
+ ## 📈 Gain Dashboard
84
+
85
+ `tersio gain --open` serves a local dashboard (127.0.0.1 only) that charts your own savings from `~/.omp/plugins/tersio-usage.jsonl`. Three views:
86
+
87
+ **Live feed and savings.** Token throughput, cost, savings bento, and recent activity — the top of the dashboard.
88
+
89
+ ![Gain dashboard: live token feed, cost, savings, and activity](/assets/GainHero.webp)
90
+
91
+ **Top models and recent requests.** Per-model cost breakdown plus the last requests with elapsed time and token speed.
92
+
93
+ ![Gain dashboard: top models with per-model cost, recent requests with elapsed time and token speed](/assets/GainModels.webp)
94
+
95
+ **Command tools.** Token savings, average rate, and timing per shell tool.
96
+
97
+ ![Gain dashboard: command tools with token savings, average rate, and timing](/assets/GainTools.webp)
98
+
83
99
  ## 🖥️ CLI
84
100
 
85
101
  | Command | Purpose |
package/cli/dashboard.js CHANGED
@@ -88,6 +88,10 @@ async function runDashboard(options) {
88
88
  res.end(html);
89
89
  });
90
90
  server.listen(options.port, '127.0.0.1', () => {
91
+ try {
92
+ process.title = 'tersio gain';
93
+ }
94
+ catch { /* non-POSIX shells keep node */ }
91
95
  const address = server.address();
92
96
  const port = typeof address === 'object' && address ? address.port : options.port;
93
97
  const url = `http://127.0.0.1:${port}`;
package/cli/usage.js CHANGED
@@ -1,8 +1,15 @@
1
1
  // cli/usage.ts — ledger + session-token usage report, tokscale-style.
2
+ import path from 'node:path';
2
3
  import { co2GramsFor, energyWhFor, importSessionTokens, ledgerPath, priceFor, readResetWatermark, readUsage, refreshPricesIfStale, sessionsDir, usdCost, } from '../extensions/shared/usage-ledger.js';
3
4
  import { readRtkGain } from '../extensions/shared/rtk-gain.js';
4
5
  import { withInteractiveSpinner } from './interactive.js';
5
6
  import { PACKAGE_VERSION } from './common.js';
7
+ // RTK's history is machine-wide, so every project's commands have to reach
8
+ // the table or a busy repo silently stands in for all of them. The old cap of
9
+ // 10 rows ranked by tokens saved hid whole projects whose commands save little
10
+ // to nothing; 2000 groups covers a long history (~1.7k today) while still
11
+ // bounding /data.json.
12
+ const RTK_COMMAND_ROWS = 2000;
6
13
  export function summarizeUsage(rows) {
7
14
  refreshPricesIfStale();
8
15
  const byKind = {};
@@ -18,12 +25,14 @@ export function summarizeUsage(rows) {
18
25
  let priced = true;
19
26
  let savedUsd = 0;
20
27
  const byModelUsd = {};
28
+ const byModelBucketUsd = {};
21
29
  let co2g = 0;
22
30
  let energyWh = 0;
23
31
  for (const [model, t] of Object.entries(session.byModel)) {
24
32
  const c = usdCost(t, model);
25
33
  usd += c.usd;
26
34
  byModelUsd[model] = c.usd;
35
+ byModelBucketUsd[model] = c.buckets;
27
36
  co2g += co2GramsFor(model, t.output);
28
37
  energyWh += energyWhFor(model, t.output);
29
38
  if (!c.priced)
@@ -41,12 +50,16 @@ export function summarizeUsage(rows) {
41
50
  tokens: session.totals,
42
51
  byModel: session.byModel,
43
52
  byModelUsd,
53
+ byModelBucketUsd,
44
54
  byModelMessages: session.byModelMessages,
45
55
  byDay: session.byDay,
46
56
  byDayModel: session.byDayModel,
47
57
  byTool,
48
- recent: session.recent,
49
- rtkGain: readRtkGain(10, watermark || undefined),
58
+ recent: session.recent.map((r) => ({
59
+ ...r,
60
+ est: usdCost({ input: r.i, output: r.o, cacheRead: r.cr ?? 0, cacheWrite: r.cw ?? 0 }, r.m).usd,
61
+ })),
62
+ rtkGain: readRtkGain(RTK_COMMAND_ROWS, watermark || undefined),
50
63
  usd,
51
64
  priced,
52
65
  savedUsd,
@@ -119,28 +132,30 @@ function printReport(report) {
119
132
  console.log(l);
120
133
  }
121
134
  }
122
- const cmdRows = report.byTool.map(([tool, n]) => ({ name: tool, count: n, saved: null, avgPct: null, avgMs: null }));
135
+ const cmdRows = report.byTool.map(([tool, n]) => ({ name: tool, project: '', count: n, saved: null, avgPct: null, avgMs: null }));
123
136
  for (const r of report.rtkGain.byCommand) {
124
- cmdRows.push({ name: r.command, count: r.count, saved: r.saved, avgPct: r.avgPct, avgMs: r.avgMs });
137
+ cmdRows.push({ name: r.command, project: r.project, count: r.count, saved: r.saved, avgPct: r.avgPct, avgMs: r.avgMs });
125
138
  }
126
139
  cmdRows.sort((a, b) => b.count - a.count);
127
140
  if (cmdRows.length) {
128
141
  const g = report.rtkGain;
142
+ const projects = new Set(g.byCommand.map((r) => r.project).filter(Boolean));
129
143
  const scope = g.commands
130
- ? ` COMMAND TOOLS ${fmt(report.byTool.length)} tools · ${fmt(g.commands)} commands · ${fmtShort(g.saved)} saved`
144
+ ? ` COMMAND TOOLS ${fmt(report.byTool.length)} tools · ${fmt(g.commands)} commands · ${fmtShort(g.saved)} saved · ${fmt(projects.size)} projects`
131
145
  : ' COMMAND TOOLS';
132
146
  console.log(scope);
133
147
  const top = cmdRows.reduce((m, r) => Math.max(m, r.count), 1);
134
148
  const crows = cmdRows.slice(0, 15).map((r, idx) => [
135
149
  String(idx + 1),
136
150
  r.name,
151
+ r.project ? path.basename(r.project) : '–',
137
152
  fmt(r.count),
138
153
  r.saved === null ? '–' : fmtShort(r.saved),
139
154
  r.avgPct === null ? '–' : `${r.avgPct.toFixed(1)}%`,
140
155
  r.avgMs === null ? '–' : fmtMs(r.avgMs),
141
156
  bar(r.count / top, 8),
142
157
  ]);
143
- for (const l of table(['#', 'Tool/Command', 'Count', 'Saved', 'Avg%', 'Time', 'Impact'], crows, [true, false, true, true, true, true, false], 30)) {
158
+ for (const l of table(['#', 'Tool/Command', 'Project', 'Count', 'Saved', 'Avg%', 'Time', 'Impact'], crows, [true, false, false, true, true, true, true, false], 30)) {
144
159
  console.log(l);
145
160
  }
146
161
  }