@memoraone/mcp 0.1.28 → 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 +785 -63
- 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);
|
|
@@ -685,13 +686,648 @@ async function cliCleanup(argv) {
|
|
|
685
686
|
return result.exitCode;
|
|
686
687
|
}
|
|
687
688
|
|
|
689
|
+
// src/cursorGlobalMcpConfig.ts
|
|
690
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
691
|
+
var os2 = __toESM(require("os"), 1);
|
|
692
|
+
var path4 = __toESM(require("path"), 1);
|
|
693
|
+
var import_node_child_process2 = require("child_process");
|
|
694
|
+
var import_node_util2 = require("util");
|
|
695
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
696
|
+
function buildMemoraoneCursorMcpServer(npxPath) {
|
|
697
|
+
return {
|
|
698
|
+
command: npxPath,
|
|
699
|
+
args: ["-y", "@memoraone/mcp@latest"],
|
|
700
|
+
env: {
|
|
701
|
+
MEMORAONE_API_URL: "https://api.memoraone.com",
|
|
702
|
+
MEMORAONE_IDE_TYPE: "cursor"
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
async function pathExists(filePath) {
|
|
707
|
+
try {
|
|
708
|
+
await fs4.access(filePath);
|
|
709
|
+
return true;
|
|
710
|
+
} catch {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function stripLeadingLineComments(text) {
|
|
715
|
+
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
716
|
+
}
|
|
717
|
+
function getKnownCursorGlobalMcpConfigCandidates(homeDir) {
|
|
718
|
+
return [path4.join(homeDir, ".cursor", "mcp.json")];
|
|
719
|
+
}
|
|
720
|
+
async function detectCursorGlobalMcpConfig(options) {
|
|
721
|
+
if (options?.explicitPath) {
|
|
722
|
+
return { ok: true, path: options.explicitPath, detectedExisting: await pathExists(options.explicitPath) };
|
|
723
|
+
}
|
|
724
|
+
const homeDir = options?.homeDir ?? os2.homedir();
|
|
725
|
+
const candidates = getKnownCursorGlobalMcpConfigCandidates(homeDir);
|
|
726
|
+
const existing = [];
|
|
727
|
+
for (const candidate of candidates) {
|
|
728
|
+
if (await pathExists(candidate)) existing.push(candidate);
|
|
729
|
+
}
|
|
730
|
+
if (existing.length > 1) {
|
|
731
|
+
return {
|
|
732
|
+
ok: false,
|
|
733
|
+
error: "[setup-ide-files] Multiple Cursor global MCP config paths found. Specify one explicitly.",
|
|
734
|
+
candidates: existing
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
if (existing.length === 1) {
|
|
738
|
+
return { ok: true, path: existing[0], detectedExisting: true };
|
|
739
|
+
}
|
|
740
|
+
const defaultPath = candidates[0];
|
|
741
|
+
if (!defaultPath) {
|
|
742
|
+
return {
|
|
743
|
+
ok: false,
|
|
744
|
+
error: "[setup-ide-files] No known Cursor global MCP config path.",
|
|
745
|
+
candidates: []
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return { ok: true, path: defaultPath, detectedExisting: false };
|
|
749
|
+
}
|
|
750
|
+
function formatBackupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
751
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
752
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
753
|
+
}
|
|
754
|
+
async function isWorkingNpx(npxPath) {
|
|
755
|
+
try {
|
|
756
|
+
if (!await pathExists(npxPath)) return false;
|
|
757
|
+
if (process.platform !== "win32") {
|
|
758
|
+
try {
|
|
759
|
+
await fs4.access(npxPath, fs4.constants.X_OK);
|
|
760
|
+
} catch {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
await execFileAsync2(npxPath, ["--version"], { timeout: 1e4 });
|
|
765
|
+
return true;
|
|
766
|
+
} catch {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
async function resolveNpxPath() {
|
|
771
|
+
const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
772
|
+
const candidates = [];
|
|
773
|
+
if (process.platform === "darwin") {
|
|
774
|
+
candidates.push("/opt/homebrew/bin/npx", "/usr/local/bin/npx");
|
|
775
|
+
} else if (process.platform === "linux") {
|
|
776
|
+
candidates.push("/usr/local/bin/npx");
|
|
777
|
+
}
|
|
778
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
779
|
+
for (const dir of (process.env.PATH ?? "").split(pathSep)) {
|
|
780
|
+
if (!dir) continue;
|
|
781
|
+
candidates.push(path4.join(dir, npxName));
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
const lookupCmd = process.platform === "win32" ? "where" : "which";
|
|
785
|
+
const { stdout } = await execFileAsync2(lookupCmd, [npxName], { timeout: 5e3 });
|
|
786
|
+
const first = stdout.trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
787
|
+
if (first) candidates.unshift(first);
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
const seen = /* @__PURE__ */ new Set();
|
|
791
|
+
for (const candidate of candidates) {
|
|
792
|
+
const abs = path4.isAbsolute(candidate) ? candidate : path4.resolve(candidate);
|
|
793
|
+
const key = process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
794
|
+
if (seen.has(key)) continue;
|
|
795
|
+
seen.add(key);
|
|
796
|
+
if (await isWorkingNpx(abs)) return abs;
|
|
797
|
+
}
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
function mergeCursorGlobalMcpConfigObject(existing, npxPath) {
|
|
801
|
+
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
802
|
+
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
803
|
+
mcpServers.memoraone = buildMemoraoneCursorMcpServer(npxPath);
|
|
804
|
+
return { ...base, mcpServers };
|
|
805
|
+
}
|
|
806
|
+
function memoraoneServerMatches(server, npxPath) {
|
|
807
|
+
if (!server || typeof server !== "object") return false;
|
|
808
|
+
const s = server;
|
|
809
|
+
if (s.command !== npxPath) return false;
|
|
810
|
+
if (!Array.isArray(s.args) || s.args.length !== 2) return false;
|
|
811
|
+
if (s.args[0] !== "-y" || s.args[1] !== "@memoraone/mcp@latest") return false;
|
|
812
|
+
const env = s.env;
|
|
813
|
+
if (!env || typeof env !== "object") return false;
|
|
814
|
+
const e = env;
|
|
815
|
+
return e.MEMORAONE_API_URL === "https://api.memoraone.com" && e.MEMORAONE_IDE_TYPE === "cursor";
|
|
816
|
+
}
|
|
817
|
+
function validateCursorGlobalMcpConfig(parsed, npxPath) {
|
|
818
|
+
if (!parsed || typeof parsed !== "object") {
|
|
819
|
+
throw new Error("[setup-ide-files] Cursor global MCP config must be a JSON object.");
|
|
820
|
+
}
|
|
821
|
+
const mcpServers = parsed.mcpServers;
|
|
822
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
823
|
+
throw new Error("[setup-ide-files] Cursor global MCP config missing mcpServers object.");
|
|
824
|
+
}
|
|
825
|
+
const memoraone = mcpServers.memoraone;
|
|
826
|
+
if (!memoraoneServerMatches(memoraone, npxPath)) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
"[setup-ide-files] Cursor global MCP config mcpServers.memoraone is missing or invalid."
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
async function setupCursorGlobalMcpConfig(options) {
|
|
833
|
+
const { configPath, npxPath, dryRun } = options;
|
|
834
|
+
const existed = await pathExists(configPath);
|
|
835
|
+
let existing = null;
|
|
836
|
+
if (existed) {
|
|
837
|
+
const raw = await fs4.readFile(configPath, "utf8");
|
|
838
|
+
try {
|
|
839
|
+
existing = JSON.parse(stripLeadingLineComments(raw));
|
|
840
|
+
} catch {
|
|
841
|
+
throw new Error(
|
|
842
|
+
`[setup-ide-files] Invalid JSON in Cursor global MCP config: ${configPath}`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const merged = mergeCursorGlobalMcpConfigObject(existing, npxPath);
|
|
847
|
+
const body = JSON.stringify(merged, null, 2) + "\n";
|
|
848
|
+
if (existed) {
|
|
849
|
+
const currentMemoraone = existing && typeof existing.mcpServers === "object" && existing.mcpServers !== null && !Array.isArray(existing.mcpServers) ? existing.mcpServers.memoraone : void 0;
|
|
850
|
+
if (memoraoneServerMatches(currentMemoraone, npxPath)) {
|
|
851
|
+
return { outcome: "skipped" };
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (dryRun) {
|
|
855
|
+
return { outcome: existed ? "updated" : "created" };
|
|
856
|
+
}
|
|
857
|
+
let backupPath;
|
|
858
|
+
if (existed) {
|
|
859
|
+
backupPath = `${configPath}.backup-${formatBackupTimestamp()}`;
|
|
860
|
+
await fs4.copyFile(configPath, backupPath);
|
|
861
|
+
}
|
|
862
|
+
await fs4.mkdir(path4.dirname(configPath), { recursive: true });
|
|
863
|
+
await fs4.writeFile(configPath, body, "utf8");
|
|
864
|
+
const verifyRaw = await fs4.readFile(configPath, "utf8");
|
|
865
|
+
const verifyParsed = JSON.parse(stripLeadingLineComments(verifyRaw));
|
|
866
|
+
validateCursorGlobalMcpConfig(verifyParsed, npxPath);
|
|
867
|
+
return { outcome: existed ? "updated" : "created", backupPath };
|
|
868
|
+
}
|
|
869
|
+
function logCursorGlobalMcpCliSummary(info, dryRun) {
|
|
870
|
+
const { configPath, npxPath, backupPath, outcome } = info;
|
|
871
|
+
console.log(`[setup-ide-files] Cursor global MCP config: ${configPath}`);
|
|
872
|
+
console.log(`[setup-ide-files] Resolved npx: ${npxPath}`);
|
|
873
|
+
if (backupPath) {
|
|
874
|
+
console.log(`[setup-ide-files] Cursor global MCP config backup: ${backupPath}`);
|
|
875
|
+
}
|
|
876
|
+
if (outcome === "created") {
|
|
877
|
+
console.log(
|
|
878
|
+
dryRun ? `[setup-ide-files] Cursor global MCP config would be created: ${configPath}` : `[setup-ide-files] Cursor global MCP config created: ${configPath}`
|
|
879
|
+
);
|
|
880
|
+
} else if (outcome === "updated") {
|
|
881
|
+
console.log(
|
|
882
|
+
dryRun ? `[setup-ide-files] Cursor global MCP config would be updated: ${configPath}` : `[setup-ide-files] Cursor global MCP config updated: ${configPath}`
|
|
883
|
+
);
|
|
884
|
+
} else if (outcome === "skipped") {
|
|
885
|
+
console.log(`[setup-ide-files] Cursor global MCP config unchanged: ${configPath}`);
|
|
886
|
+
}
|
|
887
|
+
console.log(
|
|
888
|
+
"[setup-ide-files] Repo-level .cursor/mcp.json was not created or modified."
|
|
889
|
+
);
|
|
890
|
+
console.log(
|
|
891
|
+
"[setup-ide-files] Fully quit Cursor and reopen this repo for MCP changes to take effect."
|
|
892
|
+
);
|
|
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
|
+
|
|
688
1324
|
// src/setupIdeFiles.ts
|
|
689
1325
|
var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
|
|
690
1326
|
var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
|
|
691
1327
|
var GITIGNORE_MEMORAONE_ENTRY = "memoraone.m1";
|
|
692
|
-
function buildMemoraoneMcpServer(ideType) {
|
|
1328
|
+
function buildMemoraoneMcpServer(ideType, command = "npx") {
|
|
693
1329
|
return {
|
|
694
|
-
command
|
|
1330
|
+
command,
|
|
695
1331
|
args: ["-y", "@memoraone/mcp@latest"],
|
|
696
1332
|
env: {
|
|
697
1333
|
MEMORAONE_API_URL: "https://api.memoraone.com",
|
|
@@ -700,15 +1336,15 @@ function buildMemoraoneMcpServer(ideType) {
|
|
|
700
1336
|
};
|
|
701
1337
|
}
|
|
702
1338
|
function assertUnderRepoRoot(repoRoot, absPath) {
|
|
703
|
-
const normRoot =
|
|
704
|
-
const normPath =
|
|
705
|
-
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)) {
|
|
706
1342
|
throw new Error(`[setup-ide-files] Refusing to write outside repo root: ${absPath}`);
|
|
707
1343
|
}
|
|
708
1344
|
}
|
|
709
|
-
async function
|
|
1345
|
+
async function pathExists3(filePath) {
|
|
710
1346
|
try {
|
|
711
|
-
await
|
|
1347
|
+
await fs6.access(filePath);
|
|
712
1348
|
return true;
|
|
713
1349
|
} catch {
|
|
714
1350
|
return false;
|
|
@@ -729,12 +1365,12 @@ ${GITIGNORE_MEMORAONE_ENTRY}
|
|
|
729
1365
|
}
|
|
730
1366
|
async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
731
1367
|
if (opts.noGitignore) return "skipped";
|
|
732
|
-
const abs =
|
|
1368
|
+
const abs = path6.join(repoRoot, ".gitignore");
|
|
733
1369
|
assertUnderRepoRoot(repoRoot, abs);
|
|
734
1370
|
let prior = "";
|
|
735
1371
|
let existed = false;
|
|
736
1372
|
try {
|
|
737
|
-
prior = await
|
|
1373
|
+
prior = await fs6.readFile(abs, "utf8");
|
|
738
1374
|
existed = true;
|
|
739
1375
|
} catch (err) {
|
|
740
1376
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
@@ -745,25 +1381,25 @@ async function ensureGitignoreMemoraone(repoRoot, opts) {
|
|
|
745
1381
|
const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
746
1382
|
const next = (existed ? prior : "") + separator + block;
|
|
747
1383
|
if (opts.dryRun) return existed ? "updated" : "created";
|
|
748
|
-
await
|
|
1384
|
+
await fs6.writeFile(abs, next, "utf8");
|
|
749
1385
|
return existed ? "updated" : "created";
|
|
750
1386
|
}
|
|
751
1387
|
async function findRepoRoot(startDir) {
|
|
752
|
-
let current =
|
|
753
|
-
const root =
|
|
1388
|
+
let current = path6.resolve(startDir);
|
|
1389
|
+
const root = path6.parse(current).root;
|
|
754
1390
|
while (true) {
|
|
755
|
-
const gitPath =
|
|
756
|
-
const m1Path =
|
|
757
|
-
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)) {
|
|
758
1394
|
return current;
|
|
759
1395
|
}
|
|
760
1396
|
if (current === root) {
|
|
761
1397
|
return null;
|
|
762
1398
|
}
|
|
763
|
-
current =
|
|
1399
|
+
current = path6.dirname(current);
|
|
764
1400
|
}
|
|
765
1401
|
}
|
|
766
|
-
function
|
|
1402
|
+
function stripLeadingLineComments3(text) {
|
|
767
1403
|
return text.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
|
|
768
1404
|
}
|
|
769
1405
|
function cursorRuleBody() {
|
|
@@ -813,50 +1449,43 @@ function buildVscodeMcpJsonBody(existing) {
|
|
|
813
1449
|
const merged = { ...base, servers };
|
|
814
1450
|
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
815
1451
|
}
|
|
816
|
-
function buildCursorMcpJsonBody(existing) {
|
|
817
|
-
const base = existing && typeof existing === "object" ? { ...existing } : { mcpServers: {} };
|
|
818
|
-
const mcpServers = typeof base.mcpServers === "object" && base.mcpServers !== null && !Array.isArray(base.mcpServers) ? { ...base.mcpServers } : {};
|
|
819
|
-
mcpServers.memoraone = buildMemoraoneMcpServer("cursor");
|
|
820
|
-
const merged = { ...base, mcpServers };
|
|
821
|
-
return mcpJsonHeader() + JSON.stringify(merged, null, 2) + "\n";
|
|
822
|
-
}
|
|
823
1452
|
async function writeManagedMarkdown(repoRoot, relPath, fullContent, opts) {
|
|
824
|
-
const abs =
|
|
1453
|
+
const abs = path6.join(repoRoot, relPath);
|
|
825
1454
|
assertUnderRepoRoot(repoRoot, abs);
|
|
826
1455
|
let prior = "";
|
|
827
1456
|
let existed = false;
|
|
828
1457
|
try {
|
|
829
|
-
prior = await
|
|
1458
|
+
prior = await fs6.readFile(abs, "utf8");
|
|
830
1459
|
existed = true;
|
|
831
1460
|
} catch (err) {
|
|
832
1461
|
if (err?.code !== "ENOENT") throw err;
|
|
833
1462
|
}
|
|
834
1463
|
if (!existed) {
|
|
835
1464
|
if (opts.dryRun) return "created";
|
|
836
|
-
await
|
|
837
|
-
await
|
|
1465
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1466
|
+
await fs6.writeFile(abs, fullContent, "utf8");
|
|
838
1467
|
return "created";
|
|
839
1468
|
}
|
|
840
1469
|
if (prior.includes(MANAGED_MARKER)) {
|
|
841
1470
|
if (prior === fullContent) return "skipped";
|
|
842
1471
|
if (opts.dryRun) return "updated";
|
|
843
|
-
await
|
|
844
|
-
await
|
|
1472
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1473
|
+
await fs6.writeFile(abs, fullContent, "utf8");
|
|
845
1474
|
return "updated";
|
|
846
1475
|
}
|
|
847
1476
|
if (!opts.force) return "skipped-untracked";
|
|
848
1477
|
if (opts.dryRun) return "updated";
|
|
849
|
-
await
|
|
850
|
-
await
|
|
1478
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1479
|
+
await fs6.writeFile(abs, fullContent, "utf8");
|
|
851
1480
|
return "updated";
|
|
852
1481
|
}
|
|
853
1482
|
async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
854
|
-
const abs =
|
|
1483
|
+
const abs = path6.join(repoRoot, relPath);
|
|
855
1484
|
assertUnderRepoRoot(repoRoot, abs);
|
|
856
1485
|
let raw = "";
|
|
857
1486
|
let existed = false;
|
|
858
1487
|
try {
|
|
859
|
-
raw = await
|
|
1488
|
+
raw = await fs6.readFile(abs, "utf8");
|
|
860
1489
|
existed = true;
|
|
861
1490
|
} catch (err) {
|
|
862
1491
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -864,15 +1493,15 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
864
1493
|
if (!existed) {
|
|
865
1494
|
const body = buildBody(null);
|
|
866
1495
|
if (opts.dryRun) return "created";
|
|
867
|
-
await
|
|
868
|
-
await
|
|
1496
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1497
|
+
await fs6.writeFile(abs, body, "utf8");
|
|
869
1498
|
return "created";
|
|
870
1499
|
}
|
|
871
1500
|
const managed = raw.includes(MANAGED_MARKER);
|
|
872
1501
|
if (!managed && !opts.force) return "skipped-untracked";
|
|
873
1502
|
let parsed = null;
|
|
874
1503
|
try {
|
|
875
|
-
parsed = JSON.parse(
|
|
1504
|
+
parsed = JSON.parse(stripLeadingLineComments3(raw));
|
|
876
1505
|
} catch {
|
|
877
1506
|
parsed = null;
|
|
878
1507
|
}
|
|
@@ -880,8 +1509,8 @@ async function writeIdeMcpJson(repoRoot, relPath, buildBody, opts) {
|
|
|
880
1509
|
const next = buildBody(parsed);
|
|
881
1510
|
if (managed && next === raw) return "skipped";
|
|
882
1511
|
if (opts.dryRun) return "updated";
|
|
883
|
-
await
|
|
884
|
-
await
|
|
1512
|
+
await fs6.mkdir(path6.dirname(abs), { recursive: true });
|
|
1513
|
+
await fs6.writeFile(abs, next, "utf8");
|
|
885
1514
|
return "updated";
|
|
886
1515
|
}
|
|
887
1516
|
function parseSetupIdeFlags(argv) {
|
|
@@ -893,6 +1522,8 @@ function parseSetupIdeFlags(argv) {
|
|
|
893
1522
|
let dryRun = false;
|
|
894
1523
|
let noGitignore = false;
|
|
895
1524
|
let cleanup = false;
|
|
1525
|
+
let devMode = false;
|
|
1526
|
+
let repair = false;
|
|
896
1527
|
const unknown = [];
|
|
897
1528
|
for (const a of argv) {
|
|
898
1529
|
if (a === "--cursor") cursor = true;
|
|
@@ -903,6 +1534,8 @@ function parseSetupIdeFlags(argv) {
|
|
|
903
1534
|
else if (a === "--dry-run") dryRun = true;
|
|
904
1535
|
else if (a === "--no-gitignore") noGitignore = true;
|
|
905
1536
|
else if (a === "--cleanup") cleanup = true;
|
|
1537
|
+
else if (a === "--dev") devMode = true;
|
|
1538
|
+
else if (a === "--repair") repair = true;
|
|
906
1539
|
else if (a.startsWith("-")) unknown.push(a);
|
|
907
1540
|
}
|
|
908
1541
|
const specific = cursor || vscode || jetbrains;
|
|
@@ -912,7 +1545,7 @@ function parseSetupIdeFlags(argv) {
|
|
|
912
1545
|
} else {
|
|
913
1546
|
targets = { cursor, vscode, jetbrains };
|
|
914
1547
|
}
|
|
915
|
-
return { targets, force, dryRun, noGitignore, cleanup, unknown };
|
|
1548
|
+
return { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown };
|
|
916
1549
|
}
|
|
917
1550
|
function summarizeOutcomes(outcomes) {
|
|
918
1551
|
const created = [];
|
|
@@ -936,6 +1569,8 @@ function summarizeOutcomes(outcomes) {
|
|
|
936
1569
|
}
|
|
937
1570
|
async function runSetupIdeFiles(o) {
|
|
938
1571
|
const outcomes = {};
|
|
1572
|
+
let cursorGlobalMcp;
|
|
1573
|
+
let jetbrainsMcp;
|
|
939
1574
|
const repoRoot = await findRepoRoot(o.cwd);
|
|
940
1575
|
if (!repoRoot) {
|
|
941
1576
|
return {
|
|
@@ -955,21 +1590,62 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
955
1590
|
|
|
956
1591
|
` + cursorRuleBody();
|
|
957
1592
|
if (o.targets.cursor) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1593
|
+
const detection = await detectCursorGlobalMcpConfig({
|
|
1594
|
+
homeDir: o.homeDir,
|
|
1595
|
+
explicitPath: o.cursorGlobalMcpConfigPath
|
|
1596
|
+
});
|
|
1597
|
+
if (!detection.ok) {
|
|
1598
|
+
return {
|
|
1599
|
+
exitCode: 1,
|
|
1600
|
+
repoRoot,
|
|
1601
|
+
outcomes,
|
|
1602
|
+
error: `${detection.error}
|
|
1603
|
+
${detection.candidates.join("\n ")}`
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
let npxPath;
|
|
1607
|
+
if (o.npxPathOverride !== void 0) {
|
|
1608
|
+
npxPath = o.npxPathOverride;
|
|
1609
|
+
} else {
|
|
1610
|
+
npxPath = await resolveNpxPath();
|
|
1611
|
+
}
|
|
1612
|
+
if (!npxPath) {
|
|
1613
|
+
return {
|
|
1614
|
+
exitCode: 1,
|
|
1615
|
+
repoRoot,
|
|
1616
|
+
outcomes,
|
|
1617
|
+
error: "[setup-ide-files] Could not resolve a working npx executable. Install Node.js/npm or ensure npx is on PATH before configuring Cursor global MCP."
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
967
1620
|
outcomes[".cursor/rules/memoraone-mcp.mdc"] = await writeManagedMarkdown(
|
|
968
1621
|
repoRoot,
|
|
969
1622
|
".cursor/rules/memoraone-mcp.mdc",
|
|
970
1623
|
cursorContent,
|
|
971
1624
|
{ force: o.force, dryRun: o.dryRun }
|
|
972
1625
|
);
|
|
1626
|
+
try {
|
|
1627
|
+
const globalSetup = await setupCursorGlobalMcpConfig({
|
|
1628
|
+
configPath: detection.path,
|
|
1629
|
+
npxPath,
|
|
1630
|
+
dryRun: o.dryRun
|
|
1631
|
+
});
|
|
1632
|
+
cursorGlobalMcp = {
|
|
1633
|
+
configPath: detection.path,
|
|
1634
|
+
outcome: globalSetup.outcome,
|
|
1635
|
+
npxPath,
|
|
1636
|
+
backupPath: globalSetup.backupPath
|
|
1637
|
+
};
|
|
1638
|
+
outcomes[`cursor-global:${detection.path}`] = globalSetup.outcome;
|
|
1639
|
+
} catch (err) {
|
|
1640
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1641
|
+
return {
|
|
1642
|
+
exitCode: 1,
|
|
1643
|
+
repoRoot,
|
|
1644
|
+
outcomes,
|
|
1645
|
+
cursorGlobalMcp: { configPath: detection.path, outcome: "skipped", npxPath },
|
|
1646
|
+
error: message
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
973
1649
|
}
|
|
974
1650
|
if (o.targets.vscode) {
|
|
975
1651
|
outcomes[".vscode/mcp.json"] = await writeIdeMcpJson(
|
|
@@ -995,11 +1671,45 @@ description: MemoraOne MCP \u2014 IDE agent instructions
|
|
|
995
1671
|
copilotAndJetBrainsBody("MemoraOne MCP \u2014 JetBrains AI Assistant"),
|
|
996
1672
|
{ force: o.force, dryRun: o.dryRun }
|
|
997
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
|
+
}
|
|
998
1708
|
}
|
|
999
|
-
return { exitCode: 0, repoRoot, outcomes };
|
|
1709
|
+
return { exitCode: 0, repoRoot, outcomes, cursorGlobalMcp, jetbrainsMcp };
|
|
1000
1710
|
}
|
|
1001
1711
|
async function cliSetupIdeFiles(argv) {
|
|
1002
|
-
const { targets, force, dryRun, noGitignore, cleanup, unknown } = parseSetupIdeFlags(argv);
|
|
1712
|
+
const { targets, force, dryRun, noGitignore, cleanup, devMode, repair, unknown } = parseSetupIdeFlags(argv);
|
|
1003
1713
|
if (unknown.length) {
|
|
1004
1714
|
console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
|
|
1005
1715
|
return 1;
|
|
@@ -1009,15 +1719,27 @@ async function cliSetupIdeFiles(argv) {
|
|
|
1009
1719
|
targets,
|
|
1010
1720
|
force,
|
|
1011
1721
|
dryRun,
|
|
1012
|
-
noGitignore
|
|
1722
|
+
noGitignore,
|
|
1723
|
+
devMode,
|
|
1724
|
+
repair
|
|
1013
1725
|
});
|
|
1014
1726
|
if (result.error) {
|
|
1015
1727
|
console.error(result.error);
|
|
1728
|
+
if (result.repoRoot) {
|
|
1729
|
+
console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
|
|
1730
|
+
}
|
|
1731
|
+
summarizeOutcomes(result.outcomes);
|
|
1016
1732
|
return result.exitCode;
|
|
1017
1733
|
}
|
|
1018
1734
|
if (result.repoRoot) {
|
|
1019
1735
|
console.log(`[setup-ide-files] Repo root: ${result.repoRoot}`);
|
|
1020
1736
|
}
|
|
1737
|
+
if (targets.cursor && result.cursorGlobalMcp) {
|
|
1738
|
+
logCursorGlobalMcpCliSummary(result.cursorGlobalMcp, dryRun);
|
|
1739
|
+
}
|
|
1740
|
+
if (targets.jetbrains && result.jetbrainsMcp) {
|
|
1741
|
+
logJetBrainsMcpCliSummary(result.jetbrainsMcp, dryRun);
|
|
1742
|
+
}
|
|
1021
1743
|
summarizeOutcomes(result.outcomes);
|
|
1022
1744
|
if (dryRun) {
|
|
1023
1745
|
console.log("[setup-ide-files] Dry run: no files written.");
|
|
@@ -1054,7 +1776,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
1054
1776
|
}
|
|
1055
1777
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1056
1778
|
console.log(
|
|
1057
|
-
"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]"
|
|
1058
1780
|
);
|
|
1059
1781
|
process.exit(0);
|
|
1060
1782
|
}
|
|
@@ -1081,8 +1803,8 @@ if (args[0] === "cleanup") {
|
|
|
1081
1803
|
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1082
1804
|
const parts = [];
|
|
1083
1805
|
if (raw !== void 0 && raw.trim() !== "") {
|
|
1084
|
-
for (const p of raw.split(
|
|
1085
|
-
parts.push(
|
|
1806
|
+
for (const p of raw.split(path7.delimiter).map((s) => s.trim()).filter(Boolean)) {
|
|
1807
|
+
parts.push(path7.resolve(p));
|
|
1086
1808
|
}
|
|
1087
1809
|
}
|
|
1088
1810
|
parts.push(process.cwd());
|
|
@@ -1096,10 +1818,10 @@ if (args[0] === "cleanup") {
|
|
|
1096
1818
|
}
|
|
1097
1819
|
return deduped;
|
|
1098
1820
|
}, connectWithRetry = function(socketPath) {
|
|
1099
|
-
return new Promise((
|
|
1821
|
+
return new Promise((resolve7, reject) => {
|
|
1100
1822
|
const tryConnect = (attempt) => {
|
|
1101
1823
|
const socket = net.connect(socketPath, () => {
|
|
1102
|
-
|
|
1824
|
+
resolve7(socket);
|
|
1103
1825
|
});
|
|
1104
1826
|
socket.on("error", (err) => {
|
|
1105
1827
|
if (attempt >= MAX_RETRIES) {
|
|
@@ -1135,7 +1857,7 @@ if (args[0] === "cleanup") {
|
|
|
1135
1857
|
socket = await connectWithRetry(socketPath);
|
|
1136
1858
|
} catch {
|
|
1137
1859
|
log("daemon not running, spawning...");
|
|
1138
|
-
const child = (0,
|
|
1860
|
+
const child = (0, import_node_child_process4.spawn)(
|
|
1139
1861
|
process.execPath,
|
|
1140
1862
|
buildDaemonSpawnArgs(process.argv[1], binding.projectId),
|
|
1141
1863
|
{
|