// setup-mcp — interactive wizard for adding / removing external MCP
// servers (P3 symmetry follow-up to setup-llm).
//
// Same shape as setup-llm: subcommands list / add / remove / health
// / help. Validates through configSchema before touching disk so a
// typo (e.g. setting both `command` and `url`) is caught before the
// file is written. Symmetric with Stage 6's /api/mcp/* REST + the
// SPA panel — operator can pick whichever surface fits their flow.
//
// Invoked as `agim setup mcp [<subcommand>] [<name>]`.
import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
import { join } from 'node:path';
import { createInterface } from 'node:readline';
import { AGIM_HOME } from '../core/agim-paths.js';
import { configSchema } from '../core/config-schema.js';
const CONFIG_FILE = join(AGIM_HOME, 'config.json');
const PRESETS = [
    {
        label: 'Filesystem (read/write a local dir; very common quick-start)',
        transport: 'stdio',
        command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem'],
        hint: 'Will prompt for the directory to expose.',
    },
    {
        label: 'GitHub (issues, PRs, code search; needs GITHUB_PERSONAL_ACCESS_TOKEN env)',
        transport: 'stdio',
        command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'],
    },
    {
        label: 'Postgres (read-only SQL queries; needs DATABASE_URL)',
        transport: 'stdio',
        command: 'npx', args: ['-y', '@modelcontextprotocol/server-postgres'],
        hint: 'Will prompt for the DATABASE_URL.',
    },
    {
        label: 'Brave Search (BRAVE_API_KEY required)',
        transport: 'stdio',
        command: 'npx', args: ['-y', '@modelcontextprotocol/server-brave-search'],
    },
    {
        label: 'Custom HTTP / SSE endpoint',
        transport: 'http',
    },
    {
        label: 'Custom stdio command',
        transport: 'stdio',
    },
];
// ─── Entry point ────────────────────────────────────────────────────
export async function runSetupMcp(argv) {
    const sub = (argv[0] || '').toLowerCase();
    try {
        if (sub === '' || sub === 'help' || sub === '-h' || sub === '--help')
            return showHelp();
        if (sub === 'list' || sub === 'ls')
            return await listAction();
        if (sub === 'add')
            return await addAction();
        if (sub === 'remove' || sub === 'rm')
            return await removeAction(argv[1]);
        if (sub === 'health' || sub === 'ping')
            return await healthAction(argv[1]);
        return showHelp(`unknown subcommand: ${sub}`);
    }
    catch (err) {
        process.stderr.write(`\n❌ wizard failed: ${err instanceof Error ? err.message : String(err)}\n`);
        return 1;
    }
}
function showHelp(error) {
    if (error)
        process.stderr.write(`${error}\n\n`);
    process.stdout.write(`
agim setup mcp — manage external MCP servers interactively.

Subcommands:
  list                  show configured MCP servers
  add                   add a new server (preset picker → name → command/url → save)
  remove [<name>]       remove a server (config.json mcpServers)
  health [<name>]       reload + probe one server (or all when name omitted)

Notes:
  · Server metadata lives in ~/.agim/config.json under "mcpServers".
  · Discovered tools are exposed to the native agent via the
    "mcp_<server>_<tool>" naming.
  · CLI agents have their OWN MCP configs (typically inside
    ~/.claude/.../mcp-config.json) — those are unaffected by this
    wizard. agim's mcpServers list is for the native agent loop.
`);
    return error ? 1 : 0;
}
// ─── subcommands ────────────────────────────────────────────────────
async function listAction() {
    const cfg = await readConfig();
    const servers = cfg.mcpServers ?? [];
    if (servers.length === 0) {
        process.stdout.write('(no MCP servers configured — run `agim setup mcp add`)\n');
        return 0;
    }
    process.stdout.write('\nConfigured MCP servers:\n');
    for (const s of servers) {
        const transport = s.command ? 'stdio'
            : s.url?.endsWith('/sse') ? 'SSE'
                : s.url ? 'http'
                    : '-';
        const target = s.command
            ? `${s.command}${s.args ? ` ${s.args.join(' ')}` : ''}`
            : (s.url ?? '-');
        const enabled = s.enabled === false ? ' [disabled]' : '';
        process.stdout.write(`  · ${s.name} [${transport}]${enabled}  →  ${target}\n`);
    }
    return 0;
}
async function addAction() {
    const rl = readline();
    try {
        process.stdout.write('\n── agim setup mcp add ──\n');
        process.stdout.write('Pick a server preset:\n');
        PRESETS.forEach((p, i) => { process.stdout.write(`  ${i + 1}. ${p.label}\n`); });
        const choice = await ask(rl, `Choice [1-${PRESETS.length}]: `);
        const idx = parseInt(choice, 10) - 1;
        if (!Number.isFinite(idx) || idx < 0 || idx >= PRESETS.length) {
            return cancel('not a valid choice');
        }
        const preset = PRESETS[idx];
        const defaultName = suggestServerName(preset);
        const name = (await ask(rl, `Server name [${defaultName}]: `)).trim() || defaultName;
        if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
            return cancel('name must match [A-Za-z0-9][A-Za-z0-9._-]*');
        }
        const server = { name };
        if (preset.transport === 'stdio') {
            // command + args (presets prefill; operator can override or extend)
            const cmdDefault = preset.command ?? '';
            const cmd = (await ask(rl, `Command${cmdDefault ? ` [${cmdDefault}]` : ''}: `)).trim() || cmdDefault;
            if (!cmd)
                return cancel('empty command');
            server.command = cmd;
            const baseArgs = preset.args ? preset.args.join(' ') : '';
            // The filesystem preset needs a directory tail; prompt the
            // operator if the preset is filesystem-ish.
            let argsHint = baseArgs;
            if (preset.label.startsWith('Filesystem')) {
                const dir = (await ask(rl, 'Directory to expose to MCP (absolute path): ')).trim();
                if (!dir)
                    return cancel('empty directory');
                argsHint = `${baseArgs}${baseArgs ? ' ' : ''}${dir}`.trim();
            }
            else if (preset.label.startsWith('Postgres')) {
                const url = (await ask(rl, 'DATABASE_URL: ', { silent: true })).trim();
                if (!url)
                    return cancel('empty DATABASE_URL');
                argsHint = `${baseArgs}${baseArgs ? ' ' : ''}${url}`.trim();
            }
            const argsLine = (await ask(rl, `Args (space-separated)${argsHint ? ` [${argsHint}]` : ''}: `)).trim() || argsHint;
            const argsArr = argsLine.split(/\s+/).filter(Boolean);
            if (argsArr.length > 0)
                server.args = argsArr;
        }
        else {
            // http / SSE
            const url = (await ask(rl, 'URL (http(s)://...; suffix /sse for SSE transport): ')).trim();
            if (!url)
                return cancel('empty URL');
            server.url = url;
            const headersJson = (await ask(rl, 'Headers as JSON (optional, e.g. {"Authorization":"Bearer ..."}): ')).trim();
            if (headersJson) {
                try {
                    const parsed = JSON.parse(headersJson);
                    server.headers = parsed;
                }
                catch {
                    return cancel('headers must be valid JSON');
                }
            }
        }
        const cfg = await readConfig();
        const existing = cfg.mcpServers ?? [];
        if (existing.some((s) => s.name === name)) {
            return cancel(`server "${name}" already exists — use \`agim setup mcp remove ${name}\` first`);
        }
        cfg.mcpServers = [...existing, server];
        const result = configSchema.safeParse(cfg);
        if (!result.success) {
            return cancel(`config validation failed: ${result.error.issues.map((i) => i.message).join('; ')}`);
        }
        await writeConfig(cfg);
        process.stdout.write(`\n✅ Added MCP server "${name}".\n`);
        process.stdout.write(`(restart agim, or run \`agim setup mcp health ${name}\` to probe live connection)\n`);
        return 0;
    }
    finally {
        rl.close();
    }
}
async function removeAction(maybeName) {
    const cfg = await readConfig();
    const servers = cfg.mcpServers ?? [];
    if (servers.length === 0) {
        process.stdout.write('(no MCP servers to remove)\n');
        return 0;
    }
    let name = (maybeName ?? '').trim();
    if (!name) {
        const rl = readline();
        try {
            process.stdout.write('\nWhich server?\n');
            servers.forEach((s, i) => { process.stdout.write(`  ${i + 1}. ${s.name}\n`); });
            const choice = await ask(rl, `Choice [1-${servers.length}]: `);
            const idx = parseInt(choice, 10) - 1;
            if (!Number.isFinite(idx) || idx < 0 || idx >= servers.length)
                return cancel('not a valid choice');
            name = servers[idx].name;
        }
        finally {
            rl.close();
        }
    }
    const next = servers.filter((s) => s.name !== name);
    if (next.length === servers.length)
        return cancel(`no server named "${name}"`);
    cfg.mcpServers = next;
    await writeConfig(cfg);
    process.stdout.write(`✅ Removed MCP server "${name}".\n`);
    return 0;
}
async function healthAction(maybeName) {
    // Delegates to runDiagMcp for the all-servers case; for one server
    // we lazy-import the registry directly to keep the report focused.
    const cfg = await readConfig();
    const servers = cfg.mcpServers ?? [];
    if (servers.length === 0) {
        process.stdout.write('(no MCP servers configured)\n');
        return 0;
    }
    if (!maybeName) {
        const { runDiagMcp } = await import('./diag.js');
        return await runDiagMcp();
    }
    const target = servers.find((s) => s.name === maybeName);
    if (!target)
        return cancel(`no server named "${maybeName}"`);
    const { loadFromConfig: loadMcp, connectAll, describeRegistry, closeAll, } = await import('../core/llm/mcp-registry.js');
    loadMcp(cfg);
    try {
        await connectAll();
        const snap = describeRegistry();
        const row = snap.servers.find((r) => r.name === maybeName);
        if (!row) {
            process.stdout.write(`server "${maybeName}" not in registry after reload\n`);
            return 1;
        }
        process.stdout.write(`\nServer: ${row.name}\n`);
        process.stdout.write(`Connected: ${row.connected ? 'yes' : 'no'}\n`);
        process.stdout.write(`Tools:     ${row.toolCount}\n`);
        if (row.error)
            process.stdout.write(`Error:     ${row.error}\n`);
        return row.connected ? 0 : 1;
    }
    finally {
        try {
            await closeAll();
        }
        catch { /* ignore */ }
    }
}
// ─── helpers ────────────────────────────────────────────────────────
async function readConfig() {
    try {
        return JSON.parse(await readFile(CONFIG_FILE, 'utf-8'));
    }
    catch (err) {
        if (err.code === 'ENOENT') {
            return { messengers: [], agents: [], defaultAgent: 'claude-code' };
        }
        throw err;
    }
}
async function writeConfig(cfg) {
    await mkdir(AGIM_HOME, { recursive: true, mode: 0o700 });
    await writeFile(CONFIG_FILE, JSON.stringify(cfg, null, 2), { mode: 0o600 });
    try {
        await chmod(CONFIG_FILE, 0o600);
    }
    catch { /* ignore on Windows */ }
}
function suggestServerName(preset) {
    // 'Filesystem (read/write a local dir...)' → 'filesystem'
    const head = preset.label.split(/[\s(]/)[0]?.toLowerCase() ?? 'server';
    return head.replace(/[^a-z0-9-]/g, '');
}
function readline() {
    return createInterface({ input: process.stdin, output: process.stdout });
}
function ask(rl, prompt, opts = {}) {
    void opts; // see setup-llm.ts: silent input is a stretch goal
    return new Promise((resolve) => rl.question(prompt, resolve));
}
function cancel(reason) {
    process.stdout.write(`\n✖ aborted: ${reason}\n`);
    return 1;
}
// ─── test hooks ────────────────────────────────────────────────────
export const _internal = {
    suggestServerName,
    PRESETS,
};
//# sourceMappingURL=setup-mcp.js.map