/**
 * CLI Command - Unified CLI tool executor command
 * Provides interface for executing Gemini, Qwen, and Codex
 */
import chalk from 'chalk';
import http from 'http';
import inquirer from 'inquirer';
import { SmartContentFormatter } from '../tools/cli-output-converter.js';
import { cliExecutorTool, getCliToolsStatus, getExecutionHistoryAsync, getConversationDetail, killCurrentCliProcess } from '../tools/cli-executor.js';
import { getStorageStats, getStorageConfig, cleanProjectStorage, cleanAllStorage, formatBytes, formatTimeAgo, resolveProjectId, projectExists, getStorageLocationInstructions } from '../tools/storage-manager.js';
import { getHistoryStore, findProjectWithExecution } from '../tools/cli-history-store.js';
import { createSpinner } from '../utils/ui.js';
import { loadClaudeCliSettings } from '../tools/claude-cli-tools.js';
// Dashboard notification settings
const DASHBOARD_PORT = process.env.CCW_PORT || 3456;
/**
 * Notify dashboard of CLI execution events (fire and forget)
 */
function notifyDashboard(data) {
    const payload = JSON.stringify({
        type: 'cli_execution',
        ...data,
        timestamp: new Date().toISOString()
    });
    const req = http.request({
        hostname: 'localhost',
        port: Number(DASHBOARD_PORT),
        path: '/api/hook',
        method: 'POST',
        timeout: 2000, // 2 second timeout to prevent hanging
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(payload)
        }
    });
    // Fire and forget - don't block process exit
    req.on('socket', (socket) => {
        socket.unref(); // Allow process to exit even if socket is open
    });
    req.on('error', (err) => {
        if (process.env.DEBUG)
            console.error('[Dashboard] CLI notification failed:', err.message);
    });
    req.on('timeout', () => {
        req.destroy();
        if (process.env.DEBUG)
            console.error('[Dashboard] CLI notification timed out');
    });
    req.write(payload);
    req.end();
}
/**
 * Broadcast WebSocket event to Dashboard for real-time streaming
 * Uses specific event types that match frontend handlers
 */
function broadcastStreamEvent(eventType, payload) {
    if (process.env.DEBUG_CLI_EVENTS) {
        console.error(`[CLI-BROADCAST] START ${eventType} at ${Date.now()}`);
    }
    const data = JSON.stringify({
        type: eventType,
        ...payload,
        timestamp: new Date().toISOString()
    });
    const req = http.request({
        hostname: 'localhost',
        port: Number(DASHBOARD_PORT),
        path: '/api/hook',
        method: 'POST',
        timeout: 1000, // Short timeout for streaming
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(data)
        }
    });
    // Fire and forget - don't block streaming
    req.on('socket', (socket) => {
        socket.unref();
    });
    req.on('error', () => {
        // Silently ignore - dashboard may not be running
    });
    req.on('timeout', () => {
        req.destroy();
    });
    req.write(data);
    req.end();
    if (process.env.DEBUG_CLI_EVENTS) {
        console.error(`[CLI-BROADCAST] END ${eventType} at ${Date.now()}`);
    }
}
/**
 * Show storage information and management options
 */
async function storageAction(subAction, options) {
    switch (subAction) {
        case 'info':
        case undefined:
            await showStorageInfo();
            break;
        case 'clean':
            await cleanStorage(options);
            break;
        case 'config':
            showStorageConfig();
            break;
        default:
            showStorageHelp();
    }
}
/**
 * Show storage information
 */
async function showStorageInfo() {
    console.log(chalk.bold.cyan('\n  CCW Storage Information\n'));
    const config = getStorageConfig();
    const stats = getStorageStats();
    // Configuration
    console.log(chalk.bold.white('  Location:'));
    console.log(`    ${chalk.cyan(stats.rootPath)}`);
    if (config.isCustom) {
        console.log(chalk.gray(`    (Custom: CCW_DATA_DIR=${config.envVar})`));
    }
    console.log();
    // Summary
    console.log(chalk.bold.white('  Summary:'));
    console.log(`    Total Size:     ${chalk.yellow(formatBytes(stats.totalSize))}`);
    console.log(`    Projects:       ${chalk.yellow(stats.projectCount.toString())}`);
    console.log(`    Global DB:      ${stats.globalDb.exists ? chalk.green(formatBytes(stats.globalDb.size)) : chalk.gray('Not created')}`);
    console.log();
    // Projects breakdown
    if (stats.projects.length > 0) {
        console.log(chalk.bold.white('  Projects:'));
        console.log(chalk.gray('    ID               Size       History    Last Used'));
        console.log(chalk.gray('    ─────────────────────────────────────────────────────'));
        for (const project of stats.projects) {
            const historyInfo = project.cliHistory.recordCount !== undefined
                ? `${project.cliHistory.recordCount} records`
                : (project.cliHistory.exists ? 'Yes' : '-');
            console.log(`    ${chalk.dim(project.projectId)}  ` +
                `${formatBytes(project.totalSize).padStart(8)}   ` +
                `${historyInfo.padStart(10)}   ` +
                `${chalk.gray(formatTimeAgo(project.lastModified))}`);
        }
        console.log();
    }
    // Usage tips
    console.log(chalk.gray('  Commands:'));
    console.log(chalk.gray('    ccw cli storage clean              Clean all storage'));
    console.log(chalk.gray('    ccw cli storage clean --project <path>  Clean specific project'));
    console.log(chalk.gray('    ccw cli storage config             Show location config'));
    console.log();
}
/**
 * Clean storage
 */
async function cleanStorage(options) {
    const { all, project, force, cliHistory, memory, storageCache, config } = options;
    // Determine what to clean
    const cleanTypes = {
        cliHistory: cliHistory || (!cliHistory && !memory && !storageCache && !config),
        memory: memory || (!cliHistory && !memory && !storageCache && !config),
        cache: storageCache || (!cliHistory && !memory && !storageCache && !config),
        config: config || false, // Config requires explicit flag
        all: !cliHistory && !memory && !storageCache && !config
    };
    if (project) {
        // Clean specific project
        const projectId = resolveProjectId(project);
        if (!projectExists(projectId)) {
            console.log(chalk.yellow(`\n  No storage found for project: ${project}`));
            console.log(chalk.gray(`  (Project ID: ${projectId})\n`));
            return;
        }
        if (!force) {
            console.log(chalk.bold.yellow('\n  Warning: This will delete storage for project:'));
            console.log(`    Path: ${project}`);
            console.log(`    ID:   ${projectId}`);
            console.log(chalk.gray('\n  Use --force to confirm deletion.\n'));
            return;
        }
        console.log(chalk.bold.cyan('\n  Cleaning project storage...\n'));
        const result = cleanProjectStorage(projectId, cleanTypes);
        if (result.success) {
            console.log(chalk.green(`  ✓ Cleaned ${formatBytes(result.freedBytes)}`));
        }
        else {
            console.log(chalk.red('  ✗ Cleanup completed with errors:'));
            for (const err of result.errors) {
                console.log(chalk.red(`    - ${err}`));
            }
        }
    }
    else {
        // Clean all storage
        const stats = getStorageStats();
        if (stats.projectCount === 0) {
            console.log(chalk.yellow('\n  No storage to clean.\n'));
            return;
        }
        if (!force) {
            const { proceed } = await inquirer.prompt([{
                    type: 'confirm',
                    name: 'proceed',
                    message: `Delete ALL CCW storage? This will remove ${stats.projectCount} projects (${formatBytes(stats.totalSize)}). This action cannot be undone.`,
                    default: false
                }]);
            if (!proceed) {
                console.log(chalk.yellow('\n  Storage clean cancelled.\n'));
                return;
            }
        }
        console.log(chalk.bold.cyan('\n  Cleaning all storage...\n'));
        const result = cleanAllStorage(cleanTypes);
        if (result.success) {
            console.log(chalk.green(`  ✓ Cleaned ${result.projectsCleaned} projects, freed ${formatBytes(result.freedBytes)}`));
        }
        else {
            console.log(chalk.yellow(`  ⚠ Cleaned ${result.projectsCleaned} projects with some errors:`));
            for (const err of result.errors) {
                console.log(chalk.red(`    - ${err}`));
            }
        }
    }
    console.log();
}
/**
 * Show storage configuration
 */
function showStorageConfig() {
    console.log(getStorageLocationInstructions());
}
/**
 * Show storage help
 */
function showStorageHelp() {
    console.log(chalk.bold.cyan('\n  CCW Storage Management\n'));
    console.log('  Subcommands:');
    console.log(chalk.gray('    info                Show storage information (default)'));
    console.log(chalk.gray('    clean               Clean storage'));
    console.log(chalk.gray('    config              Show configuration instructions'));
    console.log();
    console.log('  Clean Options:');
    console.log(chalk.gray('    --project <path>    Clean specific project storage'));
    console.log(chalk.gray('    --force             Confirm deletion'));
    console.log(chalk.gray('    --cli-history       Clean only CLI history'));
    console.log(chalk.gray('    --memory            Clean only memory store'));
    console.log(chalk.gray('    --cache             Clean only cache'));
    console.log(chalk.gray('    --config            Clean config (requires explicit flag)'));
    console.log();
    console.log('  Examples:');
    console.log(chalk.gray('    ccw cli storage                           # Show storage info'));
    console.log(chalk.gray('    ccw cli storage clean --force             # Clean all storage'));
    console.log(chalk.gray('    ccw cli storage clean --project . --force # Clean current project'));
    console.log(chalk.gray('    ccw cli storage config                    # Show config instructions'));
    console.log();
}
/**
 * Show cached output for a conversation with pagination
 * Supports automatic discovery of project path from current directory or parents
 */
async function outputAction(conversationId, options) {
    if (!conversationId) {
        console.error(chalk.red('Error: Conversation ID is required'));
        console.error(chalk.gray('Usage: ccw cli output <conversation-id> [--offset N] [--limit N] [--project <path>]'));
        process.exit(1);
    }
    // Determine project path to use
    let projectPath = options.project || process.cwd();
    let store = getHistoryStore(projectPath);
    // Try to get result from specified/current directory
    let result = store.getCachedOutput(conversationId, options.turn ? parseInt(options.turn) : undefined, {
        offset: parseInt(options.offset || '0'),
        limit: parseInt(options.limit || '10000'),
        outputType: options.outputType || 'both'
    });
    // If not found and no explicit project specified, try to find it
    if (!result && !options.project) {
        const found = findProjectWithExecution(conversationId, process.cwd());
        if (found) {
            projectPath = found.projectPath;
            store = getHistoryStore(projectPath);
            result = store.getCachedOutput(conversationId, options.turn ? parseInt(options.turn) : undefined, {
                offset: parseInt(options.offset || '0'),
                limit: parseInt(options.limit || '10000'),
                outputType: options.outputType || 'both'
            });
        }
    }
    if (!result) {
        const hint = options.project
            ? `in project: ${options.project}`
            : 'in current directory or parent directories';
        console.error(chalk.red(`Error: Execution not found: ${conversationId}`));
        console.error(chalk.gray(`  Searched ${hint}`));
        console.error(chalk.gray('Usage: ccw cli output <conversation-id> [--project <path>]'));
        process.exit(1);
    }
    if (options.raw) {
        // Raw output only (for piping)
        if (result.stdout)
            console.log(result.stdout.content);
        return;
    }
    if (options.final) {
        // Final result only with usage hint
        // Prefer finalOutput (agent_message only) > parsedOutput (filtered) > raw stdout
        const outputContent = result.finalOutput?.content || result.parsedOutput?.content || result.stdout?.content;
        if (outputContent) {
            console.log(outputContent);
        }
        console.log();
        console.log(chalk.gray('─'.repeat(60)));
        console.log(chalk.dim(`Usage: ccw cli output ${conversationId} [options]`));
        console.log(chalk.dim('  --raw           Raw output (no formatting)'));
        console.log(chalk.dim('  --offset <n>    Start from byte offset'));
        console.log(chalk.dim('  --limit <n>     Limit output bytes'));
        console.log(chalk.dim('  --project <p>   Specify project path explicitly'));
        console.log(chalk.dim(`  --resume        ccw cli -p "..." --resume ${conversationId}`));
        return;
    }
    // Formatted output
    console.log(chalk.bold.cyan('Execution Output\n'));
    console.log(`  ${chalk.gray('ID:')}        ${result.conversationId}`);
    console.log(`  ${chalk.gray('Turn:')}      ${result.turnNumber}`);
    console.log(`  ${chalk.gray('Cached:')}    ${result.cached ? chalk.green('Yes') : chalk.yellow('No')}`);
    console.log(`  ${chalk.gray('Status:')}    ${result.status}`);
    console.log(`  ${chalk.gray('Time:')}      ${result.timestamp}`);
    console.log(`  ${chalk.gray('Project:')}   ${chalk.cyan(projectPath)}`);
    console.log();
    if (result.stdout) {
        console.log(`  ${chalk.gray('Stdout:')} (${result.stdout.totalBytes} bytes, offset ${result.stdout.offset})`);
        console.log(chalk.gray('  ' + '-'.repeat(60)));
        console.log(result.stdout.content);
        console.log(chalk.gray('  ' + '-'.repeat(60)));
        if (result.stdout.hasMore) {
            console.log(chalk.yellow(`  ... ${result.stdout.totalBytes - result.stdout.offset - result.stdout.content.length} more bytes available`));
            console.log(chalk.gray(`  Use --offset ${result.stdout.offset + result.stdout.content.length} to continue`));
        }
        console.log();
    }
    if (result.stderr && result.stderr.content) {
        console.log(`  ${chalk.gray('Stderr:')} (${result.stderr.totalBytes} bytes, offset ${result.stderr.offset})`);
        console.log(chalk.gray('  ' + '-'.repeat(60)));
        console.log(result.stderr.content);
        console.log(chalk.gray('  ' + '-'.repeat(60)));
        if (result.stderr.hasMore) {
            console.log(chalk.yellow(`  ... ${result.stderr.totalBytes - result.stderr.offset - result.stderr.content.length} more bytes available`));
        }
        console.log();
    }
}
/**
 * Test endpoint for debugging multi-line prompt parsing
 * Shows exactly how Commander.js parsed the arguments
 */
function testParseAction(args, options) {
    console.log(chalk.bold.cyan('\n  ═══════════════════════════════════════════════'));
    console.log(chalk.bold.cyan('  │       CLI PARSE TEST ENDPOINT              │'));
    console.log(chalk.bold.cyan('  ═══════════════════════════════════════════════\n'));
    // Debug: show raw options.prompt with JSON.stringify to reveal hidden characters
    console.log(chalk.bold.yellow('🔬 RAW OPTIONS.PROMPT (JSON):'));
    console.log(chalk.cyan('   ' + JSON.stringify(options.prompt)));
    console.log();
    // Show args array parsing
    console.log(chalk.bold.yellow('📦 Positional Arguments (args[]):'));
    console.log(chalk.gray('   Length: ') + chalk.white(args.length));
    if (args.length === 0) {
        console.log(chalk.gray('   (empty)'));
    }
    else {
        args.forEach((arg, i) => {
            console.log(chalk.gray(`   [${i}]: `) + chalk.green(`"${arg}"`));
            if (arg.includes('\n')) {
                console.log(chalk.yellow(`        ↳ Contains ${arg.split('\n').length} lines`));
            }
        });
    }
    console.log();
    // Show options parsing
    console.log(chalk.bold.yellow('⚙️  Options:'));
    const optionEntries = Object.entries(options).filter(([_, v]) => v !== undefined);
    if (optionEntries.length === 0) {
        console.log(chalk.gray('   (none)'));
    }
    else {
        optionEntries.forEach(([key, value]) => {
            const displayValue = typeof value === 'string' && value.includes('\n')
                ? `"${value.substring(0, 50)}..." (${value.split('\n').length} lines)`
                : JSON.stringify(value);
            console.log(chalk.gray(`   --${key}: `) + chalk.cyan(displayValue));
        });
    }
    console.log();
    // Show what would be used as prompt
    console.log(chalk.bold.yellow('🎯 Final Prompt Resolution:'));
    const { prompt: optionPrompt, file } = options;
    if (file) {
        console.log(chalk.gray('   Source: ') + chalk.magenta('--file/-f option'));
        console.log(chalk.gray('   File: ') + chalk.cyan(file));
    }
    else if (optionPrompt) {
        console.log(chalk.gray('   Source: ') + chalk.magenta('--prompt/-p option'));
        console.log(chalk.gray('   Value: ') + chalk.green(`"${optionPrompt.substring(0, 100)}${optionPrompt.length > 100 ? '...' : ''}"`));
        if (optionPrompt.includes('\n')) {
            console.log(chalk.yellow(`   ↳ Multiline: ${optionPrompt.split('\n').length} lines`));
        }
    }
    else if (args[0]) {
        console.log(chalk.gray('   Source: ') + chalk.magenta('positional argument (args[0])'));
        console.log(chalk.gray('   Value: ') + chalk.green(`"${args[0].substring(0, 100)}${args[0].length > 100 ? '...' : ''}"`));
        if (args[0].includes('\n')) {
            console.log(chalk.yellow(`   ↳ Multiline: ${args[0].split('\n').length} lines`));
        }
    }
    else {
        console.log(chalk.red('   No prompt found!'));
    }
    console.log();
    // Show raw debug info
    console.log(chalk.bold.yellow('🔍 Raw Debug Info:'));
    console.log(chalk.gray('   process.argv:'));
    process.argv.forEach((arg, i) => {
        console.log(chalk.gray(`     [${i}]: `) + chalk.dim(arg.length > 60 ? arg.substring(0, 60) + '...' : arg));
    });
    console.log(chalk.bold.cyan('\n  ═══════════════════════════════════════════════\n'));
}
/**
 * Show CLI tool status
 */
async function statusAction(debug) {
    // Enable debug mode if --debug flag is set
    if (debug) {
        process.env.DEBUG = 'true';
        console.log(chalk.yellow('  Debug mode enabled\n'));
    }
    console.log(chalk.bold.cyan('\n  CLI Tools Status\n'));
    const status = await getCliToolsStatus();
    for (const [tool, info] of Object.entries(status)) {
        const statusIcon = info.available ? chalk.green('●') : chalk.red('○');
        const statusText = info.available ? chalk.green('Available') : chalk.red('Not Found');
        console.log(`  ${statusIcon} ${chalk.bold.white(tool.padEnd(10))} ${statusText}`);
        if (info.available && info.path) {
            console.log(chalk.gray(`      ${info.path}`));
        }
    }
    console.log();
}
/**
 * Execute a CLI tool
 * @param {string} prompt - Prompt to execute
 * @param {Object} options - CLI options
 */
async function execAction(positionalPrompt, options) {
    const { prompt: optionPrompt, file, tool: userTool, mode = 'analysis', model, cd, includeDirs, stream, resume, id, noNative, cache, injectMode, debug, uncommitted, base, commit, title, rule, toFile } = options;
    // Determine the tool to use: explicit --tool option, or defaultTool from config
    let tool = userTool;
    if (!tool) {
        try {
            const settings = loadClaudeCliSettings(cd || process.cwd());
            tool = settings.defaultTool || 'gemini';
        }
        catch {
            // Fallback to gemini if config cannot be loaded
            tool = 'gemini';
        }
    }
    // Enable debug mode if --debug flag is set
    if (debug) {
        process.env.DEBUG = 'true';
        console.log(chalk.yellow('  Debug mode enabled\n'));
    }
    // Priority: 1. --file, 2. stdin (piped), 3. --prompt/-p option, 4. positional argument
    // Note: On Windows, quoted arguments like -p "say hello" may be split into
    // -p "say" and positional "hello". We merge them back together.
    let finalPrompt;
    if (file) {
        // Read from file
        const { readFileSync, existsSync } = await import('fs');
        const { resolve } = await import('path');
        const filePath = resolve(file);
        if (!existsSync(filePath)) {
            console.error(chalk.red(`Error: File not found: ${filePath}`));
            process.exit(1);
        }
        finalPrompt = readFileSync(filePath, 'utf8').trim();
        if (!finalPrompt) {
            console.error(chalk.red('Error: File is empty'));
            process.exit(1);
        }
    }
    else if (!process.stdin.isTTY) {
        // Read from stdin (piped input) - enables: echo "prompt" | ccw cli --tool gemini
        // This bypasses Windows shell multi-line argument limitations
        const { readFileSync } = await import('fs');
        try {
            finalPrompt = readFileSync(0, 'utf8').trim(); // fd 0 = stdin
            if (debug) {
                console.log(chalk.gray(`  Read ${finalPrompt.length} chars from stdin`));
            }
        }
        catch {
            // stdin not available or empty, fall through to other methods
        }
    }
    // If no stdin input, try --prompt/-p option or positional argument
    if (!finalPrompt) {
        if (optionPrompt) {
            // Use --prompt/-p option (preferred for multi-line)
            // Merge with positional argument if Windows split the quoted string
            finalPrompt = positionalPrompt ? `${optionPrompt} ${positionalPrompt}` : optionPrompt;
        }
        else {
            // Fall back to positional argument
            finalPrompt = positionalPrompt;
        }
    }
    // Prompt is required unless resuming OR using review mode with target flags
    // codex review: --uncommitted, --base, --commit don't require a prompt
    const isReviewWithTarget = mode === 'review' && (uncommitted || base || commit);
    if (!finalPrompt && !resume && !isReviewWithTarget) {
        console.error(chalk.red('Error: Prompt is required'));
        console.error(chalk.gray('Usage: ccw cli -p "<prompt>" --tool gemini'));
        console.error(chalk.gray('   or: ccw cli -f prompt.txt --tool codex'));
        console.error(chalk.gray('   or: ccw cli --resume --tool gemini'));
        console.error(chalk.gray('   or: ccw cli --tool codex --mode review --uncommitted'));
        process.exit(1);
    }
    const prompt_to_use = finalPrompt || '';
    // Load rules templates (concatenation mode - directly append to prompt)
    // Skip template loading when using target flags with codex review
    // codex review: --uncommitted, --base, --commit are all mutually exclusive with [PROMPT]
    // Default to universal-rigorous-style if --rule not specified
    const skipTemplates = mode === 'review' && (uncommitted || base || commit);
    const effectiveRule = rule || 'universal-rigorous-style';
    let systemRules = ''; // Protocol content
    let roles = ''; // Template content
    if (skipTemplates) {
        if (debug) {
            console.log(chalk.gray(`  Skipping templates: --commit with review mode doesn't support prompt`));
        }
    }
    else {
        try {
            const { loadProtocol, loadTemplate } = await import('../tools/template-discovery.js');
            const proto = loadProtocol(mode);
            const tmpl = loadTemplate(effectiveRule);
            if (proto)
                systemRules = proto;
            if (tmpl)
                roles = tmpl;
            if (debug) {
                console.log(chalk.gray(`  Rule loaded: ${effectiveRule}${!rule ? ' (default)' : ''}`));
                console.log(chalk.gray(`  systemRules(${systemRules.length} chars) + roles(${roles.length} chars)`));
                console.log(chalk.gray(`  Rules will be appended to prompt automatically`));
            }
        }
        catch (error) {
            console.error(chalk.red(`Error loading rule template: ${error instanceof Error ? error.message : error}`));
            process.exit(1);
        }
    }
    // Handle cache option: pack @patterns and/or content
    let cacheSessionId;
    let actualPrompt = prompt_to_use;
    if (cache) {
        const { handler: contextCacheHandler } = await import('../tools/context-cache.js');
        // Parse cache config from comma-separated string
        // Items starting with @ are patterns, others are text content
        let cacheConfig = {};
        if (cache === true) {
            // --cache without value: auto-extract from CONTEXT field
            const contextMatch = prompt_to_use.match(/CONTEXT:\s*([^\n]+)/i);
            if (contextMatch) {
                const contextLine = contextMatch[1];
                const patternMatches = contextLine.matchAll(/@[^\s|]+/g);
                cacheConfig.patterns = Array.from(patternMatches).map(m => m[0]);
            }
        }
        else if (typeof cache === 'string') {
            // Parse comma-separated items: @patterns and text content
            const items = cache.split(',').map(s => s.trim()).filter(Boolean);
            const patterns = [];
            const contentParts = [];
            for (const item of items) {
                if (item.startsWith('@')) {
                    patterns.push(item);
                }
                else {
                    contentParts.push(item);
                }
            }
            if (patterns.length > 0) {
                cacheConfig.patterns = patterns;
            }
            if (contentParts.length > 0) {
                cacheConfig.content = contentParts.join('\n');
            }
        }
        // Also extract patterns from CONTEXT if not provided
        if ((!cacheConfig.patterns || cacheConfig.patterns.length === 0) && prompt_to_use) {
            const contextMatch = prompt_to_use.match(/CONTEXT:\s*([^\n]+)/i);
            if (contextMatch) {
                const contextLine = contextMatch[1];
                const patternMatches = contextLine.matchAll(/@[^\s|]+/g);
                cacheConfig.patterns = Array.from(patternMatches).map(m => m[0]);
            }
        }
        // Pack if we have patterns or content
        if ((cacheConfig.patterns && cacheConfig.patterns.length > 0) || cacheConfig.content) {
            const patternCount = cacheConfig.patterns?.length || 0;
            const hasContent = !!cacheConfig.content;
            console.log(chalk.gray(`  Caching: ${patternCount} pattern(s)${hasContent ? ' + text content' : ''}...`));
            const cacheResult = await contextCacheHandler({
                operation: 'pack',
                patterns: cacheConfig.patterns,
                content: cacheConfig.content,
                cwd: cd || process.cwd(),
                include_dirs: includeDirs ? includeDirs.split(',') : undefined,
            });
            if (cacheResult.success && cacheResult.result) {
                const packResult = cacheResult.result;
                cacheSessionId = packResult.session_id;
                console.log(chalk.gray(`  Cached: ${packResult.files_packed} files, ${packResult.total_bytes} bytes`));
                console.log(chalk.gray(`  Session: ${cacheSessionId}`));
                // Determine inject mode:
                // --inject-mode explicitly set > tool default (codex=full, others=none)
                const effectiveInjectMode = injectMode ?? (tool === 'codex' ? 'full' : 'none');
                if (effectiveInjectMode !== 'none' && cacheSessionId) {
                    if (effectiveInjectMode === 'full') {
                        // Read full cache content
                        const readResult = await contextCacheHandler({
                            operation: 'read',
                            session_id: cacheSessionId,
                            offset: 0,
                            limit: 1024 * 1024, // 1MB max
                        });
                        if (readResult.success && readResult.result) {
                            const { content: cachedContent, total_bytes } = readResult.result;
                            console.log(chalk.gray(`  Injecting ${total_bytes} bytes (full mode)...`));
                            actualPrompt = `=== CACHED CONTEXT (${packResult.files_packed} files) ===\n${cachedContent}\n\n=== USER PROMPT ===\n${prompt_to_use}`;
                        }
                    }
                    else if (effectiveInjectMode === 'progressive') {
                        // Progressive mode: read first page only (64KB default)
                        const pageLimit = 65536;
                        const readResult = await contextCacheHandler({
                            operation: 'read',
                            session_id: cacheSessionId,
                            offset: 0,
                            limit: pageLimit,
                        });
                        if (readResult.success && readResult.result) {
                            const { content: cachedContent, total_bytes, has_more, next_offset } = readResult.result;
                            console.log(chalk.gray(`  Injecting ${cachedContent.length}/${total_bytes} bytes (progressive mode)...`));
                            const moreInfo = has_more
                                ? `\n[... ${total_bytes - cachedContent.length} more bytes available via: context_cache(operation="read", session_id="${cacheSessionId}", offset=${next_offset}) ...]`
                                : '';
                            actualPrompt = `=== CACHED CONTEXT (${packResult.files_packed} files, progressive) ===\n${cachedContent}${moreInfo}\n\n=== USER PROMPT ===\n${prompt_to_use}`;
                        }
                    }
                }
                console.log();
            }
            else {
                console.log(chalk.yellow(`  Cache warning: ${cacheResult.error}`));
            }
        }
    }
    // Concatenate systemRules and roles to the end of prompt (if loaded)
    // Format: [USER_PROMPT]\n\nProtocol (mode): [content]\n\nTemplate (rule): [content]
    // Skip concatenation when using --commit with review mode (prompt not allowed)
    if (!skipTemplates && (systemRules || roles)) {
        const parts = [actualPrompt];
        if (systemRules) {
            parts.push(`Protocol (${mode}):\n${systemRules}`);
        }
        if (roles) {
            parts.push(`Template (${effectiveRule}):\n${roles}`);
        }
        actualPrompt = parts.join('\n\n');
        if (debug) {
            console.log(chalk.gray(`  Prompt structure: USER_PROMPT(${prompt_to_use.length}) + Protocol(${systemRules.length}) + Template(${roles.length})`));
            console.log(chalk.gray(`  Total prompt length: ${actualPrompt.length} chars`));
        }
    }
    // Parse resume IDs for merge scenario
    const resumeIds = resume && typeof resume === 'string' ? resume.split(',').map(s => s.trim()).filter(Boolean) : [];
    const isMerge = resumeIds.length > 1;
    // Show execution mode
    let resumeInfo = '';
    if (isMerge) {
        resumeInfo = ` merging ${resumeIds.length} conversations`;
    }
    else if (resume) {
        resumeInfo = typeof resume === 'string' ? ` resuming ${resume}` : ' resuming last';
    }
    const nativeMode = noNative ? ' (prompt-concat)' : '';
    const idInfo = id ? ` [${id}]` : '';
    // Show merge details
    if (isMerge) {
        console.log(chalk.gray('  Merging conversations:'));
        for (const rid of resumeIds) {
            console.log(chalk.gray(`    • ${rid}`));
        }
        console.log();
    }
    // Generate execution ID for streaming (use custom ID or timestamp-based)
    const executionId = id || `${Date.now()}-${tool}`;
    const startTime = Date.now();
    const modelInfo = model ? ` @${model}` : '';
    const spinnerBaseText = `Executing ${tool}${modelInfo} (${mode} mode${resumeInfo}${nativeMode})${idInfo}...`;
    console.log();
    const spinner = stream ? null : createSpinner(`  ${spinnerBaseText}`).start();
    const elapsedInterval = spinner
        ? setInterval(() => {
            const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
            spinner.text = `  ${spinnerBaseText} (${elapsedSeconds}s elapsed)`;
        }, 1000)
        : null;
    elapsedInterval?.unref?.();
    if (!spinner) {
        console.log(chalk.cyan(`  ${spinnerBaseText}\n`));
    }
    // Handle process interruption (SIGINT/SIGTERM) to notify dashboard
    const handleInterrupt = (signal) => {
        const duration = Date.now() - startTime;
        if (elapsedInterval)
            clearInterval(elapsedInterval);
        if (spinner) {
            spinner.warn(`Interrupted by ${signal} (${Math.floor(duration / 1000)}s elapsed)`);
        }
        else {
            console.log(chalk.yellow(`\n  Interrupted by ${signal}`));
        }
        // Kill child process (gemini/codex/qwen CLI) if running
        killCurrentCliProcess();
        // Broadcast interruption to dashboard
        broadcastStreamEvent('CLI_EXECUTION_COMPLETED', {
            executionId,
            success: false,
            duration,
            interrupted: true
        });
        // Give time for the event to be sent before exiting
        setTimeout(() => process.exit(130), 100);
    };
    process.on('SIGINT', () => handleInterrupt('SIGINT'));
    process.on('SIGTERM', () => handleInterrupt('SIGTERM'));
    // Notify dashboard: execution started (legacy)
    notifyDashboard({
        event: 'started',
        tool,
        mode,
        prompt_preview: prompt_to_use.substring(0, 100) + (prompt_to_use.length > 100 ? '...' : ''),
        custom_id: id || null
    });
    // Broadcast CLI_EXECUTION_STARTED for real-time streaming viewer
    // Note: /api/hook wraps extraData into payload, so send fields directly
    broadcastStreamEvent('CLI_EXECUTION_STARTED', {
        executionId,
        tool,
        mode
    });
    if (process.env.DEBUG) {
        console.error(`[CLI] Generated executionId: ${executionId}`);
    }
    // Buffer to accumulate output when both --stream and --to-file are specified
    let streamBuffer = '';
    // Streaming output handler - broadcasts to dashboard AND writes to stdout
    const onOutput = (unit) => {
        // Always broadcast to dashboard for real-time viewing
        // Note: /api/hook wraps extraData into payload, so send fields directly
        // Maintain backward compatibility with frontend expecting { chunkType, data }
        // Use SmartContentFormatter for intelligent content formatting (never returns null)
        const content = SmartContentFormatter.format(unit.content, unit.type);
        broadcastStreamEvent('CLI_OUTPUT', {
            executionId,
            chunkType: unit.type, // For backward compatibility
            data: content, // For backward compatibility (now formatted)
            unit // New structured format
        });
        // Write to terminal only when --stream flag is passed
        if (stream) {
            switch (unit.type) {
                case 'stdout':
                case 'code':
                case 'streaming_content': // Show streaming delta content in real-time
                    const content1 = typeof unit.content === 'string' ? unit.content : JSON.stringify(unit.content);
                    process.stdout.write(content1);
                    if (toFile)
                        streamBuffer += content1;
                    break;
                case 'stderr':
                    const content2 = typeof unit.content === 'string' ? unit.content : JSON.stringify(unit.content);
                    process.stderr.write(content2);
                    if (toFile)
                        streamBuffer += content2;
                    break;
                case 'thought':
                    // Optional: display thinking process with different color
                    // For now, skip to reduce noise
                    break;
                case 'progress':
                    // Optional: update progress bar
                    // For now, skip
                    break;
                default:
                    // Other types: output content if available
                    if (unit.content) {
                        const content3 = typeof unit.content === 'string' ? unit.content : '';
                        process.stdout.write(content3);
                        if (toFile)
                            streamBuffer += content3;
                    }
            }
        }
    };
    // Use JSON-lines parsing by default to enable type badges (thought, code, file_diff, etc.)
    // All CLI tools may output structured JSON that can be parsed for richer UI
    const outputFormat = 'json-lines';
    try {
        const result = await cliExecutorTool.execute({
            tool,
            prompt: actualPrompt,
            mode,
            model,
            cd,
            includeDirs,
            // timeout removed - controlled by external caller (bash timeout)
            resume,
            id: executionId, // unified execution ID (matches broadcast events)
            noNative,
            stream: !!stream, // stream=true → streaming enabled (no cache), stream=false → cache output (default)
            outputFormat, // Enable JSONL parsing for tools that support it
            // Codex review options
            uncommitted,
            base,
            commit,
            title
            // Rules are now concatenated directly into prompt (no env vars)
        }, onOutput); // Always pass onOutput for real-time dashboard streaming
        if (elapsedInterval)
            clearInterval(elapsedInterval);
        if (spinner) {
            const durationSeconds = (result.execution.duration_ms / 1000).toFixed(1);
            const turnInfo = result.success && result.conversation.turn_count > 1
                ? ` (turn ${result.conversation.turn_count})`
                : '';
            if (result.success) {
                spinner.succeed(`Completed in ${durationSeconds}s${turnInfo}`);
            }
            else {
                spinner.fail(`Failed after ${durationSeconds}s`);
            }
        }
        // If not streaming (default), print output now
        // Prefer parsedOutput (from stream parser) over raw stdout for better formatting
        if (!stream) {
            const output = result.parsedOutput || result.stdout;
            if (output) {
                console.log(output);
                // Save to file if --to-file is specified
                if (toFile) {
                    try {
                        const { writeFileSync, mkdirSync } = await import('fs');
                        const { dirname, resolve } = await import('path');
                        const filePath = resolve(cd || process.cwd(), toFile);
                        const dir = dirname(filePath);
                        mkdirSync(dir, { recursive: true });
                        writeFileSync(filePath, output, 'utf8');
                        if (debug) {
                            console.log(chalk.gray(`  Saved output to: ${filePath}`));
                        }
                    }
                    catch (err) {
                        console.error(chalk.red(`  Error saving to file: ${err.message}`));
                    }
                }
            }
        }
        // Print summary with execution ID and turn info
        console.log();
        if (result.success) {
            // Save streaming output to file if needed
            if (stream && toFile && streamBuffer) {
                try {
                    const { writeFileSync, mkdirSync } = await import('fs');
                    const { dirname, resolve } = await import('path');
                    const filePath = resolve(cd || process.cwd(), toFile);
                    const dir = dirname(filePath);
                    mkdirSync(dir, { recursive: true });
                    writeFileSync(filePath, streamBuffer, 'utf8');
                }
                catch (err) {
                    console.error(chalk.red(`  Error saving to file: ${err.message}`));
                }
            }
            if (!spinner) {
                const turnInfo = result.conversation.turn_count > 1
                    ? ` (turn ${result.conversation.turn_count})`
                    : '';
                console.log(chalk.green(`  ✓ Completed in ${(result.execution.duration_ms / 1000).toFixed(1)}s${turnInfo}`));
            }
            console.log(chalk.gray(`  ID: ${result.execution.id}`));
            if (isMerge && !id) {
                // Merge without custom ID: updated all source conversations
                console.log(chalk.gray(`  Updated ${resumeIds.length} conversations: ${resumeIds.join(', ')}`));
            }
            else if (isMerge && id) {
                // Merge with custom ID: created new merged conversation
                console.log(chalk.gray(`  Created merged conversation from ${resumeIds.length} sources`));
            }
            if (result.conversation.turn_count > 1) {
                console.log(chalk.gray(`  Total: ${result.conversation.turn_count} turns, ${(result.conversation.total_duration_ms / 1000).toFixed(1)}s`));
            }
            console.log(chalk.dim(`  Continue: ccw cli -p "..." --resume ${result.execution.id}`));
            if (!stream) {
                console.log(chalk.dim(`  Output (optional): ccw cli output ${result.execution.id}`));
            }
            if (toFile) {
                const { resolve } = await import('path');
                const filePath = resolve(cd || process.cwd(), toFile);
                console.log(chalk.green(`  Saved to: ${filePath}`));
            }
            // Notify dashboard: execution completed (legacy)
            notifyDashboard({
                event: 'completed',
                tool,
                mode,
                execution_id: result.execution.id,
                success: true,
                duration_ms: result.execution.duration_ms,
                turn_count: result.conversation.turn_count
            });
            // Broadcast CLI_EXECUTION_COMPLETED for real-time streaming viewer
            if (process.env.DEBUG_CLI_EVENTS) {
                console.error(`[CLI-TIMING] Broadcasting CLI_EXECUTION_COMPLETED at ${Date.now()}`);
            }
            broadcastStreamEvent('CLI_EXECUTION_COMPLETED', {
                executionId, // Use the same executionId as started event
                success: true,
                duration: result.execution.duration_ms
            });
            if (process.env.DEBUG_CLI_EVENTS) {
                console.error(`[CLI-TIMING] Broadcast returned, setting timeout at ${Date.now()}`);
            }
            // Ensure clean exit after successful execution
            // Delay to allow HTTP request to complete
            // FIX: Increased from 150ms to 500ms for long-running executions
            setTimeout(() => {
                if (process.env.DEBUG_CLI_EVENTS) {
                    console.error(`[CLI-TIMING] process.exit(0) at ${Date.now()}`);
                }
                process.exit(0);
            }, 500);
        }
        else {
            if (!spinner) {
                console.log(chalk.red(`  ✗ Failed (${result.execution.status})`));
            }
            console.log(chalk.gray(`  ID: ${result.execution.id}`));
            console.log(chalk.gray(`  Duration: ${(result.execution.duration_ms / 1000).toFixed(1)}s`));
            console.log(chalk.gray(`  Exit Code: ${result.execution.exit_code}`));
            // Show stderr with better formatting
            if (result.stderr) {
                console.log();
                console.log(chalk.red.bold('  Error Output:'));
                console.log(chalk.gray('  ' + '─'.repeat(60)));
                // Indent stderr for better readability
                const stderrLines = result.stderr.split('\n');
                for (const line of stderrLines.slice(0, 30)) { // Limit to 30 lines
                    console.error(chalk.red(`  ${line}`));
                }
                if (stderrLines.length > 30) {
                    console.log(chalk.yellow(`  ... ${stderrLines.length - 30} more lines`));
                    console.log(chalk.cyan(`  💡 View full output: ccw cli output ${result.execution.id}`));
                    console.log();
                }
                console.log(chalk.gray('  ' + '─'.repeat(60)));
            }
            // Show troubleshooting hints
            console.log();
            console.log(chalk.yellow.bold('  Troubleshooting:'));
            console.log(chalk.gray(`    • Check if ${tool} is properly installed: ccw cli status`));
            console.log(chalk.gray(`    • Enable debug mode: DEBUG=true ccw cli -p "..."  or  set DEBUG=true && ccw cli -p "..."`));
            if (result.stderr?.includes('API key') || result.stderr?.includes('Authentication')) {
                console.log(chalk.gray(`    • Check API key configuration for ${tool}`));
            }
            if (result.stderr?.includes('rate limit')) {
                console.log(chalk.gray(`    • Wait and retry - rate limit exceeded`));
            }
            // Notify dashboard: execution failed (legacy)
            notifyDashboard({
                event: 'completed',
                tool,
                mode,
                execution_id: result.execution.id,
                success: false,
                status: result.execution.status,
                duration_ms: result.execution.duration_ms
            });
            // Broadcast CLI_EXECUTION_COMPLETED for real-time streaming viewer
            broadcastStreamEvent('CLI_EXECUTION_COMPLETED', {
                executionId, // Use the same executionId as started event
                success: false,
                duration: result.execution.duration_ms
            });
            // Delay to allow HTTP request to complete
            setTimeout(() => process.exit(1), 150);
        }
    }
    catch (error) {
        const err = error;
        if (elapsedInterval)
            clearInterval(elapsedInterval);
        if (spinner)
            spinner.fail('Execution error');
        console.error(chalk.red.bold(`\n  ✗ Execution Error\n`));
        console.error(chalk.red(`  ${err.message}`));
        // Parse error message for additional context
        if (err.message.includes('Failed to spawn')) {
            console.log();
            console.log(chalk.yellow.bold('  Troubleshooting:'));
            console.log(chalk.gray(`    • Check if ${tool} is installed: npm ls -g @google/gemini-cli (or qwen/codex)`));
            console.log(chalk.gray(`    • Verify PATH includes npm global bin directory`));
            console.log(chalk.gray(`    • Run: ccw cli status`));
            console.log(chalk.gray(`    • Enable debug mode: DEBUG=true ccw cli -p "..."`));
        }
        else if (err.message.includes('not available')) {
            console.log();
            console.log(chalk.yellow.bold('  Troubleshooting:'));
            console.log(chalk.gray(`    • Install the tool: npm install -g <package-name>`));
            console.log(chalk.gray(`    • Run: ccw cli status`));
        }
        // Notify dashboard: execution error (legacy)
        notifyDashboard({
            event: 'error',
            tool,
            mode,
            error: err.message
        });
        // Broadcast CLI_EXECUTION_ERROR for real-time streaming viewer
        broadcastStreamEvent('CLI_EXECUTION_ERROR', {
            executionId,
            error: err.message
        });
        // Delay to allow HTTP request to complete
        setTimeout(() => process.exit(1), 150);
    }
}
/**
 * Show execution history
 * @param {Object} options - CLI options
 */
async function historyAction(options) {
    const { limit = '20', tool, status } = options;
    console.log(chalk.bold.cyan('\n  CLI Execution History\n'));
    // Use recursive: true to aggregate history from parent and child projects (matches Dashboard behavior)
    const history = await getExecutionHistoryAsync(process.cwd(), { limit: parseInt(limit, 10), tool, status, recursive: true });
    if (history.executions.length === 0) {
        console.log(chalk.gray('  No executions found.\n'));
        return;
    }
    // Count by tool
    const toolCounts = {};
    for (const exec of history.executions) {
        toolCounts[exec.tool] = (toolCounts[exec.tool] || 0) + 1;
    }
    const toolSummary = Object.entries(toolCounts).map(([t, c]) => `${t}:${c}`).join(' ');
    // Compact table header with tool breakdown
    console.log(chalk.gray(`  Total: ${history.total} | Showing: ${history.executions.length} (${toolSummary})\n`));
    console.log(chalk.gray('  Status  Tool      Time         Duration   ID'));
    console.log(chalk.gray('  ' + '─'.repeat(70)));
    for (const exec of history.executions) {
        const statusIcon = exec.status === 'success' ? chalk.green('●') :
            exec.status === 'timeout' ? chalk.yellow('●') : chalk.red('●');
        const duration = exec.duration_ms >= 1000
            ? `${(exec.duration_ms / 1000).toFixed(1)}s`
            : `${exec.duration_ms}ms`;
        const timeAgo = getTimeAgo(new Date(exec.updated_at || exec.timestamp));
        const turnInfo = exec.turn_count && exec.turn_count > 1 ? chalk.cyan(`[${exec.turn_count}t]`) : '    ';
        // Compact format: status tool time duration [turns] + id on same line (no truncation)
        // Truncate prompt preview to 50 chars for compact display
        const shortPrompt = exec.prompt_preview.replace(/\n/g, ' ').substring(0, 50).trim();
        console.log(`  ${statusIcon}     ${chalk.bold.white(exec.tool.padEnd(8))}  ${chalk.gray(timeAgo.padEnd(11))}  ${chalk.gray(duration.padEnd(8))} ${turnInfo} ${chalk.dim(exec.id)}`);
        console.log(chalk.gray(`        ${shortPrompt}${exec.prompt_preview.length > 50 ? '...' : ''}`));
    }
    // Usage hint
    console.log();
    console.log(chalk.gray('  ' + '─'.repeat(70)));
    console.log(chalk.dim('  Filter: ccw cli history --tool <gemini|codex|qwen> --limit <n>'));
    console.log(chalk.dim('  Output: ccw cli output <id> --final'));
    console.log();
}
/**
 * Show conversation detail with all turns
 * @param {string} conversationId - Conversation ID
 */
async function detailAction(conversationId) {
    if (!conversationId) {
        console.error(chalk.red('Error: Conversation ID is required'));
        console.error(chalk.gray('Usage: ccw cli detail <conversation-id>'));
        process.exit(1);
    }
    const conversation = getConversationDetail(process.cwd(), conversationId);
    if (!conversation) {
        console.error(chalk.red(`Error: Conversation not found: ${conversationId}`));
        process.exit(1);
    }
    console.log(chalk.bold.cyan('\n  Conversation Detail\n'));
    console.log(`  ${chalk.gray('ID:')}         ${conversation.id}`);
    console.log(`  ${chalk.gray('Tool:')}       ${conversation.tool}`);
    console.log(`  ${chalk.gray('Model:')}      ${conversation.model}`);
    console.log(`  ${chalk.gray('Mode:')}       ${conversation.mode}`);
    console.log(`  ${chalk.gray('Status:')}     ${conversation.latest_status}`);
    console.log(`  ${chalk.gray('Turns:')}      ${conversation.turn_count}`);
    console.log(`  ${chalk.gray('Duration:')}   ${(conversation.total_duration_ms / 1000).toFixed(1)}s total`);
    console.log(`  ${chalk.gray('Created:')}    ${conversation.created_at}`);
    if (conversation.turn_count > 1) {
        console.log(`  ${chalk.gray('Updated:')}    ${conversation.updated_at}`);
    }
    // Show all turns
    for (const turn of conversation.turns) {
        console.log(chalk.bold.cyan(`\n  ═══ Turn ${turn.turn} ═══`));
        console.log(chalk.gray(`  ${turn.timestamp} | ${turn.status} | ${(turn.duration_ms / 1000).toFixed(1)}s`));
        console.log(chalk.bold.white('\n  Prompt:'));
        console.log(chalk.gray('  ' + turn.prompt.split('\n').join('\n  ')));
        if (turn.output.stdout) {
            console.log(chalk.bold.white('\n  Output:'));
            console.log(turn.output.stdout);
        }
        if (turn.output.stderr) {
            console.log(chalk.bold.red('\n  Errors:'));
            console.log(turn.output.stderr);
        }
        if (turn.output.truncated) {
            console.log(chalk.yellow('\n  Note: Output was truncated due to size.'));
        }
    }
    console.log(chalk.dim(`\n  Continue: ccw cli -p "..." --resume ${conversation.id}`));
    console.log();
}
/**
 * Get human-readable time ago string
 * @param {Date} date
 * @returns {string}
 */
function getTimeAgo(date) {
    const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
    if (seconds < 60)
        return 'just now';
    if (seconds < 3600)
        return `${Math.floor(seconds / 60)}m ago`;
    if (seconds < 86400)
        return `${Math.floor(seconds / 3600)}h ago`;
    if (seconds < 604800)
        return `${Math.floor(seconds / 86400)}d ago`;
    return date.toLocaleDateString();
}
/**ccw cli -p
 * CLI command entry point
 * @param {string} subcommand - Subcommand (status, exec, history, detail)
 * @param {string[]} args - Arguments array
 * @param {Object} options - CLI options
 */
export async function cliCommand(subcommand, args, options) {
    const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
    switch (subcommand) {
        case 'status':
            await statusAction(options.debug);
            break;
        case 'history':
            await historyAction(options);
            break;
        case 'detail':
            await detailAction(argsArray[0]);
            break;
        case 'storage':
            await storageAction(argsArray[0], options);
            break;
        case 'output':
            await outputAction(argsArray[0], options);
            break;
        case 'test-parse':
            // Test endpoint to debug multi-line prompt parsing
            testParseAction(argsArray, options);
            break;
        default: {
            const execOptions = options;
            // Auto-exec if: has -p/--prompt, has -f/--file, has --resume, subcommand looks like a prompt,
            // review mode with target flags (--uncommitted, --base, --commit), or stdin has piped input
            const hasPromptOption = !!execOptions.prompt;
            const hasFileOption = !!execOptions.file;
            const hasResume = execOptions.resume !== undefined;
            const subcommandIsPrompt = subcommand && !subcommand.startsWith('-');
            const hasReviewTarget = execOptions.mode === 'review' &&
                (execOptions.uncommitted || execOptions.base || execOptions.commit);
            const hasStdinInput = !process.stdin.isTTY; // piped input detected
            if (hasPromptOption || hasFileOption || hasResume || subcommandIsPrompt || hasReviewTarget || hasStdinInput) {
                // Treat as exec: use subcommand as positional prompt if no -p/-f option
                let positionalPrompt = subcommandIsPrompt ? subcommand : undefined;
                // On Windows, quoted arguments like -p "a b c" may be split across argsArray
                // Merge them back together to reconstruct the full prompt
                if (argsArray.length > 0 && hasPromptOption) {
                    const extraArgs = argsArray.join(' ');
                    positionalPrompt = positionalPrompt ? `${positionalPrompt} ${extraArgs}` : extraArgs;
                }
                await execAction(positionalPrompt, execOptions);
            }
            else {
                // Show help
                console.log(chalk.bold.cyan('\n  CCW CLI Tool Executor\n'));
                console.log('  Unified interface for Gemini, Qwen, and Codex CLI tools.\n');
                console.log('  Usage:');
                console.log(chalk.gray('    ccw cli -f prompt.txt --tool <tool>     Execute from file (recommended for multi-line)'));
                console.log(chalk.gray('    ccw cli -p "<prompt>" --tool <tool>     Execute with prompt (single-line)'));
                console.log(chalk.gray('    echo "prompt" | ccw cli --tool <tool>   Execute from stdin (pipe)'));
                console.log();
                console.log('  Subcommands:');
                console.log(chalk.gray('    status              Check CLI tools availability'));
                console.log(chalk.gray('    storage [cmd]       Manage CCW storage (info/clean/config)'));
                console.log(chalk.gray('    history             Show execution history'));
                console.log(chalk.gray('    detail <id>         Show execution detail'));
                console.log(chalk.gray('    output <id>         Show execution output with pagination'));
                console.log(chalk.gray('    test-parse [args]   Debug CLI argument parsing'));
                console.log();
                console.log('  Options:');
                console.log(chalk.gray('    -f, --file <file>   Read prompt from file (recommended for multi-line prompts)'));
                console.log(chalk.gray('    -p, --prompt <text> Prompt text (single-line)'));
                console.log(chalk.gray('    --tool <tool>       Tool: gemini, qwen, codex (default: gemini)'));
                console.log(chalk.gray('    --mode <mode>       Mode: analysis, write, auto, review (default: analysis)'));
                console.log(chalk.gray('    -d, --debug         Enable debug logging for troubleshooting'));
                console.log(chalk.gray('    --model <model>     Model override'));
                console.log(chalk.gray('    --cd <path>         Working directory'));
                console.log(chalk.gray('    --includeDirs <dirs>  Additional directories'));
                // --timeout removed - controlled by external caller (bash timeout)
                console.log(chalk.gray('    --resume [id]       Resume previous session'));
                console.log(chalk.gray('    --cache <items>     Cache: comma-separated @patterns and text'));
                console.log(chalk.gray('    --inject-mode <m>   Inject mode: none, full, progressive'));
                console.log();
                console.log('  Examples:');
                console.log(chalk.gray('    ccw cli -f my-prompt.txt --tool gemini'));
                console.log();
                console.log(chalk.gray('    # Bash/Linux heredoc'));
                console.log(chalk.gray("    ccw cli -f <(cat <<'EOF'"));
                console.log(chalk.gray('    PURPOSE: Multi-line prompt'));
                console.log(chalk.gray('    TASK: Example task'));
                console.log(chalk.gray('    EOF'));
                console.log(chalk.gray('    ) --tool gemini'));
                console.log();
                console.log(chalk.gray('    # PowerShell multi-line'));
                console.log(chalk.gray("    @'"));
                console.log(chalk.gray('    PURPOSE: Multi-line prompt'));
                console.log(chalk.gray('    TASK: Example task'));
                console.log(chalk.gray("    '@ | Out-File -Encoding utf8 prompt.tmp; ccw cli -f prompt.tmp --tool gemini"));
                console.log();
                console.log(chalk.gray('    ccw cli --resume --tool gemini'));
                console.log(chalk.gray('    ccw cli -p "..." --cache "@src/**/*.ts" --tool codex'));
                console.log(chalk.gray('    ccw cli -p "..." --cache "@src/**/*" --inject-mode progressive --tool gemini'));
                console.log(chalk.gray('    ccw cli output <id> --final      # View result with usage hint'));
                console.log();
                console.log('  Cache format:');
                console.log(chalk.gray('    --cache "@src/**/*.ts,@CLAUDE.md"     # @patterns to pack'));
                console.log(chalk.gray('    --cache "@src/**/*,extra context"     # patterns + text content'));
                console.log(chalk.gray('    --cache                               # auto from CONTEXT field'));
                console.log();
                console.log('  Inject modes:');
                console.log(chalk.gray('    none:        cache only, no injection (default for gemini/qwen)'));
                console.log(chalk.gray('    full:        inject all cached content (default for codex)'));
                console.log(chalk.gray('    progressive: inject first 64KB with MCP continuation hint'));
                console.log();
                console.log('  Output options (ccw cli output <id>):');
                console.log(chalk.gray('    --final      Final result only with usage hint'));
                console.log(chalk.gray('    --raw        Raw output only (no formatting, for piping)'));
                console.log(chalk.gray('    --offset <n> Start from byte offset'));
                console.log(chalk.gray('    --limit <n>  Limit output bytes'));
                console.log();
                console.log(chalk.dim('  Tip: For complex prompts, use --file to avoid shell escaping issues'));
                console.log();
            }
        }
    }
}
//# sourceMappingURL=cli.js.map