#!/usr/bin/env node
/**
 * CLI entry point for OpenWriter.
 * Usage: openwriter [--api-key av_live_xxx] [--port 5050] [--no-open] [--av-url URL] [--plugins name1,name2]
 *
 * API key resolution (first wins):
 *   1. --api-key CLI flag
 *   2. AV_API_KEY environment variable
 *   3. Saved in ~/.openwriter/config.json (from a previous --api-key)
 *
 * If no key found, server starts anyway — plugins that need it will report errors.
 *
 * Boot order optimized for fast MCP startup:
 *   1. Parse args + config (light imports only)
 *   2. Port check (fast TCP probe)
 *   3. Start MCP stdio transport (what Claude Code waits for)
 *   4. Lazy-load Express server + plugins (heavy deps deferred)
 */
// Redirect all console output to stderr so MCP stdio protocol stays clean on stdout
const originalLog = console.log;
console.log = (...args) => console.error(...args);
// ── Crash guards ──
// The MCP StdioServerTransport writes to stdout with no error handler.
// When Claude Code closes the pipe, the write throws EPIPE and kills the process,
// taking the HTTP server (browser UI) down with it. Catch everything so the
// HTTP server survives MCP disconnects.
process.on('uncaughtException', (err) => {
    // EPIPE / ERR_STREAM_DESTROYED from broken MCP pipe — non-fatal, ignore
    if (err?.code === 'EPIPE' || err?.code === 'ERR_STREAM_DESTROYED')
        return;
    console.error('[FATAL] Uncaught exception:', err);
});
process.on('unhandledRejection', (reason) => {
    console.error('[WARN] Unhandled rejection:', reason);
});
// Catch broken-pipe errors on stdout directly (MCP transport writes here)
process.stdout.on('error', (err) => {
    if (err?.code === 'EPIPE' || err?.code === 'ERR_STREAM_DESTROYED')
        return;
    console.error('[stdout error]', err);
});
// Monitor stdin lifecycle — when Claude Code closes the pipe, stdin ends.
// Log it so we know exactly when MCP disconnects.
process.stdin.on('end', () => {
    console.error('[MCP] stdin EOF — Claude Code disconnected. HTTP server still running.');
});
process.stdin.on('close', () => {
    console.error('[MCP] stdin closed.');
});
// Only light imports here — helpers.js uses fs/path/os/crypto (all Node stdlib)
import { createConnection } from 'net';
import { readConfig, saveConfig } from '../server/helpers.js';
const args = process.argv.slice(2);
// Subcommands (run and exit, don't start server)
// `setup` is the canonical one-time bootstrap verb (installs the package,
// wires MCP, drops the skill). `install-skill` is kept as a hidden alias for
// backwards compatibility with existing links/docs.
if (args[0] === 'setup' || args[0] === 'install-skill') {
    import('../server/install-skill.js').then(m => m.installSkill());
}
else if (args[0] === 'plugin') {
    import('../server/plugin-install.js').then(m => m.handlePluginCommand(args.slice(1)));
}
else {
    let port = 5050;
    let noOpen = false;
    let cliApiKey;
    let cliAvUrl;
    let plugins = [];
    for (let i = 0; i < args.length; i++) {
        if (args[i] === '--port' && args[i + 1]) {
            port = parseInt(args[i + 1], 10);
            i++;
        }
        if (args[i] === '--no-open') {
            noOpen = true;
        }
        if (args[i] === '--api-key' && args[i + 1]) {
            cliApiKey = args[i + 1];
            i++;
        }
        if (args[i] === '--av-url' && args[i + 1]) {
            cliAvUrl = args[i + 1];
            i++;
        }
        if (args[i] === '--plugins' && args[i + 1]) {
            plugins = args[i + 1].split(',').map((s) => s.trim()).filter(Boolean);
            i++;
        }
    }
    // Resolve API key: CLI flag → env var → saved config
    const config = readConfig();
    // Restore active profile from config
    const { setActiveProfile } = await import('../server/helpers.js');
    setActiveProfile(config.activeProfile || 'Default');
    const avApiKey = cliApiKey || process.env.AV_API_KEY || config.avApiKey || '';
    const avBackendUrl = cliAvUrl || process.env.AV_BACKEND_URL || config.avBackendUrl;
    // Persist new values to config so future starts don't need them
    const updates = {};
    if (cliApiKey && cliApiKey !== config.avApiKey)
        updates.avApiKey = cliApiKey;
    if (cliAvUrl && cliAvUrl !== config.avBackendUrl)
        updates.avBackendUrl = cliAvUrl;
    if (Object.keys(updates).length > 0) {
        saveConfig(updates);
        console.log('Config saved to ~/.openwriter/config.json');
    }
    // Set env vars for downstream code (plugins read process.env)
    if (avApiKey)
        process.env.AV_API_KEY = avApiKey;
    if (avBackendUrl)
        process.env.AV_BACKEND_URL = avBackendUrl;
    // Port check with health verification — detects orphaned servers
    async function checkPort() {
        const taken = await new Promise((resolve) => {
            const socket = createConnection({ port, host: '127.0.0.1' });
            socket.once('connect', () => { socket.destroy(); resolve(true); });
            socket.once('error', () => { resolve(false); });
        });
        if (!taken)
            return 'free';
        // Port is taken — verify it's a healthy OpenWriter server
        try {
            const res = await fetch(`http://127.0.0.1:${port}/api/status`, { signal: AbortSignal.timeout(2000) });
            return res.ok ? 'healthy' : 'orphaned';
        }
        catch {
            return 'orphaned';
        }
    }
    let portState = await checkPort();
    // Orphaned server: wait for it to die, then claim primary mode
    if (portState === 'orphaned') {
        console.error(`[OpenWriter] Port ${port} held by unresponsive process — waiting for release...`);
        await new Promise(r => setTimeout(r, 3000));
        portState = await checkPort();
        if (portState === 'orphaned') {
            // Still held — wait once more
            await new Promise(r => setTimeout(r, 3000));
            portState = await checkPort();
        }
        if (portState !== 'free') {
            console.error(`[OpenWriter] Port ${port} still unavailable — entering client mode`);
        }
    }
    if (portState === 'healthy') {
        // Client mode: proxy MCP calls to existing primary server via HTTP
        console.error(`[OpenWriter] Port ${port} in use by healthy server — entering client mode`);
        const { startMcpClientServer } = await import('../server/mcp-client.js');
        startMcpClientServer(port).catch((err) => {
            console.error('[MCP-Client] Failed to start:', err);
        });
    }
    else {
        // Primary mode: start MCP stdio FIRST, then lazy-load Express
        const { load } = await import('../server/state.js');
        load();
        const { startMcpServer } = await import('../server/mcp.js');
        startMcpServer().catch((err) => {
            console.error('[MCP] Failed to start:', err);
        });
        // Deferred: load Express + plugins (heavy deps) after MCP is connecting
        const { startHttpServer } = await import('../server/index.js');
        startHttpServer({ port, noOpen, plugins }).catch((err) => {
            console.error('[HTTP] Failed to start:', err);
        });
    }
}
