#!/usr/bin/env node
/**
 * ShieldCortex - Brain-like Memory System for Claude Code
 *
 * This server provides persistent, intelligent memory for Claude Code,
 * solving the context compaction and session persistence problems.
 *
 * Usage:
 *   npx shieldcortex                         # Start MCP server (default)
 *   npx shieldcortex --mode mcp              # Start MCP server
 *   npx shieldcortex --mode api              # Start visualization API server
 *   npx shieldcortex --mode both             # Start both servers
 *   npx shieldcortex --dashboard             # Start API + Dashboard (admin panel)
 *   npx shieldcortex --db /path/to.db        # Custom database path
 *   npx shieldcortex scan "text"               # Quick content scan (no MCP/ML)
 *   npx shieldcortex status                   # Show database and system status
 *   npx shieldcortex setup                    # Configure Claude for proactive memory use
 *   npx shieldcortex install                  # Alias for setup
 *   npx shieldcortex hook pre-compact         # Run pre-compact hook (for settings.json)
 *   npx shieldcortex hook session-start       # Run session-start hook (for settings.json)
 *   npx shieldcortex hook session-end         # Run session-end hook (for settings.json)
 *   npx shieldcortex service install         # Auto-start dashboard on login
 *   npx shieldcortex service uninstall       # Remove auto-start
 *   npx shieldcortex service status          # Check service status
 *   npx shieldcortex openclaw install        # Install OpenClaw hook
 *   npx shieldcortex openclaw uninstall      # Remove OpenClaw hook
 *   npx shieldcortex openclaw status         # Check OpenClaw hook status
 *   npx shieldcortex copilot install         # Configure MCP server for VS Code + Cursor
 *   npx shieldcortex copilot uninstall       # Remove MCP server configuration
 *   npx shieldcortex copilot status          # Check MCP server configuration
 *   npx shieldcortex migrate                  # Migrate from Claude Cortex
 *   npx shieldcortex setup uninstall         # Remove hooks + CLAUDE.md block (requires confirmation)
 *   npx shieldcortex uninstall               # Full uninstall (requires confirmation)
 *   npx shieldcortex uninstall --confirm     # Full uninstall (non-interactive)
 *   npx shieldcortex uninstall --keep-logs   # Full uninstall but keep log files
 */
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import path from 'path';
import { createServer } from './server.js';
import { startVisualizationServer } from './api/visualization-server.js';
import { handleServiceCommand } from './service/install.js';
import { setupClaudeMd } from './setup/claude-md.js';
import { handleHookCommand } from './setup/hooks.js';
import { handleOpenClawCommand } from './setup/openclaw.js';
import { handleCopilotCommand } from './setup/copilot.js';
import { createRequire } from 'module';
import { disposeModel } from './embeddings/index.js';
import { stopDefaultWorker } from './worker/brain-worker.js';
const require = createRequire(import.meta.url);
const pkg = require('../package.json');
// Get the directory of this file for relative paths
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Parse command line arguments
function parseArgs() {
    const args = process.argv.slice(2);
    const result = { mode: 'mcp' };
    for (let i = 0; i < args.length; i++) {
        if (args[i] === '--db' && args[i + 1]) {
            result.dbPath = args[i + 1];
            i++;
        }
        else if (args[i] === '--dashboard') {
            result.mode = 'dashboard';
        }
        else if (args[i] === '--mode' && args[i + 1]) {
            const mode = args[i + 1].toLowerCase();
            if (mode === 'mcp' || mode === 'api' || mode === 'both' || mode === 'dashboard') {
                result.mode = mode;
            }
            i++;
        }
    }
    return result;
}
/**
 * Start MCP server for Claude Code integration
 */
async function startMcpServer(dbPath) {
    // Create the MCP server
    const server = createServer(dbPath);
    const transport = new StdioServerTransport();
    // Register signal handlers BEFORE connect so cleanup runs even if connect() throws
    process.on('SIGINT', async () => {
        stopDefaultWorker();
        await server.close();
        await disposeModel();
        process.exit(0);
    });
    process.on('SIGTERM', async () => {
        stopDefaultWorker();
        await server.close();
        await disposeModel();
        process.exit(0);
    });
    // Connect via stdio transport
    await server.connect(transport);
}
/**
 * Find the dashboard standalone server path
 * Tries multiple locations to support npm package, global install, and local dev
 */
function findDashboardPath() {
    const fs = require('fs');
    // Candidate locations to check (in order of priority)
    // All paths are relative to __dirname (dist/) — no self-referencing require.resolve
    const candidates = [
        // Standard npm package structure (dist/ is in package root)
        {
            serverPath: path.resolve(__dirname, '..', 'dashboard', '.next', 'standalone', 'dashboard', 'server.js'),
            cwd: path.resolve(__dirname, '..', 'dashboard', '.next', 'standalone', 'dashboard'),
        },
        // Global npm install (some platforms put dist/ differently)
        {
            serverPath: path.resolve(__dirname, '..', '..', 'dashboard', '.next', 'standalone', 'dashboard', 'server.js'),
            cwd: path.resolve(__dirname, '..', '..', 'dashboard', '.next', 'standalone', 'dashboard'),
        },
    ];
    for (const candidate of candidates) {
        try {
            if (fs.existsSync(candidate.serverPath)) {
                return candidate;
            }
        }
        catch {
            // Ignore errors (e.g., require.resolve may fail)
        }
    }
    return null;
}
/**
 * Start the Next.js dashboard as a child process
 */
function startDashboard() {
    const fs = require('fs');
    // Find dashboard server path
    const dashboardPath = findDashboardPath();
    const dashboardDir = path.resolve(__dirname, '..', 'dashboard');
    const useStandalone = dashboardPath !== null;
    console.log(`
╔══════════════════════════════════════════════════════════════╗
║             🧠 ShieldCortex Dashboard                        ║
╠══════════════════════════════════════════════════════════════╣
║  Starting Next.js dashboard...                               ║
║  Dashboard: http://localhost:3030                            ║
║  API:       http://localhost:3001                            ║
╚══════════════════════════════════════════════════════════════╝
  `);
    let dashboard;
    if (useStandalone && dashboardPath) {
        // Use standalone server (npm package distribution)
        dashboard = spawn(process.execPath, ['server.js'], {
            cwd: dashboardPath.cwd,
            stdio: ['ignore', 'pipe', 'pipe'],
            env: { ...process.env, PORT: '3030', HOSTNAME: '0.0.0.0' },
        });
    }
    else {
        // Fall back to npm run start (local development)
        // Use explicit shell path for macOS Tahoe compatibility (no /bin/sh)
        const shellPath = process.env.SHELL || '/bin/zsh';
        dashboard = spawn('npm', ['run', 'start'], {
            cwd: dashboardDir,
            stdio: ['ignore', 'pipe', 'pipe'],
            shell: shellPath,
        });
    }
    // Pipe dashboard output with prefix
    dashboard.stdout?.on('data', (data) => {
        const lines = data.toString().split('\n').filter((l) => l.trim());
        for (const line of lines) {
            console.log(`[dashboard] ${line}`);
        }
    });
    dashboard.stderr?.on('data', (data) => {
        const lines = data.toString().split('\n').filter((l) => l.trim());
        for (const line of lines) {
            console.error(`[dashboard] ${line}`);
        }
    });
    dashboard.on('error', (error) => {
        console.error('[dashboard] Failed to start:', error.message);
        if (error.message.includes('ENOENT') || error.message.includes('spawn')) {
            console.error(`
╔══════════════════════════════════════════════════════════════╗
║  ⚠️  Dashboard spawn failed (shell not found)                 ║
╠══════════════════════════════════════════════════════════════╣
║  This can happen on newer macOS versions (Tahoe+).           ║
║                                                              ║
║  Manual workaround:                                          ║
║    cd ${dashboardDir}
║    npm run start                                             ║
║                                                              ║
║  Then visit: http://localhost:3030                           ║
╚══════════════════════════════════════════════════════════════╝
      `);
        }
        else if (!useStandalone) {
            console.error('[dashboard] Make sure to run "npm run build" in the dashboard directory first.');
        }
    });
    dashboard.on('exit', (code, signal) => {
        if (signal) {
            console.log(`[dashboard] Killed by signal ${signal}`);
        }
        else if (code !== 0) {
            console.error(`[dashboard] Exited with code ${code}`);
        }
    });
    return dashboard;
}
/**
 * Main entry point
 */
async function main() {
    // Handle --version / -v
    if (process.argv[2] === '--version' || process.argv[2] === '-v') {
        console.log(pkg.version);
        return;
    }
    // Handle "doctor" subcommand
    if (process.argv[2] === 'doctor') {
        const { handleDoctorCommand } = await import('./setup/doctor.js');
        await handleDoctorCommand();
        return;
    }
    // Handle "setup" subcommand (alias: "install")
    if (process.argv[2] === 'setup' || process.argv[2] === 'install') {
        if (process.argv[3] === 'uninstall') {
            const { uninstallSetup } = await import('./setup/uninstall.js');
            await uninstallSetup();
            return;
        }
        const stopHook = process.argv.includes('--with-stop-hook');
        await setupClaudeMd({ stopHook });
        return;
    }
    // Handle "migrate" subcommand
    if (process.argv[2] === 'migrate') {
        const { handleMigrateCommand } = await import('./setup/migrate.js');
        await handleMigrateCommand();
        return;
    }
    // Handle "uninstall" subcommand
    if (process.argv[2] === 'uninstall') {
        const { uninstallAll } = await import('./setup/uninstall.js');
        await uninstallAll({ keepLogs: process.argv.includes('--keep-logs') });
        return;
    }
    // Handle "hook" subcommand
    if (process.argv[2] === 'hook') {
        await handleHookCommand(process.argv[3] || '');
        return;
    }
    // Handle "openclaw" subcommand (backward compat: "clawdbot" also accepted)
    if (process.argv[2] === 'openclaw' || process.argv[2] === 'clawdbot') {
        await handleOpenClawCommand(process.argv[3] || '');
        return;
    }
    // Handle "copilot" subcommand (VS Code + Cursor MCP setup)
    if (process.argv[2] === 'copilot') {
        await handleCopilotCommand(process.argv[3] || '');
        return;
    }
    // Handle "service" subcommand before normal mode parsing
    if (process.argv[2] === 'service') {
        await handleServiceCommand(process.argv[3] || '');
        return;
    }
    // Handle "config" subcommand (cloud sync configuration)
    if (process.argv[2] === 'config') {
        const { handleCloudConfig } = await import('./cloud/cli.js');
        handleCloudConfig(process.argv.slice(3));
        return;
    }
    // Handle "status" subcommand
    if (process.argv[2] === 'status') {
        const { handleStatusCommand } = await import('./setup/status.js');
        await handleStatusCommand();
        return;
    }
    // Handle "graph" subcommand
    if (process.argv[2] === 'graph') {
        const action = process.argv[3];
        if (action === 'backfill') {
            const { initDatabase } = await import('./database/init.js');
            const os = await import('os');
            const dbPath = process.env.CLAUDE_MEMORY_DB || path.join(os.homedir(), '.shieldcortex', 'memories.db');
            initDatabase(dbPath);
            const { backfillGraph } = await import('./graph/backfill.js');
            console.log('Backfilling knowledge graph from existing memories...');
            const result = backfillGraph();
            console.log(`Done! Extracted ${result.entities} new entities, ${result.triples} new triples from ${result.memoriesProcessed} memories.`);
        }
        else {
            console.error('Unknown graph command. Available: backfill');
            process.exit(1);
        }
        return;
    }
    // Handle "scan" subcommand — lightweight content scan (no MCP server, no ONNX model)
    if (process.argv[2] === 'scan') {
        const text = process.argv[3];
        if (!text) {
            console.error('Usage: npx shieldcortex scan "text to analyse"');
            console.error('  Runs the defence pipeline (firewall + trust + sensitivity).');
            console.error('  No MCP server or ML model required — works on ARM64.');
            process.exit(1);
        }
        // Init database for audit logging
        const os = await import('os');
        const dbPath = process.env.CLAUDE_MEMORY_DB || path.join(os.homedir(), '.shieldcortex', 'memories.db');
        const { initDatabase } = await import('./database/init.js');
        initDatabase(dbPath);
        const { runDefencePipeline } = await import('./defence/pipeline.js');
        const source = { type: 'cli', identifier: 'shieldcortex-scan' };
        const result = runDefencePipeline(text, 'CLI Scan', source);
        const bold = '\x1b[1m';
        const reset = '\x1b[0m';
        const green = '\x1b[32m';
        const red = '\x1b[31m';
        const yellow = '\x1b[33m';
        const resultColor = result.firewall.result === 'ALLOW' ? green :
            result.firewall.result === 'QUARANTINE' ? yellow : red;
        console.log(`\n${bold}ShieldCortex Scan Result${reset}`);
        console.log(`${'─'.repeat(50)}`);
        console.log(`  Result:      ${resultColor}${result.firewall.result}${reset}`);
        console.log(`  Trust:       ${result.trust.score.toFixed(2)}`);
        console.log(`  Sensitivity: ${result.sensitivity.level}`);
        console.log(`  Anomaly:     ${result.firewall.anomalyScore.toFixed(2)}`);
        console.log(`  Reason:      ${result.firewall.reason}`);
        if (result.firewall.threatIndicators.length > 0) {
            console.log(`  Threats:     ${result.firewall.threatIndicators.join(', ')}`);
        }
        if (result.firewall.blockedPatterns.length > 0) {
            console.log(`  Patterns:    ${result.firewall.blockedPatterns.join(', ')}`);
        }
        console.log();
        process.exit(result.allowed ? 0 : 1);
    }
    // Handle "scan-skill" subcommand — scan a single skill/instruction file
    if (process.argv[2] === 'scan-skill') {
        const filePath = process.argv[3];
        if (!filePath) {
            console.error('Usage: npx shieldcortex scan-skill <path>');
            console.error('  Scans an agent instruction file for threats.');
            console.error('  Supports: SKILL.md, HOOK.md, handler.js, .cursorrules,');
            console.error('  .windsurfrules, .clinerules, CLAUDE.md, copilot-instructions.md,');
            console.error('  .aider.conf.yml, .continue/config.json');
            process.exit(1);
        }
        const { scanSkill } = await import('./defence/skill-scanner/index.js');
        const result = scanSkill(filePath);
        // Coloured output
        const severityColor = {
            critical: '\x1b[31m', // red
            high: '\x1b[91m', // bright red
            medium: '\x1b[33m', // yellow
            low: '\x1b[36m', // cyan
        };
        const reset = '\x1b[0m';
        const bold = '\x1b[1m';
        console.log(`\n${bold}Skill Scanner Report${reset}`);
        console.log(`${'─'.repeat(50)}`);
        console.log(`  Skill:   ${result.skillName}`);
        console.log(`  Format:  ${result.format}`);
        console.log(`  Risk:    ${result.safe ? '\x1b[32mSAFE\x1b[0m' : `${severityColor[result.riskLevel] ?? ''}${result.riskLevel.toUpperCase()}${reset}`}`);
        console.log(`  Time:    ${result.scanDurationMs}ms`);
        if (result.findings.length > 0) {
            console.log(`\n${bold}Findings (${result.findings.length}):${reset}`);
            for (const f of result.findings) {
                const color = severityColor[f.severity] ?? '';
                console.log(`  ${color}[${f.severity.toUpperCase()}]${reset} ${f.pattern}`);
                console.log(`         ${f.description}`);
                if (f.matchedText) {
                    console.log(`         Match: "${f.matchedText}"`);
                }
            }
        }
        else {
            console.log(`\n  \x1b[32mNo threats detected.\x1b[0m`);
        }
        console.log(`\n${result.summary}\n`);
        process.exit(result.safe ? 0 : 1);
    }
    // Handle "scan-skills" subcommand — scan all installed skills/hooks
    if (process.argv[2] === 'scan-skills') {
        const { scanSkill, discoverSkillFiles } = await import('./defence/skill-scanner/index.js');
        const customDir = process.argv.indexOf('--dir') !== -1
            ? process.argv[process.argv.indexOf('--dir') + 1]
            : undefined;
        const filesToScan = discoverSkillFiles(customDir);
        if (filesToScan.length === 0) {
            console.log('\nNo agent instruction files found to scan.');
            console.log('Checked: Claude Code skills, OpenClaw hooks, .cursorrules, CLAUDE.md, and more.');
            console.log('Use --dir <path> to scan a custom directory.\n');
            process.exit(0);
        }
        const bold = '\x1b[1m';
        const reset = '\x1b[0m';
        console.log(`\n${bold}Scanning ${filesToScan.length} file(s)...${reset}\n`);
        let threatCount = 0;
        const results = [];
        for (const fp of filesToScan) {
            const result = scanSkill(fp);
            results.push({ path: fp, safe: result.safe, riskLevel: result.riskLevel, summary: result.summary });
            if (!result.safe)
                threatCount++;
            const icon = result.safe ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
            const risk = result.safe ? '\x1b[32msafe\x1b[0m' : `\x1b[31m${result.riskLevel}\x1b[0m`;
            console.log(`  ${icon} ${risk.padEnd(20)} ${fp}`);
            if (!result.safe) {
                for (const f of result.findings) {
                    if (f.severity === 'high' || f.severity === 'critical') {
                        console.log(`    \x1b[31m[${f.severity}]\x1b[0m ${f.description}`);
                    }
                }
            }
        }
        console.log(`\n${bold}Summary:${reset} ${filesToScan.length} scanned, ${threatCount} with threats\n`);
        process.exit(threatCount > 0 ? 1 : 0);
    }
    const { dbPath, mode } = parseArgs();
    let dashboardProcess = null;
    if (mode === 'api') {
        // API mode only - for dashboard visualization
        console.log('Starting ShieldCortex in API mode...');
        startVisualizationServer(dbPath);
    }
    else if (mode === 'dashboard') {
        // Dashboard mode - API + Next.js dashboard
        console.log('Starting ShieldCortex with Dashboard...');
        startVisualizationServer(dbPath);
        dashboardProcess = startDashboard();
        // Kill dashboard child process on any exit (prevents orphans)
        const killDashboard = () => {
            if (dashboardProcess && !dashboardProcess.killed) {
                dashboardProcess.kill('SIGTERM');
            }
        };
        // Clean up on normal signals
        const shutdown = (signal) => {
            console.log(`\nReceived ${signal}, shutting down...`);
            killDashboard();
            process.exit(0);
        };
        process.on('SIGINT', () => shutdown('SIGINT'));
        process.on('SIGTERM', () => shutdown('SIGTERM'));
        // Clean up on any exit (catches crashes, EADDRINUSE, uncaught exceptions)
        process.on('exit', killDashboard);
        process.on('uncaughtException', (err) => {
            console.error('[ShieldCortex] Uncaught exception:', err.message);
            killDashboard();
            process.exit(1);
        });
    }
    else if (mode === 'both') {
        // Both modes - API in background, MCP in foreground
        console.log('Starting ShieldCortex in both modes...');
        startVisualizationServer(dbPath);
        await startMcpServer(dbPath);
    }
    else {
        // MCP mode (default) - for Claude Code integration
        await startMcpServer(dbPath);
    }
}
// Run
main().catch((error) => {
    // Log to stderr to avoid corrupting MCP protocol
    console.error('Failed to start shieldcortex server:', error);
    process.exit(1);
});
//# sourceMappingURL=index.js.map