sigrank 0.18.5 → 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 +94 -41
  4. package/adapters/index.mjs +1100 -0
  5. package/adapters/tokenpull.mjs +568 -0
  6. package/adapters.mjs +1 -857
  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,119 @@
1
+ /**
2
+ * tools/enroll.mjs — enroll tool.
3
+ */
4
+
5
+ import os from "node:os";
6
+ import { ensureIdentity, recordEnrollment } from "../identity/keystore.mjs";
7
+ import { ENROLL_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
8
+ import { TERMS_VERSION, PRIVACY_VERSION } from "../lib/constants.mjs";
9
+
10
+ export const TOOL_DEF = {
11
+ name: "enroll",
12
+ description:
13
+ 'Bind THIS device to your SigRank operator so your signed token runs cascade to the live board. Paste the key from signalaf.com → Settings → "New key" (or "Generate connect code"). On first run it generates + stores a local ed25519 keypair (~/.sigrank-mcp/identity.json); only the PUBLIC key is ever sent. By enrolling you agree to the SignalAF Terms of Service (signalaf.com/terms) and Privacy Policy (signalaf.com/privacy). Need a new key? Click "New key" at signalaf.com → Settings, then paste it here.',
14
+ annotations: {
15
+ title: "Enroll device identity",
16
+ ...ANNOTATIONS.destructiveHint,
17
+ ...ANNOTATIONS.idempotentHint,
18
+ ...ANNOTATIONS.openWorldHint,
19
+ },
20
+ inputSchema: {
21
+ type: "object",
22
+ properties: {
23
+ code: {
24
+ type: "string",
25
+ description:
26
+ "the key / connect code (SIGR-XXXXX-XXXXX-XXXXX) from Settings → New key (or Generate connect code)",
27
+ },
28
+ device_label: {
29
+ type: "string",
30
+ description:
31
+ "optional label for this device (default: hostname · agent version)",
32
+ },
33
+ },
34
+ required: ["code"],
35
+ },
36
+ outputSchema: ENROLL_OUTPUT,
37
+ };
38
+
39
+ export async function handleEnroll(args, ctx) {
40
+ // Redeem a web connect code → bind this device. Generates/loads the local keypair;
41
+ // sends ONLY the public key. operator binding happens server-side from the code row.
42
+ const code = String(args?.code || "").trim();
43
+ if (!code)
44
+ throw new Error(
45
+ "enroll requires a `code` — paste your connect code from signalaf.com → Settings → Connect a device.",
46
+ );
47
+ const id = ctx.opts.identity || ensureIdentity();
48
+ const deviceLabel = String(
49
+ args?.device_label || `${os.hostname()} · ${id.agent_version}`,
50
+ ).slice(0, 120);
51
+ const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/devices/enroll`, {
52
+ method: "POST",
53
+ headers: {
54
+ "content-type": "application/json",
55
+ accept: "application/json",
56
+ },
57
+ body: JSON.stringify({
58
+ code,
59
+ device_id: id.device_id,
60
+ public_key: id.public_key,
61
+ device_label: deviceLabel,
62
+ agent_version: id.agent_version,
63
+ consent_acknowledged: true,
64
+ terms_version: TERMS_VERSION,
65
+ privacy_version: PRIVACY_VERSION,
66
+ }),
67
+ });
68
+ let ack;
69
+ try {
70
+ ack = await res.json();
71
+ } catch {
72
+ ack = {};
73
+ }
74
+ if (res.status === 201 && ack.status === "enrolled") {
75
+ // Persist the binding locally (skipped when a test injects opts.identity → no keystore write).
76
+ if (!ctx.opts.identity)
77
+ recordEnrollment({
78
+ codename: ack.codename,
79
+ operator_id: ack.operator_id,
80
+ });
81
+ return {
82
+ status: "enrolled",
83
+ codename: ack.codename ?? null,
84
+ operator_id: ack.operator_id ?? null,
85
+ device_id: id.device_id,
86
+ trust_status: ack.trust_status ?? "trusted",
87
+ };
88
+ }
89
+ // Recovery: if the server says device_already_enrolled but includes the
90
+ // codename/operator_id, the device IS bound server-side — record it locally
91
+ // and return enrolled instead of erroring. This handles the case where the
92
+ // local binding was lost (partial write, version transition) but the device
93
+ // is still enrolled server-side.
94
+ if (
95
+ ack.reason === "device_already_enrolled" &&
96
+ ack.codename &&
97
+ ack.operator_id
98
+ ) {
99
+ if (!ctx.opts.identity)
100
+ recordEnrollment({
101
+ codename: ack.codename,
102
+ operator_id: ack.operator_id,
103
+ });
104
+ return {
105
+ status: "enrolled",
106
+ codename: ack.codename,
107
+ operator_id: ack.operator_id,
108
+ device_id: id.device_id,
109
+ trust_status: ack.trust_status ?? "trusted",
110
+ recovered: true,
111
+ };
112
+ }
113
+ return {
114
+ status: "error",
115
+ httpStatus: res.status,
116
+ reason: ack.reason || ack.status || `http_${res.status}`,
117
+ detail: ack.detail ?? null,
118
+ };
119
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * tools/get-best-operator.mjs — get_best_operator tool.
3
+ */
4
+
5
+ import { BEST_OPERATOR_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
6
+ import { DEFAULT_API_BASE } from "./_helpers.mjs";
7
+ import { _behavioralFraming, _competitiveLayer } from "./_framing.mjs";
8
+ import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
9
+
10
+ export const TOOL_DEF = {
11
+ name: "get_best_operator",
12
+ description:
13
+ "Returns the top N operators on the SigRank leaderboard with behavioral framing in power-user language. Wraps get_leaderboard and adds plain-language interpretation of each top operator's cascade: what their yield, leverage, and velocity mean in terms of AI power-user behavior (cache reuse, input economy, output productivity). Use this when users ask 'who is the best AI user?' or 'who tops the SigRank leaderboard?' or 'show me the AI user leaderboard'. Do NOT use get_leaderboard if you want the raw array without interpretation — use this for the power-user framing. Intent: BEST_OPERATOR.",
14
+ annotations: { title: "Get best operator", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {
18
+ n: {
19
+ type: "integer",
20
+ description:
21
+ "Number of top operators to return (default: 5, max: 20). Returns the top N by yield.",
22
+ minimum: 1,
23
+ maximum: 20,
24
+ },
25
+ },
26
+ description:
27
+ "Optional: how many top operators to return. Defaults to 5.",
28
+ },
29
+ outputSchema: BEST_OPERATOR_OUTPUT,
30
+ };
31
+
32
+ export async function handleGetBestOperator(args, ctx) {
33
+ const rawN = args?.n;
34
+ const n = Math.min(20, Math.max(1, rawN == null ? 5 : Number(rawN)));
35
+ const board = await ctx.fetchJson(`/api/v1/leaderboard?metric=${LEADERBOARD_METRIC}`);
36
+ const ops = (board.operators || board || []).slice(0, n);
37
+ const total = Array.isArray(board.operators || board)
38
+ ? (board.operators || board).length
39
+ : 0;
40
+
41
+ const top = ops.map((op) => ({
42
+ ...op,
43
+ behavioral_framing: _behavioralFraming(op),
44
+ competitive: _competitiveLayer(op, board),
45
+ }));
46
+
47
+ const best = top[0];
48
+ const summary = best
49
+ ? `${best.codename} tops the SigRank leaderboard at Υ ${best.yield_?.toLocaleString?.() || best.yield_} — ${_behavioralFraming(best)}`
50
+ : "No operators on the board yet.";
51
+
52
+ return {
53
+ top_operators: top,
54
+ total_operators: total,
55
+ summary,
56
+ cta: "Check my rank",
57
+ shareable_url: best ? `${DEFAULT_API_BASE}/operator/${encodeURIComponent(best.codename)}` : null,
58
+ };
59
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * tools/get-leaderboard.mjs — get_leaderboard tool.
3
+ */
4
+
5
+ import { LEADERBOARD_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
6
+ import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
7
+
8
+ export const TOOL_DEF = {
9
+ name: "get_leaderboard",
10
+ description:
11
+ "Fetches the live public SigRank leaderboard from signalaf.com. Reads all ranked operators sorted by yield (Υ = Cache Reads × Output / Input²) and returns an array of operator summaries. Each entry contains: codename (public display name), yield (Υ, the headline efficiency metric), leverage ratio (Cr/I = cache reads divided by input), velocity (O/I = output divided by input), class tier (one of 24 experience stages: 8 tiers × 3 sub-stages, e.g. ARCH+ I, REFINER II, IGNITER III), and rank position (integer, 1-based). Returns an empty array if no operators have submitted yet. Use this to see where operators stand overall, to find specific codenames for get_operator lookups, or to display the current rankings. Do NOT use this to check your own rank if you already know your codename — use get_operator instead for a single-operator profile with per-window breakdowns. After calling this, follow up with get_operator to get detailed metrics for any operator of interest.",
12
+ annotations: { title: "Get leaderboard", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {},
16
+ description:
17
+ "This tool takes no parameters. It always fetches the full public leaderboard.",
18
+ },
19
+ outputSchema: LEADERBOARD_OUTPUT,
20
+ };
21
+
22
+ export async function handleGetLeaderboard(args, ctx) {
23
+ const params = new URLSearchParams({ metric: LEADERBOARD_METRIC });
24
+ if (args?.limit) params.set("limit", String(args.limit));
25
+ if (args?.window) params.set("window", args.window);
26
+ return ctx.fetchJson(`/api/v1/leaderboard?${params}`);
27
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * tools/get-operator.mjs — get_operator tool.
3
+ */
4
+
5
+ import { OPERATOR_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
6
+
7
+ export const TOOL_DEF = {
8
+ name: "get_operator",
9
+ description:
10
+ "Fetches one operator's live profile from the SigRank board by their codename. Reads the operator's current submission data from signalaf.com and returns their detailed metrics: yield (Υ), leverage ratio (Cr/I), velocity (O/I), class tier (one of 24 experience stages: 8 tiers × 3 sub-stages, e.g. ARCH+ I, REFINER II, IGNITER III), rank position (integer, 1-based), and per-window breakdowns for each time range (7d, 30d, 90d, all-time) with the four canonical pillars (input, output, cacheCreate, cacheRead) per window. Returns an error if the codename is not found on the board. Use this to look up any operator who has submitted to the board — codenames are public and visible on the leaderboard. Do NOT use this to browse all operators — use get_leaderboard for that. After calling this, you can use simulate_change to model what would happen if the operator adjusted their token mix.",
11
+ annotations: { title: "Get operator profile", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ codename: {
16
+ type: "string",
17
+ description:
18
+ 'The operator\'s public codename as shown on the SigRank leaderboard. Case-insensitive — "Ghost Falcon" and "ghost falcon" are equivalent. Must match a codename that exists on the board; returns an error if not found. To discover valid codenames, call get_leaderboard first.',
19
+ },
20
+ },
21
+ required: ["codename"],
22
+ description:
23
+ "Requires the operator's codename. No other parameters are accepted.",
24
+ },
25
+ outputSchema: OPERATOR_OUTPUT,
26
+ };
27
+
28
+ export async function handleGetOperator(args, ctx) {
29
+ const codename = String(args?.codename || "").trim();
30
+ if (!codename)
31
+ throw new Error("get_operator requires a non-empty `codename` argument.");
32
+ return ctx.fetchJson(`/api/v1/operators/${encodeURIComponent(codename)}`);
33
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * tools/index.mjs — the SigRank MCP tool table + dispatcher, transport-free so it can be
3
+ * unit-tested without spawning the stdio server.
4
+ *
5
+ * Imports individual tool modules from ./ and re-exports the TOOLS array + callTool
6
+ * dispatcher. Backward-compatible root import via ../tools.mjs (shim).
7
+ */
8
+
9
+ import { ALL_PLATFORMS } from "../adapters/index.mjs";
10
+ import { execFileAsync } from "./_helpers.mjs";
11
+ import { buildFetch, DEFAULT_API_BASE, DEFAULT_FETCH_TIMEOUT } from "./_helpers.mjs";
12
+ import {
13
+ TOKSCALE_CLIENT_MAP,
14
+ TOKSCALE_BLIND_PLATFORMS,
15
+ } from "../lib/constants.mjs";
16
+
17
+ import { TOOL_DEF as rankPasteTool, handleRankPaste } from "./rank-paste.mjs";
18
+ import { TOOL_DEF as getLeaderboardTool, handleGetLeaderboard } from "./get-leaderboard.mjs";
19
+ import { TOOL_DEF as getOperatorTool, handleGetOperator } from "./get-operator.mjs";
20
+ import { TOOL_DEF as submitPasteTool, handleSubmitPaste } from "./submit-paste.mjs";
21
+ import { TOOL_DEF as tokenpullTool, handleTokenpull } from "./tokenpull.mjs";
22
+ import { TOOL_DEF as tokenpullSubmitTool, handleTokenpullSubmit } from "./tokenpull-submit.mjs";
23
+ import { TOOL_DEF as rankWindowsTool, handleRankWindows } from "./rank-windows.mjs";
24
+ import { TOOL_DEF as watchTokenpullTool, handleWatchTokenpull } from "./watch-tokenpull.mjs";
25
+ import { TOOL_DEF as tokenpullCompareTool, handleTokenpullCompare } from "./tokenpull-compare.mjs";
26
+ import { TOOL_DEF as enrollTool, handleEnroll } from "./enroll.mjs";
27
+ import { TOOL_DEF as submitVerifiedTool, handleSubmitVerified } from "./submit-verified.mjs";
28
+ import { TOOL_DEF as simulateChangeTool, handleSimulateChange } from "./simulate-change.mjs";
29
+ import { TOOL_DEF as diagnoseCascadeTool, handleDiagnoseCascade } from "./diagnose-cascade.mjs";
30
+ import { TOOL_DEF as suggestImprovementsTool, handleSuggestImprovements } from "./suggest-improvements.mjs";
31
+ import { TOOL_DEF as selfImproveTool, handleSelfImprove } from "./self-improve.mjs";
32
+ import { TOOL_DEF as discoverPeersTool, handleDiscoverPeers } from "./discover-peers.mjs";
33
+ // NOTE: get_best_operator, compare_self, compare_operators, describe_power_user,
34
+ // and optimize_efficiency were moved to bestuser-router-mcp (the marketing layer).
35
+ // They are duplicated there with richer params (metric sorting, platform filters,
36
+ // local text scoring). sigrank-mcp stays focused on local pulling + scoring +
37
+ // submission + tokscale analytics.
38
+ import { TOOL_DEF as tokscaleBreakdownTool, handleTokscaleBreakdown } from "./tokscale-breakdown.mjs";
39
+ import { TOOL_DEF as tokscaleMarketShareTool, handleTokscaleMarketShare } from "./tokscale-market-share.mjs";
40
+ import { TOOL_DEF as tokscaleDeveloperProfileTool, handleTokscaleDeveloperProfile } from "./tokscale-developer-profile.mjs";
41
+ import { TOOL_DEF as tokscaleModelTrendsTool, handleTokscaleModelTrends } from "./tokscale-model-trends.mjs";
42
+ import { TOOL_DEF as tokscaleCostAnalysisTool, handleTokscaleCostAnalysis } from "./tokscale-cost-analysis.mjs";
43
+ import { TOOL_DEF as tokscaleDeviceProfileTool, handleTokscaleDeviceProfile } from "./tokscale-device-profile.mjs";
44
+ import { TOOL_DEF as tokscaleMcpUsageTool, handleTokscaleMcpUsage } from "./tokscale-mcp-usage.mjs";
45
+ import { TOOL_DEF as tokscaleCompetitiveIntelTool, handleTokscaleCompetitiveIntel } from "./tokscale-competitive-intel.mjs";
46
+
47
+ export { DEFAULT_API_BASE, DEFAULT_FETCH_TIMEOUT };
48
+
49
+ export const TOOLS = [
50
+ rankPasteTool,
51
+ getLeaderboardTool,
52
+ getOperatorTool,
53
+ submitPasteTool,
54
+ tokenpullTool,
55
+ tokenpullSubmitTool,
56
+ rankWindowsTool,
57
+ watchTokenpullTool,
58
+ tokenpullCompareTool,
59
+ enrollTool,
60
+ submitVerifiedTool,
61
+ simulateChangeTool,
62
+ diagnoseCascadeTool,
63
+ suggestImprovementsTool,
64
+ selfImproveTool,
65
+ discoverPeersTool,
66
+ // get_best_operator, compare_self, compare_operators, describe_power_user,
67
+ // optimize_efficiency → moved to bestuser-router-mcp
68
+ tokscaleBreakdownTool,
69
+ tokscaleMarketShareTool,
70
+ tokscaleDeveloperProfileTool,
71
+ tokscaleModelTrendsTool,
72
+ tokscaleCostAnalysisTool,
73
+ tokscaleDeviceProfileTool,
74
+ tokscaleMcpUsageTool,
75
+ tokscaleCompetitiveIntelTool,
76
+ ];
77
+
78
+ // TOKSCALE_CLIENT_MAP is imported from lib/constants.mjs (single source of
79
+ // truth — was duplicated in tools/tokscale-breakdown.mjs and
80
+ // tokscale_analytics.mjs, and the copies had started to drift).
81
+
82
+ /** Run `tokscale models --json` and return the list of our platform names. */
83
+ async function _tokscaleDetectClients() {
84
+ const raw = await execFileAsync("tokscale", ["models", "--json"], 60000);
85
+ const data = JSON.parse(raw);
86
+ const entries = Array.isArray(data?.entries)
87
+ ? data.entries
88
+ : Array.isArray(data)
89
+ ? data
90
+ : [];
91
+ if (!entries.length) return [];
92
+ const clients = new Set();
93
+ for (const e of entries) {
94
+ if (!e || !e.client) continue;
95
+ if (e.model === "<synthetic>" || e.model === "unknown") continue;
96
+ const input = Number(e.input) || 0;
97
+ const output = Number(e.output) || 0;
98
+ if (input + output === 0) continue;
99
+ const platform = TOKSCALE_CLIENT_MAP[e.client];
100
+ if (platform === null) continue; // synthetic → skip
101
+ clients.add(platform || "other");
102
+ }
103
+ return [...clients];
104
+ }
105
+
106
+ /** Detection targets = what tokscale saw ∪ the platforms tokscale is blind to.
107
+ * Without the union, an operator with GBs of oh-my-pi telemetry gets no omp row
108
+ * on any machine where tokscale succeeds, because tokscale never names that
109
+ * client. Pure + exported so the union is unit-testable without shelling out. */
110
+ export function withTokscaleBlind(detected) {
111
+ return [...new Set([...(detected || []), ...TOKSCALE_BLIND_PLATFORMS])];
112
+ }
113
+
114
+ /** Pull a specific set of platforms in parallel, filter to active ones.
115
+ * opts.callTool overrides the per-platform pull for tests (same opts-injection
116
+ * convention as opts.adapter in pullByPlatform) so the target-set logic is
117
+ * assertable without a real filesystem scan. */
118
+ async function _pullExplicit(platforms, opts = {}) {
119
+ const pull = opts.callTool || callTool;
120
+ const settled = await Promise.allSettled(
121
+ platforms.map((p) => pull("tokenpull", { platform: p }, opts)),
122
+ );
123
+ const active = settled
124
+ .filter((r) => r.status === "fulfilled" && r.value)
125
+ .map((r) => r.value)
126
+ .filter((d) =>
127
+ (d.windows || []).some(
128
+ (w) => (w.pillars?.input ?? 0) + (w.pillars?.output ?? 0) > 0,
129
+ ),
130
+ );
131
+ const rank = (p) =>
132
+ p === "claude" ? -2 : p === "codex" ? -1 : ALL_PLATFORMS.indexOf(p);
133
+ active.sort((a, b) => rank(a.platform) - rank(b.platform));
134
+ return active;
135
+ }
136
+
137
+ // ── Shared active-platform loader ───────────────────────────────────────────
138
+ // THE single data path for "show my cascade across platforms" — used by `me`,
139
+ // `watch`, and the TUI Dashboard so they can't drift apart again. Pulls each
140
+ // target platform via the tokenpull tool (enriched: pillars + cascade + card),
141
+ // keeps only platforms with real local data, and sorts claude → codex → rest.
142
+ // Pass `platforms` to scope it (e.g. ['claude'] for a fast first paint).
143
+ export async function pullActivePlatforms({ platforms } = {}, opts = {}) {
144
+ // If the caller specified explicit platforms, use those.
145
+ if (platforms && platforms.length) {
146
+ return _pullExplicit(platforms, opts);
147
+ }
148
+ // Auto-detect: run tokscale once to discover all active clients, then pull
149
+ // only those (plus the tokscale-blind platforms). This is much faster than trying
150
+ // all adapters (most fail silently on a machine that doesn't have that tool).
151
+ //
152
+ // Flow: tokscale surfaces ALL clients → map to our platform names →
153
+ // ccusage is primary for Claude (more accurate) → other platforms use
154
+ // their adapters. Anything in tokscale NOT in ccusage gets included.
155
+ // opts.detectClients overrides the tokscale probe for tests (no shelling out).
156
+ const detect = opts.detectClients || _tokscaleDetectClients;
157
+ const detected = await detect().catch(() => null);
158
+ if (detected && detected.length > 0) {
159
+ return _pullExplicit(withTokscaleBlind(detected), opts);
160
+ }
161
+ // Fallback: tokscale not installed or failed → try all adapters (old behavior)
162
+ return _pullExplicit(ALL_PLATFORMS, opts);
163
+ }
164
+
165
+ export async function callTool(name, args, opts = {}) {
166
+ const { apiBase, doFetch, fetchJson } = buildFetch(opts);
167
+ const ctx = {
168
+ apiBase,
169
+ doFetch,
170
+ fetchJson,
171
+ opts,
172
+ callTool: (n, a) => callTool(n, a, opts),
173
+ };
174
+
175
+ switch (name) {
176
+ case "rank_paste":
177
+ return handleRankPaste(args);
178
+ case "get_leaderboard":
179
+ return handleGetLeaderboard(args, ctx);
180
+ case "get_operator":
181
+ return handleGetOperator(args, ctx);
182
+ case "submit_paste":
183
+ return handleSubmitPaste(args, ctx);
184
+ case "tokenpull":
185
+ return handleTokenpull(args, ctx);
186
+ case "tokenpull_submit":
187
+ return handleTokenpullSubmit(args, ctx);
188
+ case "rank_windows":
189
+ return handleRankWindows(args);
190
+ case "watch_tokenpull":
191
+ return handleWatchTokenpull(args, ctx);
192
+ case "tokenpull_compare":
193
+ return handleTokenpullCompare(args, ctx);
194
+ case "enroll":
195
+ return handleEnroll(args, ctx);
196
+ case "submit_verified":
197
+ return handleSubmitVerified(args, ctx);
198
+ case "simulate_change":
199
+ return handleSimulateChange(args);
200
+ case "diagnose_cascade":
201
+ return handleDiagnoseCascade(args);
202
+ case "suggest_improvements":
203
+ return handleSuggestImprovements(args);
204
+ case "self_improve":
205
+ return handleSelfImprove(args, ctx);
206
+ case "discover_peers":
207
+ return handleDiscoverPeers(args, ctx);
208
+ // get_best_operator, compare_self, compare_operators, describe_power_user,
209
+ // optimize_efficiency → moved to bestuser-router-mcp
210
+ case "tokscale_breakdown":
211
+ return handleTokscaleBreakdown(args);
212
+ case "tokscale_market_share":
213
+ return handleTokscaleMarketShare();
214
+ case "tokscale_developer_profile":
215
+ return handleTokscaleDeveloperProfile();
216
+ case "tokscale_model_trends":
217
+ return handleTokscaleModelTrends();
218
+ case "tokscale_cost_analysis":
219
+ return handleTokscaleCostAnalysis();
220
+ case "tokscale_device_profile":
221
+ return handleTokscaleDeviceProfile();
222
+ case "tokscale_mcp_usage":
223
+ return handleTokscaleMcpUsage();
224
+ case "tokscale_competitive_intel":
225
+ return handleTokscaleCompetitiveIntel(args);
226
+ default:
227
+ throw new Error(`Unknown tool: ${name}`);
228
+ }
229
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * tools/optimize-efficiency.mjs — optimize_efficiency tool.
3
+ */
4
+
5
+ import { cascade, parsePillars } from "../analytics/cascade.mjs";
6
+ import { ANNOTATIONS, CLASS_ENUM } from "./_schemas.mjs";
7
+ import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
8
+ import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
9
+ import {
10
+ _improvementSuggestion,
11
+ _competitiveLayer,
12
+ _competitiveSummary,
13
+ FALLBACK_CLASS,
14
+ bandOf,
15
+ } from "./_framing.mjs";
16
+
17
+ export const TOOL_DEF = {
18
+ name: "optimize_efficiency",
19
+ description:
20
+ "Returns actionable suggestions for improving your token cascade efficiency, tied to your current metrics. Accepts either a codename (fetches from board) or raw token pillars (computes locally). Returns: your current metrics, ranked efficiency suggestions tied to cascade shape (increase cache reuse, reduce input, increase output), and references to power-user practices. Use this when users ask 'how can I use AI more efficiently?' or 'reduce token burn' or 'optimize token usage' or 'stop tokenmaxxing'. Intent: OPTIMIZE_EFFICIENCY (Informational + Transactional).",
21
+ annotations: { title: "Optimize efficiency", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.idempotentHint },
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ codename: {
26
+ type: "string",
27
+ description:
28
+ "Your codename on the SigRank leaderboard. If provided, fetches your live profile from the board.",
29
+ },
30
+ text: {
31
+ type: "string",
32
+ description:
33
+ 'Alternative: raw token pillars to score locally (ccusage JSON or "input output cacheCreate cacheRead"). Use this if you are not on the board yet.',
34
+ },
35
+ },
36
+ description:
37
+ "Provide either `codename` (to fetch from the board) or `text` (to score locally). At least one is required.",
38
+ },
39
+ outputSchema: {
40
+ type: "object",
41
+ properties: {
42
+ your_metrics: {
43
+ type: "object",
44
+ description: "Your current cascade metrics",
45
+ properties: {
46
+ yield_: { type: "number" },
47
+ leverage: { type: "number" },
48
+ velocity: { type: "number" },
49
+ class: { type: "string", enum: CLASS_ENUM },
50
+ },
51
+ },
52
+ suggestions: {
53
+ type: "array",
54
+ description: "Ranked efficiency suggestions",
55
+ items: {
56
+ type: "object",
57
+ properties: {
58
+ action: { type: "string", description: "What to change" },
59
+ why: { type: "string", description: "Why this helps your yield" },
60
+ power_user_practice: { type: "string", description: "The power-user practice this maps to" },
61
+ },
62
+ },
63
+ },
64
+ summary: { type: "string", description: "One-line summary of your efficiency status" },
65
+ },
66
+ },
67
+ };
68
+
69
+ export async function handleOptimizeEfficiency(args, ctx) {
70
+ const codename = String(args?.codename || "").trim();
71
+ const text = String(args?.text || "").trim();
72
+
73
+ if (!codename && !text)
74
+ throw new Error(
75
+ "optimize_efficiency requires either `codename` (to fetch from the board) or `text` (raw token pillars to score locally).",
76
+ );
77
+
78
+ let metrics;
79
+ if (codename) {
80
+ metrics = await ctx.fetchJson(
81
+ `/api/v1/operators/${encodeURIComponent(codename)}`,
82
+ );
83
+ } else {
84
+ if (text.length > MAX_INPUT) {
85
+ return { error: "input_too_large", detail: `text exceeds ${MAX_INPUT} chars.` };
86
+ }
87
+ const pillars = parsePillars(text);
88
+ const c = withParseWarnings(pillars, cascade(pillars));
89
+ metrics = {
90
+ codename: "you (local)",
91
+ yield_: c.yield,
92
+ leverage: c.leverage,
93
+ velocity: c.velocity,
94
+ class: c.class,
95
+ };
96
+ }
97
+
98
+ const klass = metrics.class || FALLBACK_CLASS;
99
+ const l = metrics.leverage || 0;
100
+ const v = metrics.velocity || 0;
101
+ const y = metrics.yield_ || 0;
102
+
103
+ // Build ranked suggestions based on current cascade shape
104
+ const suggestions = [];
105
+
106
+ if (l < 5) {
107
+ suggestions.push({
108
+ action: "Increase cache reuse — reuse prompts, templates, and workflows instead of starting from scratch",
109
+ why: "Your leverage is " + l.toFixed(1) + "×, meaning most of your context is fresh input. Each reused cached token multiplies your yield because input² is in the denominator.",
110
+ power_user_practice: "Power users build template libraries and workflow patterns they invoke repeatedly, letting cached context accumulate.",
111
+ });
112
+ }
113
+ if (v < 1) {
114
+ suggestions.push({
115
+ action: "Increase output per session — produce more, don't just read",
116
+ why: "Your velocity is " + v.toFixed(2) + ", meaning you're consuming more input than producing output. Yield rewards output production.",
117
+ power_user_practice: "Power users maximize output per session — they ask AI to generate, transform, and produce, not just explain.",
118
+ });
119
+ }
120
+ // Top-band tiers (apex/high) already have leverage + velocity — the
121
+ // "extend session" suggestion is for mid/early/entry operators whose
122
+ // leverage and velocity are both solid but not yet at the top. The legacy
123
+ // `klass !== "10xer"` guard never matched because the classifier emits the
124
+ // 8-tier dev10x names, not "10xer" — so this branch fired for EVERY
125
+ // operator including top-tier ones. Now keyed off the canonical bands.
126
+ const TOP_BANDS = new Set(["apex", "high"]);
127
+ if (l >= 5 && v >= 1 && !TOP_BANDS.has(bandOf(klass))) {
128
+ suggestions.push({
129
+ action: "Extend session length to compound cached context further",
130
+ why: "Your leverage (" + l.toFixed(1) + "×) and velocity (" + v.toFixed(2) + ") are solid. Longer sessions with consistent context will push your yield higher.",
131
+ power_user_practice: "Power users maintain long, context-rich sessions where the cache grows and compounds.",
132
+ });
133
+ }
134
+ if (suggestions.length === 0) {
135
+ suggestions.push({
136
+ action: "Maintain your cascade architecture — you're at the top tier",
137
+ why: "Your yield (" + y.toLocaleString() + "), leverage (" + l.toFixed(1) + "×), and velocity (" + v.toFixed(2) + ") are all strong. Keep doing what you're doing.",
138
+ power_user_practice: "Power users don't rest on their metrics — they experiment with new workflow patterns and measure the impact.",
139
+ });
140
+ }
141
+
142
+ const summary = `Your Υ Yield is ${y.toLocaleString()} (${klass}). ${_improvementSuggestion(klass, metrics)}`;
143
+
144
+ // Competitive layer per SHARED_DESIGN_DECISIONS.md §3/§4/§5
145
+ const board = await ctx.fetchJson(`/api/v1/leaderboard?metric=${LEADERBOARD_METRIC}`);
146
+ const competitive = _competitiveLayer(metrics, board);
147
+ const competitiveSummary = _competitiveSummary(metrics, board);
148
+
149
+ return {
150
+ your_metrics: {
151
+ yield_: y,
152
+ leverage: l,
153
+ velocity: v,
154
+ class: klass,
155
+ },
156
+ competitive: {
157
+ rank: competitive.rank,
158
+ total_operators: competitive.total_operators,
159
+ percentile: competitive.percentile,
160
+ class_tier: competitive.class_tier,
161
+ delta_from_average: competitive.delta_from_average,
162
+ delta_from_top: competitive.delta_from_top,
163
+ },
164
+ competitive_summary: competitiveSummary,
165
+ shareable_url: competitive.shareable_url,
166
+ suggestions,
167
+ summary,
168
+ cta: "Improve my score",
169
+ };
170
+ }