@memoraone/mcp 0.1.37 → 0.1.39

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 (4) hide show
  1. package/dist/cli.cjs +2311 -515
  2. package/dist/daemon.cjs +526 -89
  3. package/dist/index.cjs +525 -88
  4. package/package.json +11 -11
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.37",
33
+ version: "0.1.39",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -56,6 +56,7 @@ var require_package = __commonJS({
56
56
  "@modelcontextprotocol/sdk": "^1.25.1",
57
57
  "@napi-rs/keyring": "1.3.0",
58
58
  dotenv: "^16.4.5",
59
+ "smol-toml": "^1.7.1",
59
60
  zod: "^4.0.0"
60
61
  },
61
62
  devDependencies: {
@@ -366,7 +367,7 @@ async function acquireLocalLock(lockName, options = {}) {
366
367
  `[memoraone-mcp] Failed to acquire lock ${lockName} after ${maxRetries} retries`
367
368
  );
368
369
  }
369
- await new Promise((resolve17) => setTimeout(resolve17, retryDelayMs));
370
+ await new Promise((resolve21) => setTimeout(resolve21, retryDelayMs));
370
371
  continue;
371
372
  }
372
373
  throw err;
@@ -530,6 +531,24 @@ async function keyringGetPassword(repositoryBindingId, options = {}) {
530
531
  );
531
532
  }
532
533
  }
534
+ async function keyringDeletePassword(repositoryBindingId, options = {}) {
535
+ const mod = await loadKeyringModule(options.loader);
536
+ const account = keyringAccountForBinding(repositoryBindingId);
537
+ try {
538
+ const entry = new mod.Entry(KEYRING_SERVICE, account);
539
+ entry.deletePassword();
540
+ } catch (err) {
541
+ const message = err instanceof Error ? err.message : String(err);
542
+ if (/NoEntry|not found|no entry/i.test(message)) {
543
+ return;
544
+ }
545
+ if (err instanceof KeyringUnavailableError) throw err;
546
+ throw new KeyringOperationError(
547
+ `[memoraone-mcp] Failed to delete credentials from OS keyring for ${account}`,
548
+ err
549
+ );
550
+ }
551
+ }
533
552
 
534
553
  // src/localState/installationCredentials.ts
535
554
  function parsePayload(raw, repositoryBindingId) {
@@ -618,6 +637,9 @@ async function ensureClientRedeemKey(repositoryBindingId, options = {}) {
618
637
  );
619
638
  return { payload, clientRedeemKey, created: true };
620
639
  }
640
+ async function deleteInstallationCredentials(repositoryBindingId, options = {}) {
641
+ await keyringDeletePassword(repositoryBindingId, options);
642
+ }
621
643
  function hasUsableAccessToken(payload) {
622
644
  return Boolean(payload?.accessToken && payload.accessToken.startsWith("mia_"));
623
645
  }
@@ -855,9 +877,9 @@ var MemoraOneHttpError = class extends Error {
855
877
  };
856
878
 
857
879
  // src/localState/localConnectClient.ts
858
- async function requestJson(baseUrl, method, path21, options = {}) {
880
+ async function requestJson(baseUrl, method, path25, options = {}) {
859
881
  const fetchImpl = options.fetchImpl ?? fetch;
860
- const url = `${baseUrl.replace(/\/+$/, "")}${path21.startsWith("/") ? path21 : `/${path21}`}`;
882
+ const url = `${baseUrl.replace(/\/+$/, "")}${path25.startsWith("/") ? path25 : `/${path25}`}`;
861
883
  const res = await fetchImpl(url, {
862
884
  method,
863
885
  headers: {
@@ -1266,12 +1288,20 @@ var HASH_SOCKET_FILENAME_RE = new RegExp(
1266
1288
  `^mcp-[0-9a-f]{${BINDING_SOCKET_HASH_LENGTH}}\\.sock$`,
1267
1289
  "i"
1268
1290
  );
1269
- var LEGACY_SOCKET_FILENAME_RE = /^mcp-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:-([0-9a-f]{12}))?(?:-(cursor|jetbrains|copilot-vscode))?\.sock$/i;
1291
+ var LEGACY_SOCKET_FILENAME_RE = /^mcp-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:-([0-9a-f]{12}))?(?:-(cursor|jetbrains|copilot-vscode|claude-code|windsurf|opencode|codex))?\.sock$/i;
1270
1292
  var LEGACY_SOCKET_PROJECT_ID_RE = LEGACY_SOCKET_FILENAME_RE;
1271
1293
  function getMcpBaseDir() {
1272
1294
  return BASE_DIR;
1273
1295
  }
1274
- var IDE_TYPES = ["cursor", "copilot-vscode", "jetbrains"];
1296
+ var IDE_TYPES = [
1297
+ "cursor",
1298
+ "copilot-vscode",
1299
+ "jetbrains",
1300
+ "claude-code",
1301
+ "windsurf",
1302
+ "opencode",
1303
+ "codex"
1304
+ ];
1275
1305
  var IDE_TYPE_SET = new Set(IDE_TYPES);
1276
1306
  function parseIdeType(value) {
1277
1307
  if (value === void 0 || value.trim() === "" || !IDE_TYPE_SET.has(value)) {
@@ -1283,27 +1313,32 @@ function resolveIdeTypeFromEnv(env2 = process.env) {
1283
1313
  return parseIdeType(env2.MEMORAONE_IDE_TYPE);
1284
1314
  }
1285
1315
  function parseIdeTypeFromCommandLine(commandLine) {
1286
- const match = commandLine.match(/--ide\s+(cursor|copilot-vscode|jetbrains)(?:\s|$)/);
1316
+ const match = commandLine.match(
1317
+ /--ide\s+(cursor|copilot-vscode|jetbrains|claude-code|windsurf|opencode|codex)(?:\s|$)/
1318
+ );
1287
1319
  return match ? match[1] : void 0;
1288
1320
  }
1289
- function buildDaemonSpawnArgs(scriptPath, repositoryBindingId, env2 = process.env) {
1321
+ function buildDaemonSpawnArgs(scriptPath, repositoryBindingId, env2 = process.env, ideTypeOverride) {
1290
1322
  const args2 = [scriptPath, "--daemon", "--binding-id", repositoryBindingId];
1291
- const ideType = resolveIdeTypeFromEnv(env2);
1323
+ const ideType = ideTypeOverride ?? resolveIdeTypeFromEnv(env2);
1292
1324
  if (ideType) {
1293
1325
  args2.push("--ide", ideType);
1294
1326
  }
1295
1327
  return args2;
1296
1328
  }
1297
- function resolveBindingIdeType(env2 = process.env) {
1329
+ function resolveBindingIdeType(env2 = process.env, ideTypeOverride) {
1330
+ if (ideTypeOverride !== void 0) {
1331
+ return ideTypeOverride;
1332
+ }
1298
1333
  return resolveIdeTypeFromEnv(env2) ?? "";
1299
1334
  }
1300
- function getBindingSocketFilename(binding, env2 = process.env) {
1301
- const ideType = resolveBindingIdeType(env2);
1335
+ function getBindingSocketFilename(binding, env2 = process.env, ideTypeOverride) {
1336
+ const ideType = resolveBindingIdeType(env2, ideTypeOverride);
1302
1337
  const hash = hashBindingIdentity(binding.repositoryBindingId, binding.workspaceRoot, ideType);
1303
1338
  return `mcp-${hash}.sock`;
1304
1339
  }
1305
- function getBindingSocketPath(binding, env2 = process.env) {
1306
- return path9.join(BASE_DIR, getBindingSocketFilename(binding, env2));
1340
+ function getBindingSocketPath(binding, env2 = process.env, ideTypeOverride) {
1341
+ return path9.join(BASE_DIR, getBindingSocketFilename(binding, env2, ideTypeOverride));
1307
1342
  }
1308
1343
  function ensureBaseDir() {
1309
1344
  fs7.mkdirSync(BASE_DIR, { recursive: true });
@@ -1419,7 +1454,7 @@ function formatBindingMismatchError(socketPath, sidecar, expected, detail) {
1419
1454
  lines.push("The bridge will replace this stale daemon automatically from local binding state.");
1420
1455
  return lines.join("\n");
1421
1456
  }
1422
- function verifyDaemonSidecarBinding(socketPath, expected, env2 = process.env) {
1457
+ function verifyDaemonSidecarBinding(socketPath, expected, env2 = process.env, ideTypeOverride) {
1423
1458
  const record = readBindingSidecarRecord(socketPath);
1424
1459
  if (!record) {
1425
1460
  return null;
@@ -1431,7 +1466,7 @@ function verifyDaemonSidecarBinding(socketPath, expected, env2 = process.env) {
1431
1466
  if (!bindingsMatch(sidecar, expected)) {
1432
1467
  throw new Error(formatBindingMismatchError(socketPath, sidecar, expected));
1433
1468
  }
1434
- const expectedIdeType = resolveBindingIdeType(env2);
1469
+ const expectedIdeType = resolveBindingIdeType(env2, ideTypeOverride);
1435
1470
  if (record.ideType !== void 0 && record.ideType !== expectedIdeType) {
1436
1471
  throw new Error(
1437
1472
  formatBindingMismatchError(
@@ -1528,8 +1563,8 @@ var StdioLineReader = class {
1528
1563
  if (this.closed) {
1529
1564
  return null;
1530
1565
  }
1531
- return new Promise((resolve17) => {
1532
- this.waiters.push(resolve17);
1566
+ return new Promise((resolve21) => {
1567
+ this.waiters.push(resolve21);
1533
1568
  });
1534
1569
  }
1535
1570
  /** Re-queue lines read during an intermediate protocol step (e.g. roots/list) for the main bridge loop. */
@@ -1978,19 +2013,24 @@ function resolveIdeApiUrl(options) {
1978
2013
  return MEMORAONE_LOCAL_API_URL;
1979
2014
  }
1980
2015
  function buildMemoraoneCursorMcpServer(options) {
2016
+ const ideType = options.ideType ?? "cursor";
1981
2017
  const env2 = {
1982
2018
  MEMORAONE_API_URL: resolveIdeApiUrl({
1983
2019
  environment: options.environment,
1984
2020
  apiUrl: options.apiUrl
1985
- }),
1986
- MEMORAONE_IDE_TYPE: "cursor"
2021
+ })
1987
2022
  };
2023
+ if (ideType !== "claude-code") {
2024
+ env2.MEMORAONE_IDE_TYPE = ideType;
2025
+ }
1988
2026
  if (options.workspaceRoot !== void 0) {
1989
2027
  env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path14.resolve(options.workspaceRoot);
1990
2028
  }
1991
2029
  if (options.environment === "local") {
1992
2030
  if (!options.cliPath) {
1993
- throw new Error("[setup-ide-files] Local Cursor MCP config requires a built CLI path.");
2031
+ throw new Error(
2032
+ `[setup-ide-files] Local ${ideType} MCP config requires a built CLI path.`
2033
+ );
1994
2034
  }
1995
2035
  return {
1996
2036
  command: "node",
@@ -1999,7 +2039,7 @@ function buildMemoraoneCursorMcpServer(options) {
1999
2039
  };
2000
2040
  }
2001
2041
  if (!options.npxPath) {
2002
- throw new Error("[setup-ide-files] Cursor MCP config requires a resolved npx path.");
2042
+ throw new Error(`[setup-ide-files] ${ideType} MCP config requires a resolved npx path.`);
2003
2043
  }
2004
2044
  const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(options.environment);
2005
2045
  return {
@@ -2113,10 +2153,30 @@ function mergeCursorRepoMcpConfigObject(existing, writeOptions) {
2113
2153
  cliPath: writeOptions.cliPath,
2114
2154
  workspaceRoot: writeOptions.repoRoot,
2115
2155
  apiUrl: writeOptions.apiUrl,
2116
- npmPackageChannel: writeOptions.npmPackageChannel
2156
+ npmPackageChannel: writeOptions.npmPackageChannel,
2157
+ ideType: "cursor"
2158
+ });
2159
+ return { ...base, mcpServers };
2160
+ }
2161
+ function mergeClaudeCodeMcpConfigObject(existing, writeOptions) {
2162
+ const environment = writeOptions.environment ?? "production";
2163
+ const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
2164
+ const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
2165
+ delete mcpServers.memoraone;
2166
+ mcpServers.memoraone = buildMemoraoneCursorMcpServer({
2167
+ environment,
2168
+ npxPath: writeOptions.npxPath,
2169
+ cliPath: writeOptions.cliPath,
2170
+ workspaceRoot: writeOptions.repoRoot,
2171
+ apiUrl: writeOptions.apiUrl,
2172
+ npmPackageChannel: writeOptions.npmPackageChannel,
2173
+ ideType: "claude-code"
2117
2174
  });
2118
2175
  return { ...base, mcpServers };
2119
2176
  }
2177
+ function getClaudeCodeMcpConfigPath(repoRoot) {
2178
+ return path14.join(path14.resolve(repoRoot), ".mcp.json");
2179
+ }
2120
2180
  function isMemoraoneManagedApiUrl(url) {
2121
2181
  if (typeof url !== "string" || url.length === 0) return false;
2122
2182
  if (url === MEMORAONE_PROD_API_URL) return true;
@@ -2236,45 +2296,46 @@ function logCursorMcpConfigAudit(prefix, audit) {
2236
2296
  function logCursorMcpCliSummary(info, dryRun, opts) {
2237
2297
  const { repoConfigPath, repoOutcome, npxPath, cliPath, repoBackupPath, globalConfigPath, globalMemoraoneRemoved, globalBackupPath } = info;
2238
2298
  const interactive = opts?.forInteractivePostSetup === true;
2239
- console.log(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
2299
+ const println = opts?.println ?? console.log;
2300
+ println(`[setup-ide-files] Cursor repo MCP config: ${repoConfigPath}`);
2240
2301
  if (cliPath) {
2241
- console.log(`[setup-ide-files] Resolved local CLI: ${cliPath}`);
2302
+ println(`[setup-ide-files] Resolved local CLI: ${cliPath}`);
2242
2303
  } else if (npxPath) {
2243
- console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
2304
+ println(`[setup-ide-files] Resolved npx: ${npxPath}`);
2244
2305
  }
2245
2306
  if (repoBackupPath) {
2246
- console.log(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
2307
+ println(`[setup-ide-files] Cursor repo MCP config backup: ${repoBackupPath}`);
2247
2308
  }
2248
2309
  if (!interactive) {
2249
2310
  if (repoOutcome === "created") {
2250
- console.log(
2311
+ println(
2251
2312
  dryRun ? `[setup-ide-files] Cursor repo MCP config would be created: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config created: ${repoConfigPath}`
2252
2313
  );
2253
2314
  } else if (repoOutcome === "updated") {
2254
- console.log(
2315
+ println(
2255
2316
  dryRun ? `[setup-ide-files] Cursor repo MCP config would be updated: ${repoConfigPath}` : `[setup-ide-files] Cursor repo MCP config updated: ${repoConfigPath}`
2256
2317
  );
2257
2318
  } else if (repoOutcome === "skipped") {
2258
- console.log(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
2319
+ println(`[setup-ide-files] Cursor repo MCP config unchanged: ${repoConfigPath}`);
2259
2320
  }
2260
2321
  }
2261
2322
  if (globalMemoraoneRemoved && globalConfigPath) {
2262
- console.log(
2323
+ println(
2263
2324
  dryRun ? `[setup-ide-files] Would remove memoraone from Cursor global MCP config: ${globalConfigPath}` : `[setup-ide-files] Removed memoraone from Cursor global MCP config: ${globalConfigPath}`
2264
2325
  );
2265
2326
  if (globalBackupPath) {
2266
- console.log(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
2327
+ println(`[setup-ide-files] Cursor global MCP config backup: ${globalBackupPath}`);
2267
2328
  }
2268
2329
  } else if (globalConfigPath) {
2269
- console.log(
2330
+ println(
2270
2331
  `[setup-ide-files] Cursor global MCP config unchanged (no managed memoraone to remove): ${globalConfigPath}`
2271
2332
  );
2272
2333
  }
2273
- console.log(
2334
+ println(
2274
2335
  "[setup-ide-files] Each Cursor window uses this repo\u2019s .cursor/mcp.json (separate MCP process per repo)."
2275
2336
  );
2276
2337
  if (!interactive) {
2277
- console.log(
2338
+ println(
2278
2339
  "[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
2279
2340
  );
2280
2341
  }
@@ -2420,14 +2481,14 @@ async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide, wor
2420
2481
  const resolvedRoot = workspaceRoot ? path15.resolve(workspaceRoot) : null;
2421
2482
  const filtered = [];
2422
2483
  for (const socketPath of socketPaths) {
2423
- const basename11 = path15.basename(socketPath);
2424
- if (isLegacySocketFilename(basename11)) {
2425
- if (isSocketFilenameForProjectAndIde(basename11, normalizedProjectId, ide)) {
2484
+ const basename14 = path15.basename(socketPath);
2485
+ if (isLegacySocketFilename(basename14)) {
2486
+ if (isSocketFilenameForProjectAndIde(basename14, normalizedProjectId, ide)) {
2426
2487
  filtered.push(socketPath);
2427
2488
  }
2428
2489
  continue;
2429
2490
  }
2430
- if (isHashSocketFilename(basename11)) {
2491
+ if (isHashSocketFilename(basename14)) {
2431
2492
  const record = readBindingSidecarRecord(socketPath);
2432
2493
  if (!record || record.ideType !== ide) continue;
2433
2494
  const sameProject = record.projectId.trim().toLowerCase() === normalizedProjectId;
@@ -2807,6 +2868,179 @@ async function cliCleanup(argv) {
2807
2868
  return result.exitCode;
2808
2869
  }
2809
2870
 
2871
+ // src/config.ts
2872
+ var process2 = __toESM(require("process"), 1);
2873
+ var fs12 = __toESM(require("fs"), 1);
2874
+ var path16 = __toESM(require("path"), 1);
2875
+ var dotenv = __toESM(require("dotenv"), 1);
2876
+ var import_v4 = require("zod/v4");
2877
+
2878
+ // src/configUtils.ts
2879
+ var DEFAULT_API_URL = "http://localhost:3001";
2880
+ var DEV_API_URL = "http://localhost:3001";
2881
+ function resolveApiUrl(env2) {
2882
+ const explicitUrl = env2.MEMORAONE_API_URL?.trim();
2883
+ if (explicitUrl) {
2884
+ return explicitUrl;
2885
+ }
2886
+ const aliasUrl = env2.MEMORA_API_URL?.trim();
2887
+ if (aliasUrl) {
2888
+ return aliasUrl;
2889
+ }
2890
+ if (env2.MEMORAONE_DEV_MODE === "1") {
2891
+ return DEV_API_URL;
2892
+ }
2893
+ return DEFAULT_API_URL;
2894
+ }
2895
+
2896
+ // src/config.ts
2897
+ var dotenvPath = path16.resolve(process2.cwd(), ".env");
2898
+ if (fs12.existsSync(dotenvPath)) {
2899
+ try {
2900
+ dotenv.config({ path: dotenvPath });
2901
+ } catch (err) {
2902
+ process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
2903
+ }
2904
+ }
2905
+ var EnvSchema = import_v4.z.object({
2906
+ MEMORAONE_API_URL: import_v4.z.string().url().optional(),
2907
+ MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
2908
+ MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
2909
+ MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
2910
+ MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
2911
+ MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
2912
+ MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains", "claude-code", "windsurf", "opencode", "codex"]).optional(),
2913
+ MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
2914
+ MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
2915
+ MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
2916
+ });
2917
+ var requiredEnvVars = [];
2918
+ var missingEnvVars = requiredEnvVars.filter((key) => {
2919
+ const value = process2.env[key];
2920
+ return value === void 0 || value.trim() === "";
2921
+ });
2922
+ if (missingEnvVars.length > 0) {
2923
+ for (const key of missingEnvVars) {
2924
+ process2.stderr.write(`Missing ${key}
2925
+ `);
2926
+ }
2927
+ process2.exit(1);
2928
+ }
2929
+ var parsed = EnvSchema.safeParse(process2.env);
2930
+ var resolvedApiUrl = resolveApiUrl(process2.env);
2931
+ if (!parsed.success) {
2932
+ const formatted = parsed.error.format();
2933
+ process2.stderr.write(
2934
+ "[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
2935
+ );
2936
+ throw new Error("Config validation failed");
2937
+ }
2938
+ var parseBooleanFlag2 = (value, defaultValue) => {
2939
+ if (value === void 0) {
2940
+ return defaultValue;
2941
+ }
2942
+ const normalized = value.trim().toLowerCase();
2943
+ if (["1", "true", "yes", "on"].includes(normalized)) {
2944
+ return true;
2945
+ }
2946
+ if (["0", "false", "no", "off"].includes(normalized)) {
2947
+ return false;
2948
+ }
2949
+ return defaultValue;
2950
+ };
2951
+ var config2 = {
2952
+ apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
2953
+ apiKey: parsed.data.MEMORAONE_API_KEY,
2954
+ agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
2955
+ agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
2956
+ source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
2957
+ ideType: parsed.data.MEMORAONE_IDE_TYPE,
2958
+ devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
2959
+ worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
2960
+ heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
2961
+ // Cadence is owned by LOCAL_MCP_HEARTBEAT_INTERVAL_MS in heartbeat.ts (1_000).
2962
+ // Env override is accepted for forward-compat but the timer path ignores it.
2963
+ heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "1000", 10)
2964
+ };
2965
+
2966
+ // src/ideType.ts
2967
+ function mapReliableClientInfoName(name) {
2968
+ if (typeof name !== "string") {
2969
+ return void 0;
2970
+ }
2971
+ const normalized = name.trim().toLowerCase();
2972
+ if (normalized === "") {
2973
+ return void 0;
2974
+ }
2975
+ if (normalized === "claude-code") {
2976
+ return "claude-code";
2977
+ }
2978
+ if (normalized === "devin") {
2979
+ return "windsurf";
2980
+ }
2981
+ if (normalized === "windsurf" || /^windsurf[\s_-].+$/.test(normalized)) {
2982
+ return "windsurf";
2983
+ }
2984
+ return void 0;
2985
+ }
2986
+ function mapReliableHostIdentity(env2) {
2987
+ if (env2.WINDSURF_IDE_TYPE === "windsurf") {
2988
+ return "windsurf";
2989
+ }
2990
+ if (env2.ACP_BACKEND === "windsurf") {
2991
+ return "windsurf";
2992
+ }
2993
+ if (env2.__CFBundleIdentifier === "com.exafunction.windsurf") {
2994
+ return "windsurf";
2995
+ }
2996
+ return void 0;
2997
+ }
2998
+ function inferIdeType(params, options = {}) {
2999
+ const env2 = options.env ?? process.env;
3000
+ const argv = (options.argv ?? process.argv).join(" ").toLowerCase();
3001
+ const hasExplicitConfigOption = Object.prototype.hasOwnProperty.call(options, "configIdeType");
3002
+ const explicitHint = hasExplicitConfigOption ? options.configIdeType : resolveIdeTypeFromEnv(env2) ?? config2.ideType;
3003
+ const fromClientInfo = mapReliableClientInfoName(params?.clientInfo?.name);
3004
+ if (fromClientInfo) {
3005
+ return fromClientInfo;
3006
+ }
3007
+ const fromHost = mapReliableHostIdentity(env2);
3008
+ if (fromHost) {
3009
+ return fromHost;
3010
+ }
3011
+ if (explicitHint) {
3012
+ return explicitHint;
3013
+ }
3014
+ const clientInfoName = String(params?.clientInfo?.name ?? "").toLowerCase();
3015
+ const clientInfoVersion = String(params?.clientInfo?.version ?? "").toLowerCase();
3016
+ const termProgram = String(env2.TERM_PROGRAM ?? "").toLowerCase();
3017
+ const envKeys = Object.keys(env2);
3018
+ const hasCursorSignals = envKeys.some((key) => key.startsWith("CURSOR_")) || termProgram === "cursor" || clientInfoName.includes("cursor") || clientInfoVersion.includes("cursor") || argv.includes("cursor");
3019
+ if (hasCursorSignals) {
3020
+ return "cursor";
3021
+ }
3022
+ const hasJetBrainsSignals = envKeys.some(
3023
+ (key) => [
3024
+ "JETBRAINS_IDE",
3025
+ "IDEA_INITIAL_DIRECTORY",
3026
+ "JETBRAINS_REMOTE_RUN",
3027
+ "INTELLIJ_ENVIRONMENT_READER"
3028
+ ].includes(key)
3029
+ ) || String(env2.TERMINAL_EMULATOR ?? "").toLowerCase().includes("jetbrains") || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
3030
+ clientInfoName
3031
+ ) || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
3032
+ argv
3033
+ );
3034
+ if (hasJetBrainsSignals) {
3035
+ return "jetbrains";
3036
+ }
3037
+ const hasVsCodeSignals = envKeys.some((key) => key.startsWith("VSCODE_")) || /(visual studio code|vscode|vs code|github copilot)/.test(clientInfoName) || /(visual studio code|vscode|vs code)/.test(argv);
3038
+ if (hasVsCodeSignals) {
3039
+ return "copilot-vscode";
3040
+ }
3041
+ return void 0;
3042
+ }
3043
+
2810
3044
  // src/bridgeProxy.ts
2811
3045
  var defaultLog = (msg) => {
2812
3046
  process.stderr.write(`[memoraone-mcp][bridge] ${msg}
@@ -2827,9 +3061,9 @@ function summarizeJsonRpcMethod(line) {
2827
3061
  }
2828
3062
  }
2829
3063
  function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
2830
- return new Promise((resolve17, reject) => {
3064
+ return new Promise((resolve21, reject) => {
2831
3065
  const tryConnect = (attempt) => {
2832
- connect2(socketPath).then(resolve17).catch((err) => {
3066
+ connect2(socketPath).then(resolve21).catch((err) => {
2833
3067
  if (attempt >= maxRetries) {
2834
3068
  reject(err);
2835
3069
  return;
@@ -2851,8 +3085,8 @@ async function resolveBridgeSessionBinding(params, env2 = process.env, options =
2851
3085
  ...bridgeOptions
2852
3086
  });
2853
3087
  }
2854
- async function stopDaemonsForStaleBinding(stale, env2, log) {
2855
- const ideType = resolveBindingIdeType(env2);
3088
+ async function stopDaemonsForStaleBinding(stale, env2, log, ideTypeOverride) {
3089
+ const ideType = resolveBindingIdeType(env2, ideTypeOverride);
2856
3090
  let processes;
2857
3091
  try {
2858
3092
  processes = await defaultListDaemonProcesses();
@@ -2879,9 +3113,10 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2879
3113
  `refreshed stale binding ${sessionBinding.repositoryBindingId}: project=${sessionBinding.projectId}`
2880
3114
  );
2881
3115
  }
2882
- const socketPath = getBindingSocketPath(sessionBinding, opts.env);
3116
+ const ideType = opts.ideType;
3117
+ const socketPath = getBindingSocketPath(sessionBinding, opts.env, ideType);
2883
3118
  opts.log(
2884
- `target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}`
3119
+ `target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}` + (ideType ? ` ideType=${ideType}` : "")
2885
3120
  );
2886
3121
  let socket;
2887
3122
  try {
@@ -2892,7 +3127,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2892
3127
  opts.retryDelayMs,
2893
3128
  opts.connect
2894
3129
  );
2895
- verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
3130
+ verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
2896
3131
  opts.log("reusing running daemon for session binding");
2897
3132
  return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
2898
3133
  } catch (err) {
@@ -2907,11 +3142,11 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2907
3142
  opts.log("stale daemon binding detected; replacing daemon for current local binding");
2908
3143
  const staleSidecar = readBindingSidecar(socketPath);
2909
3144
  if (staleSidecar) {
2910
- await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log);
3145
+ await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log, ideType);
2911
3146
  }
2912
- await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log);
3147
+ await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log, ideType);
2913
3148
  if (!bindingsMatch(binding, sessionBinding)) {
2914
- await stopDaemonsForStaleBinding(binding, opts.env, opts.log);
3149
+ await stopDaemonsForStaleBinding(binding, opts.env, opts.log, ideType);
2915
3150
  }
2916
3151
  removeDaemonSocketArtifacts(socketPath);
2917
3152
  }
@@ -2926,13 +3161,14 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2926
3161
  opts.retryDelayMs,
2927
3162
  opts.connect
2928
3163
  );
2929
- verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
3164
+ verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
2930
3165
  return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
2931
3166
  }
2932
3167
  var BridgeDaemonRouter = class {
2933
3168
  constructor(options) {
2934
3169
  this.activeSocket = null;
2935
3170
  this.activeBinding = null;
3171
+ this.activeIdeType = void 0;
2936
3172
  this.socketLineReader = null;
2937
3173
  this.lastInitializeLine = null;
2938
3174
  this.pendingDeferredClientLines = [];
@@ -2945,19 +3181,23 @@ var BridgeDaemonRouter = class {
2945
3181
  this.maxRetries = options.maxRetries ?? 5;
2946
3182
  this.retryDelayMs = options.retryDelayMs ?? 200;
2947
3183
  this.lineReader = options.lineReader ?? null;
2948
- this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve17, reject) => {
2949
- const socket = net.connect(socketPath, () => resolve17(socket));
3184
+ this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve21, reject) => {
3185
+ const socket = net.connect(socketPath, () => resolve21(socket));
2950
3186
  socket.on("error", reject);
2951
3187
  }));
2952
3188
  this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
3189
+ const ideType = this.activeIdeType;
2953
3190
  const child = (0, import_node_child_process4.spawn)(
2954
3191
  process.execPath,
2955
- buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env),
3192
+ buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env, ideType),
2956
3193
  {
2957
3194
  detached: true,
2958
3195
  stdio: "ignore",
2959
3196
  env: {
2960
3197
  ...this.env,
3198
+ // Align child hint with the effective live identity for this session.
3199
+ // Does not mutate the bridge process environment.
3200
+ ...ideType ? { MEMORAONE_IDE_TYPE: ideType } : {},
2961
3201
  // Secret-free handoff only; tokens stay in the OS keyring.
2962
3202
  MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
2963
3203
  }
@@ -2976,6 +3216,9 @@ var BridgeDaemonRouter = class {
2976
3216
  getActiveBinding() {
2977
3217
  return this.activeBinding;
2978
3218
  }
3219
+ getActiveIdeType() {
3220
+ return this.activeIdeType;
3221
+ }
2979
3222
  hasClientInitialize() {
2980
3223
  return this.clientInitializeSeen;
2981
3224
  }
@@ -2985,6 +3228,14 @@ var BridgeDaemonRouter = class {
2985
3228
  this.env,
2986
3229
  `initialize payload: ${summarizeInitializeParamsForDebug(params)}`
2987
3230
  );
3231
+ const effectiveIdeType = inferIdeType(params, {
3232
+ env: this.env,
3233
+ configIdeType: resolveIdeTypeFromEnv(this.env)
3234
+ });
3235
+ this.activeIdeType = effectiveIdeType;
3236
+ if (effectiveIdeType) {
3237
+ this.log(`effective ideType=${effectiveIdeType} (initialize identity)`);
3238
+ }
2988
3239
  const bridgeOptions = getBridgeBindingResolveOptions(this.env);
2989
3240
  const initializeRoots = extractWorkspaceRootsFromInitialize(params);
2990
3241
  let rootsListUris;
@@ -3034,6 +3285,7 @@ var BridgeDaemonRouter = class {
3034
3285
  this.detachSocketReader();
3035
3286
  this.activeSocket?.destroy();
3036
3287
  this.activeSocket = null;
3288
+ this.activeIdeType = effectiveIdeType;
3037
3289
  }
3038
3290
  this.activeBinding = binding;
3039
3291
  await this.connectActiveDaemon();
@@ -3077,6 +3329,7 @@ var BridgeDaemonRouter = class {
3077
3329
  this.handshakeDeferredClientLines = [];
3078
3330
  this.clientInitializeSeen = false;
3079
3331
  this.activeBinding = null;
3332
+ this.activeIdeType = void 0;
3080
3333
  }
3081
3334
  async connectActiveDaemon() {
3082
3335
  if (!this.activeBinding) {
@@ -3089,7 +3342,8 @@ var BridgeDaemonRouter = class {
3089
3342
  maxRetries: this.maxRetries,
3090
3343
  retryDelayMs: this.retryDelayMs,
3091
3344
  connect: this.connectImpl,
3092
- spawnDaemon: this.spawnDaemonImpl
3345
+ spawnDaemon: this.spawnDaemonImpl,
3346
+ ideType: this.activeIdeType
3093
3347
  });
3094
3348
  this.activeBinding = connected.binding;
3095
3349
  this.activeSocket = connected.socket;
@@ -3208,37 +3462,17 @@ async function runBridgeProxy(options) {
3208
3462
  }
3209
3463
 
3210
3464
  // src/setupIdeFiles.ts
3211
- var fs13 = __toESM(require("fs/promises"), 1);
3212
- var os6 = __toESM(require("os"), 1);
3213
- var path17 = __toESM(require("path"), 1);
3214
- var import_node_crypto5 = require("crypto");
3465
+ var fs17 = __toESM(require("fs/promises"), 1);
3466
+ var os7 = __toESM(require("os"), 1);
3467
+ var path21 = __toESM(require("path"), 1);
3468
+ var import_node_crypto8 = require("crypto");
3215
3469
 
3216
3470
  // src/jetbrainsMcpConfig.ts
3217
- var fs12 = __toESM(require("fs/promises"), 1);
3471
+ var fs13 = __toESM(require("fs/promises"), 1);
3218
3472
  var os5 = __toESM(require("os"), 1);
3219
- var path16 = __toESM(require("path"), 1);
3473
+ var path17 = __toESM(require("path"), 1);
3220
3474
  var import_node_crypto4 = require("crypto");
3221
3475
  var import_node_child_process5 = require("child_process");
3222
-
3223
- // src/configUtils.ts
3224
- var DEFAULT_API_URL = "http://localhost:3001";
3225
- var DEV_API_URL = "http://localhost:3001";
3226
- function resolveApiUrl(env2) {
3227
- const explicitUrl = env2.MEMORAONE_API_URL?.trim();
3228
- if (explicitUrl) {
3229
- return explicitUrl;
3230
- }
3231
- const aliasUrl = env2.MEMORA_API_URL?.trim();
3232
- if (aliasUrl) {
3233
- return aliasUrl;
3234
- }
3235
- if (env2.MEMORAONE_DEV_MODE === "1") {
3236
- return DEV_API_URL;
3237
- }
3238
- return DEFAULT_API_URL;
3239
- }
3240
-
3241
- // src/jetbrainsMcpConfig.ts
3242
3476
  var JETBRAINS_DEBUG_ENV_VARS = [
3243
3477
  "MEMORAONE_DEBUG_INIT",
3244
3478
  "MEMORAONE_DEBUG_MINIMAL_TOOLS",
@@ -3250,7 +3484,7 @@ function stripLeadingLineComments2(text) {
3250
3484
  }
3251
3485
  async function pathExists3(filePath) {
3252
3486
  try {
3253
- await fs12.access(filePath);
3487
+ await fs13.access(filePath);
3254
3488
  return true;
3255
3489
  } catch {
3256
3490
  return false;
@@ -3261,12 +3495,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
3261
3495
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
3262
3496
  }
3263
3497
  function getJetBrainsGlobalMcpConfigPath(homeDir) {
3264
- return path16.join(homeDir, ".ai", "mcp", "mcp.json");
3498
+ return path17.join(homeDir, ".ai", "mcp", "mcp.json");
3265
3499
  }
3266
3500
  function getJetBrainsProjectMcpConfigPaths(repoRoot) {
3267
3501
  return [
3268
- { kind: "project-ai", path: path16.join(repoRoot, ".ai", "mcp", "mcp.json") },
3269
- { kind: "project-ij", path: path16.join(repoRoot, ".ij", "mcp", "mcp.json") }
3502
+ { kind: "project-ai", path: path17.join(repoRoot, ".ai", "mcp", "mcp.json") },
3503
+ { kind: "project-ij", path: path17.join(repoRoot, ".ij", "mcp", "mcp.json") }
3270
3504
  ];
3271
3505
  }
3272
3506
  function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
@@ -3277,7 +3511,7 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
3277
3511
  }
3278
3512
  async function isZeroByteConfigFile(filePath) {
3279
3513
  if (!await pathExists3(filePath)) return false;
3280
- const stat4 = await fs12.stat(filePath);
3514
+ const stat4 = await fs13.stat(filePath);
3281
3515
  return stat4.size === 0;
3282
3516
  }
3283
3517
  function buildMemoraoneJetBrainsMcpServer(options) {
@@ -3288,7 +3522,7 @@ function buildMemoraoneJetBrainsMcpServer(options) {
3288
3522
  apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
3289
3523
  }),
3290
3524
  MEMORAONE_IDE_TYPE: "jetbrains",
3291
- [MEMORAONE_WORKSPACE_ROOT_ENV]: path16.resolve(options.workspaceRoot)
3525
+ [MEMORAONE_WORKSPACE_ROOT_ENV]: path17.resolve(options.workspaceRoot)
3292
3526
  };
3293
3527
  if (environment === "local" || options.devMode) {
3294
3528
  env2.MEMORAONE_DEV_MODE = "1";
@@ -3310,18 +3544,18 @@ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
3310
3544
  return { ...base, mcpServers };
3311
3545
  }
3312
3546
  async function writeJetBrainsMcpJsonAtomic(filePath, content) {
3313
- const dir = path16.dirname(filePath);
3314
- await fs12.mkdir(dir, { recursive: true });
3315
- const tmpPath = path16.join(
3547
+ const dir = path17.dirname(filePath);
3548
+ await fs13.mkdir(dir, { recursive: true });
3549
+ const tmpPath = path17.join(
3316
3550
  dir,
3317
- `.${path16.basename(filePath)}.${process.pid}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`
3551
+ `.${path17.basename(filePath)}.${process.pid}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`
3318
3552
  );
3319
3553
  try {
3320
- await fs12.writeFile(tmpPath, content, "utf8");
3321
- await fs12.rename(tmpPath, filePath);
3554
+ await fs13.writeFile(tmpPath, content, "utf8");
3555
+ await fs13.rename(tmpPath, filePath);
3322
3556
  } catch (err) {
3323
3557
  try {
3324
- await fs12.unlink(tmpPath);
3558
+ await fs13.unlink(tmpPath);
3325
3559
  } catch {
3326
3560
  }
3327
3561
  throw err;
@@ -3362,13 +3596,13 @@ function validateJetBrainsMcpConfig(parsed2, expected) {
3362
3596
  }
3363
3597
  }
3364
3598
  async function readJsonConfig(filePath) {
3365
- const raw = await fs12.readFile(filePath, "utf8");
3599
+ const raw = await fs13.readFile(filePath, "utf8");
3366
3600
  if (raw.trim() === "") return null;
3367
3601
  return JSON.parse(stripLeadingLineComments2(raw));
3368
3602
  }
3369
3603
  async function backupConfigFile(filePath) {
3370
3604
  const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
3371
- await fs12.copyFile(filePath, backupPath);
3605
+ await fs13.copyFile(filePath, backupPath);
3372
3606
  return backupPath;
3373
3607
  }
3374
3608
  async function repairZeroByteConfigFile(filePath, dryRun) {
@@ -3379,7 +3613,7 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
3379
3613
  return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
3380
3614
  }
3381
3615
  const backupPath = await backupConfigFile(filePath);
3382
- await fs12.unlink(filePath);
3616
+ await fs13.unlink(filePath);
3383
3617
  return { repaired: true, backupPath };
3384
3618
  }
3385
3619
  function configHasMemoraone(parsed2) {
@@ -3410,24 +3644,24 @@ async function removeMemoraoneFromProjectConfig(options) {
3410
3644
  delete mcpServers.memoraone;
3411
3645
  const hasOtherServers = Object.keys(mcpServers).length > 0;
3412
3646
  if (!hasOtherServers) {
3413
- await fs12.unlink(configPath);
3647
+ await fs13.unlink(configPath);
3414
3648
  return { changed: true, backupPath };
3415
3649
  }
3416
3650
  const next = { ...parsed2, mcpServers };
3417
- await fs12.mkdir(path16.dirname(configPath), { recursive: true });
3418
- await fs12.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
3651
+ await fs13.mkdir(path17.dirname(configPath), { recursive: true });
3652
+ await fs13.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
3419
3653
  return { changed: true, backupPath };
3420
3654
  }
3421
3655
  async function resolveLocalCliPathAsync() {
3422
- const here = process.argv[1] ? path16.dirname(path16.resolve(process.argv[1])) : process.cwd();
3656
+ const here = process.argv[1] ? path17.dirname(path17.resolve(process.argv[1])) : process.cwd();
3423
3657
  const candidates = [
3424
- path16.join(here, "cli.cjs"),
3425
- path16.join(here, "..", "dist", "cli.cjs"),
3426
- path16.join(here, "..", "..", "dist", "cli.cjs")
3658
+ path17.join(here, "cli.cjs"),
3659
+ path17.join(here, "..", "dist", "cli.cjs"),
3660
+ path17.join(here, "..", "..", "dist", "cli.cjs")
3427
3661
  ];
3428
3662
  for (const candidate of candidates) {
3429
3663
  if (await pathExists3(candidate)) {
3430
- return path16.resolve(candidate);
3664
+ return path17.resolve(candidate);
3431
3665
  }
3432
3666
  }
3433
3667
  return null;
@@ -3472,12 +3706,21 @@ async function buildJetBrainsMemoraoneServer(options) {
3472
3706
  apiUrl: options.apiUrl
3473
3707
  });
3474
3708
  }
3709
+ function isOptionalJetBrainsHandshakeUnavailable(detail) {
3710
+ if (!detail) return false;
3711
+ if (detail.includes("-32601")) return true;
3712
+ if (/Method not found/i.test(detail)) return true;
3713
+ return false;
3714
+ }
3715
+ function formatOptionalJetBrainsHandshakeUnavailableDetail(detail) {
3716
+ return "MCP handshake optional verification unavailable while configuration succeeded" + (detail ? ` (${detail})` : "");
3717
+ }
3475
3718
  async function verifyJetBrainsMcpHandshake(options) {
3476
3719
  const timeoutMs = options.timeoutMs ?? 15e3;
3477
3720
  const { server } = options;
3478
- return new Promise((resolve17) => {
3721
+ return new Promise((resolve21) => {
3479
3722
  let settled = false;
3480
- const finish = (ok, detail) => {
3723
+ const finish = (ok, detail, optionalUnavailable) => {
3481
3724
  if (settled) return;
3482
3725
  settled = true;
3483
3726
  clearTimeout(timer);
@@ -3485,7 +3728,7 @@ async function verifyJetBrainsMcpHandshake(options) {
3485
3728
  child.kill();
3486
3729
  } catch {
3487
3730
  }
3488
- resolve17({ ok, detail });
3731
+ resolve21({ ok, detail, optionalUnavailable });
3489
3732
  };
3490
3733
  const child = (0, import_node_child_process5.spawn)(server.command, [...server.args], {
3491
3734
  env: { ...process.env, ...server.env },
@@ -3525,7 +3768,12 @@ async function verifyJetBrainsMcpHandshake(options) {
3525
3768
  finish(true, "initialize OK; tools/list OK");
3526
3769
  }
3527
3770
  if (msg.error) {
3528
- finish(false, `JSON-RPC error: ${JSON.stringify(msg.error)}`);
3771
+ const errDetail = `JSON-RPC error: ${JSON.stringify(msg.error)}`;
3772
+ if (isOptionalJetBrainsHandshakeUnavailable(errDetail)) {
3773
+ finish(true, errDetail, true);
3774
+ } else {
3775
+ finish(false, errDetail);
3776
+ }
3529
3777
  }
3530
3778
  }
3531
3779
  });
@@ -3554,7 +3802,7 @@ async function verifyJetBrainsMcpHandshake(options) {
3554
3802
  async function setupJetBrainsMcpConfig(options) {
3555
3803
  const homeDir = options.homeDir ?? os5.homedir();
3556
3804
  const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
3557
- const workspaceRoot = path16.resolve(options.repoRoot);
3805
+ const workspaceRoot = path17.resolve(options.repoRoot);
3558
3806
  const repairActions = [];
3559
3807
  const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
3560
3808
  for (const location of allLocations) {
@@ -3627,7 +3875,7 @@ async function setupJetBrainsMcpConfig(options) {
3627
3875
  backupPath = await backupConfigFile(globalPath);
3628
3876
  }
3629
3877
  await writeJetBrainsMcpJsonAtomic(globalPath, body);
3630
- const verifyRaw = await fs12.readFile(globalPath, "utf8");
3878
+ const verifyRaw = await fs13.readFile(globalPath, "utf8");
3631
3879
  const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
3632
3880
  validateJetBrainsMcpConfig(verifyParsed, memoraone);
3633
3881
  const outcome = existed ? "updated" : "created";
@@ -3637,98 +3885,1070 @@ async function setupJetBrainsMcpConfig(options) {
3637
3885
  if (options.verify !== false) {
3638
3886
  const verify = await verifyJetBrainsMcpHandshake({ server: memoraone });
3639
3887
  verifyOk = verify.ok;
3640
- verifyDetail = verify.detail;
3641
- repairActions.push({ type: "verify-handshake", ok: verify.ok, detail: verify.detail });
3888
+ verifyDetail = verify.optionalUnavailable ? formatOptionalJetBrainsHandshakeUnavailableDetail(verify.detail) : verify.detail;
3889
+ repairActions.push({
3890
+ type: "verify-handshake",
3891
+ ok: verify.ok,
3892
+ detail: verifyDetail ?? verify.detail
3893
+ });
3642
3894
  }
3643
3895
  return { outcome, backupPath, repairActions, verifyOk, verifyDetail, memoraone };
3644
3896
  }
3645
- function logJetBrainsMcpCliSummary(info, dryRun) {
3897
+ function formatJetBrainsHandshakeLogLine(action) {
3898
+ const optional = /optional verification unavailable/i.test(action.detail) || isOptionalJetBrainsHandshakeUnavailable(action.detail);
3899
+ if (optional) {
3900
+ if (/optional verification unavailable/i.test(action.detail)) {
3901
+ return `[setup-ide-files] ${action.detail}`;
3902
+ }
3903
+ return `[setup-ide-files] ${formatOptionalJetBrainsHandshakeUnavailableDetail(action.detail)}`;
3904
+ }
3905
+ if (action.ok) {
3906
+ return `[setup-ide-files] MCP handshake verification: ${action.detail}`;
3907
+ }
3908
+ return `[setup-ide-files] MCP handshake verification skipped/failed: ${action.detail}`;
3909
+ }
3910
+ function logJetBrainsMcpCliSummary(info, dryRun, println = console.log) {
3646
3911
  for (const action of info.repairActions) {
3647
3912
  if (action.type === "found-config") {
3648
- console.log(`[setup-ide-files] Found JetBrains MCP config (${action.location.kind}): ${action.location.path}`);
3913
+ println(`[setup-ide-files] Found JetBrains MCP config (${action.location.kind}): ${action.location.path}`);
3649
3914
  } else if (action.type === "repaired-zero-byte") {
3650
- console.log(`[setup-ide-files] Repaired zero-byte MCP config: ${action.path}`);
3651
- console.log(`[setup-ide-files] Backup: ${action.backupPath}`);
3915
+ println(`[setup-ide-files] Repaired zero-byte MCP config: ${action.path}`);
3916
+ println(`[setup-ide-files] Backup: ${action.backupPath}`);
3652
3917
  } else if (action.type === "backed-up-conflicting-project-config") {
3653
- console.log(`[setup-ide-files] Backed up conflicting project MCP config: ${action.path}`);
3654
- console.log(`[setup-ide-files] Backup: ${action.backupPath}`);
3918
+ println(`[setup-ide-files] Backed up conflicting project MCP config: ${action.path}`);
3919
+ println(`[setup-ide-files] Backup: ${action.backupPath}`);
3655
3920
  } else if (action.type === "removed-project-memoraone") {
3656
- console.log(`[setup-ide-files] Removed project-scoped memoraone definition: ${action.path}`);
3921
+ println(`[setup-ide-files] Removed project-scoped memoraone definition: ${action.path}`);
3657
3922
  } else if (action.type === "verify-handshake") {
3658
- if (action.ok) {
3659
- console.log(`[setup-ide-files] MCP handshake verification: ${action.detail}`);
3660
- } else {
3661
- console.log(`[setup-ide-files] MCP handshake verification skipped/failed: ${action.detail}`);
3662
- }
3923
+ println(formatJetBrainsHandshakeLogLine(action));
3663
3924
  }
3664
3925
  }
3665
3926
  const prefix = dryRun ? "would be " : "";
3666
3927
  if (info.outcome === "created") {
3667
- console.log(`[setup-ide-files] JetBrains global MCP config ${prefix}created: ${info.activeConfigPath}`);
3928
+ println(`[setup-ide-files] JetBrains global MCP config ${prefix}created: ${info.activeConfigPath}`);
3668
3929
  } else if (info.outcome === "updated") {
3669
- console.log(`[setup-ide-files] JetBrains global MCP config ${prefix}updated: ${info.activeConfigPath}`);
3930
+ println(`[setup-ide-files] JetBrains global MCP config ${prefix}updated: ${info.activeConfigPath}`);
3670
3931
  } else {
3671
- console.log(`[setup-ide-files] JetBrains global MCP config unchanged: ${info.activeConfigPath}`);
3932
+ println(`[setup-ide-files] JetBrains global MCP config unchanged: ${info.activeConfigPath}`);
3672
3933
  }
3673
3934
  if (info.backupPath) {
3674
- console.log(`[setup-ide-files] JetBrains global MCP config backup: ${info.backupPath}`);
3935
+ println(`[setup-ide-files] JetBrains global MCP config backup: ${info.backupPath}`);
3675
3936
  }
3676
3937
  if (info.npxPath) {
3677
- console.log(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
3938
+ println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
3678
3939
  }
3679
- console.log(`[setup-ide-files] Final active JetBrains MCP config: ${info.activeConfigPath}`);
3680
- console.log(
3940
+ println(`[setup-ide-files] Final active JetBrains MCP config: ${info.activeConfigPath}`);
3941
+ println(
3681
3942
  "[setup-ide-files] Fully quit JetBrains IDE and reopen this repo for MCP changes to take effect."
3682
3943
  );
3683
3944
  }
3684
3945
 
3685
- // src/openCursorMcpSettings.ts
3686
- var import_node_child_process6 = require("child_process");
3687
- var readline4 = __toESM(require("readline/promises"), 1);
3688
- var import_node_util4 = require("util");
3689
-
3690
- // src/terminalPresentation.ts
3691
- var ANSI = {
3692
- reset: "\x1B[0m",
3693
- bold: "\x1B[1m",
3694
- dim: "\x1B[2m",
3695
- green: "\x1B[32m",
3696
- yellow: "\x1B[33m",
3697
- cyan: "\x1B[36m"
3698
- };
3699
- function isCiLikeEnv(env2 = process.env) {
3700
- if (env2.CI === "true" || env2.CI === "1") return true;
3701
- if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
3702
- if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
3703
- if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
3704
- if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
3705
- if (typeof env2.CI === "string" && env2.CI.trim() !== "" && env2.CI !== "0" && env2.CI !== "false") {
3946
+ // src/windsurfMcpConfig.ts
3947
+ var fs14 = __toESM(require("fs/promises"), 1);
3948
+ var os6 = __toESM(require("os"), 1);
3949
+ var path18 = __toESM(require("path"), 1);
3950
+ var import_node_crypto5 = require("crypto");
3951
+ function stripLeadingLineComments3(text) {
3952
+ return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
3953
+ }
3954
+ async function pathExists4(filePath) {
3955
+ try {
3956
+ await fs14.access(filePath);
3706
3957
  return true;
3958
+ } catch {
3959
+ return false;
3707
3960
  }
3708
- return false;
3709
3961
  }
3710
- function shouldEnableAnsiColor(opts = {}) {
3711
- if (typeof opts.color === "boolean") return opts.color;
3712
- const env2 = opts.env ?? process.env;
3713
- if (env2.NO_COLOR !== void 0) return false;
3714
- if (isCiLikeEnv(env2)) return false;
3715
- const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
3716
- return tty;
3962
+ function getWindsurfGlobalMcpConfigPath(homeDir) {
3963
+ return path18.join(homeDir, ".config", "devin", "mcp_config.json");
3717
3964
  }
3718
- function shouldUseUnicodeSymbols(opts = {}) {
3719
- if (typeof opts.unicode === "boolean") return opts.unicode;
3720
- const env2 = opts.env ?? process.env;
3721
- const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
3722
- if (!tty) return false;
3723
- if (env2.TERM === "dumb") return false;
3724
- if (process.platform === "win32") {
3725
- return Boolean(
3726
- env2.WT_SESSION || env2.WT_PROFILE_ID || env2.ConEmuANSI === "ON" || env2.TERM_PROGRAM === "vscode" || env2.TERM_PROGRAM === "cursor" || typeof env2.TERM === "string" && env2.TERM !== "" && env2.TERM !== "dumb"
3727
- );
3728
- }
3729
- return true;
3965
+ function getWindsurfLegacyMcpConfigPath(homeDir) {
3966
+ return path18.join(homeDir, ".codeium", "windsurf", "mcp_config.json");
3730
3967
  }
3731
- function paint(enabled, code, text) {
3968
+ function formatWindsurfBackupTimestamp(d = /* @__PURE__ */ new Date()) {
3969
+ const pad = (n) => String(n).padStart(2, "0");
3970
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
3971
+ }
3972
+ function buildMemoraoneWindsurfMcpServer(options) {
3973
+ const environment = options.environment ?? (options.devMode ? "local" : "production");
3974
+ const env2 = {
3975
+ MEMORAONE_API_URL: resolveIdeApiUrl({
3976
+ environment,
3977
+ apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
3978
+ }),
3979
+ MEMORAONE_IDE_TYPE: "windsurf",
3980
+ [MEMORAONE_WORKSPACE_ROOT_ENV]: path18.resolve(options.workspaceRoot)
3981
+ };
3982
+ if (environment === "local" || options.devMode) {
3983
+ env2.MEMORAONE_DEV_MODE = "1";
3984
+ }
3985
+ if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
3986
+ throw new Error(
3987
+ "[setup-ide-files] Devin Desktop MCP config must not include credentials or .m1 paths"
3988
+ );
3989
+ }
3990
+ return {
3991
+ command: options.command,
3992
+ args: options.args,
3993
+ env: env2
3994
+ };
3995
+ }
3996
+ function mergeWindsurfMcpConfigObject(existing, memoraone) {
3997
+ const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
3998
+ const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
3999
+ delete mcpServers.memoraone;
4000
+ mcpServers.memoraone = memoraone;
4001
+ return { ...base, mcpServers };
4002
+ }
4003
+ function stripMemoraoneFromWindsurfConfigObject(existing) {
4004
+ const base = { ...existing };
4005
+ if (typeof base.mcpServers !== "object" || base.mcpServers === null || Array.isArray(base.mcpServers)) {
4006
+ return { next: base, removed: false };
4007
+ }
4008
+ const mcpServers = { ...base.mcpServers };
4009
+ if (!("memoraone" in mcpServers)) {
4010
+ return { next: base, removed: false };
4011
+ }
4012
+ delete mcpServers.memoraone;
4013
+ return { next: { ...base, mcpServers }, removed: true };
4014
+ }
4015
+ async function writeWindsurfMcpJsonAtomic(filePath, content) {
4016
+ const dir = path18.dirname(filePath);
4017
+ await fs14.mkdir(dir, { recursive: true });
4018
+ const tmpPath = path18.join(
4019
+ dir,
4020
+ `.${path18.basename(filePath)}.${process.pid}.${(0, import_node_crypto5.randomBytes)(8).toString("hex")}.tmp`
4021
+ );
4022
+ try {
4023
+ await fs14.writeFile(tmpPath, content, "utf8");
4024
+ await fs14.rename(tmpPath, filePath);
4025
+ } catch (err) {
4026
+ try {
4027
+ await fs14.unlink(tmpPath);
4028
+ } catch {
4029
+ }
4030
+ throw err;
4031
+ }
4032
+ }
4033
+ function memoraoneServerMatches2(server, expected) {
4034
+ if (!server || typeof server !== "object") return false;
4035
+ const s = server;
4036
+ if (s.command !== expected.command) return false;
4037
+ if (!Array.isArray(s.args) || s.args.length !== expected.args.length) return false;
4038
+ for (let i = 0; i < expected.args.length; i += 1) {
4039
+ if (s.args[i] !== expected.args[i]) return false;
4040
+ }
4041
+ const env2 = s.env;
4042
+ if (!env2 || typeof env2 !== "object") return false;
4043
+ const e = env2;
4044
+ for (const [key, value] of Object.entries(expected.env)) {
4045
+ if (e[key] !== value) return false;
4046
+ }
4047
+ return true;
4048
+ }
4049
+ function validateWindsurfMcpConfig(parsed2, expected) {
4050
+ if (!parsed2 || typeof parsed2 !== "object") {
4051
+ throw new Error("[setup-ide-files] Devin Desktop MCP config must be a JSON object.");
4052
+ }
4053
+ const mcpServers = parsed2.mcpServers;
4054
+ if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
4055
+ throw new Error("[setup-ide-files] Devin Desktop MCP config missing mcpServers object.");
4056
+ }
4057
+ const memoraone = mcpServers.memoraone;
4058
+ if (!memoraoneServerMatches2(memoraone, expected)) {
4059
+ throw new Error(
4060
+ "[setup-ide-files] Devin Desktop MCP config mcpServers.memoraone is missing or invalid."
4061
+ );
4062
+ }
4063
+ }
4064
+ async function readJsonConfig2(filePath) {
4065
+ const raw = await fs14.readFile(filePath, "utf8");
4066
+ if (raw.trim() === "") return null;
4067
+ return JSON.parse(stripLeadingLineComments3(raw));
4068
+ }
4069
+ async function backupConfigFile2(filePath) {
4070
+ const backupPath = `${filePath}.bak-${formatWindsurfBackupTimestamp()}`;
4071
+ await fs14.copyFile(filePath, backupPath);
4072
+ return backupPath;
4073
+ }
4074
+ function extractMemoraone(existing) {
4075
+ if (!existing || typeof existing.mcpServers !== "object" || existing.mcpServers === null || Array.isArray(existing.mcpServers)) {
4076
+ return void 0;
4077
+ }
4078
+ return existing.mcpServers.memoraone;
4079
+ }
4080
+ async function buildWindsurfMemoraoneServer(options) {
4081
+ const environment = options.environment ?? (options.devMode ? "local" : "production");
4082
+ if (environment === "local" || options.devMode) {
4083
+ let cliPath = options.cliPathOverride;
4084
+ if (cliPath === void 0) {
4085
+ cliPath = await resolveLocalCliPathAsync();
4086
+ }
4087
+ if (!cliPath) {
4088
+ throw new Error(
4089
+ "[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
4090
+ );
4091
+ }
4092
+ return buildMemoraoneWindsurfMcpServer({
4093
+ command: process.execPath,
4094
+ args: [cliPath],
4095
+ workspaceRoot: options.workspaceRoot,
4096
+ environment: "local",
4097
+ devMode: true,
4098
+ apiUrl: options.apiUrl
4099
+ });
4100
+ }
4101
+ let npxPath = options.npxPathOverride;
4102
+ if (npxPath === void 0) {
4103
+ npxPath = await resolveNpxPath();
4104
+ }
4105
+ if (!npxPath) {
4106
+ throw new Error(
4107
+ "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Devin Desktop MCP."
4108
+ );
4109
+ }
4110
+ const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
4111
+ return buildMemoraoneWindsurfMcpServer({
4112
+ command: npxPath,
4113
+ args: ["-y", memoraoneNpmPackageSpec(channel)],
4114
+ workspaceRoot: options.workspaceRoot,
4115
+ environment,
4116
+ devMode: false,
4117
+ apiUrl: options.apiUrl
4118
+ });
4119
+ }
4120
+ async function setupWindsurfMcpConfig(options) {
4121
+ const homeDir = options.homeDir ?? os6.homedir();
4122
+ const activePath = options.globalConfigPath ?? getWindsurfGlobalMcpConfigPath(homeDir);
4123
+ const legacyPath = options.legacyConfigPath ?? getWindsurfLegacyMcpConfigPath(homeDir);
4124
+ const samePath = path18.resolve(activePath) === path18.resolve(legacyPath);
4125
+ const workspaceRoot = path18.resolve(options.repoRoot);
4126
+ const memoraone = await buildWindsurfMemoraoneServer({
4127
+ workspaceRoot,
4128
+ devMode: options.devMode,
4129
+ environment: options.environment,
4130
+ npmPackageChannel: options.npmPackageChannel,
4131
+ apiUrl: options.apiUrl,
4132
+ npxPathOverride: options.npxPathOverride,
4133
+ cliPathOverride: options.cliPathOverride
4134
+ });
4135
+ const activeExisted = await pathExists4(activePath);
4136
+ let activeExisting = null;
4137
+ if (activeExisted) {
4138
+ try {
4139
+ activeExisting = await readJsonConfig2(activePath);
4140
+ } catch {
4141
+ throw new Error(
4142
+ `[setup-ide-files] Invalid JSON in shared Devin Desktop MCP config (file preserved, not modified): ${activePath}. Fix or remove the file, then re-run setup-ide-files.`
4143
+ );
4144
+ }
4145
+ }
4146
+ let legacyExisting = null;
4147
+ let legacyMalformed = false;
4148
+ const legacyExisted = !samePath && await pathExists4(legacyPath);
4149
+ if (legacyExisted) {
4150
+ try {
4151
+ legacyExisting = await readJsonConfig2(legacyPath);
4152
+ } catch {
4153
+ legacyMalformed = true;
4154
+ }
4155
+ }
4156
+ if (!activeExisted && legacyMalformed) {
4157
+ throw new Error(
4158
+ `[setup-ide-files] Invalid JSON in legacy Windsurf MCP config (file preserved, not modified): ${legacyPath}. Fix or remove the file, then re-run setup-ide-files. Active Devin Desktop path was not written: ${activePath}.`
4159
+ );
4160
+ }
4161
+ const mergeBase = activeExisted ? activeExisting : legacyExisting;
4162
+ const merged = mergeWindsurfMcpConfigObject(mergeBase, memoraone);
4163
+ const body = JSON.stringify(merged, null, 2) + "\n";
4164
+ const activeMemoraone = extractMemoraone(activeExisting);
4165
+ const activeAlreadyCorrect = activeExisted && memoraoneServerMatches2(activeMemoraone, memoraone);
4166
+ let legacyNeedsCleanup = false;
4167
+ if (legacyExisted && !legacyMalformed && legacyExisting) {
4168
+ const legacyMemoraone = extractMemoraone(legacyExisting);
4169
+ legacyNeedsCleanup = legacyMemoraone !== void 0;
4170
+ }
4171
+ if (activeAlreadyCorrect && !legacyNeedsCleanup) {
4172
+ return {
4173
+ outcome: "skipped",
4174
+ memoraone,
4175
+ activeConfigPath: activePath,
4176
+ legacyConfigPath: legacyPath,
4177
+ legacyCleanup: legacyExisted ? legacyMalformed ? "malformed" : "unchanged" : "absent",
4178
+ warning: legacyMalformed ? `[setup-ide-files] Legacy Windsurf MCP config has invalid JSON and was left unchanged: ${legacyPath}` : void 0
4179
+ };
4180
+ }
4181
+ if (options.dryRun) {
4182
+ let legacyCleanup2 = "absent";
4183
+ if (legacyExisted) {
4184
+ if (legacyMalformed) legacyCleanup2 = "malformed";
4185
+ else if (legacyNeedsCleanup) legacyCleanup2 = "skipped-dry-run";
4186
+ else legacyCleanup2 = "unchanged";
4187
+ }
4188
+ return {
4189
+ outcome: activeExisted ? "updated" : "created",
4190
+ memoraone,
4191
+ activeConfigPath: activePath,
4192
+ legacyConfigPath: legacyPath,
4193
+ legacyCleanup: legacyCleanup2,
4194
+ warning: legacyMalformed ? `[setup-ide-files] Legacy Windsurf MCP config has invalid JSON and was left unchanged: ${legacyPath}` : void 0
4195
+ };
4196
+ }
4197
+ let backupPath;
4198
+ if (activeExisted && !activeAlreadyCorrect) {
4199
+ backupPath = await backupConfigFile2(activePath);
4200
+ }
4201
+ if (!activeAlreadyCorrect) {
4202
+ await writeWindsurfMcpJsonAtomic(activePath, body);
4203
+ const verifyRaw = await fs14.readFile(activePath, "utf8");
4204
+ const verifyParsed = JSON.parse(stripLeadingLineComments3(verifyRaw));
4205
+ validateWindsurfMcpConfig(verifyParsed, memoraone);
4206
+ }
4207
+ let legacyCleanup = "absent";
4208
+ let warning;
4209
+ if (legacyExisted) {
4210
+ if (legacyMalformed) {
4211
+ legacyCleanup = "malformed";
4212
+ warning = `[setup-ide-files] Legacy Windsurf MCP config has invalid JSON and was left unchanged: ${legacyPath}. Devin Desktop active config was written; fix or remove the legacy file to avoid ambiguity.`;
4213
+ } else if (legacyNeedsCleanup && legacyExisting) {
4214
+ await backupConfigFile2(legacyPath);
4215
+ const { next, removed } = stripMemoraoneFromWindsurfConfigObject(legacyExisting);
4216
+ if (removed) {
4217
+ const legacyBody = JSON.stringify(next, null, 2) + "\n";
4218
+ await writeWindsurfMcpJsonAtomic(legacyPath, legacyBody);
4219
+ legacyCleanup = "removed-memoraone";
4220
+ } else {
4221
+ legacyCleanup = "unchanged";
4222
+ }
4223
+ } else {
4224
+ legacyCleanup = "unchanged";
4225
+ }
4226
+ }
4227
+ return {
4228
+ outcome: activeExisted ? "updated" : "created",
4229
+ backupPath,
4230
+ memoraone,
4231
+ activeConfigPath: activePath,
4232
+ legacyConfigPath: legacyPath,
4233
+ legacyCleanup,
4234
+ warning
4235
+ };
4236
+ }
4237
+ function logWindsurfMcpCliSummary(info, dryRun, println = console.log) {
4238
+ const prefix = dryRun ? "would be " : "";
4239
+ if (info.outcome === "created") {
4240
+ println(
4241
+ `[setup-ide-files] Devin Desktop global MCP config ${prefix}created: ${info.activeConfigPath}`
4242
+ );
4243
+ } else if (info.outcome === "updated") {
4244
+ println(
4245
+ `[setup-ide-files] Devin Desktop global MCP config ${prefix}updated: ${info.activeConfigPath}`
4246
+ );
4247
+ } else {
4248
+ println(
4249
+ `[setup-ide-files] Devin Desktop global MCP config unchanged: ${info.activeConfigPath}`
4250
+ );
4251
+ }
4252
+ if (info.backupPath) {
4253
+ println(`[setup-ide-files] Devin Desktop global MCP config backup: ${info.backupPath}`);
4254
+ }
4255
+ if (info.legacyConfigPath) {
4256
+ println(`[setup-ide-files] Legacy Windsurf MCP config path: ${info.legacyConfigPath}`);
4257
+ }
4258
+ if (info.legacyCleanup === "removed-memoraone") {
4259
+ println(
4260
+ `[setup-ide-files] Removed mcpServers.memoraone from legacy Windsurf MCP config: ${info.legacyConfigPath}`
4261
+ );
4262
+ } else if (info.legacyCleanup === "skipped-dry-run") {
4263
+ println(
4264
+ `[setup-ide-files] Legacy Windsurf mcpServers.memoraone would be removed: ${info.legacyConfigPath}`
4265
+ );
4266
+ }
4267
+ if (info.warning) {
4268
+ println(info.warning);
4269
+ }
4270
+ if (info.npxPath) {
4271
+ println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
4272
+ }
4273
+ println(`[setup-ide-files] Final active Devin Desktop MCP config: ${info.activeConfigPath}`);
4274
+ println(
4275
+ "[setup-ide-files] Refresh MCP servers in Devin Desktop settings, or restart Devin Desktop, for MCP changes to take effect."
4276
+ );
4277
+ }
4278
+
4279
+ // src/opencodeMcpConfig.ts
4280
+ var fs15 = __toESM(require("fs/promises"), 1);
4281
+ var path19 = __toESM(require("path"), 1);
4282
+ var import_node_crypto6 = require("crypto");
4283
+ var OPENCODE_CONFIG_SCHEMA_URL = "https://opencode.ai/config.json";
4284
+ var OpenCodeMcpJsonParseError = class extends Error {
4285
+ constructor(configPath) {
4286
+ super(
4287
+ `[setup-ide-files] Invalid JSON in shared OpenCode MCP config (file preserved, not modified): ${configPath}. Fix or remove the file, then re-run setup-ide-files.`
4288
+ );
4289
+ this.name = "OpenCodeMcpJsonParseError";
4290
+ this.configPath = configPath;
4291
+ }
4292
+ };
4293
+ var OpenCodeJsoncOnlyError = class extends Error {
4294
+ constructor(configPath) {
4295
+ super(
4296
+ `[setup-ide-files] Project OpenCode config is JSONC-only (file preserved, not modified): ${configPath}. Automatic setup manages opencode.json only and cannot safely preserve JSONC comments. Add or convert to opencode.json, then re-run setup-ide-files.`
4297
+ );
4298
+ this.name = "OpenCodeJsoncOnlyError";
4299
+ this.configPath = configPath;
4300
+ }
4301
+ };
4302
+ async function pathExists5(filePath) {
4303
+ try {
4304
+ await fs15.access(filePath);
4305
+ return true;
4306
+ } catch {
4307
+ return false;
4308
+ }
4309
+ }
4310
+ function getOpenCodeProjectMcpConfigPath(repoRoot) {
4311
+ return path19.join(path19.resolve(repoRoot), "opencode.json");
4312
+ }
4313
+ function getOpenCodeProjectJsoncConfigPath(repoRoot) {
4314
+ return path19.join(path19.resolve(repoRoot), "opencode.jsonc");
4315
+ }
4316
+ function buildMemoraoneOpenCodeMcpServer(options) {
4317
+ const environment = options.environment ?? (options.devMode ? "local" : "production");
4318
+ const env2 = {
4319
+ MEMORAONE_API_URL: resolveIdeApiUrl({
4320
+ environment,
4321
+ apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
4322
+ }),
4323
+ MEMORAONE_IDE_TYPE: "opencode",
4324
+ [MEMORAONE_WORKSPACE_ROOT_ENV]: path19.resolve(options.workspaceRoot)
4325
+ };
4326
+ if (environment === "local" || options.devMode) {
4327
+ env2.MEMORAONE_DEV_MODE = "1";
4328
+ }
4329
+ if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
4330
+ throw new Error("[setup-ide-files] OpenCode MCP config must not include credentials or .m1 paths");
4331
+ }
4332
+ return {
4333
+ type: "local",
4334
+ command: [options.command, ...options.args],
4335
+ environment: env2,
4336
+ enabled: true
4337
+ };
4338
+ }
4339
+ function mergeOpenCodeMcpConfigObject(existing, memoraone) {
4340
+ const base = existing && typeof existing === "object" ? { ...existing } : { $schema: OPENCODE_CONFIG_SCHEMA_URL };
4341
+ const mcp = typeof base.mcp === "object" && base.mcp !== null && !Array.isArray(base.mcp) ? { ...base.mcp } : {};
4342
+ delete mcp.memoraone;
4343
+ if (typeof mcp.servers === "object" && mcp.servers !== null && !Array.isArray(mcp.servers)) {
4344
+ const servers = { ...mcp.servers };
4345
+ delete servers.memoraone;
4346
+ if (Object.keys(servers).length === 0) {
4347
+ delete mcp.servers;
4348
+ } else {
4349
+ mcp.servers = servers;
4350
+ }
4351
+ }
4352
+ mcp.memoraone = memoraone;
4353
+ return { ...base, mcp };
4354
+ }
4355
+ async function writeOpenCodeMcpJsonAtomic(filePath, content) {
4356
+ const dir = path19.dirname(filePath);
4357
+ await fs15.mkdir(dir, { recursive: true });
4358
+ const tmpPath = path19.join(
4359
+ dir,
4360
+ `.${path19.basename(filePath)}.${process.pid}.${(0, import_node_crypto6.randomBytes)(8).toString("hex")}.tmp`
4361
+ );
4362
+ try {
4363
+ await fs15.writeFile(tmpPath, content, "utf8");
4364
+ await fs15.rename(tmpPath, filePath);
4365
+ } catch (err) {
4366
+ try {
4367
+ await fs15.unlink(tmpPath);
4368
+ } catch {
4369
+ }
4370
+ throw err;
4371
+ }
4372
+ }
4373
+ function memoraoneServerMatches3(server, expected) {
4374
+ if (!server || typeof server !== "object") return false;
4375
+ const s = server;
4376
+ if (s.type !== "local") return false;
4377
+ if (s.enabled !== true) return false;
4378
+ if (!Array.isArray(s.command) || s.command.length !== expected.command.length) return false;
4379
+ for (let i = 0; i < expected.command.length; i += 1) {
4380
+ if (s.command[i] !== expected.command[i]) return false;
4381
+ }
4382
+ const environment = s.environment;
4383
+ if (!environment || typeof environment !== "object") return false;
4384
+ const e = environment;
4385
+ for (const [key, value] of Object.entries(expected.environment)) {
4386
+ if (e[key] !== value) return false;
4387
+ }
4388
+ return true;
4389
+ }
4390
+ function validateOpenCodeMcpConfig(parsed2, expected) {
4391
+ if (!parsed2 || typeof parsed2 !== "object") {
4392
+ throw new Error("[setup-ide-files] OpenCode MCP config must be a JSON object.");
4393
+ }
4394
+ const mcp = parsed2.mcp;
4395
+ if (!mcp || typeof mcp !== "object" || Array.isArray(mcp)) {
4396
+ throw new Error("[setup-ide-files] OpenCode MCP config missing mcp object.");
4397
+ }
4398
+ const memoraone = mcp.memoraone;
4399
+ if (!memoraoneServerMatches3(memoraone, expected)) {
4400
+ throw new Error(
4401
+ "[setup-ide-files] OpenCode MCP config mcp.memoraone is missing or invalid."
4402
+ );
4403
+ }
4404
+ }
4405
+ async function readJsonConfig3(filePath) {
4406
+ const raw = await fs15.readFile(filePath, "utf8");
4407
+ if (raw.trim() === "") return null;
4408
+ return JSON.parse(raw);
4409
+ }
4410
+ async function buildOpenCodeMemoraoneServer(options) {
4411
+ const environment = options.environment ?? (options.devMode ? "local" : "production");
4412
+ if (environment === "local" || options.devMode) {
4413
+ let cliPath = options.cliPathOverride;
4414
+ if (cliPath === void 0) {
4415
+ cliPath = await resolveLocalCliPathAsync();
4416
+ }
4417
+ if (!cliPath) {
4418
+ throw new Error(
4419
+ "[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
4420
+ );
4421
+ }
4422
+ return buildMemoraoneOpenCodeMcpServer({
4423
+ command: process.execPath,
4424
+ args: [cliPath],
4425
+ workspaceRoot: options.workspaceRoot,
4426
+ environment: "local",
4427
+ devMode: true,
4428
+ apiUrl: options.apiUrl
4429
+ });
4430
+ }
4431
+ let npxPath = options.npxPathOverride;
4432
+ if (npxPath === void 0) {
4433
+ npxPath = await resolveNpxPath();
4434
+ }
4435
+ if (!npxPath) {
4436
+ throw new Error(
4437
+ "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring OpenCode MCP."
4438
+ );
4439
+ }
4440
+ const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
4441
+ return buildMemoraoneOpenCodeMcpServer({
4442
+ command: npxPath,
4443
+ args: ["-y", memoraoneNpmPackageSpec(channel)],
4444
+ workspaceRoot: options.workspaceRoot,
4445
+ environment,
4446
+ devMode: false,
4447
+ apiUrl: options.apiUrl
4448
+ });
4449
+ }
4450
+ async function setupOpenCodeMcpConfig(options) {
4451
+ const projectPath = options.projectConfigPath ?? getOpenCodeProjectMcpConfigPath(options.repoRoot);
4452
+ const jsoncPath = getOpenCodeProjectJsoncConfigPath(options.repoRoot);
4453
+ const workspaceRoot = path19.resolve(options.repoRoot);
4454
+ const memoraone = await buildOpenCodeMemoraoneServer({
4455
+ workspaceRoot,
4456
+ devMode: options.devMode,
4457
+ environment: options.environment,
4458
+ npmPackageChannel: options.npmPackageChannel,
4459
+ apiUrl: options.apiUrl,
4460
+ npxPathOverride: options.npxPathOverride,
4461
+ cliPathOverride: options.cliPathOverride
4462
+ });
4463
+ const jsonExists = await pathExists5(projectPath);
4464
+ const jsoncExists = await pathExists5(jsoncPath);
4465
+ if (!jsonExists && jsoncExists) {
4466
+ throw new OpenCodeJsoncOnlyError(jsoncPath);
4467
+ }
4468
+ let existing = null;
4469
+ if (jsonExists) {
4470
+ try {
4471
+ existing = await readJsonConfig3(projectPath);
4472
+ } catch {
4473
+ throw new OpenCodeMcpJsonParseError(projectPath);
4474
+ }
4475
+ if (existing !== null && (typeof existing !== "object" || Array.isArray(existing))) {
4476
+ throw new OpenCodeMcpJsonParseError(projectPath);
4477
+ }
4478
+ }
4479
+ const merged = mergeOpenCodeMcpConfigObject(existing, memoraone);
4480
+ const body = JSON.stringify(merged, null, 2) + "\n";
4481
+ if (jsonExists && existing) {
4482
+ const currentMemoraone = existing.mcp && typeof existing.mcp === "object" && !Array.isArray(existing.mcp) ? existing.mcp.memoraone : void 0;
4483
+ if (memoraoneServerMatches3(currentMemoraone, memoraone)) {
4484
+ return { outcome: "skipped", memoraone };
4485
+ }
4486
+ }
4487
+ if (options.dryRun) {
4488
+ return { outcome: jsonExists ? "updated" : "created", memoraone };
4489
+ }
4490
+ await writeOpenCodeMcpJsonAtomic(projectPath, body);
4491
+ const verifyRaw = await fs15.readFile(projectPath, "utf8");
4492
+ const verifyParsed = JSON.parse(verifyRaw);
4493
+ validateOpenCodeMcpConfig(verifyParsed, memoraone);
4494
+ return {
4495
+ outcome: jsonExists ? "updated" : "created",
4496
+ memoraone
4497
+ };
4498
+ }
4499
+ function logOpenCodeMcpCliSummary(info, dryRun, println = console.log) {
4500
+ const prefix = dryRun ? "would be " : "";
4501
+ if (info.outcome === "created") {
4502
+ println(`[setup-ide-files] OpenCode project MCP config ${prefix}created: ${info.activeConfigPath}`);
4503
+ } else if (info.outcome === "updated") {
4504
+ println(`[setup-ide-files] OpenCode project MCP config ${prefix}updated: ${info.activeConfigPath}`);
4505
+ } else {
4506
+ println(`[setup-ide-files] OpenCode project MCP config unchanged: ${info.activeConfigPath}`);
4507
+ }
4508
+ if (info.npxPath) {
4509
+ println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
4510
+ }
4511
+ println(`[setup-ide-files] Final active OpenCode MCP config: ${info.activeConfigPath}`);
4512
+ println(
4513
+ "[setup-ide-files] Fully quit OpenCode and reopen this repo for MCP changes to take effect."
4514
+ );
4515
+ }
4516
+
4517
+ // src/codexMcpConfig.ts
4518
+ var fs16 = __toESM(require("fs/promises"), 1);
4519
+ var path20 = __toESM(require("path"), 1);
4520
+ var import_node_crypto7 = require("crypto");
4521
+ var import_smol_toml = require("smol-toml");
4522
+ var CODEX_MCP_SERVER_NAME = "memoraone";
4523
+ var CODEX_OWNED_TABLE_PREFIX = `mcp_servers.${CODEX_MCP_SERVER_NAME}`;
4524
+ var CodexMcpTomlParseError = class extends Error {
4525
+ constructor(configPath) {
4526
+ super(
4527
+ `[setup-ide-files] Invalid TOML in shared Codex MCP config (file preserved, not modified): ${configPath}. Fix or remove the file, then re-run setup-ide-files.`
4528
+ );
4529
+ this.name = "CodexMcpTomlParseError";
4530
+ this.configPath = configPath;
4531
+ }
4532
+ };
4533
+ async function pathExists6(filePath) {
4534
+ try {
4535
+ await fs16.access(filePath);
4536
+ return true;
4537
+ } catch {
4538
+ return false;
4539
+ }
4540
+ }
4541
+ function getCodexProjectMcpConfigPath(repoRoot) {
4542
+ return path20.join(path20.resolve(repoRoot), ".codex", "config.toml");
4543
+ }
4544
+ function escapeTomlBasicString(value) {
4545
+ return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r") + '"';
4546
+ }
4547
+ function formatMemoraoneCodexTomlTables(memoraone) {
4548
+ const args2 = memoraone.args.map(escapeTomlBasicString).join(", ");
4549
+ const envLines = Object.entries(memoraone.env).map(([key, value]) => `${key} = ${escapeTomlBasicString(value)}`).join("\n");
4550
+ return `[mcp_servers.${CODEX_MCP_SERVER_NAME}]
4551
+ command = ${escapeTomlBasicString(memoraone.command)}
4552
+ args = [${args2}]
4553
+
4554
+ [mcp_servers.${CODEX_MCP_SERVER_NAME}.env]
4555
+ ${envLines}
4556
+ `;
4557
+ }
4558
+ function buildMemoraoneCodexMcpServer(options) {
4559
+ const environment = options.environment ?? (options.devMode ? "local" : "production");
4560
+ const env2 = {
4561
+ MEMORAONE_API_URL: resolveIdeApiUrl({
4562
+ environment,
4563
+ apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
4564
+ }),
4565
+ MEMORAONE_IDE_TYPE: "codex",
4566
+ [MEMORAONE_WORKSPACE_ROOT_ENV]: path20.resolve(options.workspaceRoot)
4567
+ };
4568
+ if (environment === "local" || options.devMode) {
4569
+ env2.MEMORAONE_DEV_MODE = "1";
4570
+ }
4571
+ if ("MEMORAONE_M1_PATH" in env2 || "MEMORAONE_API_KEY" in env2) {
4572
+ throw new Error("[setup-ide-files] Codex MCP config must not include credentials or .m1 paths");
4573
+ }
4574
+ return {
4575
+ command: options.command,
4576
+ args: [...options.args],
4577
+ env: env2
4578
+ };
4579
+ }
4580
+ function normalizeTomlTablePath(headerInner) {
4581
+ return headerInner.split(".").map((part) => {
4582
+ const trimmed = part.trim();
4583
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
4584
+ return trimmed.slice(1, -1);
4585
+ }
4586
+ return trimmed;
4587
+ }).join(".");
4588
+ }
4589
+ function isMemoraoneOwnedCodexTablePath(headerInner) {
4590
+ const normalized = normalizeTomlTablePath(headerInner);
4591
+ return normalized === CODEX_OWNED_TABLE_PREFIX || normalized.startsWith(`${CODEX_OWNED_TABLE_PREFIX}.`);
4592
+ }
4593
+ var TABLE_HEADER_RE = /^\s*\[\[?([^\]]+)\]\]?\s*(?:#.*)?$/;
4594
+ function splitTomlTableSegments(source) {
4595
+ const lines = source.split(/(?<=\n)/);
4596
+ const segments = [];
4597
+ let current = { tablePath: null, arrayOfTables: false, start: 0, end: 0 };
4598
+ let offset = 0;
4599
+ for (const line of lines) {
4600
+ const match = line.match(TABLE_HEADER_RE);
4601
+ if (match) {
4602
+ current.end = offset;
4603
+ if (current.end > current.start || current.tablePath !== null) {
4604
+ segments.push(current);
4605
+ }
4606
+ const arrayOfTables = /^\s*\[\[/.test(line);
4607
+ current = {
4608
+ tablePath: match[1].trim(),
4609
+ arrayOfTables,
4610
+ start: offset,
4611
+ end: offset + line.length
4612
+ };
4613
+ } else {
4614
+ current.end = offset + line.length;
4615
+ }
4616
+ offset += line.length;
4617
+ }
4618
+ current.end = source.length;
4619
+ if (current.end > current.start || current.tablePath !== null) {
4620
+ segments.push(current);
4621
+ }
4622
+ return segments;
4623
+ }
4624
+ function stripMemoraoneAssignmentsFromMcpServersBody(body) {
4625
+ const lines = body.split(/(?<=\n)/);
4626
+ const kept = [];
4627
+ let skippingMultiline = false;
4628
+ let multilineTerminator = null;
4629
+ for (const line of lines) {
4630
+ if (skippingMultiline) {
4631
+ if (multilineTerminator && line.includes(multilineTerminator)) {
4632
+ skippingMultiline = false;
4633
+ multilineTerminator = null;
4634
+ }
4635
+ continue;
4636
+ }
4637
+ const trimmed = line.trim();
4638
+ if (!trimmed || trimmed.startsWith("#")) {
4639
+ kept.push(line);
4640
+ continue;
4641
+ }
4642
+ const assignMatch = trimmed.match(
4643
+ /^(?:memoraone|"memoraone"|'memoraone')(?:\.[A-Za-z0-9_\-"']+)*\s*=\s*(.*)$/
4644
+ );
4645
+ if (!assignMatch) {
4646
+ kept.push(line);
4647
+ continue;
4648
+ }
4649
+ const rhs = assignMatch[1] ?? "";
4650
+ if (rhs.startsWith('"""')) {
4651
+ if (!rhs.slice(3).includes('"""')) {
4652
+ skippingMultiline = true;
4653
+ multilineTerminator = '"""';
4654
+ }
4655
+ continue;
4656
+ }
4657
+ if (rhs.startsWith("'''")) {
4658
+ if (!rhs.slice(3).includes("'''")) {
4659
+ skippingMultiline = true;
4660
+ multilineTerminator = "'''";
4661
+ }
4662
+ continue;
4663
+ }
4664
+ }
4665
+ return kept.join("");
4666
+ }
4667
+ function removeMemoraoneOwnedCodexToml(source) {
4668
+ const segments = splitTomlTableSegments(source);
4669
+ let removed = false;
4670
+ const parts = [];
4671
+ for (const segment of segments) {
4672
+ if (segment.tablePath && isMemoraoneOwnedCodexTablePath(segment.tablePath)) {
4673
+ removed = true;
4674
+ continue;
4675
+ }
4676
+ const chunk = source.slice(segment.start, segment.end);
4677
+ if (segment.tablePath && normalizeTomlTablePath(segment.tablePath) === "mcp_servers" && !segment.arrayOfTables) {
4678
+ const headerEnd = chunk.indexOf("\n");
4679
+ if (headerEnd === -1) {
4680
+ parts.push(chunk);
4681
+ continue;
4682
+ }
4683
+ const header = chunk.slice(0, headerEnd + 1);
4684
+ const body = chunk.slice(headerEnd + 1);
4685
+ const stripped = stripMemoraoneAssignmentsFromMcpServersBody(body);
4686
+ if (stripped !== body) removed = true;
4687
+ const hasKeys = stripped.split(/\r?\n/).some((line) => {
4688
+ const t = line.trim();
4689
+ return t.length > 0 && !t.startsWith("#");
4690
+ });
4691
+ if (!hasKeys && removed && body !== stripped) {
4692
+ const otherServersRemain = segments.some(
4693
+ (s) => s.tablePath != null && normalizeTomlTablePath(s.tablePath).startsWith("mcp_servers.") && !isMemoraoneOwnedCodexTablePath(s.tablePath)
4694
+ );
4695
+ if (!otherServersRemain) {
4696
+ const hasComments = stripped.split(/\r?\n/).some((line) => line.trim().startsWith("#"));
4697
+ if (hasComments) {
4698
+ parts.push(header + stripped);
4699
+ }
4700
+ continue;
4701
+ }
4702
+ }
4703
+ parts.push(header + stripped);
4704
+ continue;
4705
+ }
4706
+ parts.push(chunk);
4707
+ }
4708
+ let remainder = parts.join("");
4709
+ remainder = remainder.replace(/\n{3,}$/g, "\n\n");
4710
+ if (remainder.length > 0 && !remainder.endsWith("\n")) {
4711
+ remainder += "\n";
4712
+ }
4713
+ return { remainder, removed };
4714
+ }
4715
+ function mergeCodexMcpConfigToml(existingSource, memoraone) {
4716
+ const ownedBlock = formatMemoraoneCodexTomlTables(memoraone);
4717
+ if (existingSource === null || existingSource.trim() === "") {
4718
+ return ownedBlock;
4719
+ }
4720
+ const { remainder } = removeMemoraoneOwnedCodexToml(existingSource);
4721
+ if (remainder.trim() === "") {
4722
+ return ownedBlock;
4723
+ }
4724
+ const sep2 = remainder.endsWith("\n\n") ? "" : remainder.endsWith("\n") ? "\n" : "\n\n";
4725
+ return remainder + sep2 + ownedBlock;
4726
+ }
4727
+ function asStringRecord(value) {
4728
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
4729
+ return value;
4730
+ }
4731
+ function extractMemoraoneFromCodexParsed(parsed2) {
4732
+ const root = asStringRecord(parsed2);
4733
+ if (!root) return null;
4734
+ const mcpServers = asStringRecord(root.mcp_servers);
4735
+ if (!mcpServers) return null;
4736
+ const memoraone = asStringRecord(mcpServers[CODEX_MCP_SERVER_NAME]);
4737
+ if (!memoraone) return null;
4738
+ if (typeof memoraone.command !== "string") return null;
4739
+ if (!Array.isArray(memoraone.args) || !memoraone.args.every((a) => typeof a === "string")) {
4740
+ return null;
4741
+ }
4742
+ const env2 = asStringRecord(memoraone.env);
4743
+ if (!env2) return null;
4744
+ if (typeof env2.MEMORAONE_API_URL !== "string") return null;
4745
+ if (env2.MEMORAONE_IDE_TYPE !== "codex") return null;
4746
+ if (typeof env2[MEMORAONE_WORKSPACE_ROOT_ENV] !== "string") return null;
4747
+ const out = {
4748
+ command: memoraone.command,
4749
+ args: memoraone.args,
4750
+ env: {
4751
+ MEMORAONE_API_URL: env2.MEMORAONE_API_URL,
4752
+ MEMORAONE_IDE_TYPE: "codex",
4753
+ [MEMORAONE_WORKSPACE_ROOT_ENV]: env2[MEMORAONE_WORKSPACE_ROOT_ENV]
4754
+ }
4755
+ };
4756
+ if (typeof env2.MEMORAONE_DEV_MODE === "string") {
4757
+ out.env.MEMORAONE_DEV_MODE = env2.MEMORAONE_DEV_MODE;
4758
+ }
4759
+ return out;
4760
+ }
4761
+ function memoraoneServerMatches4(actual, expected) {
4762
+ if (!actual) return false;
4763
+ if (actual.command !== expected.command) return false;
4764
+ if (actual.args.length !== expected.args.length) return false;
4765
+ for (let i = 0; i < expected.args.length; i += 1) {
4766
+ if (actual.args[i] !== expected.args[i]) return false;
4767
+ }
4768
+ for (const [key, value] of Object.entries(expected.env)) {
4769
+ if (actual.env[key] !== value) return false;
4770
+ }
4771
+ for (const key of Object.keys(actual.env)) {
4772
+ if (!(key in expected.env)) return false;
4773
+ }
4774
+ return true;
4775
+ }
4776
+ function validateCodexMcpConfig(parsed2, expected) {
4777
+ const actual = extractMemoraoneFromCodexParsed(parsed2);
4778
+ if (!memoraoneServerMatches4(actual, expected)) {
4779
+ throw new Error(
4780
+ "[setup-ide-files] Codex MCP config mcp_servers.memoraone is missing or invalid."
4781
+ );
4782
+ }
4783
+ }
4784
+ function parseCodexTomlOrThrow(source, configPath) {
4785
+ try {
4786
+ return (0, import_smol_toml.parse)(source);
4787
+ } catch (err) {
4788
+ if (err instanceof import_smol_toml.TomlError || err instanceof Error && err.name === "TomlError") {
4789
+ throw new CodexMcpTomlParseError(configPath);
4790
+ }
4791
+ throw new CodexMcpTomlParseError(configPath);
4792
+ }
4793
+ }
4794
+ async function writeCodexTomlAtomic(filePath, content) {
4795
+ const dir = path20.dirname(filePath);
4796
+ await fs16.mkdir(dir, { recursive: true });
4797
+ const tmpPath = path20.join(
4798
+ dir,
4799
+ `.${path20.basename(filePath)}.${process.pid}.${(0, import_node_crypto7.randomBytes)(8).toString("hex")}.tmp`
4800
+ );
4801
+ try {
4802
+ await fs16.writeFile(tmpPath, content, "utf8");
4803
+ await fs16.rename(tmpPath, filePath);
4804
+ } catch (err) {
4805
+ try {
4806
+ await fs16.unlink(tmpPath);
4807
+ } catch {
4808
+ }
4809
+ throw err;
4810
+ }
4811
+ }
4812
+ async function buildCodexMemoraoneServer(options) {
4813
+ const environment = options.environment ?? (options.devMode ? "local" : "production");
4814
+ if (environment === "local" || options.devMode) {
4815
+ let cliPath = options.cliPathOverride;
4816
+ if (cliPath === void 0) {
4817
+ cliPath = await resolveLocalCliPathAsync();
4818
+ }
4819
+ if (!cliPath) {
4820
+ throw new Error(
4821
+ "[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
4822
+ );
4823
+ }
4824
+ return buildMemoraoneCodexMcpServer({
4825
+ command: process.execPath,
4826
+ args: [cliPath],
4827
+ workspaceRoot: options.workspaceRoot,
4828
+ environment: "local",
4829
+ devMode: true,
4830
+ apiUrl: options.apiUrl
4831
+ });
4832
+ }
4833
+ let npxPath = options.npxPathOverride;
4834
+ if (npxPath === void 0) {
4835
+ npxPath = await resolveNpxPath();
4836
+ }
4837
+ if (!npxPath) {
4838
+ throw new Error(
4839
+ "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Codex MCP."
4840
+ );
4841
+ }
4842
+ const channel = options.npmPackageChannel ?? npmPackageChannelFromEnvironment(environment);
4843
+ return buildMemoraoneCodexMcpServer({
4844
+ command: npxPath,
4845
+ args: ["-y", memoraoneNpmPackageSpec(channel)],
4846
+ workspaceRoot: options.workspaceRoot,
4847
+ environment,
4848
+ devMode: false,
4849
+ apiUrl: options.apiUrl
4850
+ });
4851
+ }
4852
+ async function setupCodexMcpConfig(options) {
4853
+ const projectPath = options.projectConfigPath ?? getCodexProjectMcpConfigPath(options.repoRoot);
4854
+ const workspaceRoot = path20.resolve(options.repoRoot);
4855
+ const memoraone = await buildCodexMemoraoneServer({
4856
+ workspaceRoot,
4857
+ devMode: options.devMode,
4858
+ environment: options.environment,
4859
+ npmPackageChannel: options.npmPackageChannel,
4860
+ apiUrl: options.apiUrl,
4861
+ npxPathOverride: options.npxPathOverride,
4862
+ cliPathOverride: options.cliPathOverride
4863
+ });
4864
+ const exists = await pathExists6(projectPath);
4865
+ let existingRaw = null;
4866
+ if (exists) {
4867
+ existingRaw = await fs16.readFile(projectPath, "utf8");
4868
+ const parsed2 = parseCodexTomlOrThrow(existingRaw, projectPath);
4869
+ const current = extractMemoraoneFromCodexParsed(parsed2);
4870
+ if (memoraoneServerMatches4(current, memoraone)) {
4871
+ return { outcome: "skipped", memoraone };
4872
+ }
4873
+ }
4874
+ const next = mergeCodexMcpConfigToml(existingRaw, memoraone);
4875
+ if (options.dryRun) {
4876
+ return { outcome: exists ? "updated" : "created", memoraone };
4877
+ }
4878
+ await writeCodexTomlAtomic(projectPath, next);
4879
+ const verifyRaw = await fs16.readFile(projectPath, "utf8");
4880
+ const verifyParsed = parseCodexTomlOrThrow(verifyRaw, projectPath);
4881
+ validateCodexMcpConfig(verifyParsed, memoraone);
4882
+ return {
4883
+ outcome: exists ? "updated" : "created",
4884
+ memoraone
4885
+ };
4886
+ }
4887
+ function logCodexMcpCliSummary(info, dryRun, println = console.log) {
4888
+ const prefix = dryRun ? "would be " : "";
4889
+ if (info.outcome === "created") {
4890
+ println(`[setup-ide-files] Codex project MCP config ${prefix}created: ${info.activeConfigPath}`);
4891
+ } else if (info.outcome === "updated") {
4892
+ println(`[setup-ide-files] Codex project MCP config ${prefix}updated: ${info.activeConfigPath}`);
4893
+ } else {
4894
+ println(`[setup-ide-files] Codex project MCP config unchanged: ${info.activeConfigPath}`);
4895
+ }
4896
+ if (info.npxPath) {
4897
+ println(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
4898
+ }
4899
+ println(`[setup-ide-files] Final active Codex MCP config: ${info.activeConfigPath}`);
4900
+ println(
4901
+ "[setup-ide-files] Trust this project in Codex if prompted, then restart Codex."
4902
+ );
4903
+ }
4904
+
4905
+ // src/openCursorMcpSettings.ts
4906
+ var import_node_child_process6 = require("child_process");
4907
+ var readline4 = __toESM(require("readline/promises"), 1);
4908
+ var import_node_util4 = require("util");
4909
+
4910
+ // src/terminalPresentation.ts
4911
+ var ANSI = {
4912
+ reset: "\x1B[0m",
4913
+ bold: "\x1B[1m",
4914
+ dim: "\x1B[2m",
4915
+ green: "\x1B[32m",
4916
+ yellow: "\x1B[33m",
4917
+ cyan: "\x1B[36m"
4918
+ };
4919
+ function isCiLikeEnv(env2 = process.env) {
4920
+ if (env2.CI === "true" || env2.CI === "1") return true;
4921
+ if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
4922
+ if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
4923
+ if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
4924
+ if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
4925
+ if (typeof env2.CI === "string" && env2.CI.trim() !== "" && env2.CI !== "0" && env2.CI !== "false") {
4926
+ return true;
4927
+ }
4928
+ return false;
4929
+ }
4930
+ function shouldEnableAnsiColor(opts = {}) {
4931
+ if (typeof opts.color === "boolean") return opts.color;
4932
+ const env2 = opts.env ?? process.env;
4933
+ if (env2.NO_COLOR !== void 0) return false;
4934
+ if (isCiLikeEnv(env2)) return false;
4935
+ const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
4936
+ return tty;
4937
+ }
4938
+ function shouldUseUnicodeSymbols(opts = {}) {
4939
+ if (typeof opts.unicode === "boolean") return opts.unicode;
4940
+ const env2 = opts.env ?? process.env;
4941
+ const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
4942
+ if (!tty) return false;
4943
+ if (env2.TERM === "dumb") return false;
4944
+ if (process.platform === "win32") {
4945
+ return Boolean(
4946
+ env2.WT_SESSION || env2.WT_PROFILE_ID || env2.ConEmuANSI === "ON" || env2.TERM_PROGRAM === "vscode" || env2.TERM_PROGRAM === "cursor" || typeof env2.TERM === "string" && env2.TERM !== "" && env2.TERM !== "dumb"
4947
+ );
4948
+ }
4949
+ return true;
4950
+ }
4951
+ function paint(enabled, code, text) {
3732
4952
  if (!enabled || text === "") return text;
3733
4953
  return `${code}${text}${ANSI.reset}`;
3734
4954
  }
@@ -3750,6 +4970,7 @@ function createTerminalPresentation(opts = {}) {
3750
4970
  warningSymbol,
3751
4971
  nextActionPrefix,
3752
4972
  successLine: (message) => paint(color, ANSI.green, `${successSymbol} ${message}`),
4973
+ checkLine: (message) => `${paint(color, ANSI.green, successSymbol)} ${message}`,
3753
4974
  warningLine: (message) => paint(color, ANSI.yellow, `${warningSymbol} ${message}`),
3754
4975
  nextActionLine: (message) => `${nextActionPrefix} ${message}`,
3755
4976
  heading: (text) => paint(color, ANSI.bold, text),
@@ -3802,44 +5023,6 @@ async function confirmYesDefault(question, deps = {}) {
3802
5023
  rl.close();
3803
5024
  }
3804
5025
  }
3805
- function collectOutcomePaths(outcomes) {
3806
- const created = [];
3807
- const updated = [];
3808
- for (const [file, outcome] of Object.entries(outcomes)) {
3809
- if (outcome === "created") created.push(file);
3810
- else if (outcome === "updated") updated.push(file);
3811
- }
3812
- return { created, updated };
3813
- }
3814
- function formatCursorSetupCompletedSummary(opts, presentation = createTerminalPresentation({ color: false, unicode: false })) {
3815
- const { created, updated } = collectOutcomePaths(opts.outcomes);
3816
- const tp = presentation;
3817
- const lines = [
3818
- tp.successLine("MemoraOne setup completed for Cursor"),
3819
- "",
3820
- tp.heading("Repository"),
3821
- tp.indent(tp.cyan(opts.repoRoot)),
3822
- "",
3823
- tp.heading("Changes")
3824
- ];
3825
- if (created.length === 0 && updated.length === 0) {
3826
- lines.push(tp.indent(tp.dim("No file changes needed")));
3827
- } else {
3828
- if (created.length) {
3829
- lines.push(tp.indent(`Created: ${created.join(", ")}`));
3830
- }
3831
- if (updated.length) {
3832
- lines.push(tp.indent(`Updated: ${updated.join(", ")}`));
3833
- }
3834
- }
3835
- return lines;
3836
- }
3837
- function printCursorSetupCompletedSummary(opts, println = console.log, presentation) {
3838
- const tp = presentation ?? createTerminalPresentation();
3839
- for (const line of formatCursorSetupCompletedSummary(opts, tp)) {
3840
- println(line);
3841
- }
3842
- }
3843
5026
  function macosOpenCursorMcpSettingsAppleScript() {
3844
5027
  return [
3845
5028
  'tell application "Cursor" to activate',
@@ -3910,6 +5093,69 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
3910
5093
  printManualCursorMcpSettingsSteps(platform2, println);
3911
5094
  }
3912
5095
 
5096
+ // src/setupSuccessOutput.ts
5097
+ var CLAUDE_CODE_APPROVAL_FOLLOW_UP = "Approve the MemoraOne MCP server in Claude Code if prompted.";
5098
+ var WINDSURF_REFRESH_FOLLOW_UP = "Refresh MCP servers in Devin Desktop settings, or restart Devin Desktop.";
5099
+ var OPENCODE_RESTART_FOLLOW_UP = "Fully quit OpenCode and reopen this repo for MCP changes to take effect.";
5100
+ var CODEX_TRUST_FOLLOW_UP = "Trust this project in Codex if prompted, then restart Codex.";
5101
+ var RESTART_LINE = "Restart your IDEs to finish setup.";
5102
+ function formatSetupSuccessLines(opts = {}) {
5103
+ const tp = opts.presentation ?? createTerminalPresentation(opts.presentationOptions ?? { color: false, unicode: true });
5104
+ const targets = opts.targets ?? {};
5105
+ const lines = [];
5106
+ if (opts.repositoryConnected) {
5107
+ lines.push(tp.checkLine("Repository connected"));
5108
+ }
5109
+ if (targets.cursor) {
5110
+ lines.push(tp.checkLine("Cursor configured"));
5111
+ }
5112
+ if (targets.vscode) {
5113
+ lines.push(tp.checkLine("VS Code configured"));
5114
+ }
5115
+ if (targets.jetbrains) {
5116
+ lines.push(tp.checkLine("JetBrains configured"));
5117
+ }
5118
+ if (targets.claudeCode) {
5119
+ lines.push(tp.checkLine("Claude Code configured"));
5120
+ }
5121
+ if (targets.windsurf) {
5122
+ lines.push(tp.checkLine("Devin Desktop configured"));
5123
+ }
5124
+ if (targets.opencode) {
5125
+ lines.push(tp.checkLine("OpenCode configured"));
5126
+ }
5127
+ if (targets.codex) {
5128
+ lines.push(tp.checkLine("Codex configured"));
5129
+ }
5130
+ const hasIde = Boolean(
5131
+ targets.cursor || targets.vscode || targets.jetbrains || targets.claudeCode || targets.windsurf || targets.opencode || targets.codex
5132
+ );
5133
+ lines.push("");
5134
+ lines.push(tp.checkLine("MemoraOne is ready"));
5135
+ if (hasIde) {
5136
+ lines.push("");
5137
+ lines.push(RESTART_LINE);
5138
+ }
5139
+ if (targets.claudeCode) {
5140
+ lines.push(CLAUDE_CODE_APPROVAL_FOLLOW_UP);
5141
+ }
5142
+ if (targets.windsurf) {
5143
+ lines.push(WINDSURF_REFRESH_FOLLOW_UP);
5144
+ }
5145
+ if (targets.opencode) {
5146
+ lines.push(OPENCODE_RESTART_FOLLOW_UP);
5147
+ }
5148
+ if (targets.codex) {
5149
+ lines.push(CODEX_TRUST_FOLLOW_UP);
5150
+ }
5151
+ return lines;
5152
+ }
5153
+ function printSetupSuccess(opts, println = console.log) {
5154
+ for (const line of formatSetupSuccessLines(opts)) {
5155
+ println(line);
5156
+ }
5157
+ }
5158
+
3913
5159
  // src/setupIdeFiles.ts
3914
5160
  var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
3915
5161
  function buildMemoraoneMcpServer(ideType, options = {}) {
@@ -3918,13 +5164,14 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
3918
5164
  MEMORAONE_API_URL: resolveIdeApiUrl({ environment, apiUrl: options.apiUrl }),
3919
5165
  MEMORAONE_IDE_TYPE: ideType
3920
5166
  };
5167
+ const includeWorkspaceRoot = options.workspaceRoot !== void 0 && (environment === "local" || ideType === "cursor" || ideType === "claude-code");
5168
+ if (includeWorkspaceRoot) {
5169
+ env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path21.resolve(options.workspaceRoot);
5170
+ }
3921
5171
  if (environment === "local") {
3922
5172
  if (!options.cliPath) {
3923
5173
  throw new Error("[setup-ide-files] Local VS Code MCP config requires a built CLI path.");
3924
5174
  }
3925
- if (options.workspaceRoot !== void 0) {
3926
- env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path17.resolve(options.workspaceRoot);
3927
- }
3928
5175
  return {
3929
5176
  command: "node",
3930
5177
  args: [options.cliPath],
@@ -3939,15 +5186,15 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
3939
5186
  };
3940
5187
  }
3941
5188
  function assertUnderRepoRoot(repoRoot, absPath) {
3942
- const normRoot = path17.resolve(repoRoot) + path17.sep;
3943
- const normPath = path17.resolve(absPath);
3944
- if (normPath !== path17.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
5189
+ const normRoot = path21.resolve(repoRoot) + path21.sep;
5190
+ const normPath = path21.resolve(absPath);
5191
+ if (normPath !== path21.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
3945
5192
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
3946
5193
  }
3947
5194
  }
3948
- async function pathExists4(filePath) {
5195
+ async function pathExists7(filePath) {
3949
5196
  try {
3950
- await fs13.access(filePath);
5197
+ await fs17.access(filePath);
3951
5198
  return true;
3952
5199
  } catch {
3953
5200
  return false;
@@ -3957,21 +5204,21 @@ async function ensureGitignoreMemoraone(_repoRoot, _opts) {
3957
5204
  return "skipped";
3958
5205
  }
3959
5206
  async function findRepoRoot(startDir) {
3960
- let current = path17.resolve(startDir);
3961
- const root = path17.parse(current).root;
5207
+ let current = path21.resolve(startDir);
5208
+ const root = path21.parse(current).root;
3962
5209
  while (true) {
3963
- const gitPath = path17.join(current, ".git");
3964
- const m1Path = path17.join(current, "memoraone.m1");
3965
- if (await pathExists4(gitPath) || await pathExists4(m1Path)) {
5210
+ const gitPath = path21.join(current, ".git");
5211
+ const m1Path = path21.join(current, "memoraone.m1");
5212
+ if (await pathExists7(gitPath) || await pathExists7(m1Path)) {
3966
5213
  return current;
3967
5214
  }
3968
5215
  if (current === root) {
3969
5216
  return null;
3970
5217
  }
3971
- current = path17.dirname(current);
5218
+ current = path21.dirname(current);
3972
5219
  }
3973
5220
  }
3974
- function stripLeadingLineComments3(text) {
5221
+ function stripLeadingLineComments4(text) {
3975
5222
  return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
3976
5223
  }
3977
5224
  function cursorRuleBody() {
@@ -4023,18 +5270,18 @@ function buildVscodeMcpJsonBody(existing, options = {}) {
4023
5270
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
4024
5271
  }
4025
5272
  async function writeSharedMcpJsonAtomic(filePath, content) {
4026
- const dir = path17.dirname(filePath);
4027
- await fs13.mkdir(dir, { recursive: true });
4028
- const tmpPath = path17.join(
5273
+ const dir = path21.dirname(filePath);
5274
+ await fs17.mkdir(dir, { recursive: true });
5275
+ const tmpPath = path21.join(
4029
5276
  dir,
4030
- `.${path17.basename(filePath)}.${process.pid}.${(0, import_node_crypto5.randomBytes)(8).toString("hex")}.tmp`
5277
+ `.${path21.basename(filePath)}.${process.pid}.${(0, import_node_crypto8.randomBytes)(8).toString("hex")}.tmp`
4031
5278
  );
4032
5279
  try {
4033
- await fs13.writeFile(tmpPath, content, "utf8");
4034
- await fs13.rename(tmpPath, filePath);
5280
+ await fs17.writeFile(tmpPath, content, "utf8");
5281
+ await fs17.rename(tmpPath, filePath);
4035
5282
  } catch (err) {
4036
5283
  try {
4037
- await fs13.unlink(tmpPath);
5284
+ await fs17.unlink(tmpPath);
4038
5285
  } catch {
4039
5286
  }
4040
5287
  throw err;
@@ -4053,43 +5300,79 @@ function buildCursorMcpJsonBody(existing, writeOptions) {
4053
5300
  const merged = mergeCursorRepoMcpConfigObject(existing, writeOptions);
4054
5301
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
4055
5302
  }
5303
+ function buildClaudeCodeMcpJsonBody(existing, writeOptions) {
5304
+ const merged = mergeClaudeCodeMcpConfigObject(existing, writeOptions);
5305
+ return JSON.stringify(merged, null, 2) + "\n";
5306
+ }
5307
+ async function writeClaudeCodeMcpJson(repoRoot, buildBody, opts) {
5308
+ const abs = getClaudeCodeMcpConfigPath(repoRoot);
5309
+ assertUnderRepoRoot(repoRoot, abs);
5310
+ let raw = "";
5311
+ let existed = false;
5312
+ try {
5313
+ raw = await fs17.readFile(abs, "utf8");
5314
+ existed = true;
5315
+ } catch (err) {
5316
+ if (err?.code !== "ENOENT") throw err;
5317
+ }
5318
+ if (!existed) {
5319
+ const body = buildBody(null);
5320
+ if (opts.dryRun) return "created";
5321
+ await writeSharedMcpJsonAtomic(abs, body);
5322
+ return "created";
5323
+ }
5324
+ let parsed2;
5325
+ try {
5326
+ parsed2 = JSON.parse(stripLeadingLineComments4(raw));
5327
+ } catch {
5328
+ throw new SharedMcpJsonParseError(abs);
5329
+ }
5330
+ if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
5331
+ throw new SharedMcpJsonParseError(abs);
5332
+ }
5333
+ const next = buildBody(parsed2);
5334
+ if (next === raw) return "skipped";
5335
+ if (opts.dryRun) return "updated";
5336
+ await writeSharedMcpJsonAtomic(abs, next);
5337
+ return "updated";
5338
+ }
4056
5339
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
4057
- const abs = path17.join(repoRoot, relPath);
5340
+ const abs = path21.join(repoRoot, relPath);
4058
5341
  assertUnderRepoRoot(repoRoot, abs);
4059
5342
  let prior = "";
4060
5343
  let existed = false;
4061
5344
  try {
4062
- prior = await fs13.readFile(abs, "utf8");
5345
+ prior = await fs17.readFile(abs, "utf8");
4063
5346
  existed = true;
4064
5347
  } catch (err) {
4065
5348
  if (err?.code !== "ENOENT") throw err;
4066
5349
  }
4067
5350
  if (!existed) {
4068
5351
  if (opts.dryRun) return "created";
4069
- await fs13.mkdir(path17.dirname(abs), { recursive: true });
4070
- await fs13.writeFile(abs, fullContent, "utf8");
5352
+ await fs17.mkdir(path21.dirname(abs), { recursive: true });
5353
+ await fs17.writeFile(abs, fullContent, "utf8");
4071
5354
  return "created";
4072
5355
  }
4073
5356
  if (prior.includes(MANAGED_MARKER)) {
4074
5357
  if (prior === fullContent) return "skipped";
4075
5358
  if (opts.dryRun) return "updated";
4076
- await fs13.mkdir(path17.dirname(abs), { recursive: true });
4077
- await fs13.writeFile(abs, fullContent, "utf8");
5359
+ await fs17.mkdir(path21.dirname(abs), { recursive: true });
5360
+ await fs17.writeFile(abs, fullContent, "utf8");
4078
5361
  return "updated";
4079
5362
  }
4080
5363
  if (!opts.force) return "skipped-untracked";
4081
5364
  if (opts.dryRun) return "updated";
4082
- await fs13.mkdir(path17.dirname(abs), { recursive: true });
4083
- await fs13.writeFile(abs, fullContent, "utf8");
5365
+ await fs17.mkdir(path21.dirname(abs), { recursive: true });
5366
+ await fs17.writeFile(abs, fullContent, "utf8");
4084
5367
  return "updated";
4085
5368
  }
4086
5369
  async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
4087
- const abs = path17.join(repoRoot, relPath);
5370
+ const abs = path21.join(repoRoot, relPath);
4088
5371
  assertUnderRepoRoot(repoRoot, abs);
4089
5372
  let raw = "";
4090
5373
  let existed = false;
4091
5374
  try {
4092
- raw = await fs13.readFile(abs, "utf8");
5375
+ raw = await fs17.readFile(abs, "utf8");
4093
5376
  existed = true;
4094
5377
  } catch (err) {
4095
5378
  if (err?.code !== "ENOENT") throw err;
@@ -4104,7 +5387,7 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
4104
5387
  if (!managed && !opts.force) return "skipped-untracked";
4105
5388
  let parsed2;
4106
5389
  try {
4107
- parsed2 = JSON.parse(stripLeadingLineComments3(raw));
5390
+ parsed2 = JSON.parse(stripLeadingLineComments4(raw));
4108
5391
  } catch {
4109
5392
  throw new SharedMcpJsonParseError(abs);
4110
5393
  }
@@ -4121,6 +5404,10 @@ function parseSetupIdeFlags(argv) {
4121
5404
  let cursor = false;
4122
5405
  let vscode = false;
4123
5406
  let jetbrains = false;
5407
+ let claudeCode = false;
5408
+ let windsurf = false;
5409
+ let opencode = false;
5410
+ let codex = false;
4124
5411
  let all = false;
4125
5412
  let force = false;
4126
5413
  let dryRun = false;
@@ -4130,6 +5417,7 @@ function parseSetupIdeFlags(argv) {
4130
5417
  let repair = false;
4131
5418
  let local = false;
4132
5419
  let staging = false;
5420
+ let verbose = false;
4133
5421
  let workspaceRoot;
4134
5422
  let apiUrl;
4135
5423
  const unknown = [];
@@ -4139,6 +5427,10 @@ function parseSetupIdeFlags(argv) {
4139
5427
  if (a === "--cursor") cursor = true;
4140
5428
  else if (a === "--vscode") vscode = true;
4141
5429
  else if (a === "--jetbrains") jetbrains = true;
5430
+ else if (a === "--claude-code") claudeCode = true;
5431
+ else if (a === "--windsurf") windsurf = true;
5432
+ else if (a === "--opencode") opencode = true;
5433
+ else if (a === "--codex") codex = true;
4142
5434
  else if (a === "--all") all = true;
4143
5435
  else if (a === "--force") force = true;
4144
5436
  else if (a === "--dry-run") dryRun = true;
@@ -4148,6 +5440,7 @@ function parseSetupIdeFlags(argv) {
4148
5440
  else if (a === "--repair") repair = true;
4149
5441
  else if (a === "--local") local = true;
4150
5442
  else if (a === "--staging") staging = true;
5443
+ else if (a === "--verbose") verbose = true;
4151
5444
  else if (a === "--workspace-root") {
4152
5445
  const value = argv[++i];
4153
5446
  if (!value || value.startsWith("-")) {
@@ -4178,12 +5471,20 @@ function parseSetupIdeFlags(argv) {
4178
5471
  }
4179
5472
  } else if (a.startsWith("-")) unknown.push(a);
4180
5473
  }
4181
- const specific = cursor || vscode || jetbrains;
5474
+ const specific = cursor || vscode || jetbrains || claudeCode || windsurf || opencode || codex;
4182
5475
  let targets;
4183
5476
  if (all || !specific) {
4184
- targets = { cursor: true, vscode: true, jetbrains: true };
5477
+ targets = {
5478
+ cursor: true,
5479
+ vscode: true,
5480
+ jetbrains: true,
5481
+ claudeCode: true,
5482
+ windsurf: true,
5483
+ opencode: true,
5484
+ codex: true
5485
+ };
4185
5486
  } else {
4186
- targets = { cursor, vscode, jetbrains };
5487
+ targets = { cursor, vscode, jetbrains, claudeCode, windsurf, opencode, codex };
4187
5488
  }
4188
5489
  if (!flagError && local && staging) {
4189
5490
  flagError = "[setup-ide-files] --local and --staging are mutually exclusive.";
@@ -4205,10 +5506,46 @@ function parseSetupIdeFlags(argv) {
4205
5506
  workspaceRoot,
4206
5507
  apiUrl,
4207
5508
  explicitCursor: cursor,
5509
+ verbose,
4208
5510
  unknown,
4209
5511
  flagError
4210
5512
  };
4211
5513
  }
5514
+ function logSetupIdeFilesVerboseSuccess(opts, println = console.log) {
5515
+ const { targets, dryRun, result } = opts;
5516
+ if (result.repoRoot) {
5517
+ println(`[setup-ide-files] Repo root: ${result.repoRoot}`);
5518
+ }
5519
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
5520
+ logSetupIdeCleanupSummary(result.daemonCleanup, println);
5521
+ }
5522
+ if (targets.cursor && result.cursorMcp) {
5523
+ logCursorMcpCliSummary(result.cursorMcp, dryRun, {
5524
+ forInteractivePostSetup: opts.forInteractivePostSetup,
5525
+ println
5526
+ });
5527
+ }
5528
+ if (targets.jetbrains && result.jetbrainsMcp) {
5529
+ logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun, println);
5530
+ }
5531
+ if (targets.windsurf && result.windsurfMcp) {
5532
+ logWindsurfMcpCliSummary(result.windsurfMcp, dryRun, println);
5533
+ }
5534
+ if (targets.opencode && result.opencodeMcp) {
5535
+ logOpenCodeMcpCliSummary(result.opencodeMcp, dryRun, println);
5536
+ }
5537
+ if (targets.codex && result.codexMcp) {
5538
+ logCodexMcpCliSummary(result.codexMcp, dryRun, println);
5539
+ }
5540
+ summarizeOutcomes(result.outcomes, println);
5541
+ if (dryRun) {
5542
+ println("[setup-ide-files] Dry run: no files written.");
5543
+ if (result.daemonCleanup && !result.daemonCleanup.skipped) {
5544
+ println("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
5545
+ }
5546
+ }
5547
+ println(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
5548
+ }
4212
5549
  async function resolveSetupApiUrl(o, repoRoot) {
4213
5550
  if (o.apiUrl) return normalizeApiUrl2(o.apiUrl);
4214
5551
  const binding = await findBindingRecordByWorkspaceRoot(repoRoot, o.homeDir);
@@ -4235,7 +5572,7 @@ function cursorEnvironmentFromFlags(local, staging) {
4235
5572
  if (staging) return "staging";
4236
5573
  return "production";
4237
5574
  }
4238
- function summarizeOutcomes(outcomes) {
5575
+ function summarizeOutcomes(outcomes, println = console.log) {
4239
5576
  const created = [];
4240
5577
  const updated = [];
4241
5578
  const skipped = [];
@@ -4253,17 +5590,21 @@ function summarizeOutcomes(outcomes) {
4253
5590
  if (skippedUntracked.length) {
4254
5591
  lines.push(` skipped (unmanaged existing file, use --force): ${skippedUntracked.join(", ")}`);
4255
5592
  }
4256
- console.log(lines.join("\n"));
5593
+ println(lines.join("\n"));
4257
5594
  }
4258
5595
  function ideTypesFromSetupTargets(targets) {
4259
5596
  const ides = [];
4260
5597
  if (targets.cursor) ides.push("cursor");
4261
5598
  if (targets.vscode) ides.push("copilot-vscode");
4262
5599
  if (targets.jetbrains) ides.push("jetbrains");
5600
+ if (targets.claudeCode) ides.push("claude-code");
5601
+ if (targets.windsurf) ides.push("windsurf");
5602
+ if (targets.opencode) ides.push("opencode");
5603
+ if (targets.codex) ides.push("codex");
4263
5604
  return ides;
4264
5605
  }
4265
5606
  function setupTargetsAllIdes(targets) {
4266
- return targets.cursor && targets.vscode && targets.jetbrains;
5607
+ return targets.cursor && targets.vscode && targets.jetbrains && targets.claudeCode && targets.windsurf && targets.opencode && targets.codex;
4267
5608
  }
4268
5609
  function aggregateCleanupResults(results) {
4269
5610
  const killedPids = /* @__PURE__ */ new Set();
@@ -4294,28 +5635,28 @@ function aggregateCleanupResults(results) {
4294
5635
  error
4295
5636
  };
4296
5637
  }
4297
- function logSetupIdeCleanupSummary(cleanup) {
5638
+ function logSetupIdeCleanupSummary(cleanup, println = console.log) {
4298
5639
  if (cleanup.skipped) return;
4299
- console.log(`[setup-ide-files] Project id: ${cleanup.projectId}`);
5640
+ println(`[setup-ide-files] Project id: ${cleanup.projectId}`);
4300
5641
  if (cleanup.foundDaemonCount > 0) {
4301
- console.log(`[setup-ide-files] Found ${cleanup.foundDaemonCount} stale daemon(s)`);
5642
+ println(`[setup-ide-files] Found ${cleanup.foundDaemonCount} stale daemon(s)`);
4302
5643
  if (cleanup.dryRun) {
4303
- console.log(`[setup-ide-files] Would stop ${cleanup.foundDaemonCount} stale daemon(s)`);
5644
+ println(`[setup-ide-files] Would stop ${cleanup.foundDaemonCount} stale daemon(s)`);
4304
5645
  } else if (cleanup.stoppedDaemonCount > 0) {
4305
- console.log(`[setup-ide-files] Stopped ${cleanup.stoppedDaemonCount} stale daemon(s)`);
5646
+ println(`[setup-ide-files] Stopped ${cleanup.stoppedDaemonCount} stale daemon(s)`);
4306
5647
  }
4307
5648
  } else {
4308
- console.log("[setup-ide-files] No stale daemons found for this project and IDE target(s).");
5649
+ println("[setup-ide-files] No stale daemons found for this project and IDE target(s).");
4309
5650
  }
4310
5651
  if (cleanup.removedSocketCount > 0) {
4311
5652
  if (cleanup.dryRun) {
4312
- console.log(`[setup-ide-files] Would remove ${cleanup.removedSocketCount} stale socket(s)`);
5653
+ println(`[setup-ide-files] Would remove ${cleanup.removedSocketCount} stale socket(s)`);
4313
5654
  } else {
4314
- console.log(`[setup-ide-files] Removed ${cleanup.removedSocketCount} stale socket(s)`);
5655
+ println(`[setup-ide-files] Removed ${cleanup.removedSocketCount} stale socket(s)`);
4315
5656
  }
4316
5657
  }
4317
5658
  if (cleanup.skippedUnrelatedDaemonCount > 0) {
4318
- console.log(
5659
+ println(
4319
5660
  `[setup-ide-files] Skipped ${cleanup.skippedUnrelatedDaemonCount} unrelated project daemon(s)`
4320
5661
  );
4321
5662
  }
@@ -4383,8 +5724,18 @@ function restartIdeInstruction(targets) {
4383
5724
  if (targets.cursor) names.push("Cursor");
4384
5725
  if (targets.vscode) names.push("VS Code");
4385
5726
  if (targets.jetbrains) names.push("JetBrains IDE");
5727
+ if (targets.claudeCode) names.push("Claude Code");
5728
+ if (targets.windsurf) names.push("Devin Desktop");
5729
+ if (targets.opencode) names.push("OpenCode");
5730
+ if (targets.codex) names.push("Codex");
4386
5731
  if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
4387
5732
  if (names.length === 1) {
5733
+ if (targets.windsurf) {
5734
+ return "Refresh MCP servers in Devin Desktop settings, or restart Devin Desktop, for MCP changes to take effect.";
5735
+ }
5736
+ if (targets.codex) {
5737
+ return "Trust this project in Codex if prompted, then restart Codex.";
5738
+ }
4388
5739
  return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
4389
5740
  }
4390
5741
  const last = names.pop();
@@ -4394,7 +5745,10 @@ async function runSetupIdeFiles(o) {
4394
5745
  const outcomes = {};
4395
5746
  let cursorMcp;
4396
5747
  let jetbrainsMcp;
4397
- const repoRoot = o.workspaceRoot !== void 0 && o.workspaceRoot !== "" ? path17.resolve(o.workspaceRoot) : await findRepoRoot(o.cwd);
5748
+ let windsurfMcp;
5749
+ let opencodeMcp;
5750
+ let codexMcp;
5751
+ const repoRoot = o.workspaceRoot !== void 0 && o.workspaceRoot !== "" ? path21.resolve(o.workspaceRoot) : await findRepoRoot(o.cwd);
4398
5752
  if (!repoRoot) {
4399
5753
  return {
4400
5754
  exitCode: 1,
@@ -4616,7 +5970,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
4616
5970
  { force: o.force, dryRun: o.dryRun }
4617
5971
  );
4618
5972
  try {
4619
- const homeDir = o.jetbrainsHomeDir ?? os6.homedir();
5973
+ const homeDir = o.jetbrainsHomeDir ?? os7.homedir();
4620
5974
  const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
4621
5975
  const jetbrainsSetup = await setupJetBrainsMcpConfig({
4622
5976
  homeDir,
@@ -4655,7 +6009,207 @@ description: MemoraOne MCP \u2014 IDE agent instructions
4655
6009
  };
4656
6010
  }
4657
6011
  }
4658
- return { exitCode: 0, repoRoot, outcomes, cursorMcp, jetbrainsMcp, daemonCleanup };
6012
+ if (o.targets.claudeCode) {
6013
+ let npxPath = null;
6014
+ let cliPath;
6015
+ if (cursorEnvironment === "local") {
6016
+ let resolvedCliPath = o.cursorLocalCliPathOverride;
6017
+ if (resolvedCliPath === void 0) {
6018
+ resolvedCliPath = o.cliPathOverride;
6019
+ }
6020
+ if (resolvedCliPath === void 0) {
6021
+ resolvedCliPath = await resolveBuiltCliPathAsync();
6022
+ }
6023
+ if (!resolvedCliPath) {
6024
+ return {
6025
+ exitCode: 1,
6026
+ repoRoot,
6027
+ outcomes,
6028
+ cursorMcp,
6029
+ jetbrainsMcp,
6030
+ daemonCleanup,
6031
+ error: "[setup-ide-files] Local mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
6032
+ };
6033
+ }
6034
+ cliPath = resolvedCliPath;
6035
+ } else {
6036
+ if (o.npxPathOverride !== void 0) {
6037
+ npxPath = o.npxPathOverride;
6038
+ } else {
6039
+ npxPath = await resolveNpxPath();
6040
+ }
6041
+ if (!npxPath) {
6042
+ return {
6043
+ exitCode: 1,
6044
+ repoRoot,
6045
+ outcomes,
6046
+ cursorMcp,
6047
+ jetbrainsMcp,
6048
+ daemonCleanup,
6049
+ error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Claude Code MCP."
6050
+ };
6051
+ }
6052
+ }
6053
+ const claudeWriteOptions = {
6054
+ environment: cursorEnvironment,
6055
+ npxPath: npxPath ?? void 0,
6056
+ cliPath,
6057
+ repoRoot,
6058
+ apiUrl: resolvedApiUrl2,
6059
+ npmPackageChannel
6060
+ };
6061
+ try {
6062
+ outcomes[".mcp.json"] = await writeClaudeCodeMcpJson(
6063
+ repoRoot,
6064
+ (existing) => buildClaudeCodeMcpJsonBody(existing, claudeWriteOptions),
6065
+ { dryRun: o.dryRun }
6066
+ );
6067
+ } catch (err) {
6068
+ const message = err instanceof Error ? err.message : String(err);
6069
+ return {
6070
+ exitCode: 1,
6071
+ repoRoot,
6072
+ outcomes,
6073
+ cursorMcp,
6074
+ jetbrainsMcp,
6075
+ windsurfMcp,
6076
+ daemonCleanup,
6077
+ error: message
6078
+ };
6079
+ }
6080
+ }
6081
+ if (o.targets.windsurf) {
6082
+ try {
6083
+ const homeDir = o.windsurfHomeDir ?? os7.homedir();
6084
+ const activePath = o.windsurfGlobalMcpConfigPath ?? getWindsurfGlobalMcpConfigPath(homeDir);
6085
+ const legacyPath = o.windsurfLegacyMcpConfigPath ?? getWindsurfLegacyMcpConfigPath(homeDir);
6086
+ const windsurfSetup = await setupWindsurfMcpConfig({
6087
+ homeDir,
6088
+ repoRoot,
6089
+ globalConfigPath: activePath,
6090
+ legacyConfigPath: legacyPath,
6091
+ dryRun: o.dryRun,
6092
+ // Local dogfood: --dev / local environment → node + built CLI.
6093
+ // Published staging/production: npx + channel-specific package spec.
6094
+ devMode: localOrDev,
6095
+ environment: localOrDev ? "local" : cursorEnvironment,
6096
+ npmPackageChannel,
6097
+ apiUrl: resolvedApiUrl2,
6098
+ npxPathOverride: o.npxPathOverride,
6099
+ cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
6100
+ });
6101
+ windsurfMcp = {
6102
+ activeConfigPath: windsurfSetup.activeConfigPath,
6103
+ outcome: windsurfSetup.outcome,
6104
+ npxPath: windsurfSetup.memoraone?.command,
6105
+ backupPath: windsurfSetup.backupPath,
6106
+ legacyConfigPath: windsurfSetup.legacyConfigPath,
6107
+ legacyCleanup: windsurfSetup.legacyCleanup,
6108
+ warning: windsurfSetup.warning
6109
+ };
6110
+ outcomes[`windsurf-global:${activePath}`] = windsurfSetup.outcome;
6111
+ } catch (err) {
6112
+ const message = err instanceof Error ? err.message : String(err);
6113
+ return {
6114
+ exitCode: 1,
6115
+ repoRoot,
6116
+ outcomes,
6117
+ cursorMcp,
6118
+ jetbrainsMcp,
6119
+ windsurfMcp,
6120
+ opencodeMcp,
6121
+ daemonCleanup,
6122
+ error: message
6123
+ };
6124
+ }
6125
+ }
6126
+ if (o.targets.opencode) {
6127
+ try {
6128
+ const activePath = o.opencodeProjectMcpConfigPath ?? getOpenCodeProjectMcpConfigPath(repoRoot);
6129
+ const opencodeSetup = await setupOpenCodeMcpConfig({
6130
+ repoRoot,
6131
+ projectConfigPath: activePath,
6132
+ dryRun: o.dryRun,
6133
+ // Local dogfood: --dev / local environment → node + built CLI.
6134
+ // Published staging/production: npx + channel-specific package spec.
6135
+ devMode: localOrDev,
6136
+ environment: localOrDev ? "local" : cursorEnvironment,
6137
+ npmPackageChannel,
6138
+ apiUrl: resolvedApiUrl2,
6139
+ npxPathOverride: o.npxPathOverride,
6140
+ cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
6141
+ });
6142
+ opencodeMcp = {
6143
+ activeConfigPath: activePath,
6144
+ outcome: opencodeSetup.outcome,
6145
+ npxPath: opencodeSetup.memoraone?.command[0]
6146
+ };
6147
+ outcomes["opencode.json"] = opencodeSetup.outcome;
6148
+ } catch (err) {
6149
+ const message = err instanceof Error ? err.message : String(err);
6150
+ return {
6151
+ exitCode: 1,
6152
+ repoRoot,
6153
+ outcomes,
6154
+ cursorMcp,
6155
+ jetbrainsMcp,
6156
+ windsurfMcp,
6157
+ opencodeMcp,
6158
+ codexMcp,
6159
+ daemonCleanup,
6160
+ error: message
6161
+ };
6162
+ }
6163
+ }
6164
+ if (o.targets.codex) {
6165
+ try {
6166
+ const activePath = o.codexProjectMcpConfigPath ?? getCodexProjectMcpConfigPath(repoRoot);
6167
+ const codexSetup = await setupCodexMcpConfig({
6168
+ repoRoot,
6169
+ projectConfigPath: activePath,
6170
+ dryRun: o.dryRun,
6171
+ // Local dogfood: --dev / local environment → node + built CLI.
6172
+ // Published staging/production: npx + channel-specific package spec.
6173
+ devMode: localOrDev,
6174
+ environment: localOrDev ? "local" : cursorEnvironment,
6175
+ npmPackageChannel,
6176
+ apiUrl: resolvedApiUrl2,
6177
+ npxPathOverride: o.npxPathOverride,
6178
+ cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
6179
+ });
6180
+ codexMcp = {
6181
+ activeConfigPath: activePath,
6182
+ outcome: codexSetup.outcome,
6183
+ npxPath: codexSetup.memoraone?.command
6184
+ };
6185
+ outcomes[".codex/config.toml"] = codexSetup.outcome;
6186
+ } catch (err) {
6187
+ const message = err instanceof Error ? err.message : String(err);
6188
+ return {
6189
+ exitCode: 1,
6190
+ repoRoot,
6191
+ outcomes,
6192
+ cursorMcp,
6193
+ jetbrainsMcp,
6194
+ windsurfMcp,
6195
+ opencodeMcp,
6196
+ codexMcp,
6197
+ daemonCleanup,
6198
+ error: message
6199
+ };
6200
+ }
6201
+ }
6202
+ return {
6203
+ exitCode: 0,
6204
+ repoRoot,
6205
+ outcomes,
6206
+ cursorMcp,
6207
+ jetbrainsMcp,
6208
+ windsurfMcp,
6209
+ opencodeMcp,
6210
+ codexMcp,
6211
+ daemonCleanup
6212
+ };
4659
6213
  }
4660
6214
  async function cliSetupIdeFiles(argv, options = {}) {
4661
6215
  const {
@@ -4672,6 +6226,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
4672
6226
  workspaceRoot,
4673
6227
  apiUrl,
4674
6228
  explicitCursor,
6229
+ verbose,
4675
6230
  unknown,
4676
6231
  flagError
4677
6232
  } = parseSetupIdeFlags(argv);
@@ -4687,6 +6242,7 @@ async function cliSetupIdeFiles(argv, options = {}) {
4687
6242
  const openDeps = options.openCursorMcpSettings ?? {};
4688
6243
  const stdinIsTty = openDeps.stdinIsTty ?? process.stdin.isTTY === true;
4689
6244
  const env2 = openDeps.env ?? process.env;
6245
+ const println = options.println ?? openDeps.println ?? console.log;
4690
6246
  const promptOpenCursorSettings = shouldPromptOpenCursorMcpSettings({
4691
6247
  explicitCursor,
4692
6248
  all,
@@ -4712,55 +6268,42 @@ async function cliSetupIdeFiles(argv, options = {}) {
4712
6268
  if (result.error) {
4713
6269
  console.error(result.error);
4714
6270
  if (result.repoRoot) {
4715
- console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
6271
+ println(`[setup-ide-files] Repo root: ${result.repoRoot}`);
4716
6272
  }
4717
6273
  summarizeOutcomes(result.outcomes);
4718
6274
  return result.exitCode;
4719
6275
  }
4720
- if (result.repoRoot) {
4721
- console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
4722
- }
4723
- if (result.daemonCleanup && !result.daemonCleanup.skipped) {
4724
- logSetupIdeCleanupSummary(result.daemonCleanup);
4725
- }
4726
- if (targets.cursor && result.cursorMcp) {
4727
- logCursorMcpCliSummary(result.cursorMcp, dryRun, {
4728
- forInteractivePostSetup: promptOpenCursorSettings
4729
- });
4730
- }
4731
- if (targets.jetbrains && result.jetbrainsMcp) {
4732
- logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
6276
+ const presentation = openDeps.presentation ?? createTerminalPresentation({
6277
+ env: env2,
6278
+ stdoutIsTty: openDeps.stdoutIsTty ?? process.stdout.isTTY === true,
6279
+ color: openDeps.color,
6280
+ unicode: openDeps.unicode
6281
+ });
6282
+ const showVerbose = verbose || dryRun;
6283
+ if (showVerbose) {
6284
+ logSetupIdeFilesVerboseSuccess(
6285
+ {
6286
+ targets,
6287
+ dryRun,
6288
+ result,
6289
+ forInteractivePostSetup: promptOpenCursorSettings
6290
+ },
6291
+ println
6292
+ );
6293
+ } else {
6294
+ printSetupSuccess({ targets, presentation }, println);
4733
6295
  }
4734
6296
  if (promptOpenCursorSettings && result.repoRoot) {
4735
- const presentation = openDeps.presentation ?? createTerminalPresentation({
4736
- env: env2,
4737
- stdoutIsTty: openDeps.stdoutIsTty ?? process.stdout.isTTY === true,
4738
- color: openDeps.color,
4739
- unicode: openDeps.unicode
4740
- });
4741
- printCursorSetupCompletedSummary(
4742
- { repoRoot: result.repoRoot, outcomes: result.outcomes },
4743
- openDeps.println,
4744
- presentation
4745
- );
4746
6297
  await runOpenCursorMcpSettingsFlow({
4747
6298
  ...openDeps,
4748
6299
  stdinIsTty,
4749
6300
  env: env2,
4750
- presentation
4751
- });
4752
- } else {
4753
- summarizeOutcomes(result.outcomes);
4754
- if (dryRun) {
4755
- console.log("[setup-ide-files] Dry run: no files written.");
4756
- if (result.daemonCleanup && !result.daemonCleanup.skipped) {
4757
- console.log("[setup-ide-files] Dry run: no daemons stopped, no sockets removed.");
4758
- }
4759
- }
4760
- console.log(`[setup-ide-files] ${restartIdeInstruction(targets)}`);
6301
+ presentation,
6302
+ println
6303
+ });
4761
6304
  }
4762
6305
  if (cleanup) {
4763
- console.log("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
6306
+ println("[setup-ide-files] Running additional full-project cleanup (--cleanup)...");
4764
6307
  const cleanupResult = await runCleanup({
4765
6308
  cwd: cwd2,
4766
6309
  dryRun,
@@ -4776,85 +6319,13 @@ async function cliSetupIdeFiles(argv, options = {}) {
4776
6319
  }
4777
6320
 
4778
6321
  // src/localState/connectCommand.ts
4779
- var path20 = __toESM(require("path"), 1);
4780
- var os7 = __toESM(require("os"), 1);
4781
-
4782
- // src/config.ts
4783
- var process2 = __toESM(require("process"), 1);
4784
- var fs14 = __toESM(require("fs"), 1);
4785
- var path18 = __toESM(require("path"), 1);
4786
- var dotenv = __toESM(require("dotenv"), 1);
4787
- var import_v4 = require("zod/v4");
4788
- var dotenvPath = path18.resolve(process2.cwd(), ".env");
4789
- if (fs14.existsSync(dotenvPath)) {
4790
- try {
4791
- dotenv.config({ path: dotenvPath });
4792
- } catch (err) {
4793
- process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
4794
- }
4795
- }
4796
- var EnvSchema = import_v4.z.object({
4797
- MEMORAONE_API_URL: import_v4.z.string().url().optional(),
4798
- MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
4799
- MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
4800
- MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
4801
- MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
4802
- MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
4803
- MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains"]).optional(),
4804
- MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
4805
- MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
4806
- MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
4807
- });
4808
- var requiredEnvVars = [];
4809
- var missingEnvVars = requiredEnvVars.filter((key) => {
4810
- const value = process2.env[key];
4811
- return value === void 0 || value.trim() === "";
4812
- });
4813
- if (missingEnvVars.length > 0) {
4814
- for (const key of missingEnvVars) {
4815
- process2.stderr.write(`Missing ${key}
4816
- `);
4817
- }
4818
- process2.exit(1);
4819
- }
4820
- var parsed = EnvSchema.safeParse(process2.env);
4821
- var resolvedApiUrl = resolveApiUrl(process2.env);
4822
- if (!parsed.success) {
4823
- const formatted = parsed.error.format();
4824
- process2.stderr.write(
4825
- "[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
4826
- );
4827
- throw new Error("Config validation failed");
4828
- }
4829
- var parseBooleanFlag2 = (value, defaultValue) => {
4830
- if (value === void 0) {
4831
- return defaultValue;
4832
- }
4833
- const normalized = value.trim().toLowerCase();
4834
- if (["1", "true", "yes", "on"].includes(normalized)) {
4835
- return true;
4836
- }
4837
- if (["0", "false", "no", "off"].includes(normalized)) {
4838
- return false;
4839
- }
4840
- return defaultValue;
4841
- };
4842
- var config2 = {
4843
- apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
4844
- apiKey: parsed.data.MEMORAONE_API_KEY,
4845
- agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
4846
- agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
4847
- source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
4848
- ideType: parsed.data.MEMORAONE_IDE_TYPE,
4849
- devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
4850
- worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
4851
- heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
4852
- heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "30000", 10)
4853
- };
6322
+ var fs20 = __toESM(require("fs/promises"), 1);
6323
+ var path24 = __toESM(require("path"), 1);
6324
+ var os8 = __toESM(require("os"), 1);
4854
6325
 
4855
6326
  // src/repoFingerprint.ts
4856
- var fs15 = __toESM(require("fs"), 1);
4857
- var path19 = __toESM(require("path"), 1);
6327
+ var fs18 = __toESM(require("fs"), 1);
6328
+ var path22 = __toESM(require("path"), 1);
4858
6329
  var crypto2 = __toESM(require("crypto"), 1);
4859
6330
  var parseBooleanFlag3 = (value) => {
4860
6331
  if (!value) {
@@ -4884,16 +6355,16 @@ var sha256 = (value) => {
4884
6355
  };
4885
6356
  var resolveGitDir = (gitPath) => {
4886
6357
  try {
4887
- const stat4 = fs15.statSync(gitPath);
6358
+ const stat4 = fs18.statSync(gitPath);
4888
6359
  if (stat4.isDirectory()) {
4889
6360
  return gitPath;
4890
6361
  }
4891
6362
  if (stat4.isFile()) {
4892
- const content = fs15.readFileSync(gitPath, "utf8");
6363
+ const content = fs18.readFileSync(gitPath, "utf8");
4893
6364
  const match = content.match(/^gitdir:\s*(.+)$/m);
4894
6365
  if (match) {
4895
6366
  const gitDir = match[1].trim();
4896
- return path19.resolve(path19.dirname(gitPath), gitDir);
6367
+ return path22.resolve(path22.dirname(gitPath), gitDir);
4897
6368
  }
4898
6369
  }
4899
6370
  } catch {
@@ -4902,16 +6373,16 @@ var resolveGitDir = (gitPath) => {
4902
6373
  return null;
4903
6374
  };
4904
6375
  var findGitRoot = (start) => {
4905
- let current = path19.resolve(start);
6376
+ let current = path22.resolve(start);
4906
6377
  while (true) {
4907
- const gitPath = path19.join(current, ".git");
4908
- if (fs15.existsSync(gitPath)) {
6378
+ const gitPath = path22.join(current, ".git");
6379
+ if (fs18.existsSync(gitPath)) {
4909
6380
  const gitDir = resolveGitDir(gitPath);
4910
6381
  if (gitDir) {
4911
6382
  return { gitRoot: current, gitDir };
4912
6383
  }
4913
6384
  }
4914
- const parent = path19.dirname(current);
6385
+ const parent = path22.dirname(current);
4915
6386
  if (parent === current) {
4916
6387
  break;
4917
6388
  }
@@ -4920,9 +6391,9 @@ var findGitRoot = (start) => {
4920
6391
  return null;
4921
6392
  };
4922
6393
  var readOriginRemote = (gitDir) => {
4923
- const configPath = path19.join(gitDir, "config");
6394
+ const configPath = path22.join(gitDir, "config");
4924
6395
  try {
4925
- const content = fs15.readFileSync(configPath, "utf8");
6396
+ const content = fs18.readFileSync(configPath, "utf8");
4926
6397
  const lines = content.split(/\r?\n/);
4927
6398
  let inOrigin = false;
4928
6399
  for (const line of lines) {
@@ -4946,7 +6417,7 @@ var readOriginRemote = (gitDir) => {
4946
6417
  function resolveRepoFingerprint(cwd2) {
4947
6418
  const found = findGitRoot(cwd2);
4948
6419
  if (!found) {
4949
- const fallbackPath = path19.resolve(cwd2);
6420
+ const fallbackPath = path22.resolve(cwd2);
4950
6421
  const fingerprint2 = sha256(fallbackPath);
4951
6422
  debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
4952
6423
  return {
@@ -4968,7 +6439,7 @@ function resolveRepoFingerprint(cwd2) {
4968
6439
  source: "git-remote"
4969
6440
  };
4970
6441
  }
4971
- const fingerprint = sha256(path19.resolve(gitRoot));
6442
+ const fingerprint = sha256(path22.resolve(gitRoot));
4972
6443
  debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
4973
6444
  return {
4974
6445
  fingerprint,
@@ -4977,6 +6448,188 @@ function resolveRepoFingerprint(cwd2) {
4977
6448
  };
4978
6449
  }
4979
6450
 
6451
+ // src/localState/replaceRootLocalState.ts
6452
+ var fs19 = __toESM(require("fs/promises"), 1);
6453
+ var path23 = __toESM(require("path"), 1);
6454
+ var WORKSPACE_MAP_FILENAME = "workspaces.json";
6455
+ var FINGERPRINT_REGEX = /^[0-9a-f]{64}$/i;
6456
+ function canonicalizeWorkspaceRoot(workspaceRoot) {
6457
+ return path23.resolve(workspaceRoot);
6458
+ }
6459
+ function getWorkspaceMapPathForHome(homeDir) {
6460
+ return path23.join(getMemoraoneStateDir(homeDir), WORKSPACE_MAP_FILENAME);
6461
+ }
6462
+ async function loadWorkspaceMapFile(homeDir) {
6463
+ const filePath = getWorkspaceMapPathForHome(homeDir);
6464
+ const raw = await readJsonFile(filePath);
6465
+ if (raw == null) return {};
6466
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
6467
+ throw new Error(`[memoraone-mcp] Corrupt ${WORKSPACE_MAP_FILENAME}`);
6468
+ }
6469
+ return { ...raw };
6470
+ }
6471
+ async function saveWorkspaceMapFile(map, homeDir) {
6472
+ await writeJsonAtomic(getWorkspaceMapPathForHome(homeDir), map);
6473
+ }
6474
+ function rootsMatch(a, b) {
6475
+ return path23.resolve(a) === path23.resolve(b);
6476
+ }
6477
+ async function collectRootAssociatedBindingIds(workspaceRoot, options = {}) {
6478
+ const canonicalRoot = canonicalizeWorkspaceRoot(workspaceRoot);
6479
+ const identity = await captureRootFilesystemIdentity(canonicalRoot, options.identityDeps);
6480
+ const identityKey = filesystemIdentityKey(identity);
6481
+ const bindingIds = /* @__PURE__ */ new Set();
6482
+ const index = await loadPathIndex(options.homeDir);
6483
+ const byPathId = index.byPath[canonicalRoot];
6484
+ if (byPathId) bindingIds.add(byPathId);
6485
+ const byIdentityId = index.byIdentity[identityKey];
6486
+ if (byIdentityId) bindingIds.add(byIdentityId);
6487
+ for (const [p, id] of Object.entries(index.byPath)) {
6488
+ if (rootsMatch(p, canonicalRoot)) {
6489
+ bindingIds.add(id);
6490
+ }
6491
+ }
6492
+ const records = await listBindingRecords(options.homeDir);
6493
+ for (const record of records) {
6494
+ if (rootsMatch(record.workspaceRoot, canonicalRoot)) {
6495
+ bindingIds.add(record.repositoryBindingId);
6496
+ continue;
6497
+ }
6498
+ if (identitiesMatch(record.filesystemIdentity, identity)) {
6499
+ bindingIds.add(record.repositoryBindingId);
6500
+ }
6501
+ }
6502
+ return { canonicalRoot, identity, bindingIds };
6503
+ }
6504
+ async function snapshotAndRemoveRootLocalState(workspaceRoot, options = {}) {
6505
+ const { canonicalRoot, identity, bindingIds } = await collectRootAssociatedBindingIds(
6506
+ workspaceRoot,
6507
+ options
6508
+ );
6509
+ const identityKey = filesystemIdentityKey(identity);
6510
+ const bindingRecords = [];
6511
+ const credentials = [];
6512
+ for (const id of bindingIds) {
6513
+ const record = await readBindingRecord(id, options.homeDir);
6514
+ if (record) bindingRecords.push(record);
6515
+ try {
6516
+ const creds = await readInstallationCredentials(id, options.credentialOptions);
6517
+ if (creds) credentials.push(creds);
6518
+ } catch {
6519
+ }
6520
+ }
6521
+ const fingerprints = /* @__PURE__ */ new Set();
6522
+ for (const fp of options.extraFingerprints ?? []) {
6523
+ if (FINGERPRINT_REGEX.test(fp)) fingerprints.add(fp.toLowerCase());
6524
+ }
6525
+ for (const record of bindingRecords) {
6526
+ if (FINGERPRINT_REGEX.test(record.rootFingerprint)) {
6527
+ fingerprints.add(record.rootFingerprint.toLowerCase());
6528
+ }
6529
+ }
6530
+ const removedPathEntries = {};
6531
+ const removedIdentityEntries = {};
6532
+ await withLocalLock(
6533
+ "path-index",
6534
+ async () => {
6535
+ const index = await loadPathIndex(options.homeDir);
6536
+ for (const [p, id] of Object.entries(index.byPath)) {
6537
+ if (bindingIds.has(id) || rootsMatch(p, canonicalRoot)) {
6538
+ removedPathEntries[p] = id;
6539
+ delete index.byPath[p];
6540
+ }
6541
+ }
6542
+ for (const [key, id] of Object.entries(index.byIdentity)) {
6543
+ if (bindingIds.has(id) || key === identityKey) {
6544
+ removedIdentityEntries[key] = id;
6545
+ delete index.byIdentity[key];
6546
+ }
6547
+ }
6548
+ await savePathIndex(index, options.homeDir);
6549
+ },
6550
+ { homeDir: options.homeDir }
6551
+ );
6552
+ const removedWorkspaceMapEntries = {};
6553
+ if (fingerprints.size > 0) {
6554
+ const map = await loadWorkspaceMapFile(options.homeDir);
6555
+ let changed = false;
6556
+ for (const fp of fingerprints) {
6557
+ const key = Object.keys(map).find((k) => k.toLowerCase() === fp) ?? fp;
6558
+ if (key in map) {
6559
+ removedWorkspaceMapEntries[key] = map[key];
6560
+ delete map[key];
6561
+ changed = true;
6562
+ }
6563
+ }
6564
+ if (changed) {
6565
+ await saveWorkspaceMapFile(map, options.homeDir);
6566
+ }
6567
+ }
6568
+ for (const id of bindingIds) {
6569
+ try {
6570
+ await deleteInstallationCredentials(id, options.credentialOptions);
6571
+ } catch {
6572
+ }
6573
+ try {
6574
+ await fs19.unlink(getBindingFilePath(id, options.homeDir));
6575
+ } catch (err) {
6576
+ if (err?.code !== "ENOENT") throw err;
6577
+ }
6578
+ }
6579
+ return {
6580
+ canonicalRoot,
6581
+ identity,
6582
+ bindingIds: [...bindingIds],
6583
+ bindingRecords,
6584
+ removedPathEntries,
6585
+ removedIdentityEntries,
6586
+ removedWorkspaceMapEntries,
6587
+ credentials
6588
+ };
6589
+ }
6590
+ async function restoreRootLocalState(snapshot, options = {}) {
6591
+ for (const id of options.discardBindingIds ?? []) {
6592
+ try {
6593
+ await deleteInstallationCredentials(id, options.credentialOptions);
6594
+ } catch {
6595
+ }
6596
+ try {
6597
+ await fs19.unlink(getBindingFilePath(id, options.homeDir));
6598
+ } catch (err) {
6599
+ if (err?.code !== "ENOENT") throw err;
6600
+ }
6601
+ }
6602
+ for (const record of snapshot.bindingRecords) {
6603
+ await writeBindingRecord(record, options.homeDir);
6604
+ }
6605
+ await withLocalLock(
6606
+ "path-index",
6607
+ async () => {
6608
+ const index = await loadPathIndex(options.homeDir);
6609
+ for (const id of options.discardBindingIds ?? []) {
6610
+ for (const [p, mappedId] of Object.entries(index.byPath)) {
6611
+ if (mappedId === id) delete index.byPath[p];
6612
+ }
6613
+ for (const [key, mappedId] of Object.entries(index.byIdentity)) {
6614
+ if (mappedId === id) delete index.byIdentity[key];
6615
+ }
6616
+ }
6617
+ Object.assign(index.byPath, snapshot.removedPathEntries);
6618
+ Object.assign(index.byIdentity, snapshot.removedIdentityEntries);
6619
+ await savePathIndex(index, options.homeDir);
6620
+ },
6621
+ { homeDir: options.homeDir }
6622
+ );
6623
+ if (Object.keys(snapshot.removedWorkspaceMapEntries).length > 0) {
6624
+ const map = await loadWorkspaceMapFile(options.homeDir);
6625
+ Object.assign(map, snapshot.removedWorkspaceMapEntries);
6626
+ await saveWorkspaceMapFile(map, options.homeDir);
6627
+ }
6628
+ for (const creds of snapshot.credentials) {
6629
+ await writeInstallationCredentials(creds, options.credentialOptions);
6630
+ }
6631
+ }
6632
+
4980
6633
  // src/localState/connectCommand.ts
4981
6634
  function normalizeConnectCode(code) {
4982
6635
  const trimmed = code.trim();
@@ -4994,12 +6647,29 @@ function normalizeGitRemote(remoteUrl) {
4994
6647
  normalized = normalized.replace(/\/+$/, "");
4995
6648
  return normalized.toLowerCase() || null;
4996
6649
  }
6650
+ async function detectLegacyM1Warning2(workspaceRoot) {
6651
+ const candidate = path24.join(path24.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
6652
+ try {
6653
+ await fs20.access(candidate);
6654
+ return candidate;
6655
+ } catch {
6656
+ return void 0;
6657
+ }
6658
+ }
6659
+ async function rollbackFailedConnect(options) {
6660
+ await restoreRootLocalState(options.snapshot, {
6661
+ homeDir: options.homeDir,
6662
+ credentialOptions: options.credentialOptions,
6663
+ discardBindingIds: [options.discardBindingId]
6664
+ });
6665
+ }
4997
6666
  async function runConnectCommand(options) {
4998
6667
  const code = normalizeConnectCode(options.code);
4999
- const cwd2 = path20.resolve(options.cwd ?? process.cwd());
6668
+ const cwd2 = path24.resolve(options.cwd ?? process.cwd());
5000
6669
  const apiUrl = (options.apiUrl ?? config2.apiUrl).replace(/\/+$/, "");
5001
- const homeDir = options.homeDir ?? os7.homedir();
6670
+ const homeDir = options.homeDir ?? os8.homedir();
5002
6671
  const environment = "local";
6672
+ const verifyLocalBinding = options.verifyLocalBinding ?? resolveLocalBinding;
5003
6673
  const executionMode = await resolvePackageExecutionMode({
5004
6674
  executionMode: options.executionMode,
5005
6675
  // Only honor an explicit caller cliPath; never auto-resolve before mode detection
@@ -5020,154 +6690,240 @@ async function runConnectCommand(options) {
5020
6690
  }
5021
6691
  resolvedMode = { kind: "local", cliPath };
5022
6692
  }
5023
- const ensured = await ensureRepositoryBindingForRoot(cwd2, {
5024
- homeDir,
5025
- identityDeps: options.identityDeps,
5026
- createIfMissing: true
5027
- });
5028
- if (ensured.legacyM1WarningPath) {
6693
+ const fingerprint = resolveRepoFingerprint(cwd2);
6694
+ const legacyM1WarningPath = await detectLegacyM1Warning2(cwd2);
6695
+ if (legacyM1WarningPath) {
5029
6696
  process.stderr.write(
5030
- `[memoraone-mcp] warning: ignoring legacy ${path20.basename(ensured.legacyM1WarningPath)} at ${ensured.legacyM1WarningPath} (credentials and binding are package-managed)
6697
+ `[memoraone-mcp] warning: ignoring legacy ${path24.basename(legacyM1WarningPath)} at ${legacyM1WarningPath} (credentials and binding are package-managed)
5031
6698
  `
5032
6699
  );
5033
6700
  }
5034
- const fingerprint = resolveRepoFingerprint(cwd2);
5035
- const displayName = cwd2;
5036
- const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
5037
- const { clientRedeemKey } = await ensureClientRedeemKey(
5038
- ensured.repositoryBindingId,
5039
- options.credentialOptions
5040
- );
5041
- const now = (/* @__PURE__ */ new Date()).toISOString();
5042
- const pendingRecord = {
5043
- v: 1,
5044
- repositoryBindingId: ensured.repositoryBindingId,
5045
- workspaceRoot: cwd2,
5046
- filesystemIdentity: ensured.identity,
5047
- rootFingerprint: fingerprint.fingerprint,
5048
- displayName,
5049
- environment,
5050
- apiUrl,
5051
- normalizedGitRemote,
5052
- status: "pending",
5053
- createdAt: now,
5054
- updatedAt: now,
5055
- packageVersion: options.packageVersion ?? null,
5056
- ideType: options.ideType ?? null
5057
- };
5058
- await writeBindingRecord(pendingRecord, homeDir);
5059
- await upsertPathIndexEntry({
5060
- repositoryBindingId: ensured.repositoryBindingId,
5061
- workspaceRoot: cwd2,
5062
- identity: ensured.identity,
6701
+ const snapshot = await snapshotAndRemoveRootLocalState(cwd2, {
5063
6702
  homeDir,
5064
- previousPath: ensured.renamedFrom
6703
+ identityDeps: options.identityDeps,
6704
+ credentialOptions: options.credentialOptions,
6705
+ extraFingerprints: [fingerprint.fingerprint]
5065
6706
  });
5066
- const redeemed = await redeemLocalConnectCode(
5067
- apiUrl,
5068
- {
5069
- code,
5070
- client_redeem_key: clientRedeemKey,
5071
- repository_binding_id: ensured.repositoryBindingId,
5072
- root_fingerprint: fingerprint.fingerprint,
5073
- display_name: displayName,
6707
+ const identity = await captureRootFilesystemIdentity(cwd2, options.identityDeps);
6708
+ const repositoryBindingId = generateRepositoryBindingId();
6709
+ const createdBinding = true;
6710
+ const displayName = cwd2;
6711
+ const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
6712
+ try {
6713
+ const { clientRedeemKey } = await ensureClientRedeemKey(
6714
+ repositoryBindingId,
6715
+ options.credentialOptions
6716
+ );
6717
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6718
+ const pendingRecord = {
6719
+ v: 1,
6720
+ repositoryBindingId,
6721
+ workspaceRoot: cwd2,
6722
+ filesystemIdentity: identity,
6723
+ rootFingerprint: fingerprint.fingerprint,
6724
+ displayName,
5074
6725
  environment,
5075
- normalized_git_remote: normalizedGitRemote,
5076
- platform: ensured.identity.platform,
5077
- package_version: options.packageVersion ?? null,
5078
- ide_type: options.ideType ?? null
5079
- },
5080
- { fetchImpl: options.fetchImpl }
5081
- );
5082
- await updateInstallationCredentials(
5083
- ensured.repositoryBindingId,
5084
- {
5085
- accessToken: redeemed.access_token,
5086
- refreshToken: redeemed.refresh_token,
5087
- accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
5088
- refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
6726
+ apiUrl,
6727
+ normalizedGitRemote,
6728
+ status: "pending",
6729
+ createdAt: now,
6730
+ updatedAt: now,
6731
+ packageVersion: options.packageVersion ?? null,
6732
+ ideType: options.ideType ?? null
6733
+ };
6734
+ await writeBindingRecord(pendingRecord, homeDir);
6735
+ await upsertPathIndexEntry({
6736
+ repositoryBindingId,
6737
+ workspaceRoot: cwd2,
6738
+ identity,
6739
+ homeDir
6740
+ });
6741
+ const redeemed = await redeemLocalConnectCode(
6742
+ apiUrl,
6743
+ {
6744
+ code,
6745
+ client_redeem_key: clientRedeemKey,
6746
+ repository_binding_id: repositoryBindingId,
6747
+ root_fingerprint: fingerprint.fingerprint,
6748
+ display_name: displayName,
6749
+ environment,
6750
+ normalized_git_remote: normalizedGitRemote,
6751
+ platform: identity.platform,
6752
+ package_version: options.packageVersion ?? null,
6753
+ ide_type: options.ideType ?? null
6754
+ },
6755
+ { fetchImpl: options.fetchImpl }
6756
+ );
6757
+ await updateInstallationCredentials(
6758
+ repositoryBindingId,
6759
+ {
6760
+ accessToken: redeemed.access_token,
6761
+ refreshToken: redeemed.refresh_token,
6762
+ accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
6763
+ refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
6764
+ installationPublicId: redeemed.installation_public_id,
6765
+ projectId: redeemed.project_id
6766
+ },
6767
+ { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
6768
+ );
6769
+ const connectedRecord = {
6770
+ ...pendingRecord,
5089
6771
  installationPublicId: redeemed.installation_public_id,
5090
- projectId: redeemed.project_id
5091
- },
5092
- { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
5093
- );
5094
- const connectedRecord = {
5095
- ...pendingRecord,
5096
- installationPublicId: redeemed.installation_public_id,
5097
- projectId: redeemed.project_id,
5098
- status: "connected",
5099
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5100
- };
5101
- await writeBindingRecord(connectedRecord, homeDir);
5102
- const baseSuccess = `Connected repository binding ${ensured.repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
5103
- if (options.configureIdes !== false) {
5104
- const targets = { cursor: true, vscode: true, jetbrains: true };
5105
- const setup = options.setupIdeFiles ?? runSetupIdeFiles;
5106
- const modeSetup = setupOptionsFromPackageExecutionMode(resolvedMode);
5107
- let setupResult;
6772
+ projectId: redeemed.project_id,
6773
+ status: "connected",
6774
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6775
+ };
6776
+ await writeBindingRecord(connectedRecord, homeDir);
6777
+ let verified;
5108
6778
  try {
5109
- setupResult = await setup({
5110
- cwd: cwd2,
5111
- // Use the exact bound workspace root — do not rediscover via .git / .m1.
5112
- workspaceRoot: cwd2,
5113
- targets,
5114
- force: true,
5115
- dryRun: false,
5116
- noGitignore: true,
5117
- skipDaemonCleanup: true,
6779
+ verified = await verifyLocalBinding(cwd2, {
5118
6780
  homeDir,
5119
- // Propagate the redeemed binding API URL (backend only — not execution mode).
5120
- apiUrl,
5121
- ...modeSetup,
5122
- ...options.setupIdeOptions
6781
+ identityDeps: options.identityDeps,
6782
+ credentialOptions: options.credentialOptions
5123
6783
  });
5124
6784
  } catch (err) {
5125
- const message = err instanceof Error ? err.message : String(err);
5126
- setupResult = {
6785
+ const detail = err instanceof Error ? err.message : String(err);
6786
+ await rollbackFailedConnect({
6787
+ snapshot,
6788
+ discardBindingId: repositoryBindingId,
6789
+ homeDir,
6790
+ credentialOptions: options.credentialOptions
6791
+ });
6792
+ return {
5127
6793
  exitCode: 1,
5128
- repoRoot: cwd2,
5129
- outcomes: {},
5130
- error: message
6794
+ repositoryBindingId,
6795
+ createdBinding,
6796
+ legacyM1WarningPath,
6797
+ executionMode: resolvedMode,
6798
+ verificationError: detail,
6799
+ message: `Connect wrote binding ${repositoryBindingId} but runtime binding lookup failed for ${cwd2}: ${detail}. Previous root-specific local state was restored. IDE contracts were not updated. Check ~/.memoraone/path-index.json and ~/.memoraone/bindings/.`
5131
6800
  };
5132
6801
  }
5133
- if (setupResult.exitCode !== 0) {
5134
- const detail = setupResult.error ?? "unknown IDE setup error";
5135
- const repair = formatIdeSetupRepairHint(cwd2, resolvedMode);
6802
+ if (verified.repositoryBindingId !== repositoryBindingId) {
6803
+ const detail = `expected repository_binding_id ${repositoryBindingId}, lookup returned ${verified.repositoryBindingId}`;
6804
+ await rollbackFailedConnect({
6805
+ snapshot,
6806
+ discardBindingId: repositoryBindingId,
6807
+ homeDir,
6808
+ credentialOptions: options.credentialOptions
6809
+ });
5136
6810
  return {
5137
6811
  exitCode: 1,
5138
- repositoryBindingId: ensured.repositoryBindingId,
6812
+ repositoryBindingId,
6813
+ createdBinding,
6814
+ legacyM1WarningPath,
6815
+ executionMode: resolvedMode,
6816
+ verificationError: detail,
6817
+ message: `Connect verification failed for ${cwd2}: ${detail}. Previous root-specific local state was restored. IDE contracts were not updated.`
6818
+ };
6819
+ }
6820
+ const baseSuccess = `Connected repository binding ${repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
6821
+ if (options.configureIdes !== false) {
6822
+ const targets = {
6823
+ cursor: true,
6824
+ vscode: true,
6825
+ jetbrains: true,
6826
+ claudeCode: true,
6827
+ windsurf: true,
6828
+ opencode: true,
6829
+ codex: true
6830
+ };
6831
+ const setup = options.setupIdeFiles ?? runSetupIdeFiles;
6832
+ const modeSetup = setupOptionsFromPackageExecutionMode(resolvedMode);
6833
+ let setupResult;
6834
+ try {
6835
+ setupResult = await setup({
6836
+ cwd: cwd2,
6837
+ // Use the exact bound workspace root — do not rediscover via .git / .m1.
6838
+ workspaceRoot: cwd2,
6839
+ targets,
6840
+ force: true,
6841
+ dryRun: false,
6842
+ noGitignore: true,
6843
+ skipDaemonCleanup: true,
6844
+ homeDir,
6845
+ // Propagate the redeemed binding API URL (backend only — not execution mode).
6846
+ apiUrl,
6847
+ ...modeSetup,
6848
+ ...options.setupIdeOptions
6849
+ });
6850
+ } catch (err) {
6851
+ const message = err instanceof Error ? err.message : String(err);
6852
+ setupResult = {
6853
+ exitCode: 1,
6854
+ repoRoot: cwd2,
6855
+ outcomes: {},
6856
+ error: message
6857
+ };
6858
+ }
6859
+ if (setupResult.exitCode !== 0) {
6860
+ const detail = setupResult.error ?? "unknown IDE setup error";
6861
+ const repair = formatIdeSetupRepairHint(cwd2, resolvedMode);
6862
+ return {
6863
+ exitCode: 1,
6864
+ repositoryBindingId,
6865
+ projectId: redeemed.project_id,
6866
+ installationPublicId: redeemed.installation_public_id,
6867
+ recovered: redeemed.recovered,
6868
+ createdBinding,
6869
+ legacyM1WarningPath,
6870
+ ideSetupError: detail,
6871
+ executionMode: resolvedMode,
6872
+ message: `Repository connection exists for binding ${repositoryBindingId} (project ${redeemed.project_id}), but IDE configuration failed: ${detail}. Credentials and binding were kept. ${repair}`
6873
+ };
6874
+ }
6875
+ return {
6876
+ exitCode: 0,
6877
+ repositoryBindingId,
5139
6878
  projectId: redeemed.project_id,
5140
6879
  installationPublicId: redeemed.installation_public_id,
5141
6880
  recovered: redeemed.recovered,
5142
- createdBinding: ensured.created,
5143
- legacyM1WarningPath: ensured.legacyM1WarningPath,
5144
- ideSetupError: detail,
6881
+ createdBinding,
6882
+ legacyM1WarningPath,
5145
6883
  executionMode: resolvedMode,
5146
- message: `Repository connection exists for binding ${ensured.repositoryBindingId} (project ${redeemed.project_id}), but IDE configuration failed: ${detail}. Credentials and binding were kept. ${repair}`
6884
+ configuredTargets: targets,
6885
+ setupResult,
6886
+ message: baseSuccess
5147
6887
  };
5148
6888
  }
6889
+ return {
6890
+ exitCode: 0,
6891
+ repositoryBindingId,
6892
+ projectId: redeemed.project_id,
6893
+ installationPublicId: redeemed.installation_public_id,
6894
+ recovered: redeemed.recovered,
6895
+ createdBinding,
6896
+ legacyM1WarningPath,
6897
+ executionMode: resolvedMode,
6898
+ message: baseSuccess
6899
+ };
6900
+ } catch (err) {
6901
+ await rollbackFailedConnect({
6902
+ snapshot,
6903
+ discardBindingId: repositoryBindingId,
6904
+ homeDir,
6905
+ credentialOptions: options.credentialOptions
6906
+ });
6907
+ throw err;
5149
6908
  }
5150
- return {
5151
- exitCode: 0,
5152
- repositoryBindingId: ensured.repositoryBindingId,
5153
- projectId: redeemed.project_id,
5154
- installationPublicId: redeemed.installation_public_id,
5155
- recovered: redeemed.recovered,
5156
- createdBinding: ensured.created,
5157
- legacyM1WarningPath: ensured.legacyM1WarningPath,
5158
- executionMode: resolvedMode,
5159
- message: baseSuccess
5160
- };
5161
6909
  }
5162
6910
  function parseConnectArgv(argv) {
5163
6911
  let code;
5164
6912
  let apiUrl;
6913
+ let verbose = false;
5165
6914
  for (let i = 0; i < argv.length; i++) {
5166
6915
  const a = argv[i];
6916
+ if (a === "--verbose") {
6917
+ verbose = true;
6918
+ continue;
6919
+ }
5167
6920
  if (a === "--api-url") {
5168
6921
  const value = argv[++i];
5169
6922
  if (!value || value.startsWith("-")) {
5170
- return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
6923
+ return {
6924
+ verbose,
6925
+ error: "Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]"
6926
+ };
5171
6927
  }
5172
6928
  apiUrl = value;
5173
6929
  continue;
@@ -5175,23 +6931,29 @@ function parseConnectArgv(argv) {
5175
6931
  if (a.startsWith("--api-url=")) {
5176
6932
  const value = a.slice("--api-url=".length);
5177
6933
  if (!value) {
5178
- return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
6934
+ return {
6935
+ verbose,
6936
+ error: "Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]"
6937
+ };
5179
6938
  }
5180
6939
  apiUrl = value;
5181
6940
  continue;
5182
6941
  }
5183
6942
  if (a.startsWith("-")) {
5184
- return { error: `Unknown connect option: ${a}` };
6943
+ return { verbose, error: `Unknown connect option: ${a}` };
5185
6944
  }
5186
6945
  if (!code) {
5187
6946
  code = a;
5188
6947
  continue;
5189
6948
  }
5190
- return { error: "Usage: memoraone-mcp connect <code> [--api-url <url>]" };
6949
+ return {
6950
+ verbose,
6951
+ error: "Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]"
6952
+ };
5191
6953
  }
5192
- return { code, apiUrl };
6954
+ return { code, apiUrl, verbose };
5193
6955
  }
5194
- async function cliConnect(argv) {
6956
+ async function cliConnect(argv, options = {}) {
5195
6957
  const parsed2 = parseConnectArgv(argv);
5196
6958
  if (parsed2.error) {
5197
6959
  process.stderr.write(`${parsed2.error}
@@ -5199,19 +6961,53 @@ async function cliConnect(argv) {
5199
6961
  return 1;
5200
6962
  }
5201
6963
  if (!parsed2.code) {
5202
- process.stderr.write("Usage: memoraone-mcp connect <code> [--api-url <url>]\n");
6964
+ process.stderr.write("Usage: memoraone-mcp connect <code> [--api-url <url>] [--verbose]\n");
5203
6965
  return 1;
5204
6966
  }
6967
+ const println = options.println ?? ((line) => process.stdout.write(`${line}
6968
+ `));
5205
6969
  try {
5206
6970
  const result = await runConnectCommand({
5207
6971
  code: parsed2.code,
5208
- cwd: process.cwd(),
6972
+ cwd: options.cwd ?? process.cwd(),
5209
6973
  apiUrl: parsed2.apiUrl,
5210
- packageVersion: process.env.npm_package_version ?? null
6974
+ packageVersion: process.env.npm_package_version ?? null,
6975
+ env: options.env,
6976
+ ...options.connectOptions
5211
6977
  });
5212
6978
  if (result.exitCode === 0) {
5213
- process.stdout.write(`${result.message}
5214
- `);
6979
+ if (parsed2.verbose) {
6980
+ println(result.message);
6981
+ if (result.projectId) {
6982
+ println(`[memoraone-mcp] Project id: ${result.projectId}`);
6983
+ }
6984
+ println(`[memoraone-mcp] Repository root: ${options.cwd ?? process.cwd()}`);
6985
+ if (result.setupResult && result.configuredTargets) {
6986
+ logSetupIdeFilesVerboseSuccess(
6987
+ {
6988
+ targets: result.configuredTargets,
6989
+ dryRun: false,
6990
+ result: result.setupResult
6991
+ },
6992
+ println
6993
+ );
6994
+ }
6995
+ } else {
6996
+ const presentation = createTerminalPresentation({
6997
+ env: options.env ?? process.env,
6998
+ stdoutIsTty: options.stdoutIsTty ?? process.stdout.isTTY === true,
6999
+ color: options.color,
7000
+ unicode: options.unicode
7001
+ });
7002
+ printSetupSuccess(
7003
+ {
7004
+ repositoryConnected: true,
7005
+ targets: result.configuredTargets,
7006
+ presentation
7007
+ },
7008
+ println
7009
+ );
7010
+ }
5215
7011
  } else {
5216
7012
  process.stderr.write(`[memoraone-mcp] ${result.message}
5217
7013
  `);
@@ -5233,7 +7029,7 @@ if (args.includes("--version") || args.includes("-v")) {
5233
7029
  }
5234
7030
  if (args.includes("--help") || args.includes("-h")) {
5235
7031
  console.log(
5236
- "Usage: memoraone-mcp [--version] [--help]\n memoraone-mcp connect <code> [--api-url <url>]\n memoraone-mcp [--daemon --binding-id <mrb_\u2026> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair] [--workspace-root <path>] [--api-url <url>]\n Cursor API environment (with --cursor or --all): --local (node + built cli.cjs + local API) | --staging (npx + staging API)\n --workspace-root: configure an explicit bound workspace (skips .git discovery; for fileless Local MCP repair)\n --api-url: developer-only local/dev API endpoint (defaults from binding or http://localhost:3001; never Studio :3000)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
7032
+ "Usage: memoraone-mcp [--version] [--help]\n memoraone-mcp connect <code> [--api-url <url>] [--verbose]\n memoraone-mcp [--daemon --binding-id <mrb_\u2026> [--ide cursor|copilot-vscode|jetbrains|claude-code|windsurf|opencode|codex]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains|--claude-code|--windsurf|--opencode|--codex] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair] [--workspace-root <path>] [--api-url <url>] [--verbose]\n Cursor API environment (with --cursor or --all): --local (node + built cli.cjs + local API) | --staging (npx + staging API)\n --workspace-root: configure an explicit bound workspace (skips .git discovery; for fileless Local MCP repair)\n --api-url: developer-only local/dev API endpoint (defaults from binding or http://localhost:3001; never Studio :3000)\n --verbose: show developer diagnostics (paths, backups, daemon cleanup, handshake)\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains|claude-code|windsurf|opencode|codex] [--dry-run] [--all-projects] [--yes]"
5237
7033
  );
5238
7034
  process.exit(0);
5239
7035
  }