@krtclcdy/tersio 2.10.0 → 2.12.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.
@@ -78,8 +78,10 @@ export default function comboToggleExtension(pi) {
78
78
  return useState(reconcileSharedComboEntries(sessionEntries(ctx)), ctx);
79
79
  }
80
80
  function listen(ctx) {
81
+ // Stable identity: the bridge set dedupes, so repeated track()/command
82
+ // calls register once instead of stacking duplicate listeners.
81
83
  if (ctx?.hasUI)
82
- setSharedComboListener((state) => useState(state));
84
+ setSharedComboListener(useState);
83
85
  }
84
86
  function track(ctx) {
85
87
  listen(ctx);
@@ -112,8 +114,6 @@ export default function comboToggleExtension(pi) {
112
114
  useState(setSharedComboLevel(level), ctx);
113
115
  const active = activeModesSummary(getSharedComboState());
114
116
  ctx?.ui?.notify?.(level === 'off' ? `Combo off — all tersio modes inactive for this session. Active: ${active}.` : `Combo ${level} on — ${active} active for this session.`, 'info');
115
- if (ctx?.reload)
116
- await ctx.reload();
117
117
  },
118
118
  });
119
119
  pi.on('session_start', async (_event, ctx) => {
@@ -1,6 +1,6 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
- import { activeModesSummary, asPromptArray, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, paintStatusBar, reconcileSharedComboEntries, sessionEntries, setSharedComboMode } from '../shared/session-state.js';
3
+ import { activeModesSummary, asPromptArray, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, paintStatusBar, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode } from '../shared/session-state.js';
4
4
  import { readRtkDefault } from '../shared/plugin-settings.js';
5
5
  const DEFAULT_ENABLED = false;
6
6
  function asBoolean(value) {
@@ -48,6 +48,13 @@ export default function rtkSessionExtension(pi) {
48
48
  ctx?.ui?.notify?.(enabled ? `RTK on — compact shell output for this session. Active: ${active}.` : `RTK off. Active: ${active}.`, 'info');
49
49
  }
50
50
  pi.setLabel?.('RTK session toggle');
51
+ // Live mirror: a /tersio or /combo switch publishes shared state — adopt
52
+ // it at once so the next turn and the rtk_run gate see it, no reload.
53
+ function syncFromShared(state) {
54
+ enabled = state.rtk === 'on';
55
+ syncStatus();
56
+ }
57
+ setSharedComboListener(syncFromShared);
51
58
  pi.registerCommand?.('rtk', {
52
59
  description: 'Toggle RTK compact shell-output guidance for this session',
53
60
  handler: async (args, ctx) => {
@@ -49,12 +49,13 @@ function bridge() {
49
49
  if (existing?.state)
50
50
  return existing;
51
51
  const initial = normalizedState((existing || COMBO_LEVELS.off));
52
- return (globalThis[BRIDGE_KEY] = { state: initial, listener: null });
52
+ return (globalThis[BRIDGE_KEY] = { state: initial, listeners: new Set() });
53
53
  }
54
54
  function publish(state) {
55
55
  const shared = bridge();
56
56
  shared.state = state;
57
- shared.listener?.(state);
57
+ for (const listener of shared.listeners)
58
+ listener(state);
58
59
  return state;
59
60
  }
60
61
  export function normalizeInputCommand(value) {
@@ -148,7 +149,13 @@ export function activeModesSummary(state) {
148
149
  return `caveman=${state.caveman.toUpperCase()}, rtk=${state.rtk.toUpperCase()}, ponytail=${state.ponytail.toUpperCase()}`;
149
150
  }
150
151
  export function setSharedComboListener(listener) {
151
- bridge().listener = typeof listener === 'function' ? listener : null;
152
+ // Additive: every sibling extension syncs its local mirror on publish, so
153
+ // a /tersio or /combo switch takes effect next turn with no session reload.
154
+ // A null listener clears all (no current callers; reserved for teardown).
155
+ if (typeof listener === 'function')
156
+ bridge().listeners.add(listener);
157
+ else
158
+ bridge().listeners.clear();
152
159
  }
153
160
  export function resetSharedComboState() {
154
161
  return setSharedComboLevel('off');
@@ -87,11 +87,16 @@ export function importSessionTokens() {
87
87
  walkJsonl(codexSessionsDir(), files, 2000);
88
88
  }
89
89
  let codexProvider = null;
90
+ const recent = [];
90
91
  function ingest(model, usage, ts) {
91
92
  addInto(totals, usage);
92
93
  byModel[model] ??= zeroBreakdown();
93
94
  addInto(byModel[model], usage);
94
95
  byModelMessages[model] = (byModelMessages[model] ?? 0) + 1;
96
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? Math.floor(v) : 0);
97
+ const ms = ts === undefined ? NaN : typeof ts === 'number' ? ts : Date.parse(ts);
98
+ if (Number.isFinite(ms))
99
+ recent.push({ m: model, i: num(usage.input), o: num(usage.output), t: ms });
95
100
  const day = ts !== undefined ? dayKey(ts) : null;
96
101
  if (day) {
97
102
  byDay[day] ??= zeroBreakdown();
@@ -153,7 +158,8 @@ export function importSessionTokens() {
153
158
  catch { /* skip corrupt lines */ }
154
159
  }
155
160
  }
156
- return { messages, totals, byModel, byDay, byDayModel, byTool, byModelMessages, costMeasured };
161
+ recent.sort((a, b) => b.t - a.t);
162
+ return { messages, totals, byModel, byDay, byDayModel, byTool, byModelMessages, costMeasured, recent: recent.slice(0, 25) };
157
163
  }
158
164
  // Lead binary of a shell string: first segment head past `cd` chains and
159
165
  // VAR=x assignments (`cd /x && git status` → `git`). Falls back to `bash`.
@@ -215,3 +221,14 @@ export function readUsage() {
215
221
  }
216
222
  return rows;
217
223
  }
224
+ // Delete the ledger file. Returns the rows cleared (0 when already absent).
225
+ // Only tersio-owned data lives here; session transcripts and the RTK
226
+ // database are never touched.
227
+ export function clearUsageLedger() {
228
+ const rows = readUsage().length;
229
+ try {
230
+ fs.unlinkSync(ledgerPath());
231
+ }
232
+ catch { /* already absent */ }
233
+ return rows;
234
+ }
@@ -1,8 +1,8 @@
1
1
  // /tersio unified root command — routes every tersio capability through one
2
2
  // namespaced entrypoint. Aliases (/caveman, /rtk, /combo, /ai-addons) keep
3
3
  // working: this router drives the same shared bridge + session entries the
4
- // sibling extensions restore from, then reloads so their local mirrors sync
5
- // (same mechanism /combo already uses to drive its siblings).
4
+ // sibling extensions restore from. Shared-state publishes sync their local
5
+ // mirrors live, so switches notify and take effect next turn — no reload.
6
6
  import { COMBO_LEVELS, activeModesSummary, getSharedComboState, normalizeComboLevel, normalizeMode, reconcileSharedComboEntries, sessionEntries, setSharedComboLevel, setSharedComboMode, } from '../shared/session-state.js';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
@@ -56,10 +56,9 @@ async function openGainDashboard(pi, ctx) {
56
56
  }
57
57
  export default function tersioCommandsExtension(pi) {
58
58
  pi.setLabel?.('Tersio unified root command');
59
- async function reload(ctx) {
60
- if (ctx?.reload)
61
- await ctx.reload();
62
- }
59
+ // Mode switches never reload the session: entries persist, the shared
60
+ // bridge publishes to every sibling mirror, and the change injects on the
61
+ // next turn like a normal input message. Only notify below.
63
62
  pi.registerCommand?.('tersio', {
64
63
  description: 'Tersio root: caveman|combo|rtk|ponytail|status|check|update|gain|usage|help',
65
64
  handler: async (args, ctx) => {
@@ -86,7 +85,6 @@ export default function tersioCommandsExtension(pi) {
86
85
  setSharedComboMode('caveman', mode);
87
86
  appendUsage('toggle', `caveman=${mode}`);
88
87
  ctx?.ui?.notify?.(`Caveman ${mode} on — Active: ${activeModesSummary(getSharedComboState())}.`, 'info');
89
- await reload(ctx);
90
88
  return;
91
89
  }
92
90
  if (sub === 'rtk') {
@@ -104,7 +102,6 @@ export default function tersioCommandsExtension(pi) {
104
102
  setSharedComboMode('rtk', enabled);
105
103
  appendUsage('toggle', `rtk=${rest}`);
106
104
  ctx?.ui?.notify?.(`RTK ${rest} — Active: ${activeModesSummary(getSharedComboState())}.`, 'info');
107
- await reload(ctx);
108
105
  return;
109
106
  }
110
107
  if (sub === 'ponytail') {
@@ -117,7 +114,6 @@ export default function tersioCommandsExtension(pi) {
117
114
  setSharedComboMode('ponytail', mode);
118
115
  appendUsage('toggle', `ponytail=${mode}`);
119
116
  ctx?.ui?.notify?.(`Ponytail ${mode} — Active: ${activeModesSummary(getSharedComboState())}.`, 'info');
120
- await reload(ctx);
121
117
  return;
122
118
  }
123
119
  if (sub === 'combo') {
@@ -140,7 +136,6 @@ export default function tersioCommandsExtension(pi) {
140
136
  setSharedComboLevel(level);
141
137
  appendUsage('toggle', `combo=${level}`);
142
138
  ctx?.ui?.notify?.(`Combo ${level} on — ${activeModesSummary(getSharedComboState())} active for this session.`, 'info');
143
- await reload(ctx);
144
139
  return;
145
140
  }
146
141
  if (sub === 'check') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krtclcdy/tersio",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "✂️ Token-saving OMP add-ons: terse Caveman replies, compact RTK shell output, lean Ponytail code calls — one-command combo presets.",
5
5
  "keywords": [
6
6
  "omp",
package/tersio.js CHANGED
@@ -2,30 +2,32 @@
2
2
  // tersio.ts — Install Tersio (caveman/rtk/ponytail) add-ons on any OMP device.
3
3
  // Usage: node tersio.js [install|update|reinstall|doctor|uninstall|version|help] [options]
4
4
  // Requires: node/npm and omp CLI
5
- import { PACKAGE_BIN, PACKAGE_VERSION, applyUpdate, commandArg, gain, dashboardExport, dashboardOpen, dashboardPort, doctor, showHelp, showVersion, uninstall, unknownCommand, update, usage, } from './cli/common.js';
5
+ import { PACKAGE_BIN, PACKAGE_VERSION, applyUpdate, commandArg, gain, dashboardExport, dashboardOpen, dashboardPort, doctor, reset, showHelp, showVersion, uninstall, unknownCommand, update, usage, } from './cli/common.js';
6
6
  import { closeRL } from './cli/interactive.js';
7
7
  import { runInstall } from './cli/install.js';
8
8
  import { runDoctor } from './cli/doctor.js';
9
9
  import { runLatestUpdate } from './cli/update.js';
10
10
  import { runUninstall } from './cli/uninstall.js';
11
11
  import { runUsage } from './cli/usage.js';
12
+ import { runReset } from './cli/reset.js';
12
13
  import { runDashboard } from './cli/dashboard.js';
13
14
  function printHelp() {
14
15
  console.log(`Usage: ${PACKAGE_BIN} [command] [options]
15
16
 
16
17
  Commands:
17
- install Install the add-ons (user scope by default)
18
+ install Install the add-ons (user scope: all OMP sessions)
18
19
  update Refresh the CLI and add-ons (RTK binary, Caveman rule, Ponytail)
19
20
  reinstall Clean and reinstall the user-scope add-ons
20
21
  doctor Check the current installation
21
22
  usage Ledger-backed usage + savings report
22
23
  gain Open the gain dashboard (localhost only)
24
+ reset Clear tersio statistics (usage ledger)
23
25
  uninstall Remove the managed extensions
24
26
  version Print the package version
25
27
  help Show this help
26
28
 
27
29
  Options:
28
- --scope user|project|both
30
+ --scope user (legacy; accepted and ignored — user scope is the only scope)
29
31
  --keep-ponytail (uninstall: keep the Ponytail plugin — removed by default)
30
32
  --remove-rtk (uninstall: also remove the RTK binary)
31
33
  --combo-default off|medium|balanced|max (implies caveman, rtk, ponytail)
@@ -79,6 +81,11 @@ async function main() {
79
81
  closeRL();
80
82
  return;
81
83
  }
84
+ if (reset) {
85
+ await runReset();
86
+ closeRL();
87
+ return;
88
+ }
82
89
  if (uninstall) {
83
90
  await runUninstall();
84
91
  closeRL();