sigrank 0.0.178 → 0.0.179

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 (72) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/MCP_SERVER_DESCRIPTION.md +141 -0
  3. package/README.md +8 -0
  4. package/adapters/index.mjs +865 -0
  5. package/adapters/tokenpull.mjs +576 -0
  6. package/adapters.mjs +1 -865
  7. package/analytics/cascade.mjs +274 -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 +242 -0
  13. package/identity/sign.mjs +106 -0
  14. package/index.mjs +29 -153
  15. package/keystore.mjs +1 -242
  16. package/narrate.mjs +1 -77
  17. package/package.json +29 -13
  18. package/preflight.mjs +1 -191
  19. package/presentation/cli.mjs +1874 -0
  20. package/presentation/narrate.mjs +77 -0
  21. package/presentation/tui.mjs +2668 -0
  22. package/prompts/check-my-efficiency.md +1 -0
  23. package/prompts/compare-with-leader.md +1 -0
  24. package/prompts/simulate-improvement.md +1 -0
  25. package/resources/class-tiers.md +23 -0
  26. package/resources/data-policy.md +27 -0
  27. package/resources/install-guide.md +59 -0
  28. package/resources/privacy-model.md +30 -0
  29. package/resources/scoring-formula.md +28 -0
  30. package/review.mjs +263 -0
  31. package/sign.mjs +1 -106
  32. package/submit/index.mjs +309 -0
  33. package/submit/preflight.mjs +191 -0
  34. package/submit.mjs +1 -303
  35. package/tokenpull.mjs +1 -576
  36. package/tools/_framing.mjs +139 -0
  37. package/tools/_helpers.mjs +258 -0
  38. package/tools/_report.mjs +262 -0
  39. package/tools/_schemas.mjs +394 -0
  40. package/tools/_verifiers.mjs +148 -0
  41. package/tools/compare-operators.mjs +79 -0
  42. package/tools/compare-self.mjs +122 -0
  43. package/tools/describe-power-user.mjs +68 -0
  44. package/tools/diagnose-cascade.mjs +192 -0
  45. package/tools/enroll.mjs +121 -0
  46. package/tools/get-best-operator.mjs +58 -0
  47. package/tools/get-leaderboard.mjs +26 -0
  48. package/tools/get-operator.mjs +33 -0
  49. package/tools/index.mjs +257 -0
  50. package/tools/optimize-efficiency.mjs +160 -0
  51. package/tools/rank-paste.mjs +51 -0
  52. package/tools/rank-windows.mjs +120 -0
  53. package/tools/self-improve.mjs +423 -0
  54. package/tools/simulate-change.mjs +207 -0
  55. package/tools/submit-paste.mjs +118 -0
  56. package/tools/submit-verified.mjs +153 -0
  57. package/tools/suggest-improvements.mjs +168 -0
  58. package/tools/tokenpull-compare.mjs +123 -0
  59. package/tools/tokenpull-submit.mjs +234 -0
  60. package/tools/tokenpull.mjs +113 -0
  61. package/tools/tokscale-breakdown.mjs +159 -0
  62. package/tools/tokscale-competitive-intel.mjs +45 -0
  63. package/tools/tokscale-cost-analysis.mjs +31 -0
  64. package/tools/tokscale-developer-profile.mjs +36 -0
  65. package/tools/tokscale-device-profile.mjs +35 -0
  66. package/tools/tokscale-market-share.mjs +38 -0
  67. package/tools/tokscale-mcp-usage.mjs +33 -0
  68. package/tools/tokscale-model-trends.mjs +32 -0
  69. package/tools/watch-tokenpull.mjs +134 -0
  70. package/tools.mjs +1 -3897
  71. package/tui.mjs +4 -2650
  72. package/banner.svg +0 -7
@@ -0,0 +1,394 @@
1
+ /**
2
+ * tools/_schemas.mjs — MCP tool annotation + input/output schema objects.
3
+ */
4
+
5
+ // Smithery quality: output schemas + annotations
6
+ // MCP tool annotations hint to clients about side-effects, read-only status, etc.
7
+ export const ANNOTATIONS = {
8
+ readOnlyHint: { readOnlyHint: true },
9
+ destructiveHint: { destructiveHint: false },
10
+ idempotentHint: { idempotentHint: true },
11
+ openWorldHint: { openWorldHint: false },
12
+ };
13
+
14
+ // Common output schema for cascade results (rank_paste, simulate_change, etc.)
15
+ export const CASCADE_OUTPUT = {
16
+ type: "object",
17
+ properties: {
18
+ yield_: {
19
+ type: "number",
20
+ description:
21
+ "Υ Yield — the headline efficiency metric (Cache Reads × Output / Input²)",
22
+ },
23
+ snr: { type: "number", description: "Signal-to-noise ratio" },
24
+ leverage: {
25
+ type: "number",
26
+ description: "Cr/I — cache reads divided by input",
27
+ },
28
+ velocity: { type: "number", description: "O/I — output divided by input" },
29
+ tenx_dev: { type: "number", description: "10xDEV score" },
30
+ class: {
31
+ type: "string",
32
+ enum: ["Burner", "Builder", "10xer"],
33
+ description: "Operator class tier",
34
+ },
35
+ card: {
36
+ type: "string",
37
+ description: "Deterministic prose summary of the cascade result",
38
+ },
39
+ warnings: {
40
+ type: "array",
41
+ items: { type: "string" },
42
+ description: "Parse or data warnings if any",
43
+ },
44
+ },
45
+ required: ["yield_", "class"],
46
+ };
47
+
48
+ export const LEADERBOARD_OUTPUT = {
49
+ type: "object",
50
+ properties: {
51
+ operators: {
52
+ type: "array",
53
+ description: "Array of ranked operators sorted by yield",
54
+ items: {
55
+ type: "object",
56
+ properties: {
57
+ codename: { type: "string", description: "Public display name" },
58
+ yield_: { type: "number", description: "Υ Yield metric" },
59
+ leverage: { type: "number", description: "Cr/I ratio" },
60
+ velocity: { type: "number", description: "O/I ratio" },
61
+ class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
62
+ rank: { type: "integer", description: "1-based rank position" },
63
+ },
64
+ },
65
+ },
66
+ },
67
+ };
68
+
69
+ export const OPERATOR_OUTPUT = {
70
+ type: "object",
71
+ properties: {
72
+ codename: { type: "string", description: "Operator display name" },
73
+ yield_: { type: "number", description: "Υ Yield metric" },
74
+ leverage: { type: "number", description: "Cr/I ratio" },
75
+ velocity: { type: "number", description: "O/I ratio" },
76
+ class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
77
+ rank: { type: "integer", description: "1-based rank position" },
78
+ windows: {
79
+ type: "array",
80
+ description: "Per-window breakdowns (7d, 30d, 90d, all-time)",
81
+ items: {
82
+ type: "object",
83
+ properties: {
84
+ window: { type: "string", enum: ["7d", "30d", "90d", "all_time"] },
85
+ pillars: {
86
+ type: "object",
87
+ properties: {
88
+ input: { type: "integer" },
89
+ output: { type: "integer" },
90
+ cacheCreate: { type: "integer" },
91
+ cacheRead: { type: "integer" },
92
+ },
93
+ },
94
+ },
95
+ },
96
+ },
97
+ },
98
+ };
99
+
100
+ export const BEST_OPERATOR_OUTPUT = {
101
+ type: "object",
102
+ properties: {
103
+ top_operators: {
104
+ type: "array",
105
+ description: "Top N operators ranked by yield",
106
+ items: {
107
+ type: "object",
108
+ properties: {
109
+ codename: { type: "string", description: "Public display name" },
110
+ yield_: { type: "number", description: "Υ Yield metric" },
111
+ leverage: { type: "number", description: "Cr/I ratio" },
112
+ velocity: { type: "number", description: "O/I ratio" },
113
+ class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
114
+ rank: { type: "integer", description: "1-based rank position" },
115
+ behavioral_framing: {
116
+ type: "string",
117
+ description: "Plain-language interpretation of the operator's cascade in power-user terms",
118
+ },
119
+ },
120
+ },
121
+ },
122
+ total_operators: { type: "integer", description: "Total operators on the board" },
123
+ summary: {
124
+ type: "string",
125
+ description: "One-line summary of the top operator's achievement in behavioral terms",
126
+ },
127
+ },
128
+ };
129
+
130
+ export const COMPARE_SELF_OUTPUT = {
131
+ type: "object",
132
+ properties: {
133
+ your_metrics: {
134
+ type: "object",
135
+ description: "Your cascade metrics",
136
+ properties: {
137
+ codename: { type: "string" },
138
+ yield_: { type: "number", description: "Υ Yield metric" },
139
+ leverage: { type: "number", description: "Cr/I ratio" },
140
+ velocity: { type: "number", description: "O/I ratio" },
141
+ class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
142
+ rank: { type: "integer", description: "1-based rank position" },
143
+ },
144
+ },
145
+ power_user_assessment: {
146
+ type: "string",
147
+ description: "Behavioral interpretation: are you an AI power user? Maps class tier to power-user language.",
148
+ },
149
+ comparison: {
150
+ type: "object",
151
+ description: "How you compare to board averages and archetypes",
152
+ properties: {
153
+ your_yield_vs_avg: { type: "string", description: "Your yield vs board average" },
154
+ your_class_meaning: { type: "string", description: "What your class tier means in power-user terms" },
155
+ percentile: { type: "number", description: "Your percentile rank (0-100)" },
156
+ },
157
+ },
158
+ suggestion: {
159
+ type: "string",
160
+ description: "One actionable suggestion to improve your cascade efficiency",
161
+ },
162
+ },
163
+ };
164
+
165
+ export const COMPARE_OPERATORS_OUTPUT = {
166
+ type: "object",
167
+ properties: {
168
+ operator_a: {
169
+ type: "object",
170
+ description: "First operator's metrics",
171
+ properties: {
172
+ codename: { type: "string" },
173
+ yield_: { type: "number" },
174
+ leverage: { type: "number" },
175
+ velocity: { type: "number" },
176
+ class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
177
+ rank: { type: "integer" },
178
+ },
179
+ },
180
+ operator_b: {
181
+ type: "object",
182
+ description: "Second operator's metrics",
183
+ properties: {
184
+ codename: { type: "string" },
185
+ yield_: { type: "number" },
186
+ leverage: { type: "number" },
187
+ velocity: { type: "number" },
188
+ class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
189
+ rank: { type: "integer" },
190
+ },
191
+ },
192
+ verdict: {
193
+ type: "string",
194
+ description: "Who is more efficient and why, in behavioral terms",
195
+ },
196
+ yield_delta: { type: "number", description: "Yield difference (A - B)" },
197
+ },
198
+ };
199
+
200
+ export const SUBMIT_OUTPUT = {
201
+ type: "object",
202
+ properties: {
203
+ status: {
204
+ type: "string",
205
+ enum: ["ok", "error", "skipped"],
206
+ description: "Submission status",
207
+ },
208
+ preview: CASCADE_OUTPUT,
209
+ server_response: {
210
+ type: "object",
211
+ description: "Server-side response including new rank if accepted",
212
+ },
213
+ reason: {
214
+ type: "string",
215
+ description: "Error or skip reason if status is not ok",
216
+ },
217
+ },
218
+ };
219
+
220
+ export const TOKENPULL_OUTPUT = {
221
+ type: "object",
222
+ properties: {
223
+ platform: { type: "string", description: "Source platform name" },
224
+ generatedAt: { type: "string", description: "ISO timestamp of the pull" },
225
+ windows: {
226
+ type: "array",
227
+ description: "Per-window token usage + cascade results",
228
+ items: {
229
+ type: "object",
230
+ properties: {
231
+ window: { type: "string", enum: ["7d", "30d", "90d", "all"] },
232
+ pillars: {
233
+ type: "object",
234
+ properties: {
235
+ input: { type: "integer" },
236
+ output: { type: "integer" },
237
+ cacheCreate: { type: "integer" },
238
+ cacheRead: { type: "integer" },
239
+ },
240
+ },
241
+ messages: {
242
+ type: "integer",
243
+ description: "Number of messages in window",
244
+ },
245
+ estimated: {
246
+ type: "boolean",
247
+ description: "True if cacheCreate was estimated",
248
+ },
249
+ cascade: CASCADE_OUTPUT,
250
+ },
251
+ },
252
+ },
253
+ },
254
+ };
255
+
256
+ export const ENROLL_OUTPUT = {
257
+ type: "object",
258
+ properties: {
259
+ status: {
260
+ type: "string",
261
+ enum: ["enrolled", "error"],
262
+ description: "Enrollment result",
263
+ },
264
+ codename: { type: "string", description: "Operator codename if enrolled" },
265
+ operator_id: { type: "string", description: "Operator ID if enrolled" },
266
+ device_id: { type: "string", description: "Local device ID" },
267
+ trust_status: { type: "string", description: "Trust level of the device" },
268
+ reason: { type: "string", description: "Error reason if status is error" },
269
+ },
270
+ };
271
+
272
+ export const COMPARE_OUTPUT = {
273
+ type: "object",
274
+ properties: {
275
+ platform: { type: "string" },
276
+ sources: {
277
+ type: "array",
278
+ description: "Side-by-side comparison of each token source",
279
+ items: {
280
+ type: "object",
281
+ properties: {
282
+ source: {
283
+ type: "string",
284
+ description:
285
+ "Source name (tokenpull, ccusage, token-dash, tokscale)",
286
+ },
287
+ pillars: { type: "object" },
288
+ cascade: CASCADE_OUTPUT,
289
+ delta_pct: {
290
+ type: "object",
291
+ description: "Delta % vs tokenpull baseline",
292
+ },
293
+ },
294
+ },
295
+ },
296
+ },
297
+ };
298
+
299
+ export const SIMULATE_OUTPUT = {
300
+ type: "object",
301
+ properties: {
302
+ current: CASCADE_OUTPUT,
303
+ simulated: CASCADE_OUTPUT,
304
+ yield_delta: {
305
+ type: "number",
306
+ description: "Υ Yield change (simulated - current)",
307
+ },
308
+ class_change: {
309
+ type: "string",
310
+ description: "Class tier change description",
311
+ },
312
+ metric_diffs: {
313
+ type: "object",
314
+ description: "Per-metric before/after diffs",
315
+ },
316
+ },
317
+ };
318
+
319
+ export const DIAGNOSE_OUTPUT = {
320
+ type: "object",
321
+ properties: {
322
+ pillars: { type: "object", description: "The 4 raw token pillars" },
323
+ cascade: CASCADE_OUTPUT,
324
+ diagnosis: {
325
+ type: "array",
326
+ description: "Ranked list of efficiency leaks found, worst first",
327
+ items: {
328
+ type: "object",
329
+ properties: {
330
+ metric: {
331
+ type: "string",
332
+ description: "Which metric is underperforming",
333
+ },
334
+ severity: {
335
+ type: "string",
336
+ enum: ["critical", "warning", "info"],
337
+ description: "How bad the leak is",
338
+ },
339
+ finding: { type: "string", description: "What the analysis found" },
340
+ recommendation: {
341
+ type: "string",
342
+ description: "What to do about it",
343
+ },
344
+ estimated_yield_impact: {
345
+ type: "string",
346
+ description: "Estimated Υ improvement if fixed",
347
+ },
348
+ },
349
+ },
350
+ },
351
+ summary: {
352
+ type: "string",
353
+ description: "One-line summary of the operator's cascade health",
354
+ },
355
+ },
356
+ };
357
+
358
+ export const SUGGEST_OUTPUT = {
359
+ type: "object",
360
+ properties: {
361
+ suggestions: {
362
+ type: "array",
363
+ description: "Ranked recommendations, highest Υ impact first",
364
+ items: {
365
+ type: "object",
366
+ properties: {
367
+ rank: { type: "number", description: "1-based rank by Υ impact" },
368
+ action: { type: "string", description: "What to change" },
369
+ pillar: { type: "string", description: "Which pillar to adjust" },
370
+ delta: { type: "string", description: "How much to change it" },
371
+ simulated_yield: {
372
+ type: "number",
373
+ description: "Projected Υ after the change",
374
+ },
375
+ yield_delta: { type: "number", description: "Υ change vs current" },
376
+ class_after: {
377
+ type: "string",
378
+ description: "Projected class after the change",
379
+ },
380
+ rationale: { type: "string", description: "Why this helps" },
381
+ },
382
+ },
383
+ },
384
+ current_yield: {
385
+ type: "number",
386
+ description: "Current Υ before any changes",
387
+ },
388
+ current_class: { type: "string", description: "Current class tier" },
389
+ best_single_change: {
390
+ type: "string",
391
+ description: "The single highest-impact change",
392
+ },
393
+ },
394
+ };
@@ -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 };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * tools/compare-operators.mjs — compare_operators tool.
3
+ */
4
+
5
+ import { COMPARE_OPERATORS_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
6
+ import { _behavioralFraming, _classMeaning, _competitiveLayer } from "./_framing.mjs";
7
+
8
+ export const TOOL_DEF = {
9
+ name: "compare_operators",
10
+ description:
11
+ "Compares two operators side-by-side with a behavioral verdict. Fetches both profiles from the board and returns their yield, leverage, velocity, class, and rank side-by-side, plus a verdict explaining who is more efficient and why in power-user language. Use this when users ask 'compare operator X vs Y' or 'who is more efficient' or 'how do two AI users compare'. Intent: COMPARE_OPERATORS.",
12
+ annotations: { title: "Compare two operators", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ codename_a: {
17
+ type: "string",
18
+ description:
19
+ "First operator's codename from the SigRank leaderboard. Case-insensitive.",
20
+ },
21
+ codename_b: {
22
+ type: "string",
23
+ description:
24
+ "Second operator's codename from the SigRank leaderboard. Case-insensitive.",
25
+ },
26
+ },
27
+ required: ["codename_a", "codename_b"],
28
+ description:
29
+ "Requires both codenames. Both must exist on the board.",
30
+ },
31
+ outputSchema: COMPARE_OPERATORS_OUTPUT,
32
+ };
33
+
34
+ export async function handleCompareOperators(args, ctx) {
35
+ const nameA = String(args?.codename_a || "").trim();
36
+ const nameB = String(args?.codename_b || "").trim();
37
+ if (!nameA || !nameB)
38
+ throw new Error(
39
+ "compare_operators requires both `codename_a` and `codename_b`.",
40
+ );
41
+
42
+ const [opA, opB, board] = await Promise.all([
43
+ ctx.fetchJson(`/api/v1/operators/${encodeURIComponent(nameA)}`),
44
+ ctx.fetchJson(`/api/v1/operators/${encodeURIComponent(nameB)}`),
45
+ ctx.fetchJson("/api/v1/leaderboard?metric=yield_"),
46
+ ]);
47
+
48
+ const yieldA = opA.yield_ || 0;
49
+ const yieldB = opB.yield_ || 0;
50
+ const delta = yieldA - yieldB;
51
+
52
+ const winner = yieldA > yieldB ? opA : opB;
53
+ const loser = yieldA > yieldB ? opB : opA;
54
+ const verdict = `${winner.codename} is more token-efficient (${winner.yield_?.toLocaleString?.() || winner.yield_} vs ${loser.yield_?.toLocaleString?.() || loser.yield_} Υ). ${_behavioralFraming(winner)} ${loser.codename} ${_classMeaning(loser.class).toLowerCase()}`;
55
+
56
+ return {
57
+ operator_a: {
58
+ codename: opA.codename,
59
+ yield_: opA.yield_,
60
+ leverage: opA.leverage,
61
+ velocity: opA.velocity,
62
+ class: opA.class,
63
+ rank: opA.rank,
64
+ competitive: _competitiveLayer(opA, board),
65
+ },
66
+ operator_b: {
67
+ codename: opB.codename,
68
+ yield_: opB.yield_,
69
+ leverage: opB.leverage,
70
+ velocity: opB.velocity,
71
+ class: opB.class,
72
+ rank: opB.rank,
73
+ competitive: _competitiveLayer(opB, board),
74
+ },
75
+ verdict,
76
+ yield_delta: delta,
77
+ cta: "Compare me to others",
78
+ };
79
+ }