@memoraone/mcp 0.1.29 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +519 -45
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var require_package = __commonJS({
|
|
|
30
30
|
"package.json"(exports2, module2) {
|
|
31
31
|
module2.exports = {
|
|
32
32
|
name: "@memoraone/mcp",
|
|
33
|
-
version: "0.1.
|
|
33
|
+
version: "0.1.30",
|
|
34
34
|
type: "module",
|
|
35
35
|
main: "dist/index.cjs",
|
|
36
36
|
bin: {
|
|
@@ -67,9 +67,9 @@ var require_package = __commonJS({
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
// src/cli.ts
|
|
70
|
-
var
|
|
70
|
+
var path7 = __toESM(require("path"), 1);
|
|
71
71
|
var net = __toESM(require("net"), 1);
|
|
72
|
-
var
|
|
72
|
+
var import_node_child_process4 = require("child_process");
|
|
73
73
|
|
|
74
74
|
// src/socketPaths.ts
|
|
75
75
|
var os = __toESM(require("os"), 1);
|
|
@@ -292,8 +292,9 @@ function encodeResolvedBinding(binding) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// src/setupIdeFiles.ts
|
|
295
|
-
var
|
|
296
|
-
var
|
|
295
|
+
var fs6 = __toESM(require("fs/promises"), 1);
|
|
296
|
+
var os4 = __toESM(require("os"), 1);
|
|
297
|
+
var path6 = __toESM(require("path"), 1);
|
|
297
298
|
|
|
298
299
|
// src/cleanup.ts
|
|
299
300
|
var fs3 = __toESM(require("fs/promises"), 1);
|
|
@@ -891,6 +892,435 @@ function logCursorGlobalMcpCliSummary(info, dryRun) {
|
|
|
891
892
|
);
|
|
892
893
|
}
|
|
893
894
|
|
|
895
|
+
// src/jetbrainsMcpConfig.ts
|
|
896
|
+
var fs5 = __toESM(require("fs/promises"), 1);
|
|
897
|
+
var os3 = __toESM(require("os"), 1);
|
|
898
|
+
var path5 = __toESM(require("path"), 1);
|
|
899
|
+
var import_node_child_process3 = require("child_process");
|
|
900
|
+
|
|
901
|
+
// src/configUtils.ts
|
|
902
|
+
var DEV_API_URL = "http://localhost:3001";
|
|
903
|
+
|
|
904
|
+
// src/jetbrainsMcpConfig.ts
|
|
905
|
+
var PROD_API_URL = "https://api.memoraone.com";
|
|
906
|
+
var JETBRAINS_DEBUG_ENV_VARS = [
|
|
907
|
+
"MEMORAONE_DEBUG_INIT",
|
|
908
|
+
"MEMORAONE_DEBUG_MINIMAL_TOOLS",
|
|
909
|
+
"MEMORAONE_DEBUG_MINIMAL_INITIALIZE",
|
|
910
|
+
"MEMORAONE_DEBUG_DIRECT_STDIO"
|
|
911
|
+
];
|
|
912
|
+
function stripLeadingLineComments2(text) {
|
|
913
|
+
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
914
|
+
}
|
|
915
|
+
async function pathExists2(filePath) {
|
|
916
|
+
try {
|
|
917
|
+
await fs5.access(filePath);
|
|
918
|
+
return true;
|
|
919
|
+
} catch {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function formatJetBrainsBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
924
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
925
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
926
|
+
}
|
|
927
|
+
function getJetBrainsGlobalMcpConfigPath(homeDir) {
|
|
928
|
+
return path5.join(homeDir, ".ai", "mcp", "mcp.json");
|
|
929
|
+
}
|
|
930
|
+
function getJetBrainsProjectMcpConfigPaths(repoRoot) {
|
|
931
|
+
return [
|
|
932
|
+
{ kind: "project-ai", path: path5.join(repoRoot, ".ai", "mcp", "mcp.json") },
|
|
933
|
+
{ kind: "project-ij", path: path5.join(repoRoot, ".ij", "mcp", "mcp.json") }
|
|
934
|
+
];
|
|
935
|
+
}
|
|
936
|
+
function getKnownJetBrainsMcpConfigLocations(homeDir, repoRoot) {
|
|
937
|
+
return [
|
|
938
|
+
{ kind: "global", path: getJetBrainsGlobalMcpConfigPath(homeDir) },
|
|
939
|
+
...getJetBrainsProjectMcpConfigPaths(repoRoot)
|
|
940
|
+
];
|
|
941
|
+
}
|
|
942
|
+
async function isZeroByteConfigFile(filePath) {
|
|
943
|
+
if (!await pathExists2(filePath)) return false;
|
|
944
|
+
const stat2 = await fs5.stat(filePath);
|
|
945
|
+
return stat2.size === 0;
|
|
946
|
+
}
|
|
947
|
+
function buildMemoraoneJetBrainsMcpServer(options) {
|
|
948
|
+
const env = {
|
|
949
|
+
MEMORAONE_API_URL: options.devMode ? DEV_API_URL : PROD_API_URL,
|
|
950
|
+
MEMORAONE_IDE_TYPE: "jetbrains",
|
|
951
|
+
MEMORAONE_M1_PATH: options.m1Path
|
|
952
|
+
};
|
|
953
|
+
if (options.devMode) {
|
|
954
|
+
env.MEMORAONE_DEV_MODE = "1";
|
|
955
|
+
}
|
|
956
|
+
return {
|
|
957
|
+
command: options.command,
|
|
958
|
+
args: options.args,
|
|
959
|
+
env
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
function mergeJetBrainsMcpConfigObject(existing, memoraone) {
|
|
963
|
+
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
964
|
+
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
965
|
+
mcpServers.memoraone = memoraone;
|
|
966
|
+
return { ...base, mcpServers };
|
|
967
|
+
}
|
|
968
|
+
function memoraoneServerMatches2(server, expected) {
|
|
969
|
+
if (!server || typeof server !== "object") return false;
|
|
970
|
+
const s = server;
|
|
971
|
+
if (s.command !== expected.command) return false;
|
|
972
|
+
if (!Array.isArray(s.args) || s.args.length !== expected.args.length) return false;
|
|
973
|
+
for (let i = 0; i < expected.args.length; i += 1) {
|
|
974
|
+
if (s.args[i] !== expected.args[i]) return false;
|
|
975
|
+
}
|
|
976
|
+
const env = s.env;
|
|
977
|
+
if (!env || typeof env !== "object") return false;
|
|
978
|
+
const e = env;
|
|
979
|
+
for (const [key, value] of Object.entries(expected.env)) {
|
|
980
|
+
if (e[key] !== value) return false;
|
|
981
|
+
}
|
|
982
|
+
for (const debugKey of JETBRAINS_DEBUG_ENV_VARS) {
|
|
983
|
+
if (debugKey in e) return false;
|
|
984
|
+
}
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
function validateJetBrainsMcpConfig(parsed, expected) {
|
|
988
|
+
if (!parsed || typeof parsed !== "object") {
|
|
989
|
+
throw new Error("[setup-ide-files] JetBrains MCP config must be a JSON object.");
|
|
990
|
+
}
|
|
991
|
+
const mcpServers = parsed.mcpServers;
|
|
992
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
993
|
+
throw new Error("[setup-ide-files] JetBrains MCP config missing mcpServers object.");
|
|
994
|
+
}
|
|
995
|
+
const memoraone = mcpServers.memoraone;
|
|
996
|
+
if (!memoraoneServerMatches2(memoraone, expected)) {
|
|
997
|
+
throw new Error(
|
|
998
|
+
"[setup-ide-files] JetBrains MCP config mcpServers.memoraone is missing or invalid."
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async function readJsonConfig(filePath) {
|
|
1003
|
+
const raw = await fs5.readFile(filePath, "utf8");
|
|
1004
|
+
if (raw.trim() === "") return null;
|
|
1005
|
+
return JSON.parse(stripLeadingLineComments2(raw));
|
|
1006
|
+
}
|
|
1007
|
+
async function backupConfigFile(filePath) {
|
|
1008
|
+
const backupPath = `${filePath}.bak-${formatJetBrainsBackupTimestamp()}`;
|
|
1009
|
+
await fs5.copyFile(filePath, backupPath);
|
|
1010
|
+
return backupPath;
|
|
1011
|
+
}
|
|
1012
|
+
async function repairZeroByteConfigFile(filePath, dryRun) {
|
|
1013
|
+
if (!await isZeroByteConfigFile(filePath)) {
|
|
1014
|
+
return { repaired: false };
|
|
1015
|
+
}
|
|
1016
|
+
if (dryRun) {
|
|
1017
|
+
return { repaired: true, backupPath: `${filePath}.bak-<timestamp>` };
|
|
1018
|
+
}
|
|
1019
|
+
const backupPath = await backupConfigFile(filePath);
|
|
1020
|
+
await fs5.unlink(filePath);
|
|
1021
|
+
return { repaired: true, backupPath };
|
|
1022
|
+
}
|
|
1023
|
+
function configHasMemoraone(parsed) {
|
|
1024
|
+
if (!parsed) return false;
|
|
1025
|
+
const mcpServers = parsed.mcpServers;
|
|
1026
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return false;
|
|
1027
|
+
return Boolean(mcpServers.memoraone);
|
|
1028
|
+
}
|
|
1029
|
+
async function removeMemoraoneFromProjectConfig(options) {
|
|
1030
|
+
const { configPath, dryRun } = options;
|
|
1031
|
+
if (!await pathExists2(configPath)) {
|
|
1032
|
+
return { changed: false };
|
|
1033
|
+
}
|
|
1034
|
+
let parsed = null;
|
|
1035
|
+
try {
|
|
1036
|
+
parsed = await readJsonConfig(configPath);
|
|
1037
|
+
} catch {
|
|
1038
|
+
return { changed: false };
|
|
1039
|
+
}
|
|
1040
|
+
if (!configHasMemoraone(parsed)) {
|
|
1041
|
+
return { changed: false };
|
|
1042
|
+
}
|
|
1043
|
+
if (dryRun) {
|
|
1044
|
+
return { changed: true, backupPath: `${configPath}.bak-<timestamp>` };
|
|
1045
|
+
}
|
|
1046
|
+
const backupPath = await backupConfigFile(configPath);
|
|
1047
|
+
const mcpServers = parsed && typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
|
|
1048
|
+
delete mcpServers.memoraone;
|
|
1049
|
+
const hasOtherServers = Object.keys(mcpServers).length > 0;
|
|
1050
|
+
if (!hasOtherServers) {
|
|
1051
|
+
await fs5.unlink(configPath);
|
|
1052
|
+
return { changed: true, backupPath };
|
|
1053
|
+
}
|
|
1054
|
+
const next = { ...parsed, mcpServers };
|
|
1055
|
+
await fs5.mkdir(path5.dirname(configPath), { recursive: true });
|
|
1056
|
+
await fs5.writeFile(configPath, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
1057
|
+
return { changed: true, backupPath };
|
|
1058
|
+
}
|
|
1059
|
+
async function resolveLocalCliPathAsync() {
|
|
1060
|
+
const here = process.argv[1] ? path5.dirname(path5.resolve(process.argv[1])) : process.cwd();
|
|
1061
|
+
const candidates = [
|
|
1062
|
+
path5.join(here, "cli.cjs"),
|
|
1063
|
+
path5.join(here, "..", "dist", "cli.cjs"),
|
|
1064
|
+
path5.join(here, "..", "..", "dist", "cli.cjs")
|
|
1065
|
+
];
|
|
1066
|
+
for (const candidate of candidates) {
|
|
1067
|
+
if (await pathExists2(candidate)) {
|
|
1068
|
+
return path5.resolve(candidate);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
async function buildJetBrainsMemoraoneServer(options) {
|
|
1074
|
+
if (options.devMode) {
|
|
1075
|
+
let cliPath = options.cliPathOverride;
|
|
1076
|
+
if (cliPath === void 0) {
|
|
1077
|
+
cliPath = await resolveLocalCliPathAsync();
|
|
1078
|
+
}
|
|
1079
|
+
if (!cliPath) {
|
|
1080
|
+
throw new Error(
|
|
1081
|
+
"[setup-ide-files] Dev mode requires a built CLI at packages/mcp/dist/cli.cjs. Run pnpm build first."
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
return buildMemoraoneJetBrainsMcpServer({
|
|
1085
|
+
command: process.execPath,
|
|
1086
|
+
args: [cliPath],
|
|
1087
|
+
m1Path: options.m1Path,
|
|
1088
|
+
devMode: true
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
let npxPath = options.npxPathOverride;
|
|
1092
|
+
if (npxPath === void 0) {
|
|
1093
|
+
npxPath = await resolveNpxPath();
|
|
1094
|
+
}
|
|
1095
|
+
if (!npxPath) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
"[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring JetBrains MCP."
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
return buildMemoraoneJetBrainsMcpServer({
|
|
1101
|
+
command: npxPath,
|
|
1102
|
+
args: ["-y", "@memoraone/mcp@latest"],
|
|
1103
|
+
m1Path: options.m1Path,
|
|
1104
|
+
devMode: false
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
async function verifyJetBrainsMcpHandshake(options) {
|
|
1108
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
1109
|
+
const { server } = options;
|
|
1110
|
+
return new Promise((resolve7) => {
|
|
1111
|
+
let settled = false;
|
|
1112
|
+
const finish = (ok, detail) => {
|
|
1113
|
+
if (settled) return;
|
|
1114
|
+
settled = true;
|
|
1115
|
+
clearTimeout(timer);
|
|
1116
|
+
try {
|
|
1117
|
+
child.kill();
|
|
1118
|
+
} catch {
|
|
1119
|
+
}
|
|
1120
|
+
resolve7({ ok, detail });
|
|
1121
|
+
};
|
|
1122
|
+
const child = (0, import_node_child_process3.spawn)(server.command, [...server.args], {
|
|
1123
|
+
env: { ...process.env, ...server.env },
|
|
1124
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1125
|
+
});
|
|
1126
|
+
let buffer = "";
|
|
1127
|
+
let initializeOk = false;
|
|
1128
|
+
let toolsListOk = false;
|
|
1129
|
+
let nextId = 1;
|
|
1130
|
+
const send = (method, params) => {
|
|
1131
|
+
const msg = JSON.stringify({ jsonrpc: "2.0", id: nextId, method, params }) + "\n";
|
|
1132
|
+
nextId += 1;
|
|
1133
|
+
child.stdin?.write(msg);
|
|
1134
|
+
};
|
|
1135
|
+
const timer = setTimeout(() => {
|
|
1136
|
+
finish(false, `handshake timed out after ${timeoutMs}ms`);
|
|
1137
|
+
}, timeoutMs);
|
|
1138
|
+
child.stdout?.on("data", (chunk) => {
|
|
1139
|
+
buffer += chunk.toString("utf8");
|
|
1140
|
+
const lines = buffer.split("\n");
|
|
1141
|
+
buffer = lines.pop() ?? "";
|
|
1142
|
+
for (const line of lines) {
|
|
1143
|
+
if (!line.trim()) continue;
|
|
1144
|
+
let msg;
|
|
1145
|
+
try {
|
|
1146
|
+
msg = JSON.parse(line);
|
|
1147
|
+
} catch {
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (msg.id === 1 && msg.result) {
|
|
1151
|
+
initializeOk = true;
|
|
1152
|
+
send("notifications/initialized", {});
|
|
1153
|
+
send("tools/list", {});
|
|
1154
|
+
}
|
|
1155
|
+
if (msg.id === 2 && msg.result) {
|
|
1156
|
+
toolsListOk = true;
|
|
1157
|
+
finish(true, "initialize OK; tools/list OK");
|
|
1158
|
+
}
|
|
1159
|
+
if (msg.error) {
|
|
1160
|
+
finish(false, `JSON-RPC error: ${JSON.stringify(msg.error)}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
child.on("error", (err) => {
|
|
1165
|
+
finish(false, `spawn error: ${String(err)}`);
|
|
1166
|
+
});
|
|
1167
|
+
child.on("exit", (code) => {
|
|
1168
|
+
if (!settled) {
|
|
1169
|
+
if (initializeOk && toolsListOk) {
|
|
1170
|
+
finish(true, "initialize OK; tools/list OK");
|
|
1171
|
+
} else {
|
|
1172
|
+
finish(
|
|
1173
|
+
false,
|
|
1174
|
+
`process exited code=${code ?? "null"} (initialize=${initializeOk}, tools/list=${toolsListOk})`
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
send("initialize", {
|
|
1180
|
+
protocolVersion: "2024-11-05",
|
|
1181
|
+
capabilities: {},
|
|
1182
|
+
clientInfo: { name: "memoraone-setup", version: "1.0.0" }
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
async function setupJetBrainsMcpConfig(options) {
|
|
1187
|
+
const homeDir = options.homeDir ?? os3.homedir();
|
|
1188
|
+
const globalPath = options.globalConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
1189
|
+
const m1Path = path5.join(path5.resolve(options.repoRoot), "memoraone.m1");
|
|
1190
|
+
const repairActions = [];
|
|
1191
|
+
const allLocations = getKnownJetBrainsMcpConfigLocations(homeDir, options.repoRoot);
|
|
1192
|
+
for (const location of allLocations) {
|
|
1193
|
+
if (await pathExists2(location.path)) {
|
|
1194
|
+
repairActions.push({ type: "found-config", location });
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
for (const location of allLocations) {
|
|
1198
|
+
const zeroByte = await repairZeroByteConfigFile(location.path, options.dryRun);
|
|
1199
|
+
if (zeroByte.repaired) {
|
|
1200
|
+
repairActions.push({
|
|
1201
|
+
type: "repaired-zero-byte",
|
|
1202
|
+
path: location.path,
|
|
1203
|
+
backupPath: zeroByte.backupPath ?? `${location.path}.bak-<timestamp>`
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const memoraone = await buildJetBrainsMemoraoneServer({
|
|
1208
|
+
m1Path,
|
|
1209
|
+
devMode: options.devMode,
|
|
1210
|
+
npxPathOverride: options.npxPathOverride,
|
|
1211
|
+
cliPathOverride: options.cliPathOverride
|
|
1212
|
+
});
|
|
1213
|
+
for (const location of getJetBrainsProjectMcpConfigPaths(options.repoRoot)) {
|
|
1214
|
+
const removal = await removeMemoraoneFromProjectConfig({
|
|
1215
|
+
configPath: location.path,
|
|
1216
|
+
dryRun: options.dryRun
|
|
1217
|
+
});
|
|
1218
|
+
if (removal.changed) {
|
|
1219
|
+
if (removal.backupPath) {
|
|
1220
|
+
repairActions.push({
|
|
1221
|
+
type: "backed-up-conflicting-project-config",
|
|
1222
|
+
path: location.path,
|
|
1223
|
+
backupPath: removal.backupPath
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
repairActions.push({ type: "removed-project-memoraone", path: location.path });
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const existed = await pathExists2(globalPath);
|
|
1230
|
+
let existing = null;
|
|
1231
|
+
if (existed) {
|
|
1232
|
+
try {
|
|
1233
|
+
existing = await readJsonConfig(globalPath);
|
|
1234
|
+
} catch {
|
|
1235
|
+
if (options.dryRun) {
|
|
1236
|
+
existing = null;
|
|
1237
|
+
} else {
|
|
1238
|
+
const backupPath2 = await backupConfigFile(globalPath);
|
|
1239
|
+
repairActions.push({
|
|
1240
|
+
type: "repaired-zero-byte",
|
|
1241
|
+
path: globalPath,
|
|
1242
|
+
backupPath: backupPath2
|
|
1243
|
+
});
|
|
1244
|
+
await fs5.unlink(globalPath);
|
|
1245
|
+
existing = null;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
const merged = mergeJetBrainsMcpConfigObject(existing, memoraone);
|
|
1250
|
+
const body = JSON.stringify(merged, null, 2) + "\n";
|
|
1251
|
+
if (existed && existing) {
|
|
1252
|
+
const currentMemoraone = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
|
|
1253
|
+
if (memoraoneServerMatches2(currentMemoraone, memoraone)) {
|
|
1254
|
+
repairActions.push({ type: "wrote-global-config", path: globalPath, outcome: "skipped" });
|
|
1255
|
+
return { outcome: "skipped", repairActions, memoraone };
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (options.dryRun) {
|
|
1259
|
+
const outcome2 = existed ? "updated" : "created";
|
|
1260
|
+
repairActions.push({ type: "wrote-global-config", path: globalPath, outcome: outcome2 });
|
|
1261
|
+
return { outcome: outcome2, repairActions, memoraone };
|
|
1262
|
+
}
|
|
1263
|
+
let backupPath;
|
|
1264
|
+
if (existed) {
|
|
1265
|
+
backupPath = await backupConfigFile(globalPath);
|
|
1266
|
+
}
|
|
1267
|
+
await fs5.mkdir(path5.dirname(globalPath), { recursive: true });
|
|
1268
|
+
await fs5.writeFile(globalPath, body, "utf8");
|
|
1269
|
+
const verifyRaw = await fs5.readFile(globalPath, "utf8");
|
|
1270
|
+
const verifyParsed = JSON.parse(stripLeadingLineComments2(verifyRaw));
|
|
1271
|
+
validateJetBrainsMcpConfig(verifyParsed, memoraone);
|
|
1272
|
+
const outcome = existed ? "updated" : "created";
|
|
1273
|
+
repairActions.push({ type: "wrote-global-config", path: globalPath, outcome });
|
|
1274
|
+
let verifyOk;
|
|
1275
|
+
let verifyDetail;
|
|
1276
|
+
if (options.verify !== false) {
|
|
1277
|
+
const verify = await verifyJetBrainsMcpHandshake({ server: memoraone });
|
|
1278
|
+
verifyOk = verify.ok;
|
|
1279
|
+
verifyDetail = verify.detail;
|
|
1280
|
+
repairActions.push({ type: "verify-handshake", ok: verify.ok, detail: verify.detail });
|
|
1281
|
+
}
|
|
1282
|
+
return { outcome, backupPath, repairActions, verifyOk, verifyDetail, memoraone };
|
|
1283
|
+
}
|
|
1284
|
+
function logJetBrainsMcpCliSummary(info, dryRun) {
|
|
1285
|
+
for (const action of info.repairActions) {
|
|
1286
|
+
if (action.type === "found-config") {
|
|
1287
|
+
console.log(`[setup-ide-files] Found JetBrains MCP config (${action.location.kind}): ${action.location.path}`);
|
|
1288
|
+
} else if (action.type === "repaired-zero-byte") {
|
|
1289
|
+
console.log(`[setup-ide-files] Repaired zero-byte MCP config: ${action.path}`);
|
|
1290
|
+
console.log(`[setup-ide-files] Backup: ${action.backupPath}`);
|
|
1291
|
+
} else if (action.type === "backed-up-conflicting-project-config") {
|
|
1292
|
+
console.log(`[setup-ide-files] Backed up conflicting project MCP config: ${action.path}`);
|
|
1293
|
+
console.log(`[setup-ide-files] Backup: ${action.backupPath}`);
|
|
1294
|
+
} else if (action.type === "removed-project-memoraone") {
|
|
1295
|
+
console.log(`[setup-ide-files] Removed project-scoped memoraone definition: ${action.path}`);
|
|
1296
|
+
} else if (action.type === "verify-handshake") {
|
|
1297
|
+
if (action.ok) {
|
|
1298
|
+
console.log(`[setup-ide-files] MCP handshake verification: ${action.detail}`);
|
|
1299
|
+
} else {
|
|
1300
|
+
console.log(`[setup-ide-files] MCP handshake verification skipped/failed: ${action.detail}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
const prefix = dryRun ? "would be " : "";
|
|
1305
|
+
if (info.outcome === "created") {
|
|
1306
|
+
console.log(`[setup-ide-files] JetBrains global MCP config ${prefix}created: ${info.activeConfigPath}`);
|
|
1307
|
+
} else if (info.outcome === "updated") {
|
|
1308
|
+
console.log(`[setup-ide-files] JetBrains global MCP config ${prefix}updated: ${info.activeConfigPath}`);
|
|
1309
|
+
} else {
|
|
1310
|
+
console.log(`[setup-ide-files] JetBrains global MCP config unchanged: ${info.activeConfigPath}`);
|
|
1311
|
+
}
|
|
1312
|
+
if (info.backupPath) {
|
|
1313
|
+
console.log(`[setup-ide-files] JetBrains global MCP config backup: ${info.backupPath}`);
|
|
1314
|
+
}
|
|
1315
|
+
if (info.npxPath) {
|
|
1316
|
+
console.log(`[setup-ide-files] Resolved npx: ${info.npxPath}`);
|
|
1317
|
+
}
|
|
1318
|
+
console.log(`[setup-ide-files] Final active JetBrains MCP config: ${info.activeConfigPath}`);
|
|
1319
|
+
console.log(
|
|
1320
|
+
"[setup-ide-files] Fully quit JetBrains IDE and reopen this repo for MCP changes to take effect."
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
894
1324
|
// src/setupIdeFiles.ts
|
|
895
1325
|
var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
|
|
896
1326
|
var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
|
|
@@ -906,15 +1336,15 @@ function buildMemoraoneMcpServer(ideType, command = "npx") {
|
|
|
906
1336
|
};
|
|
907
1337
|
}
|
|
908
1338
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
909
|
-
const normRoot =
|
|
910
|
-
const normPath =
|
|
911
|
-
if (normPath !==
|
|
1339
|
+
const normRoot = path6.resolve(repoRoot) + path6.sep;
|
|
1340
|
+
const normPath = path6.resolve(absPath);
|
|
1341
|
+
if (normPath !== path6.resolve(repoRoot) && !normPath.startsWith(normRoot)) {
|
|
912
1342
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
913
1343
|
}
|
|
914
1344
|
}
|
|
915
|
-
async function
|
|
1345
|
+
async function pathExists3(filePath) {
|
|
916
1346
|
try {
|
|
917
|
-
await
|
|
1347
|
+
await fs6.access(filePath);
|
|
918
1348
|
return true;
|
|
919
1349
|
} catch {
|
|
920
1350
|
return false;
|
|
@@ -935,12 +1365,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
|
|
|
935
1365
|
}
|
|
936
1366
|
async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
937
1367
|
if (opts.noGitignore) return "skipped";
|
|
938
|
-
const abs =
|
|
1368
|
+
const abs = path6.join(repoRoot, ".gitignore");
|
|
939
1369
|
assertUnderRepoRoot(repoRoot, abs);
|
|
940
1370
|
let prior = "";
|
|
941
1371
|
let existed = false;
|
|
942
1372
|
try {
|
|
943
|
-
prior = await
|
|
1373
|
+
prior = await fs6.readFile(abs, "utf8");
|
|
944
1374
|
existed = true;
|
|
945
1375
|
} catch (err) {
|
|
946
1376
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
@@ -951,25 +1381,25 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
|
951
1381
|
const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
952
1382
|
const next = (existed ? prior : "") + separator + block;
|
|
953
1383
|
if (opts.dryRun) return existed ? "updated" : "created";
|
|
954
|
-
await
|
|
1384
|
+
await fs6.writeFile(abs, next, "utf8");
|
|
955
1385
|
return existed ? "updated" : "created";
|
|
956
1386
|
}
|
|
957
1387
|
async function findRepoRoot(startDir) {
|
|
958
|
-
let current =
|
|
959
|
-
const root =
|
|
1388
|
+
let current = path6.resolve(startDir);
|
|
1389
|
+
const root = path6.parse(current).root;
|
|
960
1390
|
while (true) {
|
|
961
|
-
const gitPath =
|
|
962
|
-
const m1Path =
|
|
963
|
-
if (await
|
|
1391
|
+
const gitPath = path6.join(current, ".git");
|
|
1392
|
+
const m1Path = path6.join(current, "memoraone.m1");
|
|
1393
|
+
if (await pathExists3(gitPath) || await pathExists3(m1Path)) {
|
|
964
1394
|
return current;
|
|
965
1395
|
}
|
|
966
1396
|
if (current === root) {
|
|
967
1397
|
return null;
|
|
968
1398
|
}
|
|
969
|
-
current =
|
|
1399
|
+
current = path6.dirname(current);
|
|
970
1400
|
}
|
|
971
1401
|
}
|
|
972
|
-
function
|
|
1402
|
+
function stripLeadingLineComments3(text) {
|
|
973
1403
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
974
1404
|
}
|
|
975
1405
|
function cursorRuleBody() {
|
|
@@ -1020,42 +1450,42 @@ function buildVscodeMcpJsonBody(existing) {
|
|
|
1020
1450
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
1021
1451
|
}
|
|
1022
1452
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
1023
|
-
const abs =
|
|
1453
|
+
const abs = path6.join(repoRoot, relPath);
|
|
1024
1454
|
assertUnderRepoRoot(repoRoot, abs);
|
|
1025
1455
|
let prior = "";
|
|
1026
1456
|
let existed = false;
|
|
1027
1457
|
try {
|
|
1028
|
-
prior = await
|
|
1458
|
+
prior = await fs6.readFile(abs, "utf8");
|
|
1029
1459
|
existed = true;
|
|
1030
1460
|
} catch (err) {
|
|
1031
1461
|
if (err?.code !== "ENOENT") throw err;
|
|
1032
1462
|
}
|
|
1033
1463
|
if (!existed) {
|
|
1034
1464
|
if (opts.dryRun) return "created";
|
|
1035
|
-
await
|
|
1036
|
-
await
|
|
1465
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1466
|
+
await fs6.writeFile(abs, fullContent, "utf8");
|
|
1037
1467
|
return "created";
|
|
1038
1468
|
}
|
|
1039
1469
|
if (prior.includes(MANAGED_MARKER)) {
|
|
1040
1470
|
if (prior === fullContent) return "skipped";
|
|
1041
1471
|
if (opts.dryRun) return "updated";
|
|
1042
|
-
await
|
|
1043
|
-
await
|
|
1472
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1473
|
+
await fs6.writeFile(abs, fullContent, "utf8");
|
|
1044
1474
|
return "updated";
|
|
1045
1475
|
}
|
|
1046
1476
|
if (!opts.force) return "skipped-untracked";
|
|
1047
1477
|
if (opts.dryRun) return "updated";
|
|
1048
|
-
await
|
|
1049
|
-
await
|
|
1478
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1479
|
+
await fs6.writeFile(abs, fullContent, "utf8");
|
|
1050
1480
|
return "updated";
|
|
1051
1481
|
}
|
|
1052
1482
|
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
1053
|
-
const abs =
|
|
1483
|
+
const abs = path6.join(repoRoot, relPath);
|
|
1054
1484
|
assertUnderRepoRoot(repoRoot, abs);
|
|
1055
1485
|
let raw = "";
|
|
1056
1486
|
let existed = false;
|
|
1057
1487
|
try {
|
|
1058
|
-
raw = await
|
|
1488
|
+
raw = await fs6.readFile(abs, "utf8");
|
|
1059
1489
|
existed = true;
|
|
1060
1490
|
} catch (err) {
|
|
1061
1491
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -1063,15 +1493,15 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
1063
1493
|
if (!existed) {
|
|
1064
1494
|
const body = buildBody(null);
|
|
1065
1495
|
if (opts.dryRun) return "created";
|
|
1066
|
-
await
|
|
1067
|
-
await
|
|
1496
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1497
|
+
await fs6.writeFile(abs, body, "utf8");
|
|
1068
1498
|
return "created";
|
|
1069
1499
|
}
|
|
1070
1500
|
const managed = raw.includes(MANAGED_MARKER);
|
|
1071
1501
|
if (!managed && !opts.force) return "skipped-untracked";
|
|
1072
1502
|
let parsed = null;
|
|
1073
1503
|
try {
|
|
1074
|
-
parsed = JSON.parse(
|
|
1504
|
+
parsed = JSON.parse(stripLeadingLineComments3(raw));
|
|
1075
1505
|
} catch {
|
|
1076
1506
|
parsed = null;
|
|
1077
1507
|
}
|
|
@@ -1079,8 +1509,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
1079
1509
|
const next = buildBody(parsed);
|
|
1080
1510
|
if (managed && next === raw) return "skipped";
|
|
1081
1511
|
if (opts.dryRun) return "updated";
|
|
1082
|
-
await
|
|
1083
|
-
await
|
|
1512
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1513
|
+
await fs6.writeFile(abs, next, "utf8");
|
|
1084
1514
|
return "updated";
|
|
1085
1515
|
}
|
|
1086
1516
|
function parseSetupIdeFlags(argv) {
|
|
@@ -1092,6 +1522,8 @@ function parseSetupIdeFlags(argv) {
|
|
|
1092
1522
|
let dryRun = false;
|
|
1093
1523
|
let noGitignore = false;
|
|
1094
1524
|
let cleanup = false;
|
|
1525
|
+
let devMode = false;
|
|
1526
|
+
let repair = false;
|
|
1095
1527
|
const unknown = [];
|
|
1096
1528
|
for (const a of argv) {
|
|
1097
1529
|
if (a === "--cursor") cursor = true;
|
|
@@ -1102,6 +1534,8 @@ function parseSetupIdeFlags(argv) {
|
|
|
1102
1534
|
else if (a === "--dry-run") dryRun = true;
|
|
1103
1535
|
else if (a === "--no-gitignore") noGitignore = true;
|
|
1104
1536
|
else if (a === "--cleanup") cleanup = true;
|
|
1537
|
+
else if (a === "--dev") devMode = true;
|
|
1538
|
+
else if (a === "--repair") repair = true;
|
|
1105
1539
|
else if (a.startsWith("-")) unknown.push(a);
|
|
1106
1540
|
}
|
|
1107
1541
|
const specific = cursor || vscode || jetbrains;
|
|
@@ -1111,7 +1545,7 @@ function parseSetupIdeFlags(argv) {
|
|
|
1111
1545
|
} else {
|
|
1112
1546
|
targets = { cursor, vscode, jetbrains };
|
|
1113
1547
|
}
|
|
1114
|
-
return { targets, force, dryRun, noGitignore, cleanup, unknown };
|
|
1548
|
+
return { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown };
|
|
1115
1549
|
}
|
|
1116
1550
|
function summarizeOutcomes(outcomes) {
|
|
1117
1551
|
const created = [];
|
|
@@ -1136,6 +1570,7 @@ function summarizeOutcomes(outcomes) {
|
|
|
1136
1570
|
async function runSetupIdeFiles(o) {
|
|
1137
1571
|
const outcomes = {};
|
|
1138
1572
|
let cursorGlobalMcp;
|
|
1573
|
+
let jetbrainsMcp;
|
|
1139
1574
|
const repoRoot = await findRepoRoot(o.cwd);
|
|
1140
1575
|
if (!repoRoot) {
|
|
1141
1576
|
return {
|
|
@@ -1236,11 +1671,45 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
1236
1671
|
copilotAndJetBrainsBody("MemoraOne MCP \u2014 JetBrains AI Assistant"),
|
|
1237
1672
|
{ force: o.force, dryRun: o.dryRun }
|
|
1238
1673
|
);
|
|
1674
|
+
try {
|
|
1675
|
+
const homeDir = o.jetbrainsHomeDir ?? os4.homedir();
|
|
1676
|
+
const activePath = o.jetbrainsGlobalMcpConfigPath ?? getJetBrainsGlobalMcpConfigPath(homeDir);
|
|
1677
|
+
const jetbrainsSetup = await setupJetBrainsMcpConfig({
|
|
1678
|
+
homeDir,
|
|
1679
|
+
repoRoot,
|
|
1680
|
+
globalConfigPath: activePath,
|
|
1681
|
+
dryRun: o.dryRun,
|
|
1682
|
+
devMode: o.devMode,
|
|
1683
|
+
repair: o.repair ?? false,
|
|
1684
|
+
verify: o.verifyHandshake ?? !o.dryRun,
|
|
1685
|
+
npxPathOverride: o.npxPathOverride,
|
|
1686
|
+
cliPathOverride: o.cliPathOverride
|
|
1687
|
+
});
|
|
1688
|
+
jetbrainsMcp = {
|
|
1689
|
+
activeConfigPath: activePath,
|
|
1690
|
+
outcome: jetbrainsSetup.outcome,
|
|
1691
|
+
npxPath: jetbrainsSetup.memoraone?.command,
|
|
1692
|
+
backupPath: jetbrainsSetup.backupPath,
|
|
1693
|
+
repairActions: jetbrainsSetup.repairActions,
|
|
1694
|
+
verifyOk: jetbrainsSetup.verifyOk,
|
|
1695
|
+
verifyDetail: jetbrainsSetup.verifyDetail
|
|
1696
|
+
};
|
|
1697
|
+
outcomes[`jetbrains-global:${activePath}`] = jetbrainsSetup.outcome;
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1700
|
+
return {
|
|
1701
|
+
exitCode: 1,
|
|
1702
|
+
repoRoot,
|
|
1703
|
+
outcomes,
|
|
1704
|
+
cursorGlobalMcp,
|
|
1705
|
+
error: message
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1239
1708
|
}
|
|
1240
|
-
return { exitCode: 0, repoRoot, outcomes, cursorGlobalMcp };
|
|
1709
|
+
return { exitCode: 0, repoRoot, outcomes, cursorGlobalMcp, jetbrainsMcp };
|
|
1241
1710
|
}
|
|
1242
1711
|
async function cliSetupIdeFiles(argv) {
|
|
1243
|
-
const { targets, force, dryRun, noGitignore, cleanup, unknown } = parseSetupIdeFlags(argv);
|
|
1712
|
+
const { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown } = parseSetupIdeFlags(argv);
|
|
1244
1713
|
if (unknown.length) {
|
|
1245
1714
|
console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
|
|
1246
1715
|
return 1;
|
|
@@ -1250,7 +1719,9 @@ async function cliSetupIdeFiles(argv) {
|
|
|
1250
1719
|
targets,
|
|
1251
1720
|
force,
|
|
1252
1721
|
dryRun,
|
|
1253
|
-
noGitignore
|
|
1722
|
+
noGitignore,
|
|
1723
|
+
devMode,
|
|
1724
|
+
repair
|
|
1254
1725
|
});
|
|
1255
1726
|
if (result.error) {
|
|
1256
1727
|
console.error(result.error);
|
|
@@ -1266,6 +1737,9 @@ async function cliSetupIdeFiles(argv) {
|
|
|
1266
1737
|
if (targets.cursor && result.cursorGlobalMcp) {
|
|
1267
1738
|
logCursorGlobalMcpCliSummary(result.cursorGlobalMcp, dryRun);
|
|
1268
1739
|
}
|
|
1740
|
+
if (targets.jetbrains && result.jetbrainsMcp) {
|
|
1741
|
+
logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
|
|
1742
|
+
}
|
|
1269
1743
|
summarizeOutcomes(result.outcomes);
|
|
1270
1744
|
if (dryRun) {
|
|
1271
1745
|
console.log("[setup-ide-files] Dry run: no files written.");
|
|
@@ -1302,7 +1776,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
1302
1776
|
}
|
|
1303
1777
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1304
1778
|
console.log(
|
|
1305
|
-
"Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup]\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
|
|
1779
|
+
"Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid> [--ide cursor|copilot-vscode|jetbrains]]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore] [--cleanup] [--dev] [--repair]\n memoraone-mcp cleanup [--project-id <uuid>] [--ide cursor|copilot-vscode|jetbrains] [--dry-run] [--all-projects] [--yes]"
|
|
1306
1780
|
);
|
|
1307
1781
|
process.exit(0);
|
|
1308
1782
|
}
|
|
@@ -1329,8 +1803,8 @@ if (args[0] === "cleanup") {
|
|
|
1329
1803
|
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1330
1804
|
const parts = [];
|
|
1331
1805
|
if (raw !== void 0 && raw.trim() !== "") {
|
|
1332
|
-
for (const p of raw.split(
|
|
1333
|
-
parts.push(
|
|
1806
|
+
for (const p of raw.split(path7.delimiter).map((s) => s.trim()).filter(Boolean)) {
|
|
1807
|
+
parts.push(path7.resolve(p));
|
|
1334
1808
|
}
|
|
1335
1809
|
}
|
|
1336
1810
|
parts.push(process.cwd());
|
|
@@ -1344,10 +1818,10 @@ if (args[0] === "cleanup") {
|
|
|
1344
1818
|
}
|
|
1345
1819
|
return deduped;
|
|
1346
1820
|
}, connectWithRetry = function(socketPath) {
|
|
1347
|
-
return new Promise((
|
|
1821
|
+
return new Promise((resolve7, reject) => {
|
|
1348
1822
|
const tryConnect = (attempt) => {
|
|
1349
1823
|
const socket = net.connect(socketPath, () => {
|
|
1350
|
-
|
|
1824
|
+
resolve7(socket);
|
|
1351
1825
|
});
|
|
1352
1826
|
socket.on("error", (err) => {
|
|
1353
1827
|
if (attempt >= MAX_RETRIES) {
|
|
@@ -1383,7 +1857,7 @@ if (args[0] === "cleanup") {
|
|
|
1383
1857
|
socket = await connectWithRetry(socketPath);
|
|
1384
1858
|
} catch {
|
|
1385
1859
|
log("daemon not running, spawning...");
|
|
1386
|
-
const child = (0,
|
|
1860
|
+
const child = (0, import_node_child_process4.spawn)(
|
|
1387
1861
|
process.execPath,
|
|
1388
1862
|
buildDaemonSpawnArgs(process.argv[1], binding.projectId),
|
|
1389
1863
|
{
|