mrmainspring 0.2.1 → 0.2.3
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 +140 -22
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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,36 +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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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) {
|
|
209
|
-
|
|
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";
|
|
295
|
+
clack.log.info("x402 setup — 3 steps:");
|
|
296
|
+
clack.log.info("Step 1 — Get your Casper account hash\n\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" +
|
|
303
|
+
" account-hash-d0a57c6a95e74463de156cac761e17f0923eafc730ce3ce3a0c747c6598b0500\n\n" +
|
|
304
|
+
" No casper-client? Install: cargo install casper-client");
|
|
305
|
+
clack.log.info("Step 2 — Add to your .env file\n\n" +
|
|
210
306
|
" X402_ENABLE_REAL_SETTLEMENT=true\n" +
|
|
211
307
|
" X402_SETTLEMENT_MODE=casper-cli\n" +
|
|
212
|
-
" X402_BUYER_ACCOUNT_HASH=account-hash-<
|
|
213
|
-
" CASPER_ENABLE_REAL_SUBMISSION=true\n
|
|
214
|
-
|
|
308
|
+
" X402_BUYER_ACCOUNT_HASH=account-hash-<your hash>\n" +
|
|
309
|
+
" CASPER_ENABLE_REAL_SUBMISSION=true\n" +
|
|
310
|
+
` CASPER_RPC_URL=${x402RpcUrl}\n` +
|
|
311
|
+
" CASPER_ACCOUNT_KEY_PATH=./keys/secret_key.pem\n\n" +
|
|
312
|
+
` Your .env: ${result.envFile}`);
|
|
313
|
+
clack.log.info("Step 3 — Fund your account\n\n" +
|
|
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}`);
|
|
215
333
|
}
|
|
216
334
|
clack.outro("Restart your MCP clients to load the server.");
|
|
217
335
|
}
|
package/package.json
CHANGED