sigrank 0.0.177 → 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.
- package/CHANGELOG.md +100 -0
- package/MCP_SERVER_DESCRIPTION.md +141 -0
- package/README.md +23 -3
- package/adapters/index.mjs +865 -0
- package/adapters/tokenpull.mjs +576 -0
- package/adapters.mjs +1 -750
- package/analytics/cascade.mjs +274 -0
- package/cascade.mjs +1 -274
- package/cli.mjs +1 -1850
- package/connect.mjs +1 -13
- package/identity/connect.mjs +13 -0
- package/identity/keystore.mjs +242 -0
- package/identity/sign.mjs +106 -0
- package/index.mjs +30 -148
- package/keystore.mjs +1 -242
- package/narrate.mjs +1 -77
- package/package.json +31 -15
- package/preflight.mjs +1 -191
- package/presentation/cli.mjs +1874 -0
- package/presentation/narrate.mjs +77 -0
- package/presentation/tui.mjs +2668 -0
- package/prompts/check-my-efficiency.md +1 -0
- package/prompts/compare-with-leader.md +1 -0
- package/prompts/simulate-improvement.md +1 -0
- package/resources/class-tiers.md +23 -0
- package/resources/data-policy.md +27 -0
- package/resources/install-guide.md +59 -0
- package/resources/privacy-model.md +30 -0
- package/resources/scoring-formula.md +28 -0
- package/review.mjs +263 -0
- package/sign.mjs +1 -106
- package/submit/index.mjs +309 -0
- package/submit/preflight.mjs +191 -0
- package/submit.mjs +1 -303
- package/tokenpull.mjs +1 -584
- package/tools/_framing.mjs +139 -0
- package/tools/_helpers.mjs +258 -0
- package/tools/_report.mjs +262 -0
- package/tools/_schemas.mjs +394 -0
- package/tools/_verifiers.mjs +148 -0
- package/tools/compare-operators.mjs +79 -0
- package/tools/compare-self.mjs +122 -0
- package/tools/describe-power-user.mjs +68 -0
- package/tools/diagnose-cascade.mjs +192 -0
- package/tools/enroll.mjs +121 -0
- package/tools/get-best-operator.mjs +58 -0
- package/tools/get-leaderboard.mjs +26 -0
- package/tools/get-operator.mjs +33 -0
- package/tools/index.mjs +257 -0
- package/tools/optimize-efficiency.mjs +160 -0
- package/tools/rank-paste.mjs +51 -0
- package/tools/rank-windows.mjs +120 -0
- package/tools/self-improve.mjs +423 -0
- package/tools/simulate-change.mjs +207 -0
- package/tools/submit-paste.mjs +118 -0
- package/tools/submit-verified.mjs +153 -0
- package/tools/suggest-improvements.mjs +168 -0
- package/tools/tokenpull-compare.mjs +123 -0
- package/tools/tokenpull-submit.mjs +234 -0
- package/tools/tokenpull.mjs +113 -0
- package/tools/tokscale-breakdown.mjs +159 -0
- package/tools/tokscale-competitive-intel.mjs +45 -0
- package/tools/tokscale-cost-analysis.mjs +31 -0
- package/tools/tokscale-developer-profile.mjs +36 -0
- package/tools/tokscale-device-profile.mjs +35 -0
- package/tools/tokscale-market-share.mjs +38 -0
- package/tools/tokscale-mcp-usage.mjs +33 -0
- package/tools/tokscale-model-trends.mjs +32 -0
- package/tools/watch-tokenpull.mjs +134 -0
- package/tools.mjs +1 -3385
- package/tui.mjs +4 -2650
- package/banner.svg +0 -7
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/compare-self.mjs — compare_self tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cascade, parsePillars } from "../analytics/cascade.mjs";
|
|
6
|
+
import { COMPARE_SELF_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
7
|
+
import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
|
|
8
|
+
import {
|
|
9
|
+
_powerUserAssessment,
|
|
10
|
+
_classMeaning,
|
|
11
|
+
_improvementSuggestion,
|
|
12
|
+
_competitiveLayer,
|
|
13
|
+
_competitiveSummary,
|
|
14
|
+
} from "./_framing.mjs";
|
|
15
|
+
|
|
16
|
+
export const TOOL_DEF = {
|
|
17
|
+
name: "compare_self",
|
|
18
|
+
description:
|
|
19
|
+
"Compares an operator's metrics against board averages and power-user archetypes, returning a behavioral assessment. Accepts either a codename (fetches from the board) or raw token pillars (computes locally). Returns: your yield/leverage/velocity/class/rank, a power-user assessment mapping your class tier to AI power-user language, comparison vs board averages (your percentile), and one actionable suggestion to improve. Use this when users ask 'how do I measure up to other AI users?' or 'am I a power user?' or 'compare me to others'. Intent: COMPARE_SELF.",
|
|
20
|
+
annotations: { title: "Compare self to board", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
codename: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description:
|
|
27
|
+
"Your codename on the SigRank leaderboard. If provided, fetches your live profile from the board. Case-insensitive.",
|
|
28
|
+
},
|
|
29
|
+
text: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description:
|
|
32
|
+
'Alternative: raw token pillars to score locally (ccusage JSON or "input output cacheCreate cacheRead"). Use this if you are not on the board yet but want to see how you would compare.',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
description:
|
|
36
|
+
"Provide either `codename` (to fetch from the board) or `text` (to score locally). At least one is required.",
|
|
37
|
+
},
|
|
38
|
+
outputSchema: COMPARE_SELF_OUTPUT,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export async function handleCompareSelf(args, ctx) {
|
|
42
|
+
const codename = String(args?.codename || "").trim();
|
|
43
|
+
const text = String(args?.text || "").trim();
|
|
44
|
+
|
|
45
|
+
if (!codename && !text)
|
|
46
|
+
throw new Error(
|
|
47
|
+
"compare_self requires either `codename` (to fetch from the board) or `text` (raw token pillars to score locally).",
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
let yourMetrics;
|
|
51
|
+
if (codename) {
|
|
52
|
+
yourMetrics = await ctx.fetchJson(
|
|
53
|
+
`/api/v1/operators/${encodeURIComponent(codename)}`,
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
if (text.length > MAX_INPUT) {
|
|
57
|
+
return {
|
|
58
|
+
error: "input_too_large",
|
|
59
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const pillars = parsePillars(text);
|
|
63
|
+
const c = withParseWarnings(pillars, cascade(pillars));
|
|
64
|
+
yourMetrics = {
|
|
65
|
+
codename: "you (local)",
|
|
66
|
+
yield_: c.yield,
|
|
67
|
+
leverage: c.leverage,
|
|
68
|
+
velocity: c.velocity,
|
|
69
|
+
class: c.class,
|
|
70
|
+
rank: null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Fetch board for comparison
|
|
75
|
+
const board = await ctx.fetchJson("/api/v1/leaderboard?metric=yield_");
|
|
76
|
+
const allOps = board.operators || board || [];
|
|
77
|
+
const yields = allOps.map((o) => o.yield_ || 0).sort((a, b) => a - b);
|
|
78
|
+
const avgYield = yields.length
|
|
79
|
+
? yields.reduce((s, y) => s + y, 0) / yields.length
|
|
80
|
+
: 0;
|
|
81
|
+
const yourYield = yourMetrics.yield_ || 0;
|
|
82
|
+
const percentile = yields.length
|
|
83
|
+
? Math.round(
|
|
84
|
+
(yields.filter((y) => y < yourYield).length / yields.length) * 100,
|
|
85
|
+
)
|
|
86
|
+
: 0;
|
|
87
|
+
|
|
88
|
+
const klass = yourMetrics.class || "Burner";
|
|
89
|
+
const powerUserAssessment = _powerUserAssessment(klass, yourMetrics);
|
|
90
|
+
const classMeaning = _classMeaning(klass);
|
|
91
|
+
|
|
92
|
+
const yieldVsAvg = yields.length
|
|
93
|
+
? yourYield > avgYield
|
|
94
|
+
? `${(yourYield / avgYield).toFixed(1)}× the board average`
|
|
95
|
+
: `${((yourYield / avgYield) * 100).toFixed(0)}% of the board average`
|
|
96
|
+
: "board has no other operators";
|
|
97
|
+
|
|
98
|
+
const suggestion = _improvementSuggestion(klass, yourMetrics);
|
|
99
|
+
|
|
100
|
+
// Competitive layer per SHARED_DESIGN_DECISIONS.md §3/§4/§5
|
|
101
|
+
const competitive = _competitiveLayer(yourMetrics, board);
|
|
102
|
+
const competitiveSummary = _competitiveSummary(yourMetrics, board);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
your_metrics: yourMetrics,
|
|
106
|
+
power_user_assessment: powerUserAssessment,
|
|
107
|
+
comparison: {
|
|
108
|
+
your_yield_vs_avg: yieldVsAvg,
|
|
109
|
+
your_class_meaning: classMeaning,
|
|
110
|
+
percentile,
|
|
111
|
+
rank: competitive.rank,
|
|
112
|
+
total_operators: competitive.total_operators,
|
|
113
|
+
class_tier: competitive.class_tier,
|
|
114
|
+
delta_from_average: competitive.delta_from_average,
|
|
115
|
+
delta_from_top: competitive.delta_from_top,
|
|
116
|
+
},
|
|
117
|
+
competitive_summary: competitiveSummary,
|
|
118
|
+
shareable_url: competitive.shareable_url,
|
|
119
|
+
suggestion,
|
|
120
|
+
cta: "See where I stand",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/describe-power-user.mjs — describe_power_user tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ANNOTATIONS } from "./_schemas.mjs";
|
|
6
|
+
import { DEFAULT_API_BASE } from "./_helpers.mjs";
|
|
7
|
+
|
|
8
|
+
export const TOOL_DEF = {
|
|
9
|
+
name: "describe_power_user",
|
|
10
|
+
description:
|
|
11
|
+
"Returns an explanatory description of what makes an AI power user, anchored in SigRank's metrics and operator classes. Explains the yield metric, leverage, velocity, and how class tiers (Burner/Builder/10xer) map to power-user behavior patterns. Use this when users ask 'what is an AI power user?' or 'what makes a good AI user?' or 'describe advanced AI user behavior'. Intent: DESCRIBE_POWER_USER (Informational).",
|
|
12
|
+
annotations: { title: "Describe power user", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.idempotentHint },
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {},
|
|
16
|
+
description:
|
|
17
|
+
"This tool takes no parameters. It returns a static explanatory response about AI power users.",
|
|
18
|
+
},
|
|
19
|
+
outputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
description: { type: "string", description: "What is an AI power user" },
|
|
23
|
+
metrics_explained: {
|
|
24
|
+
type: "object",
|
|
25
|
+
description: "How SigRank metrics map to power-user behavior",
|
|
26
|
+
properties: {
|
|
27
|
+
yield_: { type: "string", description: "What yield measures in power-user terms" },
|
|
28
|
+
leverage: { type: "string", description: "What leverage means for power users" },
|
|
29
|
+
velocity: { type: "string", description: "What velocity means for power users" },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
class_tiers: {
|
|
33
|
+
type: "array",
|
|
34
|
+
description: "Operator class tiers and their power-user meaning",
|
|
35
|
+
items: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
|
|
39
|
+
meaning: { type: "string" },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
link: { type: "string", description: "URL to learn more" },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export async function handleDescribePowerUser() {
|
|
49
|
+
return {
|
|
50
|
+
description:
|
|
51
|
+
"An AI power user isn't someone who sends the most tokens — it's someone who compounds signal. " +
|
|
52
|
+
"Power users build workflows where cached context does the heavy lifting, fresh input stays lean, " +
|
|
53
|
+
"and output per session is high. SigRank quantifies this with the yield metric (Υ = cache_read × output / input²).",
|
|
54
|
+
metrics_explained: {
|
|
55
|
+
yield_: "Yield (Υ) measures how well you compound signal, not how much you burn. High yield = your cached context is doing work for you.",
|
|
56
|
+
leverage: "Leverage (Cr/I) measures how much you reuse prior work vs starting fresh. High leverage = you're building on cached results, not re-explaining everything.",
|
|
57
|
+
velocity: "Velocity (O/I) measures how much output you get per token spent. High velocity = you're productive, not just active.",
|
|
58
|
+
},
|
|
59
|
+
class_tiers: [
|
|
60
|
+
{ class: "10xer", meaning: "AI power user archetype — disciplined, system-level reuse, high output per input. Leverage > 10×, high velocity." },
|
|
61
|
+
{ class: "Builder", meaning: "Building momentum — moderate cache reuse, approaching power-user patterns. Growing leverage and velocity." },
|
|
62
|
+
{ class: "Burner", meaning: "Early-stage — tokens burned more than compounded. Low leverage, low velocity. The shift: reuse prior context." },
|
|
63
|
+
],
|
|
64
|
+
link: "https://signalaf.com/score — check your class tier and yield",
|
|
65
|
+
shareable_url: `${DEFAULT_API_BASE}/score`,
|
|
66
|
+
cta: "Learn the scoring",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/diagnose-cascade.mjs — diagnose_cascade tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cascade, parsePillars } from "../analytics/cascade.mjs";
|
|
6
|
+
import { DIAGNOSE_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
7
|
+
import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
|
|
8
|
+
|
|
9
|
+
export const TOOL_DEF = {
|
|
10
|
+
name: "diagnose_cascade",
|
|
11
|
+
description:
|
|
12
|
+
"Analyzes your token cascade and diagnoses where you're leaking efficiency. Takes your 4 pillars (input/output/cacheCreate/cacheRead) and produces a ranked list of efficiency leaks with severity (critical/warning/info), findings, and recommendations. Checks: cache leverage (are you rereading what you wrote?), velocity (are you generating enough output per input?), SNR (is your signal drowning in noise?), cache creation ratio (are you over-committing?), input bloat (is fresh input too high?), and 10xDEV (is the full cascade compounding?). Each finding includes an estimated Υ impact. Pure local math — no network, no submission. Use this BEFORE simulate_change to understand what's wrong, then use simulate_change to test fixes. Accepts the same input formats as rank_paste (JSON or 4 whitespace numbers).",
|
|
13
|
+
annotations: {
|
|
14
|
+
title: "Diagnose cascade breakdown",
|
|
15
|
+
...ANNOTATIONS.readOnlyHint,
|
|
16
|
+
...ANNOTATIONS.idempotentHint,
|
|
17
|
+
...ANNOTATIONS.openWorldHint,
|
|
18
|
+
},
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
text: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description:
|
|
25
|
+
'Token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["text"],
|
|
29
|
+
},
|
|
30
|
+
outputSchema: DIAGNOSE_OUTPUT,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export async function handleDiagnoseCascade(args) {
|
|
34
|
+
if (!args?.text)
|
|
35
|
+
throw new Error(
|
|
36
|
+
"diagnose_cascade requires a non-empty `text` argument (token pillars).",
|
|
37
|
+
);
|
|
38
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
39
|
+
return {
|
|
40
|
+
status: "error",
|
|
41
|
+
reason: "input_too_large",
|
|
42
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const p = parsePillars(args.text);
|
|
47
|
+
const result = withParseWarnings(p, cascade(p));
|
|
48
|
+
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = p;
|
|
49
|
+
const diagnosis = [];
|
|
50
|
+
|
|
51
|
+
// Cache leverage check — the #1 cascade efficiency signal
|
|
52
|
+
const leverage = result.leverage;
|
|
53
|
+
if (leverage !== null) {
|
|
54
|
+
if (leverage < 10) {
|
|
55
|
+
diagnosis.push({
|
|
56
|
+
metric: "cache_leverage",
|
|
57
|
+
severity: "critical",
|
|
58
|
+
finding: `Cache leverage is ${leverage}× — you're reading only ${leverage}× your fresh input from cache. TRANSMITTER-class operators hit 200×+.`,
|
|
59
|
+
recommendation:
|
|
60
|
+
"Increase context reuse: load prior session context, use longer conversation threads, reference earlier outputs.",
|
|
61
|
+
estimated_yield_impact: `+${Math.round((1 - leverage / 50) * 100)}% Υ potential`,
|
|
62
|
+
});
|
|
63
|
+
} else if (leverage < 50) {
|
|
64
|
+
diagnosis.push({
|
|
65
|
+
metric: "cache_leverage",
|
|
66
|
+
severity: "warning",
|
|
67
|
+
finding: `Cache leverage is ${leverage}× — decent but below the ARCH+ threshold (~100×+).`,
|
|
68
|
+
recommendation:
|
|
69
|
+
"Push cache reads higher by reusing prior context more aggressively.",
|
|
70
|
+
estimated_yield_impact: `+${Math.round((1 - leverage / 100) * 50)}% Υ potential`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Velocity check — output per input
|
|
76
|
+
const velocity = result.velocity;
|
|
77
|
+
if (velocity !== null) {
|
|
78
|
+
if (velocity < 0.5) {
|
|
79
|
+
diagnosis.push({
|
|
80
|
+
metric: "velocity",
|
|
81
|
+
severity: "critical",
|
|
82
|
+
finding: `Velocity is ${velocity} — generating only ${velocity}× your input as output. You're reading more than you produce.`,
|
|
83
|
+
recommendation:
|
|
84
|
+
"Increase output: ask the agent to generate more code/text per turn, reduce over-reading.",
|
|
85
|
+
estimated_yield_impact: `+${Math.round((0.5 - velocity) * 100)}% Υ per 0.1 velocity gain`,
|
|
86
|
+
});
|
|
87
|
+
} else if (velocity < 1.0) {
|
|
88
|
+
diagnosis.push({
|
|
89
|
+
metric: "velocity",
|
|
90
|
+
severity: "warning",
|
|
91
|
+
finding: `Velocity is ${velocity} — below 1.0 (output < input). Healthy operators hit 1.5×+.`,
|
|
92
|
+
recommendation:
|
|
93
|
+
"Generate more output per input token — larger edits, more complete responses.",
|
|
94
|
+
estimated_yield_impact: `+${Math.round((1 - velocity) * 30)}% Υ potential`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// SNR check — signal-to-noise
|
|
100
|
+
const snr = result.snr;
|
|
101
|
+
if (snr !== null && snr < 0.3) {
|
|
102
|
+
diagnosis.push({
|
|
103
|
+
metric: "snr",
|
|
104
|
+
severity: "warning",
|
|
105
|
+
finding: `SNR is ${snr} — less than 30% of your token flow is output. Input is dominating.`,
|
|
106
|
+
recommendation:
|
|
107
|
+
"Reduce fresh input (reuse context) or increase output generation.",
|
|
108
|
+
estimated_yield_impact:
|
|
109
|
+
"Indirect — improves both velocity and leverage",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Cache creation ratio — are you over-committing?
|
|
114
|
+
if (cw > 0 && o > 0) {
|
|
115
|
+
const commitRatio = cw / o;
|
|
116
|
+
if (commitRatio > 20) {
|
|
117
|
+
diagnosis.push({
|
|
118
|
+
metric: "cache_creation",
|
|
119
|
+
severity: "info",
|
|
120
|
+
finding: `Cache creation is ${commitRatio.toFixed(1)}× your output — high commitment. This is fine if you're rereading it (check leverage), but wasteful if not.`,
|
|
121
|
+
recommendation:
|
|
122
|
+
"Ensure you're rereading committed context. If leverage is low, you're writing cache you never read.",
|
|
123
|
+
estimated_yield_impact: "Cost reduction, not Υ directly",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Input bloat — is fresh input too high relative to total?
|
|
129
|
+
const total = i + o + cw + cr;
|
|
130
|
+
if (total > 0) {
|
|
131
|
+
const inputPct = (i / total) * 100;
|
|
132
|
+
if (inputPct > 10) {
|
|
133
|
+
diagnosis.push({
|
|
134
|
+
metric: "input_bloat",
|
|
135
|
+
severity: "warning",
|
|
136
|
+
finding: `Fresh input is ${inputPct.toFixed(1)}% of your total token flow — high. Efficient operators keep input under 1% by leaning on cache.`,
|
|
137
|
+
recommendation:
|
|
138
|
+
"Reduce fresh input by reusing prior context instead of re-pasting it.",
|
|
139
|
+
estimated_yield_impact: `+${Math.round((inputPct - 1) * 5)}% Υ potential`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 10xDEV check — is the full cascade compounding?
|
|
145
|
+
if (result.dev10x === null && cw === 0) {
|
|
146
|
+
diagnosis.push({
|
|
147
|
+
metric: "10xdev",
|
|
148
|
+
severity: "critical",
|
|
149
|
+
finding:
|
|
150
|
+
"No cache creation — the cascade cannot compound. You're operating in a non-compounding mode (like ChatGPT without prompt caching).",
|
|
151
|
+
recommendation:
|
|
152
|
+
"Switch to a platform with prompt caching (Claude Code) or enable caching if available.",
|
|
153
|
+
estimated_yield_impact: "Enables the full cascade — potentially 10×+ Υ",
|
|
154
|
+
});
|
|
155
|
+
} else if (result.dev10x !== null && result.dev10x < 1.0) {
|
|
156
|
+
diagnosis.push({
|
|
157
|
+
metric: "10xdev",
|
|
158
|
+
severity: "info",
|
|
159
|
+
finding: `10xDEV is ${result.dev10x} — below 1.0 (BASE threshold). The cascade is compounding but not strongly.`,
|
|
160
|
+
recommendation:
|
|
161
|
+
"Improve both leverage AND velocity — 10xDEV = log10(transmission × commitment × reuse).",
|
|
162
|
+
estimated_yield_impact: "Class tier improvement",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Sort by severity (critical > warning > info)
|
|
167
|
+
const sevOrder = { critical: 0, warning: 1, info: 2 };
|
|
168
|
+
diagnosis.sort((a, b) => sevOrder[a.severity] - sevOrder[b.severity]);
|
|
169
|
+
|
|
170
|
+
const healthScore = diagnosis.filter(
|
|
171
|
+
(d) => d.severity === "critical",
|
|
172
|
+
).length;
|
|
173
|
+
const summary =
|
|
174
|
+
healthScore === 0
|
|
175
|
+
? `Cascade is healthy — Υ ${result.yield}, class ${result.class}. ${diagnosis.length} minor optimizations available.`
|
|
176
|
+
: `Cascade has ${healthScore} critical leak${healthScore > 1 ? "s" : ""} — Υ ${result.yield}, class ${result.class}. Fix the critical items first.`;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
pillars: p,
|
|
180
|
+
cascade: {
|
|
181
|
+
yield_: result.yield,
|
|
182
|
+
snr: result.snr,
|
|
183
|
+
leverage: result.leverage,
|
|
184
|
+
velocity: result.velocity,
|
|
185
|
+
tenx_dev: result.dev10x,
|
|
186
|
+
class: result.class,
|
|
187
|
+
warnings: result.warnings,
|
|
188
|
+
},
|
|
189
|
+
diagnosis,
|
|
190
|
+
summary,
|
|
191
|
+
};
|
|
192
|
+
}
|
package/tools/enroll.mjs
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
|
|
9
|
+
const TERMS_VERSION = "2026-07-21";
|
|
10
|
+
const PRIVACY_VERSION = "2026-07-21";
|
|
11
|
+
|
|
12
|
+
export const TOOL_DEF = {
|
|
13
|
+
name: "enroll",
|
|
14
|
+
description:
|
|
15
|
+
'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.',
|
|
16
|
+
annotations: {
|
|
17
|
+
title: "Enroll device identity",
|
|
18
|
+
...ANNOTATIONS.destructiveHint,
|
|
19
|
+
...ANNOTATIONS.idempotentHint,
|
|
20
|
+
...ANNOTATIONS.openWorldHint,
|
|
21
|
+
},
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
code: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description:
|
|
28
|
+
"the key / connect code (SIGR-XXXXX-XXXXX-XXXXX) from Settings → New key (or Generate connect code)",
|
|
29
|
+
},
|
|
30
|
+
device_label: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description:
|
|
33
|
+
"optional label for this device (default: hostname · agent version)",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ["code"],
|
|
37
|
+
},
|
|
38
|
+
outputSchema: ENROLL_OUTPUT,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export async function handleEnroll(args, ctx) {
|
|
42
|
+
// Redeem a web connect code → bind this device. Generates/loads the local keypair;
|
|
43
|
+
// sends ONLY the public key. operator binding happens server-side from the code row.
|
|
44
|
+
const code = String(args?.code || "").trim();
|
|
45
|
+
if (!code)
|
|
46
|
+
throw new Error(
|
|
47
|
+
"enroll requires a `code` — paste your connect code from signalaf.com → Settings → Connect a device.",
|
|
48
|
+
);
|
|
49
|
+
const id = ctx.opts.identity || ensureIdentity();
|
|
50
|
+
const deviceLabel = String(
|
|
51
|
+
args?.device_label || `${os.hostname()} · ${id.agent_version}`,
|
|
52
|
+
).slice(0, 120);
|
|
53
|
+
const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/devices/enroll`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: {
|
|
56
|
+
"content-type": "application/json",
|
|
57
|
+
accept: "application/json",
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
code,
|
|
61
|
+
device_id: id.device_id,
|
|
62
|
+
public_key: id.public_key,
|
|
63
|
+
device_label: deviceLabel,
|
|
64
|
+
agent_version: id.agent_version,
|
|
65
|
+
consent_acknowledged: true,
|
|
66
|
+
terms_version: TERMS_VERSION,
|
|
67
|
+
privacy_version: PRIVACY_VERSION,
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
let ack;
|
|
71
|
+
try {
|
|
72
|
+
ack = await res.json();
|
|
73
|
+
} catch {
|
|
74
|
+
ack = {};
|
|
75
|
+
}
|
|
76
|
+
if (res.status === 201 && ack.status === "enrolled") {
|
|
77
|
+
// Persist the binding locally (skipped when a test injects opts.identity → no keystore write).
|
|
78
|
+
if (!ctx.opts.identity)
|
|
79
|
+
recordEnrollment({
|
|
80
|
+
codename: ack.codename,
|
|
81
|
+
operator_id: ack.operator_id,
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
status: "enrolled",
|
|
85
|
+
codename: ack.codename ?? null,
|
|
86
|
+
operator_id: ack.operator_id ?? null,
|
|
87
|
+
device_id: id.device_id,
|
|
88
|
+
trust_status: ack.trust_status ?? "trusted",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Recovery: if the server says device_already_enrolled but includes the
|
|
92
|
+
// codename/operator_id, the device IS bound server-side — record it locally
|
|
93
|
+
// and return enrolled instead of erroring. This handles the case where the
|
|
94
|
+
// local binding was lost (partial write, version transition) but the device
|
|
95
|
+
// is still enrolled server-side.
|
|
96
|
+
if (
|
|
97
|
+
ack.reason === "device_already_enrolled" &&
|
|
98
|
+
ack.codename &&
|
|
99
|
+
ack.operator_id
|
|
100
|
+
) {
|
|
101
|
+
if (!ctx.opts.identity)
|
|
102
|
+
recordEnrollment({
|
|
103
|
+
codename: ack.codename,
|
|
104
|
+
operator_id: ack.operator_id,
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
status: "enrolled",
|
|
108
|
+
codename: ack.codename,
|
|
109
|
+
operator_id: ack.operator_id,
|
|
110
|
+
device_id: id.device_id,
|
|
111
|
+
trust_status: ack.trust_status ?? "trusted",
|
|
112
|
+
recovered: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
status: "error",
|
|
117
|
+
httpStatus: res.status,
|
|
118
|
+
reason: ack.reason || ack.status || `http_${res.status}`,
|
|
119
|
+
detail: ack.detail ?? null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
|
|
9
|
+
export const TOOL_DEF = {
|
|
10
|
+
name: "get_best_operator",
|
|
11
|
+
description:
|
|
12
|
+
"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.",
|
|
13
|
+
annotations: { title: "Get best operator", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
n: {
|
|
18
|
+
type: "integer",
|
|
19
|
+
description:
|
|
20
|
+
"Number of top operators to return (default: 5, max: 20). Returns the top N by yield.",
|
|
21
|
+
minimum: 1,
|
|
22
|
+
maximum: 20,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
description:
|
|
26
|
+
"Optional: how many top operators to return. Defaults to 5.",
|
|
27
|
+
},
|
|
28
|
+
outputSchema: BEST_OPERATOR_OUTPUT,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export async function handleGetBestOperator(args, ctx) {
|
|
32
|
+
const rawN = args?.n;
|
|
33
|
+
const n = Math.min(20, Math.max(1, rawN == null ? 5 : Number(rawN)));
|
|
34
|
+
const board = await ctx.fetchJson("/api/v1/leaderboard?metric=yield_");
|
|
35
|
+
const ops = (board.operators || board || []).slice(0, n);
|
|
36
|
+
const total = Array.isArray(board.operators || board)
|
|
37
|
+
? (board.operators || board).length
|
|
38
|
+
: 0;
|
|
39
|
+
|
|
40
|
+
const top = ops.map((op) => ({
|
|
41
|
+
...op,
|
|
42
|
+
behavioral_framing: _behavioralFraming(op),
|
|
43
|
+
competitive: _competitiveLayer(op, board),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const best = top[0];
|
|
47
|
+
const summary = best
|
|
48
|
+
? `${best.codename} tops the SigRank leaderboard at Υ ${best.yield_?.toLocaleString?.() || best.yield_} — ${_behavioralFraming(best)}`
|
|
49
|
+
: "No operators on the board yet.";
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
top_operators: top,
|
|
53
|
+
total_operators: total,
|
|
54
|
+
summary,
|
|
55
|
+
cta: "Check my rank",
|
|
56
|
+
shareable_url: best ? `${DEFAULT_API_BASE}/operator/${encodeURIComponent(best.codename)}` : null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/get-leaderboard.mjs — get_leaderboard tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { LEADERBOARD_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
6
|
+
|
|
7
|
+
export const TOOL_DEF = {
|
|
8
|
+
name: "get_leaderboard",
|
|
9
|
+
description:
|
|
10
|
+
"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 (Burner / Builder / 10xer), 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.",
|
|
11
|
+
annotations: { title: "Get leaderboard", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {},
|
|
15
|
+
description:
|
|
16
|
+
"This tool takes no parameters. It always fetches the full public leaderboard.",
|
|
17
|
+
},
|
|
18
|
+
outputSchema: LEADERBOARD_OUTPUT,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function handleGetLeaderboard(args, ctx) {
|
|
22
|
+
const params = new URLSearchParams({ metric: "yield_" });
|
|
23
|
+
if (args?.limit) params.set("limit", String(args.limit));
|
|
24
|
+
if (args?.window) params.set("window", args.window);
|
|
25
|
+
return ctx.fetchJson(`/api/v1/leaderboard?${params}`);
|
|
26
|
+
}
|
|
@@ -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 (Burner / Builder / 10xer), 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
|
+
}
|