sigrank 1.0.45 → 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 +14 -0
- package/README.md +6 -3
- package/adapters/index.mjs +2 -0
- package/adapters/tokenpull.mjs +1 -1
- package/identity/keystore.mjs +3 -1
- package/manifest.json +1 -1
- package/package.json +3 -3
- package/presentation/cli.mjs +9 -4
- package/presentation/tui.mjs +2 -1
- package/proxy.mjs +16 -0
- package/resources/privacy-model.md +7 -4
- package/tokscale_analytics.mjs +0 -1
- package/tools/_helpers.mjs +6 -0
- package/tools/enroll.mjs +17 -3
- package/tools/submit-verified.mjs +13 -1
- package/tools/tokscale-developer-profile.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
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
|
+
|
|
3
17
|
## [1.0.44](https://github.com/SunrisesIllNeverSee/sigrank-mcp/releases/tag/v1.0.44) (2026-10-05)
|
|
4
18
|
|
|
5
19
|
### Features
|
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
|
|
|
@@ -314,7 +314,7 @@ Or if installed globally:
|
|
|
314
314
|
| `self_improve` | `{text?}` | One-click optimize: diagnoses, suggests, and simulates the best change in a single call |
|
|
315
315
|
| `tokscale_breakdown(threshold?)` | `{threshold?}` | Per-model token breakdown across platforms (models under threshold → "other") |
|
|
316
316
|
| `tokscale_market_share()` | `{}` | AI tool market share: each tool's % of tokens/cost/messages, ranked. From local tokscale data |
|
|
317
|
-
| `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 |
|
|
318
318
|
| `tokscale_model_trends()` | `{}` | Model adoption over time: per-model first/last seen, active days, month-by-month adoption curve |
|
|
319
319
|
| `tokscale_cost_analysis()` | `{}` | Cost per developer per model: cost_per_million_tokens, cost_per_message, share_cost, client rollup |
|
|
320
320
|
| `tokscale_device_profile()` | `{}` | Device fingerprinting: installed tools, session counts, active days, day-of-week distribution, concurrency. Paths redacted |
|
|
@@ -405,6 +405,8 @@ All adapters are token-only (no message content, no cost fields, no credentials)
|
|
|
405
405
|
|
|
406
406
|
## Privacy
|
|
407
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.
|
|
408
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.
|
|
409
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.
|
|
410
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.
|
|
@@ -417,7 +419,8 @@ All adapters are token-only (no message content, no cost fields, no credentials)
|
|
|
417
419
|
|
|
418
420
|
| Var | Default | Description |
|
|
419
421
|
| ----------------------- | ---------------------- | ---------------------------- |
|
|
420
|
-
| `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 |
|
|
421
424
|
| `SIGRANK_FETCH_TIMEOUT` | `10000` | Board API fetch timeout (ms) |
|
|
422
425
|
|
|
423
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
|
@@ -564,7 +564,7 @@ async function _freshTokscale(platform = "claude") {
|
|
|
564
564
|
*
|
|
565
565
|
* - ccusage : `ccusage <platform> daily --json` → 7d/30d/90d/all
|
|
566
566
|
* - tokendash: refresh + read ~/.claude/token-dashboard.db → all (claude only)
|
|
567
|
-
* - tokscale : `
|
|
567
|
+
* - tokscale : `tokscale models --json` (bundled, pinned) → all
|
|
568
568
|
*/
|
|
569
569
|
export async function freshVerifierPillars(platform = "claude") {
|
|
570
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/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",
|
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
],
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"token-cascade": "0.2.1",
|
|
85
|
-
"ccusage": "
|
|
86
|
-
"tokscale": "
|
|
85
|
+
"ccusage": "20.0.30",
|
|
86
|
+
"tokscale": "4.18.0",
|
|
87
87
|
"@modelcontextprotocol/server": "2.3.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
package/presentation/cli.mjs
CHANGED
|
@@ -1719,10 +1719,14 @@ async function runEnroll({ label } = {}) {
|
|
|
1719
1719
|
|
|
1720
1720
|
let out;
|
|
1721
1721
|
try {
|
|
1722
|
-
out = await callTool(
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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
|
+
);
|
|
1726
1730
|
} catch (e) {
|
|
1727
1731
|
writeln(red(` ✗ ${e.message}`));
|
|
1728
1732
|
process.exitCode = 1;
|
|
@@ -1778,6 +1782,7 @@ async function runSubmit({ platform = "claude", window } = {}) {
|
|
|
1778
1782
|
platform,
|
|
1779
1783
|
window: typeof window === "string" ? window : undefined,
|
|
1780
1784
|
dry_run: process.argv.includes("--dry-run"),
|
|
1785
|
+
confirm: true, // explicit CLI invocation is the confirmation
|
|
1781
1786
|
});
|
|
1782
1787
|
} catch (e) {
|
|
1783
1788
|
writeln(red(` ✗ ${e.message}`));
|
package/presentation/tui.mjs
CHANGED
|
@@ -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
|
@@ -356,7 +356,6 @@ export async function tokscaleDeveloperProfile() {
|
|
|
356
356
|
sessions_path_exists: c.sessions_path_exists ?? null,
|
|
357
357
|
headless_supported: c.headless_supported ?? null,
|
|
358
358
|
workspace_count: c.workspaces.size,
|
|
359
|
-
workspaces: [...c.workspaces].sort(),
|
|
360
359
|
models: c.models,
|
|
361
360
|
};
|
|
362
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/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",
|
|
@@ -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
|
|
@@ -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",
|