mrmainspring 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts CHANGED
@@ -3,7 +3,7 @@ type InitResult = {
3
3
  dataDir: string;
4
4
  logsDir: string;
5
5
  };
6
- export declare function runCliCommand(args: string[]): boolean;
6
+ export declare function runCliCommand(args: string[]): Promise<boolean>;
7
7
  export declare function initializeLocalSetup(env?: NodeJS.ProcessEnv): InitResult;
8
8
  export declare function formatMcpConfig(): string;
9
9
  export {};
package/dist/cli.js CHANGED
@@ -2,8 +2,9 @@ import { 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";
5
+ import * as clack from "@clack/prompts";
5
6
  import { loadConfig } from "./config.js";
6
- import { setupAllClients, formatClientSetupReport } from "./client-setup.js";
7
+ import { detectClients, configureClients, setupAllClients, formatClientSetupReport } from "./client-setup.js";
7
8
  import { ensureGrimoireMasterKey, loadLocalEnvFile, resolveEnvPath } from "./env-file.js";
8
9
  import { getDefaultMainspringPaths } from "./paths.js";
9
10
  const _pkgRoot = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -34,7 +35,7 @@ Advanced users can still set SIGIL_ENV_FILE, SIGIL_DATA_DIR, Supabase, Casper,
34
35
  and x402 env vars. No env vars are required for local memory, Grimoire, audit,
35
36
  or payment preflight tools.
36
37
  `;
37
- export function runCliCommand(args) {
38
+ export async function runCliCommand(args) {
38
39
  const [command, target] = args;
39
40
  if (!command || command === "stdio" || command === "server" || command === "mcp") {
40
41
  return false;
@@ -57,23 +58,37 @@ export function runCliCommand(args) {
57
58
  return true;
58
59
  }
59
60
  if (command === "setup") {
60
- const result = initializeLocalSetup();
61
- process.stdout.write(formatInitResult(result));
62
- const clientResults = setupAllClients();
63
- const report = formatClientSetupReport(clientResults);
64
- if (report) {
65
- process.stdout.write(report);
61
+ if (process.stdin.isTTY) {
62
+ await runInteractiveSetup();
66
63
  }
67
64
  else {
68
- process.stdout.write("\nNo MCP clients detected automatically.\n" +
69
- "Add this to your MCP client config (Claude Desktop, Cursor, Windsurf, Zed, VS Code, Continue, or any MCP host):\n\n");
70
- process.stdout.write(`${formatMcpConfig()}\n`);
71
- process.stdout.write("\nConfig file locations:\n" +
72
- " Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json\n" +
73
- " Cursor ~/.cursor/mcp.json\n" +
74
- " Windsurf ~/.codeium/windsurf/mcp_config.json\n" +
75
- " Zed ~/.config/zed/settings.json (context_servers format)\n" +
76
- " VS Code ~/.vscode/mcp.json\n\n");
65
+ const result = initializeLocalSetup();
66
+ process.stdout.write(formatInitResult(result));
67
+ const clientResults = setupAllClients();
68
+ const report = formatClientSetupReport(clientResults);
69
+ if (report) {
70
+ process.stdout.write(report);
71
+ }
72
+ else {
73
+ process.stdout.write("\nNo MCP clients detected automatically.\n" +
74
+ "Add this to your MCP client config:\n\n");
75
+ process.stdout.write(`${formatMcpConfig()}\n`);
76
+ process.stdout.write("\nConfig file locations:\n" +
77
+ " Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json\n" +
78
+ " Claude Code ~/.claude/settings.json\n" +
79
+ " Cursor ~/.cursor/mcp.json\n" +
80
+ " Windsurf ~/.codeium/windsurf/mcp_config.json\n" +
81
+ " Zed ~/.config/zed/settings.json (context_servers format)\n" +
82
+ " VS Code ~/.vscode/mcp.json\n" +
83
+ " Cline ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json\n" +
84
+ " Roo Code ~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json\n" +
85
+ " Kilo Code ~/Library/Application Support/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json\n" +
86
+ " OpenCode ~/.config/opencode/config.json\n" +
87
+ " Amazon Q ~/.aws/amazonq/mcp.json\n" +
88
+ " Gemini CLI ~/.gemini/settings.json\n" +
89
+ " Codex CLI ~/.codex/config.json\n" +
90
+ " Continue.dev ~/.continue/config.json (array format)\n\n");
91
+ }
77
92
  }
78
93
  return true;
79
94
  }
@@ -92,6 +107,114 @@ export function runCliCommand(args) {
92
107
  process.exitCode = 1;
93
108
  return true;
94
109
  }
110
+ const CLOCK_ART = [
111
+ " ╷ ",
112
+ " ╭────┴────╮ ",
113
+ " ╭╯ 12 ╰╮ ",
114
+ " │ 11 │ 1 │ ",
115
+ " │ │ │ ",
116
+ " 9 ─┤ ──◎ ├─ 3 ",
117
+ " │ │ ",
118
+ " │ 8 4 │ ",
119
+ " ╰╮ 7 5 ╭╯ ",
120
+ " ╰╮ 6 ╭╯ ",
121
+ " ╰────────╯ ",
122
+ " ",
123
+ " Mr Mainspring ",
124
+ " Memory · Anchor · Pay ",
125
+ ].join("\n");
126
+ async function runInteractiveSetup() {
127
+ process.stdout.write(`\n${CLOCK_ART}\n\n`);
128
+ clack.intro("setup");
129
+ const result = initializeLocalSetup();
130
+ clack.log.success(`Local files ready\n Config: ${result.envFile}\n Data: ${result.dataDir}`);
131
+ const clients = detectClients();
132
+ const installed = clients.filter(c => c.installed);
133
+ const choices = await clack.multiselect({
134
+ message: "Which MCP clients should we configure?",
135
+ options: clients.map(c => ({
136
+ value: c,
137
+ label: c.name,
138
+ hint: c.installed ? c.configPath : "not detected — config will be created"
139
+ })),
140
+ initialValues: installed,
141
+ required: false
142
+ });
143
+ if (clack.isCancel(choices)) {
144
+ clack.cancel("Setup cancelled.");
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ const selected = choices;
149
+ if (selected.length === 0) {
150
+ clack.log.warn("No clients selected.");
151
+ const customPath = await clack.text({
152
+ message: "Enter a config file path to configure manually (or press Enter to skip):",
153
+ placeholder: "~/.config/myapp/mcp.json",
154
+ validate: () => undefined
155
+ });
156
+ if (!clack.isCancel(customPath) && customPath && customPath.trim()) {
157
+ 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];
162
+ if (r?.status === "written")
163
+ clack.log.success(`Configured: ${expandedPath}`);
164
+ else if (r?.status === "already-set")
165
+ clack.log.info(`Already configured: ${expandedPath}`);
166
+ else if (r?.error)
167
+ clack.log.error(`Failed: ${r.error}`);
168
+ }
169
+ else {
170
+ clack.log.info("Add this JSON to your MCP client config:\n\n" + formatMcpConfig());
171
+ }
172
+ clack.outro("Restart your MCP client after updating the config.");
173
+ return;
174
+ }
175
+ const configResults = configureClients(selected);
176
+ const written = configResults.filter(r => r.status === "written").map(r => r.name);
177
+ const alreadySet = configResults.filter(r => r.status === "already-set").map(r => r.name);
178
+ const errors = configResults.filter(r => r.status === "error");
179
+ if (written.length > 0)
180
+ clack.log.success(`Configured: ${written.join(", ")}`);
181
+ if (alreadySet.length > 0)
182
+ clack.log.info(`Already configured: ${alreadySet.join(", ")}`);
183
+ for (const e of errors)
184
+ clack.log.error(`${e.name}: ${e.error}`);
185
+ const wantCasper = await clack.confirm({
186
+ message: "Enable Casper on-chain anchoring?",
187
+ initialValue: false
188
+ });
189
+ if (clack.isCancel(wantCasper)) {
190
+ clack.cancel("Setup cancelled.");
191
+ return;
192
+ }
193
+ if (wantCasper) {
194
+ 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.");
199
+ }
200
+ const wantX402 = await clack.confirm({
201
+ message: "Enable x402 micropayments?",
202
+ initialValue: false
203
+ });
204
+ if (clack.isCancel(wantX402)) {
205
+ clack.cancel("Setup cancelled.");
206
+ return;
207
+ }
208
+ if (wantX402) {
209
+ clack.log.info("Add to your .env file:\n\n" +
210
+ " X402_ENABLE_REAL_SETTLEMENT=true\n" +
211
+ " X402_SETTLEMENT_MODE=casper-cli\n" +
212
+ " X402_BUYER_ACCOUNT_HASH=account-hash-<64 hex chars>\n" +
213
+ " CASPER_ENABLE_REAL_SUBMISSION=true\n\n" +
214
+ "Get your account hash: casper-client account-address --public-key <your-pubkey-hex>");
215
+ }
216
+ clack.outro("Restart your MCP clients to load the server.");
217
+ }
95
218
  export function initializeLocalSetup(env = process.env) {
96
219
  const paths = getDefaultMainspringPaths(env);
97
220
  const envFile = env.SIGIL_ENV_FILE?.trim() ? resolveEnvPath(env) : paths.envFile;
@@ -4,5 +4,13 @@ export type ClientSetupResult = {
4
4
  status: "written" | "already-set" | "not-installed" | "error";
5
5
  error?: string;
6
6
  };
7
+ export type ClientDetection = {
8
+ name: string;
9
+ configPath: string;
10
+ installed: boolean;
11
+ format: "standard" | "zed" | "continue";
12
+ };
13
+ export declare function detectClients(env?: NodeJS.ProcessEnv): ClientDetection[];
14
+ export declare function configureClients(selected: ClientDetection[], env?: NodeJS.ProcessEnv): ClientSetupResult[];
7
15
  export declare function setupAllClients(env?: NodeJS.ProcessEnv): ClientSetupResult[];
8
16
  export declare function formatClientSetupReport(results: ClientSetupResult[]): string;
@@ -17,14 +17,81 @@ const CLIENTS = [
17
17
  { name: "Cursor", path: "%USERPROFILE%/.cursor/mcp.json", platforms: ["win32"], format: "standard" },
18
18
  // Windsurf
19
19
  { name: "Windsurf", path: "~/.codeium/windsurf/mcp_config.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
20
- // Windsurf (also on Windows via AppData)
21
20
  { name: "Windsurf", path: "%APPDATA%/Windsurf/User/globalStorage/codeium.windsurf/mcp_config.json", platforms: ["win32"], format: "standard" },
22
21
  // Zed
23
22
  { name: "Zed", path: "~/.config/zed/settings.json", platforms: ["darwin", "linux"], format: "zed" },
24
23
  // Continue.dev
25
24
  { name: "Continue", path: "~/.continue/config.json", platforms: ["darwin", "linux", "win32"], format: "continue" },
26
- // VS Code (workspace-agnostic user MCP config — requires MCP extension)
25
+ // VS Code (user-level MCP config — requires GitHub Copilot or MCP extension)
27
26
  { name: "VS Code", path: "~/.vscode/mcp.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
27
+ // Cline (VS Code extension by saoudrizwan)
28
+ {
29
+ name: "Cline",
30
+ path: "~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
31
+ detectDir: "~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev",
32
+ platforms: ["darwin"], format: "standard"
33
+ },
34
+ {
35
+ name: "Cline",
36
+ path: "~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
37
+ detectDir: "~/.config/Code/User/globalStorage/saoudrizwan.claude-dev",
38
+ platforms: ["linux"], format: "standard"
39
+ },
40
+ {
41
+ name: "Cline",
42
+ path: "%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
43
+ detectDir: "%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev",
44
+ platforms: ["win32"], format: "standard"
45
+ },
46
+ // Roo Code (VS Code extension, fork of Cline)
47
+ {
48
+ name: "Roo Code",
49
+ path: "~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json",
50
+ detectDir: "~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline",
51
+ platforms: ["darwin"], format: "standard"
52
+ },
53
+ {
54
+ name: "Roo Code",
55
+ path: "~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json",
56
+ detectDir: "~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline",
57
+ platforms: ["linux"], format: "standard"
58
+ },
59
+ {
60
+ name: "Roo Code",
61
+ path: "%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json",
62
+ detectDir: "%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline",
63
+ platforms: ["win32"], format: "standard"
64
+ },
65
+ // Kilo Code (VS Code extension)
66
+ {
67
+ name: "Kilo Code",
68
+ path: "~/Library/Application Support/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json",
69
+ detectDir: "~/Library/Application Support/Code/User/globalStorage/kilocode.kilo-code",
70
+ platforms: ["darwin"], format: "standard"
71
+ },
72
+ {
73
+ name: "Kilo Code",
74
+ path: "~/.config/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json",
75
+ detectDir: "~/.config/Code/User/globalStorage/kilocode.kilo-code",
76
+ platforms: ["linux"], format: "standard"
77
+ },
78
+ {
79
+ name: "Kilo Code",
80
+ path: "%APPDATA%/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json",
81
+ detectDir: "%APPDATA%/Code/User/globalStorage/kilocode.kilo-code",
82
+ platforms: ["win32"], format: "standard"
83
+ },
84
+ // OpenCode (terminal AI coding assistant)
85
+ { name: "OpenCode", path: "~/.config/opencode/config.json", platforms: ["darwin", "linux"], format: "standard" },
86
+ { name: "OpenCode", path: "%APPDATA%/opencode/config.json", platforms: ["win32"], format: "standard" },
87
+ // Amazon Q Developer
88
+ { name: "Amazon Q", path: "~/.aws/amazonq/mcp.json", platforms: ["darwin", "linux"], format: "standard" },
89
+ { name: "Amazon Q", path: "%USERPROFILE%/.aws/amazonq/mcp.json", platforms: ["win32"], format: "standard" },
90
+ // Gemini CLI (Google)
91
+ { name: "Gemini CLI", path: "~/.gemini/settings.json", platforms: ["darwin", "linux"], format: "standard" },
92
+ { name: "Gemini CLI", path: "%USERPROFILE%/.gemini/settings.json", platforms: ["win32"], format: "standard" },
93
+ // Codex CLI (OpenAI)
94
+ { name: "Codex CLI", path: "~/.codex/config.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
28
95
  ];
29
96
  function expandPath(p, env) {
30
97
  const home = env.HOME ?? env.USERPROFILE ?? homedir();
@@ -33,8 +100,9 @@ function expandPath(p, env) {
33
100
  .replace(/%APPDATA%/gi, env.APPDATA ?? "")
34
101
  .replace(/%USERPROFILE%/gi, env.USERPROFILE ?? home);
35
102
  }
36
- function isInstalled(configPath) {
37
- return existsSync(dirname(configPath)) || existsSync(configPath);
103
+ function isInstalled(configPath, detectDir) {
104
+ const dir = detectDir ?? dirname(configPath);
105
+ return existsSync(dir) || existsSync(configPath);
38
106
  }
39
107
  function readJson(path) {
40
108
  if (!existsSync(path))
@@ -82,6 +150,42 @@ function mergeConfig(json, format) {
82
150
  }
83
151
  };
84
152
  }
153
+ export function detectClients(env = process.env) {
154
+ const plat = process.platform;
155
+ const seen = new Set();
156
+ const results = [];
157
+ for (const client of CLIENTS) {
158
+ if (!client.platforms.includes(plat))
159
+ continue;
160
+ const configPath = expandPath(client.path, env);
161
+ const key = `${client.name}:${configPath}`;
162
+ if (seen.has(key))
163
+ continue;
164
+ seen.add(key);
165
+ const detectDir = client.detectDir ? expandPath(client.detectDir, env) : undefined;
166
+ results.push({ name: client.name, configPath, installed: isInstalled(configPath, detectDir), format: client.format });
167
+ }
168
+ return results;
169
+ }
170
+ export function configureClients(selected, env = process.env) {
171
+ const results = [];
172
+ for (const client of selected) {
173
+ try {
174
+ const json = readJson(client.configPath);
175
+ if (alreadySet(json, client.format)) {
176
+ results.push({ name: client.name, configPath: client.configPath, status: "already-set" });
177
+ continue;
178
+ }
179
+ mkdirSync(dirname(client.configPath), { recursive: true });
180
+ writeFileSync(client.configPath, JSON.stringify(mergeConfig(json, client.format), null, 2) + "\n", "utf8");
181
+ results.push({ name: client.name, configPath: client.configPath, status: "written" });
182
+ }
183
+ catch (e) {
184
+ results.push({ name: client.name, configPath: client.configPath, status: "error", error: String(e) });
185
+ }
186
+ }
187
+ return results;
188
+ }
85
189
  export function setupAllClients(env = process.env) {
86
190
  const plat = process.platform;
87
191
  const results = [];
@@ -94,7 +198,8 @@ export function setupAllClients(env = process.env) {
94
198
  if (seen.has(key))
95
199
  continue;
96
200
  seen.add(key);
97
- if (!isInstalled(configPath)) {
201
+ const detectDir = client.detectDir ? expandPath(client.detectDir, env) : undefined;
202
+ if (!isInstalled(configPath, detectDir)) {
98
203
  results.push({ name: client.name, configPath, status: "not-installed" });
99
204
  continue;
100
205
  }
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { loadConfig } from "./config.js";
5
5
  import { ensureGrimoireMasterKey, loadLocalEnvFile } from "./env-file.js";
6
6
  import { createSigilServer } from "./server.js";
7
7
  async function main() {
8
- if (runCliCommand(process.argv.slice(2))) {
8
+ if (await runCliCommand(process.argv.slice(2))) {
9
9
  return;
10
10
  }
11
11
  loadLocalEnvFile();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mrmainspring",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
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",
@@ -47,6 +47,7 @@
47
47
  "x402:signer": "tsx scripts/x402-signer-sidecar.ts"
48
48
  },
49
49
  "dependencies": {
50
+ "@clack/prompts": "^1.5.1",
50
51
  "@modelcontextprotocol/sdk": "^1.13.0",
51
52
  "zod": "^3.25.76"
52
53
  },