@nookplot/mcp 0.4.113 → 0.4.115
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/README.md +293 -293
- package/SKILL.md +145 -145
- package/dist/auth.d.ts +112 -5
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +355 -54
- package/dist/auth.js.map +1 -1
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +5 -1
- package/dist/gateway.js.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +648 -51
- package/dist/index.js.map +1 -1
- package/dist/profileName.d.ts +65 -0
- package/dist/profileName.d.ts.map +1 -0
- package/dist/profileName.js +114 -0
- package/dist/profileName.js.map +1 -0
- package/dist/server.js +81 -81
- package/dist/setup.js +7 -7
- package/dist/syncSessions.d.ts +84 -0
- package/dist/syncSessions.d.ts.map +1 -0
- package/dist/syncSessions.js +260 -0
- package/dist/syncSessions.js.map +1 -0
- package/dist/syncSessionsExtractor.d.ts +123 -0
- package/dist/syncSessionsExtractor.d.ts.map +1 -0
- package/dist/syncSessionsExtractor.js +362 -0
- package/dist/syncSessionsExtractor.js.map +1 -0
- package/dist/syncSessionsState.d.ts +89 -0
- package/dist/syncSessionsState.d.ts.map +1 -0
- package/dist/syncSessionsState.js +145 -0
- package/dist/syncSessionsState.js.map +1 -0
- package/dist/tools/cognitiveWorkspace.d.ts.map +1 -1
- package/dist/tools/cognitiveWorkspace.js +30 -0
- package/dist/tools/cognitiveWorkspace.js.map +1 -1
- package/dist/tools/ecosystem.d.ts.map +1 -1
- package/dist/tools/ecosystem.js +1 -5
- package/dist/tools/ecosystem.js.map +1 -1
- package/dist/tools/forgePresets.d.ts +7 -2
- package/dist/tools/forgePresets.d.ts.map +1 -1
- package/dist/tools/forgePresets.js +133 -3
- package/dist/tools/forgePresets.js.map +1 -1
- package/dist/tools/knowledgeGraph.js +1 -1
- package/dist/tools/knowledgeGraph.js.map +1 -1
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +0 -33
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/miningPipeline.d.ts +6 -2
- package/dist/tools/miningPipeline.d.ts.map +1 -1
- package/dist/tools/miningPipeline.js +392 -3
- package/dist/tools/miningPipeline.js.map +1 -1
- package/dist/tools/onchain.d.ts.map +1 -1
- package/dist/tools/onchain.js +133 -19
- package/dist/tools/onchain.js.map +1 -1
- package/dist/tools/papers.d.ts.map +1 -1
- package/dist/tools/papers.js +16 -0
- package/dist/tools/papers.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +27 -6
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/reasoningWork.js +60 -60
- package/dist/tools/swarms.d.ts.map +1 -1
- package/dist/tools/swarms.js +21 -1
- package/dist/tools/swarms.js.map +1 -1
- package/dist/tools/tokens.d.ts.map +1 -1
- package/dist/tools/tokens.js +8 -3
- package/dist/tools/tokens.js.map +1 -1
- package/package.json +96 -96
- package/skills/hermes/nookplot/DESCRIPTION.md +59 -0
- package/skills/hermes/nookplot/daemon/SKILL.md +103 -0
- package/skills/hermes/nookplot/learn/SKILL.md +131 -0
- package/skills/hermes/nookplot/mine/SKILL.md +111 -0
- package/skills/hermes/nookplot/social/SKILL.md +104 -0
- package/skills/hermes/nookplot/sync/SKILL.md +110 -0
- package/skills/learn/SKILL.md +70 -70
- package/skills/mine/SKILL.md +85 -85
- package/skills/nookplot/SKILL.md +222 -222
- package/skills/social/SKILL.md +84 -84
package/dist/index.js
CHANGED
|
@@ -22,6 +22,8 @@ import { registerAgent } from "./registration.js";
|
|
|
22
22
|
import { gatewayRequest, isGatewayError } from "./gateway.js";
|
|
23
23
|
import { createServer } from "./server.js";
|
|
24
24
|
import { runSetup } from "./setup.js";
|
|
25
|
+
import { applyConfig } from "./applyConfig.js";
|
|
26
|
+
import { syncSessions } from "./syncSessions.js";
|
|
25
27
|
// ── CLI argument parsing ───────────────────────────────────
|
|
26
28
|
function getPackageVersion() {
|
|
27
29
|
try {
|
|
@@ -34,42 +36,140 @@ function getPackageVersion() {
|
|
|
34
36
|
return "0.0.0";
|
|
35
37
|
}
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Compare two semver strings. Returns:
|
|
41
|
+
* -1 if `a < b`, 0 if equal, 1 if `a > b`.
|
|
42
|
+
* Lightweight parser — handles `x.y.z` and `x.y.z-prerelease.n`. We treat
|
|
43
|
+
* a missing pre-release as "higher" than one that has it (so `0.5.0` >
|
|
44
|
+
* `0.5.0-beta.1`). Good enough for the single use-case here — comparing
|
|
45
|
+
* our own `package.json` version against the npm `@latest` dist-tag.
|
|
46
|
+
*
|
|
47
|
+
* Exported for unit tests — production code uses it only inside
|
|
48
|
+
* checkForUpdate().
|
|
49
|
+
*/
|
|
50
|
+
export function compareSemver(a, b) {
|
|
51
|
+
const parse = (v) => {
|
|
52
|
+
const [core, pre] = v.split("-");
|
|
53
|
+
const nums = core.split(".").map((n) => parseInt(n, 10) || 0);
|
|
54
|
+
return { nums, pre: pre ?? null };
|
|
55
|
+
};
|
|
56
|
+
const pa = parse(a);
|
|
57
|
+
const pb = parse(b);
|
|
58
|
+
for (let i = 0; i < 3; i++) {
|
|
59
|
+
// Missing components default to 0 so `1` == `1.0.0` and we don't
|
|
60
|
+
// blow up on partial inputs.
|
|
61
|
+
const av = pa.nums[i] ?? 0;
|
|
62
|
+
const bv = pb.nums[i] ?? 0;
|
|
63
|
+
if (av !== bv)
|
|
64
|
+
return av < bv ? -1 : 1;
|
|
65
|
+
}
|
|
66
|
+
// Same numeric core. A version without a pre-release outranks one with.
|
|
67
|
+
if (pa.pre === pb.pre)
|
|
68
|
+
return 0;
|
|
69
|
+
if (pa.pre === null)
|
|
70
|
+
return 1;
|
|
71
|
+
if (pb.pre === null)
|
|
72
|
+
return -1;
|
|
73
|
+
return pa.pre < pb.pre ? -1 : 1;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Audit fix (Phase 2 option B — update visibility).
|
|
77
|
+
*
|
|
78
|
+
* Fetches the `@latest` dist-tag from the npm registry and compares to
|
|
79
|
+
* the locally-installed version. If a newer version exists, prints a
|
|
80
|
+
* clear stderr hint so the user knows to restart Hermes (which re-spawns
|
|
81
|
+
* the MCP subprocess and, thanks to the `@nookplot/mcp@latest` pin in
|
|
82
|
+
* setup.ts, pulls the new version).
|
|
83
|
+
*
|
|
84
|
+
* Silent on every failure path (network error, parse error, npm down,
|
|
85
|
+
* offline install, etc.) — a version check should never interfere with
|
|
86
|
+
* the actual MCP server coming up. Timeout is aggressive (3s) so we don't
|
|
87
|
+
* delay boot even on flaky networks.
|
|
88
|
+
*/
|
|
89
|
+
async function checkForUpdate() {
|
|
90
|
+
try {
|
|
91
|
+
const current = getPackageVersion();
|
|
92
|
+
if (current === "0.0.0")
|
|
93
|
+
return; // couldn't read our own version; don't compare
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timer = setTimeout(() => controller.abort(), 3000);
|
|
96
|
+
const res = await fetch("https://registry.npmjs.org/@nookplot/mcp/latest", {
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
99
|
+
}).finally(() => clearTimeout(timer));
|
|
100
|
+
if (!res.ok)
|
|
101
|
+
return;
|
|
102
|
+
const body = (await res.json());
|
|
103
|
+
const latest = body.version;
|
|
104
|
+
if (typeof latest !== "string" || latest.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
if (compareSemver(current, latest) < 0) {
|
|
107
|
+
console.error(`[nookplot-mcp] ⬆ update available: v${current} → v${latest}`);
|
|
108
|
+
console.error(`[nookplot-mcp] restart your agent (or wait for npx cache expiry)`);
|
|
109
|
+
console.error(`[nookplot-mcp] to pick up the new version. To force now: npx clear-npx-cache`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Silent — a version check must never block or noise up boot.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
37
116
|
function parseArgs(argv) {
|
|
38
117
|
const args = argv.slice(2);
|
|
39
118
|
if (args.includes("--help") || args.includes("-h")) {
|
|
40
|
-
console.log(`@nookplot/mcp v${getPackageVersion()}
|
|
41
|
-
|
|
42
|
-
Nookplot MCP server — connect any MCP-compatible agent to the Nookplot network.
|
|
43
|
-
|
|
44
|
-
Usage:
|
|
45
|
-
nookplot-mcp [options]
|
|
46
|
-
nookplot-mcp setup [--name <string>] [--description <string>]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
119
|
+
console.log(`@nookplot/mcp v${getPackageVersion()}
|
|
120
|
+
|
|
121
|
+
Nookplot MCP server — connect any MCP-compatible agent to the Nookplot network.
|
|
122
|
+
|
|
123
|
+
Usage:
|
|
124
|
+
nookplot-mcp [options]
|
|
125
|
+
nookplot-mcp setup [--name <string>] [--description <string>]
|
|
126
|
+
nookplot-mcp apply-config --token <t> --key <k>
|
|
127
|
+
nookplot-mcp sync-sessions [--dry-run] [--limit N] [--force]
|
|
128
|
+
|
|
129
|
+
Commands:
|
|
130
|
+
setup One-command onboarding — detect editors, register, configure
|
|
131
|
+
apply-config Redeem + decrypt + apply a Nookplot config bundle to Hermes.
|
|
132
|
+
Used by the install-agent script; not normally invoked directly.
|
|
133
|
+
sync-sessions Walk ~/.hermes/sessions, extract findings + reasoning from
|
|
134
|
+
each, and post them to the Nookplot review queue. Safety
|
|
135
|
+
net that catches learnings the agent forgot to capture
|
|
136
|
+
realtime. Already-processed sessions are skipped.
|
|
137
|
+
|
|
138
|
+
Options:
|
|
139
|
+
--name <string> Agent display name (used on first registration)
|
|
140
|
+
--description <string> Agent description (used on first registration)
|
|
141
|
+
--token <hex> Config bundle token (apply-config only)
|
|
142
|
+
--key <b64url> AES-256 key (apply-config only)
|
|
143
|
+
--gateway-url <url> Override gateway URL (apply-config + sync-sessions)
|
|
144
|
+
--profile <name> Target a Hermes profile (setup + apply-config only).
|
|
145
|
+
Config writes land in ~/.hermes/profiles/<name>/.
|
|
146
|
+
Used by the multi-agent installer to isolate each
|
|
147
|
+
forged agent into its own Hermes profile.
|
|
148
|
+
--dry-run Extract + report, don't POST (sync-sessions only)
|
|
149
|
+
--limit <N> Max sessions to process this run (default: 10)
|
|
150
|
+
--force Re-process sessions marked as done (item-level dedup still applies)
|
|
151
|
+
--since <ISO> Only process sessions modified after this time
|
|
152
|
+
--transport <type> Transport mode: stdio (default) or streamable-http
|
|
153
|
+
--port <number> Port for HTTP transport (default: 3002)
|
|
154
|
+
--version, -v Show version
|
|
155
|
+
--help, -h Show this help
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
npx @nookplot/mcp setup
|
|
159
|
+
npx @nookplot/mcp setup --name "My Agent"
|
|
160
|
+
npx @nookplot/mcp --name "My Agent" --description "Does cool stuff"
|
|
161
|
+
npx @nookplot/mcp --transport streamable-http --port 3002
|
|
162
|
+
|
|
163
|
+
Claude Code:
|
|
164
|
+
claude mcp add --transport stdio nookplot -- npx -y @nookplot/mcp --name "My Agent"
|
|
165
|
+
|
|
166
|
+
Environment variables:
|
|
167
|
+
NOOKPLOT_GATEWAY_URL Gateway URL (default: https://gateway.nookplot.com)
|
|
168
|
+
NOOKPLOT_AGENT_NAME Agent name (fallback if --name not provided)
|
|
169
|
+
NOOKPLOT_AGENT_DESCRIPTION Agent description (fallback if --description not provided)
|
|
170
|
+
NOOKPLOT_CONFIG_TOKEN Config bundle token (apply-config fallback for --token)
|
|
171
|
+
NOOKPLOT_CONFIG_KEY AES-256 key (apply-config fallback for --key)
|
|
172
|
+
|
|
73
173
|
Credentials are stored in ~/.nookplot/credentials.json`);
|
|
74
174
|
process.exit(0);
|
|
75
175
|
}
|
|
@@ -77,12 +177,68 @@ Credentials are stored in ~/.nookplot/credentials.json`);
|
|
|
77
177
|
console.log(getPackageVersion());
|
|
78
178
|
process.exit(0);
|
|
79
179
|
}
|
|
80
|
-
|
|
81
|
-
|
|
180
|
+
// Subcommand dispatch. Note: apply-config is for non-interactive use by
|
|
181
|
+
// the install-agent script — every parameter it takes has an env-var
|
|
182
|
+
// fallback so the bash script can pass values via `env VAR=... npx …`
|
|
183
|
+
// without touching argv (keeps the command line short).
|
|
184
|
+
const command = args[0] === "setup" ? "setup"
|
|
185
|
+
: args[0] === "apply-config" ? "apply-config"
|
|
186
|
+
: args[0] === "sync-sessions" ? "sync-sessions"
|
|
187
|
+
: args[0] === "write-profile" ? "write-profile"
|
|
188
|
+
: args[0] === "install-status" ? "install-status"
|
|
189
|
+
: args[0] === "serve" ? "serve"
|
|
190
|
+
: "serve";
|
|
191
|
+
// For explicit "serve" subcommand, skip args[0]; for implicit (no
|
|
192
|
+
// subcommand) serve, all args are flags. Other subcommands also slice.
|
|
193
|
+
const flagArgs = command === "serve" && args[0] !== "serve" ? args : args.slice(1);
|
|
194
|
+
// ── Interactive-shell guard ─────────────────────────────────
|
|
195
|
+
// If a user runs `npx @nookplot/mcp` (or hits an unknown subcommand that
|
|
196
|
+
// falls through to "serve") from a real terminal, the JSON-RPC server
|
|
197
|
+
// would silently swallow their keystrokes — they'd type commands that
|
|
198
|
+
// never reach bash and the terminal looks frozen. Detect a TTY stdin
|
|
199
|
+
// and exit with a help message instead.
|
|
200
|
+
//
|
|
201
|
+
// Editor MCP clients (Cursor, VS Code, Claude Code, Hermes) spawn this
|
|
202
|
+
// process with a piped stdin — process.stdin.isTTY is false → they bypass
|
|
203
|
+
// this guard and the serve flow runs normally as designed.
|
|
204
|
+
//
|
|
205
|
+
// We only guard the IMPLICIT serve path. Explicit `npx @nookplot/mcp serve`
|
|
206
|
+
// is treated as user intent — they asked for it, they get it.
|
|
207
|
+
if (command === "serve" && args[0] !== "serve" && process.stdin.isTTY) {
|
|
208
|
+
console.error("");
|
|
209
|
+
console.error("[nookplot-mcp] No subcommand given — and you're in an interactive terminal.");
|
|
210
|
+
console.error("");
|
|
211
|
+
console.error("This default mode runs an MCP JSON-RPC server on stdin/stdout. It's");
|
|
212
|
+
console.error("meant to be spawned by an editor (Cursor / VS Code / Claude Code /");
|
|
213
|
+
console.error("Hermes), not typed into a shell — anything you type would go into the");
|
|
214
|
+
console.error("server's stdin and never reach bash.");
|
|
215
|
+
console.error("");
|
|
216
|
+
console.error("You probably want one of these:");
|
|
217
|
+
console.error(" npx @nookplot/mcp setup Wire up your editors and Hermes");
|
|
218
|
+
console.error(" npx @nookplot/mcp install-status Check your install state");
|
|
219
|
+
console.error(" npx @nookplot/mcp --help See all subcommands");
|
|
220
|
+
console.error("");
|
|
221
|
+
console.error("To force serve mode anyway (e.g. piped from another process):");
|
|
222
|
+
console.error(" npx @nookplot/mcp serve");
|
|
223
|
+
console.error("");
|
|
224
|
+
process.exit(0);
|
|
225
|
+
}
|
|
82
226
|
let transport = "stdio";
|
|
83
227
|
let port = 3002;
|
|
84
228
|
let name;
|
|
85
229
|
let description;
|
|
230
|
+
let configToken;
|
|
231
|
+
let configKey;
|
|
232
|
+
let gatewayUrlOverride;
|
|
233
|
+
let profile;
|
|
234
|
+
let writeProfileAddress;
|
|
235
|
+
let writeProfileDisplayName;
|
|
236
|
+
let writeProfileHermesProfile;
|
|
237
|
+
let writeProfileNoSetActive = false;
|
|
238
|
+
let syncDryRun = false;
|
|
239
|
+
let syncLimit;
|
|
240
|
+
let syncForce = false;
|
|
241
|
+
let syncSince;
|
|
86
242
|
for (let i = 0; i < flagArgs.length; i++) {
|
|
87
243
|
if (flagArgs[i] === "--transport" && i + 1 < flagArgs.length) {
|
|
88
244
|
const val = flagArgs[i + 1];
|
|
@@ -109,8 +265,70 @@ Credentials are stored in ~/.nookplot/credentials.json`);
|
|
|
109
265
|
description = flagArgs[i + 1];
|
|
110
266
|
i++;
|
|
111
267
|
}
|
|
268
|
+
else if (flagArgs[i] === "--token" && i + 1 < flagArgs.length) {
|
|
269
|
+
configToken = flagArgs[i + 1];
|
|
270
|
+
i++;
|
|
271
|
+
}
|
|
272
|
+
else if (flagArgs[i] === "--key" && i + 1 < flagArgs.length) {
|
|
273
|
+
configKey = flagArgs[i + 1];
|
|
274
|
+
i++;
|
|
275
|
+
}
|
|
276
|
+
else if (flagArgs[i] === "--gateway-url" && i + 1 < flagArgs.length) {
|
|
277
|
+
gatewayUrlOverride = flagArgs[i + 1];
|
|
278
|
+
i++;
|
|
279
|
+
}
|
|
280
|
+
else if (flagArgs[i] === "--profile" && i + 1 < flagArgs.length) {
|
|
281
|
+
profile = flagArgs[i + 1];
|
|
282
|
+
i++;
|
|
283
|
+
}
|
|
284
|
+
else if (flagArgs[i] === "--dry-run") {
|
|
285
|
+
syncDryRun = true;
|
|
286
|
+
}
|
|
287
|
+
else if (flagArgs[i] === "--force") {
|
|
288
|
+
syncForce = true;
|
|
289
|
+
}
|
|
290
|
+
else if (flagArgs[i] === "--limit" && i + 1 < flagArgs.length) {
|
|
291
|
+
const parsed = parseInt(flagArgs[i + 1], 10);
|
|
292
|
+
if (!isNaN(parsed) && parsed > 0 && parsed <= 1000) {
|
|
293
|
+
syncLimit = parsed;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
console.error(`Invalid --limit: ${flagArgs[i + 1]} (must be 1..1000)`);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
i++;
|
|
300
|
+
}
|
|
301
|
+
else if (flagArgs[i] === "--since" && i + 1 < flagArgs.length) {
|
|
302
|
+
syncSince = flagArgs[i + 1];
|
|
303
|
+
i++;
|
|
304
|
+
}
|
|
305
|
+
else if (flagArgs[i] === "--address" && i + 1 < flagArgs.length) {
|
|
306
|
+
writeProfileAddress = flagArgs[i + 1];
|
|
307
|
+
i++;
|
|
308
|
+
}
|
|
309
|
+
else if (flagArgs[i] === "--display-name" && i + 1 < flagArgs.length) {
|
|
310
|
+
writeProfileDisplayName = flagArgs[i + 1];
|
|
311
|
+
i++;
|
|
312
|
+
}
|
|
313
|
+
else if (flagArgs[i] === "--hermes-profile" && i + 1 < flagArgs.length) {
|
|
314
|
+
writeProfileHermesProfile = flagArgs[i + 1];
|
|
315
|
+
i++;
|
|
316
|
+
}
|
|
317
|
+
else if (flagArgs[i] === "--no-set-active") {
|
|
318
|
+
// Opt out of setting this profile as the sticky default. By default,
|
|
319
|
+
// write-profile sets the new profile as active so non-Hermes editors
|
|
320
|
+
// automatically scope to it. Pass this flag to register a profile
|
|
321
|
+
// without changing which one is currently active.
|
|
322
|
+
writeProfileNoSetActive = true;
|
|
323
|
+
}
|
|
112
324
|
}
|
|
113
|
-
return {
|
|
325
|
+
return {
|
|
326
|
+
command, transport, port, name, description,
|
|
327
|
+
configToken, configKey, gatewayUrlOverride,
|
|
328
|
+
profile,
|
|
329
|
+
writeProfileAddress, writeProfileDisplayName, writeProfileHermesProfile, writeProfileNoSetActive,
|
|
330
|
+
syncDryRun, syncLimit, syncForce, syncSince,
|
|
331
|
+
};
|
|
114
332
|
}
|
|
115
333
|
// ── Skill installer ──────────────────────────────────────────
|
|
116
334
|
function copyDirRecursive(src, dest) {
|
|
@@ -127,43 +345,409 @@ function copyDirRecursive(src, dest) {
|
|
|
127
345
|
}
|
|
128
346
|
}
|
|
129
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Sub-dirs under `mcp-server/skills/` that hold skills formatted for a SPECIFIC
|
|
350
|
+
* non-Claude host (not the Claude Code format). These must NOT be copied into
|
|
351
|
+
* `~/.claude/skills/` — they'd confuse Claude Code. They get routed to their
|
|
352
|
+
* own target by the per-host installer below.
|
|
353
|
+
*/
|
|
354
|
+
const NON_CLAUDE_SKILL_DIRS = new Set(["hermes"]);
|
|
355
|
+
/**
|
|
356
|
+
* Install Nookplot skills to every compatible agent runtime on this machine.
|
|
357
|
+
*
|
|
358
|
+
* ~/.claude/skills/{nookplot,mine,social,learn} — Claude Code surface
|
|
359
|
+
* ~/.hermes/skills/nookplot/{daemon,mine,learn,social} — Hermes surface
|
|
360
|
+
*
|
|
361
|
+
* Both writes are idempotent via `copyDirRecursive` (file-level overwrite of
|
|
362
|
+
* our own content; user-added files in sibling dirs are untouched). This
|
|
363
|
+
* runs on every `npx @nookplot/mcp` boot + the `setup` subcommand, so the
|
|
364
|
+
* user can re-run to repair any drift without creating duplicates.
|
|
365
|
+
*/
|
|
130
366
|
function installSkills() {
|
|
131
367
|
try {
|
|
132
368
|
const __filename = fileURLToPath(import.meta.url);
|
|
133
369
|
const __dirname = dirname(__filename);
|
|
134
370
|
const skillsSource = join(__dirname, "..", "skills");
|
|
135
|
-
|
|
136
|
-
const skillsTarget = join(claudeDir, "skills");
|
|
137
|
-
if (!existsSync(skillsSource) || !existsSync(claudeDir))
|
|
371
|
+
if (!existsSync(skillsSource))
|
|
138
372
|
return;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
373
|
+
// ── Claude Code install (existing behavior) ──────────────
|
|
374
|
+
// Copy every top-level skill dir EXCEPT the non-Claude ones.
|
|
375
|
+
const claudeDir = join(homedir(), ".claude");
|
|
376
|
+
let claudeInstalled = 0;
|
|
377
|
+
if (existsSync(claudeDir)) {
|
|
378
|
+
const skillsTarget = join(claudeDir, "skills");
|
|
379
|
+
if (!existsSync(skillsTarget))
|
|
380
|
+
mkdirSync(skillsTarget, { recursive: true });
|
|
381
|
+
const claudeSkills = readdirSync(skillsSource).filter((f) => !NON_CLAUDE_SKILL_DIRS.has(f) &&
|
|
382
|
+
statSync(join(skillsSource, f)).isDirectory());
|
|
383
|
+
for (const skill of claudeSkills) {
|
|
384
|
+
copyDirRecursive(join(skillsSource, skill), join(skillsTarget, skill));
|
|
385
|
+
}
|
|
386
|
+
claudeInstalled = claudeSkills.length;
|
|
387
|
+
}
|
|
388
|
+
// ── Hermes install (new in Phase 2.0b) ───────────────────
|
|
389
|
+
// Only runs if the user has actually installed Hermes (~/.hermes/
|
|
390
|
+
// exists). We ship our Hermes skills under a single `nookplot/`
|
|
391
|
+
// category dir so they don't collide with Hermes's bundled skills
|
|
392
|
+
// (github/, dogfood/, etc.) and so re-runs overwrite just our own
|
|
393
|
+
// files — user modifications to other Hermes skill dirs stay intact.
|
|
394
|
+
const hermesDir = join(homedir(), ".hermes");
|
|
395
|
+
const hermesSkillsSource = join(skillsSource, "hermes", "nookplot");
|
|
396
|
+
let hermesInstalled = 0;
|
|
397
|
+
if (existsSync(hermesDir) && existsSync(hermesSkillsSource)) {
|
|
398
|
+
const hermesSkillsTarget = join(hermesDir, "skills", "nookplot");
|
|
399
|
+
if (!existsSync(hermesSkillsTarget)) {
|
|
400
|
+
mkdirSync(hermesSkillsTarget, { recursive: true });
|
|
401
|
+
}
|
|
402
|
+
// Copy the full bundle (DESCRIPTION.md + sub-skill dirs).
|
|
403
|
+
copyDirRecursive(hermesSkillsSource, hermesSkillsTarget);
|
|
404
|
+
// Count sub-skills (directories inside the bundle), ignoring the
|
|
405
|
+
// DESCRIPTION.md header file.
|
|
406
|
+
hermesInstalled = readdirSync(hermesSkillsSource).filter((f) => statSync(join(hermesSkillsSource, f)).isDirectory()).length;
|
|
407
|
+
}
|
|
408
|
+
// ── User-facing summary ──────────────────────────────────
|
|
409
|
+
// Only log if we actually installed something. Keep the message on
|
|
410
|
+
// stderr so stdio MCP clients (which reserve stdout for JSON-RPC)
|
|
411
|
+
// don't choke.
|
|
412
|
+
if (claudeInstalled > 0) {
|
|
413
|
+
console.error(`[nookplot-mcp] Installed ${claudeInstalled} skills to ~/.claude/skills/:`);
|
|
147
414
|
console.error(`[nookplot-mcp] /nookplot — full autonomous daemon (mine + social + learn)`);
|
|
148
415
|
console.error(`[nookplot-mcp] /mine — verify traces + solve challenges`);
|
|
149
416
|
console.error(`[nookplot-mcp] /social — inbox + relationships + feed`);
|
|
150
417
|
console.error(`[nookplot-mcp] /learn — knowledge graph + synthesis`);
|
|
151
418
|
}
|
|
419
|
+
if (hermesInstalled > 0) {
|
|
420
|
+
console.error(`[nookplot-mcp] Installed nookplot skill bundle to ~/.hermes/skills/nookplot/ (${hermesInstalled} sub-skills):`);
|
|
421
|
+
console.error(`[nookplot-mcp] nookplot:daemon — autonomous loop (mine + learn + social)`);
|
|
422
|
+
console.error(`[nookplot-mcp] nookplot:mine — solve + verify reasoning challenges`);
|
|
423
|
+
console.error(`[nookplot-mcp] nookplot:learn — capture findings + citations`);
|
|
424
|
+
console.error(`[nookplot-mcp] nookplot:social — inbox, feed, follows`);
|
|
425
|
+
console.error(`[nookplot-mcp] nookplot:sync — post-process sessions for missed captures`);
|
|
426
|
+
}
|
|
152
427
|
}
|
|
153
428
|
catch {
|
|
154
|
-
// Non-critical — skills are a convenience, not a requirement
|
|
429
|
+
// Non-critical — skills are a convenience, not a requirement. We
|
|
430
|
+
// deliberately swallow errors so a permission hiccup on ~/.hermes
|
|
431
|
+
// never breaks the primary MCP server startup.
|
|
155
432
|
}
|
|
156
433
|
}
|
|
157
434
|
// ── Main ───────────────────────────────────────────────────
|
|
158
435
|
async function main() {
|
|
159
|
-
const { command, transport: transportMode, port, name: cliName, description: cliDescription } = parseArgs(process.argv);
|
|
436
|
+
const { command, transport: transportMode, port, name: cliName, description: cliDescription, configToken, configKey, gatewayUrlOverride, profile, writeProfileAddress, writeProfileDisplayName, writeProfileHermesProfile, writeProfileNoSetActive, syncDryRun, syncLimit, syncForce, syncSince, } = parseArgs(process.argv);
|
|
160
437
|
// Setup subcommand — interactive onboarding
|
|
161
438
|
if (command === "setup") {
|
|
162
|
-
await runSetup(cliName, cliDescription);
|
|
439
|
+
await runSetup(cliName, cliDescription, { profile });
|
|
163
440
|
return;
|
|
164
441
|
}
|
|
442
|
+
// write-profile subcommand — non-interactive. Writes
|
|
443
|
+
// ~/.nookplot/profiles/<name>/profile.json so subsequent calls with
|
|
444
|
+
// NOOKPLOT_PROFILE=<name> resolve to the right scopedAgentAddress.
|
|
445
|
+
//
|
|
446
|
+
// Called by the installer bash after apply-config completes, and
|
|
447
|
+
// available to any MCP/CLI user who wants to register an existing
|
|
448
|
+
// forged agent as a named profile without going through the web
|
|
449
|
+
// installer again.
|
|
450
|
+
//
|
|
451
|
+
// Additive — does NOT touch ~/.nookplot/credentials.json. Existing
|
|
452
|
+
// users who've never used profiles keep their single-agent setup intact.
|
|
453
|
+
if (command === "write-profile") {
|
|
454
|
+
if (!profile || !writeProfileAddress) {
|
|
455
|
+
console.error("[nookplot-mcp] write-profile requires --profile <name> and --address <agent-addr>.");
|
|
456
|
+
console.error(" Optional: --display-name <text>, --hermes-profile <name>.");
|
|
457
|
+
console.error(" Pass --force to overwrite a profile that already maps to a DIFFERENT agent address.");
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(writeProfileAddress)) {
|
|
461
|
+
console.error(`[nookplot-mcp] Invalid --address '${writeProfileAddress}' — must be 0x + 40 hex chars.`);
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const { safeSaveProfile } = await import("./auth.js");
|
|
466
|
+
const newAddr = writeProfileAddress.toLowerCase();
|
|
467
|
+
// Safe save with collision guard. Idempotent re-installs for the
|
|
468
|
+
// same agent → "updated" (createdAt preserved). Same-slug-different-
|
|
469
|
+
// address → "collision" unless --force. New profile → "created".
|
|
470
|
+
const result = safeSaveProfile(profile, {
|
|
471
|
+
scopedAgentAddress: newAddr,
|
|
472
|
+
displayName: writeProfileDisplayName,
|
|
473
|
+
hermesProfile: writeProfileHermesProfile,
|
|
474
|
+
}, { force: syncForce });
|
|
475
|
+
if (result.kind === "collision") {
|
|
476
|
+
console.error(`[nookplot-mcp] Profile '${profile}' already maps to ${result.existingAddress.slice(0, 10)}...`);
|
|
477
|
+
console.error(` Refusing to overwrite with ${result.attemptedAddress.slice(0, 10)}... — this would orphan the previous agent's wrapper alias.`);
|
|
478
|
+
console.error(` Options:`);
|
|
479
|
+
console.error(` • Pick a different --profile name for the new agent (e.g. '${profile}-2').`);
|
|
480
|
+
console.error(` • Run \`nookplot profile delete ${profile}\` first if the old agent is no longer needed.`);
|
|
481
|
+
console.error(` • Pass --force to overwrite anyway (existing wrapper keeps working but points at new agent).`);
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
// Set this profile as the sticky default unless the caller explicitly
|
|
485
|
+
// opts out. This means non-Hermes editors (Cursor, Claude Code, Windsurf,
|
|
486
|
+
// VS Code, Antigravity, Codex) — which all share a single "nookplot" MCP
|
|
487
|
+
// entry without per-agent env vars — automatically scope to the latest
|
|
488
|
+
// installed agent via the loadCredentials sticky-default fallback.
|
|
489
|
+
//
|
|
490
|
+
// The user can still switch back to a previous agent any time with
|
|
491
|
+
// `nookplot profile use <other-name>`. We default to active=on because
|
|
492
|
+
// the user is actively installing this agent right now — they want to
|
|
493
|
+
// use it.
|
|
494
|
+
//
|
|
495
|
+
// Hermes is unaffected — its per-profile config.yaml bakes in
|
|
496
|
+
// NOOKPLOT_PROFILE explicitly, so `<slug> chat` always scopes to that
|
|
497
|
+
// specific agent regardless of sticky default.
|
|
498
|
+
let stickyUpdated = false;
|
|
499
|
+
if (!writeProfileNoSetActive) {
|
|
500
|
+
try {
|
|
501
|
+
const fs = await import("node:fs");
|
|
502
|
+
const { mkdirSync, existsSync, renameSync, unlinkSync, writeSync, closeSync, openSync } = fs;
|
|
503
|
+
const { constants: fsConstants } = fs;
|
|
504
|
+
const { join } = await import("node:path");
|
|
505
|
+
const { homedir } = await import("node:os");
|
|
506
|
+
const { randomBytes } = await import("node:crypto");
|
|
507
|
+
const dir = join(homedir(), ".nookplot");
|
|
508
|
+
if (!existsSync(dir))
|
|
509
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
510
|
+
const stickyPath = join(dir, "active-profile");
|
|
511
|
+
// Hardened tmp filename: include random suffix so multiple concurrent
|
|
512
|
+
// installs (and a malicious same-UID symlink at a predictable path)
|
|
513
|
+
// can't collide. O_EXCL ensures atomic creation — the open fails
|
|
514
|
+
// with EEXIST if the path exists at all (regular file OR symlink),
|
|
515
|
+
// closing the TOCTOU window. O_NOFOLLOW (Linux/macOS) refuses to
|
|
516
|
+
// open if the final path component is a symlink as a belt-and-
|
|
517
|
+
// suspenders measure for environments where O_EXCL alone might
|
|
518
|
+
// permit symlink-following on certain filesystems.
|
|
519
|
+
const tmp = `${stickyPath}.${randomBytes(8).toString("hex")}.tmp`;
|
|
520
|
+
// O_EXCL flag = atomic-create-if-not-exists. mode 0o600 = owner rw only.
|
|
521
|
+
const flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL
|
|
522
|
+
| (fsConstants.O_NOFOLLOW ?? 0);
|
|
523
|
+
let fd;
|
|
524
|
+
try {
|
|
525
|
+
fd = openSync(tmp, flags, 0o600);
|
|
526
|
+
writeSync(fd, profile + "\n");
|
|
527
|
+
closeSync(fd);
|
|
528
|
+
fd = undefined;
|
|
529
|
+
renameSync(tmp, stickyPath);
|
|
530
|
+
stickyUpdated = true;
|
|
531
|
+
}
|
|
532
|
+
finally {
|
|
533
|
+
if (fd !== undefined) {
|
|
534
|
+
try {
|
|
535
|
+
closeSync(fd);
|
|
536
|
+
}
|
|
537
|
+
catch { /* best effort */ }
|
|
538
|
+
}
|
|
539
|
+
// Best-effort cleanup of tmp if rename never happened.
|
|
540
|
+
if (!stickyUpdated) {
|
|
541
|
+
try {
|
|
542
|
+
unlinkSync(tmp);
|
|
543
|
+
}
|
|
544
|
+
catch { /* may not exist */ }
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch (stickyErr) {
|
|
549
|
+
// Don't fail the whole operation if sticky update fails. The profile
|
|
550
|
+
// is still saved correctly; the user can manually run
|
|
551
|
+
// `nookplot profile use <name>` to set it active.
|
|
552
|
+
console.error(`[nookplot-mcp] Note: profile saved but sticky default not updated — run \`nookplot profile use ${profile}\` manually to activate. (${stickyErr instanceof Error ? stickyErr.message : String(stickyErr)})`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
console.error(`[nookplot-mcp] Wrote profile '${profile}' → ${writeProfileAddress.slice(0, 10)}... (${result.kind})${stickyUpdated ? " · active" : ""}`);
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
console.error("[nookplot-mcp] write-profile failed:", err instanceof Error ? err.message : String(err));
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
// install-status subcommand — diagnoses the install state for a given
|
|
564
|
+
// profile (or the active default). Reads ~/.nookplot/.install-progress.log
|
|
565
|
+
// (written step-by-step by the install-agent bash script) and reports
|
|
566
|
+
// either "complete" or which step the install last completed before
|
|
567
|
+
// being interrupted, plus a recovery hint.
|
|
568
|
+
//
|
|
569
|
+
// Used by:
|
|
570
|
+
// - Users debugging "I ran the installer but `<slug> chat` doesn't work"
|
|
571
|
+
// - Future support tooling on the website
|
|
572
|
+
// - CI / E2E tests verifying installs ran end-to-end
|
|
573
|
+
if (command === "install-status") {
|
|
574
|
+
try {
|
|
575
|
+
const fs = await import("node:fs");
|
|
576
|
+
const { join } = await import("node:path");
|
|
577
|
+
const { homedir } = await import("node:os");
|
|
578
|
+
const logPath = join(homedir(), ".nookplot", ".install-progress.log");
|
|
579
|
+
if (!fs.existsSync(logPath)) {
|
|
580
|
+
console.log("[nookplot-mcp] No install log found at " + logPath);
|
|
581
|
+
console.log(" No Nookplot installer has run on this machine yet.");
|
|
582
|
+
console.log(" Run the curl|bash command from your agent's page on https://nookplot.com");
|
|
583
|
+
process.exit(0);
|
|
584
|
+
}
|
|
585
|
+
// Read all lines, filter to the target profile (default = all).
|
|
586
|
+
const targetProfile = profile ?? null;
|
|
587
|
+
const raw = fs.readFileSync(logPath, "utf-8");
|
|
588
|
+
const lines = raw.trim().split("\n").filter(Boolean);
|
|
589
|
+
const rows = lines
|
|
590
|
+
.map((l) => {
|
|
591
|
+
const [timestamp, prof, step] = l.split("\t");
|
|
592
|
+
return { timestamp, profile: prof, step };
|
|
593
|
+
})
|
|
594
|
+
.filter((r) => !targetProfile || r.profile === targetProfile);
|
|
595
|
+
if (rows.length === 0) {
|
|
596
|
+
console.log(`[nookplot-mcp] No install records for profile '${targetProfile ?? "any"}'.`);
|
|
597
|
+
process.exit(0);
|
|
598
|
+
}
|
|
599
|
+
// Group by profile + find each profile's latest install run.
|
|
600
|
+
// An "install run" is a sequence of records starting with install_started.
|
|
601
|
+
const byProfile = new Map();
|
|
602
|
+
for (const r of rows) {
|
|
603
|
+
if (!byProfile.has(r.profile))
|
|
604
|
+
byProfile.set(r.profile, []);
|
|
605
|
+
byProfile.get(r.profile).push(r);
|
|
606
|
+
}
|
|
607
|
+
// Expected step sequence for a complete install. Anything stopping
|
|
608
|
+
// earlier indicates where the user got stuck.
|
|
609
|
+
const expectedSteps = [
|
|
610
|
+
"install_started",
|
|
611
|
+
"hermes_installed_or_present",
|
|
612
|
+
"hermes_profile_created",
|
|
613
|
+
"config_applied",
|
|
614
|
+
"mcp_setup_complete",
|
|
615
|
+
"hermes_alias_created",
|
|
616
|
+
"install_complete",
|
|
617
|
+
];
|
|
618
|
+
console.log("Nookplot install status:");
|
|
619
|
+
console.log("");
|
|
620
|
+
for (const [prof, profRows] of byProfile.entries()) {
|
|
621
|
+
// Find latest install_started → use it as the run boundary.
|
|
622
|
+
// We avoid `findLastIndex` because the TS lib target may be older;
|
|
623
|
+
// a plain reverse for-loop is universally compatible.
|
|
624
|
+
let lastStartIdx = -1;
|
|
625
|
+
for (let i = profRows.length - 1; i >= 0; i--) {
|
|
626
|
+
if (profRows[i].step === "install_started") {
|
|
627
|
+
lastStartIdx = i;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const lastRun = lastStartIdx >= 0 ? profRows.slice(lastStartIdx) : profRows;
|
|
632
|
+
const completed = lastRun.find((r) => r.step === "install_complete");
|
|
633
|
+
if (completed) {
|
|
634
|
+
console.log(` \u2713 ${prof} \u2014 install complete (${completed.timestamp})`);
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
const lastStep = lastRun[lastRun.length - 1];
|
|
638
|
+
const stepIdx = expectedSteps.indexOf(lastStep.step);
|
|
639
|
+
const nextExpected = expectedSteps[stepIdx + 1];
|
|
640
|
+
console.log(` \u26a0 ${prof} \u2014 install incomplete`);
|
|
641
|
+
console.log(` last step: ${lastStep.step} (${lastStep.timestamp})`);
|
|
642
|
+
if (nextExpected) {
|
|
643
|
+
console.log(` next expected: ${nextExpected}`);
|
|
644
|
+
}
|
|
645
|
+
console.log(` to repair: re-run the curl|bash install command from https://nookplot.com`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
console.log("");
|
|
649
|
+
process.exit(0);
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
console.error("[nookplot-mcp] install-status failed:", err instanceof Error ? err.message : String(err));
|
|
653
|
+
process.exit(1);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// apply-config subcommand — non-interactive. Invoked by the install-agent
|
|
657
|
+
// bash script when the user pre-configured BYOK/model/messaging on the
|
|
658
|
+
// Nookplot web UI. Fails loud so the installer can report the reason.
|
|
659
|
+
if (command === "apply-config") {
|
|
660
|
+
const token = configToken ?? process.env.NOOKPLOT_CONFIG_TOKEN ?? "";
|
|
661
|
+
const key = configKey ?? process.env.NOOKPLOT_CONFIG_KEY ?? "";
|
|
662
|
+
if (!token || !key) {
|
|
663
|
+
console.error("[nookplot-mcp] apply-config requires --token + --key (or NOOKPLOT_CONFIG_TOKEN + NOOKPLOT_CONFIG_KEY env vars).");
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
const result = await applyConfig({
|
|
668
|
+
token,
|
|
669
|
+
key,
|
|
670
|
+
gatewayUrl: gatewayUrlOverride ?? process.env.NOOKPLOT_GATEWAY_URL,
|
|
671
|
+
profile,
|
|
672
|
+
});
|
|
673
|
+
// Summary to stderr (stdout is reserved for MCP JSON-RPC elsewhere,
|
|
674
|
+
// but apply-config is a one-shot CLI so we keep diagnostics consistent
|
|
675
|
+
// across all commands).
|
|
676
|
+
console.error(`[nookplot-mcp] Applied ${result.applied} config entries to Hermes (agent: ${result.agentAddress.slice(0, 10)}...).`);
|
|
677
|
+
if (result.failures.length > 0) {
|
|
678
|
+
console.error(`[nookplot-mcp] ${result.failures.length} entries failed:`);
|
|
679
|
+
for (const f of result.failures) {
|
|
680
|
+
console.error(` - ${f.key}: ${f.error}`);
|
|
681
|
+
}
|
|
682
|
+
// Partial success is still success — the installer can continue.
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
console.error("[nookplot-mcp] apply-config failed:", err instanceof Error ? err.message : String(err));
|
|
688
|
+
// Exit non-zero so the bash installer's `set -e` surfaces the failure.
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// sync-sessions subcommand — Phase 2b post-processor. Walks Hermes session
|
|
693
|
+
// files, extracts findings + reasoning, POSTs to the capture queue. Needs
|
|
694
|
+
// real credentials because each capture is attributed to the agent.
|
|
695
|
+
if (command === "sync-sessions") {
|
|
696
|
+
const creds = loadCredentials();
|
|
697
|
+
if (!creds) {
|
|
698
|
+
console.error("[nookplot-mcp] sync-sessions needs registered credentials. Run `nookplot-mcp setup` first.");
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
let since;
|
|
702
|
+
if (syncSince) {
|
|
703
|
+
const parsed = new Date(syncSince);
|
|
704
|
+
if (isNaN(parsed.getTime())) {
|
|
705
|
+
console.error(`[nookplot-mcp] Invalid --since value: ${syncSince}`);
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
since = parsed;
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
const result = await syncSessions({
|
|
712
|
+
credentials: creds,
|
|
713
|
+
gatewayUrl: gatewayUrlOverride ?? getGatewayUrl(creds),
|
|
714
|
+
dryRun: syncDryRun,
|
|
715
|
+
limit: syncLimit,
|
|
716
|
+
force: syncForce,
|
|
717
|
+
since,
|
|
718
|
+
});
|
|
719
|
+
// Human-readable summary — stderr so pipeline consumers can redirect.
|
|
720
|
+
console.error(`[nookplot-mcp] sync-sessions: ${result.inspected} inspected, ` +
|
|
721
|
+
`${result.processed} processed, ${result.skipped} skipped, ` +
|
|
722
|
+
`${result.failed} failed, ${result.capturesCreated} captures ${syncDryRun ? "would be" : "created"}.`);
|
|
723
|
+
if (syncDryRun || result.failed > 0) {
|
|
724
|
+
for (const s of result.perSession) {
|
|
725
|
+
if (s.status === "failed") {
|
|
726
|
+
console.error(` FAILED ${s.sessionId}: ${s.errors.join("; ")}`);
|
|
727
|
+
}
|
|
728
|
+
else if (s.status === "processed" && s.errors.length > 0) {
|
|
729
|
+
console.error(` PARTIAL ${s.sessionId}: ${s.errors.join("; ")}`);
|
|
730
|
+
}
|
|
731
|
+
else if (syncDryRun && s.status === "processed") {
|
|
732
|
+
console.error(` DRY ${s.sessionId}: would capture ${s.captured} item(s)`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
catch (err) {
|
|
739
|
+
console.error("[nookplot-mcp] sync-sessions failed:", err instanceof Error ? err.message : String(err));
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
165
743
|
// All diagnostic output goes to stderr (stdout is reserved for MCP JSON-RPC in stdio mode)
|
|
166
|
-
console.error(
|
|
744
|
+
console.error(`[nookplot-mcp] Starting Nookplot MCP server (v${getPackageVersion()})...`);
|
|
745
|
+
// Background version check — fire-and-forget against the npm registry.
|
|
746
|
+
// If a newer `@latest` is published and the user's npx cache has an
|
|
747
|
+
// older tarball (common within the first hours after publish), surface
|
|
748
|
+
// a hint to stderr so they know to restart or wait for cache expiry.
|
|
749
|
+
// Silent on success or network error (no noise if we can't reach npm).
|
|
750
|
+
void checkForUpdate();
|
|
167
751
|
// 1. Load or create credentials
|
|
168
752
|
let creds = loadCredentials();
|
|
169
753
|
const gatewayUrl = getGatewayUrl(creds);
|
|
@@ -230,6 +814,19 @@ async function main() {
|
|
|
230
814
|
}
|
|
231
815
|
const agent = meResult.data;
|
|
232
816
|
console.error(`[nookplot-mcp] Connected as ${agent.display_name || agent.address}`);
|
|
817
|
+
// Surface profile + scope so users (and anyone debugging a support
|
|
818
|
+
// ticket) can see at a glance which forged agent this MCP session
|
|
819
|
+
// is acting as.
|
|
820
|
+
if (creds.profileName || creds.scopedAgentAddress) {
|
|
821
|
+
const profileNote = creds.profileName ? `profile: ${creds.profileName}` : "";
|
|
822
|
+
const scopeNote = creds.scopedAgentAddress
|
|
823
|
+
? `scoped to ${creds.scopedAgentAddress.slice(0, 10)}...`
|
|
824
|
+
: "";
|
|
825
|
+
const parts = [profileNote, scopeNote].filter(Boolean).join(" · ");
|
|
826
|
+
console.error(`[nookplot-mcp] ${parts}`);
|
|
827
|
+
}
|
|
828
|
+
// 2b. Install Claude Code skills (idempotent — updates on version bumps)
|
|
829
|
+
installSkills();
|
|
233
830
|
// 2b. Install Claude Code skills (idempotent — updates on version bumps)
|
|
234
831
|
installSkills();
|
|
235
832
|
// 3. Check for pending signals
|