skiller 0.4.3

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +989 -0
  3. package/dist/agents/AbstractAgent.js +92 -0
  4. package/dist/agents/AgentsMdAgent.js +85 -0
  5. package/dist/agents/AiderAgent.js +108 -0
  6. package/dist/agents/AmazonQCliAgent.js +103 -0
  7. package/dist/agents/AmpAgent.js +13 -0
  8. package/dist/agents/AugmentCodeAgent.js +70 -0
  9. package/dist/agents/ClaudeAgent.js +95 -0
  10. package/dist/agents/ClineAgent.js +53 -0
  11. package/dist/agents/CodexCliAgent.js +143 -0
  12. package/dist/agents/CopilotAgent.js +43 -0
  13. package/dist/agents/CrushAgent.js +128 -0
  14. package/dist/agents/CursorAgent.js +93 -0
  15. package/dist/agents/FirebaseAgent.js +61 -0
  16. package/dist/agents/FirebenderAgent.js +205 -0
  17. package/dist/agents/GeminiCliAgent.js +99 -0
  18. package/dist/agents/GooseAgent.js +58 -0
  19. package/dist/agents/IAgent.js +2 -0
  20. package/dist/agents/JulesAgent.js +14 -0
  21. package/dist/agents/JunieAgent.js +53 -0
  22. package/dist/agents/KiloCodeAgent.js +63 -0
  23. package/dist/agents/KiroAgent.js +50 -0
  24. package/dist/agents/OpenCodeAgent.js +99 -0
  25. package/dist/agents/OpenHandsAgent.js +56 -0
  26. package/dist/agents/QwenCodeAgent.js +82 -0
  27. package/dist/agents/RooCodeAgent.js +139 -0
  28. package/dist/agents/TraeAgent.js +54 -0
  29. package/dist/agents/WarpAgent.js +61 -0
  30. package/dist/agents/WindsurfAgent.js +27 -0
  31. package/dist/agents/ZedAgent.js +132 -0
  32. package/dist/agents/agent-utils.js +37 -0
  33. package/dist/agents/index.js +77 -0
  34. package/dist/cli/commands.js +136 -0
  35. package/dist/cli/handlers.js +221 -0
  36. package/dist/cli/index.js +5 -0
  37. package/dist/constants.js +58 -0
  38. package/dist/core/ConfigLoader.js +274 -0
  39. package/dist/core/FileSystemUtils.js +421 -0
  40. package/dist/core/FrontmatterParser.js +142 -0
  41. package/dist/core/GitignoreUtils.js +171 -0
  42. package/dist/core/RuleProcessor.js +60 -0
  43. package/dist/core/SkillsProcessor.js +528 -0
  44. package/dist/core/SkillsUtils.js +230 -0
  45. package/dist/core/UnifiedConfigLoader.js +432 -0
  46. package/dist/core/UnifiedConfigTypes.js +2 -0
  47. package/dist/core/agent-selection.js +52 -0
  48. package/dist/core/apply-engine.js +668 -0
  49. package/dist/core/config-utils.js +30 -0
  50. package/dist/core/hash.js +24 -0
  51. package/dist/core/revert-engine.js +413 -0
  52. package/dist/lib.js +196 -0
  53. package/dist/mcp/capabilities.js +65 -0
  54. package/dist/mcp/merge.js +39 -0
  55. package/dist/mcp/propagateOpenCodeMcp.js +116 -0
  56. package/dist/mcp/propagateOpenHandsMcp.js +169 -0
  57. package/dist/mcp/validate.js +17 -0
  58. package/dist/paths/mcp.js +120 -0
  59. package/dist/revert.js +186 -0
  60. package/dist/types.js +2 -0
  61. package/dist/vscode/settings.js +117 -0
  62. package/package.json +77 -0
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAgentMcpCapabilities = getAgentMcpCapabilities;
4
+ exports.agentSupportsMcp = agentSupportsMcp;
5
+ exports.filterMcpConfigForAgent = filterMcpConfigForAgent;
6
+ /**
7
+ * Derives MCP capabilities for an agent
8
+ */
9
+ function getAgentMcpCapabilities(agent) {
10
+ return {
11
+ supportsStdio: agent.supportsMcpStdio?.() ?? false,
12
+ supportsRemote: agent.supportsMcpRemote?.() ?? false,
13
+ };
14
+ }
15
+ /**
16
+ * Checks if an agent supports any MCP functionality
17
+ */
18
+ function agentSupportsMcp(agent) {
19
+ const capabilities = getAgentMcpCapabilities(agent);
20
+ return capabilities.supportsStdio || capabilities.supportsRemote;
21
+ }
22
+ /**
23
+ * Filters MCP configuration based on agent capabilities
24
+ */
25
+ function filterMcpConfigForAgent(mcpConfig, agent) {
26
+ const capabilities = getAgentMcpCapabilities(agent);
27
+ if (!agentSupportsMcp(agent)) {
28
+ return null;
29
+ }
30
+ const servers = mcpConfig.mcpServers;
31
+ if (!servers) {
32
+ return null;
33
+ }
34
+ const filteredServers = {};
35
+ for (const [serverName, serverConfig] of Object.entries(servers)) {
36
+ const config = serverConfig;
37
+ // Determine server type
38
+ const hasCommand = 'command' in config;
39
+ const hasUrl = 'url' in config;
40
+ const isStdio = hasCommand && !hasUrl;
41
+ const isRemote = hasUrl && !hasCommand;
42
+ // Include server if agent supports its type
43
+ if (isStdio && capabilities.supportsStdio) {
44
+ filteredServers[serverName] = serverConfig;
45
+ }
46
+ else if (isRemote && capabilities.supportsRemote) {
47
+ filteredServers[serverName] = serverConfig;
48
+ }
49
+ else if (isRemote &&
50
+ !capabilities.supportsRemote &&
51
+ capabilities.supportsStdio) {
52
+ // Transform remote server to stdio server using mcp-remote
53
+ const transformedConfig = {
54
+ command: 'npx',
55
+ args: ['-y', 'mcp-remote@latest', config.url],
56
+ ...Object.fromEntries(Object.entries(config).filter(([key]) => key !== 'url')),
57
+ };
58
+ filteredServers[serverName] = transformedConfig;
59
+ }
60
+ // Note: Mixed servers (both command and url) are excluded
61
+ }
62
+ return Object.keys(filteredServers).length > 0
63
+ ? { mcpServers: filteredServers }
64
+ : null;
65
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeMcp = mergeMcp;
4
+ /**
5
+ * Merge native and incoming MCP server configurations according to strategy.
6
+ * @param base Existing native MCP config object.
7
+ * @param incoming Ruler MCP config object.
8
+ * @param strategy Merge strategy: 'merge' to union servers, 'overwrite' to replace.
9
+ * @param serverKey The key to use for servers in the output (e.g., 'servers' for Copilot, 'mcpServers' for others).
10
+ * @returns Merged MCP config object.
11
+ */
12
+ function mergeMcp(base, incoming, strategy, serverKey) {
13
+ if (strategy === 'overwrite') {
14
+ // Ensure the incoming object uses the correct server key.
15
+ // Transform from the standard (Crush) MCP config format
16
+ const incomingServers = incoming[serverKey] ||
17
+ incoming.mcpServers ||
18
+ incoming.mcp ||
19
+ {};
20
+ return {
21
+ [serverKey]: incomingServers,
22
+ };
23
+ }
24
+ const baseServers = base[serverKey] ||
25
+ base.mcpServers ||
26
+ base.mcp ||
27
+ {};
28
+ const incomingServers = incoming[serverKey] ||
29
+ incoming.mcpServers ||
30
+ incoming.mcp ||
31
+ {};
32
+ const mergedServers = { ...baseServers, ...incomingServers };
33
+ const newBase = { ...base };
34
+ delete newBase.mcpServers; // Remove old key if present
35
+ return {
36
+ ...newBase,
37
+ [serverKey]: mergedServers,
38
+ };
39
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.propagateMcpToOpenCode = propagateMcpToOpenCode;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
39
+ const path = __importStar(require("path"));
40
+ function isLocalServer(value) {
41
+ const server = value;
42
+ return (server &&
43
+ (typeof server.command === 'string' || Array.isArray(server.command)));
44
+ }
45
+ function isRemoteServer(value) {
46
+ const server = value;
47
+ return server && typeof server.url === 'string';
48
+ }
49
+ /**
50
+ * Transform ruler MCP configuration to OpenCode's specific format
51
+ */
52
+ function transformToOpenCodeFormat(rulerMcp) {
53
+ const rulerServers = rulerMcp.mcpServers || {};
54
+ const openCodeServers = {};
55
+ for (const [name, serverDef] of Object.entries(rulerServers)) {
56
+ const openCodeServer = {
57
+ type: 'local',
58
+ enabled: true,
59
+ };
60
+ if (isRemoteServer(serverDef)) {
61
+ openCodeServer.type = 'remote';
62
+ openCodeServer.url = serverDef.url;
63
+ if (serverDef.headers) {
64
+ openCodeServer.headers = serverDef.headers;
65
+ }
66
+ }
67
+ else if (isLocalServer(serverDef)) {
68
+ openCodeServer.type = 'local';
69
+ const command = Array.isArray(serverDef.command)
70
+ ? serverDef.command
71
+ : [serverDef.command];
72
+ const args = serverDef.args || [];
73
+ openCodeServer.command = [...command, ...args];
74
+ if (serverDef.env) {
75
+ openCodeServer.environment = serverDef.env;
76
+ }
77
+ }
78
+ else {
79
+ continue;
80
+ }
81
+ openCodeServers[name] = openCodeServer;
82
+ }
83
+ return {
84
+ $schema: 'https://opencode.ai/config.json',
85
+ mcp: openCodeServers,
86
+ };
87
+ }
88
+ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true) {
89
+ const rulerMcp = rulerMcpData || {};
90
+ // Read existing OpenCode config if it exists
91
+ let existingConfig = {};
92
+ try {
93
+ const existingContent = await fs.readFile(openCodeConfigPath, 'utf8');
94
+ existingConfig = JSON.parse(existingContent);
95
+ }
96
+ catch {
97
+ // File doesn't exist, we'll create it
98
+ }
99
+ // Transform ruler MCP to OpenCode format
100
+ const transformedConfig = transformToOpenCodeFormat(rulerMcp);
101
+ // Merge with existing config, preserving non-MCP settings
102
+ const finalConfig = {
103
+ ...existingConfig,
104
+ $schema: transformedConfig.$schema,
105
+ mcp: {
106
+ ...existingConfig.mcp,
107
+ ...transformedConfig.mcp,
108
+ },
109
+ };
110
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openCodeConfigPath));
111
+ if (backup) {
112
+ const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
113
+ await backupFile(openCodeConfigPath);
114
+ }
115
+ await fs.writeFile(openCodeConfigPath, JSON.stringify(finalConfig, null, 2) + '\n');
116
+ }
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.propagateMcpToOpenHands = propagateMcpToOpenHands;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const toml_1 = require("@iarna/toml");
39
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
40
+ const path = __importStar(require("path"));
41
+ function isRulerMcpServer(value) {
42
+ const server = value;
43
+ return (server &&
44
+ (typeof server.command === 'string' || typeof server.url === 'string'));
45
+ }
46
+ function classifyRemoteServer(url) {
47
+ // Heuristic: URLs containing /sse path segments are classified as SSE
48
+ return /\/sse(\/|$)/i.test(url) ? 'sse' : 'shttp';
49
+ }
50
+ function extractApiKey(headers) {
51
+ if (!headers)
52
+ return null;
53
+ const authHeader = headers.Authorization || headers.authorization;
54
+ if (!authHeader)
55
+ return null;
56
+ // Extract Bearer token if that's the only header, or if only Authorization + standard content headers
57
+ const headerCount = Object.keys(headers).length;
58
+ const hasOnlyAuthHeader = headerCount === 1;
59
+ const hasOnlyStandardHeaders = headerCount <= 2 &&
60
+ (headers['Content-Type'] ||
61
+ headers['content-type'] ||
62
+ headers['Accept'] ||
63
+ headers['accept']);
64
+ if ((hasOnlyAuthHeader || hasOnlyStandardHeaders) &&
65
+ authHeader.startsWith('Bearer ')) {
66
+ return authHeader.substring('Bearer '.length);
67
+ }
68
+ return null;
69
+ }
70
+ function createRemoteServerEntry(url, headers) {
71
+ const apiKey = extractApiKey(headers);
72
+ if (apiKey) {
73
+ return { url, api_key: apiKey };
74
+ }
75
+ return url;
76
+ }
77
+ function normalizeRemoteServerArray(entries) {
78
+ // TOML doesn't support mixed types in arrays, so we need to be consistent
79
+ // If any entry is an object, convert all simple URLs to objects
80
+ const hasObjectEntries = entries.some((entry) => typeof entry === 'object');
81
+ if (hasObjectEntries) {
82
+ return entries.map((entry) => {
83
+ if (typeof entry === 'string') {
84
+ return { url: entry };
85
+ }
86
+ return entry;
87
+ });
88
+ }
89
+ // All entries are strings, keep as is
90
+ return entries;
91
+ }
92
+ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true) {
93
+ const rulerMcp = rulerMcpData || {};
94
+ // Always use the legacy Ruler MCP config format as input (top-level "mcpServers" key)
95
+ const rulerServers = rulerMcp.mcpServers || {};
96
+ // Return early if no servers to process
97
+ if (!rulerServers ||
98
+ typeof rulerServers !== 'object' ||
99
+ Object.keys(rulerServers).length === 0) {
100
+ return;
101
+ }
102
+ let config = {};
103
+ try {
104
+ const tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
105
+ config = (0, toml_1.parse)(tomlContent);
106
+ }
107
+ catch {
108
+ // File doesn't exist, we'll create it.
109
+ }
110
+ if (!config.mcp) {
111
+ config.mcp = {};
112
+ }
113
+ if (!config.mcp.stdio_servers) {
114
+ config.mcp.stdio_servers = [];
115
+ }
116
+ if (!config.mcp.sse_servers) {
117
+ config.mcp.sse_servers = [];
118
+ }
119
+ if (!config.mcp.shttp_servers) {
120
+ config.mcp.shttp_servers = [];
121
+ }
122
+ // Build maps for merging existing servers
123
+ const existingStdioServers = new Map(config.mcp.stdio_servers.map((s) => [s.name, s]));
124
+ const existingSseServers = new Map();
125
+ config.mcp.sse_servers.forEach((entry) => {
126
+ const url = typeof entry === 'string' ? entry : entry.url;
127
+ existingSseServers.set(url, entry);
128
+ });
129
+ const existingShttpServers = new Map();
130
+ config.mcp.shttp_servers.forEach((entry) => {
131
+ const url = typeof entry === 'string' ? entry : entry.url;
132
+ existingShttpServers.set(url, entry);
133
+ });
134
+ for (const [name, serverDef] of Object.entries(rulerServers)) {
135
+ if (isRulerMcpServer(serverDef)) {
136
+ if (serverDef.command) {
137
+ // Stdio server
138
+ const { command, args, env } = serverDef;
139
+ const newServer = { name, command };
140
+ if (args)
141
+ newServer.args = args;
142
+ if (env)
143
+ newServer.env = env;
144
+ existingStdioServers.set(name, newServer);
145
+ }
146
+ else if (serverDef.url) {
147
+ // Remote server
148
+ const classification = classifyRemoteServer(serverDef.url);
149
+ const entry = createRemoteServerEntry(serverDef.url, serverDef.headers);
150
+ if (classification === 'sse') {
151
+ existingSseServers.set(serverDef.url, entry);
152
+ }
153
+ else {
154
+ existingShttpServers.set(serverDef.url, entry);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ // Convert maps back to arrays and normalize for TOML compatibility
160
+ config.mcp.stdio_servers = Array.from(existingStdioServers.values());
161
+ config.mcp.sse_servers = normalizeRemoteServerArray(Array.from(existingSseServers.values()));
162
+ config.mcp.shttp_servers = normalizeRemoteServerArray(Array.from(existingShttpServers.values()));
163
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openHandsConfigPath));
164
+ if (backup) {
165
+ const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
166
+ await backupFile(openHandsConfigPath);
167
+ }
168
+ await fs.writeFile(openHandsConfigPath, (0, toml_1.stringify)(config));
169
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateMcp = validateMcp;
4
+ /**
5
+ * Validate the structure of the Ruler MCP JSON config.
6
+ * Minimal validation: ensure 'mcpServers' property exists and is an object.
7
+ * @param data Parsed JSON object from .ruler/mcp.json.
8
+ * @throws Error if validation fails.
9
+ */
10
+ function validateMcp(data) {
11
+ if (!data ||
12
+ typeof data !== 'object' ||
13
+ !('mcpServers' in data) ||
14
+ typeof data.mcpServers !== 'object') {
15
+ throw new Error('[ruler] Invalid MCP config: must contain an object property "mcpServers" (Ruler style)');
16
+ }
17
+ }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getNativeMcpPath = getNativeMcpPath;
37
+ exports.readNativeMcp = readNativeMcp;
38
+ exports.writeNativeMcp = writeNativeMcp;
39
+ const path = __importStar(require("path"));
40
+ const fs_1 = require("fs");
41
+ /** Determine the native MCP config path for a given agent. */
42
+ async function getNativeMcpPath(adapterName, projectRoot) {
43
+ const candidates = [];
44
+ switch (adapterName) {
45
+ case 'GitHub Copilot':
46
+ candidates.push(path.join(projectRoot, '.vscode', 'mcp.json'));
47
+ break;
48
+ case 'Visual Studio':
49
+ candidates.push(path.join(projectRoot, '.mcp.json'));
50
+ candidates.push(path.join(projectRoot, '.vs', 'mcp.json'));
51
+ break;
52
+ case 'Cursor':
53
+ candidates.push(path.join(projectRoot, '.cursor', 'mcp.json'));
54
+ break;
55
+ case 'Windsurf':
56
+ candidates.push(path.join(projectRoot, '.windsurf', 'mcp_config.json'));
57
+ break;
58
+ case 'Claude Code':
59
+ candidates.push(path.join(projectRoot, '.mcp.json'));
60
+ break;
61
+ case 'OpenAI Codex CLI':
62
+ candidates.push(path.join(projectRoot, '.codex', 'config.toml'));
63
+ break;
64
+ case 'Aider':
65
+ candidates.push(path.join(projectRoot, '.mcp.json'));
66
+ break;
67
+ case 'Open Hands':
68
+ // For Open Hands, we target the main config file, not a separate mcp.json
69
+ candidates.push(path.join(projectRoot, 'config.toml'));
70
+ break;
71
+ case 'Gemini CLI':
72
+ candidates.push(path.join(projectRoot, '.gemini', 'settings.json'));
73
+ break;
74
+ case 'Qwen Code':
75
+ candidates.push(path.join(projectRoot, '.qwen', 'settings.json'));
76
+ break;
77
+ case 'Kilo Code':
78
+ candidates.push(path.join(projectRoot, '.kilocode', 'mcp.json'));
79
+ break;
80
+ case 'OpenCode':
81
+ candidates.push(path.join(projectRoot, 'opencode.json'));
82
+ break;
83
+ case 'Firebase Studio':
84
+ candidates.push(path.join(projectRoot, '.idx', 'mcp.json'));
85
+ break;
86
+ case 'Zed':
87
+ // Only consider project-local Zed settings (avoid writing to user home directory)
88
+ candidates.push(path.join(projectRoot, '.zed', 'settings.json'));
89
+ break;
90
+ default:
91
+ return null;
92
+ }
93
+ for (const p of candidates) {
94
+ try {
95
+ await fs_1.promises.access(p);
96
+ return p;
97
+ }
98
+ catch {
99
+ // continue
100
+ }
101
+ }
102
+ // default to first candidate if none exist
103
+ return candidates.length > 0 ? candidates[0] : null;
104
+ }
105
+ /** Read native MCP config from disk, or return empty object if missing/invalid. */
106
+ async function readNativeMcp(filePath) {
107
+ try {
108
+ const text = await fs_1.promises.readFile(filePath, 'utf8');
109
+ return JSON.parse(text);
110
+ }
111
+ catch {
112
+ return {};
113
+ }
114
+ }
115
+ /** Write native MCP config to disk, creating parent directories as needed. */
116
+ async function writeNativeMcp(filePath, data) {
117
+ await fs_1.promises.mkdir(path.dirname(filePath), { recursive: true });
118
+ const text = JSON.stringify(data, null, 2) + '\n';
119
+ await fs_1.promises.writeFile(filePath, text, 'utf8');
120
+ }