mrmainspring 0.2.2 → 0.2.4

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/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
@@ -126,8 +126,41 @@ const CLOCK_ART = [
126
126
  async function runInteractiveSetup() {
127
127
  process.stdout.write(`\n${CLOCK_ART}\n\n`);
128
128
  clack.intro("setup");
129
+ // ── 1. Local files ──────────────────────────────────────────────────────────
130
+ const hadKey = !!process.env.GRIMOIRE_MASTER_KEY;
129
131
  const result = initializeLocalSetup();
130
- clack.log.success(`Local files ready\n Config: ${result.envFile}\n Data: ${result.dataDir}`);
132
+ const keyNote = hadKey ? "encryption key loaded" : `encryption key generated → ${result.envFile}`;
133
+ clack.log.success(`Local files ready\n Config: ${result.envFile}\n Data: ${result.dataDir}\n ${keyNote}`);
134
+ // ── 2. Storage backend ──────────────────────────────────────────────────────
135
+ const storageChoice = await clack.select({
136
+ message: "Storage backend?",
137
+ options: [
138
+ { value: "file", label: "Local files", hint: result.dataDir },
139
+ { value: "supabase", label: "Supabase (cloud)", hint: "requires PROJECT_URL + SECRET_KEY" }
140
+ ]
141
+ });
142
+ if (clack.isCancel(storageChoice)) {
143
+ clack.cancel("Setup cancelled.");
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ if (storageChoice === "supabase") {
148
+ const projectUrl = await clack.text({ message: "Supabase PROJECT_URL:", placeholder: "https://xxx.supabase.co" });
149
+ if (clack.isCancel(projectUrl)) {
150
+ clack.cancel("Setup cancelled.");
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ const secretKey = await clack.text({ message: "Supabase SECRET_KEY (service role):", placeholder: "eyJ..." });
155
+ if (clack.isCancel(secretKey)) {
156
+ clack.cancel("Setup cancelled.");
157
+ process.exitCode = 1;
158
+ return;
159
+ }
160
+ appendFileSync(result.envFile, `\nSIGIL_STORAGE_BACKEND=supabase\nPROJECT_URL=${projectUrl}\nSECRET_KEY=${secretKey}\n`, "utf8");
161
+ clack.log.success("Supabase config saved.");
162
+ }
163
+ // ── 3. MCP clients ──────────────────────────────────────────────────────────
131
164
  const clients = detectClients();
132
165
  const installed = clients.filter(c => c.installed);
133
166
  const choices = await clack.multiselect({
@@ -155,10 +188,7 @@ async function runInteractiveSetup() {
155
188
  });
156
189
  if (!clack.isCancel(customPath) && customPath && customPath.trim()) {
157
190
  const expandedPath = customPath.replace(/^~/, process.env.HOME ?? "");
158
- const configResults = configureClients([
159
- { name: "Custom", configPath: expandedPath, installed: true, format: "standard" }
160
- ]);
161
- const r = configResults[0];
191
+ const r = configureClients([{ name: "Custom", configPath: expandedPath, installed: true, format: "standard" }])[0];
162
192
  if (r?.status === "written")
163
193
  clack.log.success(`Configured: ${expandedPath}`);
164
194
  else if (r?.status === "already-set")
@@ -182,53 +212,124 @@ async function runInteractiveSetup() {
182
212
  clack.log.info(`Already configured: ${alreadySet.join(", ")}`);
183
213
  for (const e of errors)
184
214
  clack.log.error(`${e.name}: ${e.error}`);
185
- const wantCasper = await clack.confirm({
186
- message: "Enable Casper on-chain anchoring?",
187
- initialValue: false
188
- });
215
+ // ── 4. Casper anchoring ─────────────────────────────────────────────────────
216
+ const wantCasper = await clack.confirm({ message: "Enable Casper on-chain anchoring?", initialValue: false });
189
217
  if (clack.isCancel(wantCasper)) {
190
218
  clack.cancel("Setup cancelled.");
191
219
  return;
192
220
  }
221
+ let casperRpcUrl = "https://node.testnet.casper.network/rpc";
222
+ let casperNetwork = "testnet";
193
223
  if (wantCasper) {
224
+ const network = await clack.select({
225
+ message: "Which Casper network?",
226
+ options: [
227
+ { value: "testnet", label: "Testnet", hint: "safe for testing — free faucet available" },
228
+ { value: "mainnet", label: "Mainnet", hint: "real CSPR — costs money" }
229
+ ]
230
+ });
231
+ if (clack.isCancel(network)) {
232
+ clack.cancel("Setup cancelled.");
233
+ return;
234
+ }
235
+ casperNetwork = network;
236
+ casperRpcUrl = casperNetwork === "mainnet"
237
+ ? "https://node.mainnet.casper.network/rpc"
238
+ : "https://node.testnet.casper.network/rpc";
239
+ const casperBin = process.env.CASPER_CLIENT_BIN ?? "casper-client";
240
+ const casperAvailable = !spawnSync(casperBin, ["--version"], { stdio: "pipe" }).error;
241
+ const keyOptions = [
242
+ { value: "have", label: "I already have a key pair (.pem)" },
243
+ { value: "skip", label: "Skip for now" }
244
+ ];
245
+ if (casperAvailable) {
246
+ keyOptions.splice(1, 0, { value: "generate", label: "Generate a new key pair now", hint: "runs casper-client keygen ./keys" });
247
+ }
248
+ const keyAction = await clack.select({ message: "Casper key pair?", options: keyOptions });
249
+ if (clack.isCancel(keyAction)) {
250
+ clack.cancel("Setup cancelled.");
251
+ return;
252
+ }
253
+ if (keyAction === "generate") {
254
+ mkdirSync("./keys", { recursive: true });
255
+ const keygen = spawnSync(casperBin, ["keygen", "./keys"], { stdio: "pipe" });
256
+ if (keygen.status === 0) {
257
+ clack.log.success("Key pair generated in ./keys/\n Secret: ./keys/secret_key.pem\n Public: ./keys/public_key.pem");
258
+ }
259
+ else {
260
+ clack.log.error("keygen failed: " + (keygen.stderr?.toString() ?? "unknown error"));
261
+ }
262
+ }
263
+ const keyPath = keyAction === "generate" ? "./keys/secret_key.pem" : "./keys/your-secret-key.pem";
194
264
  clack.log.info("Add to your .env file:\n\n" +
195
- " CASPER_ENABLE_REAL_SUBMISSION=true\n" +
196
- " CASPER_RPC_URL=https://node.testnet.casper.network/rpc\n" +
197
- " CASPER_ACCOUNT_KEY_PATH=./keys/your-key.pem\n\n" +
198
- "Then restart your MCP client.");
265
+ ` CASPER_ENABLE_REAL_SUBMISSION=true\n` +
266
+ ` CASPER_RPC_URL=${casperRpcUrl}\n` +
267
+ ` CASPER_ACCOUNT_KEY_PATH=${keyPath}\n\n` +
268
+ ` Your .env: ${result.envFile}`);
199
269
  }
200
- const wantX402 = await clack.confirm({
201
- message: "Enable x402 micropayments?",
202
- initialValue: false
203
- });
270
+ // ── 5. x402 payments ────────────────────────────────────────────────────────
271
+ const wantX402 = await clack.confirm({ message: "Enable x402 micropayments?", initialValue: false });
204
272
  if (clack.isCancel(wantX402)) {
205
273
  clack.cancel("Setup cancelled.");
206
274
  return;
207
275
  }
208
276
  if (wantX402) {
277
+ let x402RpcUrl = casperRpcUrl;
278
+ let x402Network = casperNetwork;
279
+ if (!wantCasper) {
280
+ const network = await clack.select({
281
+ message: "Which Casper network for payments?",
282
+ options: [
283
+ { value: "testnet", label: "Testnet", hint: "free faucet available" },
284
+ { value: "mainnet", label: "Mainnet", hint: "real CSPR" }
285
+ ]
286
+ });
287
+ if (!clack.isCancel(network)) {
288
+ x402Network = network;
289
+ x402RpcUrl = x402Network === "mainnet"
290
+ ? "https://node.mainnet.casper.network/rpc"
291
+ : "https://node.testnet.casper.network/rpc";
292
+ }
293
+ }
294
+ const explorerBase = x402Network === "mainnet" ? "https://cspr.live" : "https://testnet.cspr.live";
209
295
  clack.log.info("x402 setup — 3 steps:");
210
296
  clack.log.info("Step 1 — Get your Casper account hash\n\n" +
211
- " Your account hash identifies your Casper wallet on-chain.\n" +
212
- " You need a Casper key pair (.pem file). If you already have one:\n\n" +
213
- " casper-client account-address --public-key <path-to-public-key.pem>\n\n" +
214
- " Or from a hex public key:\n\n" +
215
- " casper-client account-address --public-key 02<your-66-char-hex-pubkey>\n\n" +
216
- " The output looks like:\n" +
297
+ " Your account hash identifies your wallet on-chain.\n" +
298
+ " From a .pem file:\n\n" +
299
+ " casper-client account-address --public-key ./keys/public_key.pem\n\n" +
300
+ " From a hex public key:\n\n" +
301
+ " casper-client account-address --public-key 02<your-hex-pubkey>\n\n" +
302
+ " Output looks like:\n" +
217
303
  " account-hash-d0a57c6a95e74463de156cac761e17f0923eafc730ce3ce3a0c747c6598b0500\n\n" +
218
- " Don't have casper-client? Install it:\n" +
219
- " cargo install casper-client (or download from github.com/casper-ecosystem/casper-client-rs)");
304
+ " No casper-client? Install: cargo install casper-client");
220
305
  clack.log.info("Step 2 — Add to your .env file\n\n" +
221
306
  " X402_ENABLE_REAL_SETTLEMENT=true\n" +
222
307
  " X402_SETTLEMENT_MODE=casper-cli\n" +
223
- " X402_BUYER_ACCOUNT_HASH=account-hash-<paste your hash here>\n" +
308
+ " X402_BUYER_ACCOUNT_HASH=account-hash-<your hash>\n" +
224
309
  " CASPER_ENABLE_REAL_SUBMISSION=true\n" +
225
- " CASPER_RPC_URL=https://node.testnet.casper.network/rpc\n" +
226
- " CASPER_ACCOUNT_KEY_PATH=./keys/your-secret-key.pem\n\n" +
227
- " Your .env is at:\n" +
228
- ` ${result.envFile}`);
310
+ ` CASPER_RPC_URL=${x402RpcUrl}\n` +
311
+ " CASPER_ACCOUNT_KEY_PATH=./keys/secret_key.pem\n\n" +
312
+ ` Your .env: ${result.envFile}`);
229
313
  clack.log.info("Step 3 — Fund your account\n\n" +
230
- " Testnet: use the Casper faucet at https://testnet.cspr.live/tools/faucet\n" +
231
- " Search your account hash on https://testnet.cspr.live to verify balance.");
314
+ (x402Network === "testnet"
315
+ ? ` Faucet: ${explorerBase}/tools/faucet\n`
316
+ : " Transfer CSPR to your account.\n") +
317
+ ` Verify balance: ${explorerBase} → search your account hash`);
318
+ }
319
+ // ── 6. Doctor check ─────────────────────────────────────────────────────────
320
+ const doctorOutput = formatDoctorReport();
321
+ const doctorLines = doctorOutput.split("\n").filter(Boolean);
322
+ const hasIssues = doctorLines.some(l => l.startsWith("[warn]") || l.startsWith("[error]"));
323
+ const doctorMsg = doctorLines
324
+ .slice(1)
325
+ .map(l => l.startsWith("[ok]") ? ` ✓ ${l.slice(5)}` :
326
+ l.startsWith("[warn]") ? ` ⚠ ${l.slice(7)}` :
327
+ l.startsWith("[error]") ? ` ✗ ${l.slice(8)}` : ` ${l}`).join("\n");
328
+ if (hasIssues) {
329
+ clack.log.warn(`System check:\n${doctorMsg}`);
330
+ }
331
+ else {
332
+ clack.log.success(`System check:\n${doctorMsg}`);
232
333
  }
233
334
  clack.outro("Restart your MCP clients to load the server.");
234
335
  }
@@ -11,19 +11,38 @@ const CLIENTS = [
11
11
  { name: "Claude Desktop", path: "%APPDATA%/Claude/claude_desktop_config.json", platforms: ["win32"], format: "standard" },
12
12
  { name: "Claude Desktop", path: "~/.config/Claude/claude_desktop_config.json", platforms: ["linux"], format: "standard" },
13
13
  // Claude Code CLI
14
- { name: "Claude Code", path: "~/.claude/settings.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
14
+ {
15
+ name: "Claude Code", path: "~/.claude/settings.json", platforms: ["darwin", "linux", "win32"], format: "standard",
16
+ appPaths: ["/usr/local/bin/claude", "/usr/bin/claude"]
17
+ },
15
18
  // Cursor
16
- { name: "Cursor", path: "~/.cursor/mcp.json", platforms: ["darwin", "linux"], format: "standard" },
17
- { name: "Cursor", path: "%USERPROFILE%/.cursor/mcp.json", platforms: ["win32"], format: "standard" },
19
+ {
20
+ name: "Cursor", path: "~/.cursor/mcp.json", platforms: ["darwin", "linux"], format: "standard",
21
+ appPaths: ["/Applications/Cursor.app", `${homedir()}/Applications/Cursor.app`]
22
+ },
23
+ {
24
+ name: "Cursor", path: "%USERPROFILE%/.cursor/mcp.json", platforms: ["win32"], format: "standard",
25
+ appPaths: ["C:/Users/Default/AppData/Local/Programs/cursor/Cursor.exe"]
26
+ },
18
27
  // Windsurf
19
- { name: "Windsurf", path: "~/.codeium/windsurf/mcp_config.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
28
+ {
29
+ name: "Windsurf", path: "~/.codeium/windsurf/mcp_config.json", platforms: ["darwin", "linux", "win32"], format: "standard",
30
+ appPaths: ["/Applications/Windsurf.app", `${homedir()}/Applications/Windsurf.app`]
31
+ },
20
32
  { name: "Windsurf", path: "%APPDATA%/Windsurf/User/globalStorage/codeium.windsurf/mcp_config.json", platforms: ["win32"], format: "standard" },
21
33
  // Zed
22
- { name: "Zed", path: "~/.config/zed/settings.json", platforms: ["darwin", "linux"], format: "zed" },
34
+ {
35
+ name: "Zed", path: "~/.config/zed/settings.json", platforms: ["darwin", "linux"], format: "zed",
36
+ appPaths: ["/Applications/Zed.app", `${homedir()}/Applications/Zed.app`]
37
+ },
23
38
  // Continue.dev
24
39
  { name: "Continue", path: "~/.continue/config.json", platforms: ["darwin", "linux", "win32"], format: "continue" },
25
40
  // VS Code (user-level MCP config — requires GitHub Copilot or MCP extension)
26
- { name: "VS Code", path: "~/.vscode/mcp.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
41
+ {
42
+ name: "VS Code", path: "~/.vscode/mcp.json", platforms: ["darwin", "linux"], format: "standard",
43
+ appPaths: ["/Applications/Visual Studio Code.app", `${homedir()}/Applications/Visual Studio Code.app`, "/usr/bin/code", "/usr/local/bin/code"]
44
+ },
45
+ { name: "VS Code", path: "~/.vscode/mcp.json", platforms: ["win32"], format: "standard" },
27
46
  // Cline (VS Code extension by saoudrizwan)
28
47
  {
29
48
  name: "Cline",
@@ -100,9 +119,11 @@ function expandPath(p, env) {
100
119
  .replace(/%APPDATA%/gi, env.APPDATA ?? "")
101
120
  .replace(/%USERPROFILE%/gi, env.USERPROFILE ?? home);
102
121
  }
103
- function isInstalled(configPath, detectDir) {
122
+ function isInstalled(configPath, detectDir, appPaths) {
104
123
  const dir = detectDir ?? dirname(configPath);
105
- return existsSync(dir) || existsSync(configPath);
124
+ if (existsSync(dir) || existsSync(configPath))
125
+ return true;
126
+ return appPaths?.some(p => existsSync(p)) ?? false;
106
127
  }
107
128
  function readJson(path) {
108
129
  if (!existsSync(path))
@@ -163,7 +184,7 @@ export function detectClients(env = process.env) {
163
184
  continue;
164
185
  seen.add(key);
165
186
  const detectDir = client.detectDir ? expandPath(client.detectDir, env) : undefined;
166
- results.push({ name: client.name, configPath, installed: isInstalled(configPath, detectDir), format: client.format });
187
+ results.push({ name: client.name, configPath, installed: isInstalled(configPath, detectDir, client.appPaths), format: client.format });
167
188
  }
168
189
  return results;
169
190
  }
@@ -199,7 +220,7 @@ export function setupAllClients(env = process.env) {
199
220
  continue;
200
221
  seen.add(key);
201
222
  const detectDir = client.detectDir ? expandPath(client.detectDir, env) : undefined;
202
- if (!isInstalled(configPath, detectDir)) {
223
+ if (!isInstalled(configPath, detectDir, client.appPaths)) {
203
224
  results.push({ name: client.name, configPath, status: "not-installed" });
204
225
  continue;
205
226
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mrmainspring",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Mr Mainspring MCP backend with memory, Grimoire policies, audit, Casper anchoring, and x402 settlement boundaries.",
5
5
  "license": "MIT",
6
6
  "type": "module",