sigrank 1.0.44 → 1.0.46
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 +44 -0
- package/MCP_SERVER_DESCRIPTION.md +22 -21
- package/README.md +10 -8
- package/adapters/index.mjs +2 -0
- package/adapters/tokenpull.mjs +10 -3
- package/identity/keystore.mjs +3 -1
- package/lib/constants.mjs +12 -0
- package/manifest.json +1 -1
- package/package.json +5 -5
- package/presentation/cli.mjs +19 -6
- package/presentation/tui.mjs +3 -2
- package/proxy.mjs +16 -0
- package/resources/privacy-model.md +7 -4
- package/tokscale_analytics.mjs +12 -9
- package/tools/_helpers.mjs +6 -0
- package/tools/_verifiers.mjs +6 -1
- package/tools/enroll.mjs +17 -3
- package/tools/index.mjs +6 -1
- package/tools/submit-verified.mjs +13 -1
- package/tools/tokscale-breakdown.mjs +9 -2
- package/tools/tokscale-developer-profile.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.46
|
|
4
|
+
|
|
5
|
+
### Security / privacy
|
|
6
|
+
|
|
7
|
+
* `enroll`: device label no longer defaults to the OS hostname; default is `sigrank <version> · <platform>` (opt-in override: `device_label` arg or `SIGRANK_DEVICE_LABEL`)
|
|
8
|
+
* `enroll` (MCP): refuses to re-bind an already-enrolled device; re-enroll from the CLI
|
|
9
|
+
* `submit_verified`: requires `confirm: true` to publish (not for `dry_run`); CLI/TUI pass it after their own consent
|
|
10
|
+
* `tokscale_developer_profile`: returns `workspace_count` only, no workspace/project names
|
|
11
|
+
* `validateApiBase`: https required except loopback hosts
|
|
12
|
+
* Pinned `ccusage` 20.0.30 and `tokscale` 4.18.0 exactly; removed stale `tokscale@latest` comment
|
|
13
|
+
* Proxy: rejects non-loopback Host headers and cross-site browser requests (403)
|
|
14
|
+
* Keystore: identity backups chmod 0600
|
|
15
|
+
* README / privacy-model corrected to list exactly what leaves the machine
|
|
16
|
+
|
|
17
|
+
## [1.0.44](https://github.com/SunrisesIllNeverSee/sigrank-mcp/releases/tag/v1.0.44) (2026-10-05)
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* MCP: explicit opt-in support for protocol revision 2026-07-28 via `serveStdio` — a `server/discover` probe with the modern `_meta` envelope pins the connection to the modern era; `initialize` openings keep the 2025-era protocol unchanged (dual-era serving, `legacy: 'serve'`)
|
|
22
|
+
* Docs: `docs/PROTOCOL-COMPATIBILITY.md` — served eras, negotiation rules, host/client matrix
|
|
23
|
+
|
|
24
|
+
### Tests
|
|
25
|
+
|
|
26
|
+
* stdio characterization suite 15 → 24: 9 modern-era tests (discover negotiation, era parity, envelope-claim requirement, `-32022` late-initialize, probe→initialize fallback, modern stdout purity + EOF)
|
|
27
|
+
|
|
28
|
+
## [1.0.43](https://github.com/SunrisesIllNeverSee/sigrank-mcp/releases/tag/v1.0.43) (2026-10-05)
|
|
29
|
+
|
|
30
|
+
### Breaking Changes
|
|
31
|
+
|
|
32
|
+
* Node.js >= 20 required (was >= 18)
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
* MCP: migrated `@modelcontextprotocol/sdk` v1 → official v2 packages (`@modelcontextprotocol/server` 2.3.0); zero v1 imports remain
|
|
37
|
+
* `sigrank standard --json`: `sigrank/0.1-draft` now correctly labeled `legacy_alias` under TTEOP authority (`tteop-spec@0.1.5-draft`)
|
|
38
|
+
|
|
39
|
+
### Fixes
|
|
40
|
+
|
|
41
|
+
* Release workflow: explicit releases only — no auto-publish after CI; real npm OIDC trusted publishing (dead NPM_TOKEN path removed); `--dry-run` + collision pre-gates; `--provenance`
|
|
42
|
+
|
|
43
|
+
### Tests
|
|
44
|
+
|
|
45
|
+
* 15-test SDK-agnostic stdio behavior freeze locked identical across v1 → v2 (tools/prompts/resources, error classes, stdout purity, routing, EOF) on Node 20/22/26
|
|
46
|
+
|
|
3
47
|
## [1.0.37](https://github.com/SunrisesIllNeverSee/sigrank-mcp/releases/tag/v1.0.37) (2026-09-08)
|
|
4
48
|
|
|
5
49
|
### Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SigRank MCP Server
|
|
2
2
|
|
|
3
|
-
> Operator leaderboard measuring users, not models —
|
|
3
|
+
> Operator leaderboard measuring users, not models — 25 tools, TUI, signed submit, privacy-first.
|
|
4
4
|
|
|
5
5
|
SigRank is the first leaderboard that ranks **AI operators** — the humans — by token cascade efficiency, not the models. The yield metric (Υ = cache_read × output / input²) turns raw token telemetry into a competitive ranking on [signalaf.com](https://signalaf.com).
|
|
6
6
|
|
|
@@ -64,7 +64,7 @@ npx sigrank diagnose # Diagnose cascade inefficiencies
|
|
|
64
64
|
npx sigrank improve # Get improvement suggestions
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
## The
|
|
67
|
+
## The 25 MCP Tools
|
|
68
68
|
|
|
69
69
|
### Read-only — pure math (no auth, no filesystem)
|
|
70
70
|
|
|
@@ -81,39 +81,40 @@ npx sigrank improve # Get improvement suggestions
|
|
|
81
81
|
|
|
82
82
|
| # | Tool | What it does |
|
|
83
83
|
|---|------|-------------|
|
|
84
|
-
| 7 | `
|
|
85
|
-
| 8 | `
|
|
86
|
-
| 9 | `
|
|
84
|
+
| 7 | `get_sigrank_standard_record` | Build the portable `sigrank/0.1-draft` compatibility record (legacy alias under TTEOP authority) from token telemetry. Computes the canonical cascade locally — no network. |
|
|
85
|
+
| 8 | `get_leaderboard` | Fetch the live public leaderboard from signalaf.com. Returns all ranked operators sorted by yield, with codename, Υ, leverage, velocity, class, and rank position. |
|
|
86
|
+
| 9 | `get_operator` | Get any operator's full profile by codename. Returns detailed metrics + per-window breakdowns (7d/30d/90d/all-time) with the four token pillars per window. |
|
|
87
|
+
| 10 | `discover_peers` | Find mentors, peers, and complementary operators for your operator on the leaderboard. |
|
|
87
88
|
|
|
88
89
|
### Read-only — local filesystem (no auth, no network)
|
|
89
90
|
|
|
90
91
|
| # | Tool | What it does |
|
|
91
92
|
|---|------|-------------|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
93
|
+
| 11 | `tokenpull` | Pull your LOCAL token usage from session logs and rank it across all four windows — zero paste. Reads token counts, not message content. Numbers stay on your machine. 17 platform adapters. |
|
|
94
|
+
| 12 | `tokenpull_compare` | Pull token usage from ALL four local sources in parallel (JSONL canon, ccusage CLI, token-dashboard SQLite, tokscale) and compare side-by-side with delta % vs baseline. |
|
|
95
|
+
| 13 | `watch_tokenpull` | One poll per call — pulls local token logs and returns current cascade for the watched window. Re-call at your desired cadence to monitor. With `submit:true`, signs + publishes (rate-limited to once per 5 min). |
|
|
95
96
|
|
|
96
97
|
### Write — submit to board (needs enrolled identity)
|
|
97
98
|
|
|
98
99
|
| # | Tool | What it does |
|
|
99
100
|
|---|------|-------------|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
101
|
+
| 14 | `submit_paste` | Ranks a paste of token counts AND publishes to the live board in one call. Computes local preview, then submits to server for authoritative scoring. Returns both local + server results. |
|
|
102
|
+
| 15 | `tokenpull_submit` | Pull your LOCAL token usage AND publish to the board in one call — the zero-paste flow. Reads pillars per window, computes cascade, submits each window server-side. Token-only, no prompt content. |
|
|
103
|
+
| 16 | `submit_verified` | Publish your LOCAL token runs as a VERIFIED operator — the enrolled, signed path. Reads pillars, builds Schema 1.0 snapshot, ed25519-signs with your device key, POSTs to board. Requires `enroll` first. |
|
|
104
|
+
| 17 | `enroll` | Bind THIS device to your SigRank operator. Paste the key from signalaf.com → Settings → "New key". Generates + stores a local ed25519 keypair. Only the PUBLIC key is ever sent. |
|
|
104
105
|
|
|
105
106
|
### Tokscale analytics — local tokscale data (no auth, no network)
|
|
106
107
|
|
|
107
108
|
| # | Tool | What it does |
|
|
108
109
|
|---|------|-------------|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
110
|
+
| 18 | `tokscale_breakdown` | Per-model token breakdown across platforms (models under threshold → "other"). |
|
|
111
|
+
| 19 | `tokscale_market_share` | AI tool market share: each tool's % of tokens/cost/messages, ranked. From local tokscale data. |
|
|
112
|
+
| 20 | `tokscale_developer_profile` | Per-developer usage profile across all detected tools: model mix, pillars, sessions, workspaces. Paths redacted. |
|
|
113
|
+
| 21 | `tokscale_model_trends` | Model adoption over time: per-model first/last seen, active days, month-by-month adoption curve. |
|
|
114
|
+
| 22 | `tokscale_cost_analysis` | Cost per developer per model: cost_per_million_tokens, cost_per_message, share_cost, client rollup. |
|
|
115
|
+
| 23 | `tokscale_device_profile` | Device fingerprinting: installed tools, session counts, active days, day-of-week distribution, concurrency. Paths redacted. |
|
|
116
|
+
| 24 | `tokscale_mcp_usage` | MCP server usage: detected servers, detection window, active days. |
|
|
117
|
+
| 25 | `tokscale_competitive_intel` | Competitive intelligence for any AI tool: rank, model mix, cost efficiency, share vs all competitors. |
|
|
117
118
|
|
|
118
119
|
### Moved to bestuser-router-mcp (5 intent tools)
|
|
119
120
|
|
|
@@ -160,7 +161,7 @@ The intent-based tools (in bestuser-router-mcp) are designed to map natural-lang
|
|
|
160
161
|
| Transport | stdio |
|
|
161
162
|
| Platform | Cross-platform (Node.js) |
|
|
162
163
|
| Language | JavaScript |
|
|
163
|
-
| Tools |
|
|
164
|
+
| Tools | 25 |
|
|
164
165
|
| Auth | None for reads; Supabase anon key for submits |
|
|
165
166
|
| Category | Developer Tools |
|
|
166
167
|
|
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ sigrank submit
|
|
|
95
95
|
sigrank submit --dry-run
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
That's it. sigrank reads your local AI session logs on-device, derives your token cascade (Υ Yield, Leverage, Velocity, 10xDEV), and publishes to [signalaf.com](https://signalaf.com). No paste, no transcript content —
|
|
98
|
+
That's it. sigrank reads your local AI session logs on-device, derives your token cascade (Υ Yield, Leverage, Velocity, 10xDEV), and publishes to [signalaf.com](https://signalaf.com). No paste, no transcript content — what leaves your machine is limited to the four token pillars plus the identifiers listed under [Privacy](#privacy).
|
|
99
99
|
|
|
100
100
|
Or just explore without signing in:
|
|
101
101
|
|
|
@@ -303,6 +303,8 @@ Or if installed globally:
|
|
|
303
303
|
| `tokenpull_submit(codename, window?)` | `{codename?, window?}` | `tokenpull` → publish to board. Omit codename for preview |
|
|
304
304
|
| `tokenpull_compare(platform?)` | `{platform?}` | All four sources side-by-side: tokenpull + ccusage + token-dash + tokscale. Returns pillars, cascade metrics, and delta % vs tokenpull per window |
|
|
305
305
|
| `rank_windows` | `{platform?, window?}` | Multi-window cascade from local logs |
|
|
306
|
+
| `get_sigrank_standard_record` | `{input, output, cache_write?, cache_read?}` | Builds the portable `sigrank/0.1-draft` compatibility record (legacy alias under TTEOP authority) — canonical cascade, computed locally |
|
|
307
|
+
| `discover_peers` | `{codename?}` | Find mentors, peers, and complementary operators on the leaderboard |
|
|
306
308
|
| `watch_tokenpull` | `{platform?, interval_s?}` | One cascade snapshot per call (interval_s advisory) |
|
|
307
309
|
| `submit_verified` | `{window?, platform?, dry_run?}` | THE ranked path: builds + ed25519-signs Schema 1.0 snapshots and POSTs them. `platform:'multi'` sums all active platforms. `dry_run:true` returns the exact payload unsent |
|
|
308
310
|
| `enroll` | `{code, device_label?}` | Bind this device with a connect code from signalaf.com → Settings |
|
|
@@ -310,20 +312,17 @@ Or if installed globally:
|
|
|
310
312
|
| `simulate_change` | `{text?, changes}` | Prescriptive "what if" — test proposed pillar changes and see the exact Υ delta + class change before committing |
|
|
311
313
|
| `suggest_improvements` | `{text?}` | Generates ranked, simulated improvement suggestions — tests strategies and returns them sorted by Υ yield impact |
|
|
312
314
|
| `self_improve` | `{text?}` | One-click optimize: diagnoses, suggests, and simulates the best change in a single call |
|
|
313
|
-
| `get_best_operator(n?)` | `{n?}` | Top N operators with behavioral framing in power-user language. Intent: "who is the best AI user?" |
|
|
314
|
-
| `compare_self(codename? \| text?)` | `{codename?}` or `{text?}` | Your metrics vs board averages + power-user assessment + percentile + suggestion. Intent: "how do I measure up?" |
|
|
315
|
-
| `compare_operators(a, b)` | `{codename_a, codename_b}` | Side-by-side comparison with behavioral verdict. Intent: "compare operator X vs Y" |
|
|
316
|
-
| `describe_power_user()` | `{}` | Static explanation of AI power user archetype + metrics explained. Intent: "what is an AI power user?" |
|
|
317
|
-
| `optimize_efficiency(codename? \| text?)` | `{codename?}` or `{text?}` | Ranked efficiency suggestions tied to your cascade shape. Intent: "how can I use AI more efficiently?" |
|
|
318
315
|
| `tokscale_breakdown(threshold?)` | `{threshold?}` | Per-model token breakdown across platforms (models under threshold → "other") |
|
|
319
316
|
| `tokscale_market_share()` | `{}` | AI tool market share: each tool's % of tokens/cost/messages, ranked. From local tokscale data |
|
|
320
|
-
| `tokscale_developer_profile()` | `{}` | Per-developer usage profile across all detected tools: model mix, pillars, sessions,
|
|
317
|
+
| `tokscale_developer_profile()` | `{}` | Per-developer usage profile across all detected tools: model mix, pillars, sessions, workspace count (no names). Paths redacted |
|
|
321
318
|
| `tokscale_model_trends()` | `{}` | Model adoption over time: per-model first/last seen, active days, month-by-month adoption curve |
|
|
322
319
|
| `tokscale_cost_analysis()` | `{}` | Cost per developer per model: cost_per_million_tokens, cost_per_message, share_cost, client rollup |
|
|
323
320
|
| `tokscale_device_profile()` | `{}` | Device fingerprinting: installed tools, session counts, active days, day-of-week distribution, concurrency. Paths redacted |
|
|
324
321
|
| `tokscale_mcp_usage()` | `{}` | MCP server usage: detected servers, detection window, active days |
|
|
325
322
|
| `tokscale_competitive_intel(target)` | `{target}` | Competitive intelligence for any AI tool: rank, model mix, cost efficiency, share vs all competitors |
|
|
326
323
|
|
|
324
|
+
Intent tools (`get_best_operator`, `compare_self`, `compare_operators`, `describe_power_user`, `optimize_efficiency`) moved to [bestuser-router-mcp](https://github.com/SunrisesIllNeverSee/bestuser-router-mcp) — `npx bestuser-router-mcp`.
|
|
325
|
+
|
|
327
326
|
---
|
|
328
327
|
|
|
329
328
|
## Cascade math
|
|
@@ -406,6 +405,8 @@ All adapters are token-only (no message content, no cost fields, no credentials)
|
|
|
406
405
|
|
|
407
406
|
## Privacy
|
|
408
407
|
|
|
408
|
+
- **Exactly what leaves your machine (signed submit):** the four token pillars (input, output, cache create, cache read) per window, your codename, device id, device public key, ed25519 signature, agent version, platform, message/session counts, and the weekly report block. `enroll` additionally sends a device label, which defaults to `sigrank <version> · <platform>` (your hostname is never sent unless you set `SIGRANK_DEVICE_LABEL` or pass `device_label`). Never sent: prompts, responses, file paths, repo/project names, or environment variables.
|
|
409
|
+
- **MCP safety rails.** The `enroll` tool refuses to re-bind a device that is already enrolled (re-enroll from the CLI), and `submit_verified` requires `confirm: true` to publish (not needed with `dry_run`). `tokscale_developer_profile` returns workspace counts only, never workspace or project names.
|
|
409
410
|
- **Token-only persistence and submission.** Local-log adapters read usage metadata only. The optional proxy necessarily handles provider-bound request and response bytes in memory, but never persists their content; it writes only token counts, model/backend metadata, and timestamps. Only token telemetry is submitted to SigRank SignalAF.
|
|
410
411
|
- **Local by default.** `tokenpull` reads only the selected or detected adapter paths on your device (e.g. `~/.claude/projects` for Claude, `~/.codex` for Codex, plus additional paths for Amp, Devin, Gemini, Goose, and other supported platforms). Numbers stay on your machine unless you explicitly submit with a codename.
|
|
411
412
|
- **Background tooling excluded.** Memory plugins, observers, summarizers (e.g. `claude-mem`, `mem0`, `observer-sessions`) are filtered from both Claude and Codex reads. `subagents/` are kept — they represent real operator work.
|
|
@@ -418,7 +419,8 @@ All adapters are token-only (no message content, no cost fields, no credentials)
|
|
|
418
419
|
|
|
419
420
|
| Var | Default | Description |
|
|
420
421
|
| ----------------------- | ---------------------- | ---------------------------- |
|
|
421
|
-
| `SIGRANK_API_BASE` | `https://signalaf.com` | Override the board host
|
|
422
|
+
| `SIGRANK_API_BASE` | `https://signalaf.com` | Override the board host (https required; http only for loopback) |
|
|
423
|
+
| `SIGRANK_DEVICE_LABEL` | `sigrank <version> · <platform>` | Optional device label sent at enroll |
|
|
422
424
|
| `SIGRANK_FETCH_TIMEOUT` | `10000` | Board API fetch timeout (ms) |
|
|
423
425
|
|
|
424
426
|
---
|
package/adapters/index.mjs
CHANGED
|
@@ -1099,6 +1099,8 @@ async function execCcusage(args) {
|
|
|
1099
1099
|
if (existsSync(localEntrypoint)) {
|
|
1100
1100
|
return execFileP(process.execPath, [localEntrypoint, ...args], options);
|
|
1101
1101
|
}
|
|
1102
|
+
// Fallback only: the bundled (pinned) ccusage above is preferred; this resolves "ccusage"
|
|
1103
|
+
// from PATH when the bundled entrypoint is missing, so a PATH binary is trusted code here.
|
|
1102
1104
|
return execFileP("ccusage", args, options);
|
|
1103
1105
|
}
|
|
1104
1106
|
|
package/adapters/tokenpull.mjs
CHANGED
|
@@ -22,7 +22,10 @@ import { join, dirname, delimiter } from "node:path";
|
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
import { ADAPTERS, walkFiles } from "./index.mjs";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
TOKSCALE_CLIENT_MAP,
|
|
27
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
28
|
+
} from "../lib/constants.mjs";
|
|
26
29
|
|
|
27
30
|
const DAY_MS = 86_400_000;
|
|
28
31
|
|
|
@@ -516,7 +519,11 @@ async function _freshTokendash(platform = "claude") {
|
|
|
516
519
|
// Now uses execFile (async) so the TUI stays responsive.
|
|
517
520
|
async function _freshTokscale(platform = "claude") {
|
|
518
521
|
try {
|
|
519
|
-
const raw = await execFileAsync(
|
|
522
|
+
const raw = await execFileAsync(
|
|
523
|
+
"tokscale",
|
|
524
|
+
["models", "--json"],
|
|
525
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
526
|
+
);
|
|
520
527
|
const data = JSON.parse(raw);
|
|
521
528
|
const entries = Array.isArray(data?.entries)
|
|
522
529
|
? data.entries
|
|
@@ -557,7 +564,7 @@ async function _freshTokscale(platform = "claude") {
|
|
|
557
564
|
*
|
|
558
565
|
* - ccusage : `ccusage <platform> daily --json` → 7d/30d/90d/all
|
|
559
566
|
* - tokendash: refresh + read ~/.claude/token-dashboard.db → all (claude only)
|
|
560
|
-
* - tokscale : `
|
|
567
|
+
* - tokscale : `tokscale models --json` (bundled, pinned) → all
|
|
561
568
|
*/
|
|
562
569
|
export async function freshVerifierPillars(platform = "claude") {
|
|
563
570
|
const p = platform || "claude";
|
package/identity/keystore.mjs
CHANGED
|
@@ -61,7 +61,9 @@ export function persistIdentity(identity) {
|
|
|
61
61
|
if (existsSync(PATH)) {
|
|
62
62
|
try {
|
|
63
63
|
const stamp = Date.now();
|
|
64
|
-
|
|
64
|
+
const bakPath = join(DIR, `${BACKUP_PREFIX}${stamp}`);
|
|
65
|
+
copyFileSync(PATH, bakPath);
|
|
66
|
+
chmodSync(bakPath, 0o600);
|
|
65
67
|
pruneBackups();
|
|
66
68
|
} catch {
|
|
67
69
|
/* best-effort — don't block the write */
|
package/lib/constants.mjs
CHANGED
|
@@ -115,6 +115,18 @@ export const TOKSCALE_CLIENT_MAP = {
|
|
|
115
115
|
*/
|
|
116
116
|
export const TOKSCALE_BLIND_PLATFORMS = [];
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Timeout for `tokscale` CLI invocations (models/clients/graph/report scans).
|
|
120
|
+
* The scan walks every session log on the machine; on heavy machines it runs
|
|
121
|
+
* ~55–60s and GROWS with log volume, so the old hardcoded 60s ceiling flaked —
|
|
122
|
+
* the subprocess was killed mid-scan and the caller silently got null
|
|
123
|
+
* (TUI Compare showed "no data from tokscale"). 180s gives 3x headroom and
|
|
124
|
+
* stays under tokscale's own nativeTimeoutMs default (300s).
|
|
125
|
+
* Override via SIGRANK_TOKSCALE_TIMEOUT_MS.
|
|
126
|
+
*/
|
|
127
|
+
export const TOKSCALE_SCAN_TIMEOUT_MS =
|
|
128
|
+
Number(process.env.SIGRANK_TOKSCALE_TIMEOUT_MS) || 180_000;
|
|
129
|
+
|
|
118
130
|
/**
|
|
119
131
|
* The "actually on the board" predicate. A submission is ranked only when the
|
|
120
132
|
* server returns HTTP ok AND verification_tier === "verified" AND persisted
|
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json",
|
|
3
3
|
"manifest_version": "0.4",
|
|
4
4
|
"name": "sigrank",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.46",
|
|
6
6
|
"description": "Ranks AI coding operators by token cascade efficiency (Yield = cache_read \u00d7 output / input\u00b2). 25 tools including SigRank Standard record, 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
7
|
"author": {
|
|
8
8
|
"name": "SunrisesIllNeverSee",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigrank",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.46",
|
|
4
4
|
"author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
|
|
5
5
|
"description": "Upsilon measurement engine distributed through the sigrank CLI/MCP compatibility package, with SigRank public leaderboard tools.",
|
|
6
6
|
"mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
"on-device"
|
|
82
82
|
],
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"token-cascade": "
|
|
85
|
-
"ccusage": "
|
|
86
|
-
"tokscale": "
|
|
87
|
-
"@modelcontextprotocol/server": "
|
|
84
|
+
"token-cascade": "0.2.1",
|
|
85
|
+
"ccusage": "20.0.30",
|
|
86
|
+
"tokscale": "4.18.0",
|
|
87
|
+
"@modelcontextprotocol/server": "2.3.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@resvg/resvg-js": "^2.6.2",
|
package/presentation/cli.mjs
CHANGED
|
@@ -29,7 +29,11 @@ import { cascade, classify, CLASS_TIERS, UNCLASSED, tierOf } from "../cascade.mj
|
|
|
29
29
|
import { ALL_PLATFORMS } from "../adapters.mjs";
|
|
30
30
|
import { ensureIdentity, keystorePath } from "../keystore.mjs";
|
|
31
31
|
import { submitSignedWindow } from "../submit.mjs";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
LEADERBOARD_METRIC,
|
|
34
|
+
TOKSCALE_CLIENT_MAP,
|
|
35
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
36
|
+
} from "../lib/constants.mjs";
|
|
33
37
|
import { execFile } from "child_process";
|
|
34
38
|
import { existsSync, readFileSync } from "fs";
|
|
35
39
|
import os from "os";
|
|
@@ -422,7 +426,11 @@ async function ccusagePillars(platform = "claude") {
|
|
|
422
426
|
async function tokscalePillars(platform = "claude") {
|
|
423
427
|
// Try the bundled tokscale CLI first (fresh data), fall back to saved report file.
|
|
424
428
|
try {
|
|
425
|
-
const raw = await execFileAsync(
|
|
429
|
+
const raw = await execFileAsync(
|
|
430
|
+
"tokscale",
|
|
431
|
+
["models", "--json"],
|
|
432
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
433
|
+
);
|
|
426
434
|
const data = JSON.parse(raw);
|
|
427
435
|
const entries = Array.isArray(data?.entries)
|
|
428
436
|
? data.entries
|
|
@@ -1711,10 +1719,14 @@ async function runEnroll({ label } = {}) {
|
|
|
1711
1719
|
|
|
1712
1720
|
let out;
|
|
1713
1721
|
try {
|
|
1714
|
-
out = await callTool(
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1722
|
+
out = await callTool(
|
|
1723
|
+
"enroll",
|
|
1724
|
+
{
|
|
1725
|
+
code,
|
|
1726
|
+
device_label: typeof label === "string" ? label : undefined,
|
|
1727
|
+
},
|
|
1728
|
+
{ allowReenroll: true }, // human at a TTY already consented above
|
|
1729
|
+
);
|
|
1718
1730
|
} catch (e) {
|
|
1719
1731
|
writeln(red(` ✗ ${e.message}`));
|
|
1720
1732
|
process.exitCode = 1;
|
|
@@ -1770,6 +1782,7 @@ async function runSubmit({ platform = "claude", window } = {}) {
|
|
|
1770
1782
|
platform,
|
|
1771
1783
|
window: typeof window === "string" ? window : undefined,
|
|
1772
1784
|
dry_run: process.argv.includes("--dry-run"),
|
|
1785
|
+
confirm: true, // explicit CLI invocation is the confirmation
|
|
1773
1786
|
});
|
|
1774
1787
|
} catch (e) {
|
|
1775
1788
|
writeln(red(` ✗ ${e.message}`));
|
package/presentation/tui.mjs
CHANGED
|
@@ -1444,7 +1444,7 @@ function renderCompare(data) {
|
|
|
1444
1444
|
const missing = [];
|
|
1445
1445
|
if (!cc) missing.push("ccusage (npm i -g ccusage)");
|
|
1446
1446
|
if (!td) missing.push("token-dashboard (~/.claude/token-dashboard.db)");
|
|
1447
|
-
if (!ts) missing.push("tokscale (
|
|
1447
|
+
if (!ts) missing.push("tokscale (not installed, or scan timed out)");
|
|
1448
1448
|
if (missing.length)
|
|
1449
1449
|
for (const ln of wrapDim(`no data from: ${missing.join(" · ")}`)) emit(ln);
|
|
1450
1450
|
emit();
|
|
@@ -2904,7 +2904,7 @@ export async function runTui({
|
|
|
2904
2904
|
didMulti = false;
|
|
2905
2905
|
for (const p of platforms) {
|
|
2906
2906
|
try {
|
|
2907
|
-
const out = await callTool("submit_verified", { platform: p });
|
|
2907
|
+
const out = await callTool("submit_verified", { platform: p, confirm: true });
|
|
2908
2908
|
const ws = out.windows || [];
|
|
2909
2909
|
const received = ws.filter((w) => w.status === "received");
|
|
2910
2910
|
const verified = received.filter(
|
|
@@ -2924,6 +2924,7 @@ export async function runTui({
|
|
|
2924
2924
|
try {
|
|
2925
2925
|
const out = await callTool("submit_verified", {
|
|
2926
2926
|
platform: "multi",
|
|
2927
|
+
confirm: true,
|
|
2927
2928
|
});
|
|
2928
2929
|
const ws = out.windows || [];
|
|
2929
2930
|
const received = ws.filter((w) => w.status === "received");
|
package/proxy.mjs
CHANGED
|
@@ -311,6 +311,22 @@ export async function startProxy({
|
|
|
311
311
|
};
|
|
312
312
|
|
|
313
313
|
const server = http.createServer(async (req, res) => {
|
|
314
|
+
// DNS-rebinding / CSRF guard: only accept loopback Host headers on our own port, and
|
|
315
|
+
// refuse browser cross-site requests (Sec-Fetch-Site other than same-origin/none).
|
|
316
|
+
const hostHeader = String(req.headers.host || "");
|
|
317
|
+
const hm = /^(127\.0\.0\.1|localhost|\[::1\])(?::(\d+))?$/i.exec(hostHeader);
|
|
318
|
+
const boundPort = server.address()?.port;
|
|
319
|
+
const sfs = req.headers["sec-fetch-site"];
|
|
320
|
+
if (
|
|
321
|
+
!hm ||
|
|
322
|
+
(boundPort && Number(hm[2] || 80) !== boundPort) ||
|
|
323
|
+
(sfs !== undefined && sfs !== "same-origin" && sfs !== "none")
|
|
324
|
+
) {
|
|
325
|
+
res.writeHead(403, { "content-type": "text/plain; charset=utf-8" });
|
|
326
|
+
res.end("Forbidden\n");
|
|
327
|
+
req.resume();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
314
330
|
let parsedUrl;
|
|
315
331
|
try {
|
|
316
332
|
parsedUrl = new URL(req.url || "/", `http://${req.headers.host || host}`);
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## What leaves your machine
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**The usage content of every submission is four numbers:**
|
|
6
6
|
- Input tokens
|
|
7
7
|
- Output tokens
|
|
8
8
|
- Cache create tokens
|
|
9
9
|
- Cache read tokens
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The signed path additionally carries: codename, device id, device public key, ed25519 signature, agent version, platform, message/session counts, and the weekly report block. `enroll` sends a device label (default `sigrank <version> · <platform>`; your hostname is not sent unless you set `SIGRANK_DEVICE_LABEL` or pass `device_label` yourself).
|
|
12
|
+
|
|
13
|
+
Never sent: prompts, responses, code, file contents, file paths, repo or project names, or environment variables. The server re-parses and re-scores the pillars authoritatively.
|
|
12
14
|
|
|
13
15
|
The optional `sigrank proxy` is a local Upsilon transport path to Anthropic/OpenAI, not a
|
|
14
16
|
submission path to the SigRank leaderboard. When explicitly enabled, it necessarily receives and
|
|
@@ -32,8 +34,9 @@ only provider-reported token counts, model/backend metadata, and timestamps to
|
|
|
32
34
|
- Your prompts or messages
|
|
33
35
|
- Your code or file contents
|
|
34
36
|
- Your tool calls or their results
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
+
- Your hostname (unless you opt in via a device label)
|
|
38
|
+
- File paths, repo/project names, or environment variables
|
|
39
|
+
- Your identity (only your chosen codename; the platform name, agent version, and message/session counts are sent)
|
|
37
40
|
|
|
38
41
|
The local proxy process is different from the Upsilon service: if you opt in, it
|
|
39
42
|
handles your provider traffic in memory solely to forward it to Anthropic or
|
package/tokscale_analytics.mjs
CHANGED
|
@@ -33,7 +33,10 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
33
33
|
import os from "node:os";
|
|
34
34
|
import path from "node:path";
|
|
35
35
|
import { fileURLToPath } from "node:url";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
TOKSCALE_CLIENT_MAP,
|
|
38
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
39
|
+
} from "./lib/constants.mjs";
|
|
37
40
|
|
|
38
41
|
// Resolve local node_modules/.bin for the bundled tokscale binary.
|
|
39
42
|
const _pkgRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -61,13 +64,14 @@ function execFileAsync(cmd, args, timeoutMs) {
|
|
|
61
64
|
});
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
// tokscale subcommand timeouts. `models`
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
67
|
+
// tokscale subcommand timeouts. `models`/`clients`/`graph`/`report` all walk the
|
|
68
|
+
// session logs — on a heavy machine the scan runs ~55-60s and grows with log
|
|
69
|
+
// volume, so they share TOKSCALE_SCAN_TIMEOUT_MS (was a flat 60s that flaked).
|
|
70
|
+
const T_MODELS = TOKSCALE_SCAN_TIMEOUT_MS;
|
|
71
|
+
const T_MONTHLY = TOKSCALE_SCAN_TIMEOUT_MS;
|
|
72
|
+
const T_CLIENTS = TOKSCALE_SCAN_TIMEOUT_MS;
|
|
73
|
+
const T_GRAPH = TOKSCALE_SCAN_TIMEOUT_MS;
|
|
74
|
+
const T_REPORT = TOKSCALE_SCAN_TIMEOUT_MS;
|
|
71
75
|
|
|
72
76
|
const HOME = os.homedir();
|
|
73
77
|
|
|
@@ -352,7 +356,6 @@ export async function tokscaleDeveloperProfile() {
|
|
|
352
356
|
sessions_path_exists: c.sessions_path_exists ?? null,
|
|
353
357
|
headless_supported: c.headless_supported ?? null,
|
|
354
358
|
workspace_count: c.workspaces.size,
|
|
355
|
-
workspaces: [...c.workspaces].sort(),
|
|
356
359
|
models: c.models,
|
|
357
360
|
};
|
|
358
361
|
});
|
package/tools/_helpers.mjs
CHANGED
|
@@ -32,6 +32,8 @@ const ALLOWED_API_HOSTS = new Set([
|
|
|
32
32
|
"sigeconomy.com",
|
|
33
33
|
"localhost",
|
|
34
34
|
"127.0.0.1",
|
|
35
|
+
"::1",
|
|
36
|
+
"[::1]",
|
|
35
37
|
"test.local", // test fixtures only
|
|
36
38
|
]);
|
|
37
39
|
|
|
@@ -42,6 +44,10 @@ export function validateApiBase(urlStr) {
|
|
|
42
44
|
if (!ALLOWED_API_HOSTS.has(u.hostname)) {
|
|
43
45
|
throw new Error(`Blocked: API host '${u.hostname}' is not in the allowlist`);
|
|
44
46
|
}
|
|
47
|
+
const loopback = ["localhost", "127.0.0.1", "::1", "[::1]", "test.local"].includes(u.hostname);
|
|
48
|
+
if (u.protocol !== "https:" && !(loopback && u.protocol === "http:")) {
|
|
49
|
+
throw new Error(`Blocked: API base must use https (http only for loopback): ${u.protocol}//${u.hostname}`);
|
|
50
|
+
}
|
|
45
51
|
return urlStr;
|
|
46
52
|
} catch (e) {
|
|
47
53
|
if (e.message.startsWith("Blocked:")) throw e;
|
package/tools/_verifiers.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
10
10
|
import os from "node:os";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { execFileAsync } from "./_helpers.mjs";
|
|
13
|
+
import { TOKSCALE_SCAN_TIMEOUT_MS } from "../lib/constants.mjs";
|
|
13
14
|
|
|
14
15
|
async function _ccusagePillars(platform = "claude") {
|
|
15
16
|
try {
|
|
@@ -87,7 +88,11 @@ async function _tokenDashPillars() {
|
|
|
87
88
|
async function _tokscalePillars(platform = "claude") {
|
|
88
89
|
// Try the bundled tokscale CLI first (fresh data), fall back to saved report file.
|
|
89
90
|
try {
|
|
90
|
-
const raw = await execFileAsync(
|
|
91
|
+
const raw = await execFileAsync(
|
|
92
|
+
"tokscale",
|
|
93
|
+
["models", "--json"],
|
|
94
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
95
|
+
);
|
|
91
96
|
const data = JSON.parse(raw);
|
|
92
97
|
const entries = Array.isArray(data?.entries)
|
|
93
98
|
? data.entries
|
package/tools/enroll.mjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* tools/enroll.mjs — enroll tool.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import os from "node:os";
|
|
6
5
|
import { ensureIdentity, recordEnrollment } from "../identity/keystore.mjs";
|
|
7
6
|
import { ENROLL_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
8
7
|
import { TERMS_VERSION, PRIVACY_VERSION } from "../lib/constants.mjs";
|
|
@@ -28,7 +27,7 @@ export const TOOL_DEF = {
|
|
|
28
27
|
device_label: {
|
|
29
28
|
type: "string",
|
|
30
29
|
description:
|
|
31
|
-
"optional label for this device (default:
|
|
30
|
+
"optional label for this device (default: sigrank <version> · <platform>; hostnames are never sent unless you pass a label yourself)",
|
|
32
31
|
},
|
|
33
32
|
},
|
|
34
33
|
required: ["code"],
|
|
@@ -45,8 +44,23 @@ export async function handleEnroll(args, ctx) {
|
|
|
45
44
|
"enroll requires a `code` — paste your connect code from signalaf.com → Settings → Connect a device.",
|
|
46
45
|
);
|
|
47
46
|
const id = ctx.opts.identity || ensureIdentity();
|
|
47
|
+
// Re-enroll guard: a model-invoked MCP call must never silently re-bind a device that
|
|
48
|
+
// already has an operator. The human CLI/TUI path sets opts.allowReenroll after its own
|
|
49
|
+
// explicit consent prompt.
|
|
50
|
+
if (id.codename && !ctx.opts.allowReenroll) {
|
|
51
|
+
return {
|
|
52
|
+
status: "already_enrolled",
|
|
53
|
+
codename: id.codename,
|
|
54
|
+
operator_id: id.operator_id ?? null,
|
|
55
|
+
device_id: id.device_id,
|
|
56
|
+
detail:
|
|
57
|
+
"This device is already enrolled. To re-enroll, run `npx sigrank enroll` in a terminal yourself; the MCP tool will not re-bind an enrolled device.",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
48
60
|
const deviceLabel = String(
|
|
49
|
-
args?.device_label ||
|
|
61
|
+
args?.device_label ||
|
|
62
|
+
process.env.SIGRANK_DEVICE_LABEL ||
|
|
63
|
+
`sigrank ${id.agent_version} · ${process.platform}`,
|
|
50
64
|
).slice(0, 120);
|
|
51
65
|
const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/devices/enroll`, {
|
|
52
66
|
method: "POST",
|
package/tools/index.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { buildFetch, DEFAULT_API_BASE, DEFAULT_FETCH_TIMEOUT } from "./_helpers.
|
|
|
12
12
|
import {
|
|
13
13
|
TOKSCALE_CLIENT_MAP,
|
|
14
14
|
TOKSCALE_BLIND_PLATFORMS,
|
|
15
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
15
16
|
} from "../lib/constants.mjs";
|
|
16
17
|
|
|
17
18
|
import { TOOL_DEF as rankPasteTool, handleRankPaste } from "./rank-paste.mjs";
|
|
@@ -83,7 +84,11 @@ export const TOOLS = [
|
|
|
83
84
|
|
|
84
85
|
/** Run `tokscale models --json` and return the list of our platform names. */
|
|
85
86
|
async function _tokscaleDetectClients() {
|
|
86
|
-
const raw = await execFileAsync(
|
|
87
|
+
const raw = await execFileAsync(
|
|
88
|
+
"tokscale",
|
|
89
|
+
["models", "--json"],
|
|
90
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
91
|
+
);
|
|
87
92
|
const data = JSON.parse(raw);
|
|
88
93
|
const entries = Array.isArray(data?.entries)
|
|
89
94
|
? data.entries
|
|
@@ -12,7 +12,7 @@ import { _computeReportBlock } from "./_report.mjs";
|
|
|
12
12
|
export const TOOL_DEF = {
|
|
13
13
|
name: "submit_verified",
|
|
14
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.
|
|
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. Publishing requires `confirm: true` (ask the user first); pass dry_run:true to inspect the exact signed payload without publishing and without confirm.",
|
|
16
16
|
annotations: {
|
|
17
17
|
title: "Submit verified score",
|
|
18
18
|
...ANNOTATIONS.destructiveHint,
|
|
@@ -33,6 +33,11 @@ export const TOOL_DEF = {
|
|
|
33
33
|
description:
|
|
34
34
|
"source platform (default: claude). 'multi' = combined cascade summed across all locally-detected platforms (needs 2+ active); empty windows are skipped.",
|
|
35
35
|
},
|
|
36
|
+
confirm: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description:
|
|
39
|
+
"REQUIRED to publish: must be true, set only after the user has explicitly approved submitting. Not needed with dry_run:true.",
|
|
40
|
+
},
|
|
36
41
|
dry_run: {
|
|
37
42
|
type: "boolean",
|
|
38
43
|
description:
|
|
@@ -52,6 +57,13 @@ export async function handleSubmitVerified(args, ctx) {
|
|
|
52
57
|
detail: "Run `npx sigrank-mcp enroll` to bind this device first.",
|
|
53
58
|
};
|
|
54
59
|
}
|
|
60
|
+
if (!args?.dry_run && args?.confirm !== true) {
|
|
61
|
+
return {
|
|
62
|
+
status: "confirmation_required",
|
|
63
|
+
detail:
|
|
64
|
+
"submit_verified publishes to the public board. Ask the user for approval, then call again with confirm: true (or use dry_run: true to preview).",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
55
67
|
const platform = args?.platform || "claude";
|
|
56
68
|
|
|
57
69
|
// MULTI: the combined cross-platform cascade. The dashboard already SUMS every
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { execFileAsync } from "./_helpers.mjs";
|
|
6
6
|
import { ANNOTATIONS } from "./_schemas.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
TOKSCALE_CLIENT_MAP,
|
|
9
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
10
|
+
} from "../lib/constants.mjs";
|
|
8
11
|
|
|
9
12
|
export const TOOL_DEF = {
|
|
10
13
|
name: "tokscale_breakdown",
|
|
@@ -47,7 +50,11 @@ export const TOOL_DEF = {
|
|
|
47
50
|
* Returns: { platform: [{ model, input, output, cacheRead, cacheCreate, pct }] }
|
|
48
51
|
*/
|
|
49
52
|
export async function tokscaleModelBreakdown(threshold = 0.01) {
|
|
50
|
-
const raw = await execFileAsync(
|
|
53
|
+
const raw = await execFileAsync(
|
|
54
|
+
"tokscale",
|
|
55
|
+
["models", "--json"],
|
|
56
|
+
TOKSCALE_SCAN_TIMEOUT_MS,
|
|
57
|
+
);
|
|
51
58
|
const data = JSON.parse(raw);
|
|
52
59
|
const entries = Array.isArray(data?.entries)
|
|
53
60
|
? data.entries
|
|
@@ -20,7 +20,7 @@ export const TOOL_DEF = {
|
|
|
20
20
|
properties: {
|
|
21
21
|
tools: {
|
|
22
22
|
type: "array",
|
|
23
|
-
description: "Per-tool profile with model mix, token pillars, session counts,
|
|
23
|
+
description: "Per-tool profile with model mix, token pillars, session counts, workspace count",
|
|
24
24
|
},
|
|
25
25
|
summary: {
|
|
26
26
|
type: "object",
|