@memoraone/mcp 0.1.38 → 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 +2091 -398
  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.38",
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;
@@ -2421,14 +2481,14 @@ async function filterSocketPathsByIdeForCleanup(socketPaths, projectId, ide, wor
2421
2481
  const resolvedRoot = workspaceRoot ? path15.resolve(workspaceRoot) : null;
2422
2482
  const filtered = [];
2423
2483
  for (const socketPath of socketPaths) {
2424
- const basename11 = path15.basename(socketPath);
2425
- if (isLegacySocketFilename(basename11)) {
2426
- if (isSocketFilenameForProjectAndIde(basename11, normalizedProjectId, ide)) {
2484
+ const basename14 = path15.basename(socketPath);
2485
+ if (isLegacySocketFilename(basename14)) {
2486
+ if (isSocketFilenameForProjectAndIde(basename14, normalizedProjectId, ide)) {
2427
2487
  filtered.push(socketPath);
2428
2488
  }
2429
2489
  continue;
2430
2490
  }
2431
- if (isHashSocketFilename(basename11)) {
2491
+ if (isHashSocketFilename(basename14)) {
2432
2492
  const record = readBindingSidecarRecord(socketPath);
2433
2493
  if (!record || record.ideType !== ide) continue;
2434
2494
  const sameProject = record.projectId.trim().toLowerCase() === normalizedProjectId;
@@ -2808,6 +2868,179 @@ async function cliCleanup(argv) {
2808
2868
  return result.exitCode;
2809
2869
  }
2810
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
+
2811
3044
  // src/bridgeProxy.ts
2812
3045
  var defaultLog = (msg) => {
2813
3046
  process.stderr.write(`[memoraone-mcp][bridge] ${msg}
@@ -2828,9 +3061,9 @@ function summarizeJsonRpcMethod(line) {
2828
3061
  }
2829
3062
  }
2830
3063
  function connectWithRetry(socketPath, log, maxRetries, retryDelayMs, connect2) {
2831
- return new Promise((resolve17, reject) => {
3064
+ return new Promise((resolve21, reject) => {
2832
3065
  const tryConnect = (attempt) => {
2833
- connect2(socketPath).then(resolve17).catch((err) => {
3066
+ connect2(socketPath).then(resolve21).catch((err) => {
2834
3067
  if (attempt >= maxRetries) {
2835
3068
  reject(err);
2836
3069
  return;
@@ -2852,8 +3085,8 @@ async function resolveBridgeSessionBinding(params, env2 = process.env, options =
2852
3085
  ...bridgeOptions
2853
3086
  });
2854
3087
  }
2855
- async function stopDaemonsForStaleBinding(stale, env2, log) {
2856
- const ideType = resolveBindingIdeType(env2);
3088
+ async function stopDaemonsForStaleBinding(stale, env2, log, ideTypeOverride) {
3089
+ const ideType = resolveBindingIdeType(env2, ideTypeOverride);
2857
3090
  let processes;
2858
3091
  try {
2859
3092
  processes = await defaultListDaemonProcesses();
@@ -2880,9 +3113,10 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2880
3113
  `refreshed stale binding ${sessionBinding.repositoryBindingId}: project=${sessionBinding.projectId}`
2881
3114
  );
2882
3115
  }
2883
- const socketPath = getBindingSocketPath(sessionBinding, opts.env);
3116
+ const ideType = opts.ideType;
3117
+ const socketPath = getBindingSocketPath(sessionBinding, opts.env, ideType);
2884
3118
  opts.log(
2885
- `target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}`
3119
+ `target daemon socket=${socketPath} project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot}` + (ideType ? ` ideType=${ideType}` : "")
2886
3120
  );
2887
3121
  let socket;
2888
3122
  try {
@@ -2893,7 +3127,7 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2893
3127
  opts.retryDelayMs,
2894
3128
  opts.connect
2895
3129
  );
2896
- verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
3130
+ verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
2897
3131
  opts.log("reusing running daemon for session binding");
2898
3132
  return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
2899
3133
  } catch (err) {
@@ -2908,11 +3142,11 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2908
3142
  opts.log("stale daemon binding detected; replacing daemon for current local binding");
2909
3143
  const staleSidecar = readBindingSidecar(socketPath);
2910
3144
  if (staleSidecar) {
2911
- await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log);
3145
+ await stopDaemonsForStaleBinding(staleSidecar, opts.env, opts.log, ideType);
2912
3146
  }
2913
- await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log);
3147
+ await stopDaemonsForStaleBinding(sessionBinding, opts.env, opts.log, ideType);
2914
3148
  if (!bindingsMatch(binding, sessionBinding)) {
2915
- await stopDaemonsForStaleBinding(binding, opts.env, opts.log);
3149
+ await stopDaemonsForStaleBinding(binding, opts.env, opts.log, ideType);
2916
3150
  }
2917
3151
  removeDaemonSocketArtifacts(socketPath);
2918
3152
  }
@@ -2927,13 +3161,14 @@ async function connectOrSpawnDaemonForBinding(binding, opts) {
2927
3161
  opts.retryDelayMs,
2928
3162
  opts.connect
2929
3163
  );
2930
- verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env);
3164
+ verifyDaemonSidecarBinding(socketPath, sessionBinding, opts.env, ideType);
2931
3165
  return { socket, binding: sessionBinding, cacheRefreshed: reconciled.cacheRefreshed };
2932
3166
  }
2933
3167
  var BridgeDaemonRouter = class {
2934
3168
  constructor(options) {
2935
3169
  this.activeSocket = null;
2936
3170
  this.activeBinding = null;
3171
+ this.activeIdeType = void 0;
2937
3172
  this.socketLineReader = null;
2938
3173
  this.lastInitializeLine = null;
2939
3174
  this.pendingDeferredClientLines = [];
@@ -2946,19 +3181,23 @@ var BridgeDaemonRouter = class {
2946
3181
  this.maxRetries = options.maxRetries ?? 5;
2947
3182
  this.retryDelayMs = options.retryDelayMs ?? 200;
2948
3183
  this.lineReader = options.lineReader ?? null;
2949
- this.connectImpl = options.connect ?? ((socketPath) => new Promise((resolve17, reject) => {
2950
- 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));
2951
3186
  socket.on("error", reject);
2952
3187
  }));
2953
3188
  this.spawnDaemonImpl = options.spawnDaemon ?? (async (binding) => {
3189
+ const ideType = this.activeIdeType;
2954
3190
  const child = (0, import_node_child_process4.spawn)(
2955
3191
  process.execPath,
2956
- buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env),
3192
+ buildDaemonSpawnArgs(this.cliPath, binding.repositoryBindingId, this.env, ideType),
2957
3193
  {
2958
3194
  detached: true,
2959
3195
  stdio: "ignore",
2960
3196
  env: {
2961
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 } : {},
2962
3201
  // Secret-free handoff only; tokens stay in the OS keyring.
2963
3202
  MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
2964
3203
  }
@@ -2977,6 +3216,9 @@ var BridgeDaemonRouter = class {
2977
3216
  getActiveBinding() {
2978
3217
  return this.activeBinding;
2979
3218
  }
3219
+ getActiveIdeType() {
3220
+ return this.activeIdeType;
3221
+ }
2980
3222
  hasClientInitialize() {
2981
3223
  return this.clientInitializeSeen;
2982
3224
  }
@@ -2986,6 +3228,14 @@ var BridgeDaemonRouter = class {
2986
3228
  this.env,
2987
3229
  `initialize payload: ${summarizeInitializeParamsForDebug(params)}`
2988
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
+ }
2989
3239
  const bridgeOptions = getBridgeBindingResolveOptions(this.env);
2990
3240
  const initializeRoots = extractWorkspaceRootsFromInitialize(params);
2991
3241
  let rootsListUris;
@@ -3035,6 +3285,7 @@ var BridgeDaemonRouter = class {
3035
3285
  this.detachSocketReader();
3036
3286
  this.activeSocket?.destroy();
3037
3287
  this.activeSocket = null;
3288
+ this.activeIdeType = effectiveIdeType;
3038
3289
  }
3039
3290
  this.activeBinding = binding;
3040
3291
  await this.connectActiveDaemon();
@@ -3078,6 +3329,7 @@ var BridgeDaemonRouter = class {
3078
3329
  this.handshakeDeferredClientLines = [];
3079
3330
  this.clientInitializeSeen = false;
3080
3331
  this.activeBinding = null;
3332
+ this.activeIdeType = void 0;
3081
3333
  }
3082
3334
  async connectActiveDaemon() {
3083
3335
  if (!this.activeBinding) {
@@ -3090,7 +3342,8 @@ var BridgeDaemonRouter = class {
3090
3342
  maxRetries: this.maxRetries,
3091
3343
  retryDelayMs: this.retryDelayMs,
3092
3344
  connect: this.connectImpl,
3093
- spawnDaemon: this.spawnDaemonImpl
3345
+ spawnDaemon: this.spawnDaemonImpl,
3346
+ ideType: this.activeIdeType
3094
3347
  });
3095
3348
  this.activeBinding = connected.binding;
3096
3349
  this.activeSocket = connected.socket;
@@ -3209,37 +3462,17 @@ async function runBridgeProxy(options) {
3209
3462
  }
3210
3463
 
3211
3464
  // src/setupIdeFiles.ts
3212
- var fs13 = __toESM(require("fs/promises"), 1);
3213
- var os6 = __toESM(require("os"), 1);
3214
- var path17 = __toESM(require("path"), 1);
3215
- 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");
3216
3469
 
3217
3470
  // src/jetbrainsMcpConfig.ts
3218
- var fs12 = __toESM(require("fs/promises"), 1);
3471
+ var fs13 = __toESM(require("fs/promises"), 1);
3219
3472
  var os5 = __toESM(require("os"), 1);
3220
- var path16 = __toESM(require("path"), 1);
3473
+ var path17 = __toESM(require("path"), 1);
3221
3474
  var import_node_crypto4 = require("crypto");
3222
3475
  var import_node_child_process5 = require("child_process");
3223
-
3224
- // src/configUtils.ts
3225
- var DEFAULT_API_URL = "http://localhost:3001";
3226
- var DEV_API_URL = "http://localhost:3001";
3227
- function resolveApiUrl(env2) {
3228
- const explicitUrl = env2.MEMORAONE_API_URL?.trim();
3229
- if (explicitUrl) {
3230
- return explicitUrl;
3231
- }
3232
- const aliasUrl = env2.MEMORA_API_URL?.trim();
3233
- if (aliasUrl) {
3234
- return aliasUrl;
3235
- }
3236
- if (env2.MEMORAONE_DEV_MODE === "1") {
3237
- return DEV_API_URL;
3238
- }
3239
- return DEFAULT_API_URL;
3240
- }
3241
-
3242
- // src/jetbrainsMcpConfig.ts
3243
3476
  var JETBRAINS_DEBUG_ENV_VARS = [
3244
3477
  "MEMORAONE_DEBUG_INIT",
3245
3478
  "MEMORAONE_DEBUG_MINIMAL_TOOLS",
@@ -3251,7 +3484,7 @@ function stripLeadingLineComments2(text) {
3251
3484
  }
3252
3485
  async function pathExists3(filePath) {
3253
3486
  try {
3254
- await fs12.access(filePath);
3487
+ await fs13.access(filePath);
3255
3488
  return true;
3256
3489
  } catch {
3257
3490
  return false;
@@ -3262,12 +3495,12 @@ function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
3262
3495
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
3263
3496
  }
3264
3497
  function getJetBrainsGlobalMcpConfigPath(homeDir) {
3265
- return path16.join(homeDir, ".ai", "mcp", "mcp.json");
3498
+ return path17.join(homeDir, ".ai", "mcp", "mcp.json");
3266
3499
  }
3267
3500
  function getJetBrainsProjectMcpConfigPaths(repoRoot) {
3268
3501
  return [
3269
- { kind: "project-ai", path: path16.join(repoRoot, ".ai", "mcp", "mcp.json") },
3270
- { 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") }
3271
3504
  ];
3272
3505
  }
3273
3506
  function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
@@ -3278,7 +3511,7 @@ function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
3278
3511
  }
3279
3512
  async function isZeroByteConfigFile(filePath) {
3280
3513
  if (!await pathExists3(filePath)) return false;
3281
- const stat4 = await fs12.stat(filePath);
3514
+ const stat4 = await fs13.stat(filePath);
3282
3515
  return stat4.size === 0;
3283
3516
  }
3284
3517
  function buildMemoraoneJetBrainsMcpServer(options) {
@@ -3289,7 +3522,7 @@ function buildMemoraoneJetBrainsMcpServer(options) {
3289
3522
  apiUrl: options.apiUrl ?? (environment === "local" ? DEV_API_URL : void 0)
3290
3523
  }),
3291
3524
  MEMORAONE_IDE_TYPE: "jetbrains",
3292
- [MEMORAONE_WORKSPACE_ROOT_ENV]: path16.resolve(options.workspaceRoot)
3525
+ [MEMORAONE_WORKSPACE_ROOT_ENV]: path17.resolve(options.workspaceRoot)
3293
3526
  };
3294
3527
  if (environment === "local" || options.devMode) {
3295
3528
  env2.MEMORAONE_DEV_MODE = "1";
@@ -3311,18 +3544,18 @@ function mergeJetBrainsMcpConfigObject(existing, memoraone) {
3311
3544
  return { ...base, mcpServers };
3312
3545
  }
3313
3546
  async function writeJetBrainsMcpJsonAtomic(filePath, content) {
3314
- const dir = path16.dirname(filePath);
3315
- await fs12.mkdir(dir, { recursive: true });
3316
- const tmpPath = path16.join(
3547
+ const dir = path17.dirname(filePath);
3548
+ await fs13.mkdir(dir, { recursive: true });
3549
+ const tmpPath = path17.join(
3317
3550
  dir,
3318
- `.${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`
3319
3552
  );
3320
3553
  try {
3321
- await fs12.writeFile(tmpPath, content, "utf8");
3322
- await fs12.rename(tmpPath, filePath);
3554
+ await fs13.writeFile(tmpPath, content, "utf8");
3555
+ await fs13.rename(tmpPath, filePath);
3323
3556
  } catch (err) {
3324
3557
  try {
3325
- await fs12.unlink(tmpPath);
3558
+ await fs13.unlink(tmpPath);
3326
3559
  } catch {
3327
3560
  }
3328
3561
  throw err;
@@ -3363,13 +3596,13 @@ function validateJetBrainsMcpConfig(parsed2, expected) {
3363
3596
  }
3364
3597
  }
3365
3598
  async function readJsonConfig(filePath) {
3366
- const raw = await fs12.readFile(filePath, "utf8");
3599
+ const raw = await fs13.readFile(filePath, "utf8");
3367
3600
  if (raw.trim() === "") return null;
3368
3601
  return JSON.parse(stripLeadingLineComments2(raw));
3369
3602
  }
3370
3603
  async function backupConfigFile(filePath) {
3371
3604
  const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
3372
- await fs12.copyFile(filePath, backupPath);
3605
+ await fs13.copyFile(filePath, backupPath);
3373
3606
  return backupPath;
3374
3607
  }
3375
3608
  async function repairZeroByteConfigFile(filePath, dryRun) {
@@ -3380,7 +3613,7 @@ async function repairZeroByteConfigFile(filePath, dryRun) {
3380
3613
  return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
3381
3614
  }
3382
3615
  const backupPath = await backupConfigFile(filePath);
3383
- await fs12.unlink(filePath);
3616
+ await fs13.unlink(filePath);
3384
3617
  return { repaired: true, backupPath };
3385
3618
  }
3386
3619
  function configHasMemoraone(parsed2) {
@@ -3411,24 +3644,24 @@ async function removeMemoraoneFromProjectConfig(options) {
3411
3644
  delete mcpServers.memoraone;
3412
3645
  const hasOtherServers = Object.keys(mcpServers).length > 0;
3413
3646
  if (!hasOtherServers) {
3414
- await fs12.unlink(configPath);
3647
+ await fs13.unlink(configPath);
3415
3648
  return { changed: true, backupPath };
3416
3649
  }
3417
3650
  const next = { ...parsed2, mcpServers };
3418
- await fs12.mkdir(path16.dirname(configPath), { recursive: true });
3419
- 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");
3420
3653
  return { changed: true, backupPath };
3421
3654
  }
3422
3655
  async function resolveLocalCliPathAsync() {
3423
- 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();
3424
3657
  const candidates = [
3425
- path16.join(here, "cli.cjs"),
3426
- path16.join(here, "..", "dist", "cli.cjs"),
3427
- 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")
3428
3661
  ];
3429
3662
  for (const candidate of candidates) {
3430
3663
  if (await pathExists3(candidate)) {
3431
- return path16.resolve(candidate);
3664
+ return path17.resolve(candidate);
3432
3665
  }
3433
3666
  }
3434
3667
  return null;
@@ -3485,7 +3718,7 @@ function formatOptionalJetBrainsHandshakeUnavailableDetail(detail) {
3485
3718
  async function verifyJetBrainsMcpHandshake(options) {
3486
3719
  const timeoutMs = options.timeoutMs ?? 15e3;
3487
3720
  const { server } = options;
3488
- return new Promise((resolve17) => {
3721
+ return new Promise((resolve21) => {
3489
3722
  let settled = false;
3490
3723
  const finish = (ok, detail, optionalUnavailable) => {
3491
3724
  if (settled) return;
@@ -3495,7 +3728,7 @@ async function verifyJetBrainsMcpHandshake(options) {
3495
3728
  child.kill();
3496
3729
  } catch {
3497
3730
  }
3498
- resolve17({ ok, detail, optionalUnavailable });
3731
+ resolve21({ ok, detail, optionalUnavailable });
3499
3732
  };
3500
3733
  const child = (0, import_node_child_process5.spawn)(server.command, [...server.args], {
3501
3734
  env: { ...process.env, ...server.env },
@@ -3569,7 +3802,7 @@ async function verifyJetBrainsMcpHandshake(options) {
3569
3802
  async function setupJetBrainsMcpConfig(options) {
3570
3803
  const homeDir = options.homeDir ?? os5.homedir();
3571
3804
  const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
3572
- const workspaceRoot = path16.resolve(options.repoRoot);
3805
+ const workspaceRoot = path17.resolve(options.repoRoot);
3573
3806
  const repairActions = [];
3574
3807
  const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
3575
3808
  for (const location of allLocations) {
@@ -3642,7 +3875,7 @@ async function setupJetBrainsMcpConfig(options) {
3642
3875
  backupPath = await backupConfigFile(globalPath);
3643
3876
  }
3644
3877
  await writeJetBrainsMcpJsonAtomic(globalPath, body);
3645
- const verifyRaw = await fs12.readFile(globalPath, "utf8");
3878
+ const verifyRaw = await fs13.readFile(globalPath, "utf8");
3646
3879
  const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
3647
3880
  validateJetBrainsMcpConfig(verifyParsed, memoraone);
3648
3881
  const outcome = existed ? "updated" : "created";
@@ -3710,51 +3943,1010 @@ function logJetBrainsMcpCliSummary(info, dryRun, println = console.log) {
3710
3943
  );
3711
3944
  }
3712
3945
 
3713
- // src/openCursorMcpSettings.ts
3714
- var import_node_child_process6 = require("child_process");
3715
- var readline4 = __toESM(require("readline/promises"), 1);
3716
- var import_node_util4 = require("util");
3717
-
3718
- // src/terminalPresentation.ts
3719
- var ANSI = {
3720
- reset: "\x1B[0m",
3721
- bold: "\x1B[1m",
3722
- dim: "\x1B[2m",
3723
- green: "\x1B[32m",
3724
- yellow: "\x1B[33m",
3725
- cyan: "\x1B[36m"
3726
- };
3727
- function isCiLikeEnv(env2 = process.env) {
3728
- if (env2.CI === "true" || env2.CI === "1") return true;
3729
- if (env2.GITHUB_ACTIONS === "true" || env2.GITHUB_ACTIONS === "1") return true;
3730
- if (env2.GITLAB_CI === "true" || env2.GITLAB_CI === "1") return true;
3731
- if (env2.CIRCLECI === "true" || env2.CIRCLECI === "1") return true;
3732
- if (env2.BUILDKITE === "true" || env2.BUILDKITE === "1") return true;
3733
- 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);
3734
3957
  return true;
3958
+ } catch {
3959
+ return false;
3735
3960
  }
3736
- return false;
3737
3961
  }
3738
- function shouldEnableAnsiColor(opts = {}) {
3739
- if (typeof opts.color === "boolean") return opts.color;
3740
- const env2 = opts.env ?? process.env;
3741
- if (env2.NO_COLOR !== void 0) return false;
3742
- if (isCiLikeEnv(env2)) return false;
3743
- const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
3744
- return tty;
3962
+ function getWindsurfGlobalMcpConfigPath(homeDir) {
3963
+ return path18.join(homeDir, ".config", "devin", "mcp_config.json");
3745
3964
  }
3746
- function shouldUseUnicodeSymbols(opts = {}) {
3747
- if (typeof opts.unicode === "boolean") return opts.unicode;
3748
- const env2 = opts.env ?? process.env;
3749
- const tty = opts.stdoutIsTty ?? process.stdout.isTTY === true;
3750
- if (!tty) return false;
3751
- if (env2.TERM === "dumb") return false;
3752
- if (process.platform === "win32") {
3753
- return Boolean(
3754
- 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"
3965
+ function getWindsurfLegacyMcpConfigPath(homeDir) {
3966
+ return path18.join(homeDir, ".codeium", "windsurf", "mcp_config.json");
3967
+ }
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"
3755
3988
  );
3756
3989
  }
3757
- return true;
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;
3758
4950
  }
3759
4951
  function paint(enabled, code, text) {
3760
4952
  if (!enabled || text === "") return text;
@@ -3902,6 +5094,10 @@ async function runOpenCursorMcpSettingsFlow(deps = {}) {
3902
5094
  }
3903
5095
 
3904
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.";
3905
5101
  var RESTART_LINE = "Restart your IDEs to finish setup.";
3906
5102
  function formatSetupSuccessLines(opts = {}) {
3907
5103
  const tp = opts.presentation ?? createTerminalPresentation(opts.presentationOptions ?? { color: false, unicode: true });
@@ -3919,13 +5115,39 @@ function formatSetupSuccessLines(opts = {}) {
3919
5115
  if (targets.jetbrains) {
3920
5116
  lines.push(tp.checkLine("JetBrains configured"));
3921
5117
  }
3922
- const hasIde = Boolean(targets.cursor || targets.vscode || targets.jetbrains);
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
+ );
3923
5133
  lines.push("");
3924
5134
  lines.push(tp.checkLine("MemoraOne is ready"));
3925
5135
  if (hasIde) {
3926
5136
  lines.push("");
3927
5137
  lines.push(RESTART_LINE);
3928
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
+ }
3929
5151
  return lines;
3930
5152
  }
3931
5153
  function printSetupSuccess(opts, println = console.log) {
@@ -3942,13 +5164,14 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
3942
5164
  MEMORAONE_API_URL: resolveIdeApiUrl({ environment, apiUrl: options.apiUrl }),
3943
5165
  MEMORAONE_IDE_TYPE: ideType
3944
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
+ }
3945
5171
  if (environment === "local") {
3946
5172
  if (!options.cliPath) {
3947
5173
  throw new Error("[setup-ide-files] Local VS Code MCP config requires a built CLI path.");
3948
5174
  }
3949
- if (options.workspaceRoot !== void 0) {
3950
- env2[MEMORAONE_WORKSPACE_ROOT_ENV] = path17.resolve(options.workspaceRoot);
3951
- }
3952
5175
  return {
3953
5176
  command: "node",
3954
5177
  args: [options.cliPath],
@@ -3963,15 +5186,15 @@ function buildMemoraoneMcpServer(ideType, options = {}) {
3963
5186
  };
3964
5187
  }
3965
5188
  function assertUnderRepoRoot(repoRoot, absPath) {
3966
- const normRoot = path17.resolve(repoRoot) + path17.sep;
3967
- const normPath = path17.resolve(absPath);
3968
- 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)) {
3969
5192
  throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
3970
5193
  }
3971
5194
  }
3972
- async function pathExists4(filePath) {
5195
+ async function pathExists7(filePath) {
3973
5196
  try {
3974
- await fs13.access(filePath);
5197
+ await fs17.access(filePath);
3975
5198
  return true;
3976
5199
  } catch {
3977
5200
  return false;
@@ -3981,21 +5204,21 @@ async function ensureGitignoreMemoraone(_repoRoot, _opts) {
3981
5204
  return "skipped";
3982
5205
  }
3983
5206
  async function findRepoRoot(startDir) {
3984
- let current = path17.resolve(startDir);
3985
- const root = path17.parse(current).root;
5207
+ let current = path21.resolve(startDir);
5208
+ const root = path21.parse(current).root;
3986
5209
  while (true) {
3987
- const gitPath = path17.join(current, ".git");
3988
- const m1Path = path17.join(current, "memoraone.m1");
3989
- 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)) {
3990
5213
  return current;
3991
5214
  }
3992
5215
  if (current === root) {
3993
5216
  return null;
3994
5217
  }
3995
- current = path17.dirname(current);
5218
+ current = path21.dirname(current);
3996
5219
  }
3997
5220
  }
3998
- function stripLeadingLineComments3(text) {
5221
+ function stripLeadingLineComments4(text) {
3999
5222
  return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
4000
5223
  }
4001
5224
  function cursorRuleBody() {
@@ -4047,18 +5270,18 @@ function buildVscodeMcpJsonBody(existing, options = {}) {
4047
5270
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
4048
5271
  }
4049
5272
  async function writeSharedMcpJsonAtomic(filePath, content) {
4050
- const dir = path17.dirname(filePath);
4051
- await fs13.mkdir(dir, { recursive: true });
4052
- const tmpPath = path17.join(
5273
+ const dir = path21.dirname(filePath);
5274
+ await fs17.mkdir(dir, { recursive: true });
5275
+ const tmpPath = path21.join(
4053
5276
  dir,
4054
- `.${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`
4055
5278
  );
4056
5279
  try {
4057
- await fs13.writeFile(tmpPath, content, "utf8");
4058
- await fs13.rename(tmpPath, filePath);
5280
+ await fs17.writeFile(tmpPath, content, "utf8");
5281
+ await fs17.rename(tmpPath, filePath);
4059
5282
  } catch (err) {
4060
5283
  try {
4061
- await fs13.unlink(tmpPath);
5284
+ await fs17.unlink(tmpPath);
4062
5285
  } catch {
4063
5286
  }
4064
5287
  throw err;
@@ -4077,43 +5300,79 @@ function buildCursorMcpJsonBody(existing, writeOptions) {
4077
5300
  const merged = mergeCursorRepoMcpConfigObject(existing, writeOptions);
4078
5301
  return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
4079
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
+ }
4080
5339
  async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
4081
- const abs = path17.join(repoRoot, relPath);
5340
+ const abs = path21.join(repoRoot, relPath);
4082
5341
  assertUnderRepoRoot(repoRoot, abs);
4083
5342
  let prior = "";
4084
5343
  let existed = false;
4085
5344
  try {
4086
- prior = await fs13.readFile(abs, "utf8");
5345
+ prior = await fs17.readFile(abs, "utf8");
4087
5346
  existed = true;
4088
5347
  } catch (err) {
4089
5348
  if (err?.code !== "ENOENT") throw err;
4090
5349
  }
4091
5350
  if (!existed) {
4092
5351
  if (opts.dryRun) return "created";
4093
- await fs13.mkdir(path17.dirname(abs), { recursive: true });
4094
- await fs13.writeFile(abs, fullContent, "utf8");
5352
+ await fs17.mkdir(path21.dirname(abs), { recursive: true });
5353
+ await fs17.writeFile(abs, fullContent, "utf8");
4095
5354
  return "created";
4096
5355
  }
4097
5356
  if (prior.includes(MANAGED_MARKER)) {
4098
5357
  if (prior === fullContent) return "skipped";
4099
5358
  if (opts.dryRun) return "updated";
4100
- await fs13.mkdir(path17.dirname(abs), { recursive: true });
4101
- await fs13.writeFile(abs, fullContent, "utf8");
5359
+ await fs17.mkdir(path21.dirname(abs), { recursive: true });
5360
+ await fs17.writeFile(abs, fullContent, "utf8");
4102
5361
  return "updated";
4103
5362
  }
4104
5363
  if (!opts.force) return "skipped-untracked";
4105
5364
  if (opts.dryRun) return "updated";
4106
- await fs13.mkdir(path17.dirname(abs), { recursive: true });
4107
- await fs13.writeFile(abs, fullContent, "utf8");
5365
+ await fs17.mkdir(path21.dirname(abs), { recursive: true });
5366
+ await fs17.writeFile(abs, fullContent, "utf8");
4108
5367
  return "updated";
4109
5368
  }
4110
5369
  async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
4111
- const abs = path17.join(repoRoot, relPath);
5370
+ const abs = path21.join(repoRoot, relPath);
4112
5371
  assertUnderRepoRoot(repoRoot, abs);
4113
5372
  let raw = "";
4114
5373
  let existed = false;
4115
5374
  try {
4116
- raw = await fs13.readFile(abs, "utf8");
5375
+ raw = await fs17.readFile(abs, "utf8");
4117
5376
  existed = true;
4118
5377
  } catch (err) {
4119
5378
  if (err?.code !== "ENOENT") throw err;
@@ -4128,7 +5387,7 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
4128
5387
  if (!managed && !opts.force) return "skipped-untracked";
4129
5388
  let parsed2;
4130
5389
  try {
4131
- parsed2 = JSON.parse(stripLeadingLineComments3(raw));
5390
+ parsed2 = JSON.parse(stripLeadingLineComments4(raw));
4132
5391
  } catch {
4133
5392
  throw new SharedMcpJsonParseError(abs);
4134
5393
  }
@@ -4145,6 +5404,10 @@ function parseSetupIdeFlags(argv) {
4145
5404
  let cursor = false;
4146
5405
  let vscode = false;
4147
5406
  let jetbrains = false;
5407
+ let claudeCode = false;
5408
+ let windsurf = false;
5409
+ let opencode = false;
5410
+ let codex = false;
4148
5411
  let all = false;
4149
5412
  let force = false;
4150
5413
  let dryRun = false;
@@ -4164,6 +5427,10 @@ function parseSetupIdeFlags(argv) {
4164
5427
  if (a === "--cursor") cursor = true;
4165
5428
  else if (a === "--vscode") vscode = true;
4166
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;
4167
5434
  else if (a === "--all") all = true;
4168
5435
  else if (a === "--force") force = true;
4169
5436
  else if (a === "--dry-run") dryRun = true;
@@ -4204,12 +5471,20 @@ function parseSetupIdeFlags(argv) {
4204
5471
  }
4205
5472
  } else if (a.startsWith("-")) unknown.push(a);
4206
5473
  }
4207
- const specific = cursor || vscode || jetbrains;
5474
+ const specific = cursor || vscode || jetbrains || claudeCode || windsurf || opencode || codex;
4208
5475
  let targets;
4209
5476
  if (all || !specific) {
4210
- 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
+ };
4211
5486
  } else {
4212
- targets = { cursor, vscode, jetbrains };
5487
+ targets = { cursor, vscode, jetbrains, claudeCode, windsurf, opencode, codex };
4213
5488
  }
4214
5489
  if (!flagError && local && staging) {
4215
5490
  flagError = "[setup-ide-files] --local and --staging are mutually exclusive.";
@@ -4253,6 +5528,15 @@ function logSetupIdeFilesVerboseSuccess(opts, println = console.log) {
4253
5528
  if (targets.jetbrains && result.jetbrainsMcp) {
4254
5529
  logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun, println);
4255
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
+ }
4256
5540
  summarizeOutcomes(result.outcomes, println);
4257
5541
  if (dryRun) {
4258
5542
  println("[setup-ide-files] Dry run: no files written.");
@@ -4313,10 +5597,14 @@ function ideTypesFromSetupTargets(targets) {
4313
5597
  if (targets.cursor) ides.push("cursor");
4314
5598
  if (targets.vscode) ides.push("copilot-vscode");
4315
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");
4316
5604
  return ides;
4317
5605
  }
4318
5606
  function setupTargetsAllIdes(targets) {
4319
- return targets.cursor && targets.vscode && targets.jetbrains;
5607
+ return targets.cursor && targets.vscode && targets.jetbrains && targets.claudeCode && targets.windsurf && targets.opencode && targets.codex;
4320
5608
  }
4321
5609
  function aggregateCleanupResults(results) {
4322
5610
  const killedPids = /* @__PURE__ */ new Set();
@@ -4436,8 +5724,18 @@ function restartIdeInstruction(targets) {
4436
5724
  if (targets.cursor) names.push("Cursor");
4437
5725
  if (targets.vscode) names.push("VS Code");
4438
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");
4439
5731
  if (names.length === 0) return "Fully quit your IDE and reopen this repo for MCP changes to take effect.";
4440
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
+ }
4441
5739
  return `Fully quit ${names[0]} and reopen this repo for MCP changes to take effect.`;
4442
5740
  }
4443
5741
  const last = names.pop();
@@ -4447,7 +5745,10 @@ async function runSetupIdeFiles(o) {
4447
5745
  const outcomes = {};
4448
5746
  let cursorMcp;
4449
5747
  let jetbrainsMcp;
4450
- 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);
4451
5752
  if (!repoRoot) {
4452
5753
  return {
4453
5754
  exitCode: 1,
@@ -4669,12 +5970,203 @@ description: MemoraOne MCP \u2014 IDE agent instructions
4669
5970
  { force: o.force, dryRun: o.dryRun }
4670
5971
  );
4671
5972
  try {
4672
- const homeDir = o.jetbrainsHomeDir ?? os6.homedir();
5973
+ const homeDir = o.jetbrainsHomeDir ?? os7.homedir();
4673
5974
  const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
4674
5975
  const jetbrainsSetup = await setupJetBrainsMcpConfig({
4675
5976
  homeDir,
4676
5977
  repoRoot,
4677
- globalConfigPath: activePath,
5978
+ globalConfigPath: activePath,
5979
+ dryRun: o.dryRun,
5980
+ // Local dogfood: --dev / local environment → node + built CLI.
5981
+ // Published staging/production: npx + channel-specific package spec.
5982
+ devMode: localOrDev,
5983
+ environment: localOrDev ? "local" : cursorEnvironment,
5984
+ npmPackageChannel,
5985
+ apiUrl: resolvedApiUrl2,
5986
+ repair: o.repair ?? false,
5987
+ verify: o.verifyHandshake ?? !o.dryRun,
5988
+ npxPathOverride: o.npxPathOverride,
5989
+ cliPathOverride: o.cliPathOverride
5990
+ });
5991
+ jetbrainsMcp = {
5992
+ activeConfigPath: activePath,
5993
+ outcome: jetbrainsSetup.outcome,
5994
+ npxPath: jetbrainsSetup.memoraone?.command,
5995
+ backupPath: jetbrainsSetup.backupPath,
5996
+ repairActions: jetbrainsSetup.repairActions,
5997
+ verifyOk: jetbrainsSetup.verifyOk,
5998
+ verifyDetail: jetbrainsSetup.verifyDetail
5999
+ };
6000
+ outcomes[`jetbrains-global:${activePath}`] = jetbrainsSetup.outcome;
6001
+ } catch (err) {
6002
+ const message = err instanceof Error ? err.message : String(err);
6003
+ return {
6004
+ exitCode: 1,
6005
+ repoRoot,
6006
+ outcomes,
6007
+ cursorMcp,
6008
+ error: message
6009
+ };
6010
+ }
6011
+ }
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,
4678
6170
  dryRun: o.dryRun,
4679
6171
  // Local dogfood: --dev / local environment → node + built CLI.
4680
6172
  // Published staging/production: npx + channel-specific package spec.
@@ -4682,21 +6174,15 @@ description: MemoraOne MCP \u2014 IDE agent instructions
4682
6174
  environment: localOrDev ? "local" : cursorEnvironment,
4683
6175
  npmPackageChannel,
4684
6176
  apiUrl: resolvedApiUrl2,
4685
- repair: o.repair ?? false,
4686
- verify: o.verifyHandshake ?? !o.dryRun,
4687
6177
  npxPathOverride: o.npxPathOverride,
4688
- cliPathOverride: o.cliPathOverride
6178
+ cliPathOverride: o.cliPathOverride ?? o.cursorLocalCliPathOverride
4689
6179
  });
4690
- jetbrainsMcp = {
6180
+ codexMcp = {
4691
6181
  activeConfigPath: activePath,
4692
- outcome: jetbrainsSetup.outcome,
4693
- npxPath: jetbrainsSetup.memoraone?.command,
4694
- backupPath: jetbrainsSetup.backupPath,
4695
- repairActions: jetbrainsSetup.repairActions,
4696
- verifyOk: jetbrainsSetup.verifyOk,
4697
- verifyDetail: jetbrainsSetup.verifyDetail
6182
+ outcome: codexSetup.outcome,
6183
+ npxPath: codexSetup.memoraone?.command
4698
6184
  };
4699
- outcomes[`jetbrains-global:${activePath}`] = jetbrainsSetup.outcome;
6185
+ outcomes[".codex/config.toml"] = codexSetup.outcome;
4700
6186
  } catch (err) {
4701
6187
  const message = err instanceof Error ? err.message : String(err);
4702
6188
  return {
@@ -4704,11 +6190,26 @@ description: MemoraOne MCP \u2014 IDE agent instructions
4704
6190
  repoRoot,
4705
6191
  outcomes,
4706
6192
  cursorMcp,
6193
+ jetbrainsMcp,
6194
+ windsurfMcp,
6195
+ opencodeMcp,
6196
+ codexMcp,
6197
+ daemonCleanup,
4707
6198
  error: message
4708
6199
  };
4709
6200
  }
4710
6201
  }
4711
- return { exitCode: 0, repoRoot, outcomes, cursorMcp, jetbrainsMcp, daemonCleanup };
6202
+ return {
6203
+ exitCode: 0,
6204
+ repoRoot,
6205
+ outcomes,
6206
+ cursorMcp,
6207
+ jetbrainsMcp,
6208
+ windsurfMcp,
6209
+ opencodeMcp,
6210
+ codexMcp,
6211
+ daemonCleanup
6212
+ };
4712
6213
  }
4713
6214
  async function cliSetupIdeFiles(argv, options = {}) {
4714
6215
  const {
@@ -4818,85 +6319,13 @@ async function cliSetupIdeFiles(argv, options = {}) {
4818
6319
  }
4819
6320
 
4820
6321
  // src/localState/connectCommand.ts
4821
- var path20 = __toESM(require("path"), 1);
4822
- var os7 = __toESM(require("os"), 1);
4823
-
4824
- // src/config.ts
4825
- var process2 = __toESM(require("process"), 1);
4826
- var fs14 = __toESM(require("fs"), 1);
4827
- var path18 = __toESM(require("path"), 1);
4828
- var dotenv = __toESM(require("dotenv"), 1);
4829
- var import_v4 = require("zod/v4");
4830
- var dotenvPath = path18.resolve(process2.cwd(), ".env");
4831
- if (fs14.existsSync(dotenvPath)) {
4832
- try {
4833
- dotenv.config({ path: dotenvPath });
4834
- } catch (err) {
4835
- process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
4836
- }
4837
- }
4838
- var EnvSchema = import_v4.z.object({
4839
- MEMORAONE_API_URL: import_v4.z.string().url().optional(),
4840
- MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
4841
- MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
4842
- MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
4843
- MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
4844
- MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
4845
- MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains"]).optional(),
4846
- MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
4847
- MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
4848
- MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
4849
- });
4850
- var requiredEnvVars = [];
4851
- var missingEnvVars = requiredEnvVars.filter((key) => {
4852
- const value = process2.env[key];
4853
- return value === void 0 || value.trim() === "";
4854
- });
4855
- if (missingEnvVars.length > 0) {
4856
- for (const key of missingEnvVars) {
4857
- process2.stderr.write(`Missing ${key}
4858
- `);
4859
- }
4860
- process2.exit(1);
4861
- }
4862
- var parsed = EnvSchema.safeParse(process2.env);
4863
- var resolvedApiUrl = resolveApiUrl(process2.env);
4864
- if (!parsed.success) {
4865
- const formatted = parsed.error.format();
4866
- process2.stderr.write(
4867
- "[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
4868
- );
4869
- throw new Error("Config validation failed");
4870
- }
4871
- var parseBooleanFlag2 = (value, defaultValue) => {
4872
- if (value === void 0) {
4873
- return defaultValue;
4874
- }
4875
- const normalized = value.trim().toLowerCase();
4876
- if (["1", "true", "yes", "on"].includes(normalized)) {
4877
- return true;
4878
- }
4879
- if (["0", "false", "no", "off"].includes(normalized)) {
4880
- return false;
4881
- }
4882
- return defaultValue;
4883
- };
4884
- var config2 = {
4885
- apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
4886
- apiKey: parsed.data.MEMORAONE_API_KEY,
4887
- agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
4888
- agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
4889
- source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
4890
- ideType: parsed.data.MEMORAONE_IDE_TYPE,
4891
- devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
4892
- worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
4893
- heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
4894
- heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "30000", 10)
4895
- };
6322
+ var fs20 = __toESM(require("fs/promises"), 1);
6323
+ var path24 = __toESM(require("path"), 1);
6324
+ var os8 = __toESM(require("os"), 1);
4896
6325
 
4897
6326
  // src/repoFingerprint.ts
4898
- var fs15 = __toESM(require("fs"), 1);
4899
- var path19 = __toESM(require("path"), 1);
6327
+ var fs18 = __toESM(require("fs"), 1);
6328
+ var path22 = __toESM(require("path"), 1);
4900
6329
  var crypto2 = __toESM(require("crypto"), 1);
4901
6330
  var parseBooleanFlag3 = (value) => {
4902
6331
  if (!value) {
@@ -4926,16 +6355,16 @@ var sha256 = (value) => {
4926
6355
  };
4927
6356
  var resolveGitDir = (gitPath) => {
4928
6357
  try {
4929
- const stat4 = fs15.statSync(gitPath);
6358
+ const stat4 = fs18.statSync(gitPath);
4930
6359
  if (stat4.isDirectory()) {
4931
6360
  return gitPath;
4932
6361
  }
4933
6362
  if (stat4.isFile()) {
4934
- const content = fs15.readFileSync(gitPath, "utf8");
6363
+ const content = fs18.readFileSync(gitPath, "utf8");
4935
6364
  const match = content.match(/^gitdir:\s*(.+)$/m);
4936
6365
  if (match) {
4937
6366
  const gitDir = match[1].trim();
4938
- return path19.resolve(path19.dirname(gitPath), gitDir);
6367
+ return path22.resolve(path22.dirname(gitPath), gitDir);
4939
6368
  }
4940
6369
  }
4941
6370
  } catch {
@@ -4944,16 +6373,16 @@ var resolveGitDir = (gitPath) => {
4944
6373
  return null;
4945
6374
  };
4946
6375
  var findGitRoot = (start) => {
4947
- let current = path19.resolve(start);
6376
+ let current = path22.resolve(start);
4948
6377
  while (true) {
4949
- const gitPath = path19.join(current, ".git");
4950
- if (fs15.existsSync(gitPath)) {
6378
+ const gitPath = path22.join(current, ".git");
6379
+ if (fs18.existsSync(gitPath)) {
4951
6380
  const gitDir = resolveGitDir(gitPath);
4952
6381
  if (gitDir) {
4953
6382
  return { gitRoot: current, gitDir };
4954
6383
  }
4955
6384
  }
4956
- const parent = path19.dirname(current);
6385
+ const parent = path22.dirname(current);
4957
6386
  if (parent === current) {
4958
6387
  break;
4959
6388
  }
@@ -4962,9 +6391,9 @@ var findGitRoot = (start) => {
4962
6391
  return null;
4963
6392
  };
4964
6393
  var readOriginRemote = (gitDir) => {
4965
- const configPath = path19.join(gitDir, "config");
6394
+ const configPath = path22.join(gitDir, "config");
4966
6395
  try {
4967
- const content = fs15.readFileSync(configPath, "utf8");
6396
+ const content = fs18.readFileSync(configPath, "utf8");
4968
6397
  const lines = content.split(/\r?\n/);
4969
6398
  let inOrigin = false;
4970
6399
  for (const line of lines) {
@@ -4988,7 +6417,7 @@ var readOriginRemote = (gitDir) => {
4988
6417
  function resolveRepoFingerprint(cwd2) {
4989
6418
  const found = findGitRoot(cwd2);
4990
6419
  if (!found) {
4991
- const fallbackPath = path19.resolve(cwd2);
6420
+ const fallbackPath = path22.resolve(cwd2);
4992
6421
  const fingerprint2 = sha256(fallbackPath);
4993
6422
  debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
4994
6423
  return {
@@ -5010,7 +6439,7 @@ function resolveRepoFingerprint(cwd2) {
5010
6439
  source: "git-remote"
5011
6440
  };
5012
6441
  }
5013
- const fingerprint = sha256(path19.resolve(gitRoot));
6442
+ const fingerprint = sha256(path22.resolve(gitRoot));
5014
6443
  debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
5015
6444
  return {
5016
6445
  fingerprint,
@@ -5019,6 +6448,188 @@ function resolveRepoFingerprint(cwd2) {
5019
6448
  };
5020
6449
  }
5021
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
+
5022
6633
  // src/localState/connectCommand.ts
5023
6634
  function normalizeConnectCode(code) {
5024
6635
  const trimmed = code.trim();
@@ -5036,12 +6647,29 @@ function normalizeGitRemote(remoteUrl) {
5036
6647
  normalized = normalized.replace(/\/+$/, "");
5037
6648
  return normalized.toLowerCase() || null;
5038
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
+ }
5039
6666
  async function runConnectCommand(options) {
5040
6667
  const code = normalizeConnectCode(options.code);
5041
- const cwd2 = path20.resolve(options.cwd ?? process.cwd());
6668
+ const cwd2 = path24.resolve(options.cwd ?? process.cwd());
5042
6669
  const apiUrl = (options.apiUrl ?? config2.apiUrl).replace(/\/+$/, "");
5043
- const homeDir = options.homeDir ?? os7.homedir();
6670
+ const homeDir = options.homeDir ?? os8.homedir();
5044
6671
  const environment = "local";
6672
+ const verifyLocalBinding = options.verifyLocalBinding ?? resolveLocalBinding;
5045
6673
  const executionMode = await resolvePackageExecutionMode({
5046
6674
  executionMode: options.executionMode,
5047
6675
  // Only honor an explicit caller cliPath; never auto-resolve before mode detection
@@ -5062,157 +6690,222 @@ async function runConnectCommand(options) {
5062
6690
  }
5063
6691
  resolvedMode = { kind: "local", cliPath };
5064
6692
  }
5065
- const ensured = await ensureRepositoryBindingForRoot(cwd2, {
5066
- homeDir,
5067
- identityDeps: options.identityDeps,
5068
- createIfMissing: true
5069
- });
5070
- if (ensured.legacyM1WarningPath) {
6693
+ const fingerprint = resolveRepoFingerprint(cwd2);
6694
+ const legacyM1WarningPath = await detectLegacyM1Warning2(cwd2);
6695
+ if (legacyM1WarningPath) {
5071
6696
  process.stderr.write(
5072
- `[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)
5073
6698
  `
5074
6699
  );
5075
6700
  }
5076
- const fingerprint = resolveRepoFingerprint(cwd2);
5077
- const displayName = cwd2;
5078
- const normalizedGitRemote = normalizeGitRemote(fingerprint.remoteUrl);
5079
- const { clientRedeemKey } = await ensureClientRedeemKey(
5080
- ensured.repositoryBindingId,
5081
- options.credentialOptions
5082
- );
5083
- const now = (/* @__PURE__ */ new Date()).toISOString();
5084
- const pendingRecord = {
5085
- v: 1,
5086
- repositoryBindingId: ensured.repositoryBindingId,
5087
- workspaceRoot: cwd2,
5088
- filesystemIdentity: ensured.identity,
5089
- rootFingerprint: fingerprint.fingerprint,
5090
- displayName,
5091
- environment,
5092
- apiUrl,
5093
- normalizedGitRemote,
5094
- status: "pending",
5095
- createdAt: now,
5096
- updatedAt: now,
5097
- packageVersion: options.packageVersion ?? null,
5098
- ideType: options.ideType ?? null
5099
- };
5100
- await writeBindingRecord(pendingRecord, homeDir);
5101
- await upsertPathIndexEntry({
5102
- repositoryBindingId: ensured.repositoryBindingId,
5103
- workspaceRoot: cwd2,
5104
- identity: ensured.identity,
6701
+ const snapshot = await snapshotAndRemoveRootLocalState(cwd2, {
5105
6702
  homeDir,
5106
- previousPath: ensured.renamedFrom
6703
+ identityDeps: options.identityDeps,
6704
+ credentialOptions: options.credentialOptions,
6705
+ extraFingerprints: [fingerprint.fingerprint]
5107
6706
  });
5108
- const redeemed = await redeemLocalConnectCode(
5109
- apiUrl,
5110
- {
5111
- code,
5112
- client_redeem_key: clientRedeemKey,
5113
- repository_binding_id: ensured.repositoryBindingId,
5114
- root_fingerprint: fingerprint.fingerprint,
5115
- 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,
5116
6725
  environment,
5117
- normalized_git_remote: normalizedGitRemote,
5118
- platform: ensured.identity.platform,
5119
- package_version: options.packageVersion ?? null,
5120
- ide_type: options.ideType ?? null
5121
- },
5122
- { fetchImpl: options.fetchImpl }
5123
- );
5124
- await updateInstallationCredentials(
5125
- ensured.repositoryBindingId,
5126
- {
5127
- accessToken: redeemed.access_token,
5128
- refreshToken: redeemed.refresh_token,
5129
- accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
5130
- 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,
5131
6771
  installationPublicId: redeemed.installation_public_id,
5132
- projectId: redeemed.project_id
5133
- },
5134
- { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
5135
- );
5136
- const connectedRecord = {
5137
- ...pendingRecord,
5138
- installationPublicId: redeemed.installation_public_id,
5139
- projectId: redeemed.project_id,
5140
- status: "connected",
5141
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5142
- };
5143
- await writeBindingRecord(connectedRecord, homeDir);
5144
- const baseSuccess = `Connected repository binding ${ensured.repositoryBindingId}` + (redeemed.recovered ? " (recovered)" : "") + ` to project ${redeemed.project_id}.`;
5145
- if (options.configureIdes !== false) {
5146
- const targets = { cursor: true, vscode: true, jetbrains: true };
5147
- const setup = options.setupIdeFiles ?? runSetupIdeFiles;
5148
- const modeSetup = setupOptionsFromPackageExecutionMode(resolvedMode);
5149
- 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;
5150
6778
  try {
5151
- setupResult = await setup({
5152
- cwd: cwd2,
5153
- // Use the exact bound workspace root — do not rediscover via .git / .m1.
5154
- workspaceRoot: cwd2,
5155
- targets,
5156
- force: true,
5157
- dryRun: false,
5158
- noGitignore: true,
5159
- skipDaemonCleanup: true,
6779
+ verified = await verifyLocalBinding(cwd2, {
5160
6780
  homeDir,
5161
- // Propagate the redeemed binding API URL (backend only — not execution mode).
5162
- apiUrl,
5163
- ...modeSetup,
5164
- ...options.setupIdeOptions
6781
+ identityDeps: options.identityDeps,
6782
+ credentialOptions: options.credentialOptions
5165
6783
  });
5166
6784
  } catch (err) {
5167
- const message = err instanceof Error ? err.message : String(err);
5168
- 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 {
5169
6793
  exitCode: 1,
5170
- repoRoot: cwd2,
5171
- outcomes: {},
5172
- 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/.`
5173
6800
  };
5174
6801
  }
5175
- if (setupResult.exitCode !== 0) {
5176
- const detail = setupResult.error ?? "unknown IDE setup error";
5177
- 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
+ });
5178
6810
  return {
5179
6811
  exitCode: 1,
5180
- 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,
5181
6878
  projectId: redeemed.project_id,
5182
6879
  installationPublicId: redeemed.installation_public_id,
5183
6880
  recovered: redeemed.recovered,
5184
- createdBinding: ensured.created,
5185
- legacyM1WarningPath: ensured.legacyM1WarningPath,
5186
- ideSetupError: detail,
6881
+ createdBinding,
6882
+ legacyM1WarningPath,
5187
6883
  executionMode: resolvedMode,
5188
- 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
5189
6887
  };
5190
6888
  }
5191
6889
  return {
5192
6890
  exitCode: 0,
5193
- repositoryBindingId: ensured.repositoryBindingId,
6891
+ repositoryBindingId,
5194
6892
  projectId: redeemed.project_id,
5195
6893
  installationPublicId: redeemed.installation_public_id,
5196
6894
  recovered: redeemed.recovered,
5197
- createdBinding: ensured.created,
5198
- legacyM1WarningPath: ensured.legacyM1WarningPath,
6895
+ createdBinding,
6896
+ legacyM1WarningPath,
5199
6897
  executionMode: resolvedMode,
5200
- configuredTargets: targets,
5201
- setupResult,
5202
6898
  message: baseSuccess
5203
6899
  };
6900
+ } catch (err) {
6901
+ await rollbackFailedConnect({
6902
+ snapshot,
6903
+ discardBindingId: repositoryBindingId,
6904
+ homeDir,
6905
+ credentialOptions: options.credentialOptions
6906
+ });
6907
+ throw err;
5204
6908
  }
5205
- return {
5206
- exitCode: 0,
5207
- repositoryBindingId: ensured.repositoryBindingId,
5208
- projectId: redeemed.project_id,
5209
- installationPublicId: redeemed.installation_public_id,
5210
- recovered: redeemed.recovered,
5211
- createdBinding: ensured.created,
5212
- legacyM1WarningPath: ensured.legacyM1WarningPath,
5213
- executionMode: resolvedMode,
5214
- message: baseSuccess
5215
- };
5216
6909
  }
5217
6910
  function parseConnectArgv(argv) {
5218
6911
  let code;
@@ -5336,7 +7029,7 @@ if (args.includes("--version") || args.includes("-v")) {
5336
7029
  }
5337
7030
  if (args.includes("--help") || args.includes("-h")) {
5338
7031
  console.log(
5339
- "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]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--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] [--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]"
5340
7033
  );
5341
7034
  process.exit(0);
5342
7035
  }