sigrank 0.19.0 → 1.0.1

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 (80) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/MCP_SERVER_DESCRIPTION.md +172 -0
  3. package/README.md +89 -41
  4. package/adapters/index.mjs +1100 -0
  5. package/adapters/tokenpull.mjs +568 -0
  6. package/adapters.mjs +1 -865
  7. package/analytics/cascade.mjs +338 -0
  8. package/cascade.mjs +1 -274
  9. package/cli.mjs +1 -1853
  10. package/connect.mjs +1 -13
  11. package/identity/connect.mjs +13 -0
  12. package/identity/keystore.mjs +231 -0
  13. package/identity/sign.mjs +106 -0
  14. package/index.mjs +47 -162
  15. package/keystore.mjs +1 -242
  16. package/lib/constants.mjs +137 -0
  17. package/lib/pkg-version.mjs +59 -0
  18. package/manifest.json +46 -0
  19. package/narrate.mjs +1 -77
  20. package/package.json +41 -16
  21. package/preflight.mjs +1 -191
  22. package/presentation/cli.mjs +1926 -0
  23. package/presentation/narrate.mjs +219 -0
  24. package/presentation/tui-themes.mjs +189 -0
  25. package/presentation/tui.mjs +3316 -0
  26. package/prompts/check-my-efficiency.md +1 -0
  27. package/prompts/compare-with-leader.md +1 -0
  28. package/prompts/simulate-improvement.md +1 -0
  29. package/proxy.mjs +492 -0
  30. package/resources/class-tiers.md +81 -0
  31. package/resources/data-policy.md +31 -0
  32. package/resources/install-guide.md +59 -0
  33. package/resources/privacy-model.md +44 -0
  34. package/resources/scoring-formula.md +24 -0
  35. package/review.mjs +263 -0
  36. package/sign.mjs +1 -106
  37. package/submit/index.mjs +297 -0
  38. package/submit/preflight.mjs +191 -0
  39. package/submit.mjs +1 -303
  40. package/tokenpull.mjs +1 -576
  41. package/tokscale_analytics.mjs +864 -0
  42. package/tools/_framing.mjs +217 -0
  43. package/tools/_helpers.mjs +253 -0
  44. package/tools/_peer-matching.mjs +361 -0
  45. package/tools/_report.mjs +262 -0
  46. package/tools/_schemas.mjs +492 -0
  47. package/tools/_verifiers.mjs +148 -0
  48. package/tools/compare-operators.mjs +80 -0
  49. package/tools/compare-self.mjs +124 -0
  50. package/tools/describe-power-user.mjs +78 -0
  51. package/tools/diagnose-cascade.mjs +192 -0
  52. package/tools/discover-peers.mjs +198 -0
  53. package/tools/enroll.mjs +119 -0
  54. package/tools/get-best-operator.mjs +59 -0
  55. package/tools/get-leaderboard.mjs +27 -0
  56. package/tools/get-operator.mjs +33 -0
  57. package/tools/index.mjs +229 -0
  58. package/tools/optimize-efficiency.mjs +170 -0
  59. package/tools/rank-paste.mjs +51 -0
  60. package/tools/rank-windows.mjs +120 -0
  61. package/tools/self-improve.mjs +423 -0
  62. package/tools/simulate-change.mjs +207 -0
  63. package/tools/submit-paste.mjs +121 -0
  64. package/tools/submit-verified.mjs +153 -0
  65. package/tools/suggest-improvements.mjs +168 -0
  66. package/tools/tokenpull-compare.mjs +123 -0
  67. package/tools/tokenpull-submit.mjs +235 -0
  68. package/tools/tokenpull.mjs +113 -0
  69. package/tools/tokscale-breakdown.mjs +119 -0
  70. package/tools/tokscale-competitive-intel.mjs +45 -0
  71. package/tools/tokscale-cost-analysis.mjs +31 -0
  72. package/tools/tokscale-developer-profile.mjs +36 -0
  73. package/tools/tokscale-device-profile.mjs +35 -0
  74. package/tools/tokscale-market-share.mjs +38 -0
  75. package/tools/tokscale-mcp-usage.mjs +33 -0
  76. package/tools/tokscale-model-trends.mjs +32 -0
  77. package/tools/watch-tokenpull.mjs +134 -0
  78. package/tools.mjs +1 -3897
  79. package/tui.mjs +4 -2650
  80. package/banner.svg +0 -7
@@ -0,0 +1,492 @@
1
+ /**
2
+ * tools/_schemas.mjs — MCP tool annotation + input/output schema objects.
3
+ */
4
+
5
+ import { SIGNAL_CLASSES, UNCLASSED } from "../analytics/cascade.mjs";
6
+
7
+ // Smithery quality: output schemas + annotations
8
+ // MCP tool annotations hint to clients about side-effects, read-only status, etc.
9
+ export const ANNOTATIONS = {
10
+ readOnlyHint: { readOnlyHint: true },
11
+ destructiveHint: { destructiveHint: false },
12
+ idempotentHint: { idempotentHint: true },
13
+ openWorldHint: { openWorldHint: false },
14
+ };
15
+
16
+ // The canonical class-tier enum, single-sourced from analytics/cascade.mjs.
17
+ // Every output schema that exposes a `class` field uses this so the enum can
18
+ // never drift between tools. SIGNAL_CLASSES is the 24 sub-stage names (8 tiers
19
+ // × 3 sub-stages I/II/III) that classify() emits and the API returns.
20
+ // UNCLASSED is included for the degenerate no-data case. Exported so tool
21
+ // files that declare their own output schema (optimize-efficiency.mjs,
22
+ // describe-power-user.mjs) import the same enum instead of re-inlining a
23
+ // stale tier list.
24
+ export const CLASS_ENUM = [...SIGNAL_CLASSES, UNCLASSED];
25
+
26
+ // Common output schema for cascade results (rank_paste, simulate_change, etc.)
27
+ // Field names match the actual cascade() output in analytics/cascade.mjs:
28
+ // `yield` (not `yield_` — `yield_` was a schema-only alias that didn't match
29
+ // the real payload), `dev10x` (not `tenx_dev`), `class`, `snr`, `leverage`,
30
+ // `velocity`, `pillars`, `mode`, `warnings`, plus the tool-added `card`.
31
+ export const CASCADE_OUTPUT = {
32
+ type: "object",
33
+ properties: {
34
+ yield: {
35
+ type: "number",
36
+ description:
37
+ "Υ Yield — the headline efficiency metric (Cache Reads × Output / Input²)",
38
+ },
39
+ snr: { type: "number", description: "Signal-to-noise ratio" },
40
+ leverage: {
41
+ type: "number",
42
+ description: "Cr/I — cache reads divided by input",
43
+ },
44
+ velocity: { type: "number", description: "O/I — output divided by input" },
45
+ dev10x: { type: "number", description: "10xDEV score (log10 of the cascade product)" },
46
+ class: {
47
+ type: "string",
48
+ enum: CLASS_ENUM,
49
+ description: "Operator class tier (dev10x taxonomy)",
50
+ },
51
+ mode: {
52
+ type: "object",
53
+ description: "Detected working mode (BUILD/EDIT/DEBUG/MAINTAIN/IDLE) + confidence",
54
+ },
55
+ pillars: {
56
+ type: "object",
57
+ description: "The four raw token pillars the cascade was computed from",
58
+ },
59
+ card: {
60
+ type: "string",
61
+ description: "Deterministic prose summary of the cascade result",
62
+ },
63
+ warnings: {
64
+ type: "array",
65
+ items: { type: "string" },
66
+ description: "Parse or data warnings if any",
67
+ },
68
+ },
69
+ required: ["yield", "class"],
70
+ };
71
+
72
+ export const LEADERBOARD_OUTPUT = {
73
+ type: "object",
74
+ properties: {
75
+ operators: {
76
+ type: "array",
77
+ description: "Array of ranked operators sorted by yield",
78
+ items: {
79
+ type: "object",
80
+ properties: {
81
+ codename: { type: "string", description: "Public display name" },
82
+ yield_: { type: "number", description: "Υ Yield metric" },
83
+ leverage: { type: "number", description: "Cr/I ratio" },
84
+ velocity: { type: "number", description: "O/I ratio" },
85
+ class: { type: "string", enum: CLASS_ENUM },
86
+ rank: { type: "integer", description: "1-based rank position" },
87
+ },
88
+ },
89
+ },
90
+ },
91
+ };
92
+
93
+ export const OPERATOR_OUTPUT = {
94
+ type: "object",
95
+ properties: {
96
+ codename: { type: "string", description: "Operator display name" },
97
+ yield_: { type: "number", description: "Υ Yield metric" },
98
+ leverage: { type: "number", description: "Cr/I ratio" },
99
+ velocity: { type: "number", description: "O/I ratio" },
100
+ class: { type: "string", enum: CLASS_ENUM },
101
+ rank: { type: "integer", description: "1-based rank position" },
102
+ windows: {
103
+ type: "array",
104
+ description: "Per-window breakdowns (7d, 30d, 90d, all-time)",
105
+ items: {
106
+ type: "object",
107
+ properties: {
108
+ window: { type: "string", enum: ["7d", "30d", "90d", "all_time"] },
109
+ pillars: {
110
+ type: "object",
111
+ properties: {
112
+ input: { type: "integer" },
113
+ output: { type: "integer" },
114
+ cacheCreate: { type: "integer" },
115
+ cacheRead: { type: "integer" },
116
+ },
117
+ },
118
+ },
119
+ },
120
+ },
121
+ batched: {
122
+ type: "object",
123
+ description:
124
+ "Submission-based rolled-forward totals (never decrease when platforms delete old logs). Present when the operator has submission history.",
125
+ properties: {
126
+ per_platform: {
127
+ type: "object",
128
+ description: "Per-platform batched windows",
129
+ additionalProperties: {
130
+ type: "object",
131
+ properties: {
132
+ "90d": {
133
+ type: "object",
134
+ description: "Rolled-forward 90d pillars (sum of batches <=90 days old)",
135
+ properties: {
136
+ input: { type: "integer" },
137
+ output: { type: "integer" },
138
+ cacheCreate: { type: "integer" },
139
+ cacheRead: { type: "integer" },
140
+ },
141
+ },
142
+ all: {
143
+ type: "object",
144
+ description: "All-time pillars (baseline + all deltas — never decreases)",
145
+ properties: {
146
+ input: { type: "integer" },
147
+ output: { type: "integer" },
148
+ cacheCreate: { type: "integer" },
149
+ cacheRead: { type: "integer" },
150
+ },
151
+ },
152
+ submission_count: {
153
+ type: "integer",
154
+ description: "Number of archived submissions for this platform",
155
+ },
156
+ baseline_date: {
157
+ type: "string",
158
+ description: "ISO date of the first (baseline) submission",
159
+ },
160
+ },
161
+ },
162
+ },
163
+ combined: {
164
+ type: "object",
165
+ description: "Combined batched windows across all platforms",
166
+ properties: {
167
+ "90d": {
168
+ type: "object",
169
+ properties: {
170
+ input: { type: "integer" },
171
+ output: { type: "integer" },
172
+ cacheCreate: { type: "integer" },
173
+ cacheRead: { type: "integer" },
174
+ },
175
+ },
176
+ all: {
177
+ type: "object",
178
+ properties: {
179
+ input: { type: "integer" },
180
+ output: { type: "integer" },
181
+ cacheCreate: { type: "integer" },
182
+ cacheRead: { type: "integer" },
183
+ },
184
+ },
185
+ },
186
+ },
187
+ },
188
+ },
189
+ },
190
+ };
191
+
192
+ export const BEST_OPERATOR_OUTPUT = {
193
+ type: "object",
194
+ properties: {
195
+ top_operators: {
196
+ type: "array",
197
+ description: "Top N operators ranked by yield",
198
+ items: {
199
+ type: "object",
200
+ properties: {
201
+ codename: { type: "string", description: "Public display name" },
202
+ yield_: { type: "number", description: "Υ Yield metric" },
203
+ leverage: { type: "number", description: "Cr/I ratio" },
204
+ velocity: { type: "number", description: "O/I ratio" },
205
+ class: { type: "string", enum: CLASS_ENUM },
206
+ rank: { type: "integer", description: "1-based rank position" },
207
+ behavioral_framing: {
208
+ type: "string",
209
+ description: "Plain-language interpretation of the operator's cascade in power-user terms",
210
+ },
211
+ },
212
+ },
213
+ },
214
+ total_operators: { type: "integer", description: "Total operators on the board" },
215
+ summary: {
216
+ type: "string",
217
+ description: "One-line summary of the top operator's achievement in behavioral terms",
218
+ },
219
+ },
220
+ };
221
+
222
+ export const COMPARE_SELF_OUTPUT = {
223
+ type: "object",
224
+ properties: {
225
+ your_metrics: {
226
+ type: "object",
227
+ description: "Your cascade metrics",
228
+ properties: {
229
+ codename: { type: "string" },
230
+ yield_: { type: "number", description: "Υ Yield metric" },
231
+ leverage: { type: "number", description: "Cr/I ratio" },
232
+ velocity: { type: "number", description: "O/I ratio" },
233
+ class: { type: "string", enum: CLASS_ENUM },
234
+ rank: { type: "integer", description: "1-based rank position" },
235
+ },
236
+ },
237
+ power_user_assessment: {
238
+ type: "string",
239
+ description: "Behavioral interpretation: are you an AI power user? Maps class tier to power-user language.",
240
+ },
241
+ comparison: {
242
+ type: "object",
243
+ description: "How you compare to board averages and archetypes",
244
+ properties: {
245
+ your_yield_vs_avg: { type: "string", description: "Your yield vs board average" },
246
+ your_class_meaning: { type: "string", description: "What your class tier means in power-user terms" },
247
+ percentile: { type: "number", description: "Your percentile rank (0-100)" },
248
+ },
249
+ },
250
+ suggestion: {
251
+ type: "string",
252
+ description: "One actionable suggestion to improve your cascade efficiency",
253
+ },
254
+ },
255
+ };
256
+
257
+ export const COMPARE_OPERATORS_OUTPUT = {
258
+ type: "object",
259
+ properties: {
260
+ operator_a: {
261
+ type: "object",
262
+ description: "First operator's metrics",
263
+ properties: {
264
+ codename: { type: "string" },
265
+ yield_: { type: "number" },
266
+ leverage: { type: "number" },
267
+ velocity: { type: "number" },
268
+ class: { type: "string", enum: CLASS_ENUM },
269
+ rank: { type: "integer" },
270
+ },
271
+ },
272
+ operator_b: {
273
+ type: "object",
274
+ description: "Second operator's metrics",
275
+ properties: {
276
+ codename: { type: "string" },
277
+ yield_: { type: "number" },
278
+ leverage: { type: "number" },
279
+ velocity: { type: "number" },
280
+ class: { type: "string", enum: CLASS_ENUM },
281
+ rank: { type: "integer" },
282
+ },
283
+ },
284
+ verdict: {
285
+ type: "string",
286
+ description: "Who is more efficient and why, in behavioral terms",
287
+ },
288
+ yield_delta: { type: "number", description: "Yield difference (A - B)" },
289
+ },
290
+ };
291
+
292
+ export const SUBMIT_OUTPUT = {
293
+ type: "object",
294
+ properties: {
295
+ status: {
296
+ type: "string",
297
+ enum: ["ok", "error", "skipped"],
298
+ description: "Submission status",
299
+ },
300
+ preview: CASCADE_OUTPUT,
301
+ server_response: {
302
+ type: "object",
303
+ description: "Server-side response including new rank if accepted",
304
+ },
305
+ reason: {
306
+ type: "string",
307
+ description: "Error or skip reason if status is not ok",
308
+ },
309
+ },
310
+ };
311
+
312
+ export const TOKENPULL_OUTPUT = {
313
+ type: "object",
314
+ properties: {
315
+ platform: { type: "string", description: "Source platform name" },
316
+ window: {
317
+ type: "string",
318
+ enum: ["7d", "30d", "90d", "all"],
319
+ description: "The watched window (watch_tokenpull watches one window per call)",
320
+ },
321
+ pillars: {
322
+ type: "object",
323
+ description: "The four raw token pillars for the watched window",
324
+ properties: {
325
+ input: { type: "integer" },
326
+ output: { type: "integer" },
327
+ cacheCreate: { type: "integer" },
328
+ cacheRead: { type: "integer" },
329
+ },
330
+ },
331
+ messages: {
332
+ type: "integer",
333
+ description: "Number of messages in the watched window",
334
+ },
335
+ cascade: CASCADE_OUTPUT,
336
+ card: { type: "string", description: "Deterministic prose summary" },
337
+ generatedAt: { type: "string", description: "ISO timestamp of the pull" },
338
+ poll_interval_s: {
339
+ type: "number",
340
+ description: "Advisory poll cadence echoed back (does not make the call block)",
341
+ },
342
+ auth_submit: {
343
+ type: ["object", "null"],
344
+ description:
345
+ "Present only when submit:true. One of: {status:'received'|'cooldown'|'not_enrolled'|'error', ...}. Null when submit is false (preview only).",
346
+ },
347
+ note: {
348
+ type: "string",
349
+ description: "Reminder that one snapshot is returned per call — re-call to detect changes.",
350
+ },
351
+ },
352
+ };
353
+
354
+ export const ENROLL_OUTPUT = {
355
+ type: "object",
356
+ properties: {
357
+ status: {
358
+ type: "string",
359
+ enum: ["enrolled", "error"],
360
+ description: "Enrollment result",
361
+ },
362
+ codename: { type: "string", description: "Operator codename if enrolled" },
363
+ operator_id: { type: "string", description: "Operator ID if enrolled" },
364
+ device_id: { type: "string", description: "Local device ID" },
365
+ trust_status: { type: "string", description: "Trust level of the device" },
366
+ reason: { type: "string", description: "Error reason if status is error" },
367
+ },
368
+ };
369
+
370
+ export const COMPARE_OUTPUT = {
371
+ type: "object",
372
+ properties: {
373
+ platform: { type: "string" },
374
+ sources: {
375
+ type: "array",
376
+ description: "Side-by-side comparison of each token source",
377
+ items: {
378
+ type: "object",
379
+ properties: {
380
+ source: {
381
+ type: "string",
382
+ description:
383
+ "Source name (tokenpull, ccusage, token-dash, tokscale)",
384
+ },
385
+ pillars: { type: "object" },
386
+ cascade: CASCADE_OUTPUT,
387
+ delta_pct: {
388
+ type: "object",
389
+ description: "Delta % vs tokenpull baseline",
390
+ },
391
+ },
392
+ },
393
+ },
394
+ },
395
+ };
396
+
397
+ export const SIMULATE_OUTPUT = {
398
+ type: "object",
399
+ properties: {
400
+ current: CASCADE_OUTPUT,
401
+ simulated: CASCADE_OUTPUT,
402
+ yield_delta: {
403
+ type: "number",
404
+ description: "Υ Yield change (simulated - current)",
405
+ },
406
+ class_change: {
407
+ type: "string",
408
+ description: "Class tier change description",
409
+ },
410
+ metric_diffs: {
411
+ type: "object",
412
+ description: "Per-metric before/after diffs",
413
+ },
414
+ },
415
+ };
416
+
417
+ export const DIAGNOSE_OUTPUT = {
418
+ type: "object",
419
+ properties: {
420
+ pillars: { type: "object", description: "The 4 raw token pillars" },
421
+ cascade: CASCADE_OUTPUT,
422
+ diagnosis: {
423
+ type: "array",
424
+ description: "Ranked list of efficiency leaks found, worst first",
425
+ items: {
426
+ type: "object",
427
+ properties: {
428
+ metric: {
429
+ type: "string",
430
+ description: "Which metric is underperforming",
431
+ },
432
+ severity: {
433
+ type: "string",
434
+ enum: ["critical", "warning", "info"],
435
+ description: "How bad the leak is",
436
+ },
437
+ finding: { type: "string", description: "What the analysis found" },
438
+ recommendation: {
439
+ type: "string",
440
+ description: "What to do about it",
441
+ },
442
+ estimated_yield_impact: {
443
+ type: "string",
444
+ description: "Estimated Υ improvement if fixed",
445
+ },
446
+ },
447
+ },
448
+ },
449
+ summary: {
450
+ type: "string",
451
+ description: "One-line summary of the operator's cascade health",
452
+ },
453
+ },
454
+ };
455
+
456
+ export const SUGGEST_OUTPUT = {
457
+ type: "object",
458
+ properties: {
459
+ suggestions: {
460
+ type: "array",
461
+ description: "Ranked recommendations, highest Υ impact first",
462
+ items: {
463
+ type: "object",
464
+ properties: {
465
+ rank: { type: "number", description: "1-based rank by Υ impact" },
466
+ action: { type: "string", description: "What to change" },
467
+ pillar: { type: "string", description: "Which pillar to adjust" },
468
+ delta: { type: "string", description: "How much to change it" },
469
+ simulated_yield: {
470
+ type: "number",
471
+ description: "Projected Υ after the change",
472
+ },
473
+ yield_delta: { type: "number", description: "Υ change vs current" },
474
+ class_after: {
475
+ type: "string",
476
+ description: "Projected class after the change",
477
+ },
478
+ rationale: { type: "string", description: "Why this helps" },
479
+ },
480
+ },
481
+ },
482
+ current_yield: {
483
+ type: "number",
484
+ description: "Current Υ before any changes",
485
+ },
486
+ current_class: { type: "string", description: "Current class tier" },
487
+ best_single_change: {
488
+ type: "string",
489
+ description: "The single highest-impact change",
490
+ },
491
+ },
492
+ };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * tools/_verifiers.mjs — on-device verifier readers for tokenpull_compare.
3
+ *
4
+ * Mirrors the implementations in presentation/cli.mjs + presentation/tui.mjs
5
+ * without circular imports. Used by the tokenpull_compare tool for quick
6
+ * side-by-side comparison across local token sources.
7
+ */
8
+
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import os from "node:os";
11
+ import path from "node:path";
12
+ import { execFileAsync } from "./_helpers.mjs";
13
+
14
+ async function _ccusagePillars(platform = "claude") {
15
+ try {
16
+ const raw = await execFileAsync(
17
+ "ccusage",
18
+ [platform, "daily", "--json"],
19
+ 15000,
20
+ );
21
+ const rows = JSON.parse(raw)?.daily ?? JSON.parse(raw);
22
+ const now = Date.now();
23
+ const result = {};
24
+ for (const [win, days] of Object.entries({
25
+ "7d": 7,
26
+ "30d": 30,
27
+ "90d": 90,
28
+ })) {
29
+ const since = new Date(now - days * 86400000);
30
+ let i = 0,
31
+ o = 0,
32
+ cw = 0,
33
+ cr = 0;
34
+ for (const r of rows) {
35
+ if (new Date(r.date ?? r.day ?? "1970") >= since) {
36
+ i += r.inputTokens ?? r.input_tokens ?? 0;
37
+ o += r.outputTokens ?? r.output_tokens ?? 0;
38
+ cw += r.cacheCreationTokens ?? r.cache_create_tokens ?? 0;
39
+ cr += r.cacheReadTokens ?? r.cache_read_tokens ?? 0;
40
+ }
41
+ }
42
+ result[win] = { input: i, output: o, cacheCreate: cw, cacheRead: cr };
43
+ }
44
+ let i = 0,
45
+ o = 0,
46
+ cw = 0,
47
+ cr = 0;
48
+ for (const r of rows) {
49
+ i += r.inputTokens ?? r.input_tokens ?? 0;
50
+ o += r.outputTokens ?? r.output_tokens ?? 0;
51
+ cw += r.cacheCreationTokens ?? r.cache_create_tokens ?? 0;
52
+ cr += r.cacheReadTokens ?? r.cache_read_tokens ?? 0;
53
+ }
54
+ result["all"] = { input: i, output: o, cacheCreate: cw, cacheRead: cr };
55
+ return result;
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ async function _tokenDashPillars() {
62
+ const dbPath = path.join(os.homedir(), ".claude", "token-dashboard.db");
63
+ if (!existsSync(dbPath)) return null;
64
+ try {
65
+ const raw = await execFileAsync(
66
+ "sqlite3",
67
+ [
68
+ dbPath,
69
+ "SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens)+SUM(cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages",
70
+ ],
71
+ 5000,
72
+ );
73
+ const [i, o, cw, cr] = raw.trim().split("|").map(Number);
74
+ return {
75
+ all: {
76
+ input: i || 0,
77
+ output: o || 0,
78
+ cacheCreate: cw || 0,
79
+ cacheRead: cr || 0,
80
+ },
81
+ };
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ async function _tokscalePillars(platform = "claude") {
88
+ // Try the bundled tokscale CLI first (fresh data), fall back to saved report file.
89
+ try {
90
+ const raw = await execFileAsync("tokscale", ["models", "--json"], 60000);
91
+ const data = JSON.parse(raw);
92
+ const entries = Array.isArray(data?.entries)
93
+ ? data.entries
94
+ : Array.isArray(data)
95
+ ? data
96
+ : [];
97
+ const rows = entries.filter(
98
+ (e) =>
99
+ e &&
100
+ e.client === platform &&
101
+ e.model !== "<synthetic>" &&
102
+ e.model !== "unknown" &&
103
+ ((Number(e.input) || 0) > 0 || (Number(e.output) || 0) > 0),
104
+ );
105
+ if (rows.length) {
106
+ const acc = rows.reduce(
107
+ (a, e) => ({
108
+ input: a.input + (Number(e.input) || 0),
109
+ output: a.output + (Number(e.output) || 0),
110
+ cacheCreate: a.cacheCreate + (Number(e.cacheWrite) || 0),
111
+ cacheRead: a.cacheRead + (Number(e.cacheRead) || 0),
112
+ }),
113
+ { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
114
+ );
115
+ return { all: acc };
116
+ }
117
+ } catch {
118
+ /* fall through to file-based read */
119
+ }
120
+ // Fallback: read saved tokscale_report.json if it exists
121
+ const p = path.join(os.homedir(), "tokscale_report.json");
122
+ if (!existsSync(p)) return null;
123
+ try {
124
+ const data = JSON.parse(readFileSync(p, "utf8"));
125
+ const rows = (data.entries ?? []).filter(
126
+ (e) =>
127
+ e.client === platform &&
128
+ e.model !== "<synthetic>" &&
129
+ e.model !== "unknown" &&
130
+ (e.input > 0 || e.output > 0),
131
+ );
132
+ if (!rows.length) return null;
133
+ const acc = rows.reduce(
134
+ (a, e) => ({
135
+ input: a.input + (e.input ?? 0),
136
+ output: a.output + (e.output ?? 0),
137
+ cacheCreate: a.cacheCreate + (e.cacheWrite ?? 0),
138
+ cacheRead: a.cacheRead + (e.cacheRead ?? 0),
139
+ }),
140
+ { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
141
+ );
142
+ return { all: acc };
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ export { _ccusagePillars, _tokenDashPillars, _tokscalePillars };