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.
- package/LICENSE +21 -0
- package/README.md +989 -0
- package/dist/agents/AbstractAgent.js +92 -0
- package/dist/agents/AgentsMdAgent.js +85 -0
- package/dist/agents/AiderAgent.js +108 -0
- package/dist/agents/AmazonQCliAgent.js +103 -0
- package/dist/agents/AmpAgent.js +13 -0
- package/dist/agents/AugmentCodeAgent.js +70 -0
- package/dist/agents/ClaudeAgent.js +95 -0
- package/dist/agents/ClineAgent.js +53 -0
- package/dist/agents/CodexCliAgent.js +143 -0
- package/dist/agents/CopilotAgent.js +43 -0
- package/dist/agents/CrushAgent.js +128 -0
- package/dist/agents/CursorAgent.js +93 -0
- package/dist/agents/FirebaseAgent.js +61 -0
- package/dist/agents/FirebenderAgent.js +205 -0
- package/dist/agents/GeminiCliAgent.js +99 -0
- package/dist/agents/GooseAgent.js +58 -0
- package/dist/agents/IAgent.js +2 -0
- package/dist/agents/JulesAgent.js +14 -0
- package/dist/agents/JunieAgent.js +53 -0
- package/dist/agents/KiloCodeAgent.js +63 -0
- package/dist/agents/KiroAgent.js +50 -0
- package/dist/agents/OpenCodeAgent.js +99 -0
- package/dist/agents/OpenHandsAgent.js +56 -0
- package/dist/agents/QwenCodeAgent.js +82 -0
- package/dist/agents/RooCodeAgent.js +139 -0
- package/dist/agents/TraeAgent.js +54 -0
- package/dist/agents/WarpAgent.js +61 -0
- package/dist/agents/WindsurfAgent.js +27 -0
- package/dist/agents/ZedAgent.js +132 -0
- package/dist/agents/agent-utils.js +37 -0
- package/dist/agents/index.js +77 -0
- package/dist/cli/commands.js +136 -0
- package/dist/cli/handlers.js +221 -0
- package/dist/cli/index.js +5 -0
- package/dist/constants.js +58 -0
- package/dist/core/ConfigLoader.js +274 -0
- package/dist/core/FileSystemUtils.js +421 -0
- package/dist/core/FrontmatterParser.js +142 -0
- package/dist/core/GitignoreUtils.js +171 -0
- package/dist/core/RuleProcessor.js +60 -0
- package/dist/core/SkillsProcessor.js +528 -0
- package/dist/core/SkillsUtils.js +230 -0
- package/dist/core/UnifiedConfigLoader.js +432 -0
- package/dist/core/UnifiedConfigTypes.js +2 -0
- package/dist/core/agent-selection.js +52 -0
- package/dist/core/apply-engine.js +668 -0
- package/dist/core/config-utils.js +30 -0
- package/dist/core/hash.js +24 -0
- package/dist/core/revert-engine.js +413 -0
- package/dist/lib.js +196 -0
- package/dist/mcp/capabilities.js +65 -0
- package/dist/mcp/merge.js +39 -0
- package/dist/mcp/propagateOpenCodeMcp.js +116 -0
- package/dist/mcp/propagateOpenHandsMcp.js +169 -0
- package/dist/mcp/validate.js +17 -0
- package/dist/paths/mcp.js +120 -0
- package/dist/revert.js +186 -0
- package/dist/types.js +2 -0
- package/dist/vscode/settings.js +117 -0
- 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
|
+
}
|