#!/usr/bin/env node
/**
 * Clawx CLI — terminal-first coding/execution agent.
 *
 * EXTRACTION NOTE:
 * OpenClaw's CLI entry (openclaw.mjs → entry.ts → cli/) handles:
 * - 40+ CLI commands (agent, channel, config, daemon, gateway, install, ...)
 * - Channel initialization and routing
 * - Plugin discovery and loading
 * - Gateway server startup
 * - Daemon management
 * - Update checking
 *
 * We DISCARD all of that. Our CLI has these modes:
 * 1. `clawx` — default: TUI mode (rich terminal UI from pi-coding-agent)
 * 2. `clawx run "prompt"` — single-shot: run a task and exit
 * 3. `clawx chat` — basic readline REPL (fallback)
 * 4. `clawx continue` — resume the last session
 */
import fs from "node:fs";
import path from "node:path";
import readline from "node:readline";
import { Command } from "commander";
import { loadConfig, getGlobalConfigDir, getGlobalConfigPath } from "../config/index.js";
import { runAgent, ToolsNotSupportedError } from "../core/agent.js";
import { createStreamRenderer } from "../core/streaming.js";
import { createSessionId, saveSession, getLatestSession, listSessions, } from "../core/session.js";
import { log } from "../utils/logger.js";
import { startRepl } from "./repl.js";
import { startTui } from "./tui.js";
import { startScout } from "./scout.js";
import { createForgeCommand } from "./forge.js";
import { startForgeTUI } from "./forge-tui.js";
import { VERSION, printBannerCompact } from "./banner.js";
const program = new Command();
program
    .name("clawx")
    .description(`Terminal-first coding agent — runs locally with Ollama, DeepSeek, OpenAI, or any OpenAI-compatible endpoint

In-session tools (available inside TUI/REPL):

  Coding:        read, write, edit, bash, grep, find, ls, search_files,
                 git_status, git_diff, ssh_run

  Web:           web_search, web_read, web_download

  Browser:       browser_open, browser_snapshot, browser_click, browser_type,
                 browser_screenshot, browser_navigate, browser_extract,
                 browser_eval, browser_close

  Agent mgmt:    agent_serve, agent_list, agent_spawn_local, agent_remove,
                 agent_send, agent_run, agent_batch, agent_network,
                 agent_chat, agent_status, agent_result, agent_cleanup,
                 agent_master_status, agent_rehydrate_workers,
                 agent_cleanup_port, agent_cleanup_processes

  Peer federation: agent_peer_add, agent_peer_chat, agent_peer_send,
                   agent_peer_serve, agent_peer_list_workers

  Specialists:   agent_specialist_save, agent_specialist_list

  Persona/memory: agent_persona_show, agent_persona_set,
                  agent_memory_show, agent_memory_update,
                  agent_peer_persona_show, agent_peer_persona_set,
                  agent_peer_memory_show, agent_peer_memory_update

  Scout/Forge:   hf_search, hf_model_info, hf_readme, hf_dataset_search,
                 forge_write_capability, forge_list_capabilities`)
    .version(VERSION);
// Default action: launch TUI when no subcommand given
// e.g. `clawx` or `clawx "build me a flask app"`
program
    .argument("[prompt]", "Optional initial message for TUI mode")
    .option("-m, --model <model>", "Model to use")
    .option("-p, --provider <provider>", "Provider (ollama, deepseek, openai, anthropic, claude-code, codex)")
    .option("-u, --base-url <url>", "Provider base URL")
    .option("-d, --work-dir <dir>", "Working directory")
    .option("-v, --verbose", "Verbose logging")
    .option("--ssh", "Enable SSH tools (requires sshTargets in config)")
    .option("--basic", "Use basic readline REPL instead of TUI")
    .action(async (prompt, opts) => {
    if (opts.verbose)
        log.setLogLevel("info");
    const config = loadConfig({
        model: opts.model,
        provider: opts.provider,
        baseUrl: opts.baseUrl,
        workDir: opts.workDir,
    });
    // SSH tools are opt-in — don't let models randomly SSH into remote systems
    const sshEnabled = !!opts.ssh;
    if (!sshEnabled && opts.basic)
        config.sshTargets = {};
    if (opts.basic) {
        // Basic readline REPL fallback
        const sessionId = createSessionId();
        await startRepl(config, sessionId, []);
        return;
    }
    // TUI mode (rich terminal UI) — SSH tools are registered but inactive unless --ssh
    try {
        await startTui(config, {
            initialMessage: prompt,
            verbose: opts.verbose,
            sshEnabled,
        });
    }
    catch (e) {
        const errMsg = e instanceof Error ? e.message : String(e);
        if (e instanceof ToolsNotSupportedError || errMsg.includes("does not support tools") || errMsg.includes("does not support tool")) {
            console.error(`\n  Model '${config.model}' does not support tool calling.`);
            console.error(`  The agent loop requires structured tool calls to work.\n`);
            console.error(`  Options:`);
            console.error(`    1. Switch to a model that supports tools:`);
            console.error(`       clawx use deepseek`);
            console.error(`       clawx use glm-flash`);
            console.error(`       clawx use qwen35-35b\n`);
            console.error(`    2. Use chat mode (no tools, just conversation):`);
            console.error(`       clawx chat\n`);
            console.error(`  Run 'clawx profiles' to see all available profiles.`);
            process.exit(1);
        }
        // If TUI fails (e.g. missing terminal capabilities), fall back to basic REPL
        console.error(`TUI failed (${errMsg}), falling back to basic REPL...`);
        const sessionId = createSessionId();
        await startRepl(config, sessionId, []);
    }
});
program
    .command("run")
    .description("Run a task and exit")
    .argument("<prompt>", "Task description for the agent")
    .option("-m, --model <model>", "Model to use")
    .option("-p, --provider <provider>", "Provider (openai-completions, anthropic, etc.)")
    .option("-u, --base-url <url>", "Provider base URL")
    .option("-d, --work-dir <dir>", "Working directory")
    .option("-v, --verbose", "Verbose logging")
    .option("--ssh", "Enable SSH tools (requires sshTargets in config)")
    .action(async (prompt, opts) => {
    if (opts.verbose)
        log.setLogLevel("info");
    const config = loadConfig({
        model: opts.model,
        provider: opts.provider,
        baseUrl: opts.baseUrl,
        workDir: opts.workDir,
    });
    if (!opts.ssh)
        config.sshTargets = {};
    const sessionId = createSessionId();
    const session = {
        id: sessionId,
        startedAt: Date.now(),
        workDir: config.workDir,
        model: config.model,
        provider: config.provider,
    };
    printBannerCompact(config.model, config.provider);
    const renderer = createStreamRenderer();
    try {
        const result = await runAgent(config, {
            prompt,
            onEvent: (event) => renderer.onEvent(event),
            parseTextToolCalls: true,
        });
        renderer.finish();
        saveSession(config.sessionDir, session, result.messages);
        process.exit(result.aborted ? 1 : 0);
    }
    catch (e) {
        if (e instanceof ToolsNotSupportedError) {
            renderer.finish();
            console.error(`\n  Model '${config.model}' does not support tool calling.`);
            console.error(`  The agent loop requires structured tool calls to work.\n`);
            console.error(`  Options:`);
            console.error(`    1. Switch to a model that supports tools:`);
            console.error(`       clawx use deepseek`);
            console.error(`       clawx use glm-flash`);
            console.error(`       clawx use qwen35-35b\n`);
            console.error(`    2. Use chat mode (no tools, just conversation):`);
            console.error(`       clawx chat\n`);
            console.error(`  Run 'clawx profiles' to see all available profiles.`);
            process.exit(1);
        }
        console.error(`Fatal: ${e instanceof Error ? e.message : e}`);
        process.exit(1);
    }
});
program
    .command("chat")
    .description("Interactive chat session (no tools, just conversation)")
    .option("-m, --model <model>", "Model to use")
    .option("-p, --provider <provider>", "Provider")
    .option("-u, --base-url <url>", "Provider base URL")
    .option("-d, --work-dir <dir>", "Working directory")
    .option("-v, --verbose", "Verbose logging")
    .option("-c, --continue", "Continue the last session")
    .action(async (opts) => {
    if (opts.verbose)
        log.setLogLevel("info");
    const config = loadConfig({
        model: opts.model,
        provider: opts.provider,
        baseUrl: opts.baseUrl,
        workDir: opts.workDir,
    });
    let messages = [];
    let sessionId = createSessionId();
    if (opts.continue) {
        const latest = getLatestSession(config.sessionDir);
        if (latest) {
            messages = latest.messages;
            sessionId = latest.session.id;
            log.info(`Resuming session ${sessionId}`);
        }
    }
    // `clawx chat` is ALWAYS chat-only — no tools, just conversation.
    // This is the mode users are directed to when their model doesn't support tools.
    await startRepl(config, sessionId, messages, { chatOnly: true });
});
program
    .command("scout")
    .description("AI-powered HuggingFace model researcher — find models that fit your hardware")
    .option("-m, --model <model>", "Model to use for the scout agent")
    .option("-p, --provider <provider>", "Provider")
    .option("-u, --base-url <url>", "Provider base URL")
    .option("-d, --work-dir <dir>", "Working directory")
    .option("-v, --verbose", "Verbose logging")
    .option("--setup-hardware", "Re-prompt hardware specs (GPU, VRAM, RAM)")
    .action(async (opts) => {
    if (opts.verbose)
        log.setLogLevel("info");
    const config = loadConfig({
        model: opts.model,
        provider: opts.provider,
        baseUrl: opts.baseUrl,
        workDir: opts.workDir,
    });
    try {
        await startScout(config, {
            setupHardware: !!opts.setupHardware,
            verbose: opts.verbose,
        });
    }
    catch (e) {
        const errMsg = e instanceof Error ? e.message : String(e);
        console.error(`Scout failed: ${errMsg}`);
        process.exit(1);
    }
});
program
    .command("continue")
    .description("Continue the last session")
    .option("-v, --verbose", "Verbose logging")
    .action(async (opts) => {
    if (opts.verbose)
        log.setLogLevel("info");
    const config = loadConfig();
    const latest = getLatestSession(config.sessionDir);
    if (!latest) {
        console.error("No previous session found.");
        process.exit(1);
    }
    log.info(`Resuming session ${latest.session.id}`);
    await startRepl(config, latest.session.id, latest.messages);
});
program
    .command("sessions")
    .description("List recent sessions")
    .action(() => {
    const config = loadConfig();
    const sessions = listSessions(config.sessionDir);
    if (sessions.length === 0) {
        console.log("No sessions found.");
        return;
    }
    for (const s of sessions.slice(0, 20)) {
        const date = new Date(s.startedAt).toLocaleString();
        console.log(`${s.id}  ${date}  ${s.model}  ${s.workDir}`);
    }
});
program
    .command("init")
    .description("Set up Clawx — configure provider, model, and API key")
    .action(async () => {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    const ask = (q, def) => new Promise((resolve) => {
        const suffix = def ? ` [${def}]` : "";
        rl.question(`${q}${suffix}: `, (answer) => resolve(answer.trim() || def || ""));
    });
    console.log("\n  Clawx Setup\n");
    console.log("  Providers:");
    console.log("    1. ollama       — Local models via Ollama (free)");
    console.log("    2. deepseek     — DeepSeek API (very cheap)");
    console.log("    3. openai       — OpenAI API (GPT-4o, etc.)");
    console.log("    4. anthropic    — Anthropic API (Claude)");
    console.log("    5. local        — Any OpenAI-compatible endpoint\n");
    const providerChoice = await ask("  Choose provider (1-5)", "1");
    const providerMap = {
        "1": { provider: "ollama", baseUrl: "http://localhost:11434/v1", model: "glm-4.7-flash:latest", needsKey: false },
        "2": { provider: "deepseek", baseUrl: "https://api.deepseek.com/v1", model: "deepseek-chat", needsKey: true },
        "3": { provider: "openai", baseUrl: "https://api.openai.com/v1", model: "gpt-4o", needsKey: true },
        "4": { provider: "anthropic", baseUrl: "https://api.anthropic.com", model: "claude-sonnet-4-20250514", needsKey: true },
        "5": { provider: "local", baseUrl: "http://localhost:8080/v1", model: "my-model", needsKey: false },
        ollama: { provider: "ollama", baseUrl: "http://localhost:11434/v1", model: "glm-4.7-flash:latest", needsKey: false },
        deepseek: { provider: "deepseek", baseUrl: "https://api.deepseek.com/v1", model: "deepseek-chat", needsKey: true },
        openai: { provider: "openai", baseUrl: "https://api.openai.com/v1", model: "gpt-4o", needsKey: true },
        anthropic: { provider: "anthropic", baseUrl: "https://api.anthropic.com", model: "claude-sonnet-4-20250514", needsKey: true },
        local: { provider: "local", baseUrl: "http://localhost:8080/v1", model: "my-model", needsKey: false },
    };
    const selected = providerMap[providerChoice] || providerMap["1"];
    const baseUrl = await ask("  Base URL", selected.baseUrl);
    const model = await ask("  Model", selected.model);
    let apiKey = "not-needed";
    if (selected.needsKey) {
        apiKey = await ask("  API key");
        if (!apiKey) {
            console.error("\n  API key is required for this provider.");
            rl.close();
            process.exit(1);
        }
    }
    const maxTokens = await ask("  Max output tokens", "16384");
    rl.close();
    // Write config
    const configDir = getGlobalConfigDir();
    fs.mkdirSync(configDir, { recursive: true });
    const configContent = [
        `# Clawx config — generated by clawx init`,
        `CLAWDEX_PROVIDER=${selected.provider}`,
        `CLAWDEX_BASE_URL=${baseUrl}`,
        `CLAWDEX_MODEL=${model}`,
        `CLAWDEX_API_KEY=${apiKey}`,
        `CLAWDEX_THINKING_LEVEL=off`,
        `CLAWDEX_MAX_TOKENS=${maxTokens}`,
        `CLAWDEX_EXEC_TIMEOUT=120000`,
        "",
    ].join("\n");
    const configPath = getGlobalConfigPath();
    fs.writeFileSync(configPath, configContent, "utf-8");
    console.log(`\n  Config saved to ${configPath}`);
    // Auto-save as a profile
    const profileName = model.replace(/[:/]/g, "-").replace(/:latest$/, "");
    const profilesDir = path.join(configDir, "profiles");
    fs.mkdirSync(profilesDir, { recursive: true });
    fs.writeFileSync(path.join(profilesDir, profileName), configContent, "utf-8");
    fs.writeFileSync(path.join(configDir, "active-profile"), profileName, "utf-8");
    console.log(`  Profile '${profileName}' saved. Switch back anytime with: clawx use ${profileName}`);
    // For Ollama: check if model is available and offer to pull
    if (selected.provider === "ollama") {
        try {
            const ollamaBase = baseUrl.replace(/\/v1\/?$/, "");
            const res = await fetch(`${ollamaBase}/api/tags`);
            if (res.ok) {
                const data = (await res.json());
                const models = data.models || [];
                const found = models.some((m) => m.name === model || m.name === `${model}:latest` || `${m.name}:latest` === model);
                if (!found) {
                    const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
                    const pullAnswer = await new Promise((resolve) => {
                        rl2.question(`\n  Model '${model}' not found in Ollama. Pull it now? [Y/n] `, (a) => {
                            rl2.close();
                            resolve(a.trim().toLowerCase() || "y");
                        });
                    });
                    if (pullAnswer === "y" || pullAnswer === "yes") {
                        console.log(`\n  Pulling ${model}... (this may take a while)\n`);
                        const { execSync } = await import("node:child_process");
                        try {
                            execSync(`ollama pull ${model}`, { stdio: "inherit" });
                            console.log(`\n  Model pulled successfully.`);
                        }
                        catch {
                            console.error(`\n  Failed to pull model. Run manually: ollama pull ${model}`);
                        }
                    }
                    else {
                        console.log(`\n  Skipped. Run 'ollama pull ${model}' before using clawx.`);
                    }
                }
                else {
                    console.log(`  Model '${model}' is available in Ollama.`);
                }
            }
        }
        catch {
            console.log(`  Could not reach Ollama at ${baseUrl} — make sure 'ollama serve' is running.`);
        }
    }
    console.log(`\n  Run 'clawx' to start!\n`);
});
// --- Profile management ---
function getProfilesDir() {
    return path.join(getGlobalConfigDir(), "profiles");
}
function getActiveProfileName() {
    const activeFile = path.join(getGlobalConfigDir(), "active-profile");
    if (fs.existsSync(activeFile))
        return fs.readFileSync(activeFile, "utf-8").trim();
    return null;
}
function setActiveProfileName(name) {
    fs.writeFileSync(path.join(getGlobalConfigDir(), "active-profile"), name, "utf-8");
}
function parseProfileConfig(content) {
    const result = {};
    for (const line of content.split("\n")) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith("#"))
            continue;
        const eq = trimmed.indexOf("=");
        if (eq > 0)
            result[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
    }
    return result;
}
program
    .command("add")
    .description("Save current config as a named profile")
    .argument("<name>", "Profile name (e.g. deepseek, qwen35, gpt4)")
    .action((name) => {
    const profilesDir = getProfilesDir();
    fs.mkdirSync(profilesDir, { recursive: true });
    const configPath = getGlobalConfigPath();
    if (!fs.existsSync(configPath)) {
        console.error("  No active config found. Run 'clawx init' first.");
        process.exit(1);
    }
    const content = fs.readFileSync(configPath, "utf-8");
    const profilePath = path.join(profilesDir, name);
    fs.writeFileSync(profilePath, content, "utf-8");
    setActiveProfileName(name);
    const parsed = parseProfileConfig(content);
    console.log(`\n  Profile '${name}' saved.`);
    console.log(`  → ${parsed.CLAWDEX_MODEL || "unknown"} via ${parsed.CLAWDEX_PROVIDER || "unknown"}\n`);
});
program
    .command("use")
    .description("Switch to a saved profile")
    .argument("<name>", "Profile name")
    .action((name) => {
    const profilePath = path.join(getProfilesDir(), name);
    if (!fs.existsSync(profilePath)) {
        console.error(`\n  Profile '${name}' not found.`);
        console.error(`  Run 'clawx profiles' to see available profiles.\n`);
        process.exit(1);
    }
    const content = fs.readFileSync(profilePath, "utf-8");
    const configPath = getGlobalConfigPath();
    fs.writeFileSync(configPath, content, "utf-8");
    setActiveProfileName(name);
    const parsed = parseProfileConfig(content);
    console.log(`\n  Switched to '${name}'`);
    console.log(`  → ${parsed.CLAWDEX_MODEL || "unknown"} via ${parsed.CLAWDEX_PROVIDER || "unknown"}\n`);
});
program
    .command("profiles")
    .description("List saved profiles")
    .action(() => {
    const profilesDir = getProfilesDir();
    if (!fs.existsSync(profilesDir)) {
        console.log("\n  No profiles saved yet. Run 'clawx init' or 'clawx add <name>'.\n");
        return;
    }
    const files = fs.readdirSync(profilesDir).filter((f) => !f.startsWith("."));
    if (files.length === 0) {
        console.log("\n  No profiles saved yet. Run 'clawx init' or 'clawx add <name>'.\n");
        return;
    }
    const active = getActiveProfileName();
    console.log("\n  Saved profiles:\n");
    for (const name of files) {
        const content = fs.readFileSync(path.join(profilesDir, name), "utf-8");
        const parsed = parseProfileConfig(content);
        const model = parsed.CLAWDEX_MODEL || "unknown";
        const provider = parsed.CLAWDEX_PROVIDER || "unknown";
        const marker = name === active ? " ← active" : "";
        console.log(`    ${name.padEnd(20)} ${model} via ${provider}${marker}`);
    }
    console.log(`\n  Switch with: clawx use <name>\n`);
});
program
    .command("remove")
    .description("Delete a saved profile")
    .argument("<name>", "Profile name to remove")
    .action((name) => {
    const profilePath = path.join(getProfilesDir(), name);
    if (!fs.existsSync(profilePath)) {
        console.error(`\n  Profile '${name}' not found.\n`);
        process.exit(1);
    }
    fs.unlinkSync(profilePath);
    const active = getActiveProfileName();
    if (active === name) {
        fs.unlinkSync(path.join(getGlobalConfigDir(), "active-profile"));
    }
    console.log(`\n  Profile '${name}' removed.\n`);
});
// Add new Forge TUI command
program
    .command("forge")
    .description("Interactive capability builder (TUI mode)")
    .option("-m, --model <model>", "Model to use (e.g., 'qwen2.5-coder:7b-instruct', 'deepseek-chat')")
    .option("-p, --provider <provider>", "Provider: 'ollama', 'deepseek', 'openai', 'anthropic', 'groq', 'together'")
    .option("--base-url <url>", "Custom API base URL")
    .option("--api-key <key>", "API key (if required)")
    .option("--max-tokens <number>", "Maximum tokens to generate", "16384")
    .option("--thinking-level <level>", "Thinking level: 'off', 'fast', 'medium', 'long'", "off")
    .option("--work-dir <path>", "Working directory for file operations", ".")
    .action((options) => {
    const config = loadConfig();
    // Override with command-line options
    if (options.model)
        config.model = options.model;
    if (options.provider)
        config.provider = options.provider;
    if (options.baseUrl)
        config.baseUrl = options.baseUrl;
    if (options.apiKey)
        config.apiKey = options.apiKey;
    if (options.maxTokens)
        config.maxTokens = parseInt(options.maxTokens, 10);
    if (options.thinkingLevel)
        config.thinkingLevel = options.thinkingLevel;
    if (options.workDir)
        config.workDir = options.workDir;
    startForgeTUI(config);
});
// Add old deterministic Forge command (temporary - will be removed)
const oldForgeCommand = createForgeCommand(loadConfig());
oldForgeCommand.name("forge-old");
oldForgeCommand.description("[DEPRECATED] Old deterministic Forge CLI");
program.addCommand(oldForgeCommand);
// Add agent commands
import { createAgentCommand } from './agent.js';
program.addCommand(createAgentCommand());
program.parse();
//# sourceMappingURL=main.js.map