@agentled/cli
Version:
CLI for Agentled — manage workflows, apps, and knowledge from the command line. Zero context-window cost for AI agents.
201 lines • 7.53 kB
JavaScript
/**
* MCP client detection and config writing for `agentled setup`.
*
* Lifted from agentled-mcp-server/src/setup.ts so the CLI orchestrates the
* full onboarding without depending on the mcp-server's setup flag (which
* is being deprecated in favor of `agentled setup`).
*
* Detects Claude Code, Codex, Claude Desktop, Cursor, Windsurf and writes
* the AgentLed MCP server entry into the appropriate config location.
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { homedir } from 'node:os';
import { spawn } from 'node:child_process';
export function getClientLabel(client) {
switch (client) {
case 'claude-code': return 'Claude Code';
case 'claude-desktop': return 'Claude Desktop';
case 'codex': return 'Codex';
case 'cursor': return 'Cursor';
case 'windsurf': return 'Windsurf';
default: return 'Unknown';
}
}
export function detectMcpClient() {
if (process.env.CODEX_SHELL || process.env.CODEX_THREAD_ID || process.env.CODEX_CI)
return 'codex';
if (existsSync(join(homedir(), '.claude')))
return 'claude-code';
if (existsSync(join(homedir(), '.codex')))
return 'codex';
const desktop = getClaudeDesktopConfigPath();
if (desktop && existsSync(dirname(desktop)))
return 'claude-desktop';
if (existsSync(join(homedir(), '.cursor')))
return 'cursor';
if (existsSync(join(homedir(), '.windsurf')))
return 'windsurf';
return 'unknown';
}
function getClaudeDesktopConfigPath() {
if (process.platform === 'darwin') {
return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
}
if (process.platform === 'win32') {
return join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');
}
return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
}
function getMcpJsonPath(client) {
if (client === 'cursor')
return join(homedir(), '.cursor', 'mcp.json');
if (client === 'windsurf')
return join(homedir(), '.windsurf', 'mcp.json');
return null;
}
function configureClaudeCode(apiKey, baseUrl) {
return new Promise(resolve => {
try {
// Server name must precede -e/--env (Claude Code 2.1+ parser quirk).
const child = spawn('claude', [
'mcp', 'add',
'--transport', 'stdio',
'--scope', 'user',
'agentled',
'-e', `AGENTLED_API_KEY=${apiKey}`,
...(baseUrl !== 'https://www.agentled.app' ? ['-e', `AGENTLED_URL=${baseUrl}`] : []),
'--', 'npx', '-y', '@agentled/mcp-server',
], { stdio: 'inherit' });
child.on('close', code => resolve(code === 0));
child.on('error', () => resolve(false));
}
catch {
resolve(false);
}
});
}
function configureCodex(apiKey, baseUrl) {
return new Promise(resolve => {
try {
const child = spawn('codex', [
'mcp', 'add', 'agentled',
'--env', `AGENTLED_API_KEY=${apiKey}`,
...(baseUrl !== 'https://www.agentled.app' ? ['--env', `AGENTLED_URL=${baseUrl}`] : []),
'--', 'npx', '-y', '@agentled/mcp-server',
], { stdio: 'inherit' });
child.on('close', code => resolve(code === 0));
child.on('error', () => resolve(false));
}
catch {
resolve(false);
}
});
}
function writeMcpJsonConfig(configPath, apiKey, baseUrl) {
try {
let config = {};
if (existsSync(configPath)) {
config = JSON.parse(readFileSync(configPath, 'utf-8'));
}
if (!config.mcpServers)
config.mcpServers = {};
const env = { AGENTLED_API_KEY: apiKey };
if (baseUrl !== 'https://www.agentled.app')
env.AGENTLED_URL = baseUrl;
config.mcpServers.agentled = {
command: 'npx',
args: ['-y', '@agentled/mcp-server'],
env,
};
mkdirSync(dirname(configPath), { recursive: true });
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
return true;
}
catch {
return false;
}
}
function maskApiKey(apiKey) {
if (!apiKey)
return 'wsk_...';
if (apiKey.length <= 18)
return `${apiKey.slice(0, 8)}...`;
return `${apiKey.slice(0, 17)}...`;
}
export function manualSnippet(apiKey, baseUrl) {
const maskedApiKey = maskApiKey(apiKey);
const envArgs = baseUrl !== 'https://www.agentled.app'
? `-e AGENTLED_API_KEY=${maskedApiKey} -e AGENTLED_URL=${baseUrl}`
: `-e AGENTLED_API_KEY=${maskedApiKey}`;
const lines = [
'Could not auto-detect your MCP client. Add manually:',
'Replace the masked key below with your full Agentled API key from Workspace Settings > Developer.',
'',
'Claude Code:',
` claude mcp add --transport stdio --scope user agentled ${envArgs} -- npx -y @agentled/mcp-server`,
'',
'Codex / Cursor / Windsurf — add to your MCP config JSON:',
' {',
' "agentled": {',
' "command": "npx",',
' "args": ["-y", "@agentled/mcp-server"],',
` "env": { "AGENTLED_API_KEY": "${maskedApiKey}"${baseUrl !== 'https://www.agentled.app' ? `, "AGENTLED_URL": "${baseUrl}"` : ''} }`,
' }',
' }',
];
return lines.join('\n');
}
/**
* Auto-detect the user's MCP client and register the AgentLed MCP server.
* Returns a result describing what happened, including a fallback hint when
* the client is unknown or the config write failed.
*/
export async function configureMcp(apiKey, baseUrl, target = 'auto') {
const client = target === 'auto' ? detectMcpClient() : target;
if (client === 'claude-code') {
const ok = await configureClaudeCode(apiKey, baseUrl);
return {
client,
success: ok,
configPath: ok ? '~/.claude.json (via `claude mcp add`)' : null,
fallbackHint: ok ? undefined : manualSnippet(apiKey, baseUrl),
};
}
if (client === 'codex') {
const ok = await configureCodex(apiKey, baseUrl);
return {
client,
success: ok,
configPath: ok ? '~/.codex/config (via `codex mcp add`)' : null,
fallbackHint: ok ? undefined : manualSnippet(apiKey, baseUrl),
};
}
if (client === 'claude-desktop') {
const path = getClaudeDesktopConfigPath();
const ok = path ? writeMcpJsonConfig(path, apiKey, baseUrl) : false;
return {
client,
success: ok,
configPath: path,
fallbackHint: ok ? undefined : manualSnippet(apiKey, baseUrl),
};
}
if (client === 'cursor' || client === 'windsurf') {
const path = getMcpJsonPath(client);
const ok = path ? writeMcpJsonConfig(path, apiKey, baseUrl) : false;
return {
client,
success: ok,
configPath: path,
fallbackHint: ok ? undefined : manualSnippet(apiKey, baseUrl),
};
}
return {
client: 'unknown',
success: false,
configPath: null,
fallbackHint: manualSnippet(apiKey, baseUrl),
};
}
//# sourceMappingURL=mcp-config.js.map