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.
- package/CHANGELOG.md +158 -0
- package/MCP_SERVER_DESCRIPTION.md +172 -0
- package/README.md +94 -41
- package/adapters/index.mjs +1100 -0
- package/adapters/tokenpull.mjs +568 -0
- package/adapters.mjs +1 -857
- package/analytics/cascade.mjs +338 -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 +41 -16
- 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 +229 -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/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
package/keystore.mjs
CHANGED
|
@@ -1,242 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Persists a per-device keypair at ~/.sigrank-mcp/identity.json (dir 0700, file 0600).
|
|
4
|
-
// The PRIVATE key never leaves this machine; only the raw 32-byte public key (as
|
|
5
|
-
// "ed25519:<base64>") is sent to the server at enroll time. The device_id (uuid) is
|
|
6
|
-
// generated once and is the immutable binding target server-side.
|
|
7
|
-
//
|
|
8
|
-
// Public-key export MUST match the server's publicKeyFromAgent: generateKeyPairSync
|
|
9
|
-
// exports SPKI DER with a 12-byte prefix; we strip it to the raw 32 bytes, base64 it,
|
|
10
|
-
// and prefix "ed25519:". The server re-adds the SPKI prefix to verify. Round-trip is
|
|
11
|
-
// locked by sign.test.mjs + the canon-parity fixture.
|
|
12
|
-
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import {
|
|
16
|
-
mkdirSync,
|
|
17
|
-
readFileSync,
|
|
18
|
-
writeFileSync,
|
|
19
|
-
existsSync,
|
|
20
|
-
chmodSync,
|
|
21
|
-
unlinkSync,
|
|
22
|
-
readdirSync,
|
|
23
|
-
copyFileSync,
|
|
24
|
-
} from "node:fs";
|
|
25
|
-
import { generateKeyPairSync, randomUUID } from "node:crypto";
|
|
26
|
-
|
|
27
|
-
const DIR = join(homedir(), ".sigrank-mcp");
|
|
28
|
-
const PATH = join(DIR, "identity.json");
|
|
29
|
-
const BACKUP_PREFIX = "identity.json.bak-";
|
|
30
|
-
const MAX_BACKUPS = 5;
|
|
31
|
-
|
|
32
|
-
/** SPKI DER prefix length for an ed25519 public key (12 bytes before the raw 32). */
|
|
33
|
-
const SPKI_PREFIX_LEN = 12;
|
|
34
|
-
|
|
35
|
-
/** Resolve this package's version for the agent_version stamp (best-effort). */
|
|
36
|
-
function agentVersion() {
|
|
37
|
-
try {
|
|
38
|
-
const pkg = JSON.parse(
|
|
39
|
-
readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
|
|
40
|
-
);
|
|
41
|
-
return `sigrank-mcp/${pkg.version}`;
|
|
42
|
-
} catch {
|
|
43
|
-
return "sigrank-mcp";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function keystorePath() {
|
|
48
|
-
return PATH;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Read the stored identity, or null if absent/corrupt. */
|
|
52
|
-
export function loadIdentity() {
|
|
53
|
-
if (!existsSync(PATH)) return null;
|
|
54
|
-
try {
|
|
55
|
-
return JSON.parse(readFileSync(PATH, "utf-8"));
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Write the identity with strict perms (dir 0700, file 0600). Backs up the existing
|
|
62
|
-
* file before overwriting so a corrupt write or accidental reset can be recovered. */
|
|
63
|
-
export function persistIdentity(identity) {
|
|
64
|
-
mkdirSync(DIR, { recursive: true });
|
|
65
|
-
try {
|
|
66
|
-
chmodSync(DIR, 0o700);
|
|
67
|
-
} catch {
|
|
68
|
-
/* best-effort on platforms without chmod */
|
|
69
|
-
}
|
|
70
|
-
// Back up the existing identity before overwriting (resilience: if the write
|
|
71
|
-
// fails or the file is later lost, the backup carries the binding + keys).
|
|
72
|
-
if (existsSync(PATH)) {
|
|
73
|
-
try {
|
|
74
|
-
const stamp = Date.now();
|
|
75
|
-
copyFileSync(PATH, join(DIR, `${BACKUP_PREFIX}${stamp}`));
|
|
76
|
-
pruneBackups();
|
|
77
|
-
} catch {
|
|
78
|
-
/* best-effort — don't block the write */
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
writeFileSync(PATH, `${JSON.stringify(identity, null, 2)}\n`, {
|
|
82
|
-
mode: 0o600,
|
|
83
|
-
});
|
|
84
|
-
try {
|
|
85
|
-
chmodSync(PATH, 0o600);
|
|
86
|
-
} catch {
|
|
87
|
-
/* best-effort */
|
|
88
|
-
}
|
|
89
|
-
return identity;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Build a fresh keypair record (pure — no fs). Exposed for tests. */
|
|
93
|
-
export function generateIdentity({ device_id } = {}) {
|
|
94
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
95
|
-
const spki = publicKey.export({ type: "spki", format: "der" });
|
|
96
|
-
const rawPub = Buffer.from(spki).subarray(SPKI_PREFIX_LEN); // the raw 32 bytes
|
|
97
|
-
const pkcs8 = privateKey.export({ type: "pkcs8", format: "der" });
|
|
98
|
-
return {
|
|
99
|
-
device_id: device_id || randomUUID(),
|
|
100
|
-
codename: null,
|
|
101
|
-
operator_id: null,
|
|
102
|
-
public_key: `ed25519:${rawPub.toString("base64")}`,
|
|
103
|
-
private_key_pkcs8_b64: Buffer.from(pkcs8).toString("base64"),
|
|
104
|
-
agent_version: agentVersion(),
|
|
105
|
-
enrolled_at: null,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Decide the binding (codename/operator_id/enrolled_at) to carry onto a freshly
|
|
111
|
-
* generated identity. The binding is tied to device_id: if device_id is CHANGING
|
|
112
|
-
* (a new device, e.g. after a reset/re-enroll where the old file was deleted), the
|
|
113
|
-
* old binding is INVALIDATED — never preserve a stale codename/operator onto a
|
|
114
|
-
* different device. That mismatch is the "Frankenstein identity" root cause: a new
|
|
115
|
-
* device_id + an old codename → the server sees a mismatch → tags submissions
|
|
116
|
-
* `unverified` → never ranks, yet `isSignedIn` reads the local codename as present.
|
|
117
|
-
* Only reuse the binding when the SAME device_id is being kept. Pure — exposed for
|
|
118
|
-
* tests (no fs, so the owner's live identity is never at risk during a test run).
|
|
119
|
-
*/
|
|
120
|
-
export function bindingForFreshIdentity(existing, fresh) {
|
|
121
|
-
if (!existing)
|
|
122
|
-
return { codename: null, operator_id: null, enrolled_at: null };
|
|
123
|
-
if (existing.device_id && fresh.device_id === existing.device_id) {
|
|
124
|
-
return {
|
|
125
|
-
codename: existing.codename ?? null,
|
|
126
|
-
operator_id: existing.operator_id ?? null,
|
|
127
|
-
enrolled_at: existing.enrolled_at ?? null,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
// device_id changed → the old binding belongs to a different device; drop it.
|
|
131
|
-
return { codename: null, operator_id: null, enrolled_at: null };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Load the existing identity, or generate + persist a fresh one. Idempotent: a
|
|
136
|
-
* complete existing identity is returned untouched (never rotates a live key).
|
|
137
|
-
* Self-healing: if the identity has keys + device_id but no codename (binding
|
|
138
|
-
* lost via partial write / version transition), scans backups for a matching
|
|
139
|
-
* device_id and restores the binding before returning.
|
|
140
|
-
*/
|
|
141
|
-
export function ensureIdentity() {
|
|
142
|
-
const existing = loadIdentity();
|
|
143
|
-
if (
|
|
144
|
-
existing?.private_key_pkcs8_b64 &&
|
|
145
|
-
existing?.public_key &&
|
|
146
|
-
existing?.device_id
|
|
147
|
-
) {
|
|
148
|
-
// Self-healing: keys present but binding lost → try to restore from backups.
|
|
149
|
-
if (!existing.codename || !existing.operator_id) {
|
|
150
|
-
const restored = restoreBindingFromBackups(existing.device_id);
|
|
151
|
-
if (restored) {
|
|
152
|
-
existing.codename = restored.codename;
|
|
153
|
-
existing.operator_id = restored.operator_id;
|
|
154
|
-
existing.enrolled_at = restored.enrolled_at;
|
|
155
|
-
return persistIdentity(existing);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return existing;
|
|
159
|
-
}
|
|
160
|
-
// Regeneration path: a partial/corrupt record. The binding is tied to device_id —
|
|
161
|
-
// if a new device_id is generated, the old codename/operator MUST NOT carry over
|
|
162
|
-
// (bindingForFreshIdentity drops them). Only a reused device_id keeps its binding.
|
|
163
|
-
const fresh = generateIdentity({ device_id: existing?.device_id });
|
|
164
|
-
const binding = bindingForFreshIdentity(existing, fresh);
|
|
165
|
-
fresh.codename = binding.codename;
|
|
166
|
-
fresh.operator_id = binding.operator_id;
|
|
167
|
-
fresh.enrolled_at = binding.enrolled_at;
|
|
168
|
-
return persistIdentity(fresh);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Record a successful enrollment (codename/operator_id + enrolled_at) into the keystore. */
|
|
172
|
-
export function recordEnrollment({ codename, operator_id }) {
|
|
173
|
-
const id = ensureIdentity();
|
|
174
|
-
// OVERWRITE from the server's enroll response — a new enroll is a new binding, full
|
|
175
|
-
// stop. Never `??`-preserve a stale codename/operator onto this device (the other
|
|
176
|
-
// half of the Frankenstein bug: a re-enroll used to keep the old codename when the
|
|
177
|
-
// new one was absent). A 201 enrolled always carries both.
|
|
178
|
-
id.codename = codename ?? null;
|
|
179
|
-
id.operator_id = operator_id ?? null;
|
|
180
|
-
id.enrolled_at = new Date().toISOString();
|
|
181
|
-
return persistIdentity(id);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Clear the local identity (sign out). Next enroll provisions a fresh device_id. */
|
|
185
|
-
export function clearIdentity() {
|
|
186
|
-
try {
|
|
187
|
-
if (existsSync(PATH)) unlinkSync(PATH);
|
|
188
|
-
} catch {
|
|
189
|
-
/* best-effort */
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Keep at most MAX_BACKUPS backup files (oldest deleted first). */
|
|
194
|
-
function pruneBackups() {
|
|
195
|
-
try {
|
|
196
|
-
const files = readdirSync(DIR)
|
|
197
|
-
.filter((f) => f.startsWith(BACKUP_PREFIX))
|
|
198
|
-
.sort(); // timestamp-suffixed → lexicographic = chronological
|
|
199
|
-
while (files.length > MAX_BACKUPS) {
|
|
200
|
-
unlinkSync(join(DIR, files.shift()));
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
/* best-effort */
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Scan backup files for one with the same device_id that carries a codename +
|
|
208
|
-
* operator_id binding. Returns { codename, operator_id, enrolled_at } or null.
|
|
209
|
-
* Used by ensureIdentity() to self-heal when the live file lost its binding
|
|
210
|
-
* (e.g. a partial write or version transition that preserved keys but dropped
|
|
211
|
-
* the binding metadata). Only restores from a backup with the SAME device_id —
|
|
212
|
-
* never crosses device boundaries (Frankenstein-identity guard). */
|
|
213
|
-
export function restoreBindingFromBackups(device_id) {
|
|
214
|
-
if (!device_id) return null;
|
|
215
|
-
try {
|
|
216
|
-
const files = readdirSync(DIR)
|
|
217
|
-
.filter((f) => f.startsWith(BACKUP_PREFIX))
|
|
218
|
-
.sort()
|
|
219
|
-
.reverse(); // newest first — prefer the most recent binding
|
|
220
|
-
for (const f of files) {
|
|
221
|
-
try {
|
|
222
|
-
const bak = JSON.parse(readFileSync(join(DIR, f), "utf-8"));
|
|
223
|
-
if (
|
|
224
|
-
bak.device_id === device_id &&
|
|
225
|
-
bak.codename &&
|
|
226
|
-
bak.operator_id
|
|
227
|
-
) {
|
|
228
|
-
return {
|
|
229
|
-
codename: bak.codename,
|
|
230
|
-
operator_id: bak.operator_id,
|
|
231
|
-
enrolled_at: bak.enrolled_at ?? null,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
} catch {
|
|
235
|
-
/* skip corrupt backups */
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
} catch {
|
|
239
|
-
/* best-effort */
|
|
240
|
-
}
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
1
|
+
export * from './identity/keystore.mjs';
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// lib/constants.mjs — repo-wide constants that were previously inlined as
|
|
2
|
+
// literals across multiple files. Centralizing them here means a version bump
|
|
3
|
+
// or a client-map update lands in exactly one place.
|
|
4
|
+
//
|
|
5
|
+
// TERMS_VERSION / PRIVACY_VERSION — embedded in every enroll + submit
|
|
6
|
+
// payload (tools/enroll.mjs, tools/submit-paste.mjs,
|
|
7
|
+
// tools/tokenpull-submit.mjs, submit/index.mjs). They were inlined as
|
|
8
|
+
// "2026-07-21" in four files; a terms rev would have required finding
|
|
9
|
+
// and editing all four. Now: one export, imported everywhere.
|
|
10
|
+
//
|
|
11
|
+
// TOKSCALE_CLIENT_MAP — maps tokscale's raw client names to our platform
|
|
12
|
+
// IDs. Was duplicated verbatim in tools/index.mjs and
|
|
13
|
+
// tools/tokscale-breakdown.mjs (and again in tokscale_analytics.mjs);
|
|
14
|
+
// the three copies had already started to drift. One export here.
|
|
15
|
+
//
|
|
16
|
+
// isRankedAck — the "actually on the board" predicate (verified + persisted).
|
|
17
|
+
// Was inlined as `ack.verification_tier === "verified" && ack.persisted
|
|
18
|
+
// === true` in submit-paste, tokenpull-submit, and submit/index.mjs, with
|
|
19
|
+
// tokenpull-submit's multi-platform branch using a DIFFERENT looser
|
|
20
|
+
// predicate (`ack?.ranked ?? ack?.accepted ?? false`). One predicate here
|
|
21
|
+
// closes that loophole so every submit path agrees on what "ranked" means.
|
|
22
|
+
|
|
23
|
+
/** Current Terms of Service version the client attests to. */
|
|
24
|
+
export const TERMS_VERSION = "2026-07-21";
|
|
25
|
+
|
|
26
|
+
/** Current Privacy Policy version the client attests to. */
|
|
27
|
+
export const PRIVACY_VERSION = "2026-07-21";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The leaderboard sort metric the client requests from the server. Every
|
|
31
|
+
* leaderboard fetch site (tools/get-leaderboard.mjs, presentation/cli.mjs
|
|
32
|
+
* fetchBoard, presentation/tui.mjs, compare-self / optimize-efficiency /
|
|
33
|
+
* get-best-operator / compare-operators) MUST use this value — the server
|
|
34
|
+
* ranks by `yield_` and a different metric string would either 400 or return
|
|
35
|
+
* an unsorted board. Centralized here so a future metric rename lands in one
|
|
36
|
+
* place; the contract test in __tests__/contract/leaderboard-metric-contract.test.mjs
|
|
37
|
+
* guards against intra-repo drift and (in CI) cross-repo drift vs sigrank-app.
|
|
38
|
+
*/
|
|
39
|
+
export const LEADERBOARD_METRIC = "yield_";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Map a raw tokscale `client` string to our canonical platform ID.
|
|
43
|
+
* Values mapped to `null` are skipped (synthetic / non-attributable).
|
|
44
|
+
* Values mapped to `"other"` have no native adapter and bucket together.
|
|
45
|
+
*/
|
|
46
|
+
export const TOKSCALE_CLIENT_MAP = {
|
|
47
|
+
claude: "claude",
|
|
48
|
+
codex: "codex",
|
|
49
|
+
"devin-cli": "devin",
|
|
50
|
+
"devin-desktop": "devin",
|
|
51
|
+
gemini: "gemini",
|
|
52
|
+
amp: "amp",
|
|
53
|
+
kimi: "kimi",
|
|
54
|
+
qwen: "qwen",
|
|
55
|
+
goose: "goose",
|
|
56
|
+
kilo: "kilo",
|
|
57
|
+
kilocode: "kilo",
|
|
58
|
+
hermes: "hermes",
|
|
59
|
+
droid: "droid",
|
|
60
|
+
codebuff: "codebuff",
|
|
61
|
+
copilot: "copilot",
|
|
62
|
+
opencode: "opencode",
|
|
63
|
+
openclaw: "openclaw",
|
|
64
|
+
pi: "pi",
|
|
65
|
+
omp: "omp",
|
|
66
|
+
"oh-my-pi": "omp",
|
|
67
|
+
cursor: "other",
|
|
68
|
+
roocode: "other",
|
|
69
|
+
mux: "other",
|
|
70
|
+
crush: "other",
|
|
71
|
+
antigravity: "other",
|
|
72
|
+
"antigravity-cli": "other",
|
|
73
|
+
zed: "other",
|
|
74
|
+
kiro: "other",
|
|
75
|
+
trae: "other",
|
|
76
|
+
warp: "other",
|
|
77
|
+
cline: "other",
|
|
78
|
+
"9router": "other",
|
|
79
|
+
gjc: "other",
|
|
80
|
+
grok: "other",
|
|
81
|
+
jcode: "other",
|
|
82
|
+
commandcode: "other",
|
|
83
|
+
micode: "other",
|
|
84
|
+
junie: "other",
|
|
85
|
+
zcode: "other",
|
|
86
|
+
opencodereview: "other",
|
|
87
|
+
codebuddy: "other",
|
|
88
|
+
workbuddy: "other",
|
|
89
|
+
senpi: "other",
|
|
90
|
+
augment: "other",
|
|
91
|
+
kimchi: "other",
|
|
92
|
+
reasonix: "other",
|
|
93
|
+
"prime-agent": "other",
|
|
94
|
+
freebuff: "other",
|
|
95
|
+
synthetic: null,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Platforms whose local data tokscale CANNOT see, so `tokscale models --json`
|
|
100
|
+
* never lists them as an active client. pullActivePlatforms() auto-detect must
|
|
101
|
+
* probe these directly or their cascade row can never appear, even with GBs of
|
|
102
|
+
* native local telemetry (that was exactly the omp bug: tokscale reports
|
|
103
|
+
* claude/codex/copilot/gemini/grok/kimi/kiro/opencode/pi and nothing else, so
|
|
104
|
+
* a tokscale-detected machine silently dropped oh-my-pi entirely).
|
|
105
|
+
*
|
|
106
|
+
* The adapter pull itself filters out platforms with no data, so probing a
|
|
107
|
+
* platform the operator doesn't use costs one cheap missing-directory walk.
|
|
108
|
+
*
|
|
109
|
+
* As of 2026-08-08: oh-my-pi. Delete an entry once tokscale reports that client
|
|
110
|
+
* (TOKSCALE_CLIENT_MAP already maps the `omp` / `oh-my-pi` client names for
|
|
111
|
+
* when that happens).
|
|
112
|
+
*/
|
|
113
|
+
export const TOKSCALE_BLIND_PLATFORMS = ["omp"];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The "actually on the board" predicate. A submission is ranked only when the
|
|
117
|
+
* server returns HTTP ok AND verification_tier === "verified" AND persisted
|
|
118
|
+
* === true. An unenrolled/revoked device gets 202 but is NEVER ranked.
|
|
119
|
+
*
|
|
120
|
+
* Use this everywhere a submit path decides whether to surface `ranked: true`.
|
|
121
|
+
* The previous tokenpull-submit multi-platform branch used a looser
|
|
122
|
+
* `ack?.ranked ?? ack?.accepted ?? false` which would mark a non-persisted
|
|
123
|
+
* 202 as ranked — this predicate closes that loophole.
|
|
124
|
+
*
|
|
125
|
+
* @param {{ ok: boolean }} res the fetch Response (or { ok } shim)
|
|
126
|
+
* @param {Record<string, unknown>} ack the parsed JSON ack body
|
|
127
|
+
* @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
export function isRankedAck(res, ack) {
|
|
130
|
+
return !!(
|
|
131
|
+
res &&
|
|
132
|
+
res.ok &&
|
|
133
|
+
ack &&
|
|
134
|
+
ack.verification_tier === "verified" &&
|
|
135
|
+
ack.persisted === true
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// lib/pkg-version.mjs — single source of truth for the package version.
|
|
2
|
+
//
|
|
3
|
+
// Resolves the repo-root package.json by walking up from this module until it
|
|
4
|
+
// finds the package named "sigrank". This is robust to callers in any
|
|
5
|
+
// subdirectory (identity/, submit/, presentation/, tools/, scripts/) — the
|
|
6
|
+
// previous per-caller `new URL("./package.json", import.meta.url)` lookups
|
|
7
|
+
// resolved relative to the CALLER's own directory and hit non-existent files,
|
|
8
|
+
// falling back to a bare string. The agent_version stamp rides inside the
|
|
9
|
+
// SIGNED submit payload (agent.agent_version), so a wrong/missing version
|
|
10
|
+
// there is a high-priority bug — it survived because no test guarded it.
|
|
11
|
+
//
|
|
12
|
+
// One implementation, imported everywhere. Never throws: returns "unknown" on
|
|
13
|
+
// any failure so a missing/renamed package.json never breaks a submit.
|
|
14
|
+
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
|
|
19
|
+
/** The published package name (package.json "name"). Used to identify the
|
|
20
|
+
* repo-root package.json unambiguously past any nested package.json. */
|
|
21
|
+
const REPO_PKG_NAME = "sigrank";
|
|
22
|
+
|
|
23
|
+
/** Resolve the repo-root package version (best-effort).
|
|
24
|
+
* Returns the version string (e.g. "0.0.181") or "unknown" on any failure. */
|
|
25
|
+
export function pkgVersion() {
|
|
26
|
+
try {
|
|
27
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
for (let i = 0; i < 20; i++) {
|
|
29
|
+
const pkgPath = join(dir, "package.json");
|
|
30
|
+
let pkg;
|
|
31
|
+
try {
|
|
32
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
33
|
+
} catch {
|
|
34
|
+
// no package.json here — walk up
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir) break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (pkg.name === REPO_PKG_NAME) return pkg.version || "unknown";
|
|
41
|
+
// Found a package.json but it isn't ours (e.g. a nested dep) — keep walking.
|
|
42
|
+
const parent = dirname(dir);
|
|
43
|
+
if (parent === dir) {
|
|
44
|
+
// Reached the filesystem root without matching; use whatever we found.
|
|
45
|
+
return pkg.version || "unknown";
|
|
46
|
+
}
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
return "unknown";
|
|
50
|
+
} catch {
|
|
51
|
+
return "unknown";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The full agent-version stamp, e.g. "sigrank-mcp/0.0.181".
|
|
56
|
+
* This is the value embedded in the signed payload's agent.agent_version. */
|
|
57
|
+
export function agentVersion() {
|
|
58
|
+
return `sigrank-mcp/${pkgVersion()}`;
|
|
59
|
+
}
|
package/manifest.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json",
|
|
3
|
+
"manifest_version": "0.4",
|
|
4
|
+
"name": "sigrank",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "Ranks AI coding operators by token cascade efficiency (Yield = cache_read \u00d7 output / input\u00b2). 23 tools for yield calculation, local token usage pulls, signed submission, and tokscale-powered analytics (market share, developer profiles, model trends, cost analysis, device profiles, MCP usage, competitive intelligence). Privacy-first: runs locally, only four token counts leave the machine.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "SunrisesIllNeverSee",
|
|
9
|
+
"url": "https://github.com/SunrisesIllNeverSee"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://signalaf.com",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/SunrisesIllNeverSee/sigrank-mcp"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"server": {
|
|
18
|
+
"type": "node",
|
|
19
|
+
"entry_point": "index.mjs",
|
|
20
|
+
"mcp_config": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": [
|
|
23
|
+
"sigrank"
|
|
24
|
+
],
|
|
25
|
+
"env": {
|
|
26
|
+
"SIGRANK_API_BASE": "https://signalaf.com"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"user_config": {
|
|
31
|
+
"SIGRANK_API_BASE": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"title": "SigRank API Base URL",
|
|
34
|
+
"description": "Overrides the board host for the SigRank API. Defaults to https://signalaf.com.",
|
|
35
|
+
"default": "https://signalaf.com",
|
|
36
|
+
"required": false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"compatibility": {
|
|
40
|
+
"platforms": [
|
|
41
|
+
"darwin",
|
|
42
|
+
"win32",
|
|
43
|
+
"linux"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
package/narrate.mjs
CHANGED
|
@@ -1,77 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* narrate.mjs — deterministic prose "card" for a cascade result.
|
|
3
|
-
*
|
|
4
|
-
* Port of _template() from ~/Desktop/moses-sigrank/narrate.py. The model path
|
|
5
|
-
* (MiniCPM4-0.5B) is intentionally SKIPPED: the template is the trustworthy,
|
|
6
|
-
* instant, auditable fallback — same numbers in → same card out, and it can never
|
|
7
|
-
* emit a metric the cascade didn't produce. A model hook can layer behind this same
|
|
8
|
-
* narrate() interface later without a rewrite.
|
|
9
|
-
*
|
|
10
|
-
* Token-only. No network, no randomness.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Safe formatters: never emit NaN/Infinity/undefined into the card.
|
|
14
|
-
const safeNum = (n) => (Number.isFinite(Number(n)) ? Number(n) : null);
|
|
15
|
-
const comma = (n, dec) => {
|
|
16
|
-
const v = safeNum(n);
|
|
17
|
-
return v !== null
|
|
18
|
-
? v.toLocaleString("en-US", {
|
|
19
|
-
minimumFractionDigits: dec,
|
|
20
|
-
maximumFractionDigits: dec,
|
|
21
|
-
})
|
|
22
|
-
: "—";
|
|
23
|
-
};
|
|
24
|
-
const plain = (n, dec) => {
|
|
25
|
-
const v = safeNum(n);
|
|
26
|
-
return v !== null ? v.toFixed(dec) : "—";
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Given a cascade result ({ velocity, leverage, dev10x, pillars, class }) and an
|
|
31
|
-
* optional subject name, return "**CLASS.** <one or two sentences>". Deterministic.
|
|
32
|
-
*/
|
|
33
|
-
export function narrate(cascade, name = "This operator") {
|
|
34
|
-
const klass = cascade.class || cascade.klass || "UNCLASSED";
|
|
35
|
-
const v = safeNum(cascade.velocity);
|
|
36
|
-
const l = safeNum(cascade.leverage);
|
|
37
|
-
|
|
38
|
-
// "non-compounding" = a stateless pipe: no cache commits, so the cascade can't
|
|
39
|
-
// form. cascade.mjs leaves dev10x null when cacheCreate is 0 (the cw/o term
|
|
40
|
-
// collapses), which is exactly metrics.py's non_compounding flag.
|
|
41
|
-
// Also catches zero-input sessions where velocity/leverage are null.
|
|
42
|
-
const cw = cascade.pillars ? Number(cascade.pillars.cacheCreate) : NaN;
|
|
43
|
-
const nonCompounding =
|
|
44
|
-
cascade.dev10x == null || !(cw > 0) || v === null || l === null;
|
|
45
|
-
|
|
46
|
-
let body;
|
|
47
|
-
if (nonCompounding) {
|
|
48
|
-
const leverageStr =
|
|
49
|
-
l !== null
|
|
50
|
-
? `Leverage ${comma(l, 1)}x comes from reuse alone.`
|
|
51
|
-
: "Leverage is undefined (no fresh input recorded).";
|
|
52
|
-
const dev10xNote =
|
|
53
|
-
cascade.dev10x == null
|
|
54
|
-
? " 10xDEV is undefined — the compounding loop has not formed yet."
|
|
55
|
-
: "";
|
|
56
|
-
body =
|
|
57
|
-
`${name} runs a stateless pipe — no cache commits, so the cascade can't form. ` +
|
|
58
|
-
`High read volume, but nothing is being built forward. ${leverageStr}${dev10xNote}`;
|
|
59
|
-
} else if (v >= 1 && l >= 100) {
|
|
60
|
-
body =
|
|
61
|
-
`${name} holds both axes at once: ${plain(v, 1)}x generation AND ${comma(l, 0)}x memory leverage. ` +
|
|
62
|
-
`A closed kinetic loop — the rare operator the leverage/generation tradeoff says shouldn't exist.`;
|
|
63
|
-
} else if (l >= 10 && v < 1) {
|
|
64
|
-
body =
|
|
65
|
-
`${name} is an archival sponge — ${comma(l, 0)}x reuse but only ${plain(v, 2)}x generation. ` +
|
|
66
|
-
`Holds context beautifully, executes little with it. The reuse number is inflated by a weak commitment stage.`;
|
|
67
|
-
} else if (v >= 0.8 && l < 2) {
|
|
68
|
-
body =
|
|
69
|
-
`${name} is a volatile ingestor — ${plain(v, 2)}x generation but ${plain(l, 1)}x leverage. ` +
|
|
70
|
-
`Fast on single shots, resets between turns. Memory doesn't persist into a compounding loop.`;
|
|
71
|
-
} else {
|
|
72
|
-
body =
|
|
73
|
-
`${name} sits low on both axes: ${plain(v, 2)}x generation, ${plain(l, 1)}x leverage. ` +
|
|
74
|
-
`A transient profile — neither building state nor converting input to output efficiently.`;
|
|
75
|
-
}
|
|
76
|
-
return `**${klass}.** ${body}`;
|
|
77
|
-
}
|
|
1
|
+
export * from './presentation/narrate.mjs';
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigrank",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
|
|
5
|
-
"description": "SigRank MCP server —
|
|
5
|
+
"description": "SigRank MCP server — AI operator evaluation and token cascade efficiency tools. The yield cascade + live leaderboard as MCP tools any agent can call.",
|
|
6
6
|
"mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"license": "MIT",
|
|
@@ -24,26 +24,43 @@
|
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"start": "node index.mjs",
|
|
27
|
-
"test": "node test.mjs && node sign.test.mjs && node --test __tests__/cascade.test.mjs __tests__/badges.test.mjs",
|
|
28
|
-
"sync:platforms": "node scripts/sync-platforms.mjs"
|
|
27
|
+
"test": "node test.mjs && node __tests__/sign.test.mjs && node --test __tests__/cascade.test.mjs __tests__/badges.test.mjs __tests__/tui-input.test.mjs",
|
|
28
|
+
"sync:platforms": "node scripts/sync-platforms.mjs",
|
|
29
|
+
"sync:spine": "node scripts/sync-spine.mjs",
|
|
30
|
+
"spine:check": "node scripts/sync-spine.mjs --check",
|
|
31
|
+
"spine:diff": "node scripts/sync-spine.mjs --dry-run"
|
|
29
32
|
},
|
|
30
33
|
"files": [
|
|
31
34
|
"index.mjs",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"tools
|
|
35
|
+
"proxy.mjs",
|
|
36
|
+
"lib/",
|
|
37
|
+
"tools/",
|
|
38
|
+
"analytics/",
|
|
39
|
+
"identity/",
|
|
40
|
+
"submit/",
|
|
41
|
+
"adapters/",
|
|
42
|
+
"presentation/",
|
|
43
|
+
"resources/",
|
|
44
|
+
"prompts/",
|
|
45
|
+
"badges.mjs",
|
|
46
|
+
"review.mjs",
|
|
35
47
|
"cascade.mjs",
|
|
36
|
-
"narrate.mjs",
|
|
37
|
-
"tokenpull.mjs",
|
|
38
|
-
"adapters.mjs",
|
|
39
|
-
"sign.mjs",
|
|
40
48
|
"keystore.mjs",
|
|
41
|
-
"
|
|
49
|
+
"sign.mjs",
|
|
42
50
|
"connect.mjs",
|
|
43
|
-
"
|
|
51
|
+
"submit.mjs",
|
|
44
52
|
"preflight.mjs",
|
|
45
|
-
"
|
|
53
|
+
"adapters.mjs",
|
|
54
|
+
"tokenpull.mjs",
|
|
55
|
+
"tui.mjs",
|
|
56
|
+
"cli.mjs",
|
|
57
|
+
"narrate.mjs",
|
|
58
|
+
"tools.mjs",
|
|
59
|
+
"tokscale_analytics.mjs",
|
|
60
|
+
"manifest.json",
|
|
46
61
|
"README.md",
|
|
62
|
+
"CHANGELOG.md",
|
|
63
|
+
"MCP_SERVER_DESCRIPTION.md",
|
|
47
64
|
"LICENSE"
|
|
48
65
|
],
|
|
49
66
|
"keywords": [
|
|
@@ -65,7 +82,15 @@
|
|
|
65
82
|
],
|
|
66
83
|
"dependencies": {
|
|
67
84
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
68
|
-
"
|
|
69
|
-
"
|
|
85
|
+
"@sigrank/cascade": "^0.1.1",
|
|
86
|
+
"ccusage": "^20.0.19",
|
|
87
|
+
"tokscale": "^4.7.0"
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
91
|
+
"gifenc": "^1.0.3"
|
|
92
|
+
},
|
|
93
|
+
"overrides": {
|
|
94
|
+
"@hono/node-server": "^2.0.5"
|
|
70
95
|
}
|
|
71
96
|
}
|