#!/usr/bin/env node
// NERF Startup Loader
// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 - see LICENSE file for details.
// NERF is a trademark of defconxt.
//
// Based on NERF loader — the execution engine bootstraps the NERF runtime.
import { fileURLToPath } from 'url';
import { dirname, resolve, join, relative, delimiter } from 'path';
import { existsSync, readFileSync, mkdirSync, symlinkSync, cpSync } from 'fs';
// Fast-path: handle --version/-v and --help/-h before importing any heavy
// dependencies. This avoids loading the entire pi-coding-agent barrel import
// (~1s) just to print a version string.
const nerfRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const args = process.argv.slice(2);
const firstArg = args[0];
// Read package.json once — reused for version, banner, and NERF_VERSION below
let nerfVersion = '0.0.0';
try {
    const pkg = JSON.parse(readFileSync(join(nerfRoot, 'package.json'), 'utf-8'));
    nerfVersion = pkg.version || '0.0.0';
}
catch { /* ignore */ }
if (firstArg === '--version' || firstArg === '-v' || firstArg === '-V') {
    process.stdout.write(nerfVersion + '\n');
    process.exit(0);
}
if (firstArg === '--help' || firstArg === '-h') {
    const { printHelp } = await import('./help-text.js');
    printHelp(nerfVersion);
    process.exit(0);
}
// ---------------------------------------------------------------------------
// Home-directory safeguard — running from $HOME scatters .nerf/ state into
// the home directory, which is almost never intended. Block it unless the
// operator explicitly opts in via NERF_ALLOW_HOME=1.
// ---------------------------------------------------------------------------
{
    const { homedir } = await import('os');
    if (process.cwd() === homedir() && process.env.NERF_ALLOW_HOME !== '1') {
        const red = '\x1b[31m';
        const bold = '\x1b[1m';
        const dim = '\x1b[2m';
        const reset = '\x1b[0m';
        process.stderr.write(`\n${red}${bold}Error:${reset} Running NERF from your home directory is not allowed.\n\n` +
            `${dim}Running from $HOME would scatter .nerf/ state files into your home directory,${reset}\n` +
            `${dim}which is almost never what you want.${reset}\n\n` +
            `${bold}Fix:${reset} cd into a project directory first:\n` +
            `  cd ~/my-project && nerf\n\n` +
            `${dim}Override: set NERF_ALLOW_HOME=1 to bypass this check.${reset}\n\n`);
        process.exit(1);
    }
}
// ---------------------------------------------------------------------------
// Runtime dependency checks — fail fast with clear diagnostics before any
// heavy imports.
// ---------------------------------------------------------------------------
{
    const MIN_NODE_MAJOR = 22;
    const red = '\x1b[31m';
    const bold = '\x1b[1m';
    const dim = '\x1b[2m';
    const reset = '\x1b[0m';
    // -- Node version --
    const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
    if (nodeMajor < MIN_NODE_MAJOR) {
        process.stderr.write(`\n${red}${bold}Error:${reset} NERF requires Node.js >= ${MIN_NODE_MAJOR}.0.0\n` +
            `       You are running Node.js ${process.versions.node}\n\n` +
            `${dim}Install a supported version:${reset}\n` +
            `  nvm install ${MIN_NODE_MAJOR}   ${dim}# if using nvm${reset}\n` +
            `  fnm install ${MIN_NODE_MAJOR}   ${dim}# if using fnm${reset}\n` +
            `  brew install node@${MIN_NODE_MAJOR} ${dim}# macOS Homebrew${reset}\n\n`);
        process.exit(1);
    }
    // -- git --
    try {
        const { execFileSync } = await import('child_process');
        execFileSync('git', ['--version'], { stdio: 'ignore' });
    }
    catch {
        process.stderr.write(`\n${red}${bold}Error:${reset} NERF requires git but it was not found on PATH.\n\n` +
            `${dim}Install git:${reset}\n` +
            `  https://git-scm.com/downloads\n\n`);
        process.exit(1);
    }
}
import { agentDir, appRoot } from './app-paths.js';
import { applyRtkProcessEnv } from './rtk.js';
import { serializeBundledExtensionPaths } from './bundled-extension-paths.js';
import { discoverExtensionEntryPaths } from './extension-discovery.js';
import { loadRegistry, readManifestFromEntryPath, isExtensionEnabled } from './extension-registry.js';
import { renderLogo } from './logo.js';
// pkg/ is a shim directory: contains NERF's piConfig (package.json) and pi's
// theme assets (dist/modes/interactive/theme/) without a src/ directory.
// This allows config.js to:
//   1. Read piConfig.name → "nerf" (branding)
//   2. Resolve themes via dist/ (no src/ present → uses dist path)
const pkgDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'pkg');
// MUST be set before any dynamic import of pi SDK fires — this is what config.js
// reads to determine APP_NAME and CONFIG_DIR_NAME
process.env.PI_PACKAGE_DIR = pkgDir;
process.env.PI_README_PATH = join(nerfRoot, 'README.md');
process.env.PI_DOCS_DIR = join(nerfRoot, 'docs');
process.env.PI_EXAMPLES_DIR = join(nerfRoot, 'examples');
process.env.PI_THEMES_DIR = join(nerfRoot, 'packages', 'pi-coding-agent', 'dist', 'modes', 'interactive', 'theme');
process.env.PI_EXPORT_TEMPLATE_DIR = join(nerfRoot, 'packages', 'pi-coding-agent', 'dist', 'core', 'export-html');
process.env.PI_SKIP_VERSION_CHECK = '1';
process.title = 'nerf';
// Print branded banner on first launch (before ~/.nerf/ exists).
if (!existsSync(appRoot)) {
    const cyan = '\x1b[36m';
    const green = '\x1b[32m';
    const dim = '\x1b[2m';
    const reset = '\x1b[0m';
    const colorCyan = (s) => `${cyan}${s}${reset}`;
    process.stderr.write(renderLogo(colorCyan) +
        '\n' +
        `  NERF ${dim}v${nerfVersion}${reset}\n` +
        `  ${green}Welcome.${reset} Setting up your environment...\n\n`);
    process.env.NERF_FIRST_RUN_BANNER = '1';
}
// NERF_CODING_AGENT_DIR — tells pi's getAgentDir() to return ~/.nerf/agent/
process.env.NERF_CODING_AGENT_DIR = agentDir;
// RTK environment — make ~/.nerf/agent/bin visible to all child-process paths
applyRtkProcessEnv(process.env);
// NODE_PATH — make nerf's own node_modules available to extensions loaded via jiti.
const nerfNodeModules = join(nerfRoot, 'node_modules');
process.env.NODE_PATH = [nerfNodeModules, process.env.NODE_PATH]
    .filter(Boolean)
    .join(delimiter);
// Force Node to re-evaluate module search paths with the updated NODE_PATH.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Module } = await import('module');
Module._initPaths?.();
// Expose version for extensions
process.env.NERF_VERSION = nerfVersion;
// NERF_BIN_PATH — absolute path to this loader, used by subagent
process.env.NERF_BIN_PATH = process.argv[1];
// Workflow path — bundled workflow definition
const distRes = join(nerfRoot, 'dist', 'resources');
const srcRes = join(nerfRoot, 'src', 'resources');
const resourcesDir = existsSync(distRes) ? distRes : srcRes;
process.env.NERF_WORKFLOW_PATH = join(resourcesDir, 'NERF-WORKFLOW.md');
// Bundled extension paths — dynamically discovered
const bundledExtDir = join(resourcesDir, 'extensions');
const agentExtDir = join(agentDir, 'extensions');
const registry = loadRegistry();
const discoveredExtensionPaths = discoverExtensionEntryPaths(bundledExtDir)
    .map((entryPath) => join(agentExtDir, relative(bundledExtDir, entryPath)))
    .filter((entryPath) => {
    const manifest = readManifestFromEntryPath(entryPath);
    if (!manifest)
        return true;
    return isExtensionEnabled(registry, manifest.id);
});
process.env.NERF_BUNDLED_EXTENSION_PATHS = serializeBundledExtensionPaths(discoveredExtensionPaths);
// Proxy support
if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy) {
    const { EnvHttpProxyAgent, setGlobalDispatcher } = await import('undici');
    setGlobalDispatcher(new EnvHttpProxyAgent());
}
// Ensure workspace packages are linked before importing cli.js
const nerfScopeDir = join(nerfNodeModules, '@nerf');
const packagesDir = join(nerfRoot, 'packages');
const wsPackages = ['native', 'pi-agent-core', 'pi-ai', 'pi-coding-agent', 'pi-tui'];
try {
    if (!existsSync(nerfScopeDir))
        mkdirSync(nerfScopeDir, { recursive: true });
    for (const pkg of wsPackages) {
        const target = join(nerfScopeDir, pkg);
        const source = join(packagesDir, pkg);
        if (!existsSync(source) || existsSync(target))
            continue;
        try {
            symlinkSync(source, target, 'junction');
        }
        catch {
            try {
                cpSync(source, target, { recursive: true });
            }
            catch { /* non-fatal */ }
        }
    }
}
catch { /* non-fatal */ }
// Validate critical workspace packages — fail fast with a clear message.
// pi-ai and pi-agent-core are required for ALL LLM operations; pi-tui for the
// interactive terminal. Silently swallowing their absence leads to cryptic
// runtime failures far from the root cause.
const criticalPackages = ['pi-ai', 'pi-agent-core', 'pi-coding-agent', 'pi-tui'];
const missingPackages = criticalPackages.filter(pkg => !existsSync(join(nerfScopeDir, pkg)));
if (missingPackages.length > 0) {
    const missing = missingPackages.map(p => `@nerf/${p}`).join(', ');
    process.stderr.write(`\nError: NERF installation is broken, missing packages: ${missing}\n\n` +
        `This is usually caused by one of:\n` +
        `  • An outdated version installed from npm (run: npm install -g @defconxt/nerf@latest)\n` +
        `  • The packages/ directory was excluded from the installed tarball\n` +
        `  • A filesystem error prevented linking or copying the workspace packages\n\n` +
        `Fix it by reinstalling:\n\n` +
        `  npm install -g @defconxt/nerf@latest\n\n` +
        `If the issue persists, please open an issue at:\n` +
        `  https://github.com/defconxt/NERF/issues\n`);
    process.exit(1);
}
// Dynamic import defers ESM evaluation — config.js will see PI_PACKAGE_DIR above
// ---------------------------------------------------------------------------
// NERF subcommand dispatch — intercept NERF-specific commands before the
// NERF runtime's TTY check in cli.js. These commands produce JSON output and
// exit, so they work in non-interactive (pipe, CI, subprocess) contexts.
// ---------------------------------------------------------------------------
const NERF_COMMANDS = new Set(['status', 'version', 'doctor', 'scan', 'compliance', 'api', 'bot']);
const NERF_MODE_NAMES = new Set(['red', 'blue', 'incident', 'purple', 'recon', 'privacy', 'architect', 'researcher', 'build']);
const hasAutoFlag = args.includes('--auto');
if (hasAutoFlag) {
    // --auto mode dispatch: nerf <mode> --auto "task" or nerf --auto <mode> "task"
    const cleanedArgs = args.filter(a => a !== '--auto');
    const modeName = cleanedArgs[0]?.toLowerCase();
    if (!modeName || !NERF_MODE_NAMES.has(modeName)) {
        process.stderr.write('Error: --auto requires a valid mode name.\n');
        process.stderr.write(`Available modes: ${[...NERF_MODE_NAMES].sort().join(', ')}\n`);
        process.exit(1);
    }
    const taskParts = cleanedArgs.slice(1);
    const taskText = taskParts.join(' ') || '';
    if (process.env.NERF_DEBUG === '1') {
        process.stderr.write(`Auto dispatch mode=${modeName} task="${taskText}"\n`);
    }
    // Import and run autoLoop from the execution engine
    try {
        // @ts-ignore — cli/lib JS modules have no type declarations
        const { autoLoop } = await import('../cli/lib/execution-engine/dispatch.js');
        await autoLoop({ mode: modeName, task: taskText });
    }
    catch (err) {
        process.stderr.write(`Auto mode error: ${err?.message || err}\n`);
        process.exit(1);
    }
    process.exit(0);
}
if (firstArg && NERF_COMMANDS.has(firstArg)) {
    const commandArgs = args.slice(1);
    if (process.env.NERF_DEBUG === '1') {
        process.stderr.write(`Dispatching command=${firstArg} mode=native\n`);
    }
    try {
        // @ts-ignore — cli/lib JS modules have no type declarations
        const commands = await import('../cli/lib/gateway/commands.js');
        let result;
        switch (firstArg) {
            case 'status':
                result = await commands.handleStatus();
                break;
            case 'version':
                result = await commands.handleVersion();
                break;
            case 'doctor':
                result = await commands.handleDoctor();
                break;
            case 'scan':
                result = await commands.handleScan({ target: commandArgs[0] });
                break;
            case 'compliance':
                result = await commands.handleCompliance({ framework: commandArgs[0] });
                break;
            case 'api': {
                // @ts-ignore
                const { createAPIServer } = await import('../cli/lib/api/server.js');
                // @ts-ignore
                const { loadConfig } = await import('../cli/lib/config.js');
                const config = loadConfig();
                const portArg = commandArgs.indexOf('--port');
                const port = portArg >= 0 ? parseInt(commandArgs[portArg + 1], 10) : 8443;
                const api = createAPIServer({ ...config, port });
                const actualPort = await api.start();
                process.stderr.write(`NERF API server listening on port ${actualPort}\n`);
                await new Promise(() => { }); // keep alive
                break;
            }
            case 'bot': {
                // @ts-ignore
                const botMod = await import('../cli/lib/bot/bot.js');
                await botMod.startBot();
                await new Promise(() => { }); // keep alive
                break;
            }
        }
        if (result !== undefined) {
            process.stdout.write(JSON.stringify(result, null, 2) + '\n');
        }
    }
    catch (err) {
        process.stderr.write(`Command error: ${err?.message || err}\n`);
        process.exit(1);
    }
    process.exit(0);
}
await import('./cli.js');
