#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const yaml = __importStar(require("yaml"));
const dotenv_1 = __importDefault(require("dotenv"));
const majk_chat_core_1 = require("@majkapp/majk-chat-core");
const interactive_1 = require("./interactive/interactive");
const credentials_1 = require("./utils/credentials");
const profile_config_1 = require("./utils/profile-config");
const config_1 = require("./utils/config");
const majk_chat_mcp_1 = require("@majkapp/majk-chat-mcp");
const majk_chat_basic_tools_1 = require("@majkapp/majk-chat-basic-tools");
const majk_chat_document_tools_1 = require("@majkapp/majk-chat-document-tools");
const majk_chat_sessions_1 = require("@majkapp/majk-chat-sessions");
const models_1 = require("./utils/models");
const tool_filter_1 = require("./utils/tool-filter");
const output_1 = require("./utils/output");
const executors_1 = require("./executors");
// Load environment variables
dotenv_1.default.config();
const program = new commander_1.Command();
program
    .name('majk-chat')
    .description('CLI for multi-provider LLM chat interactions.\n\n' +
    'Run `majk-chat help-integration` for a full programmatic integration guide\n' +
    '(embedding @majkapp/majk-chat-common in your Node.js application).')
    .version('1.0.39');
// Add credential options helper function
function addCredentialOptions(command) {
    return command
        // Generic credentials
        .option('--api-key <key>', 'Generic API key (overrides environment)')
        .option('--api-key-file <file>', 'Read API key from file')
        .option('--env-file <file>', 'Load environment variables from file')
        // OpenAI specific
        .option('--openai-api-key <key>', 'OpenAI API key')
        .option('--openai-org-id <id>', 'OpenAI organization ID')
        // Anthropic specific
        .option('--anthropic-api-key <key>', 'Anthropic API key')
        // Azure OpenAI specific
        .option('--azure-openai-api-key <key>', 'Azure OpenAI API key')
        .option('--azure-openai-endpoint <url>', 'Azure OpenAI endpoint URL')
        .option('--azure-openai-deployment <name>', 'Azure OpenAI deployment name')
        // AWS Bedrock specific
        .option('--aws-access-key-id <id>', 'AWS access key ID')
        .option('--aws-secret-access-key <key>', 'AWS secret access key')
        .option('--aws-session-token <token>', 'AWS session token')
        .option('--aws-region <region>', 'AWS region')
        .option('--bedrock-api-key <key>', 'AWS Bedrock API key (bearer token)')
        // Profile system
        .option('--profile <name>', 'Use specific profile (default: "default")');
}
// Chat command
const chatCommand = program
    .command('chat [message...]')
    .description('Send a chat message to an LLM provider')
    .option('--agent <type>', 'Executor to use: "majk" (default) or "claude-code". Can also be set via MAJK_AGENT environment variable')
    .option('--provider <provider>', 'Provider to use (openai, anthropic, azure-openai, bedrock)')
    .option('--model <model>', 'Model or alias (e.g., "sonnet", "opus", "claude-3-5-sonnet-20241022")')
    .option('-M, --message <message>', 'Message to send')
    .option('-p <value>', 'JSON messages array or prompt string')
    .option('-f, --file <file>', 'Read message from file')
    .option('--image <paths...>', 'One or more image file paths to include with the message (PNG, JPEG, GIF, WebP)')
    .option('--prompts <file>', 'File with one prompt per line (sequential execution)')
    .option('-s, --system <system>', 'System message')
    .option('-S, --system-file <file>', 'Read system message from file')
    .option('--append-system-prompt <prompt>', 'Append to the default system prompt')
    .option('-t, --temperature <temp>', 'Temperature (0-2)', parseFloat)
    .option('--max-tokens <tokens>', 'Maximum tokens', parseInt)
    .option('-j, --json', 'Output JSON response')
    .option('--output-format <format>', 'Output format: "text" (default) or "stream-json"', 'text')
    .option('--tools', 'Enable core tools (bash, read, write, etc.) - equivalent to --enable-core-tools')
    .option('--enable-core-tools', 'Enable majk-chat-basic-tools (bash, ls, read, etc.)')
    .option('--enable-read-only-tools', 'Enable read-only majk-chat-basic-tools (ls, read, grep, web_fetch, etc.)')
    .option('--disable-tools', 'Disable all tools including read-only tools (overrides defaults)')
    .option('--planning-mode', 'Enable planning mode tools (enter_planning_mode, exit_planning_mode, planning_todo, todo_reminder)')
    .option('--todo-reminder-interval <interval>', 'Number of iterations between automatic todo reminders (default: 3)', parseInt, 3)
    .option('--disable-auto-reminders', 'Disable automatic todo reminders in planning mode')
    .option('--tool-result-limit <limit>', 'Truncate tool results over this character limit', parseInt, 8000)
    .option('--max-steps <steps>', 'Maximum tool execution steps', parseInt)
    .option('-c, --config <file>', 'Configuration file')
    .option('--mcp-config <value>', 'MCP configuration file path or JSON string')
    .option('--mcp-servers <json>', 'MCP servers configuration as JSON string (deprecated, use --mcp-config)')
    .option('--allowed-tools, --allowedTools <tools>', 'Comma/space-separated allowed tools (supports wildcards like "mcp__*")')
    .option('--disallowed-tools, --disallowedTools <tools>', 'Comma/space-separated disallowed tools (supports wildcards)')
    .option('--permission-prompt-tool <tool>', 'MCP tool to use for permission prompts')
    .option('--save-session', 'Save conversation to a persistent session')
    .option('--continue', 'Continue the most recent session in current directory')
    .option('--session <uuid>', 'Continue a specific session by UUID')
    .option('--session-title <title>', 'Set title for new session')
    .option('--list-sessions', 'List all sessions for current directory')
    .option('--delete-session <uuid>', 'Delete a specific session by UUID')
    .option('--dry-run', 'Enable dry run mode - plan operations without executing them')
    .option('--dry-run-review', 'Enable dry run mode with interactive review and execution of planned operations')
    .option('--save-prompts <file>', 'Save all prompts to JSONL file for analysis and debugging');
addCredentialOptions(chatCommand)
    .action(async (messageArgs, options) => {
    let cleanup = null;
    // Initialize output handler early so we can pass callbacks
    const outputHandler = new output_1.OutputHandler(options.outputFormat);
    // Handle positional message arguments
    if (messageArgs && messageArgs.length > 0 && !options.message && !options.file && !options.p) {
        options.message = messageArgs.join(' ');
    }
    try {
        const agentType = options.agent || process.env.MAJK_AGENT || 'majk';
        if (agentType === 'claude-code' || agentType === 'claude') {
            await handleClaudeCodeExecutor(options, outputHandler);
            return;
        }
        const setupOptions = {
            ...options,
            outputFormat: options.outputFormat, // Pass output format to suppress logs
            onToolCall: options.outputFormat === 'stream-json'
                ? (toolCall) => outputHandler.outputToolCallMessage(toolCall)
                : undefined,
            onToolResult: options.outputFormat === 'stream-json'
                ? (toolCall, result) => outputHandler.outputToolResult(toolCall.id, result)
                : undefined
        };
        const setupResult = await setupChat(setupOptions);
        const { chat, toolRegistry, dryRunManager, dryRunSessionId } = setupResult;
        cleanup = setupResult.cleanup;
        // Resolve model alias if provided
        // Map provider aliases to actual provider IDs (used for model resolution and session creation)
        const providerHint = (options.provider === 'bedrock' ? 'anthropic-bedrock' : options.provider);
        const context = { requestId: `cli-chat-${Date.now()}` };
        // Resolve model and capture the actual provider used
        // This ensures we use the provider inferred from the model (e.g., 'opus' -> 'anthropic')
        let model;
        let actualProvider;
        if (options.model) {
            const resolved = await (0, models_1.resolveModelWithMetadata)(chat, options.model, providerHint, context);
            model = resolved.modelId;
            actualProvider = resolved.provider;
        }
        else {
            // When no model specified, need a provider to get default
            actualProvider = providerHint || 'openai';
            model = await (0, models_1.getDefaultModel)(chat, actualProvider, undefined, context);
        }
        // Get tool definitions from registry
        const tools = toolRegistry.getDefinitions();
        const toolNames = tools.map((t) => t.function.name);
        // Update output handler with model and tool info
        outputHandler.options.model = model;
        outputHandler.options.tools = toolNames;
        outputHandler.options.cwd = process.cwd();
        // Initialize session manager and handle session operations
        const currentWorkingDir = process.cwd();
        const usesSessions = options.saveSession || options.continue || options.session || options.listSessions || options.deleteSession;
        let sessionManager = null;
        let existingMessages = [];
        let sessionId = null;
        if (usesSessions) {
            sessionManager = new majk_chat_sessions_1.SessionManager();
            // Handle session operations first
            if (options.listSessions) {
                await handleListSessions(sessionManager, currentWorkingDir, outputHandler);
                await cleanup();
                process.exit(0);
                return;
            }
            if (options.deleteSession) {
                await handleDeleteSession(sessionManager, currentWorkingDir, options.deleteSession, outputHandler);
                await cleanup();
                process.exit(0);
                return;
            }
            // Load existing session if requested
            if (options.continue) {
                const sessionData = await sessionManager.continueLatestSession(currentWorkingDir);
                if (sessionData) {
                    existingMessages = sessionData.messages;
                    sessionId = sessionData.metadata.id;
                    // Update OutputHandler to use the actual session ID
                    outputHandler.setSessionId(sessionId);
                    if (options.outputFormat !== 'stream-json') {
                        console.log(chalk_1.default.cyan(`Continuing session: ${sessionId}${sessionData.metadata.title ? ` (${sessionData.metadata.title})` : ''}`));
                    }
                }
                else if (options.outputFormat !== 'stream-json') {
                    console.log(chalk_1.default.yellow('No existing sessions found in this directory. Starting new session.'));
                }
            }
            else if (options.session) {
                const sessionData = await sessionManager.loadSession(options.session, currentWorkingDir);
                if (sessionData) {
                    existingMessages = sessionData.messages;
                    sessionId = sessionData.metadata.id;
                    // Update OutputHandler to use the actual session ID
                    outputHandler.setSessionId(sessionId);
                    if (options.outputFormat !== 'stream-json') {
                        console.log(chalk_1.default.cyan(`Loaded session: ${sessionId}${sessionData.metadata.title ? ` (${sessionData.metadata.title})` : ''}`));
                    }
                }
                else {
                    outputHandler.outputError(`Session ${options.session} not found in current directory`);
                    await cleanup();
                    process.exit(1);
                }
            }
        }
        // For new sessions, use OutputHandler's session ID as the canonical ID
        if (usesSessions && !sessionId) {
            sessionId = outputHandler.getSessionId();
        }
        // Initialize session for stream-json
        outputHandler.initializeSession(toolNames, [], model);
        // Handle sequential prompts from file
        if (options.prompts) {
            if (sessionManager) {
                await handleSequentialPromptsWithSessions(chat, toolRegistry, options, outputHandler, sessionManager, sessionId);
            }
            else {
                await handleSequentialPrompts(chat, toolRegistry, options, outputHandler);
            }
            await cleanup();
            process.exit(0);
            return;
        }
        // Get messages based on input method
        const newMessages = await buildMessages(options);
        if (!newMessages || newMessages.length === 0) {
            outputHandler.outputError('No message provided');
            await cleanup();
            process.exit(1);
        }
        // Combine existing messages with new messages, excluding duplicate system messages
        const allMessages = [...existingMessages];
        for (const newMsg of newMessages) {
            if (newMsg.role === 'system' && allMessages.some(m => m.role === 'system')) {
                continue; // Skip duplicate system message
            }
            allMessages.push(newMsg);
        }
        const spinner = outputHandler.startSpinner('Sending message...');
        const request = {
            model,
            messages: allMessages,
            temperature: options.temperature,
            max_tokens: options.maxTokens,
            // Always include tools if any are registered (from MCP or basic tools)
            tools: tools.length > 0 ? tools : undefined,
            // Enable auto-execution if tools are enabled (default unless --disable-tools) or MCP configured
            max_steps: (options.tools || !options.disableTools || options.mcpConfig || options.mcpServers)
                ? (options.maxSteps || (!options.disableTools && !options.tools && !options.mcpConfig && !options.mcpServers ? 500 : 5))
                : 0,
            // Request transcript when saving sessions to capture complete tool execution history
            return_transcript: usesSessions
        };
        // actualProvider already declared above for model resolution
        const chatSession = chat.createSession(actualProvider);
        const lastMessage = allMessages[allMessages.length - 1];
        const messageContent = typeof lastMessage.content === 'string'
            ? lastMessage.content
            : JSON.stringify(lastMessage.content);
        const apiStart = Date.now();
        const response = await chatSession.send(messageContent, request);
        const apiDuration = Date.now() - apiStart;
        if (spinner)
            spinner.succeed('Response received');
        if (options.json) {
            console.log(JSON.stringify(response, null, 2));
        }
        else {
            const assistantMessage = response.choices[0].message;
            // Output assistant message with tool calls
            outputHandler.outputMessage('assistant', assistantMessage.content, response.usage, assistantMessage.tool_calls);
            // Output tool results if any (for text format)
            if (assistantMessage.tool_calls && options.outputFormat === 'text') {
                for (const toolCall of assistantMessage.tool_calls) {
                    outputHandler.outputToolCall(toolCall.function.name, JSON.parse(toolCall.function.arguments));
                }
            }
            outputHandler.outputComplete(response.usage, apiDuration);
        }
        // Handle dry run review and execution if enabled
        if (dryRunManager && dryRunSessionId) {
            try {
                const context = {
                    requestId: 'cli-session',
                    metadata: { dryRunSessionId }
                };
                if (options.dryRunReview) {
                    // Interactive review and execution
                    await dryRunManager.reviewAndExecute(dryRunSessionId, context);
                }
                else {
                    // Just show summary for --dry-run
                    majk_chat_basic_tools_1.InteractiveReviewer.showSummary(dryRunSessionId);
                }
                // End the dry run session
                dryRunManager.endDryRun();
            }
            catch (error) {
                console.error(chalk_1.default.red('Dry run error:'), error instanceof Error ? error.message : error);
            }
        }
        // Save conversation to session (only if session tracking is enabled)
        if (sessionManager) {
            sessionId = await saveConversationToSession(sessionManager, currentWorkingDir, sessionId, allMessages, response, outputHandler, {
                provider: options.provider,
                model,
                title: options.sessionTitle
            });
        }
        // Clean up and exit
        await cleanup();
        // Force exit after a short delay to ensure cleanup completes
        setTimeout(() => {
            process.exit(0);
        }, 100);
    }
    catch (error) {
        outputHandler.outputError(error instanceof Error ? error : String(error));
        if (cleanup) {
            try {
                await cleanup();
            }
            catch (cleanupError) {
                if (options.outputFormat !== 'stream-json') {
                    console.error('Cleanup error:', cleanupError);
                }
            }
        }
        process.exit(1);
    }
});
// Interactive mode command
const interactiveCommand = program
    .command('interactive')
    .alias('i')
    .description('Start interactive chat mode')
    .option('-p, --provider <provider>', 'Provider to use')
    .option('-m, --model <model>', 'Model to use')
    .option('-s, --system <system>', 'System message')
    .option('-S, --system-file <file>', 'Read system message from file')
    .option('--tools', 'Enable core tools (bash, read, write, etc.) - equivalent to --enable-core-tools')
    .option('--enable-core-tools', 'Enable majk-chat-basic-tools (bash, ls, read, etc.)')
    .option('--enable-read-only-tools', 'Enable read-only majk-chat-basic-tools (ls, read, grep, web_fetch, etc.)')
    .option('--disable-tools', 'Disable all tools including read-only tools (overrides defaults)')
    .option('--planning-mode', 'Enable planning mode tools (enter_planning_mode, exit_planning_mode, planning_todo, todo_reminder)')
    .option('--todo-reminder-interval <interval>', 'Number of iterations between automatic todo reminders (default: 3)', parseInt, 3)
    .option('--disable-auto-reminders', 'Disable automatic todo reminders in planning mode')
    .option('--tool-result-limit <limit>', 'Truncate tool results over this character limit', parseInt, 8000)
    .option('--auto-execute', 'Auto-execute tools without confirmation')
    .option('-c, --config <file>', 'Configuration file')
    .option('--mcp-config <value>', 'MCP configuration file path or JSON string')
    .option('--mcp-servers <json>', 'MCP servers configuration as JSON string (deprecated, use --mcp-config)')
    .option('--allowed-tools <tools>', 'Comma-separated list of allowed MCP tools')
    .option('--disallowed-tools <tools>', 'Comma-separated list of disallowed MCP tools')
    .option('--permission-prompt-tool <tool>', 'MCP tool to use for permission prompts');
addCredentialOptions(interactiveCommand)
    .action(async (options) => {
    let cleanup = null;
    try {
        const setupResult = await setupChat(options);
        const { chat, toolRegistry } = setupResult;
        cleanup = setupResult.cleanup;
        const systemMessage = await getSystemMessage(options);
        // Check if we have tools available from MCP or basic tools
        const hasTools = toolRegistry.getDefinitions().length > 0;
        const interactive = new interactive_1.InteractiveMode(chat, {
            provider: options.provider,
            model: options.model || (0, models_1.getDefaultModelSync)(options.provider),
            systemMessage,
            enableTools: options.tools || hasTools, // Enable if tools flag or MCP tools available
            autoExecute: options.autoExecute
        });
        await interactive.start();
        // Clean up when interactive mode exits
        if (cleanup) {
            await cleanup();
        }
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        if (cleanup) {
            try {
                await cleanup();
            }
            catch (cleanupError) {
                if (options.outputFormat !== 'stream-json') {
                    console.error('Cleanup error:', cleanupError);
                }
            }
        }
        process.exit(1);
    }
});
// List models command
const modelsCommand = program
    .command('models')
    .description('List available models')
    .option('-p, --provider <provider>', 'Filter by provider')
    .option('-t, --tier <tier>', 'Filter by tier (opus, sonnet, haiku, mini, pro, etc.)')
    .option('--show-metadata', 'Show detailed metadata (family, version, tags)')
    .option('-c, --config <file>', 'Configuration file');
addCredentialOptions(modelsCommand)
    .action(async (options) => {
    try {
        const { chat } = await setupChat(options);
        const spinner = (0, ora_1.default)('Fetching models...').start();
        const providers = options.provider
            ? [options.provider]
            : chat.listProviders();
        const allModels = [];
        for (const provider of providers) {
            try {
                const providerAdapter = chat.getProvider(provider);
                const response = await providerAdapter.listModels({
                    requestId: `models-${Date.now()}`
                });
                for (const model of response.data) {
                    allModels.push({
                        ...model,
                        provider
                    });
                }
            }
            catch (error) {
                console.warn(chalk_1.default.yellow(`Warning: Could not fetch models from ${provider}`));
            }
        }
        spinner.succeed('Models fetched');
        if (allModels.length === 0) {
            console.log(chalk_1.default.yellow('No models found'));
            return;
        }
        // Filter by tier if specified
        let filteredModels = allModels;
        if (options.tier) {
            const tierLower = options.tier.toLowerCase();
            filteredModels = allModels.filter(m => m.tier?.toLowerCase() === tierLower);
            if (filteredModels.length === 0) {
                console.log(chalk_1.default.yellow(`No models found with tier: ${options.tier}`));
                return;
            }
        }
        // Sort models by version (highest first) within each provider
        filteredModels.sort((a, b) => {
            // First sort by provider
            if (a.provider !== b.provider) {
                return a.provider.localeCompare(b.provider);
            }
            // Then by tier (opus > sonnet > haiku, pro > standard > mini)
            const tierOrder = {
                opus: 1, pro: 1,
                sonnet: 2, standard: 2,
                haiku: 3, mini: 3, turbo: 3
            };
            const aTier = tierOrder[a.tier?.toLowerCase() || ''] || 99;
            const bTier = tierOrder[b.tier?.toLowerCase() || ''] || 99;
            if (aTier !== bTier)
                return aTier - bTier;
            // Finally by version (highest first)
            if (a.version && b.version) {
                const { ModelRegistry } = require('@majkapp/majk-chat-core');
                return ModelRegistry.compareModelVersions(b.version, a.version); // Reversed for descending
            }
            return 0;
        });
        console.log(chalk_1.default.cyan('\nAvailable Models:'));
        for (const model of filteredModels) {
            console.log(`  ${chalk_1.default.green(model.id)} (${model.provider})`);
            if (options.showMetadata) {
                if (model.displayName)
                    console.log(`    Display: ${model.displayName}`);
                if (model.family)
                    console.log(`    Family: ${model.family}`);
                if (model.tier)
                    console.log(`    Tier: ${model.tier}`);
                if (model.version) {
                    const versionStr = `${model.version.major}.${model.version.minor || 0}` +
                        (model.version.datestamp ? ` (${model.version.datestamp})` : '');
                    console.log(`    Version: ${versionStr}`);
                }
                if (model.tags && model.tags.length > 0) {
                    console.log(`    Tags: ${model.tags.join(', ')}`);
                }
            }
            if (model.capabilities) {
                const caps = [];
                if (model.capabilities.tools)
                    caps.push('tools');
                if (model.capabilities.modalities?.includes('vision'))
                    caps.push('vision');
                if (model.capabilities.json_mode)
                    caps.push('json');
                if (caps.length > 0) {
                    console.log(`    Capabilities: ${caps.join(', ')}`);
                }
                if (model.capabilities.max_input_tokens) {
                    console.log(`    Max input: ${(model.capabilities.max_input_tokens / 1000).toFixed(0)}K tokens`);
                }
                if (model.capabilities.max_output_tokens) {
                    console.log(`    Max output: ${(model.capabilities.max_output_tokens / 1000).toFixed(0)}K tokens`);
                }
            }
        }
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
// Configure command (legacy)
program
    .command('config')
    .description('Configure providers and credentials')
    .option('--init', 'Initialize configuration')
    .option('--set <key=value>', 'Set configuration value')
    .option('--get <key>', 'Get configuration value')
    .option('--list', 'List configuration')
    .action(async (options) => {
    const configPath = path.join(process.env.HOME || '~', '.majk-chat', 'config.yaml');
    if (options.init) {
        await initializeConfig(configPath);
        console.log(chalk_1.default.green(`Configuration initialized at ${configPath}`));
    }
    else if (options.set) {
        await setConfigValue(configPath, options.set);
    }
    else if (options.get) {
        await getConfigValue(configPath, options.get);
    }
    else if (options.list) {
        await listConfig(configPath);
    }
    else {
        console.log(chalk_1.default.yellow('Please specify an action: --init, --set, --get, or --list'));
    }
});
// Profile configuration commands
program
    .command('configure-provider')
    .description('Interactively configure a provider')
    .argument('<provider>', 'Provider to configure (openai, anthropic, azure-openai, bedrock)')
    .option('--profile <name>', 'Profile name (default: "default")')
    .option('--skip-validation', 'Skip API key validation')
    .action(async (provider, options) => {
    try {
        const profileManager = new profile_config_1.ProfileConfigManager();
        await profileManager.configureProvider(provider, options.profile || 'default', options.skipValidation || false);
        // Force clean exit after interactive prompt
        process.exit(0);
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
// Direct configuration commands
program
    .command('set-key')
    .description('Set API keys directly for providers')
    .option('--openai <key>', 'OpenAI API key')
    .option('--anthropic <key>', 'Anthropic API key')
    .option('--azure-openai <key> <endpoint> <deployment>', 'Azure OpenAI credentials (key endpoint deployment)')
    .option('--bedrock <accessKey> <secretKey> [region]', 'AWS Bedrock credentials (accessKey secretKey region)')
    .option('--profile <name>', 'Profile to store configuration in (default: "default")')
    .option('--skip-validation', 'Skip API key validation')
    .action(async (options) => {
    try {
        const profileManager = new profile_config_1.ProfileConfigManager();
        const directOptions = {
            profile: options.profile || 'default',
            skipValidation: options.skipValidation || false
        };
        // Handle OpenAI
        if (options.openai) {
            directOptions.setOpenaiKey = options.openai;
        }
        // Handle Anthropic
        if (options.anthropic) {
            directOptions.setAnthropicKey = options.anthropic;
        }
        // Handle Azure OpenAI
        if (options.azureOpenai) {
            const args = Array.isArray(options.azureOpenai) ? options.azureOpenai : [options.azureOpenai];
            if (args.length >= 3) {
                directOptions.setAzureOpenaiKey = args[0];
                directOptions.azureOpenaiEndpoint = args[1];
                directOptions.azureOpenaiDeployment = args[2];
            }
            else {
                throw new Error('Azure OpenAI requires key, endpoint, and deployment name');
            }
        }
        // Handle Bedrock
        if (options.bedrock) {
            const args = Array.isArray(options.bedrock) ? options.bedrock : [options.bedrock];
            if (args.length >= 2) {
                directOptions.setBedrock = true;
                directOptions.awsAccessKeyId = args[0];
                directOptions.awsSecretAccessKey = args[1];
                directOptions.awsRegion = args[2] || 'us-east-1';
            }
            else {
                throw new Error('Bedrock requires access key and secret key');
            }
        }
        // Check if any provider was specified
        if (!directOptions.setOpenaiKey && !directOptions.setAnthropicKey &&
            !directOptions.setAzureOpenaiKey && !directOptions.setBedrock) {
            console.log(chalk_1.default.yellow('No provider specified. Use --openai, --anthropic, --azure-openai, or --bedrock'));
            process.exit(1);
        }
        await profileManager.setDirectConfig(directOptions);
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
// Profile management commands
program
    .command('list-profiles')
    .description('List all available profiles')
    .action(async () => {
    try {
        const profileManager = new profile_config_1.ProfileConfigManager();
        const profiles = await profileManager.listProfiles();
        if (profiles.length === 0) {
            console.log(chalk_1.default.yellow('No profiles configured'));
            return;
        }
        console.log(chalk_1.default.cyan('\nConfigured profiles:'));
        for (const profile of profiles) {
            const config = await profileManager.getProfile(profile);
            const providers = config ? Object.keys(config.providers) : [];
            console.log(`  ${chalk_1.default.green(profile)} (${providers.join(', ')})`);
        }
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
program
    .command('delete-profile')
    .description('Delete a profile')
    .argument('<name>', 'Profile name to delete')
    .action(async (name) => {
    try {
        const profileManager = new profile_config_1.ProfileConfigManager();
        const deleted = await profileManager.deleteProfile(name);
        if (deleted) {
            console.log(chalk_1.default.green(`Profile '${name}' deleted successfully`));
        }
        else {
            console.log(chalk_1.default.yellow(`Profile '${name}' not found`));
        }
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
// Validation commands
program
    .command('test-profile')
    .description('Test all providers in a profile')
    .argument('<name>', 'Profile name to test')
    .action(async (name) => {
    try {
        const profileManager = new profile_config_1.ProfileConfigManager();
        const results = await profileManager.testProfile(name);
        console.log(chalk_1.default.cyan(`\nTesting profile: ${name}`));
        for (const result of results) {
            const status = result.valid ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
            const message = result.valid
                ? `${result.provider} (model: ${result.model})`
                : `${result.provider} - ${result.error}`;
            console.log(`  ${status} ${message}`);
        }
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
program
    .command('test-all-profiles')
    .description('Test all providers in all profiles')
    .action(async () => {
    try {
        const profileManager = new profile_config_1.ProfileConfigManager();
        const results = await profileManager.testAllProfiles();
        if (results.length === 0) {
            console.log(chalk_1.default.yellow('No profiles configured'));
            return;
        }
        console.log(chalk_1.default.cyan('\nTesting all profiles:'));
        // Group by profile
        const byProfile = {};
        for (const result of results) {
            if (!byProfile[result.profile]) {
                byProfile[result.profile] = [];
            }
            byProfile[result.profile].push(result);
        }
        for (const [profileName, profileResults] of Object.entries(byProfile)) {
            console.log(chalk_1.default.bold(`\n${profileName}:`));
            for (const result of profileResults) {
                const status = result.valid ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
                const message = result.valid
                    ? `${result.provider} (model: ${result.model})`
                    : `${result.provider} - ${result.error}`;
                console.log(`  ${status} ${message}`);
            }
        }
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
program
    .command('test-provider')
    .description('Test specific provider in a profile')
    .argument('<provider>', 'Provider to test')
    .option('--profile <name>', 'Profile to test (default: "default")')
    .action(async (provider, options) => {
    try {
        const profileManager = new profile_config_1.ProfileConfigManager();
        const result = await profileManager.testProvider(provider, options.profile || 'default');
        console.log(chalk_1.default.cyan(`\nTesting ${provider} in profile: ${options.profile || 'default'}`));
        const status = result.valid ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
        const message = result.valid
            ? `Valid (model: ${result.model})`
            : `Invalid - ${result.error}`;
        console.log(`  ${status} ${message}`);
    }
    catch (error) {
        console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
        process.exit(1);
    }
});
// Programmatic integration guide command
program
    .command('help-integration')
    .description('Print the programmatic integration guide for @majkapp/majk-chat-common')
    .action(() => {
    const guide = `
${chalk_1.default.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
${chalk_1.default.bold.white('  @majkapp/majk-chat-common — Programmatic Integration Guide')}
${chalk_1.default.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}

${chalk_1.default.bold('INSTALLATION')}

  npm install @majkapp/majk-chat-common

${chalk_1.default.bold('QUICK START')}

  ${chalk_1.default.cyan('import { MagicChat } from \'@majkapp/majk-chat-common\';')}

  ${chalk_1.default.cyan('const agent = await MagicChat.create({ provider: \'anthropic\' });')}
  ${chalk_1.default.cyan('const { text } = await agent.chat(\'List the TypeScript files in src/\');')}
  ${chalk_1.default.cyan('console.log(text);')}
  ${chalk_1.default.cyan('await agent.destroy();')}

${chalk_1.default.bold('CREDENTIAL RESOLUTION')} ${chalk_1.default.dim('(first truthy value wins)')}

  ${chalk_1.default.green('1.')} ${chalk_1.default.white('config.apiKey')}              ← HIGHEST PRIORITY (pass key explicitly)
  ${chalk_1.default.green('2.')} ${chalk_1.default.white('process.env')}                ← Shell / CI / Docker
  ${chalk_1.default.green('3.')} ${chalk_1.default.white('.env.local file')}            ← Lowest priority (gitignored)

  Provider environment variable names:
    anthropic    → ANTHROPIC_API_KEY
    openai       → OPENAI_API_KEY  (OPENAI_ORG_ID also respected)
    azure-openai → AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT
    bedrock      → AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION

${chalk_1.default.bold('TOOL PRESETS')} ${chalk_1.default.dim('(pass to tools: option)')}

  ${chalk_1.default.yellow("'read-only'")}    ls, read, glob, grep, web_fetch, document parsers  ${chalk_1.default.dim('[DEFAULT]')}
  ${chalk_1.default.yellow("'all'")}          Everything + bash, write, edit, process management
  ${chalk_1.default.yellow("'filesystem'")}   All filesystem (read + write), no web or documents
  ${chalk_1.default.yellow("'web'")}          web_fetch only
  ${chalk_1.default.yellow("'documents'")}    PDF, Excel, Word, CSV parsers only
  ${chalk_1.default.yellow("'planning'")}     Planning-mode tools (enter_planning_mode, todo_write)
  ${chalk_1.default.yellow("'none'")}         No tools — pure LLM conversation

${chalk_1.default.bold('CUSTOM TOOLS')}

  ${chalk_1.default.cyan('import { defineTool } from \'@majkapp/majk-chat-common\';')}

  ${chalk_1.default.cyan('const myTool = defineTool<{ id: string }>({\n    name: \'lookup_customer\',\n    description: \'Find a customer by ID\',\n    parameters: {\n      type: \'object\',\n      properties: { id: { type: \'string\' } },\n      required: [\'id\'],\n    },\n    execute: async ({ id }, context) => {\n      return await db.customers.findById(id);\n    },\n  });')}

  ${chalk_1.default.cyan('const agent = await MagicChat.create({\n    provider: \'anthropic\',\n    tools: [myTool],           // or mix with preset: [\'read-only\', myTool]\n  });')}

${chalk_1.default.bold('PERMISSION GATES')}

  ${chalk_1.default.dim('// undefined/false → always allow (default)')}
  ${chalk_1.default.dim('// true           → denied when no handler present (safe-side)')}
  ${chalk_1.default.dim('// function       → async predicate, true=allow, false=deny')}
  ${chalk_1.default.cyan('requiresPermission: async ({ path }) => path.startsWith(process.cwd())')}

${chalk_1.default.bold('SESSION PERSISTENCE')}

  ${chalk_1.default.dim('// In-memory (cleared on restart)')}
  ${chalk_1.default.cyan('sessions: \'memory\'')}

  ${chalk_1.default.dim('// Filesystem (persists across restarts)')}
  ${chalk_1.default.cyan('sessions: { type: \'filesystem\', path: \'./sessions\' },')}
  ${chalk_1.default.cyan('sessionId: \'user-42\',')}

  ${chalk_1.default.dim('// Custom store (Redis, DynamoDB, Postgres…)')}
  ${chalk_1.default.cyan('sessions: new RedisSessionStore(redisClient)')}

${chalk_1.default.bold('TOKEN TRACKING')}

  ${chalk_1.default.cyan('const agent = await MagicChat.create({\n    provider: \'anthropic\',\n    tokenTracker: async (event) => {\n      await analytics.track(\'llm_token_usage\', {\n        model: event.model,\n        total: event.totalTokens,\n        cost:  event.estimatedCostUsd,\n      });\n    },\n    pricing: {\n      perMillionInputTokens:  3.00,\n      perMillionOutputTokens: 15.00,\n    },\n  });')}

  ${chalk_1.default.dim('// Per-call usage returned by chat()')}
  ${chalk_1.default.cyan('const { tokenUsage } = await agent.chat(\'Hello\');\n  console.log(tokenUsage?.totalTokens);')}

  ${chalk_1.default.dim('// Aggregate across all calls')}
  ${chalk_1.default.cyan('const stats = agent.getTokenStats();\n  console.log(stats.totalTokens, stats.estimatedTotalCostUsd);')}

${chalk_1.default.bold('MCP INTEGRATION')}

  ${chalk_1.default.cyan('const agent = await MagicChat.create({\n    provider: \'anthropic\',\n    mcp: {\n      servers: {\n        github: {\n          command: \'npx\',\n          args: [\'-y\', \'@modelcontextprotocol/server-github\'],\n          env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_TOKEN },\n        },\n      },\n    },\n  });\n  await agent.destroy(); // Always call destroy() to close MCP connections')}

${chalk_1.default.bold('FULL API REFERENCE')}

  MagicChat.create(config)          → Promise<MagicChat>
  agent.chat(message)               → Promise<{ text, stepsTaken, toolsUsed, tokenUsage }>
  agent.getHistory()                → Message[]
  agent.clearHistory()              → void
  agent.saveSession(id?)            → Promise<string>
  agent.loadSession(id)             → Promise<void>
  agent.listSessions()              → Promise<SessionStoreSummary[]>
  agent.getTokenStats()             → TokenStats
  agent.resetTokenStats()           → void
  agent.destroy()                   → Promise<void>

  chat(message, config)             → Promise<string>   (one-shot helper)

${chalk_1.default.bold.dim('Full documentation: majk-chat-common/README.md')}
${chalk_1.default.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
`;
    console.log(guide);
    process.exit(0);
});
// Helper functions
async function handleClaudeCodeExecutor(options, outputHandler) {
    try {
        await executors_1.ExecutorFactory.validateExecutorChoice('claude-code');
        const messages = await buildMessages(options);
        if (!messages || messages.length === 0) {
            outputHandler.outputError('No message provided');
            process.exit(1);
        }
        const credOptions = extractCredentialOptions(options);
        // Load actual credentials from credential manager if not provided via CLI
        const credManager = new credentials_1.CredentialManager();
        const loadedCreds = await credManager.getCredentials(options.provider || 'anthropic', credOptions);
        const executorOptions = {
            provider: options.provider,
            model: options.model,
            messages,
            temperature: options.temperature,
            maxTokens: options.maxTokens,
            maxSteps: options.maxSteps,
            systemPrompt: options.system || options.appendSystemPrompt,
            outputFormat: options.outputFormat || 'text',
            tools: options.tools,
            enableCoreTools: options.enableCoreTools || options.tools,
            enableReadOnlyTools: options.enableReadOnlyTools,
            disableTools: options.disableTools,
            planningMode: options.planningMode,
            mcpConfig: options.mcpConfig,
            mcpServers: options.mcpServers,
            allowedTools: options.allowedTools,
            disallowedTools: options.disallowedTools,
            permissionPromptTool: options.permissionPromptTool,
            bedrockApiKey: credOptions.bedrockApiKey || loadedCreds?.bearerToken,
            awsAccessKeyId: credOptions.awsAccessKeyId || loadedCreds?.accessKeyId,
            awsSecretAccessKey: credOptions.awsSecretAccessKey || loadedCreds?.secretAccessKey,
            awsSessionToken: credOptions.awsSessionToken || loadedCreds?.sessionToken,
            awsRegion: credOptions.awsRegion || loadedCreds?.region,
            anthropicApiKey: credOptions.anthropicApiKey || loadedCreds?.apiKey,
            openaiApiKey: credOptions.openaiApiKey || loadedCreds?.apiKey,
            azureOpenaiApiKey: credOptions.azureOpenaiApiKey || loadedCreds?.apiKey,
            azureOpenaiEndpoint: credOptions.azureOpenaiEndpoint || loadedCreds?.endpoint,
            azureOpenaiDeployment: credOptions.azureOpenaiDeployment || loadedCreds?.deploymentName,
            cwd: process.cwd(),
            onToolCall: options.outputFormat === 'stream-json'
                ? (toolCall) => outputHandler.outputToolCallMessage(toolCall)
                : undefined,
            onToolResult: options.outputFormat === 'stream-json'
                ? (toolCall, result) => outputHandler.outputToolResult(toolCall.id, result)
                : undefined
        };
        const executor = executors_1.ExecutorFactory.create('claude-code');
        const spinner = outputHandler.startSpinner('Sending message...');
        const apiStart = Date.now();
        const result = await executor.execute(executorOptions);
        const apiDuration = Date.now() - apiStart;
        if (spinner)
            spinner.succeed('Response received');
        if (options.json) {
            console.log(JSON.stringify(result.response, null, 2));
        }
        else {
            const assistantMessage = result.response.choices[0].message;
            const content = typeof assistantMessage.content === 'string'
                ? assistantMessage.content
                : JSON.stringify(assistantMessage.content);
            outputHandler.outputMessage('assistant', content, result.response.usage, assistantMessage.tool_calls);
            outputHandler.outputComplete(result.response.usage, apiDuration);
        }
        process.exit(0);
    }
    catch (error) {
        outputHandler.outputError(error instanceof Error ? error.message : String(error));
        process.exit(1);
    }
}
// Extract credential options from CLI arguments
function extractCredentialOptions(options) {
    return {
        // Generic
        apiKey: options.apiKey,
        apiKeyFile: options.apiKeyFile,
        envFile: options.envFile,
        // OpenAI
        openaiApiKey: options.openaiApiKey,
        openaiOrgId: options.openaiOrgId,
        // Anthropic
        anthropicApiKey: options.anthropicApiKey,
        // Azure OpenAI
        azureOpenaiApiKey: options.azureOpenaiApiKey,
        azureOpenaiEndpoint: options.azureOpenaiEndpoint,
        azureOpenaiDeployment: options.azureOpenaiDeployment,
        // AWS Bedrock
        awsAccessKeyId: options.awsAccessKeyId,
        awsSecretAccessKey: options.awsSecretAccessKey,
        awsSessionToken: options.awsSessionToken,
        awsRegion: options.awsRegion,
        bedrockApiKey: options.bedrockApiKey,
        // Profile system
        profile: options.profile
    };
}
async function setupChat(options) {
    const builder = new majk_chat_core_1.MajkChatBuilder();
    const credManager = new credentials_1.CredentialManager();
    const credOptions = extractCredentialOptions(options);
    // Initialize working directory for tools to the CLI execution directory
    majk_chat_basic_tools_1.WorkingDirectoryManager.initializeFromCLI();
    // Load configuration
    if (options.config) {
        const config = await config_1.ConfigLoader.load(options.config);
        await applyConfig(builder, config);
    }
    // Setup providers with credentials
    const providers = await credManager.loadProviders(credOptions);
    // Check if we have any providers or if a specific provider was requested
    let hasValidCredentials = false;
    const missingCredentials = [];
    // Helper function to setup provider with credential validation
    async function setupProvider(providerName, shouldSetup, setupFn, credValidator) {
        if (shouldSetup) {
            const creds = await credManager.getCredentials(providerName, credOptions);
            if (credValidator(creds)) {
                setupFn(creds);
                hasValidCredentials = true;
            }
            else {
                // Always register provider even without credentials to avoid "provider not found" error
                try {
                    setupFn(undefined);
                }
                catch (e) {
                    // Some providers might require credentials to initialize, that's ok
                }
                if (options.provider === providerName || options.provider === providerName.replace('-', '')) {
                    missingCredentials.push(providerName);
                }
            }
        }
    }
    await setupProvider('openai', providers.openai || options.provider === 'openai', (creds) => builder.withOpenAI(creds), (creds) => !!creds?.apiKey);
    await setupProvider('anthropic', providers.anthropic || options.provider === 'anthropic', (creds) => builder.withAnthropic(creds), (creds) => !!creds?.apiKey);
    await setupProvider('azure-openai', providers.azureOpenai || options.provider === 'azure-openai', (creds) => builder.withAzureOpenAI(creds), (creds) => !!creds?.apiKey);
    await setupProvider('bedrock', providers.bedrock || options.provider === 'bedrock' || options.provider === 'anthropic-bedrock', (creds) => builder.withBedrock(creds), (creds) => !!(creds?.accessKeyId || creds?.bearerToken));
    // Throw specific error for missing credentials of requested provider
    if (missingCredentials.length > 0) {
        const providerName = missingCredentials[0];
        let credHelp = '';
        switch (providerName) {
            case 'openai':
                credHelp = 'Set OPENAI_API_KEY environment variable or use --openai <api-key>';
                break;
            case 'anthropic':
                credHelp = 'Set ANTHROPIC_API_KEY environment variable or use --anthropic <api-key>';
                break;
            case 'azure-openai':
                credHelp = 'Set AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT environment variables or use --azure-openai options';
                break;
            case 'bedrock':
                credHelp = 'Set AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or use --bedrock <access-key> <secret-key> [region] or --bedrock-token <bearer-token>';
                break;
        }
        throw new Error(`No valid credentials found for ${providerName} provider. ${credHelp}`);
    }
    // Throw error if no valid credentials found at all
    if (!hasValidCredentials) {
        throw new Error('No valid API credentials found. Please provide API keys via command line options, environment variables, or .env files. Use --help to see available credential options.');
    }
    // Add tools - now enabled by default unless explicitly disabled
    const shouldEnableTools = !options.disableTools; // Default to true unless --disable-tools is used
    const enableReadOnlyTools = shouldEnableTools && (options.enableReadOnlyTools !== false); // Default to true
    const enableCoreTools = options.enableCoreTools || options.tools; // --tools now enables core tools
    const enablePlanningMode = options.planningMode;
    if (enableCoreTools || enableReadOnlyTools || enablePlanningMode) {
        const toolRegistry = builder.getToolRegistry();
        if (toolRegistry) {
            // Configure basic tools 
            const basicToolsConfig = {};
            // Register appropriate tool set based on flags
            if (enableCoreTools || enablePlanningMode) {
                // Register all tools (including write/execute tools)
                (0, majk_chat_basic_tools_1.registerBasicTools)(toolRegistry, basicToolsConfig);
                // Also register document tools for full functionality
                (0, majk_chat_document_tools_1.registerDocumentTools)(toolRegistry);
            }
            else if (enableReadOnlyTools) {
                // Register only read-only tools
                (0, majk_chat_basic_tools_1.registerReadOnlyBasicTools)(toolRegistry, basicToolsConfig);
                // Register document tools (they are inherently read-only)
                (0, majk_chat_document_tools_1.registerDocumentTools)(toolRegistry);
            }
            // Apply tool filtering if specified
            if (options.allowedTools || options.disallowedTools) {
                applyToolFiltering(toolRegistry, options.allowedTools, options.disallowedTools);
            }
            // Setup context management with truncation using ContextManagementExtension
            // Handle the case where toolResultLimit might be null or NaN from commander parsing
            let toolResultLimit = 2000; // Much smaller default to handle large files within context limits
            if (options.toolResultLimit && !isNaN(options.toolResultLimit) && options.toolResultLimit > 0) {
                toolResultLimit = options.toolResultLimit;
            }
            // Always setup context management with the extension (replaces legacy setupToolResultManagement)
            builder.withContextManagement({
                enableTruncation: true,
                maxLength: toolResultLimit,
                conversationId: options.requestId || 'cli-session'
            });
            // Setup prompt logging if requested
            if (options.savePrompts) {
                builder.withExtension(new majk_chat_core_1.PromptLoggingExtension({
                    filePath: options.savePrompts,
                    includeContext: true,
                    includeTools: true,
                    includeMessages: true,
                    append: true
                }));
                console.log(chalk_1.default.cyan(`📝 Prompt logging enabled: ${options.savePrompts}`));
            }
            // Setup planning mode extension if requested
            if (enablePlanningMode) {
                builder.withPlanningMode({
                    enabled: true,
                    autoActivation: {
                        enabled: true,
                        complexityThreshold: 70
                    },
                    guidance: {
                        reminderInterval: options.todoReminderInterval || 3,
                        enableProgressTracking: !options.disableAutoReminders
                    },
                    session: {
                        conversationId: options.requestId || 'cli-session'
                    }
                });
                console.log(chalk_1.default.cyan(`🎯 Planning mode enabled: Research → Plan → Execute workflow`));
            }
            // Set default steps based on tool type - 500 for read-only tools by default, 5 for other scenarios
            const defaultSteps = enableReadOnlyTools && !enableCoreTools && !enablePlanningMode ? 500 : 5;
            builder.withAutoToolExecution(options.maxSteps || defaultSteps);
        }
    }
    let mcpPlugin = null;
    // Setup MCP if configured - this will add tools to the registry
    if (options.mcpConfig || options.mcpServers) {
        mcpPlugin = await setupMCP(builder, options);
        // Enable auto tool execution if MCP is configured
        if (!options.tools) {
            builder.withAutoToolExecution(options.maxSteps || 5);
        }
    }
    // Set tool callbacks if provided
    if (options.onToolCall || options.onToolResult) {
        builder.withToolCallbacks(options.onToolCall, options.onToolResult);
    }
    const toolRegistry = builder.getToolRegistry();
    // Setup dry run if enabled
    let dryRunManager = null;
    let dryRunSessionId = null;
    if (options.dryRun || options.dryRunReview) {
        dryRunManager = new majk_chat_basic_tools_1.DryRunManager(toolRegistry, {
            interactive: options.dryRunReview,
            showPreview: true,
            autoApprove: 'none' // Could be made configurable
        });
        dryRunSessionId = dryRunManager.startDryRun();
        if (options.outputFormat !== 'stream-json') {
            console.log(chalk_1.default.cyan(`🔍 Dry run mode enabled${options.dryRunReview ? ' with interactive review' : ''}`));
        }
    }
    return {
        chat: builder.build(),
        toolRegistry,
        dryRunManager,
        dryRunSessionId,
        cleanup: async () => {
            if (mcpPlugin) {
                try {
                    await mcpPlugin.shutdown();
                }
                catch (error) {
                    if (options.outputFormat !== 'stream-json') {
                        console.error('Error during MCP cleanup:', error);
                    }
                }
            }
            // Remove any stdin listeners that might be keeping process alive
            process.stdin.removeAllListeners();
            // Close stdin to prevent hanging
            if (process.stdin.readable) {
                process.stdin.destroy();
            }
        }
    };
}
async function loadImageFromPath(filePath) {
    const resolvedPath = path.resolve(filePath.replace(/^~/, process.env.HOME || '~'));
    const ext = path.extname(resolvedPath).toLowerCase();
    const mediaTypeMap = {
        '.png': 'image/png',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.gif': 'image/gif',
        '.webp': 'image/webp'
    };
    const media_type = mediaTypeMap[ext];
    if (!media_type) {
        throw new Error(`Unsupported image format: ${ext}. Supported: .png, .jpg, .jpeg, .gif, .webp`);
    }
    const buffer = await fs.readFile(resolvedPath);
    const base64Data = buffer.toString('base64');
    return {
        type: 'image',
        source: {
            type: 'base64',
            media_type,
            data: base64Data
        }
    };
}
async function buildMessages(options) {
    const messages = [];
    // Add system message if provided
    const systemMessage = await getSystemMessage(options);
    if (systemMessage) {
        messages.push({ role: 'system', content: systemMessage });
    }
    // Handle -p option (JSON array or prompt string)
    if (options.p) {
        const pValue = options.p.trim();
        if (pValue.startsWith('[')) {
            // Parse as JSON messages array
            try {
                const parsedMessages = JSON.parse(pValue);
                if (Array.isArray(parsedMessages)) {
                    messages.push(...parsedMessages);
                }
            }
            catch (e) {
                throw new Error(`Invalid JSON in -p option: ${e}`);
            }
        }
        else {
            // Treat as a simple prompt string
            messages.push({ role: 'user', content: pValue });
        }
    }
    else {
        // Use traditional message options
        const message = await getMessage(options);
        if (message) {
            // Check if images are provided
            if (options.image && Array.isArray(options.image) && options.image.length > 0) {
                // Build multi-modal content with text + images
                const contentBlocks = [];
                // Add text block first
                contentBlocks.push({
                    type: 'text',
                    text: message
                });
                // Add image blocks
                for (const imagePath of options.image) {
                    try {
                        const imageBlock = await loadImageFromPath(imagePath);
                        contentBlocks.push(imageBlock);
                    }
                    catch (error) {
                        throw new Error(`Failed to load image ${imagePath}: ${error instanceof Error ? error.message : error}`);
                    }
                }
                messages.push({ role: 'user', content: contentBlocks });
            }
            else {
                // Simple text message
                messages.push({ role: 'user', content: message });
            }
        }
    }
    return messages;
}
async function handleSequentialPrompts(chat, toolRegistry, options, outputHandler) {
    const promptsFile = await fs.readFile(options.prompts, 'utf-8');
    const prompts = promptsFile.split('\n').filter(line => line.trim().length > 0);
    const context = { requestId: `cli-batch-${Date.now()}` };
    const providerHint = (options.provider === 'bedrock' ? 'anthropic-bedrock' : options.provider);
    // Resolve model and capture the actual provider used
    let model;
    let actualProvider;
    if (options.model) {
        const resolved = await (0, models_1.resolveModelWithMetadata)(chat, options.model, providerHint, context);
        model = resolved.modelId;
        actualProvider = resolved.provider;
    }
    else {
        // When no model specified, need a provider to get default
        actualProvider = providerHint || 'openai';
        model = await (0, models_1.getDefaultModel)(chat, actualProvider, undefined, context);
    }
    const session = chat.createSession(actualProvider);
    const conversationMessages = [];
    // Add system message if provided
    const systemMessage = await getSystemMessage(options);
    if (systemMessage) {
        conversationMessages.push({ role: 'system', content: systemMessage });
    }
    // Get tool definitions from registry
    const tools = toolRegistry.getDefinitions();
    for (const prompt of prompts) {
        outputHandler.outputMessage('user', prompt);
        conversationMessages.push({ role: 'user', content: prompt });
        const request = {
            model,
            messages: conversationMessages,
            temperature: options.temperature,
            max_tokens: options.maxTokens,
            // Always include tools if any are registered
            tools: tools.length > 0 ? tools : undefined,
            // Enable auto-execution if tools are enabled (default unless --disable-tools) or MCP configured
            max_steps: (options.tools || !options.disableTools || options.mcpConfig || options.mcpServers)
                ? (options.maxSteps || 5)
                : 0
        };
        const spinner = outputHandler.startSpinner('Processing...');
        const response = await session.send(prompt, request);
        if (spinner)
            spinner.stop();
        const assistantMessage = response.choices[0].message;
        conversationMessages.push(assistantMessage);
        outputHandler.outputMessage('assistant', assistantMessage.content);
        // Show tool calls if any
        if (assistantMessage.tool_calls) {
            for (const toolCall of assistantMessage.tool_calls) {
                outputHandler.outputToolCall(toolCall.function.name, JSON.parse(toolCall.function.arguments));
            }
        }
    }
    outputHandler.outputComplete();
}
async function setupMCP(builder, options) {
    const mcpOptions = {};
    if (options.mcpConfig) {
        // Check if it's a JSON string or a file path
        const configValue = options.mcpConfig.trim();
        if (configValue.startsWith('{')) {
            // It's a JSON string
            mcpOptions.configString = configValue;
        }
        else {
            // It's a file path
            mcpOptions.configPath = configValue;
        }
    }
    // Support legacy --mcp-servers option
    if (options.mcpServers) {
        mcpOptions.configString = options.mcpServers;
    }
    // Parse tool filtering patterns
    if (options.allowedTools || options.disallowedTools) {
        // Note: We could use createToolFilter here for more advanced filtering
        // but for now we pass patterns directly to MCP plugin
        if (options.allowedTools) {
            mcpOptions.allowedTools = options.allowedTools
                .split(/[,\s]+/)
                .map((t) => t.trim())
                .filter((t) => t.length > 0);
        }
        if (options.disallowedTools) {
            mcpOptions.disallowedTools = options.disallowedTools
                .split(/[,\s]+/)
                .map((t) => t.trim())
                .filter((t) => t.length > 0);
        }
    }
    if (options.permissionPromptTool) {
        mcpOptions.permissionPromptTool = options.permissionPromptTool;
    }
    // Enable quiet mode for stream-json to suppress console output
    mcpOptions.quiet = options.outputFormat === 'stream-json';
    const mcpPlugin = new majk_chat_mcp_1.MCPPlugin(mcpOptions);
    const registry = builder.getToolRegistry();
    if (registry) {
        try {
            await mcpPlugin.initialize(registry);
            if (options.outputFormat !== 'stream-json') {
                console.log(chalk_1.default.cyan('MCP servers connected and tools registered'));
            }
        }
        catch (error) {
            if (options.outputFormat !== 'stream-json') {
                console.error(chalk_1.default.red('Failed to initialize MCP:'), error instanceof Error ? error.message : error);
            }
            // Continue without MCP rather than failing completely
        }
    }
    return mcpPlugin;
}
async function getMessage(options) {
    if (options.message) {
        return options.message;
    }
    if (options.file) {
        const content = await fs.readFile(options.file, 'utf-8');
        return content.trim();
    }
    // Only try to read from stdin if it's a TTY (interactive terminal)
    // In tests and CI environments, just return null instead of hanging
    if (!process.stdin.isTTY) {
        return null;
    }
    return new Promise((resolve) => {
        let data = '';
        process.stdin.on('data', chunk => data += chunk);
        process.stdin.on('end', () => resolve(data.trim()));
    });
}
async function getSystemMessage(options) {
    let systemMessage = '';
    // Get base system message
    if (options.system) {
        systemMessage = options.system;
    }
    else if (options.systemFile) {
        const content = await fs.readFile(options.systemFile, 'utf-8');
        systemMessage = content.trim();
    }
    // Append additional system prompt if provided
    if (options.appendSystemPrompt) {
        if (systemMessage) {
            systemMessage += '\n\n' + options.appendSystemPrompt;
        }
        else {
            systemMessage = options.appendSystemPrompt;
        }
    }
    return systemMessage || null;
}
async function applyConfig(builder, config) {
    if (config.providers) {
        for (const [provider, providerConfig] of Object.entries(config.providers)) {
            switch (provider) {
                case 'openai':
                    builder.withOpenAI(providerConfig);
                    break;
                case 'anthropic':
                    builder.withAnthropic(providerConfig);
                    break;
                case 'azure-openai':
                    builder.withAzureOpenAI(providerConfig);
                    break;
                case 'bedrock':
                    builder.withBedrock(providerConfig);
                    break;
            }
        }
    }
    if (config.defaultProvider) {
        builder.setDefaultProvider(config.defaultProvider);
    }
}
async function initializeConfig(configPath) {
    const defaultConfig = {
        providers: {
            openai: {
                apiKey: '${OPENAI_API_KEY}'
            },
            anthropic: {
                apiKey: '${ANTHROPIC_API_KEY}'
            },
            'azure-openai': {
                apiKey: '${AZURE_OPENAI_API_KEY}',
                endpoint: '${AZURE_OPENAI_ENDPOINT}',
                deploymentName: '${AZURE_OPENAI_DEPLOYMENT}'
            },
            bedrock: {
                region: '${AWS_REGION}',
                accessKeyId: '${AWS_ACCESS_KEY_ID}',
                secretAccessKey: '${AWS_SECRET_ACCESS_KEY}'
            }
        },
        defaultProvider: 'openai',
        tools: {
            autoExecute: false,
            maxSteps: 5
        }
    };
    await fs.ensureDir(path.dirname(configPath));
    await fs.writeFile(configPath, yaml.stringify(defaultConfig), 'utf-8');
}
async function setConfigValue(configPath, keyValue) {
    const [key, ...valueParts] = keyValue.split('=');
    const value = valueParts.join('=');
    let config = {};
    if (await fs.pathExists(configPath)) {
        const content = await fs.readFile(configPath, 'utf-8');
        config = yaml.parse(content);
    }
    // Set nested value
    const keys = key.split('.');
    let current = config;
    for (let i = 0; i < keys.length - 1; i++) {
        if (!current[keys[i]]) {
            current[keys[i]] = {};
        }
        current = current[keys[i]];
    }
    current[keys[keys.length - 1]] = value;
    await fs.ensureDir(path.dirname(configPath));
    await fs.writeFile(configPath, yaml.stringify(config), 'utf-8');
    console.log(chalk_1.default.green(`Set ${key} = ${value}`));
}
async function getConfigValue(configPath, key) {
    if (!await fs.pathExists(configPath)) {
        console.log(chalk_1.default.yellow('Configuration file not found'));
        return;
    }
    const content = await fs.readFile(configPath, 'utf-8');
    const config = yaml.parse(content);
    // Get nested value
    const keys = key.split('.');
    let current = config;
    for (const k of keys) {
        if (current && typeof current === 'object' && k in current) {
            current = current[k];
        }
        else {
            console.log(chalk_1.default.yellow(`Key not found: ${key}`));
            return;
        }
    }
    console.log(current);
}
async function listConfig(configPath) {
    if (!await fs.pathExists(configPath)) {
        console.log(chalk_1.default.yellow('Configuration file not found'));
        return;
    }
    const content = await fs.readFile(configPath, 'utf-8');
    console.log(content);
}
/**
 * Apply tool filtering to remove disallowed tools from the registry
 */
function applyToolFiltering(registry, allowedTools, disallowedTools) {
    if (!allowedTools && !disallowedTools)
        return;
    const toolFilter = (0, tool_filter_1.createToolFilter)(allowedTools, disallowedTools);
    const definitions = registry.getDefinitions();
    // Remove filtered tools
    for (const definition of definitions) {
        const toolName = definition.function?.name;
        if (toolName && !toolFilter(toolName)) {
            // Note: This assumes the registry has a remove method
            // If not available, we would need to implement it or use a different approach
            if (registry.remove) {
                registry.remove(toolName);
            }
        }
    }
}
// Session helper functions
async function handleListSessions(sessionManager, workingDirectory, outputHandler) {
    try {
        const sessions = await sessionManager.listSessions(workingDirectory);
        if (sessions.length === 0) {
            console.log(chalk_1.default.yellow('No sessions found in this directory.'));
            return;
        }
        console.log(chalk_1.default.cyan('\nSessions in current directory:'));
        for (const session of sessions) {
            console.log(`  ${chalk_1.default.green(session.id)}`);
            if (session.title) {
                console.log(`    Title: ${session.title}`);
            }
            console.log(`    Created: ${session.createdAt.toLocaleDateString()} ${session.createdAt.toLocaleTimeString()}`);
            console.log(`    Updated: ${session.updatedAt.toLocaleDateString()} ${session.updatedAt.toLocaleTimeString()}`);
            console.log(`    Messages: ${session.totalMessages}`);
            if (session.lastMessage) {
                console.log(`    Last: ${session.lastMessage}`);
            }
            console.log();
        }
    }
    catch (error) {
        outputHandler.outputError(`Failed to list sessions: ${error instanceof Error ? error.message : error}`);
    }
}
async function handleDeleteSession(sessionManager, workingDirectory, sessionId, outputHandler) {
    try {
        const deleted = await sessionManager.deleteSession(sessionId, workingDirectory);
        if (deleted) {
            console.log(chalk_1.default.green(`Session ${sessionId} deleted successfully.`));
        }
        else {
            outputHandler.outputError(`Session ${sessionId} not found.`);
        }
    }
    catch (error) {
        outputHandler.outputError(`Failed to delete session: ${error instanceof Error ? error.message : error}`);
    }
}
async function saveConversationToSession(sessionManager, workingDirectory, sessionId, allMessages, response, outputHandler, options) {
    try {
        const assistantMessage = response.choices[0].message;
        // Use complete message history if available (contains all tool calls and results)
        let finalMessages;
        if (response.complete_messages && Array.isArray(response.complete_messages)) {
            // Use the complete conversation history from orchestrator
            finalMessages = response.complete_messages;
        }
        else {
            // Fallback to basic conversation
            finalMessages = [...allMessages, assistantMessage];
        }
        if (sessionId && sessionId !== outputHandler.getSessionId()) {
            // Update existing session with complete message history
            const newMessages = finalMessages.slice(allMessages.length); // Only add the new messages
            for (const message of newMessages) {
                await sessionManager.addMessage(message);
            }
        }
        else {
            // Create new session using the OutputHandler's session ID for consistency
            sessionId = await sessionManager.createSession(workingDirectory, {
                id: outputHandler.getSessionId(), // Use OutputHandler's session ID
                title: options.title,
                provider: options.provider,
                model: options.model,
                initialMessages: finalMessages
            });
            // Auto-generate title if not provided
            if (!options.title) {
                await sessionManager.generateSessionTitle();
            }
        }
        return sessionId;
    }
    catch (error) {
        console.error(chalk_1.default.yellow(`Warning: Failed to save session: ${error instanceof Error ? error.message : error}`));
        return sessionId || 'unknown';
    }
}
async function handleSequentialPromptsWithSessions(chat, toolRegistry, options, outputHandler, _sessionManager, _sessionId) {
    // This is a modified version of handleSequentialPrompts that works with sessions
    // For now, we'll delegate to the original function and add session support later
    await handleSequentialPrompts(chat, toolRegistry, options, outputHandler);
}
// Parse and execute
program.parse(process.argv);
//# sourceMappingURL=cli.js.map