sigrank 0.19.0 → 1.0.2
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 +158 -0
- package/MCP_SERVER_DESCRIPTION.md +172 -0
- package/README.md +89 -41
- package/adapters/index.mjs +1100 -0
- package/adapters/tokenpull.mjs +568 -0
- package/adapters.mjs +1 -865
- package/analytics/cascade.mjs +338 -0
- package/bin/sigrank.mjs +137 -0
- package/cascade.mjs +1 -274
- package/cli.mjs +1 -1853
- package/connect.mjs +1 -13
- package/identity/connect.mjs +13 -0
- package/identity/keystore.mjs +231 -0
- package/identity/sign.mjs +106 -0
- package/index.mjs +47 -162
- package/keystore.mjs +1 -242
- package/lib/constants.mjs +137 -0
- package/lib/pkg-version.mjs +59 -0
- package/manifest.json +46 -0
- package/narrate.mjs +1 -77
- package/package.json +44 -18
- package/preflight.mjs +1 -191
- package/presentation/cli.mjs +1926 -0
- package/presentation/narrate.mjs +219 -0
- package/presentation/tui-themes.mjs +189 -0
- package/presentation/tui.mjs +3316 -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/proxy.mjs +492 -0
- package/resources/class-tiers.md +81 -0
- package/resources/data-policy.md +31 -0
- package/resources/install-guide.md +59 -0
- package/resources/privacy-model.md +44 -0
- package/resources/scoring-formula.md +24 -0
- package/review.mjs +263 -0
- package/sign.mjs +1 -106
- package/submit/index.mjs +297 -0
- package/submit/preflight.mjs +191 -0
- package/submit.mjs +1 -303
- package/tokenpull.mjs +1 -576
- package/tokscale_analytics.mjs +864 -0
- package/tools/_framing.mjs +217 -0
- package/tools/_helpers.mjs +253 -0
- package/tools/_peer-matching.mjs +361 -0
- package/tools/_report.mjs +262 -0
- package/tools/_schemas.mjs +492 -0
- package/tools/_verifiers.mjs +148 -0
- package/tools/compare-operators.mjs +80 -0
- package/tools/compare-self.mjs +124 -0
- package/tools/describe-power-user.mjs +78 -0
- package/tools/diagnose-cascade.mjs +192 -0
- package/tools/discover-peers.mjs +198 -0
- package/tools/enroll.mjs +119 -0
- package/tools/get-best-operator.mjs +59 -0
- package/tools/get-leaderboard.mjs +27 -0
- package/tools/get-operator.mjs +33 -0
- package/tools/index.mjs +233 -0
- package/tools/optimize-efficiency.mjs +170 -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/standard-record.mjs +108 -0
- package/tools/submit-paste.mjs +121 -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 +235 -0
- package/tools/tokenpull.mjs +113 -0
- package/tools/tokscale-breakdown.mjs +119 -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 -3897
- package/tui.mjs +4 -2650
- package/banner.svg +0 -7
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/simulate-change.mjs — simulate_change tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cascade, parsePillars } from "../analytics/cascade.mjs";
|
|
6
|
+
import { SIMULATE_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
7
|
+
import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
|
|
8
|
+
|
|
9
|
+
export const TOOL_DEF = {
|
|
10
|
+
name: "simulate_change",
|
|
11
|
+
description:
|
|
12
|
+
"The first PRESCRIPTIVE SigRank tool — 'what if I changed my token mix?' Takes your current 4 pillars (input/output/cacheCreate/cacheRead) and one or more proposed changes, runs the canonical cascade on BOTH the current and simulated values, and returns the exact Υ Yield delta, class change, and per-metric diffs. This is the 'show me the payoff before I do the work' primitive: no network, no submission, pure local math. Use it to answer 'would increasing my cache-read by 50k tokens actually move my class?' before you change your workflow. Accepts the current pillars as JSON or 4 numbers (same as rank_paste) plus a `changes` object with any of the 4 pillar names mapped to new absolute values OR relative deltas (e.g. {cacheRead: '+50000'} or {input: 800000}).",
|
|
13
|
+
annotations: {
|
|
14
|
+
title: "Simulate metric change",
|
|
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
|
+
'Current token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
26
|
+
},
|
|
27
|
+
changes: {
|
|
28
|
+
type: "object",
|
|
29
|
+
description:
|
|
30
|
+
"Proposed changes to apply. Keys: input, output, cacheCreate, cacheRead. Values are either absolute numbers (replace) or strings starting with +/- for relative deltas (add/subtract). Omitted pillars are unchanged.",
|
|
31
|
+
properties: {
|
|
32
|
+
input: {
|
|
33
|
+
type: ["number", "string"],
|
|
34
|
+
description:
|
|
35
|
+
'new input token count (absolute) or "+/-N" for a relative delta',
|
|
36
|
+
},
|
|
37
|
+
output: {
|
|
38
|
+
type: ["number", "string"],
|
|
39
|
+
description:
|
|
40
|
+
'new output token count (absolute) or "+/-N" for a relative delta',
|
|
41
|
+
},
|
|
42
|
+
cacheCreate: {
|
|
43
|
+
type: ["number", "string"],
|
|
44
|
+
description:
|
|
45
|
+
'new cache-create token count (absolute) or "+/-N" for a relative delta',
|
|
46
|
+
},
|
|
47
|
+
cacheRead: {
|
|
48
|
+
type: ["number", "string"],
|
|
49
|
+
description:
|
|
50
|
+
'new cache-read token count (absolute) or "+/-N" for a relative delta',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
required: ["text", "changes"],
|
|
56
|
+
},
|
|
57
|
+
outputSchema: SIMULATE_OUTPUT,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export async function handleSimulateChange(args) {
|
|
61
|
+
// The first prescriptive tool — "what if I changed my token mix?"
|
|
62
|
+
// Pure local math: parse current pillars, apply proposed changes, run the
|
|
63
|
+
// cascade on both, return the delta. No network, no submission.
|
|
64
|
+
if (!args?.text)
|
|
65
|
+
throw new Error(
|
|
66
|
+
"simulate_change requires a non-empty `text` argument (current pillars).",
|
|
67
|
+
);
|
|
68
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
69
|
+
return {
|
|
70
|
+
status: "error",
|
|
71
|
+
reason: "input_too_large",
|
|
72
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (!args?.changes || typeof args.changes !== "object") {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"simulate_change requires a `changes` object with at least one pillar change.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const currentPillars = parsePillars(args.text);
|
|
82
|
+
const current = withParseWarnings(currentPillars, cascade(currentPillars));
|
|
83
|
+
|
|
84
|
+
// Apply changes: each pillar is either an absolute number (replace) or a
|
|
85
|
+
// string starting with +/- (relative delta). Omitted pillars are unchanged.
|
|
86
|
+
const PILLAR_KEYS = ["input", "output", "cacheCreate", "cacheRead"];
|
|
87
|
+
const simulated = { ...currentPillars };
|
|
88
|
+
const appliedChanges = {};
|
|
89
|
+
|
|
90
|
+
for (const key of PILLAR_KEYS) {
|
|
91
|
+
if (args.changes[key] == null) continue;
|
|
92
|
+
const raw = args.changes[key];
|
|
93
|
+
let newVal;
|
|
94
|
+
|
|
95
|
+
if (typeof raw === "number") {
|
|
96
|
+
newVal = raw;
|
|
97
|
+
} else if (typeof raw === "string") {
|
|
98
|
+
const trimmed = raw.trim();
|
|
99
|
+
if (trimmed.startsWith("+") || trimmed.startsWith("-")) {
|
|
100
|
+
// Relative delta: add/subtract from current value
|
|
101
|
+
const delta = Number(trimmed);
|
|
102
|
+
if (!Number.isFinite(delta)) {
|
|
103
|
+
return {
|
|
104
|
+
status: "error",
|
|
105
|
+
reason: "invalid_change",
|
|
106
|
+
detail: `changes.${key}: "${raw}" is not a valid relative delta.`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
newVal = currentPillars[key] + delta;
|
|
110
|
+
} else {
|
|
111
|
+
// Absolute value as a string
|
|
112
|
+
newVal = Number(trimmed);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
return {
|
|
116
|
+
status: "error",
|
|
117
|
+
reason: "invalid_change",
|
|
118
|
+
detail: `changes.${key}: expected number or string, got ${typeof raw}.`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!Number.isFinite(newVal)) {
|
|
123
|
+
return {
|
|
124
|
+
status: "error",
|
|
125
|
+
reason: "invalid_change",
|
|
126
|
+
detail: `changes.${key}: result is not a finite number.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Clamp to non-negative — token counts can't be negative
|
|
130
|
+
if (newVal < 0) {
|
|
131
|
+
return {
|
|
132
|
+
status: "error",
|
|
133
|
+
reason: "invalid_change",
|
|
134
|
+
detail: `changes.${key}: result ${newVal} is negative — token counts must be >= 0.`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
simulated[key] = newVal;
|
|
139
|
+
appliedChanges[key] = {
|
|
140
|
+
from: currentPillars[key],
|
|
141
|
+
to: newVal,
|
|
142
|
+
delta: newVal - currentPillars[key],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (Object.keys(appliedChanges).length === 0) {
|
|
147
|
+
return {
|
|
148
|
+
status: "error",
|
|
149
|
+
reason: "no_changes",
|
|
150
|
+
detail: "No pillar changes specified in the `changes` object.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const simulatedResult = cascade(simulated);
|
|
155
|
+
|
|
156
|
+
// Compute deltas for every cascade metric
|
|
157
|
+
const metricDelta = (curr, sim) => {
|
|
158
|
+
if (curr == null && sim == null) return null;
|
|
159
|
+
if (curr == null) return { from: null, to: sim, delta: null };
|
|
160
|
+
if (sim == null) return { from: curr, to: null, delta: null };
|
|
161
|
+
return { from: curr, to: sim, delta: Number((sim - curr).toFixed(4)) };
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const classChanged = current.class !== simulatedResult.class;
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
current: {
|
|
168
|
+
pillars: currentPillars,
|
|
169
|
+
yield: current.yield,
|
|
170
|
+
snr: current.snr,
|
|
171
|
+
leverage: current.leverage,
|
|
172
|
+
velocity: current.velocity,
|
|
173
|
+
dev10x: current.dev10x,
|
|
174
|
+
class: current.class,
|
|
175
|
+
},
|
|
176
|
+
simulated: {
|
|
177
|
+
pillars: {
|
|
178
|
+
input: simulated.input,
|
|
179
|
+
output: simulated.output,
|
|
180
|
+
cacheCreate: simulated.cacheCreate,
|
|
181
|
+
cacheRead: simulated.cacheRead,
|
|
182
|
+
},
|
|
183
|
+
yield: simulatedResult.yield,
|
|
184
|
+
snr: simulatedResult.snr,
|
|
185
|
+
leverage: simulatedResult.leverage,
|
|
186
|
+
velocity: simulatedResult.velocity,
|
|
187
|
+
dev10x: simulatedResult.dev10x,
|
|
188
|
+
class: simulatedResult.class,
|
|
189
|
+
},
|
|
190
|
+
changes: appliedChanges,
|
|
191
|
+
deltas: {
|
|
192
|
+
yield: metricDelta(current.yield, simulatedResult.yield),
|
|
193
|
+
snr: metricDelta(current.snr, simulatedResult.snr),
|
|
194
|
+
leverage: metricDelta(current.leverage, simulatedResult.leverage),
|
|
195
|
+
velocity: metricDelta(current.velocity, simulatedResult.velocity),
|
|
196
|
+
dev10x: metricDelta(current.dev10x, simulatedResult.dev10x),
|
|
197
|
+
},
|
|
198
|
+
class_changed: classChanged,
|
|
199
|
+
...(classChanged
|
|
200
|
+
? { class_transition: `${current.class} → ${simulatedResult.class}` }
|
|
201
|
+
: {}),
|
|
202
|
+
...(simulatedResult.warnings
|
|
203
|
+
? { simulated_warnings: simulatedResult.warnings }
|
|
204
|
+
: {}),
|
|
205
|
+
note: "Local simulation only — no submission. The actual score depends on server-side RS.xx weights and class thresholds.",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { cascade } from "../analytics/cascade.mjs";
|
|
2
|
+
import { ANNOTATIONS } from "./_schemas.mjs";
|
|
3
|
+
|
|
4
|
+
export const SIGRANK_STANDARD_VERSION = "sigrank/0.1-draft";
|
|
5
|
+
|
|
6
|
+
export const TOOL_DEF = {
|
|
7
|
+
name: "get_sigrank_standard_record",
|
|
8
|
+
description:
|
|
9
|
+
"Build a SigRank Standard v0.1-draft portable operator record from four token pillars. Computes the canonical cascade locally through @sigrank/cascade and returns versioned telemetry plus the normative core metrics: Yield, Leverage, Velocity, SNR, and 10xDEV. No data is submitted or persisted. The core record contains token telemetry only and does not require prompt text, code, or response content.",
|
|
10
|
+
annotations: {
|
|
11
|
+
title: "Export SigRank standard record",
|
|
12
|
+
...ANNOTATIONS.readOnlyHint,
|
|
13
|
+
...ANNOTATIONS.destructiveHint,
|
|
14
|
+
...ANNOTATIONS.idempotentHint,
|
|
15
|
+
...ANNOTATIONS.openWorldHint,
|
|
16
|
+
},
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
required: ["input", "output", "cache_write", "cache_read"],
|
|
21
|
+
properties: {
|
|
22
|
+
input: { type: "number", minimum: 0, description: "Fresh input tokens." },
|
|
23
|
+
output: { type: "number", minimum: 0, description: "Output tokens." },
|
|
24
|
+
cache_write: { type: "number", minimum: 0, description: "Cache-write / cache-creation tokens." },
|
|
25
|
+
cache_read: { type: "number", minimum: 0, description: "Cache-read tokens." },
|
|
26
|
+
provider: { type: "string", description: "Optional provider identifier." },
|
|
27
|
+
model: { type: "string", description: "Optional model identifier." },
|
|
28
|
+
tool: { type: "string", description: "Optional tool/client identifier." },
|
|
29
|
+
timestamp: { type: "string", description: "Optional ISO-8601 timestamp. Defaults to the current time." }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
outputSchema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
required: ["spec", "timestamp", "source", "telemetry", "metrics"],
|
|
35
|
+
properties: {
|
|
36
|
+
spec: { type: "string", description: "SigRank specification version." },
|
|
37
|
+
timestamp: { type: "string", description: "ISO-8601 record timestamp." },
|
|
38
|
+
source: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
provider: { type: "string" },
|
|
42
|
+
model: { type: "string" },
|
|
43
|
+
tool: { type: "string" }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
telemetry: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
input: { type: "number" },
|
|
50
|
+
output: { type: "number" },
|
|
51
|
+
cache_write: { type: "number" },
|
|
52
|
+
cache_read: { type: "number" }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
metrics: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
yield: { type: ["number", "null"] },
|
|
59
|
+
leverage: { type: ["number", "null"] },
|
|
60
|
+
velocity: { type: ["number", "null"] },
|
|
61
|
+
snr: { type: ["number", "null"] },
|
|
62
|
+
dev10x: { type: ["number", "null"] }
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
warnings: { type: "array", items: { type: "string" } }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export async function handleGetSigRankStandardRecord(args) {
|
|
71
|
+
const input = Number(args?.input);
|
|
72
|
+
const output = Number(args?.output);
|
|
73
|
+
const cacheWrite = Number(args?.cache_write);
|
|
74
|
+
const cacheRead = Number(args?.cache_read);
|
|
75
|
+
|
|
76
|
+
if (![input, output, cacheWrite, cacheRead].every(Number.isFinite)) {
|
|
77
|
+
throw new Error("get_sigrank_standard_record requires four finite numeric token pillars.");
|
|
78
|
+
}
|
|
79
|
+
if ([input, output, cacheWrite, cacheRead].some((v) => v < 0)) {
|
|
80
|
+
throw new Error("get_sigrank_standard_record requires non-negative token pillars.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const c = cascade({ input, output, cacheCreate: cacheWrite, cacheRead });
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
spec: SIGRANK_STANDARD_VERSION,
|
|
87
|
+
timestamp: args?.timestamp || new Date().toISOString(),
|
|
88
|
+
source: {
|
|
89
|
+
provider: args?.provider || "unknown",
|
|
90
|
+
model: args?.model || "unknown",
|
|
91
|
+
tool: args?.tool || "sigrank-mcp",
|
|
92
|
+
},
|
|
93
|
+
telemetry: {
|
|
94
|
+
input,
|
|
95
|
+
output,
|
|
96
|
+
cache_write: cacheWrite,
|
|
97
|
+
cache_read: cacheRead,
|
|
98
|
+
},
|
|
99
|
+
metrics: {
|
|
100
|
+
yield: c.yield,
|
|
101
|
+
leverage: c.leverage,
|
|
102
|
+
velocity: c.velocity,
|
|
103
|
+
snr: c.snr,
|
|
104
|
+
dev10x: c.dev10x,
|
|
105
|
+
},
|
|
106
|
+
warnings: c.warnings || [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/submit-paste.mjs — submit_paste tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cascade, parsePillars } from "../analytics/cascade.mjs";
|
|
6
|
+
import { narrate } from "../presentation/narrate.mjs";
|
|
7
|
+
import { SUBMIT_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
8
|
+
import {
|
|
9
|
+
MAX_INPUT,
|
|
10
|
+
uploadStamp,
|
|
11
|
+
withParseWarnings,
|
|
12
|
+
} from "./_helpers.mjs";
|
|
13
|
+
import { TERMS_VERSION, PRIVACY_VERSION, isRankedAck } from "../lib/constants.mjs";
|
|
14
|
+
|
|
15
|
+
export const TOOL_DEF = {
|
|
16
|
+
name: "submit_paste",
|
|
17
|
+
description:
|
|
18
|
+
"Ranks a paste of token counts AND publishes it to the live SigRank board at signalaf.com in one call. First computes the cascade locally for an instant preview (yield, leverage, velocity, class, card), then submits the raw paste to the board's web-paste endpoint, which re-parses and re-scores it server-side. The server score is authoritative — it may differ from the local preview if the board applies additional validation. Returns both the local preview and the server response (including the operator's new rank if accepted). A codename is required to publish — omit it for a local preview only (no board submission). Token-only, no auth required. Use this when you have token counts from ccusage or a dashboard and want to both see your score and publish it. Do NOT use this if you want to pull your local usage automatically — use tokenpull_submit for the zero-paste flow. Do NOT use this for multi-window dashboard pastes — use rank_windows to rank them first, then submit each window. After calling this, use get_operator with your codename to verify your submission appeared on the board.",
|
|
19
|
+
annotations: {
|
|
20
|
+
title: "Submit paste to board",
|
|
21
|
+
...ANNOTATIONS.destructiveHint,
|
|
22
|
+
...ANNOTATIONS.idempotentHint,
|
|
23
|
+
...ANNOTATIONS.openWorldHint,
|
|
24
|
+
},
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
text: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description:
|
|
31
|
+
'Token counts to rank and submit. Two formats: (1) JSON {"input":N,"output":N,"cacheCreate":N,"cacheRead":N} from ccusage (preferred — the board parses this reliably), or (2) four whitespace-separated numbers: input output cacheCreate cacheRead. The 4-number form ranks locally but the board may reject it. Example: {"input":1000000,"output":500000,"cacheCreate":50000,"cacheRead":800000}',
|
|
32
|
+
},
|
|
33
|
+
codename: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description:
|
|
36
|
+
'Operator codename to publish under on the leaderboard (e.g. "Ghost Falcon"). Required to submit — omit for local preview only (no board submission, just returns the local cascade result). Must be a non-empty string.',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["text"],
|
|
40
|
+
description:
|
|
41
|
+
"Requires token counts (text). Codename is optional but required for board submission — omit it for preview-only mode.",
|
|
42
|
+
},
|
|
43
|
+
outputSchema: SUBMIT_OUTPUT,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export async function handleSubmitPaste(args, ctx) {
|
|
47
|
+
if (!args?.text)
|
|
48
|
+
throw new Error("submit_paste requires a non-empty `text` argument.");
|
|
49
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
50
|
+
return {
|
|
51
|
+
status: "error",
|
|
52
|
+
reason: "input_too_large",
|
|
53
|
+
detail: `text exceeds ${MAX_INPUT} chars (${args.text.length}). Paste only the token-count table, not full output.`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Local preview first — also validates the paste is parseable before any POST.
|
|
57
|
+
const pillars = parsePillars(args.text);
|
|
58
|
+
const c = withParseWarnings(pillars, cascade(pillars));
|
|
59
|
+
const codename = String(args?.codename || "").trim();
|
|
60
|
+
const card = narrate(c, codename || "This operator");
|
|
61
|
+
|
|
62
|
+
// No codename → cannot publish (the board endpoint requires it). Fail fast at the
|
|
63
|
+
// tool boundary with a clear message instead of an opaque server 400.
|
|
64
|
+
if (!codename) {
|
|
65
|
+
return {
|
|
66
|
+
...c,
|
|
67
|
+
card,
|
|
68
|
+
submission: {
|
|
69
|
+
status: "not_submitted",
|
|
70
|
+
reason: "codename_required",
|
|
71
|
+
detail:
|
|
72
|
+
"Pass a codename to publish to the board. Showing local preview only.",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Privacy: send ONLY the 4 parsed token numbers (canonical "input output
|
|
78
|
+
// cacheCreate cacheRead" form), never the raw user text. parsePillars above
|
|
79
|
+
// already validated the input — we reconstruct from the parsed pillars so
|
|
80
|
+
// no conversation content or prose can leak to the server. The server's
|
|
81
|
+
// ingestMeta() re-parses these 4 numbers authoritatively (Strategy 3:
|
|
82
|
+
// four bare numbers). This makes every submit path — signed and paste —
|
|
83
|
+
// send only token counts.
|
|
84
|
+
const canonicalPaste = `${pillars.input} ${pillars.output} ${pillars.cacheCreate} ${pillars.cacheRead}`;
|
|
85
|
+
const stamp = uploadStamp({
|
|
86
|
+
codename,
|
|
87
|
+
pillars: c.pillars,
|
|
88
|
+
source: "web_paste",
|
|
89
|
+
});
|
|
90
|
+
const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/ingest-paste`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
"content-type": "application/json",
|
|
94
|
+
accept: "application/json",
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
codename,
|
|
98
|
+
raw_paste: canonicalPaste,
|
|
99
|
+
consent_acknowledged: true,
|
|
100
|
+
terms_version: TERMS_VERSION,
|
|
101
|
+
privacy_version: PRIVACY_VERSION,
|
|
102
|
+
...stamp,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
let ack;
|
|
106
|
+
try {
|
|
107
|
+
ack = await res.json();
|
|
108
|
+
} catch {
|
|
109
|
+
ack = {
|
|
110
|
+
status: "error",
|
|
111
|
+
detail: `HTTP ${res.status} (non-JSON response)`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const ranked = isRankedAck(res, ack);
|
|
115
|
+
return {
|
|
116
|
+
...c,
|
|
117
|
+
card,
|
|
118
|
+
ranked,
|
|
119
|
+
submission: { ...stamp, httpStatus: res.status, ranked, ...ack },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/submit-verified.mjs — submit_verified tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ALL_PLATFORMS } from "../adapters/index.mjs";
|
|
6
|
+
import { ensureIdentity } from "../identity/keystore.mjs";
|
|
7
|
+
import { submitSignedWindow } from "../submit/index.mjs";
|
|
8
|
+
import { SUBMIT_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
9
|
+
import { pullByPlatform, WINDOW_TYPE } from "./_helpers.mjs";
|
|
10
|
+
import { _computeReportBlock } from "./_report.mjs";
|
|
11
|
+
|
|
12
|
+
export const TOOL_DEF = {
|
|
13
|
+
name: "submit_verified",
|
|
14
|
+
description:
|
|
15
|
+
"Publish your LOCAL token runs to the SigRank board as a VERIFIED operator — the enrolled, signed path. Reads your pillars (tokenpull), builds the canonical Schema 1.0 snapshot per window, ed25519-signs it with your device key, and POSTs to /api/v1/snapshots. Requires `npx sigrank-mcp enroll` first (a bound device). Only signed submissions from a trusted device rank on the board. Token-only; the private key never leaves your machine. Pass dry_run:true to inspect the exact signed payload without publishing.",
|
|
16
|
+
annotations: {
|
|
17
|
+
title: "Submit verified score",
|
|
18
|
+
...ANNOTATIONS.destructiveHint,
|
|
19
|
+
...ANNOTATIONS.idempotentHint,
|
|
20
|
+
...ANNOTATIONS.openWorldHint,
|
|
21
|
+
},
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
window: {
|
|
26
|
+
type: "string",
|
|
27
|
+
enum: ["7d", "30d", "90d", "all"],
|
|
28
|
+
description: "submit only this window (default: all 4)",
|
|
29
|
+
},
|
|
30
|
+
platform: {
|
|
31
|
+
type: "string",
|
|
32
|
+
enum: [...ALL_PLATFORMS, "multi"],
|
|
33
|
+
description:
|
|
34
|
+
"source platform (default: claude). 'multi' = combined cascade summed across all locally-detected platforms (needs 2+ active); empty windows are skipped.",
|
|
35
|
+
},
|
|
36
|
+
dry_run: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description:
|
|
39
|
+
"build + sign but do NOT publish — returns the exact payload that would be POSTed (token counts only), so you can inspect before submitting",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
outputSchema: SUBMIT_OUTPUT,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export async function handleSubmitVerified(args, ctx) {
|
|
47
|
+
// The enrolled, signed publish path → /api/v1/snapshots (only verified rows rank).
|
|
48
|
+
const id = ctx.opts.identity || ensureIdentity();
|
|
49
|
+
if (!id.codename || !id.operator_id) {
|
|
50
|
+
return {
|
|
51
|
+
status: "not_enrolled",
|
|
52
|
+
detail: "Run `npx sigrank-mcp enroll` to bind this device first.",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const platform = args?.platform || "claude";
|
|
56
|
+
|
|
57
|
+
// MULTI: the combined cross-platform cascade. The dashboard already SUMS every
|
|
58
|
+
// active platform's pillars (a "claude+codex" row) but never submitted it — this
|
|
59
|
+
// is that missing submission. Aggregate every locally-detected platform's pillars
|
|
60
|
+
// per window and publish as platform='multi' = the operator's TOTAL usage. Empty
|
|
61
|
+
// windows are skipped so a no-activity window never lands as a degenerate row.
|
|
62
|
+
if (platform === "multi") {
|
|
63
|
+
const detected = [];
|
|
64
|
+
for (const p of ALL_PLATFORMS) {
|
|
65
|
+
const r = await pullByPlatform(p, ctx.opts).catch(() => null);
|
|
66
|
+
const live =
|
|
67
|
+
r &&
|
|
68
|
+
(r.windows || []).some(
|
|
69
|
+
(w) =>
|
|
70
|
+
w.pillars.input +
|
|
71
|
+
w.pillars.output +
|
|
72
|
+
w.pillars.cacheCreate +
|
|
73
|
+
w.pillars.cacheRead >
|
|
74
|
+
0,
|
|
75
|
+
);
|
|
76
|
+
if (live) detected.push(r);
|
|
77
|
+
}
|
|
78
|
+
if (detected.length < 2) {
|
|
79
|
+
return {
|
|
80
|
+
platform: "multi",
|
|
81
|
+
codename: id.codename,
|
|
82
|
+
operator_id: id.operator_id,
|
|
83
|
+
status: "skipped",
|
|
84
|
+
reason: "need_2_platforms",
|
|
85
|
+
detail: `multi needs 2+ active platforms; found ${detected.length} (${detected.map((d) => d.platform).join(", ") || "none"}).`,
|
|
86
|
+
windows: [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const winKeys = args?.window
|
|
90
|
+
? [args.window]
|
|
91
|
+
: ["7d", "30d", "90d", "all"];
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const wk of winKeys) {
|
|
94
|
+
const sum = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
95
|
+
let msgs = 0;
|
|
96
|
+
for (const d of detected) {
|
|
97
|
+
const w = (d.windows || []).find((x) => x.window === wk);
|
|
98
|
+
if (!w) continue;
|
|
99
|
+
sum.input += w.pillars.input || 0;
|
|
100
|
+
sum.output += w.pillars.output || 0;
|
|
101
|
+
sum.cacheCreate += w.pillars.cacheCreate || 0;
|
|
102
|
+
sum.cacheRead += w.pillars.cacheRead || 0;
|
|
103
|
+
msgs += w.messages || 0;
|
|
104
|
+
}
|
|
105
|
+
if (sum.input + sum.output + sum.cacheCreate + sum.cacheRead <= 0)
|
|
106
|
+
continue; // skip empty window
|
|
107
|
+
const report = _computeReportBlock(sum);
|
|
108
|
+
const r = await submitSignedWindow(wk, sum, msgs, id, {
|
|
109
|
+
apiBase: ctx.apiBase,
|
|
110
|
+
fetchImpl: ctx.doFetch,
|
|
111
|
+
platform: "multi",
|
|
112
|
+
now: ctx.opts.now,
|
|
113
|
+
dryRun: !!args?.dry_run,
|
|
114
|
+
report,
|
|
115
|
+
});
|
|
116
|
+
out.push({ window: wk, pillars: sum, ...r });
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
platform: "multi",
|
|
120
|
+
codename: id.codename,
|
|
121
|
+
operator_id: id.operator_id,
|
|
122
|
+
sources: detected.map((d) => d.platform),
|
|
123
|
+
windows: out,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const pulled = await pullByPlatform(platform, ctx.opts);
|
|
128
|
+
const targets = args?.window
|
|
129
|
+
? pulled.windows.filter((w) => w.window === args.window)
|
|
130
|
+
: pulled.windows;
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const w of targets) {
|
|
133
|
+
// Compute the cascade report block (mode + badges + health) for this window.
|
|
134
|
+
// Pure math, computed locally, submitted alongside the 4 token pillars.
|
|
135
|
+
const report = _computeReportBlock(w.pillars);
|
|
136
|
+
const r = await submitSignedWindow(w.window, w.pillars, w.messages, id, {
|
|
137
|
+
apiBase: ctx.apiBase,
|
|
138
|
+
fetchImpl: ctx.doFetch,
|
|
139
|
+
platform: pulled.platform,
|
|
140
|
+
now: ctx.opts.now,
|
|
141
|
+
dryRun: !!args?.dry_run,
|
|
142
|
+
report,
|
|
143
|
+
});
|
|
144
|
+
out.push({ window: w.window, pillars: w.pillars, ...r });
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
platform: pulled.platform,
|
|
148
|
+
codename: id.codename,
|
|
149
|
+
operator_id: id.operator_id,
|
|
150
|
+
generatedAt: pulled.generatedAt,
|
|
151
|
+
windows: out,
|
|
152
|
+
};
|
|
153
|
+
}
|