@memoraone/mcp 0.1.32 → 0.1.34

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +514 -94
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -30,7 +30,7 @@ var require_package = __commonJS({
30
30
  "package.json"(exports2, module2) {
31
31
  module2.exports = {
32
32
  name: "@memoraone/mcp",
33
- version: "0.1.32",
33
+ version: "0.1.34",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -562,8 +562,8 @@ var StdioLineReader = class {
562
562
  if (this.closed) {
563
563
  return null;
564
564
  }
565
- return new Promise((resolve8) => {
566
- this.waiters.push(resolve8);
565
+ return new Promise((resolve9) => {
566
+ this.waiters.push(resolve9);
567
567
  });
568
568
  }
569
569
  /** Re-queue lines read during an intermediate protocol step (e.g. roots/list) for the main bridge loop. */
@@ -822,9 +822,9 @@ function summarizeJsonRpcMethod(line) {
822
822
  }
823
823
  }
824
824
  function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
825
- return new Promise((resolve8, reject) => {
825
+ return new Promise((resolve9, reject) => {
826
826
  const tryConnect = (attempt) => {
827
- connect2(socketPath).then(resolve8).catch((err) => {
827
+ connect2(socketPath).then(resolve9).catch((err) => {
828
828
  if (attempt >= maxRetries) {
829
829
  reject(err);
830
830
  return;
@@ -897,8 +897,8 @@ var BridgeDaemonRouter = class {
897
897
  this.maxRetries = options.maxRetries ?? 5;
898
898
  this.retryDelayMs = options.retryDelayMs ?? 200;
899
899
  this.lineReader = options.lineReader ?? null;
900
- this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve8, reject) => {
901
- const socket = net.connect(socketPath, () => resolve8(socket));
900
+ this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve9, reject) => {
901
+ const socket = net.connect(socketPath, () => resolve9(socket));
902
902
  socket.on("error", reject);
903
903
  }));
904
904
  this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
@@ -1142,9 +1142,9 @@ async function runBridgeProxy(options) {
1142
1142
  }
1143
1143
 
1144
1144
  // src/setupIdeFiles.ts
1145
- var fs7 = __toESM(require("fs/promises"), 1);
1145
+ var fs8 = __toESM(require("fs/promises"), 1);
1146
1146
  var os4 = __toESM(require("os"), 1);
1147
- var path9 = __toESM(require("path"), 1);
1147
+ var path10 = __toESM(require("path"), 1);
1148
1148
 
1149
1149
  // src/cleanup.ts
1150
1150
  var fs5 = __toESM(require("fs/promises"), 1);
@@ -1163,17 +1163,36 @@ var import_node_util = require("util");
1163
1163
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
1164
1164
  var MEMORAONE_PROD_API_URL = "https://api.memoraone.com";
1165
1165
  var MEMORAONE_LOCAL_API_URL = "http://localhost:3001";
1166
+ var MEMORAONE_STAGING_API_URL = "https://memora-api-staging-phbtrzocjq-uk.a.run.app";
1166
1167
  var MEMORAONE_STAGING_API_URL_PREFIX = "https://memora-api-staging-";
1167
- function buildMemoraoneCursorMcpServer(npxPath, workspaceRoot) {
1168
+ function cursorMcpApiUrl(environment) {
1169
+ if (environment === "local") return MEMORAONE_LOCAL_API_URL;
1170
+ if (environment === "staging") return MEMORAONE_STAGING_API_URL;
1171
+ return MEMORAONE_PROD_API_URL;
1172
+ }
1173
+ function buildMemoraoneCursorMcpServer(options) {
1168
1174
  const env = {
1169
- MEMORAONE_API_URL: MEMORAONE_PROD_API_URL,
1175
+ MEMORAONE_API_URL: cursorMcpApiUrl(options.environment),
1170
1176
  MEMORAONE_IDE_TYPE: "cursor"
1171
1177
  };
1172
- if (workspaceRoot !== void 0) {
1173
- env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(workspaceRoot);
1178
+ if (options.workspaceRoot !== void 0) {
1179
+ env[MEMORAONE_WORKSPACE_ROOT_ENV] = path6.resolve(options.workspaceRoot);
1180
+ }
1181
+ if (options.environment === "local") {
1182
+ if (!options.cliPath) {
1183
+ throw new Error("[setup-ide-files] Local Cursor MCP config requires a built CLI path.");
1184
+ }
1185
+ return {
1186
+ command: "node",
1187
+ args: [options.cliPath],
1188
+ env
1189
+ };
1190
+ }
1191
+ if (!options.npxPath) {
1192
+ throw new Error("[setup-ide-files] Cursor MCP config requires a resolved npx path.");
1174
1193
  }
1175
1194
  return {
1176
- command: npxPath,
1195
+ command: options.npxPath,
1177
1196
  args: ["-y", "@memoraone/mcp@latest"],
1178
1197
  env
1179
1198
  };
@@ -1272,10 +1291,16 @@ async function resolveNpxPath() {
1272
1291
  }
1273
1292
  return null;
1274
1293
  }
1275
- function mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot) {
1294
+ function mergeCursorRepoMcpConfigObject(existing, writeOptions) {
1295
+ const environment = writeOptions.environment ?? "production";
1276
1296
  const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
1277
1297
  const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
1278
- mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath, repoRoot);
1298
+ mcpServers.memoraone = buildMemoraoneCursorMcpServer({
1299
+ environment,
1300
+ npxPath: writeOptions.npxPath,
1301
+ cliPath: writeOptions.cliPath,
1302
+ workspaceRoot: writeOptions.repoRoot
1303
+ });
1279
1304
  return { ...base, mcpServers };
1280
1305
  }
1281
1306
  function isMemoraoneManagedApiUrl(url) {
@@ -1394,23 +1419,30 @@ function logCursorMcpConfigAudit(prefix, audit) {
1394
1419
  );
1395
1420
  }
1396
1421
  }
1397
- function logCursorMcpCliSummary(info, dryRun) {
1398
- const { repoConfigPath, repoOutcome, npxPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
1422
+ function logCursorMcpCliSummary(info, dryRun, opts) {
1423
+ const { repoConfigPath, repoOutcome, npxPath, cliPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
1424
+ const interactive = opts?.forInteractivePostSetup === true;
1399
1425
  console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
1400
- console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
1426
+ if (cliPath) {
1427
+ console.log(`[setup-ide-files] Resolved local CLI: ${cliPath}`);
1428
+ } else if (npxPath) {
1429
+ console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
1430
+ }
1401
1431
  if (repoBackupPath) {
1402
1432
  console.log(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
1403
1433
  }
1404
- if (repoOutcome === "created") {
1405
- console.log(
1406
- dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
1407
- );
1408
- } else if (repoOutcome === "updated") {
1409
- console.log(
1410
- dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
1411
- );
1412
- } else if (repoOutcome === "skipped") {
1413
- console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
1434
+ if (!interactive) {
1435
+ if (repoOutcome === "created") {
1436
+ console.log(
1437
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
1438
+ );
1439
+ } else if (repoOutcome === "updated") {
1440
+ console.log(
1441
+ dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
1442
+ );
1443
+ } else if (repoOutcome === "skipped") {
1444
+ console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
1445
+ }
1414
1446
  }
1415
1447
  if (globalMemoraoneRemoved && globalConfigPath) {
1416
1448
  console.log(
@@ -1427,9 +1459,11 @@ function logCursorMcpCliSummary(info, dryRun) {
1427
1459
  console.log(
1428
1460
  "[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
1429
1461
  );
1430
- console.log(
1431
- "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
1432
- );
1462
+ if (!interactive) {
1463
+ console.log(
1464
+ "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
1465
+ );
1466
+ }
1433
1467
  }
1434
1468
 
1435
1469
  // src/cleanup.ts
@@ -2094,7 +2128,7 @@ async function buildJetBrainsMemoraoneServer(options) {
2094
2128
  async function verifyJetBrainsMcpHandshake(options) {
2095
2129
  const timeoutMs = options.timeoutMs ?? 15e3;
2096
2130
  const { server } = options;
2097
- return new Promise((resolve8) => {
2131
+ return new Promise((resolve9) => {
2098
2132
  let settled = false;
2099
2133
  const finish = (ok, detail) => {
2100
2134
  if (settled) return;
@@ -2104,7 +2138,7 @@ async function verifyJetBrainsMcpHandshake(options) {
2104
2138
  child.kill();
2105
2139
  } catch {
2106
2140
  }
2107
- resolve8({ ok, detail });
2141
+ resolve9({ ok, detail });
2108
2142
  };
2109
2143
  const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
2110
2144
  env: { ...process.env, ...server.env },
@@ -2308,6 +2342,288 @@ function logJetBrainsMcpCliSummary(info, dryRun) {
2308
2342
  );
2309
2343
  }
2310
2344
 
2345
+ // src/resolveBuiltCliPath.ts
2346
+ var fs7 = __toESM(require("fs/promises"), 1);
2347
+ var path9 = __toESM(require("path"), 1);
2348
+ var MONOREPO_CLI_REL = path9.join("packages", "mcp", "dist", "cli.cjs");
2349
+ async function pathExists3(filePath) {
2350
+ try {
2351
+ await fs7.access(filePath);
2352
+ return true;
2353
+ } catch {
2354
+ return false;
2355
+ }
2356
+ }
2357
+ async function findMonorepoCliFrom(startDir) {
2358
+ let current = path9.resolve(startDir);
2359
+ const root = path9.parse(current).root;
2360
+ while (true) {
2361
+ const candidate = path9.join(current, MONOREPO_CLI_REL);
2362
+ if (await pathExists3(candidate)) {
2363
+ return path9.resolve(candidate);
2364
+ }
2365
+ if (current === root) break;
2366
+ current = path9.dirname(current);
2367
+ }
2368
+ return null;
2369
+ }
2370
+ async function resolveBuiltCliPathAsync(options) {
2371
+ const searchDirs = [];
2372
+ if (options?.searchFrom !== void 0) {
2373
+ const dirs = Array.isArray(options.searchFrom) ? options.searchFrom : [options.searchFrom];
2374
+ searchDirs.push(...dirs);
2375
+ }
2376
+ searchDirs.push(process.cwd());
2377
+ const seen = /* @__PURE__ */ new Set();
2378
+ for (const dir of searchDirs) {
2379
+ const key = path9.resolve(dir);
2380
+ if (seen.has(key)) continue;
2381
+ seen.add(key);
2382
+ const found = await findMonorepoCliFrom(key);
2383
+ if (found) return found;
2384
+ }
2385
+ const here = process.argv[1] ? path9.dirname(path9.resolve(process.argv[1])) : process.cwd();
2386
+ const candidates = [
2387
+ path9.join(here, "cli.cjs"),
2388
+ path9.join(here, "..", "dist", "cli.cjs"),
2389
+ path9.join(here, "..", "..", "dist", "cli.cjs")
2390
+ ];
2391
+ for (const candidate of candidates) {
2392
+ if (await pathExists3(candidate)) {
2393
+ return path9.resolve(candidate);
2394
+ }
2395
+ }
2396
+ return null;
2397
+ }
2398
+
2399
+ // src/openCursorMcpSettings.ts
2400
+ var import_node_child_process5 = require("child_process");
2401
+ var readline4 = __toESM(require("readline/promises"), 1);
2402
+ var import_node_util3 = require("util");
2403
+
2404
+ // src/terminalPresentation.ts
2405
+ var ANSI = {
2406
+ reset: "\x1B[0m",
2407
+ bold: "\x1B[1m",
2408
+ dim: "\x1B[2m",
2409
+ green: "\x1B[32m",
2410
+ yellow: "\x1B[33m",
2411
+ cyan: "\x1B[36m"
2412
+ };
2413
+ function isCiLikeEnv(env = process.env) {
2414
+ if (env.CI === "true" || env.CI === "1") return true;
2415
+ if (env.GITHUB_ACTIONS === "true" || env.GITHUB_ACTIONS === "1") return true;
2416
+ if (env.GITLAB_CI === "true" || env.GITLAB_CI === "1") return true;
2417
+ if (env.CIRCLECI === "true" || env.CIRCLECI === "1") return true;
2418
+ if (env.BUILDKITE === "true" || env.BUILDKITE === "1") return true;
2419
+ if (typeof env.CI === "string" && env.CI.trim() !== "" && env.CI !== "0" && env.CI !== "false") {
2420
+ return true;
2421
+ }
2422
+ return false;
2423
+ }
2424
+ function shouldEnableAnsiColor(opts = {}) {
2425
+ if (typeof opts.color === "boolean") return opts.color;
2426
+ const env = opts.env ?? process.env;
2427
+ if (env.NO_COLOR !== void 0) return false;
2428
+ if (isCiLikeEnv(env)) return false;
2429
+ const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
2430
+ return tty;
2431
+ }
2432
+ function shouldUseUnicodeSymbols(opts = {}) {
2433
+ if (typeof opts.unicode === "boolean") return opts.unicode;
2434
+ const env = opts.env ?? process.env;
2435
+ const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
2436
+ if (!tty) return false;
2437
+ if (env.TERM === "dumb") return false;
2438
+ if (process.platform === "win32") {
2439
+ return Boolean(
2440
+ env.WT_SESSION || env.WT_PROFILE_ID || env.ConEmuANSI === "ON" || env.TERM_PROGRAM === "vscode" || env.TERM_PROGRAM === "cursor" || typeof env.TERM === "string" && env.TERM !== "" && env.TERM !== "dumb"
2441
+ );
2442
+ }
2443
+ return true;
2444
+ }
2445
+ function paint(enabled, code, text) {
2446
+ if (!enabled || text === "") return text;
2447
+ return `${code}${text}${ANSI.reset}`;
2448
+ }
2449
+ function createTerminalPresentation(opts = {}) {
2450
+ const color = shouldEnableAnsiColor(opts);
2451
+ const unicode = shouldUseUnicodeSymbols(opts);
2452
+ const successSymbol = unicode ? "\u2713" : "[OK]";
2453
+ const warningSymbol = unicode ? "\u26A0" : "[WARN]";
2454
+ const nextActionPrefix = unicode ? "\u2192" : "Next:";
2455
+ return {
2456
+ color,
2457
+ unicode,
2458
+ bold: (text) => paint(color, ANSI.bold, text),
2459
+ green: (text) => paint(color, ANSI.green, text),
2460
+ cyan: (text) => paint(color, ANSI.cyan, text),
2461
+ yellow: (text) => paint(color, ANSI.yellow, text),
2462
+ dim: (text) => paint(color, ANSI.dim, text),
2463
+ successSymbol,
2464
+ warningSymbol,
2465
+ nextActionPrefix,
2466
+ successLine: (message) => paint(color, ANSI.green, `${successSymbol} ${message}`),
2467
+ warningLine: (message) => paint(color, ANSI.yellow, `${warningSymbol} ${message}`),
2468
+ nextActionLine: (message) => `${nextActionPrefix} ${message}`,
2469
+ heading: (text) => paint(color, ANSI.bold, text),
2470
+ indent: (text) => ` ${text}`
2471
+ };
2472
+ }
2473
+
2474
+ // src/openCursorMcpSettings.ts
2475
+ var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process5.execFile);
2476
+ var OPEN_CURSOR_MCP_SETTINGS_PROMPT = "Open Cursor MCP settings now? [Y/n] ";
2477
+ function resolvePresentation(deps) {
2478
+ if (deps.presentation) return deps.presentation;
2479
+ const opts = {
2480
+ env: deps.env ?? process.env,
2481
+ stdoutIsTty: deps.stdoutIsTty ?? process.stdout.isTTY === true,
2482
+ color: deps.color,
2483
+ unicode: deps.unicode
2484
+ };
2485
+ return createTerminalPresentation(opts);
2486
+ }
2487
+ function shouldPromptOpenCursorMcpSettings(opts) {
2488
+ if (!opts.explicitCursor) return false;
2489
+ if (opts.all) return false;
2490
+ if (opts.dryRun) return false;
2491
+ if (!opts.stdinIsTty) return false;
2492
+ if (isCiLikeEnv(opts.env ?? process.env)) return false;
2493
+ return true;
2494
+ }
2495
+ function isYesDefaultAnswer(answer) {
2496
+ const trimmed = answer.trim();
2497
+ if (trimmed === "") return true;
2498
+ if (/^y(es)?$/i.test(trimmed)) return true;
2499
+ if (/^n(o)?$/i.test(trimmed)) return false;
2500
+ return false;
2501
+ }
2502
+ async function confirmYesDefault(question, deps = {}) {
2503
+ if (deps.ask) {
2504
+ return isYesDefaultAnswer(await deps.ask(question));
2505
+ }
2506
+ const input2 = deps.input ?? process.stdin;
2507
+ const output2 = deps.output ?? process.stdout;
2508
+ if (!("isTTY" in input2) || !input2.isTTY) {
2509
+ return false;
2510
+ }
2511
+ const rl = readline4.createInterface({ input: input2, output: output2 });
2512
+ try {
2513
+ const answer = await rl.question(question.endsWith(" ") ? question : `${question} `);
2514
+ return isYesDefaultAnswer(answer);
2515
+ } finally {
2516
+ rl.close();
2517
+ }
2518
+ }
2519
+ function collectOutcomePaths(outcomes) {
2520
+ const created = [];
2521
+ const updated = [];
2522
+ for (const [file, outcome] of Object.entries(outcomes)) {
2523
+ if (outcome === "created") created.push(file);
2524
+ else if (outcome === "updated") updated.push(file);
2525
+ }
2526
+ return { created, updated };
2527
+ }
2528
+ function formatCursorSetupCompletedSummary(opts, presentation = createTerminalPresentation({ color: false, unicode: false })) {
2529
+ const { created, updated } = collectOutcomePaths(opts.outcomes);
2530
+ const tp = presentation;
2531
+ const lines = [
2532
+ tp.successLine("MemoraOne setup completed for Cursor"),
2533
+ "",
2534
+ tp.heading("Repository"),
2535
+ tp.indent(tp.cyan(opts.repoRoot)),
2536
+ "",
2537
+ tp.heading("Changes")
2538
+ ];
2539
+ if (created.length === 0 && updated.length === 0) {
2540
+ lines.push(tp.indent(tp.dim("No file changes needed")));
2541
+ } else {
2542
+ if (created.length) {
2543
+ lines.push(tp.indent(`Created: ${created.join(", ")}`));
2544
+ }
2545
+ if (updated.length) {
2546
+ lines.push(tp.indent(`Updated: ${updated.join(", ")}`));
2547
+ }
2548
+ }
2549
+ return lines;
2550
+ }
2551
+ function printCursorSetupCompletedSummary(opts, println = console.log, presentation) {
2552
+ const tp = presentation ?? createTerminalPresentation();
2553
+ for (const line of formatCursorSetupCompletedSummary(opts, tp)) {
2554
+ println(line);
2555
+ }
2556
+ }
2557
+ function macosOpenCursorMcpSettingsAppleScript() {
2558
+ return [
2559
+ 'tell application "Cursor" to activate',
2560
+ "delay 0.5",
2561
+ 'tell application "System Events"',
2562
+ 'keystroke "p" using {command down, shift down}',
2563
+ "delay 0.4",
2564
+ 'keystroke "View: Open MCP Settings"',
2565
+ "delay 0.4",
2566
+ "key code 36",
2567
+ "end tell"
2568
+ ].join("\n");
2569
+ }
2570
+ async function openCursorMcpSettingsViaOsascript(execFileImpl = execFileAsync3) {
2571
+ await execFileImpl("osascript", ["-e", macosOpenCursorMcpSettingsAppleScript()], {
2572
+ timeout: 3e4
2573
+ });
2574
+ }
2575
+ function manualCursorMcpSettingsSteps(platform) {
2576
+ const chord = platform === "darwin" ? "Command + Shift + P" : "Ctrl + Shift + P";
2577
+ return [
2578
+ "To finish setup manually:",
2579
+ "1. Open this repository in Cursor.",
2580
+ `2. Press ${chord}.`,
2581
+ '3. Run "View: Open MCP Settings".',
2582
+ '4. Find "memoraone" and enable it.',
2583
+ "5. Confirm it turns green.",
2584
+ "6. Return to MemoraOne Studio and refresh Sources."
2585
+ ];
2586
+ }
2587
+ function printManualCursorMcpSettingsSteps(platform, println = console.log) {
2588
+ for (const line of manualCursorMcpSettingsSteps(platform)) {
2589
+ println(line);
2590
+ }
2591
+ }
2592
+ function formatOpenCursorMcpSettingsPrompt(presentation = createTerminalPresentation({ color: false, unicode: false })) {
2593
+ return `${presentation.indent(OPEN_CURSOR_MCP_SETTINGS_PROMPT.trimEnd())} `;
2594
+ }
2595
+ async function runOpenCursorMcpSettingsFlow(deps = {}) {
2596
+ const platform = deps.platform ?? process.platform;
2597
+ const println = deps.println ?? console.log;
2598
+ const tp = resolvePresentation(deps);
2599
+ const confirm = deps.confirm ?? ((question) => confirmYesDefault(question));
2600
+ println("");
2601
+ println(tp.heading("Next"));
2602
+ const yes = await confirm(formatOpenCursorMcpSettingsPrompt(tp));
2603
+ if (!yes) {
2604
+ printManualCursorMcpSettingsSteps(platform, println);
2605
+ return;
2606
+ }
2607
+ if (platform === "darwin") {
2608
+ println(tp.warningLine("macOS may request Automation or Accessibility permission."));
2609
+ try {
2610
+ const open = deps.openViaOsascript ?? (() => openCursorMcpSettingsViaOsascript(deps.execFile ?? execFileAsync3));
2611
+ await open();
2612
+ println(tp.successLine('Confirm "memoraone" is enabled and green'));
2613
+ println(tp.nextActionLine("Return to MemoraOne Studio and refresh Sources"));
2614
+ } catch (err) {
2615
+ const detail = err instanceof Error ? err.message : String(err);
2616
+ println(tp.warningLine(`Could not open Cursor MCP settings automatically (${detail}).`));
2617
+ println(
2618
+ "Permission may be required at: System Settings \u2192 Privacy & Security \u2192 Accessibility"
2619
+ );
2620
+ printManualCursorMcpSettingsSteps("darwin", println);
2621
+ }
2622
+ return;
2623
+ }
2624
+ printManualCursorMcpSettingsSteps(platform, println);
2625
+ }
2626
+
2311
2627
  // src/setupIdeFiles.ts
2312
2628
  var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
2313
2629
  var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
@@ -2323,15 +2639,15 @@ function buildMemoraoneMcpServer(ideType, command = "npx") {
2323
2639
  };
2324
2640
  }
2325
2641
  function assertUnderRepoRoot(repoRoot, absPath) {
2326
- const normRoot = path9.resolve(repoRoot) + path9.sep;
2327
- const normPath = path9.resolve(absPath);
2328
- if (normPath !== path9.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
2642
+ const normRoot = path10.resolve(repoRoot) + path10.sep;
2643
+ const normPath = path10.resolve(absPath);
2644
+ if (normPath !== path10.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
2329
2645
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
2330
2646
  }
2331
2647
  }
2332
- async function pathExists3(filePath) {
2648
+ async function pathExists4(filePath) {
2333
2649
  try {
2334
- await fs7.access(filePath);
2650
+ await fs8.access(filePath);
2335
2651
  return true;
2336
2652
  } catch {
2337
2653
  return false;
@@ -2352,12 +2668,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
2352
2668
  }
2353
2669
  async function ensureGitignoreMemoraone(repoRoot, opts) {
2354
2670
  if (opts.noGitignore) return "skipped";
2355
- const abs = path9.join(repoRoot, ".gitignore");
2671
+ const abs = path10.join(repoRoot, ".gitignore");
2356
2672
  assertUnderRepoRoot(repoRoot, abs);
2357
2673
  let prior = "";
2358
2674
  let existed = false;
2359
2675
  try {
2360
- prior = await fs7.readFile(abs, "utf8");
2676
+ prior = await fs8.readFile(abs, "utf8");
2361
2677
  existed = true;
2362
2678
  } catch (err) {
2363
2679
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
@@ -2368,22 +2684,22 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
2368
2684
  const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
2369
2685
  const next = (existed ? prior : "") + separator + block;
2370
2686
  if (opts.dryRun) return existed ? "updated" : "created";
2371
- await fs7.writeFile(abs, next, "utf8");
2687
+ await fs8.writeFile(abs, next, "utf8");
2372
2688
  return existed ? "updated" : "created";
2373
2689
  }
2374
2690
  async function findRepoRoot(startDir) {
2375
- let current = path9.resolve(startDir);
2376
- const root = path9.parse(current).root;
2691
+ let current = path10.resolve(startDir);
2692
+ const root = path10.parse(current).root;
2377
2693
  while (true) {
2378
- const gitPath = path9.join(current, ".git");
2379
- const m1Path = path9.join(current, "memoraone.m1");
2380
- if (await pathExists3(gitPath) || await pathExists3(m1Path)) {
2694
+ const gitPath = path10.join(current, ".git");
2695
+ const m1Path = path10.join(current, "memoraone.m1");
2696
+ if (await pathExists4(gitPath) || await pathExists4(m1Path)) {
2381
2697
  return current;
2382
2698
  }
2383
2699
  if (current === root) {
2384
2700
  return null;
2385
2701
  }
2386
- current = path9.dirname(current);
2702
+ current = path10.dirname(current);
2387
2703
  }
2388
2704
  }
2389
2705
  function stripLeadingLineComments3(text) {
@@ -2436,47 +2752,47 @@ function buildVscodeMcpJsonBody(existing) {
2436
2752
  const merged = { ...base, servers };
2437
2753
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
2438
2754
  }
2439
- function buildCursorMcpJsonBody(existing, npxPath, repoRoot) {
2440
- const merged = mergeCursorRepoMcpConfigObject(existing, npxPath, repoRoot);
2755
+ function buildCursorMcpJsonBody(existing, writeOptions) {
2756
+ const merged = mergeCursorRepoMcpConfigObject(existing, writeOptions);
2441
2757
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
2442
2758
  }
2443
2759
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
2444
- const abs = path9.join(repoRoot, relPath);
2760
+ const abs = path10.join(repoRoot, relPath);
2445
2761
  assertUnderRepoRoot(repoRoot, abs);
2446
2762
  let prior = "";
2447
2763
  let existed = false;
2448
2764
  try {
2449
- prior = await fs7.readFile(abs, "utf8");
2765
+ prior = await fs8.readFile(abs, "utf8");
2450
2766
  existed = true;
2451
2767
  } catch (err) {
2452
2768
  if (err?.code !== "ENOENT") throw err;
2453
2769
  }
2454
2770
  if (!existed) {
2455
2771
  if (opts.dryRun) return "created";
2456
- await fs7.mkdir(path9.dirname(abs), { recursive: true });
2457
- await fs7.writeFile(abs, fullContent, "utf8");
2772
+ await fs8.mkdir(path10.dirname(abs), { recursive: true });
2773
+ await fs8.writeFile(abs, fullContent, "utf8");
2458
2774
  return "created";
2459
2775
  }
2460
2776
  if (prior.includes(MANAGED_MARKER)) {
2461
2777
  if (prior === fullContent) return "skipped";
2462
2778
  if (opts.dryRun) return "updated";
2463
- await fs7.mkdir(path9.dirname(abs), { recursive: true });
2464
- await fs7.writeFile(abs, fullContent, "utf8");
2779
+ await fs8.mkdir(path10.dirname(abs), { recursive: true });
2780
+ await fs8.writeFile(abs, fullContent, "utf8");
2465
2781
  return "updated";
2466
2782
  }
2467
2783
  if (!opts.force) return "skipped-untracked";
2468
2784
  if (opts.dryRun) return "updated";
2469
- await fs7.mkdir(path9.dirname(abs), { recursive: true });
2470
- await fs7.writeFile(abs, fullContent, "utf8");
2785
+ await fs8.mkdir(path10.dirname(abs), { recursive: true });
2786
+ await fs8.writeFile(abs, fullContent, "utf8");
2471
2787
  return "updated";
2472
2788
  }
2473
2789
  async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
2474
- const abs = path9.join(repoRoot, relPath);
2790
+ const abs = path10.join(repoRoot, relPath);
2475
2791
  assertUnderRepoRoot(repoRoot, abs);
2476
2792
  let raw = "";
2477
2793
  let existed = false;
2478
2794
  try {
2479
- raw = await fs7.readFile(abs, "utf8");
2795
+ raw = await fs8.readFile(abs, "utf8");
2480
2796
  existed = true;
2481
2797
  } catch (err) {
2482
2798
  if (err?.code !== "ENOENT") throw err;
@@ -2484,8 +2800,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
2484
2800
  if (!existed) {
2485
2801
  const body = buildBody(null);
2486
2802
  if (opts.dryRun) return "created";
2487
- await fs7.mkdir(path9.dirname(abs), { recursive: true });
2488
- await fs7.writeFile(abs, body, "utf8");
2803
+ await fs8.mkdir(path10.dirname(abs), { recursive: true });
2804
+ await fs8.writeFile(abs, body, "utf8");
2489
2805
  return "created";
2490
2806
  }
2491
2807
  const managed = raw.includes(MANAGED_MARKER);
@@ -2500,8 +2816,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
2500
2816
  const next = buildBody(parsed);
2501
2817
  if (managed && next === raw) return "skipped";
2502
2818
  if (opts.dryRun) return "updated";
2503
- await fs7.mkdir(path9.dirname(abs), { recursive: true });
2504
- await fs7.writeFile(abs, next, "utf8");
2819
+ await fs8.mkdir(path10.dirname(abs), { recursive: true });
2820
+ await fs8.writeFile(abs, next, "utf8");
2505
2821
  return "updated";
2506
2822
  }
2507
2823
  function parseSetupIdeFlags(argv) {
@@ -2515,6 +2831,8 @@ function parseSetupIdeFlags(argv) {
2515
2831
  let cleanup = false;
2516
2832
  let devMode = false;
2517
2833
  let repair = false;
2834
+ let local = false;
2835
+ let staging = false;
2518
2836
  const unknown = [];
2519
2837
  for (const a of argv) {
2520
2838
  if (a === "--cursor") cursor = true;
@@ -2527,6 +2845,8 @@ function parseSetupIdeFlags(argv) {
2527
2845
  else if (a === "--cleanup") cleanup = true;
2528
2846
  else if (a === "--dev") devMode = true;
2529
2847
  else if (a === "--repair") repair = true;
2848
+ else if (a === "--local") local = true;
2849
+ else if (a === "--staging") staging = true;
2530
2850
  else if (a.startsWith("-")) unknown.push(a);
2531
2851
  }
2532
2852
  const specific = cursor || vscode || jetbrains;
@@ -2536,7 +2856,30 @@ function parseSetupIdeFlags(argv) {
2536
2856
  } else {
2537
2857
  targets = { cursor, vscode, jetbrains };
2538
2858
  }
2539
- return { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown };
2859
+ let flagError;
2860
+ if (local && staging) {
2861
+ flagError = "[setup-ide-files] --local and --staging are mutually exclusive.";
2862
+ }
2863
+ return {
2864
+ targets,
2865
+ force,
2866
+ dryRun,
2867
+ noGitignore,
2868
+ cleanup,
2869
+ devMode,
2870
+ repair,
2871
+ local,
2872
+ staging,
2873
+ all,
2874
+ explicitCursor: cursor,
2875
+ unknown,
2876
+ flagError
2877
+ };
2878
+ }
2879
+ function cursorEnvironmentFromFlags(local, staging) {
2880
+ if (local) return "local";
2881
+ if (staging) return "staging";
2882
+ return "production";
2540
2883
  }
2541
2884
  function summarizeOutcomes(outcomes) {
2542
2885
  const created = [];
@@ -2737,20 +3080,44 @@ description: MemoraOne MCP \u2014 IDE agent instructions
2737
3080
 
2738
3081
  ` + cursorRuleBody();
2739
3082
  if (o.targets.cursor) {
2740
- let npxPath;
2741
- if (o.npxPathOverride !== void 0) {
2742
- npxPath = o.npxPathOverride;
3083
+ const cursorEnvironment = o.cursorEnvironment ?? "production";
3084
+ let npxPath = null;
3085
+ let cliPath;
3086
+ if (cursorEnvironment === "local") {
3087
+ let resolvedCliPath = o.cursorLocalCliPathOverride;
3088
+ if (resolvedCliPath === void 0) {
3089
+ resolvedCliPath = await resolveBuiltCliPathAsync({ searchFrom: [repoRoot, o.cwd] });
3090
+ }
3091
+ if (!resolvedCliPath) {
3092
+ return {
3093
+ exitCode: 1,
3094
+ repoRoot,
3095
+ outcomes,
3096
+ error: "[setup-ide-files] Local mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
3097
+ };
3098
+ }
3099
+ cliPath = resolvedCliPath;
2743
3100
  } else {
2744
- npxPath = await resolveNpxPath();
2745
- }
2746
- if (!npxPath) {
2747
- return {
2748
- exitCode: 1,
2749
- repoRoot,
2750
- outcomes,
2751
- error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor MCP."
2752
- };
3101
+ if (o.npxPathOverride !== void 0) {
3102
+ npxPath = o.npxPathOverride;
3103
+ } else {
3104
+ npxPath = await resolveNpxPath();
3105
+ }
3106
+ if (!npxPath) {
3107
+ return {
3108
+ exitCode: 1,
3109
+ repoRoot,
3110
+ outcomes,
3111
+ error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor MCP."
3112
+ };
3113
+ }
2753
3114
  }
3115
+ const cursorWriteOptions = {
3116
+ environment: cursorEnvironment,
3117
+ npxPath: npxPath ?? void 0,
3118
+ cliPath,
3119
+ repoRoot
3120
+ };
2754
3121
  outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
2755
3122
  repoRoot,
2756
3123
  ".cursor/rules/memoraone-mcp.mdc",
@@ -2760,7 +3127,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
2760
3127
  outcomes[".cursor/mcp.json"] = await writeIdeMcpJson(
2761
3128
  repoRoot,
2762
3129
  ".cursor/mcp.json",
2763
- (existing) => buildCursorMcpJsonBody(existing, npxPath, repoRoot),
3130
+ (existing) => buildCursorMcpJsonBody(existing, cursorWriteOptions),
2764
3131
  { force: o.force, dryRun: o.dryRun }
2765
3132
  );
2766
3133
  const repoConfigPath = getCursorRepoMcpConfigPath(repoRoot);
@@ -2772,7 +3139,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
2772
3139
  homeDir: o.homeDir,
2773
3140
  explicitPath: o.cursorGlobalMcpConfigPath
2774
3141
  });
2775
- if (!globalDetection.ok) {
3142
+ if (globalDetection.ok === false) {
2776
3143
  return {
2777
3144
  exitCode: 1,
2778
3145
  repoRoot,
@@ -2804,7 +3171,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
2804
3171
  cursorMcp = {
2805
3172
  repoConfigPath,
2806
3173
  repoOutcome,
2807
- npxPath,
3174
+ ...cursorEnvironment === "local" ? { cliPath } : { npxPath },
2808
3175
  globalConfigPath,
2809
3176
  globalMemoraoneRemoved,
2810
3177
  globalBackupPath
@@ -2871,20 +3238,51 @@ description: MemoraOne MCP \u2014 IDE agent instructions
2871
3238
  }
2872
3239
  return { exitCode: 0, repoRoot, outcomes, cursorMcp, jetbrainsMcp, daemonCleanup };
2873
3240
  }
2874
- async function cliSetupIdeFiles(argv) {
2875
- const { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown } = parseSetupIdeFlags(argv);
3241
+ async function cliSetupIdeFiles(argv, options = {}) {
3242
+ const {
3243
+ targets,
3244
+ force,
3245
+ dryRun,
3246
+ noGitignore,
3247
+ cleanup,
3248
+ devMode,
3249
+ repair,
3250
+ local,
3251
+ staging,
3252
+ all,
3253
+ explicitCursor,
3254
+ unknown,
3255
+ flagError
3256
+ } = parseSetupIdeFlags(argv);
3257
+ if (flagError) {
3258
+ console.error(flagError);
3259
+ return 1;
3260
+ }
2876
3261
  if (unknown.length) {
2877
3262
  console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
2878
3263
  return 1;
2879
3264
  }
3265
+ const cwd = options.cwd ?? process.cwd();
3266
+ const openDeps = options.openCursorMcpSettings ?? {};
3267
+ const stdinIsTty = openDeps.stdinIsTty ?? process.stdin.isTTY === true;
3268
+ const env = openDeps.env ?? process.env;
3269
+ const promptOpenCursorSettings = shouldPromptOpenCursorMcpSettings({
3270
+ explicitCursor,
3271
+ all,
3272
+ dryRun,
3273
+ stdinIsTty,
3274
+ env
3275
+ });
2880
3276
  const result = await runSetupIdeFiles({
2881
- cwd: process.cwd(),
3277
+ cwd,
2882
3278
  targets,
2883
3279
  force,
2884
3280
  dryRun,
2885
- noGitignore,
3281
+ noGitignore: options.setupOverrides?.noGitignore ?? noGitignore,
2886
3282
  devMode,
2887
- repair
3283
+ repair,
3284
+ cursorEnvironment: cursorEnvironmentFromFlags(local, staging),
3285
+ ...options.setupOverrides
2888
3286
  });
2889
3287
  if (result.error) {
2890
3288
  console.error(result.error);
@@ -2901,23 +3299,45 @@ async function cliSetupIdeFiles(argv) {
2901
3299
  logSetupIdeCleanupSummary(result.daemonCleanup);
2902
3300
  }
2903
3301
  if (targets.cursor && result.cursorMcp) {
2904
- logCursorMcpCliSummary(result.cursorMcp, dryRun);
3302
+ logCursorMcpCliSummary(result.cursorMcp, dryRun, {
3303
+ forInteractivePostSetup: promptOpenCursorSettings
3304
+ });
2905
3305
  }
2906
3306
  if (targets.jetbrains && result.jetbrainsMcp) {
2907
3307
  logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
2908
3308
  }
2909
- summarizeOutcomes(result.outcomes);
2910
- if (dryRun) {
2911
- console.log("[setup-ide-files] Dry run: no files written.");
2912
- if (result.daemonCleanup && !result.daemonCleanup.skipped) {
2913
- console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
3309
+ if (promptOpenCursorSettings && result.repoRoot) {
3310
+ const presentation = openDeps.presentation ?? createTerminalPresentation({
3311
+ env,
3312
+ stdoutIsTty: openDeps.stdoutIsTty ?? process.stdout.isTTY === true,
3313
+ color: openDeps.color,
3314
+ unicode: openDeps.unicode
3315
+ });
3316
+ printCursorSetupCompletedSummary(
3317
+ { repoRoot: result.repoRoot, outcomes: result.outcomes },
3318
+ openDeps.println,
3319
+ presentation
3320
+ );
3321
+ await runOpenCursorMcpSettingsFlow({
3322
+ ...openDeps,
3323
+ stdinIsTty,
3324
+ env,
3325
+ presentation
3326
+ });
3327
+ } else {
3328
+ summarizeOutcomes(result.outcomes);
3329
+ if (dryRun) {
3330
+ console.log("[setup-ide-files] Dry run: no files written.");
3331
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
3332
+ console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
3333
+ }
2914
3334
  }
3335
+ console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
2915
3336
  }
2916
- console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
2917
3337
  if (cleanup) {
2918
3338
  console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
2919
3339
  const cleanupResult = await runCleanup({
2920
- cwd: process.cwd(),
3340
+ cwd,
2921
3341
  dryRun,
2922
3342
  allProjects: false,
2923
3343
  assumeYes: true
@@ -2939,7 +3359,7 @@ if (args.includes("--version") || args.includes("-v")) {
2939
3359
  }
2940
3360
  if (args.includes("--help") || args.includes("-h")) {
2941
3361
  console.log(
2942
- "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair]\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
3362
+ "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair]\n Cursor API environment (with --cursor or --all): --local (node + built cli.cjs + localhost) | --staging (npx + staging API)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
2943
3363
  );
2944
3364
  process.exit(0);
2945
3365
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoraone/mcp",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {