@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.
Files changed (52) hide show
  1. package/README.md +13 -30
  2. package/dashboard/dist/index.html +18 -16
  3. package/dist/cli/backups.js +194 -0
  4. package/dist/cli/common.js +2 -2
  5. package/dist/cli/dashboard.js +125 -98
  6. package/dist/cli/doctor-fix.js +41 -44
  7. package/dist/cli/doctor.js +69 -7
  8. package/dist/cli/hosts.js +4 -4
  9. package/dist/cli/install.js +71 -32
  10. package/dist/cli/manifest.js +45 -17
  11. package/dist/cli/profile.js +1 -1
  12. package/dist/cli/uninstall.js +21 -5
  13. package/dist/cli/update.js +3 -3
  14. package/dist/cli/usage.js +75 -4
  15. package/dist/extensions/{combo-toggle → combo}/index.js +1 -1
  16. package/dist/extensions/{tersio-commands → commands}/index.js +1 -1
  17. package/dist/extensions/{rtk-filter/index.js → rtk/filter.js} +1 -1
  18. package/dist/extensions/{rtk-session/index.js → rtk/session.js} +1 -1
  19. package/dist/extensions/{opencode/server.js → server.js} +8 -8
  20. package/dist/extensions/shared/agent-stats.js +1262 -0
  21. package/dist/extensions/shared/brand-marks.js +154 -0
  22. package/dist/extensions/shared/plugin-settings.js +1 -1
  23. package/dist/extensions/shared/pricing.js +91 -27
  24. package/dist/extensions/shared/usage-ledger.js +79 -17
  25. package/dist/extensions/shared/usage-store.js +106 -22
  26. package/dist/extensions/{lib → shared}/utils.js +1 -1
  27. package/dist/extensions/{ai-addons-updater → updater}/index.js +13 -3
  28. package/dist/probe-catalog.js +48 -0
  29. package/dist/probe-catalog188.js +44 -0
  30. package/dist/tersio.js +2 -2
  31. package/extensions/{combo-toggle → combo}/index.ts +1 -1
  32. package/extensions/{tersio-commands → commands}/index.ts +1 -1
  33. package/extensions/{rtk-filter/index.ts → rtk/filter.ts} +1 -1
  34. package/extensions/{rtk-session/index.ts → rtk/session.ts} +1 -1
  35. package/extensions/{opencode/server.ts → server.ts} +9 -9
  36. package/extensions/shared/agent-stats.ts +1657 -0
  37. package/extensions/shared/brand-marks.ts +163 -0
  38. package/extensions/shared/plugin-settings.ts +1 -1
  39. package/extensions/shared/pricing.ts +107 -28
  40. package/extensions/shared/usage-ledger.ts +94 -19
  41. package/extensions/shared/usage-store.ts +106 -22
  42. package/extensions/{lib → shared}/utils.ts +1 -1
  43. package/extensions/{ai-addons-updater → updater}/index.ts +15 -4
  44. package/package.json +13 -13
  45. /package/dist/extensions/{caveman-session → caveman}/index.js +0 -0
  46. /package/dist/extensions/{caveman-session → caveman}/rule-megacave.md +0 -0
  47. /package/dist/extensions/{caveman-session → caveman}/rule-ultra.md +0 -0
  48. /package/dist/extensions/{caveman-session → caveman}/rule.md +0 -0
  49. /package/extensions/{caveman-session → caveman}/index.ts +0 -0
  50. /package/extensions/{caveman-session → caveman}/rule-megacave.md +0 -0
  51. /package/extensions/{caveman-session → caveman}/rule-ultra.md +0 -0
  52. /package/extensions/{caveman-session → caveman}/rule.md +0 -0
@@ -8,14 +8,13 @@ import {
8
8
  SQLITE_READ_BUFFER,
9
9
  canonicalModelId,
10
10
  classifySessionLine,
11
- OpencodeMessage,
12
11
  classifyOpencodeMessage,
13
12
  costOf,
14
13
  durOf,
14
+ agentOfSessionText,
15
15
  hostOfSessionFile,
16
16
  ingestSessionRow,
17
17
  messageRowId,
18
- OpencodeDbRow,
19
18
  OPENCODE_DB_OVERLAP_MS,
20
19
  opencodeDbPath,
21
20
  opencodeSessionsDir,
@@ -24,10 +23,10 @@ import {
24
23
  sessionsDirs,
25
24
  walkJsonl,
26
25
  } from './usage-ledger.ts';
27
- import { tersioDataPath } from '../lib/utils.ts';
26
+ import { tersioDataPath } from './utils.ts';
28
27
  import { MODEL_ALIASES } from './pricing.ts';
29
28
  import { tersioSettingsFile } from './plugin-settings.ts';
30
- import type { RunStatus, SessionTokens } from './usage-ledger.ts';
29
+ import type { OpencodeDbRow, OpencodeMessage, RunStatus, SessionTokens } from './usage-ledger.ts';
31
30
 
32
31
  export function usageDbPath(): string {
33
32
  const override = process.env.TERSIO_USAGE_DB;
@@ -75,7 +74,7 @@ function query(db: string, sql: string): string[][] {
75
74
  }
76
75
 
77
76
  // Bump on a parse change: unchanged transcripts are never re-read.
78
- const PARSER_VERSION = '11';
77
+ const PARSER_VERSION = '12';
79
78
 
80
79
  // A re-parse that keeps less than half the rows means transcripts vanished mid-migration; the backup is restored instead of publishing the loss.
81
80
  const REPARSE_GUARD_RATIO = 0.5;
@@ -92,8 +91,27 @@ const SCHEMA_SQL =
92
91
  `CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT UNIQUE NOT NULL, mtime REAL NOT NULL, size INTEGER NOT NULL);` +
93
92
  `CREATE TABLE IF NOT EXISTS messages (file_id INTEGER NOT NULL REFERENCES files(id), t REAL, model TEXT NOT NULL,` +
94
93
  ` i INTEGER NOT NULL, o INTEGER NOT NULL, d REAL, cr INTEGER NOT NULL, cw INTEGER NOT NULL,` +
95
- ` usd REAL, st TEXT NOT NULL, code REAL, note TEXT, tools TEXT NOT NULL DEFAULT '[]', h TEXT, id TEXT);` +
96
- `CREATE INDEX IF NOT EXISTS idx_messages_file ON messages(file_id);`;
94
+ ` usd REAL, st TEXT NOT NULL, code REAL, note TEXT, tools TEXT NOT NULL DEFAULT '[]', h TEXT, a TEXT, id TEXT);` +
95
+ // Every read filters or orders by one of these three. Without them each sync and each read
96
+ // walks the whole table: the watermark scan, the day buckets, and the recent list all scan.
97
+ `CREATE INDEX IF NOT EXISTS idx_messages_file ON messages(file_id);` +
98
+ `CREATE INDEX IF NOT EXISTS idx_messages_t ON messages(t);` +
99
+ `CREATE INDEX IF NOT EXISTS idx_messages_h_t ON messages(h, t);` +
100
+ `CREATE INDEX IF NOT EXISTS idx_messages_model ON messages(model);`;
101
+
102
+ /**
103
+ * WAL plus a checkpoint at the end of a sync: the write path stops paying an fsync per commit,
104
+ * and the main file still holds every row, so a backup that copies only usage.db loses nothing.
105
+ */
106
+ function pragmas(db: string): void {
107
+ run(db,
108
+ 'PRAGMA journal_mode=WAL;' +
109
+ 'PRAGMA synchronous=NORMAL;' +
110
+ 'PRAGMA wal_autocheckpoint=256;' +
111
+ 'PRAGMA cache_size=-8000;' +
112
+ 'PRAGMA temp_store=MEMORY;' +
113
+ 'PRAGMA mmap_size=268435456;');
114
+ }
97
115
 
98
116
  function storedParserVersion(db: string): string | null {
99
117
  try {
@@ -104,6 +122,14 @@ function storedParserVersion(db: string): string | null {
104
122
  }
105
123
  }
106
124
 
125
+ // A store written by a newer tersio: this code has no forward migration, so a re-parse would swap rotated history for whatever transcripts are left.
126
+ function isDowngrade(stored: string | null): boolean {
127
+ if (stored === null) return false;
128
+ const storedNum = Number(stored);
129
+ const currentNum = Number(PARSER_VERSION);
130
+ return Number.isFinite(storedNum) && Number.isFinite(currentNum) && storedNum > currentNum;
131
+ }
132
+
107
133
  function countMessages(db: string): number {
108
134
  try {
109
135
  const rows = query(db, `SELECT COUNT(*) FROM messages;`);
@@ -156,7 +182,7 @@ function migrateInPlace(db: string, preCount: number): boolean {
156
182
  `INSERT INTO files_new (path, mtime, size) SELECT path, mtime, size FROM files;` +
157
183
  `CREATE TABLE messages_new (file_id INTEGER NOT NULL REFERENCES files_new(id), t REAL, model TEXT NOT NULL,` +
158
184
  ` i INTEGER NOT NULL, o INTEGER NOT NULL, d REAL, cr INTEGER NOT NULL, cw INTEGER NOT NULL,` +
159
- ` usd REAL, st TEXT NOT NULL, code REAL, note TEXT, tools TEXT NOT NULL DEFAULT '[]', h TEXT, id TEXT);` +
185
+ ` usd REAL, st TEXT NOT NULL, code REAL, note TEXT, tools TEXT NOT NULL DEFAULT '[]', h TEXT, a TEXT, id TEXT);` +
160
186
  `INSERT INTO messages_new (file_id, ${cols}) SELECT (SELECT id FROM files_new WHERE files_new.path = messages.file), ${cols} FROM messages;` +
161
187
  `DROP TABLE messages;DROP TABLE files;` +
162
188
  `ALTER TABLE files_new RENAME TO files;ALTER TABLE messages_new RENAME TO messages;` +
@@ -173,6 +199,29 @@ function migrateInPlace(db: string, preCount: number): boolean {
173
199
  }
174
200
  }
175
201
 
202
+ // v12 shape gains the agent kind (`main`, `subagent`) with a bare ALTER for the two names the
203
+ // store already holds. No rows move and no re-parse is forced, so the existing rows read
204
+ // `NULL` for it: a report then shows them under the main agent only, and new rows carry it.
205
+ function canAddAgent(db: string): boolean {
206
+ try {
207
+ const names = query(db, `PRAGMA table_info(messages);`).map((r) => r[1]);
208
+ return names.includes('file_id') && !names.includes('a');
209
+ } catch {
210
+ return false;
211
+ }
212
+ }
213
+
214
+ // v12 shape gains the agent kind (`main`, `subagent`) with a bare ALTER: no rows move, so no
215
+ // re-parse is forced. Existing rows read NULL, which a report folds under the main agent.
216
+ function addAgentColumn(db: string): boolean {
217
+ try {
218
+ run(db, `ALTER TABLE messages ADD COLUMN a TEXT;`);
219
+ } catch {
220
+ return false;
221
+ }
222
+ return true;
223
+ }
224
+
176
225
  // v10 shape gains the message id with a bare ALTER: no rows move, so no re-parse and no guard trip.
177
226
  function canAlterInPlace(db: string): boolean {
178
227
  try {
@@ -192,15 +241,21 @@ function alterInPlace(db: string): boolean {
192
241
  return !canAlterInPlace(db);
193
242
  }
194
243
 
195
- function ensureSchema(db: string): void {
244
+ function ensureSchema(db: string, forceWipe: boolean): void {
196
245
  fs.mkdirSync(path.dirname(db), { recursive: true });
197
246
  // Fold in rows stored under a pre-alias spelling; the mtime ledger will not re-read those transcripts.
198
247
  const rekeys = MODEL_ALIASES.map((a) => `UPDATE messages SET model='${a.id}' WHERE model='${a.feed}';`).join('');
199
248
  const stored = storedParserVersion(db);
249
+ pragmas(db);
200
250
  if (stored === PARSER_VERSION) {
201
251
  run(db, SCHEMA_SQL + rekeys);
202
252
  return;
203
253
  }
254
+ // A newer store keeps its rows: only an explicit force re-parse may wipe it.
255
+ if (!forceWipe && isDowngrade(stored)) {
256
+ run(db, SCHEMA_SQL + rekeys);
257
+ return;
258
+ }
204
259
  // A version bump rebuilds both tables, so no ALTER patchwork survives a migration.
205
260
  try {
206
261
  if (countMessages(db) > 0 && !newestBackupMatches(stored)) backupUsageDb(db);
@@ -213,6 +268,12 @@ function ensureSchema(db: string): void {
213
268
  run(db, `INSERT OR REPLACE INTO meta (k,v) VALUES ('parser_version','${PARSER_VERSION}');`);
214
269
  return;
215
270
  }
271
+ // The agent column is additive on the current shape: adding it keeps every row, and the
272
+ // unchanged transcripts that already hold their rows simply read NULL until a re-parse.
273
+ if (stored !== null && canAddAgent(db) && addAgentColumn(db)) {
274
+ run(db, `INSERT OR REPLACE INTO meta (k,v) VALUES ('parser_version','${PARSER_VERSION}');`);
275
+ return;
276
+ }
216
277
  run(db, `DROP TABLE IF EXISTS messages;DROP TABLE IF EXISTS files;` + SCHEMA_SQL +
217
278
  `INSERT OR REPLACE INTO meta (k,v) VALUES ('parser_version','${PARSER_VERSION}');`);
218
279
  } catch { /* fresh db, nothing to invalidate */ }
@@ -335,10 +396,13 @@ interface StoredRow {
335
396
  note: string | undefined;
336
397
  tools: string[];
337
398
  host?: string;
399
+ /** `main` or `subagent`; null on a row written before the store carried it. */
400
+ agent?: string | null;
338
401
  id?: string;
339
402
  }
340
403
 
341
- // OpenCode rows carry no status, code, note, or tool list; only tokens and the msg id.
404
+ // OpenCode rows carry no status, code, note, tool list, or subagent marker: only tokens and the
405
+ // msg id. They record as the main agent, so a store never holds a NULL kind.
342
406
  function opencodeStoredRow(oc: NonNullable<ReturnType<typeof classifyOpencodeMessage>>, host: string | undefined, id: string | undefined): StoredRow {
343
407
  return {
344
408
  t: oc.ms ?? null,
@@ -354,11 +418,12 @@ function opencodeStoredRow(oc: NonNullable<ReturnType<typeof classifyOpencodeMes
354
418
  note: undefined,
355
419
  tools: [],
356
420
  host,
421
+ agent: 'main',
357
422
  id,
358
423
  };
359
424
  }
360
425
 
361
- function parseFile(text: string, host?: string, file?: string): StoredRow[] {
426
+ function parseFile(text: string, host?: string, file?: string, agent?: string): StoredRow[] {
362
427
  const rows: StoredRow[] = [];
363
428
  // Guarded: a JSONL transcript must fall through, not abort the sync.
364
429
  let oc: ReturnType<typeof classifyOpencodeMessage> = null;
@@ -403,6 +468,7 @@ function parseFile(text: string, host?: string, file?: string): StoredRow[] {
403
468
  note: undefined,
404
469
  tools: [],
405
470
  host,
471
+ agent,
406
472
  id: rid,
407
473
  });
408
474
  continue;
@@ -426,6 +492,7 @@ function parseFile(text: string, host?: string, file?: string): StoredRow[] {
426
492
  note,
427
493
  tools: parsed.tools ?? [],
428
494
  host,
495
+ agent,
429
496
  id: rid,
430
497
  });
431
498
  } catch { /* skip corrupt lines */ }
@@ -434,10 +501,10 @@ function parseFile(text: string, host?: string, file?: string): StoredRow[] {
434
501
  }
435
502
 
436
503
  function insertSql(file: string, r: StoredRow): string {
437
- return `INSERT INTO messages (file_id, t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, id) VALUES (` +
504
+ return `INSERT INTO messages (file_id, t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, a, id) VALUES (` +
438
505
  `(SELECT id FROM files WHERE path=${esc(file)}),${r.t === null ? 'NULL' : String(r.t)},${esc(r.model)},${r.i},${r.o},` +
439
506
  `${nullNum(r.d)},${r.cr},${r.cw},${nullNum(r.usd)},${esc(r.st)},` +
440
- `${nullNum(r.code)},${nullStr(r.note)},${esc(JSON.stringify(r.tools))},${nullStr(r.host)},${nullStr(r.id)});`;
507
+ `${nullNum(r.code)},${nullStr(r.note)},${esc(JSON.stringify(r.tools))},${nullStr(r.host)},${nullStr(r.agent ?? undefined)},${nullStr(r.id)});`;
441
508
  }
442
509
 
443
510
  // Unchanged transcripts are skipped via mtime+size; rows for deleted ones are kept so rotation never erases history. False when sqlite3 is unavailable.
@@ -449,11 +516,14 @@ export function syncUsageDb(): boolean {
449
516
  try {
450
517
  db = usageDbPath();
451
518
  const stored = storedParserVersion(db);
452
- migrated = stored !== null && stored !== PARSER_VERSION;
453
519
  // The operator deleted sessions on purpose and wants the mirror to match: bypass the guard.
454
520
  const forced = process.env.TERSIO_FORCE_REPARSE === '1';
521
+ // A downgrade keeps its rows: the wipe below cannot rebuild them from rotated transcripts.
522
+ const downgrade = !forced && isDowngrade(stored);
523
+ migrated = stored !== null && stored !== PARSER_VERSION && !downgrade;
524
+ if (downgrade) guardReport = `store was written by a newer tersio (parser ${stored} > ${PARSER_VERSION}); kept its rows and skipped the re-parse`;
455
525
  if (migrated && !forced) preWipe = countMessages(db);
456
- ensureSchema(db);
526
+ ensureSchema(db, forced);
457
527
  maybeScheduledBackup(db);
458
528
  } catch {
459
529
  return false;
@@ -526,7 +596,7 @@ export function syncUsageDb(): boolean {
526
596
  try {
527
597
  for (const file of changed) {
528
598
  const text = fs.readFileSync(file, 'utf8');
529
- for (const r of parseFile(text, hostOfSessionFile(file), file)) {
599
+ for (const r of parseFile(text, hostOfSessionFile(file), file, agentOfSessionText(text))) {
530
600
  if (!push(insertSql(file, r))) return false;
531
601
  }
532
602
  }
@@ -561,6 +631,8 @@ export function syncUsageDb(): boolean {
561
631
  if (migrated) {
562
632
  try { run(db, `VACUUM;`); } catch { /* best-effort */ }
563
633
  }
634
+ // Fold the WAL back into the main file: a backup copies one file, and a checkpoint keeps it whole.
635
+ try { run(db, `PRAGMA wal_checkpoint(TRUNCATE);`); } catch { /* best-effort */ }
564
636
  return true;
565
637
  }
566
638
 
@@ -573,18 +645,25 @@ export function readUsageDb(): StoredUsage | null {
573
645
  }
574
646
  let rows: string[][];
575
647
  try {
576
- rows = query(db, `SELECT t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, id FROM messages;`);
648
+ // The `tools` column is the widest by far and the common case is the literal `[]`, so it is
649
+ // read as one column and skipped in JS rather than parsed per row. A store written before
650
+ // the agent column does not hold it, so the row is read without that column.
651
+ const names = query(db, `PRAGMA table_info(messages);`).map((r) => r[1]);
652
+ const agentCol = names.includes('a') ? 'a' : "''";
653
+ rows = query(db, `SELECT t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, ${agentCol}, id FROM messages;`);
577
654
  } catch {
578
655
  return null;
579
656
  }
580
657
  const accum = newSessionAccum();
581
- for (const [t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, id] of rows) {
658
+ for (const [t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, a, id] of rows) {
582
659
  const ts = t === '' ? undefined : Number(t);
583
660
  let toolNames: string[] = [];
584
- try {
585
- const parsed: unknown = JSON.parse(tools || '[]');
586
- if (Array.isArray(parsed)) toolNames = parsed.filter((x): x is string => typeof x === 'string');
587
- } catch { /* keep empty */ }
661
+ if (tools && tools !== '[]' && tools !== '') {
662
+ try {
663
+ const parsed: unknown = JSON.parse(tools);
664
+ if (Array.isArray(parsed)) toolNames = parsed.filter((x): x is string => typeof x === 'string');
665
+ } catch { /* keep empty */ }
666
+ }
588
667
  const usage: Record<string, unknown> = {
589
668
  input: Number(i) || 0,
590
669
  output: Number(o) || 0,
@@ -609,6 +688,7 @@ export function readUsageDb(): StoredUsage | null {
609
688
  toolNames,
610
689
  h === '' ? undefined : h,
611
690
  id === '' || id === undefined ? undefined : id,
691
+ a === '' ? undefined : a,
612
692
  );
613
693
  }
614
694
  accum.recent.sort((a, b) => b.t - a.t);
@@ -627,6 +707,10 @@ export function readUsageDb(): StoredUsage | null {
627
707
  byTool: accum.byTool,
628
708
  byModelMessages: accum.byModelMessages,
629
709
  byHost: accum.byHost,
710
+ byAgent: accum.byAgent,
711
+ byAgentMessages: accum.byAgentMessages,
712
+ byModelMeasured: accum.byModelMeasured,
713
+ byModelCostMessages: accum.byModelCostMessages,
630
714
  costMeasured: accum.costMeasured,
631
715
  recent: accum.recent.slice(0, RECENT_LIMIT),
632
716
  },
@@ -13,7 +13,7 @@ export interface HttpOptions {
13
13
  }
14
14
 
15
15
  // Promise.withResolvers is Node 22+; the package supports Node 18+.
16
- export function withResolvers<T>(): PromiseWithResolvers<T> {
16
+ function withResolvers<T>(): PromiseWithResolvers<T> {
17
17
  let resolve!: (value: T | PromiseLike<T>) => void;
18
18
  let reject!: (reason?: unknown) => void;
19
19
  const promise = new Promise<T>((res, rej) => { resolve = res; reject = rej; });
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import { createHash } from 'node:crypto';
5
+ import fsSync from 'node:fs';
5
6
  import fs from 'node:fs/promises';
6
7
  import os from 'node:os';
7
8
  import path from 'node:path';
@@ -12,7 +13,6 @@ import {
12
13
  CAVEMAN_REMOTE_ULTRA,
13
14
  CAVEMAN_REMOTE_MEGACAVE,
14
15
  RTK_RELEASE_API,
15
- RtkRelease,
16
16
  fetchJson,
17
17
  findFile,
18
18
  findHoistedPackage,
@@ -24,15 +24,26 @@ import {
24
24
  readTextIfExists,
25
25
  rtkPlatformSpec,
26
26
  resolveRtkBinary,
27
- } from '../lib/utils.ts';
27
+ } from '../shared/utils.ts';
28
+ import type { RtkRelease } from '../shared/utils.ts';
28
29
 
29
30
  const IS_WINDOWS = process.platform === 'win32';
30
31
  const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
31
32
 
32
33
  const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
33
34
  const RTK_BINARY = resolveRtkBinary();
34
- // Beside this file, not a host path: these are the rules /caveman reads.
35
- const cavemanRule = (name: string) => path.join(EXTENSION_DIR, '..', 'caveman-session', name);
35
+ // Beside this file, not a host path: these are the rules /caveman reads. A retired
36
+ // agent-tree copy has no `caveman` sibling, so the OMP plugin install is the
37
+ // fallback: it is the one the plugin manifest loads from.
38
+ const CAVEMAN_SIBLING = (name: string): string => path.join(EXTENSION_DIR, '..', 'caveman', name);
39
+
40
+ function cavemanRule(name: string): string {
41
+ const beside = CAVEMAN_SIBLING(name);
42
+ if (fsSync.existsSync(beside)) return beside;
43
+ const plugin = path.join(os.homedir(), '.omp', 'plugins', 'node_modules', '@krtclcdy', 'tersio', 'extensions', 'caveman', name);
44
+ return fsSync.existsSync(plugin) ? plugin : beside;
45
+ }
46
+
36
47
  const CAVEMAN_LOCAL = cavemanRule('rule.md');
37
48
  const CAVEMAN_TRACKED: ReadonlyArray<{ file: string; remote: string }> = [
38
49
  { file: cavemanRule('rule-ultra.md'), remote: CAVEMAN_REMOTE_ULTRA },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krtclcdy/tersio",
3
- "version": "2.25.4",
3
+ "version": "2.25.5",
4
4
  "description": "✂️ Token saver and usage tracker for coding agents: terse Caveman replies, compact RTK shell output, lean Ponytail code calls, and a gain dashboard — one-command combo presets over one shared extension tree.",
5
5
  "keywords": [
6
6
  "token-saving",
@@ -24,17 +24,17 @@
24
24
  "tersio": "dist/tersio.js"
25
25
  },
26
26
  "exports": {
27
- "./server": "./dist/extensions/opencode/server.js"
27
+ "./server": "./dist/extensions/server.js"
28
28
  },
29
29
  "omp": {
30
30
  "name": "Tersio",
31
31
  "description": "Toggleable Caveman/RTK/Ponytail session modes with Combo presets",
32
32
  "extensions": [
33
- "./extensions/caveman-session/index.ts",
34
- "./extensions/rtk-session/index.ts",
35
- "./extensions/rtk-filter/index.ts",
36
- "./extensions/combo-toggle/index.ts",
37
- "./extensions/tersio-commands/index.ts"
33
+ "./extensions/caveman/index.ts",
34
+ "./extensions/rtk/session.ts",
35
+ "./extensions/rtk/filter.ts",
36
+ "./extensions/combo/index.ts",
37
+ "./extensions/commands/index.ts"
38
38
  ],
39
39
  "skills": [
40
40
  "./node_modules/@dietrichgebert/ponytail/skills"
@@ -44,7 +44,7 @@
44
44
  "description": "In-session updater for Ponytail, RTK, and Caveman (/ai-addons)",
45
45
  "default": true,
46
46
  "extensions": [
47
- "./extensions/ai-addons-updater/index.ts"
47
+ "./extensions/updater/index.ts"
48
48
  ]
49
49
  }
50
50
  },
@@ -116,16 +116,16 @@
116
116
  },
117
117
  "files": [
118
118
  "dist/**/*.js",
119
- "dist/extensions/caveman-session/*.md",
119
+ "dist/extensions/caveman/*.md",
120
120
  "dashboard/dist/**/*",
121
121
  "extensions/**/*.ts",
122
- "extensions/caveman-session/rule.md",
123
- "extensions/caveman-session/rule-ultra.md",
124
- "extensions/caveman-session/rule-megacave.md"
122
+ "extensions/caveman/rule.md",
123
+ "extensions/caveman/rule-ultra.md",
124
+ "extensions/caveman/rule-megacave.md"
125
125
  ],
126
126
  "scripts": {
127
127
  "clean": "rm -rf dist dashboard/dist",
128
- "build": "tsc && bun run build:dashboard && cp extensions/caveman-session/*.md dist/extensions/caveman-session/",
128
+ "build": "tsc && bun run build:dashboard && cp extensions/caveman/*.md dist/extensions/caveman/",
129
129
  "build:dashboard": "bun install --cwd dashboard/app --frozen-lockfile && bun run --cwd dashboard/app build",
130
130
  "prepublishOnly": "bun run build",
131
131
  "check": "tsc --noEmit",
File without changes