{"version":3,"sources":["/home/x/core/projects/battle/dist/chunk-AW3SAVB6.cjs","../src/Battle/index.ts","../src/Battle/constructor.ts","../src/Replay/index.ts","../src/Replay/constructor.ts","../src/Replay/record.ts","../src/Replay/save.ts","../src/Replay/load.ts","../src/Replay/play.ts","../src/utils/colors.ts","../src/Replay/PlaybackEngine.ts","../src/Replay/export.ts","../src/Replay/getPlaybackEngineSource.ts","../src/Battle/spawn.ts","../src/PTY/index.ts","../src/utils/ResourceManager.ts","../src/utils/CircularBuffer.ts","../src/Battle/interact.ts","../src/Battle/screenshot.ts","../src/Battle/expect.ts","../src/Battle/log.ts","../src/Battle/cleanup.ts","../src/Battle/run.ts","../src/Battle/resize.ts","../src/Battle/sendKey.ts","../src/Battle/wait.ts","../src/Battle/write.ts","../src/Battle/getCursor.ts"],"names":[],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBnC,QAAA;AAAA;AAAA;AAGmB,2BAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","file":"/home/x/core/projects/battle/dist/chunk-AW3SAVB6.cjs","sourcesContent":[null,"/**\n * Battle Test Framework Core\n * Universal terminal testing with real PTY emulation\n */\n\nimport { constructor } from './constructor.js'\nimport { spawn } from './spawn.js'\nimport { interact } from './interact.js'\nimport { screenshot } from './screenshot.js'\nimport { expect, expectVisualChange, sendKeyAndDetectResponse } from './expect.js'\nimport { log } from './log.js'\nimport { cleanup } from './cleanup.js'\nimport { run } from './run.js'\nimport { resize } from './resize.js'\nimport { sendKey } from './sendKey.js'\nimport { wait } from './wait.js'\nimport { write } from './write.js'\nimport { getCursor } from './getCursor.js'\nimport { Replay } from '../Replay/index.js'\nimport type { BattleOptions, TestResult, InteractionHandler } from '../types/index.js'\n\nexport class Battle {\n    options!: BattleOptions\n    pty: any\n    output!: string\n    screenshots!: string[]\n    logs!: string[]\n    startTime!: number\n    replay!: Replay\n    \n    constructor(options: BattleOptions = {}) {\n        constructor.call(this, options)\n    }\n    \n    async spawn(command: string, args?: string[]) {\n        return spawn.call(this, command, args)\n    }\n    \n    interact(handler: InteractionHandler) {\n        return interact.call(this, handler)\n    }\n    \n    screenshot(name?: string) {\n        return screenshot.call(this, name)\n    }\n    \n    async expect(pattern: string | RegExp, timeout?: number) {\n        return expect.call(this, pattern, timeout)\n    }\n    \n    async expectVisualChange(timeout?: number) {\n        return expectVisualChange.call(this, timeout)\n    }\n    \n    async sendKeyAndDetectResponse(key: string, timeout?: number) {\n        return sendKeyAndDetectResponse.call(this, key, timeout)\n    }\n    \n    log(level: string, message: string) {\n        return log.call(this, level, message)\n    }\n    \n    cleanup() {\n        return cleanup.call(this)\n    }\n    \n    async run(testFn: (battle: Battle) => Promise<void>): Promise<TestResult> {\n        return run.call(this, testFn)\n    }\n    \n    resize(cols: number, rows: number) {\n        return resize.call(this, cols, rows)\n    }\n    \n    sendKey(key: string) {\n        return sendKey.call(this, key)\n    }\n    \n    write(data: string) {\n        return write.call(this, data)\n    }\n    \n    wait(ms: number) {\n        return wait.call(this, ms)\n    }\n    \n    async getCursor() {\n        return getCursor.call(this)\n    }\n}\n\nexport default Battle","import { Replay } from '../Replay/index.js'\nimport { SecurityLevel } from '../security/SafeCommandMode.js'\nimport type { BattleOptions } from '../types/index.js'\n\nexport function constructor(this: any, options: BattleOptions = {}) {\n    // Store command and args if provided in constructor\n    this.command = options.command\n    this.args = options.args || []\n    \n    this.options = {\n        cols: 80,\n        rows: 24,\n        cwd: process.cwd(),\n        env: process.env,\n        timeout: 10000,\n        screenshotDir: './screenshots',\n        logDir: './logs',\n        verbose: false,\n        ...options\n    }\n    \n    // Security settings - can be overridden by user\n    this.securityLevel = options.securityLevel || SecurityLevel.BALANCED\n    this.safeCommandMode = options.safeCommandMode !== false // Default to true\n    \n    this.pty = null\n    this.output = ''\n    this.screenshots = []\n    this.logs = []\n    this.startTime = Date.now()\n    this.replay = new Replay()\n    \n    // Initialize replay metadata\n    this.replay.data.metadata.cols = this.options.cols\n    this.replay.data.metadata.rows = this.options.rows\n    this.replay.data.metadata.env = { ...this.options.env }\n}","/**\n * Battle Replay System\n * Records and replays terminal sessions like StarCraft replays\n */\n\nimport { constructor } from './constructor.js'\nimport { record } from './record.js'\nimport { save } from './save.js'\nimport { load } from './load.js'\nimport { play } from './play.js'\nimport { exportReplay } from './export.js'\nimport type { ReplayData, ReplayEvent } from '../types/index.js'\n\nexport class Replay {\n    data!: ReplayData\n    events!: ReplayEvent[]\n    startTime!: number\n    \n    constructor() {\n        constructor.call(this)\n    }\n    \n    record(event: ReplayEvent) {\n        return record.call(this, event)\n    }\n    \n    save(path: string) {\n        return save.call(this, path)\n    }\n    \n    load(path: string) {\n        return load.call(this, path)\n    }\n    \n    async play(options?: any) {\n        return play.call(this, options)\n    }\n    \n    export(format: 'json' | 'html') {\n        return exportReplay.call(this, format)\n    }\n}\n\nexport default Replay","export function constructor(this: any) {\n    this.events = []\n    this.startTime = Date.now()\n    this.data = {\n        version: '1.0.0',\n        timestamp: new Date().toISOString(),\n        duration: 0,\n        events: [],\n        metadata: {\n            cols: 80,\n            rows: 24,\n            command: '',\n            args: [],\n            env: {}\n        }\n    }\n}","import type { ReplayEvent } from '../types/index.js'\n\nexport function record(this: any, event: ReplayEvent): void {\n    // Add timestamp relative to start\n    const timestampedEvent = {\n        ...event,\n        timestamp: Date.now() - this.startTime\n    }\n    \n    this.events.push(timestampedEvent)\n}","import fs from 'fs'\nimport path from 'path'\n\nexport function save(this: any, filePath: string): string {\n    // Ensure directory exists\n    const dir = path.dirname(filePath)\n    if (!fs.existsSync(dir)) {\n        fs.mkdirSync(dir, { recursive: true })\n    }\n    \n    // Update data with final events and duration\n    this.data.events = this.events\n    this.data.duration = Date.now() - this.startTime\n    \n    // Save as compressed JSON\n    const json = JSON.stringify(this.data, null, 2)\n    fs.writeFileSync(filePath, json)\n    \n    return filePath\n}","import fs from 'fs'\nimport { ReplayValidator, PathSecurity } from '../security/index.js'\n\nexport function load(this: any, filePath: string): void {\n    // Validate file path for security\n    const pathValidation = PathSecurity.validatePath(filePath, process.cwd())\n    if (!pathValidation.valid) {\n        throw new Error(`Invalid replay file path: ${pathValidation.error}`)\n    }\n    \n    // Read file safely\n    let json: string\n    try {\n        json = fs.readFileSync(pathValidation.normalized!, 'utf-8')\n    } catch (error: any) {\n        if (error.code === 'ENOENT') {\n            throw new Error(`Replay file not found: ${filePath}`)\n        }\n        throw new Error(`Failed to read replay file: ${error.message}`)\n    }\n    \n    // Parse and validate JSON with security checks (prevents prototype pollution)\n    try {\n        this.data = ReplayValidator.parse(json)\n        this.events = this.data.events || []\n        \n        // Additional validation\n        if (!this.data.version || !this.data.events) {\n            throw new Error('Invalid replay file format')\n        }\n        \n        // Limit event count to prevent DoS\n        if (this.data.events.length > 100000) {\n            throw new Error('Replay file contains too many events (max 100000)')\n        }\n    } catch (error: any) {\n        throw new Error(`Invalid replay file: ${error.message}`)\n    }\n}","import { color as chalk } from '../utils/colors.js'\nimport { PlaybackEngine, formatTime, getEventDescription } from './PlaybackEngine.js'\n\nexport async function play(this: any, options: any = {}): Promise<void> {\n    // Capture replay data and events\n    const replayData = this.data\n    const events = this.events\n    const totalDuration = replayData.duration\n    const totalEvents = events.length\n    \n    // Create unified playback engine\n    const engine = new PlaybackEngine(events, totalDuration, {\n        speed: options.speed || 1.0,\n        verbose: options.verbose,\n        onEvent: (event, state) => executeEvent(event, state),\n        onStateChange: (state) => updateStatusLine(state),\n        onProgress: (progress, state) => updateStatusLine(state)\n    })\n    \n    // Initialize replay output buffer (no PTY needed for replay)\n    let replayOutput = ''\n    \n    // Clear screen and show initial UI\n    console.clear()\n    showHeader(replayData, totalDuration, totalEvents)\n    showControls()\n    updateStatusLine(engine.getState())\n    \n    // Setup terminal for raw input\n    if (process.stdin.isTTY) {\n        process.stdin.setRawMode(true)\n    }\n    process.stdin.resume()\n    \n    // Keyboard input handler\n    const handleKeypress = (chunk: Buffer) => {\n        const key = chunk.toString()\n        \n        switch (key) {\n            case ' ': // Space - Play/Pause\n                if (engine.isPlaying()) {\n                    engine.pause()\n                } else {\n                    engine.play()\n                }\n                break\n                \n            case 's': // Stop\n                engine.stop()\n                clearTerminal()\n                break\n                \n            case 'r': // Restart\n                engine.restart()\n                clearTerminal()\n                break\n                \n            case 'e': // End\n                engine.jumpToEnd()\n                break\n                \n            case '+': // Speed up\n            case '=':\n                engine.setSpeed(Math.min(50, engine.getSpeed() * 2))\n                break\n                \n            case '-': // Speed down\n                engine.setSpeed(Math.max(0.1, engine.getSpeed() / 2))\n                break\n                \n            case '0': // Pause (0x speed)\n                engine.setSpeed(0)\n                engine.pause()\n                break\n                \n            case '1': // 1x speed\n                engine.setSpeed(1)\n                break\n                \n            case '2': // 2x speed\n                engine.setSpeed(2)\n                break\n                \n            case '4': // 4x speed\n                engine.setSpeed(4)\n                break\n                \n            case '\\u001b': // Escape - quit\n            case 'q':\n                cleanup()\n                process.exit(0)\n                break\n                \n            case '\\u001b[C': // Right arrow - skip forward\n                engine.skipForward(1000)\n                break\n                \n            case '\\u001b[D': // Left arrow - skip backward\n                engine.skipBackward(1000)\n                break\n        }\n    }\n    \n    process.stdin.on('data', handleKeypress)\n    \n    // Event execution handler\n    function executeEvent(event: any, state: any) {\n        const eventDisplay = getEventDescription(event)\n        \n        // Show event in status area\n        process.stdout.write('\\u001b[s') // Save cursor\n        process.stdout.write('\\u001b[H')  // Go to top\n        process.stdout.write('\\u001b[12;1H') // Go to event line\n        process.stdout.write('\\u001b[K')     // Clear line\n        process.stdout.write(`📺 Event: ${chalk.cyan(eventDisplay)}`)\n        process.stdout.write('\\u001b[u')     // Restore cursor\n        \n        // Execute event logic\n        switch (event.type) {\n            case 'output':\n                // Display the output\n                process.stdout.write('\\u001b[14;1H') // Go to output area\n                process.stdout.write(event.data)\n                break\n                \n            case 'input':\n                // Just track input in the output buffer for replay\n                replayOutput += event.data\n                break\n                \n            case 'resize':\n                // Update terminal size display\n                process.stdout.write('\\u001b[s')\n                process.stdout.write('\\u001b[12;1H')\n                process.stdout.write('\\u001b[K')\n                process.stdout.write(`📐 Terminal resized to ${event.data.cols}x${event.data.rows}`)\n                process.stdout.write('\\u001b[u')\n                break\n                \n            case 'key':\n                // Visual indicator for key press\n                process.stdout.write('\\u001b[s')\n                process.stdout.write('\\u001b[13;1H')\n                process.stdout.write('\\u001b[K')\n                process.stdout.write(`⌨️  Key pressed: ${chalk.green(event.data)}`)\n                process.stdout.write('\\u001b[u')\n                break\n        }\n    }\n    \n    // Update status line\n    function updateStatusLine(state: any) {\n        const progress = engine.getProgress()\n        const progressBar = createProgressBar(progress)\n        \n        const currentTimeStr = formatTime(engine.getCurrentTime())\n        const totalTimeStr = formatTime(totalDuration)\n        \n        const statusIcon = state.isPlaying ? '▶️' : (state.isPaused ? '⏸️' : '⏹️')\n        const speedDisplay = state.speed === 0 ? '⏸️ 0×' : `🚀 ${state.speed}×`\n        \n        // Save cursor, go to status line, update, restore cursor\n        process.stdout.write('\\u001b[s')   // Save cursor\n        process.stdout.write('\\u001b[10;1H') // Go to line 10\n        process.stdout.write('\\u001b[K')     // Clear line\n        \n        process.stdout.write(\n            `${statusIcon} ${progressBar} ${chalk.yellow(currentTimeStr)}/${chalk.yellow(totalTimeStr)} ${speedDisplay} ` +\n            `[${state.currentEventIndex}/${totalEvents}]`\n        )\n        \n        process.stdout.write('\\u001b[u')   // Restore cursor\n    }\n    \n    function clearTerminal() {\n        console.clear()\n        showHeader(replayData, totalDuration, totalEvents)\n        showControls()\n        updateStatusLine(engine.getState())\n    }\n    \n    function cleanup() {\n        engine.destroy()\n        // No PTY to kill\n        \n        if (process.stdin.isTTY) {\n            process.stdin.setRawMode(false)\n        }\n        process.stdin.pause()\n        \n        console.clear()\n        console.log(chalk.green('Battle Replay Player - Goodbye!'))\n    }\n    \n    // Handle process termination\n    process.on('SIGINT', cleanup)\n    process.on('SIGTERM', cleanup)\n    \n    // Auto-start playback if not in manual mode\n    if (!options.manual) {\n        engine.play()\n    }\n    \n    // Keep the process alive\n    return new Promise((resolve) => {\n        const checkComplete = () => {\n            const state = engine.getState()\n            if (!state.isPlaying && state.currentEventIndex >= totalEvents) {\n                cleanup()\n                resolve()\n            } else {\n                setTimeout(checkComplete, 100)\n            }\n        }\n        \n        if (options.manual) {\n            // In manual mode, never auto-exit\n            // User must press Q or ESC\n        } else {\n            checkComplete()\n        }\n    })\n}\n\nfunction showHeader(data: any, duration: number, totalEvents: number) {\n    console.log(chalk.blue.bold('🎬 Battle Replay Player - YouTube Style Controls'))\n    console.log(chalk.gray(`📼 File: ${data.version} | 📅 ${data.timestamp}`))\n    console.log(chalk.gray(`⏱️  Duration: ${formatTime(duration)} | 🎯 Events: ${totalEvents}`))\n    console.log('')\n}\n\nfunction showControls() {\n    console.log(chalk.cyan.bold('🎮 CONTROLS:'))\n    console.log(chalk.white('  SPACE') + chalk.gray(' - Play/Pause  ') + \n              chalk.white('S') + chalk.gray(' - Stop  ') + \n              chalk.white('R') + chalk.gray(' - Restart  ') + \n              chalk.white('E') + chalk.gray(' - End'))\n    console.log(chalk.white('  +/-  ') + chalk.gray(' - Speed Up/Down  ') + \n              chalk.white('0-4') + chalk.gray(' - Speed Presets  ') + \n              chalk.white('←→') + chalk.gray(' - Skip'))\n    console.log(chalk.white('  Q/ESC') + chalk.gray(' - Quit'))\n    console.log('')\n}\n\nfunction createProgressBar(percent: number, width: number = 40): string {\n    const filled = Math.min(Math.max(Math.round((percent / 100) * width), 0), width)\n    const empty = Math.max(width - filled, 0)\n    \n    const filledBar = '█'.repeat(filled)\n    const emptyBar = '░'.repeat(empty)\n    \n    return chalk.red(filledBar) + chalk.gray(emptyBar) + ` ${percent.toFixed(1)}%`\n}","/**\n * Color utilities that work with both ESM and CJS builds\n * This avoids the chalk v5 ESM-only issue\n */\n\n// Simple ANSI color codes\nconst colors = {\n  reset: '\\x1b[0m',\n  red: '\\x1b[31m',\n  green: '\\x1b[32m',\n  yellow: '\\x1b[33m',\n  blue: '\\x1b[34m',\n  magenta: '\\x1b[35m',\n  cyan: '\\x1b[36m',\n  gray: '\\x1b[90m',\n  bgRed: '\\x1b[41m',\n  bgGreen: '\\x1b[42m',\n  bgYellow: '\\x1b[43m',\n  bgBlue: '\\x1b[44m',\n  bold: '\\x1b[1m',\n  dim: '\\x1b[2m',\n}\n\n// Helper to create chainable color functions\nconst createColorFunction = (colorCode: string) => {\n  const fn = (text: string) => `${colorCode}${text}${colors.reset}`\n  // Add chainable properties\n  fn.bold = (text: string) => `${colorCode}${colors.bold}${text}${colors.reset}`\n  fn.dim = (text: string) => `${colorCode}${colors.dim}${text}${colors.reset}`\n  return fn\n}\n\n// Color functions that match chalk's API with chaining support\nexport const color = {\n  red: createColorFunction(colors.red),\n  green: createColorFunction(colors.green),\n  yellow: createColorFunction(colors.yellow),\n  blue: createColorFunction(colors.blue),\n  magenta: createColorFunction(colors.magenta),\n  cyan: createColorFunction(colors.cyan),\n  gray: createColorFunction(colors.gray),\n  bold: (text: string) => `${colors.bold}${text}${colors.reset}`,\n  dim: (text: string) => `${colors.dim}${text}${colors.reset}`,\n  bgRed: (text: string) => `${colors.bgRed}${text}${colors.reset}`,\n  bgGreen: (text: string) => `${colors.bgGreen}${text}${colors.reset}`,\n  bgYellow: (text: string) => `${colors.bgYellow}${text}${colors.reset}`,\n  bgBlue: (text: string) => `${colors.bgBlue}${text}${colors.reset}`,\n  white: (text: string) => text, // White is default\n}\n\n// Try to use chalk if available, otherwise use our simple implementation\nlet chalk: typeof color = color\n\ntry {\n  // Try dynamic import for ESM\n  const loadChalk = async () => {\n    try {\n      const chalkModule = await import('chalk')\n      chalk = chalkModule.default || chalkModule\n    } catch {\n      // Fall back to our simple implementation\n      chalk = color\n    }\n  }\n  \n  // Don't block on loading chalk\n  loadChalk().catch(() => {\n    // Ignore errors, use fallback\n  })\n} catch {\n  // Use our simple implementation\n  chalk = color\n}\n\n// Export both for compatibility\nexport default chalk\nexport { chalk }","/**\n * Unified Playback Engine for Battle Replay System\n * Provides consistent playback logic for both terminal and HTML players\n */\n\nexport interface PlaybackState {\n    isPlaying: boolean\n    isPaused: boolean\n    speed: number\n    currentEventIndex: number\n    currentTime: number\n    startTime: number\n    lastPlayTime: number\n}\n\nexport interface PlaybackOptions {\n    speed?: number\n    verbose?: boolean\n    onEvent?: (event: any, state: PlaybackState) => void\n    onStateChange?: (state: PlaybackState) => void\n    onProgress?: (progress: number, state: PlaybackState) => void\n}\n\nexport class PlaybackEngine {\n    private events: any[]\n    private duration: number\n    private state: PlaybackState\n    private options: PlaybackOptions\n    private animationFrame: any = null\n    private lastFrameTime: number = 0\n\n    constructor(events: any[], duration: number, options: PlaybackOptions = {}) {\n        this.events = events\n        this.duration = duration\n        this.options = {\n            speed: 1.0,\n            verbose: false,\n            ...options\n        }\n        \n        this.state = {\n            isPlaying: false,\n            isPaused: false,\n            speed: this.options.speed || 1.0,\n            currentEventIndex: 0,\n            currentTime: 0,\n            startTime: 0,\n            lastPlayTime: 0\n        }\n    }\n\n    // Core playback controls\n    play(): void {\n        if (this.state.isPlaying) return\n        \n        this.state.isPlaying = true\n        this.state.isPaused = false\n        this.state.startTime = Date.now() - this.state.currentTime\n        this.state.lastPlayTime = Date.now()\n        \n        this.notifyStateChange()\n        this.scheduleNextFrame()\n    }\n\n    pause(): void {\n        if (!this.state.isPlaying) return\n        \n        this.state.isPlaying = false\n        this.state.isPaused = true\n        \n        if (this.animationFrame) {\n            // Use clearTimeout for both browser and Node.js environments\n            clearTimeout(this.animationFrame as any)\n            this.animationFrame = null\n        }\n        \n        this.notifyStateChange()\n    }\n\n    stop(): void {\n        this.pause()\n        this.state.currentEventIndex = 0\n        this.state.currentTime = 0\n        this.state.isPaused = false\n        this.notifyStateChange()\n        this.notifyProgress()\n    }\n\n    restart(): void {\n        this.stop()\n        this.play()\n    }\n\n    // Speed control\n    setSpeed(speed: number): void {\n        this.state.speed = Math.max(0, Math.min(50, speed))\n        this.notifyStateChange()\n    }\n\n    getSpeed(): number {\n        return this.state.speed\n    }\n\n    // Seeking controls\n    seek(timeMs: number): void {\n        const targetTime = Math.max(0, Math.min(this.duration, timeMs))\n        this.state.currentTime = targetTime\n        \n        // Find the appropriate event index\n        this.state.currentEventIndex = 0\n        for (let i = 0; i < this.events.length; i++) {\n            if (this.events[i].timestamp > targetTime) {\n                break\n            }\n            this.state.currentEventIndex = i\n        }\n        \n        // Process all events up to this point\n        for (let i = 0; i < this.state.currentEventIndex; i++) {\n            this.notifyEvent(this.events[i])\n        }\n        \n        this.notifyStateChange()\n        this.notifyProgress()\n    }\n\n    seekToPercent(percent: number): void {\n        const targetTime = (percent / 100) * this.duration\n        this.seek(targetTime)\n    }\n\n    skipForward(ms: number = 1000): void {\n        this.seek(this.state.currentTime + ms)\n    }\n\n    skipBackward(ms: number = 1000): void {\n        this.seek(this.state.currentTime - ms)\n    }\n\n    jumpToStart(): void {\n        this.seek(0)\n    }\n\n    jumpToEnd(): void {\n        this.seek(this.duration)\n        this.state.currentEventIndex = this.events.length\n    }\n\n    // State getters\n    getState(): PlaybackState {\n        return { ...this.state }\n    }\n\n    getCurrentTime(): number {\n        return this.state.currentTime\n    }\n\n    getDuration(): number {\n        return this.duration\n    }\n\n    getProgress(): number {\n        return this.duration > 0 ? (this.state.currentTime / this.duration) * 100 : 0\n    }\n\n    getCurrentEventIndex(): number {\n        return this.state.currentEventIndex\n    }\n\n    getTotalEvents(): number {\n        return this.events.length\n    }\n\n    isPlaying(): boolean {\n        return this.state.isPlaying\n    }\n\n    isPaused(): boolean {\n        return this.state.isPaused\n    }\n\n    // Private methods for animation and event processing\n    private scheduleNextFrame(): void {\n        if (!this.state.isPlaying) return\n        \n        const now = Date.now()\n        const deltaTime = now - this.state.lastPlayTime\n        this.state.lastPlayTime = now\n        \n        // Update current time based on speed\n        if (this.state.speed > 0) {\n            this.state.currentTime += deltaTime * this.state.speed\n            \n            // Process events up to current time\n            while (this.state.currentEventIndex < this.events.length) {\n                const event = this.events[this.state.currentEventIndex]\n                if (event.timestamp > this.state.currentTime) {\n                    break\n                }\n                \n                this.notifyEvent(event)\n                this.state.currentEventIndex++\n            }\n            \n            this.notifyProgress()\n        }\n        \n        // Check if playback is complete\n        if (this.state.currentTime >= this.duration || \n            this.state.currentEventIndex >= this.events.length) {\n            this.state.isPlaying = false\n            this.state.currentTime = this.duration\n            this.notifyStateChange()\n            this.notifyProgress()\n            return\n        }\n        \n        // Schedule next frame - use consistent setTimeout for all environments\n        // 16ms = ~60fps, works in both Node.js and browser\n        this.animationFrame = setTimeout(() => this.scheduleNextFrame(), 16) as any\n    }\n\n    private notifyEvent(event: any): void {\n        if (this.options.onEvent) {\n            this.options.onEvent(event, this.getState())\n        }\n    }\n\n    private notifyStateChange(): void {\n        if (this.options.onStateChange) {\n            this.options.onStateChange(this.getState())\n        }\n    }\n\n    private notifyProgress(): void {\n        if (this.options.onProgress) {\n            this.options.onProgress(this.getProgress(), this.getState())\n        }\n    }\n\n    // Cleanup\n    destroy(): void {\n        this.stop()\n        this.options = {}\n    }\n}\n\n// Helper function to format time\nexport function formatTime(ms: number): string {\n    const totalSeconds = Math.floor(ms / 1000)\n    const minutes = Math.floor(totalSeconds / 60)\n    const seconds = totalSeconds % 60\n    return `${minutes}:${seconds.toString().padStart(2, '0')}`\n}\n\n// Helper function to get event description\nexport function getEventDescription(event: any): string {\n    switch (event.type) {\n        case 'spawn':\n            return `SPAWN ${event.data.command} ${event.data.args?.join(' ') || ''}`\n        case 'output':\n            return `OUTPUT (${event.data.length} bytes)`\n        case 'input':\n            return `INPUT ${JSON.stringify(event.data)}`\n        case 'key':\n            return `KEY ${event.data}`\n        case 'resize':\n            return `RESIZE ${event.data.cols}x${event.data.rows}`\n        case 'screenshot':\n            return `SCREENSHOT ${event.data.name}`\n        case 'expect':\n            return `EXPECT ${event.data.pattern || event.data}`\n        case 'exit':\n            return `EXIT code ${event.data}`\n        default:\n            return event.type.toUpperCase()\n    }\n}","import { getPlaybackEngineSource } from './getPlaybackEngineSource.js'\n\nexport function exportReplay(this: any, format: 'json' | 'html'): string {\n    if (format === 'json') {\n        return JSON.stringify(this.data, null, 2)\n    }\n    \n    if (format === 'html') {\n        // Generate enhanced HTML player with unified playback engine\n        return `<!DOCTYPE html>\n<html>\n<head>\n    <title>Battle Replay - ${this.data.timestamp}</title>\n    <style>\n        body {\n            background: #1e1e1e;\n            color: #d4d4d4;\n            font-family: 'Cascadia Code', 'Courier New', monospace;\n            padding: 20px;\n            margin: 0;\n        }\n        \n        .header {\n            margin-bottom: 20px;\n            padding: 15px;\n            background: #2d2d30;\n            border-radius: 8px;\n        }\n        \n        .header h1 {\n            margin: 0 0 10px 0;\n            color: #007acc;\n        }\n        \n        .metadata {\n            display: flex;\n            gap: 20px;\n            font-size: 14px;\n            color: #cccccc;\n        }\n        \n        #terminal {\n            background: #000;\n            padding: 15px;\n            border-radius: 8px;\n            min-height: 400px;\n            max-height: 600px;\n            white-space: pre-wrap;\n            overflow-y: auto;\n            font-family: 'Cascadia Code', 'SF Mono', 'Monaco', 'Inconsolata', 'Consolas', monospace;\n            font-size: 14px;\n            line-height: 1.4;\n            border: 1px solid #3c3c3c;\n        }\n        \n        .controls-container {\n            background: #2d2d30;\n            padding: 15px;\n            border-radius: 8px;\n            margin: 20px 0;\n        }\n        \n        .main-controls {\n            display: flex;\n            align-items: center;\n            gap: 10px;\n            margin-bottom: 15px;\n        }\n        \n        .control-btn {\n            background: #007acc;\n            color: white;\n            border: none;\n            padding: 10px 15px;\n            border-radius: 5px;\n            cursor: pointer;\n            font-size: 14px;\n            font-weight: 500;\n            transition: background 0.2s;\n            min-width: 60px;\n        }\n        \n        .control-btn:hover {\n            background: #005a9e;\n        }\n        \n        .control-btn:active {\n            background: #004578;\n        }\n        \n        .control-btn:disabled {\n            background: #555;\n            cursor: not-allowed;\n        }\n        \n        .secondary-btn {\n            background: #6c757d;\n            padding: 8px 12px;\n            font-size: 12px;\n        }\n        \n        .secondary-btn:hover {\n            background: #545b62;\n        }\n        \n        .progress-container {\n            display: flex;\n            align-items: center;\n            gap: 15px;\n            margin-bottom: 15px;\n        }\n        \n        .progress-bar {\n            flex: 1;\n            height: 8px;\n            background: #444;\n            border-radius: 4px;\n            position: relative;\n            cursor: pointer;\n        }\n        \n        .progress-fill {\n            height: 100%;\n            background: #007acc;\n            border-radius: 4px;\n            width: 0%;\n            transition: width 0.1s linear;\n        }\n        \n        .progress-handle {\n            position: absolute;\n            top: 50%;\n            transform: translate(-50%, -50%);\n            width: 16px;\n            height: 16px;\n            background: #007acc;\n            border-radius: 50%;\n            cursor: grab;\n            opacity: 0;\n            transition: opacity 0.2s;\n        }\n        \n        .progress-bar:hover .progress-handle {\n            opacity: 1;\n        }\n        \n        .progress-handle:active {\n            cursor: grabbing;\n        }\n        \n        .time-display {\n            font-family: 'Cascadia Code', monospace;\n            font-size: 14px;\n            color: #cccccc;\n            min-width: 100px;\n        }\n        \n        .speed-controls {\n            display: flex;\n            align-items: center;\n            gap: 10px;\n            margin-bottom: 10px;\n        }\n        \n        .speed-preset {\n            background: #555;\n            border: 1px solid #666;\n            color: #ccc;\n            padding: 5px 10px;\n            border-radius: 3px;\n            cursor: pointer;\n            font-size: 12px;\n        }\n        \n        .speed-preset:hover {\n            background: #666;\n        }\n        \n        .speed-preset.active {\n            background: #007acc;\n            border-color: #007acc;\n            color: white;\n        }\n        \n        .speed-input-container {\n            display: flex;\n            align-items: center;\n            gap: 5px;\n        }\n        \n        #speedInput {\n            width: 60px;\n            padding: 5px;\n            background: #3c3c3c;\n            border: 1px solid #555;\n            border-radius: 3px;\n            color: #d4d4d4;\n            font-size: 12px;\n        }\n        \n        .event-log {\n            background: #252526;\n            border: 1px solid #3c3c3c;\n            border-radius: 8px;\n            padding: 15px;\n            margin-top: 20px;\n            max-height: 200px;\n            overflow-y: auto;\n            font-size: 12px;\n            font-family: 'Cascadia Code', monospace;\n        }\n        \n        .event {\n            padding: 2px 0;\n            border-bottom: 1px solid #3c3c3c;\n        }\n        \n        .event-spawn { color: #dcdcaa; }\n        .event-output { color: #9cdcfe; }\n        .event-input { color: #ce9178; }\n        .event-key { color: #b5cea8; }\n        .event-resize { color: #c586c0; }\n        .event-screenshot { color: #608b4e; }\n        .event-expect { color: #d7ba7d; }\n        .event-exit { color: #f48771; }\n        \n        .status-container {\n            display: flex;\n            align-items: center;\n            gap: 10px;\n            margin-top: 10px;\n            font-size: 12px;\n        }\n        \n        .status-dot {\n            width: 10px;\n            height: 10px;\n            border-radius: 50%;\n            background: #555;\n        }\n        \n        .status-dot.playing { background: #4ec9b0; animation: pulse 1s infinite; }\n        .status-dot.paused { background: #ffcc00; }\n        .status-dot.stopped { background: #f48771; }\n        \n        @keyframes pulse {\n            0%, 100% { opacity: 1; }\n            50% { opacity: 0.5; }\n        }\n        \n        #eventCounter {\n            margin-left: auto;\n            color: #808080;\n        }\n        \n        /* Keyboard shortcuts hint */\n        .shortcuts {\n            margin-top: 15px;\n            padding: 10px;\n            background: #1e1e1e;\n            border-radius: 5px;\n            font-size: 11px;\n            color: #808080;\n        }\n        \n        .shortcuts-title {\n            font-weight: bold;\n            margin-bottom: 5px;\n        }\n        \n        .shortcut {\n            display: inline-block;\n            margin-right: 15px;\n        }\n        \n        .shortcut-key {\n            background: #3c3c3c;\n            padding: 2px 5px;\n            border-radius: 3px;\n            font-family: monospace;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"header\">\n        <h1>🎬 Battle Replay Player</h1>\n        <div class=\"metadata\">\n            <span>📅 ${this.data.timestamp}</span>\n            <span>⏱️ Duration: ${(this.data.duration / 1000).toFixed(2)}s</span>\n            <span>🎯 Events: ${this.data.events.length}</span>\n            <span>📐 Terminal: ${this.data.metadata.cols}x${this.data.metadata.rows}</span>\n            <span>💻 Command: ${this.data.metadata.command} ${(this.data.metadata.args || []).join(' ')}</span>\n        </div>\n    </div>\n    \n    <div class=\"controls-container\">\n        <div class=\"progress-container\">\n            <div id=\"progressBar\" class=\"progress-bar\" onclick=\"seekToPosition(event)\">\n                <div id=\"progressFill\" class=\"progress-fill\"></div>\n                <div id=\"progressHandle\" class=\"progress-handle\"></div>\n            </div>\n            <div id=\"timeDisplay\" class=\"time-display\">0:00 / 0:00</div>\n        </div>\n        \n        <div class=\"main-controls\">\n            <button id=\"playBtn\" class=\"control-btn\" onclick=\"togglePlay()\">▶ Play</button>\n            <button class=\"control-btn secondary-btn\" onclick=\"stop()\">⏹ Stop</button>\n            <button class=\"control-btn secondary-btn\" onclick=\"jumpToStart()\">⏮ Start</button>\n            <button class=\"control-btn secondary-btn\" onclick=\"jumpToEnd()\">⏭ End</button>\n        </div>\n        \n        <div class=\"speed-controls\">\n            <span>Speed:</span>\n            <button class=\"speed-preset\" onclick=\"setSpeed.call(this, 0.5)\">0.5×</button>\n            <button class=\"speed-preset active\" onclick=\"setSpeed.call(this, 1)\">1×</button>\n            <button class=\"speed-preset\" onclick=\"setSpeed.call(this, 2)\">2×</button>\n            <button class=\"speed-preset\" onclick=\"setSpeed.call(this, 4)\">4×</button>\n            <button class=\"speed-preset\" onclick=\"setSpeed.call(this, 8)\">8×</button>\n            <div class=\"speed-input-container\">\n                <span>Custom:</span>\n                <input type=\"number\" id=\"speedInput\" value=\"1.0\" min=\"0\" max=\"50\" step=\"0.1\" onchange=\"setCustomSpeed()\">\n                <span>×</span>\n            </div>\n        </div>\n        \n        <div class=\"status-container\">\n            <div id=\"statusDot\" class=\"status-dot stopped\"></div>\n            <span id=\"statusText\">Stopped</span>\n            <span id=\"eventCounter\">Event: 0/${this.data.events.length}</span>\n        </div>\n    </div>\n    \n    <div id=\"terminal\"></div>\n    \n    <div id=\"eventLog\" class=\"event-log\">\n        <strong>Event Log</strong> (most recent first)\n    </div>\n    \n    <div id=\"eventDisplay\"></div>\n    \n    <div class=\"shortcuts\">\n        <div class=\"shortcuts-title\">⌨️ Keyboard Shortcuts:</div>\n        <span class=\"shortcut\"><span class=\"shortcut-key\">Space</span> Play/Pause</span>\n        <span class=\"shortcut\"><span class=\"shortcut-key\">←/→</span> Skip -/+ 5s</span>\n        <span class=\"shortcut\"><span class=\"shortcut-key\">↑/↓</span> Speed Up/Down</span>\n        <span class=\"shortcut\"><span class=\"shortcut-key\">0-9</span> Jump to %</span>\n    </div>\n    \n    <script>\n        ${getPlaybackEngineSource()}\n        \n        // Replay data\n        const replayData = ${JSON.stringify(this.data)};\n        const events = replayData.events;\n        const duration = replayData.duration;\n        \n        // Create unified playback engine\n        const engine = new PlaybackEngine(events, duration, {\n            speed: 1.0,\n            onEvent: processEvent,\n            onStateChange: updateDisplay,\n            onProgress: updateDisplay\n        });\n        \n        // DOM elements\n        const terminal = document.getElementById('terminal');\n        const progressBar = document.getElementById('progressBar');\n        const progressFill = document.getElementById('progressFill');\n        const progressHandle = document.getElementById('progressHandle');\n        const timeDisplay = document.getElementById('timeDisplay');\n        const eventLog = document.getElementById('eventLog');\n        const eventDisplay = document.getElementById('eventDisplay');\n        const speedInput = document.getElementById('speedInput');\n        const playBtn = document.getElementById('playBtn');\n        const statusDot = document.getElementById('statusDot');\n        const statusText = document.getElementById('statusText');\n        const eventCounter = document.getElementById('eventCounter');\n        \n        // Initialize\n        updateDisplay();\n        \n        function togglePlay() {\n            if (engine.isPlaying()) {\n                engine.pause();\n            } else {\n                engine.play();\n            }\n        }\n        \n        function play() {\n            engine.play();\n        }\n        \n        function pause() {\n            engine.pause();\n        }\n        \n        function stop() {\n            engine.stop();\n            terminal.innerHTML = '';\n            eventLog.innerHTML = '<strong>Event Log</strong> (most recent first)';\n        }\n        \n        function jumpToStart() {\n            engine.jumpToStart();\n            terminal.innerHTML = '';\n            eventLog.innerHTML = '<strong>Event Log</strong> (most recent first)';\n        }\n        \n        function jumpToEnd() {\n            engine.jumpToEnd();\n            terminal.innerHTML = '';\n            eventLog.innerHTML = '<strong>Event Log</strong> (most recent first)';\n            \n            // Process all events to current point\n            for (let i = 0; i < engine.getCurrentEventIndex(); i++) {\n                processEvent(events[i], engine.getState());\n            }\n        }\n        \n        function setSpeed(speed) {\n            engine.setSpeed(speed);\n            speedInput.value = speed.toFixed(2);\n            \n            // Update active preset\n            document.querySelectorAll('.speed-preset').forEach(btn => {\n                btn.classList.remove('active');\n            });\n            this.classList.add('active');\n        }\n        \n        function setCustomSpeed() {\n            const speed = Math.max(0, parseFloat(speedInput.value));\n            engine.setSpeed(speed);\n            \n            // Update active preset\n            document.querySelectorAll('.speed-preset').forEach(btn => {\n                btn.classList.remove('active');\n            });\n        }\n        \n        function seekToPosition(event) {\n            const rect = progressBar.getBoundingClientRect();\n            const position = (event.clientX - rect.left) / rect.width;\n            const percent = position * 100;\n            \n            engine.seekToPercent(percent);\n            terminal.innerHTML = '';\n            eventLog.innerHTML = '<strong>Event Log</strong> (most recent first)';\n            \n            // Process events up to current point\n            for (let i = 0; i < engine.getCurrentEventIndex(); i++) {\n                processEvent(events[i], engine.getState());\n            }\n        }\n        \n        function updateStatus(state, text) {\n            statusDot.className = 'status-dot ' + state;\n            statusText.textContent = text;\n        }\n        \n        function updateDisplay() {\n            const state = engine.getState();\n            const progress = engine.getProgress();\n            const currentTime = engine.getCurrentTime();\n            \n            // Update progress bar\n            progressFill.style.width = progress + '%';\n            progressHandle.style.left = progress + '%';\n            \n            // Update time display\n            const currentMins = Math.floor(currentTime / 60000);\n            const currentSecs = Math.floor((currentTime % 60000) / 1000);\n            const totalMins = Math.floor(duration / 60000);\n            const totalSecs = Math.floor((duration % 60000) / 1000);\n            \n            timeDisplay.textContent = \n                currentMins + ':' + currentSecs.toString().padStart(2, '0') + \n                ' / ' + \n                totalMins + ':' + totalSecs.toString().padStart(2, '0');\n            \n            // Update event counter\n            eventCounter.textContent = 'Event: ' + state.currentEventIndex + '/' + events.length;\n            \n            // Update play button and status\n            if (state.isPlaying) {\n                updateStatus('playing', 'Playing');\n                playBtn.textContent = '⏸ Pause';\n            } else if (state.isPaused) {\n                updateStatus('paused', 'Paused');\n                playBtn.textContent = '▶ Play';\n            } else {\n                updateStatus('stopped', 'Stopped');\n                playBtn.textContent = '▶ Play';\n            }\n        }\n        \n        function processEvent(event, state) {\n            let logEntry = '<div class=\"event event-' + event.type + '\">';\n            logEntry += '[' + (event.timestamp / 1000).toFixed(2) + 's] ';\n            \n            switch(event.type) {\n                case 'spawn':\n                    logEntry += 'SPAWN: ' + event.data.command + ' ' + (event.data.args || []).join(' ');\n                    break;\n                    \n                case 'output':\n                    terminal.innerHTML += escapeHtml(event.data);\n                    // Auto-scroll terminal\n                    terminal.scrollTop = terminal.scrollHeight;\n                    logEntry += 'OUTPUT: ' + event.data.length + ' bytes';\n                    break;\n                    \n                case 'input':\n                    logEntry += 'INPUT: ' + JSON.stringify(event.data);\n                    break;\n                    \n                case 'key':\n                    logEntry += 'KEY: ' + event.data;\n                    break;\n                    \n                case 'resize':\n                    logEntry += 'RESIZE: ' + event.data.cols + 'x' + event.data.rows;\n                    break;\n                    \n                case 'screenshot':\n                    logEntry += 'SCREENSHOT: ' + event.data.name;\n                    break;\n                    \n                case 'expect':\n                    logEntry += 'EXPECT: ' + (event.data.pattern || event.data);\n                    break;\n                    \n                case 'exit':\n                    logEntry += 'EXIT: Code ' + event.data;\n                    break;\n                    \n                default:\n                    logEntry += event.type.toUpperCase();\n            }\n            \n            logEntry += '</div>';\n            \n            // Add to event log (prepend for most recent first)\n            const currentLog = eventLog.innerHTML;\n            const titleIndex = currentLog.indexOf('</strong>');\n            if (titleIndex !== -1) {\n                eventLog.innerHTML = currentLog.slice(0, titleIndex + 9) + \n                    logEntry + currentLog.slice(titleIndex + 9);\n            }\n            \n            // Keep log size reasonable\n            const logEvents = eventLog.querySelectorAll('.event');\n            if (logEvents.length > 100) {\n                logEvents[logEvents.length - 1].remove();\n            }\n        }\n        \n        function escapeHtml(text) {\n            const div = document.createElement('div');\n            div.textContent = text;\n            return div.innerHTML;\n        }\n        \n        // Keyboard shortcuts\n        document.addEventListener('keydown', (e) => {\n            switch(e.key) {\n                case ' ':\n                    e.preventDefault();\n                    togglePlay();\n                    break;\n                case 'ArrowLeft':\n                    engine.skipBackward(5000);\n                    break;\n                case 'ArrowRight':\n                    engine.skipForward(5000);\n                    break;\n                case 'ArrowUp':\n                    engine.setSpeed(Math.min(50, engine.getSpeed() * 2));\n                    speedInput.value = engine.getSpeed().toFixed(2);\n                    break;\n                case 'ArrowDown':\n                    engine.setSpeed(Math.max(0.1, engine.getSpeed() / 2));\n                    speedInput.value = engine.getSpeed().toFixed(2);\n                    break;\n                case '0':\n                case '1':\n                case '2':\n                case '3':\n                case '4':\n                case '5':\n                case '6':\n                case '7':\n                case '8':\n                case '9':\n                    const percent = parseInt(e.key) * 10;\n                    engine.seekToPercent(percent);\n                    break;\n            }\n        });\n    </script>\n</body>\n</html>`\n    }\n    \n    throw new Error(`Unsupported export format: ${format}`)\n}","// Returns the PlaybackEngine source code for embedding in HTML\nexport function getPlaybackEngineSource(): string {\n    return `\n// Unified PlaybackEngine for consistent replay behavior\nclass PlaybackEngine {\n    constructor(events, duration, options = {}) {\n        this.events = events;\n        this.duration = duration;\n        this.options = {\n            speed: 1.0,\n            verbose: false,\n            ...options\n        };\n        \n        this.state = {\n            isPlaying: false,\n            isPaused: false,\n            speed: this.options.speed || 1.0,\n            currentEventIndex: 0,\n            currentTime: 0,\n            startTime: 0,\n            lastPlayTime: 0\n        };\n        \n        this.animationFrame = null;\n        this.lastFrameTime = 0;\n    }\n\n    // Core playback controls\n    play() {\n        if (this.state.isPlaying) return;\n        \n        this.state.isPlaying = true;\n        this.state.isPaused = false;\n        this.state.startTime = Date.now() - this.state.currentTime;\n        this.state.lastPlayTime = Date.now();\n        \n        this.notifyStateChange();\n        this.scheduleNextFrame();\n    }\n\n    pause() {\n        if (!this.state.isPlaying) return;\n        \n        this.state.isPlaying = false;\n        this.state.isPaused = true;\n        \n        if (this.animationFrame) {\n            cancelAnimationFrame(this.animationFrame);\n            this.animationFrame = null;\n        }\n        \n        this.notifyStateChange();\n    }\n\n    stop() {\n        this.pause();\n        this.state.currentEventIndex = 0;\n        this.state.currentTime = 0;\n        this.state.isPaused = false;\n        this.notifyStateChange();\n        this.notifyProgress();\n    }\n\n    restart() {\n        this.stop();\n        this.play();\n    }\n\n    // Speed control\n    setSpeed(speed) {\n        this.state.speed = Math.max(0, Math.min(50, speed));\n        this.notifyStateChange();\n    }\n\n    getSpeed() {\n        return this.state.speed;\n    }\n\n    // Seeking controls\n    seek(timeMs) {\n        const targetTime = Math.max(0, Math.min(this.duration, timeMs));\n        this.state.currentTime = targetTime;\n        \n        // Find the appropriate event index\n        this.state.currentEventIndex = 0;\n        for (let i = 0; i < this.events.length; i++) {\n            if (this.events[i].timestamp > targetTime) {\n                break;\n            }\n            this.state.currentEventIndex = i;\n        }\n        \n        // Process all events up to this point\n        for (let i = 0; i < this.state.currentEventIndex; i++) {\n            this.notifyEvent(this.events[i]);\n        }\n        \n        this.notifyStateChange();\n        this.notifyProgress();\n    }\n\n    seekToPercent(percent) {\n        const targetTime = (percent / 100) * this.duration;\n        this.seek(targetTime);\n    }\n\n    skipForward(ms = 1000) {\n        this.seek(this.state.currentTime + ms);\n    }\n\n    skipBackward(ms = 1000) {\n        this.seek(this.state.currentTime - ms);\n    }\n\n    jumpToStart() {\n        this.seek(0);\n    }\n\n    jumpToEnd() {\n        this.seek(this.duration);\n        this.state.currentEventIndex = this.events.length;\n    }\n\n    // State getters\n    getState() {\n        return { ...this.state };\n    }\n\n    getCurrentTime() {\n        return this.state.currentTime;\n    }\n\n    getDuration() {\n        return this.duration;\n    }\n\n    getProgress() {\n        return this.duration > 0 ? (this.state.currentTime / this.duration) * 100 : 0;\n    }\n\n    getCurrentEventIndex() {\n        return this.state.currentEventIndex;\n    }\n\n    getTotalEvents() {\n        return this.events.length;\n    }\n\n    isPlaying() {\n        return this.state.isPlaying;\n    }\n\n    isPaused() {\n        return this.state.isPaused;\n    }\n\n    // Private methods for animation and event processing\n    scheduleNextFrame() {\n        if (!this.state.isPlaying) return;\n        \n        const now = Date.now();\n        const deltaTime = now - this.state.lastPlayTime;\n        this.state.lastPlayTime = now;\n        \n        // Update current time based on speed\n        if (this.state.speed > 0) {\n            this.state.currentTime += deltaTime * this.state.speed;\n            \n            // Process events up to current time\n            while (this.state.currentEventIndex < this.events.length) {\n                const event = this.events[this.state.currentEventIndex];\n                if (event.timestamp > this.state.currentTime) {\n                    break;\n                }\n                \n                this.notifyEvent(event);\n                this.state.currentEventIndex++;\n            }\n            \n            this.notifyProgress();\n        }\n        \n        // Check if playback is complete\n        if (this.state.currentTime >= this.duration || \n            this.state.currentEventIndex >= this.events.length) {\n            this.state.isPlaying = false;\n            this.state.currentTime = this.duration;\n            this.notifyStateChange();\n            this.notifyProgress();\n            return;\n        }\n        \n        // Schedule next frame\n        this.animationFrame = requestAnimationFrame(() => this.scheduleNextFrame());\n    }\n\n    notifyEvent(event) {\n        if (this.options.onEvent) {\n            this.options.onEvent(event, this.getState());\n        }\n    }\n\n    notifyStateChange() {\n        if (this.options.onStateChange) {\n            this.options.onStateChange(this.getState());\n        }\n    }\n\n    notifyProgress() {\n        if (this.options.onProgress) {\n            this.options.onProgress(this.getProgress(), this.getState());\n        }\n    }\n\n    // Cleanup\n    destroy() {\n        this.stop();\n        this.options = {};\n    }\n}\n`;\n}","import { createPTY } from '../PTY/index.js'\nimport { PTYLifecycleManager, EventListenerTracker } from '../utils/ResourceManager.js'\nimport { TerminalOutputBuffer } from '../utils/CircularBuffer.js'\nimport { CommandSanitizer, EnvSanitizer } from '../security/index.js'\nimport { SafeCommandMode } from '../security/SafeCommandMode.js'\n\nexport async function spawn(this: any, command?: string, args?: string[]): Promise<void> {\n    // Use constructor values if not provided\n    const cmd = command || this.command\n    const cmdArgs = args || this.args || []\n    \n    if (!cmd) {\n        throw new Error('No command specified. Provide command in constructor or spawn() call.')\n    }\n    \n    // Use flexible security based on user preference\n    let validation\n    let sanitizedArgs = cmdArgs\n    \n    if (this.safeCommandMode) {\n        // Use safe command mode with configurable security level\n        const fullCommand = cmdArgs.length > 0 ? `${cmd} ${cmdArgs.join(' ')}` : cmd\n        validation = SafeCommandMode.validateCommand(fullCommand, this.securityLevel)\n        \n        if (!validation.valid) {\n            throw new Error(`Security validation failed: ${validation.error}`)\n        }\n        \n        // Only sanitize args in strict mode\n        if (this.securityLevel === 'strict') {\n            sanitizedArgs = CommandSanitizer.sanitizeArgs(cmdArgs)\n        }\n    } else {\n        // Legacy mode - basic validation only\n        this.log('warn', 'Running in unsafe mode - security features disabled')\n        validation = { valid: true }\n    }\n    \n    this.log('info', `Spawning: ${cmd} ${sanitizedArgs.join(' ')}`)\n    \n    // Initialize lifecycle manager if not exists\n    if (!this.ptyLifecycle) {\n        this.ptyLifecycle = new PTYLifecycleManager()\n    }\n    \n    // Kill previous PTY with proper lifecycle management\n    await this.ptyLifecycle.kill()\n    \n    // Initialize circular buffer for output\n    if (!this.outputBuffer) {\n        this.outputBuffer = new TerminalOutputBuffer(1000, 10 * 1024 * 1024) // 10MB max\n    }\n    this.outputBuffer.clear()\n    this.output = '' // Keep for backward compatibility\n    \n    // Record spawn event\n    this.replay.record({\n        type: 'spawn',\n        timestamp: 0, // Will be set by record method\n        data: { command: cmd, args: sanitizedArgs }\n    })\n    \n    // Update replay metadata with sanitized env\n    this.replay.data.metadata.command = cmd\n    this.replay.data.metadata.args = sanitizedArgs\n    this.replay.data.metadata.cols = this.options.cols\n    this.replay.data.metadata.rows = this.options.rows\n    this.replay.data.metadata.cwd = this.options.cwd || process.cwd()\n    this.replay.data.metadata.env = EnvSanitizer.sanitize(this.options.env || {})\n    \n    // Create safe environment\n    const safeEnv = EnvSanitizer.createSafeEnv(this.options.env)\n    \n    const ptyOptions = {\n        name: 'xterm-256color',\n        cols: this.options.cols || 80,\n        rows: this.options.rows || 24,\n        cwd: this.options.cwd || process.cwd(),\n        env: safeEnv\n    }\n    \n    // Create PTY with lifecycle management\n    await this.ptyLifecycle.spawn(async () => {\n        return await createPTY(cmd, sanitizedArgs, ptyOptions)\n    })\n    \n    this.pty = this.ptyLifecycle.getPTY()\n    \n    // Initialize event tracker\n    if (!this.eventTracker) {\n        this.eventTracker = new EventListenerTracker()\n    }\n    \n    // Capture all output with circular buffer\n    const dataHandler = (data: string) => {\n        this.outputBuffer.append(data)\n        this.output = this.outputBuffer.toString() // Backward compatibility\n        \n        // Record output event\n        this.replay.record({\n            type: 'output',\n            timestamp: 0,\n            data\n        })\n        \n        if (this.options.verbose) {\n            process.stdout.write(data)\n        }\n        \n        this.log('output', data)\n    }\n    \n    this.pty.onData(dataHandler)\n    this.eventTracker.track(this.pty, 'data', dataHandler)\n    \n    // Handle exit with cleanup\n    const exitHandler = (exitData: any) => {\n        const code = typeof exitData === 'number' ? exitData : (exitData?.exitCode ?? 0)\n        this.log('info', `Process exited with code: ${code}`)\n        this.exitCode = code\n        \n        // Record exit event\n        this.replay.record({\n            type: 'exit',\n            timestamp: 0,\n            data: code\n        })\n        \n        // Clean up event listeners\n        if (this.eventTracker) {\n            this.eventTracker.removeAll(this.pty)\n        }\n    }\n    \n    this.pty.onExit(exitHandler)\n    this.eventTracker.track(this.pty, 'exit', exitHandler)\n}","/**\n * PTY Compatibility Layer\n * Provides real PTY support for both Node.js and Bun\n * \n * Node.js: Uses node-pty (kernel PTY via native bindings)\n * Bun: Uses @akaoio/ruspty (real PTY via Rust FFI with ARM64 support)\n */\n\nexport interface PTYOptions {\n    name?: string\n    cols?: number\n    rows?: number\n    cwd?: string\n    env?: Record<string, string>\n}\n\nexport interface IPTY {\n    onData: (callback: (data: string) => void) => void\n    onExit: (callback: (code: number) => void) => void\n    write: (data: string) => void\n    resize: (cols: number, rows: number) => void\n    kill: (signal?: string) => void\n    readonly pid: number | undefined\n    readonly killed: boolean\n}\n\n/**\n * Detect runtime environment\n */\nexport function getRuntime(): 'bun' | 'node' | 'unknown' {\n    // @ts-ignore - Bun global\n    if (typeof Bun !== 'undefined') return 'bun'\n    if (typeof process !== 'undefined' && process.versions?.node) return 'node'\n    return 'unknown'\n}\n\n/**\n * Create PTY instance based on runtime\n */\nexport async function createPTY(\n    command: string,\n    args: string[],\n    options: PTYOptions\n): Promise<IPTY> {\n    const runtime = getRuntime()\n    \n    if (runtime === 'bun') {\n        // Use @akaoio/ruspty for real PTY support in Bun\n        const { Ruspty } = await import('./Ruspty.js')\n        return new Ruspty(command, args, options)\n    } else if (runtime === 'node') {\n        // Use node-pty for Node.js\n        const { NodePTY } = await import('./NodePTY.js')\n        return new NodePTY(command, args, options)\n    } else {\n        throw new Error(`Unsupported runtime: ${runtime}`)\n    }\n}\n\nexport default createPTY","/**\n * Resource management system to prevent leaks and ensure proper cleanup\n */\n\nimport { EventEmitter } from 'events'\n\n/**\n * Tracks and manages disposable resources\n */\nexport class ResourceManager extends EventEmitter {\n    private resources: Map<string, IResource> = new Map()\n    private cleanupCallbacks: Map<string, () => Promise<void>> = new Map()\n    private disposed: boolean = false\n    private maxResources: number = 100\n    private registrationTimestamps: Map<string, number> = new Map()\n    private lastCleanup: number = 0\n    private readonly CLEANUP_INTERVAL = 5 * 60 * 1000 // 5 minutes\n    private readonly MAX_REGISTRATIONS_PER_MINUTE = 60\n    \n    /**\n     * Registers a resource for tracking with rate limiting\n     */\n    register(id: string, resource: IResource, cleanup?: () => Promise<void>): void {\n        if (this.disposed) {\n            throw new Error('ResourceManager has been disposed')\n        }\n        \n        // Rate limiting check\n        const now = Date.now()\n        this.cleanupOldTimestamps(now)\n        \n        const recentRegistrations = Array.from(this.registrationTimestamps.values())\n            .filter(timestamp => now - timestamp < 60000) // Last minute\n        \n        if (recentRegistrations.length >= this.MAX_REGISTRATIONS_PER_MINUTE) {\n            throw new Error(`Rate limit exceeded: too many resource registrations (max ${this.MAX_REGISTRATIONS_PER_MINUTE}/minute)`)\n        }\n        \n        if (this.resources.size >= this.maxResources) {\n            throw new Error(`Resource limit exceeded (max: ${this.maxResources})`)\n        }\n        \n        if (this.resources.has(id)) {\n            throw new Error(`Resource with id ${id} already registered`)\n        }\n        \n        this.resources.set(id, resource)\n        this.registrationTimestamps.set(id, now)\n        \n        if (cleanup) {\n            this.cleanupCallbacks.set(id, cleanup)\n        }\n        \n        this.emit('resource:registered', id)\n    }\n    \n    /**\n     * Unregisters and cleans up a resource\n     */\n    async unregister(id: string): Promise<void> {\n        const resource = this.resources.get(id)\n        if (!resource) {\n            return\n        }\n        \n        try {\n            // Call cleanup callback if exists\n            const cleanup = this.cleanupCallbacks.get(id)\n            if (cleanup) {\n                await cleanup()\n            }\n            \n            // Call resource's dispose method if available\n            if ('dispose' in resource && typeof resource.dispose === 'function') {\n                await resource.dispose()\n            }\n            \n            this.resources.delete(id)\n            this.cleanupCallbacks.delete(id)\n            this.registrationTimestamps.delete(id)\n            \n            this.emit('resource:unregistered', id)\n        } catch (error) {\n            this.emit('resource:cleanup-error', { id, error })\n            throw error\n        }\n    }\n    \n    /**\n     * Gets a registered resource\n     */\n    get<T extends IResource>(id: string): T | undefined {\n        return this.resources.get(id) as T\n    }\n    \n    /**\n     * Checks if a resource is registered\n     */\n    has(id: string): boolean {\n        return this.resources.has(id)\n    }\n    \n    /**\n     * Disposes all resources\n     */\n    async dispose(): Promise<void> {\n        if (this.disposed) {\n            return\n        }\n        \n        this.disposed = true\n        \n        const errors: Array<{ id: string; error: any }> = []\n        \n        // Clean up all resources in reverse order\n        const ids = Array.from(this.resources.keys()).reverse()\n        \n        for (const id of ids) {\n            try {\n                await this.unregister(id)\n            } catch (error) {\n                errors.push({ id, error })\n            }\n        }\n        \n        this.resources.clear()\n        this.cleanupCallbacks.clear()\n        this.registrationTimestamps.clear()\n        \n        if (errors.length > 0) {\n            this.emit('dispose:errors', errors)\n        }\n        \n        this.emit('disposed')\n        this.removeAllListeners()\n    }\n    \n    /**\n     * Cleans up old timestamps for rate limiting\n     */\n    private cleanupOldTimestamps(now: number): void {\n        if (now - this.lastCleanup > this.CLEANUP_INTERVAL) {\n            const cutoff = now - 60000 // Remove timestamps older than 1 minute\n            for (const [id, timestamp] of this.registrationTimestamps.entries()) {\n                if (timestamp < cutoff) {\n                    this.registrationTimestamps.delete(id)\n                }\n            }\n            this.lastCleanup = now\n        }\n    }\n    \n    /**\n     * Gets resource statistics\n     */\n    getStats(): ResourceStats {\n        return {\n            count: this.resources.size,\n            maxResources: this.maxResources,\n            disposed: this.disposed,\n            resources: Array.from(this.resources.keys()),\n        }\n    }\n}\n\n/**\n * Manages PTY lifecycle to prevent race conditions\n */\nexport class PTYLifecycleManager {\n    private state: PTYState = 'idle'\n    private pty: any = null\n    private exitPromise: Promise<void> | null = null\n    private exitResolve: (() => void) | null = null\n    private killTimeout: NodeJS.Timeout | null = null\n    \n    /**\n     * Spawns a new PTY, ensuring previous one is cleaned up\n     */\n    async spawn(ptyFactory: () => any): Promise<void> {\n        // Ensure previous PTY is fully cleaned up\n        await this.kill()\n        \n        this.state = 'spawning'\n        \n        try {\n            this.pty = await ptyFactory()\n            \n            // Set up exit promise\n            this.exitPromise = new Promise(resolve => {\n                this.exitResolve = resolve\n            })\n            \n            // Listen for exit\n            if (this.pty && this.pty.onExit) {\n                this.pty.onExit(() => {\n                    this.handleExit()\n                })\n            }\n            \n            this.state = 'running'\n        } catch (error) {\n            this.state = 'error'\n            throw error\n        }\n    }\n    \n    /**\n     * Kills the PTY with proper cleanup\n     */\n    async kill(signal: string = 'SIGTERM'): Promise<void> {\n        if (this.state === 'idle' || this.state === 'killed') {\n            return\n        }\n        \n        this.state = 'killing'\n        \n        if (this.pty) {\n            try {\n                // Send kill signal\n                if (this.pty.kill) {\n                    this.pty.kill(signal)\n                }\n                \n                // Wait for exit with timeout\n                await this.waitForExit(5000)\n                \n            } catch (error) {\n                // Force kill if graceful kill failed\n                if (this.pty.kill) {\n                    this.pty.kill('SIGKILL')\n                }\n            } finally {\n                this.cleanup()\n            }\n        }\n        \n        this.state = 'killed'\n    }\n    \n    /**\n     * Waits for PTY to exit with timeout\n     */\n    private async waitForExit(timeout: number): Promise<void> {\n        if (!this.exitPromise) {\n            return\n        }\n        \n        return Promise.race([\n            this.exitPromise,\n            new Promise<void>((_, reject) => {\n                this.killTimeout = setTimeout(() => {\n                    reject(new Error('PTY kill timeout'))\n                }, timeout)\n            })\n        ])\n    }\n    \n    /**\n     * Handles PTY exit\n     */\n    private handleExit(): void {\n        if (this.exitResolve) {\n            this.exitResolve()\n            this.exitResolve = null\n        }\n        \n        this.cleanup()\n        this.state = 'exited'\n    }\n    \n    /**\n     * Cleans up resources\n     */\n    private cleanup(): void {\n        if (this.killTimeout) {\n            clearTimeout(this.killTimeout)\n            this.killTimeout = null\n        }\n        \n        this.pty = null\n        this.exitPromise = null\n        this.exitResolve = null\n    }\n    \n    /**\n     * Gets current state\n     */\n    getState(): PTYState {\n        return this.state\n    }\n    \n    /**\n     * Gets PTY instance\n     */\n    getPTY(): any {\n        return this.pty\n    }\n}\n\n/**\n * Tracks event listeners for cleanup\n */\nexport class EventListenerTracker {\n    private listeners: Map<string, ListenerInfo[]> = new Map()\n    \n    /**\n     * Tracks an event listener\n     */\n    track(target: any, event: string, listener: Function): void {\n        const key = this.getKey(target)\n        \n        if (!this.listeners.has(key)) {\n            this.listeners.set(key, [])\n        }\n        \n        this.listeners.get(key)!.push({\n            target,\n            event,\n            listener,\n        })\n    }\n    \n    /**\n     * Removes all tracked listeners for a target\n     */\n    removeAll(target: any): void {\n        if (!target) {\n            return\n        }\n        \n        const key = this.getKey(target)\n        const listeners = this.listeners.get(key)\n        \n        if (!listeners) {\n            return\n        }\n        \n        for (const info of listeners) {\n            this.removeListener(info)\n        }\n        \n        this.listeners.delete(key)\n    }\n    \n    /**\n     * Removes all tracked listeners\n     */\n    clear(): void {\n        for (const [_, listeners] of this.listeners) {\n            for (const info of listeners) {\n                this.removeListener(info)\n            }\n        }\n        \n        this.listeners.clear()\n    }\n    \n    /**\n     * Removes a single listener\n     */\n    private removeListener(info: ListenerInfo): void {\n        try {\n            if (info.target.removeListener) {\n                info.target.removeListener(info.event, info.listener)\n            } else if (info.target.off) {\n                info.target.off(info.event, info.listener)\n            } else if (info.target.removeEventListener) {\n                info.target.removeEventListener(info.event, info.listener)\n            }\n        } catch (error) {\n            // Ignore errors during cleanup\n        }\n    }\n    \n    /**\n     * Gets a unique key for a target\n     */\n    private getKey(target: any): string {\n        if (target && target.id) {\n            return String(target.id)\n        }\n        \n        // Generate a unique key\n        const id = Math.random().toString(36).substring(2)\n        target.__tracker_id = id\n        return id\n    }\n    \n    /**\n     * Gets statistics\n     */\n    getStats(): { targets: number; totalListeners: number } {\n        let totalListeners = 0\n        \n        for (const listeners of this.listeners.values()) {\n            totalListeners += listeners.length\n        }\n        \n        return {\n            targets: this.listeners.size,\n            totalListeners,\n        }\n    }\n}\n\n// Interfaces\ninterface IResource {\n    dispose?(): Promise<void> | void\n}\n\ninterface ResourceStats {\n    count: number\n    maxResources: number\n    disposed: boolean\n    resources: string[]\n}\n\ntype PTYState = 'idle' | 'spawning' | 'running' | 'killing' | 'killed' | 'exited' | 'error'\n\ninterface ListenerInfo {\n    target: any\n    event: string\n    listener: Function\n}\n\nexport default {\n    ResourceManager,\n    PTYLifecycleManager,\n    EventListenerTracker,\n}","/**\n * Circular buffer implementation to prevent unbounded memory growth\n * Maintains a fixed-size buffer that overwrites old data when full\n */\nexport class CircularBuffer {\n    private buffer: string[]\n    private maxSize: number\n    private totalBytes: number\n    private maxBytes: number\n    private head: number\n    private tail: number\n    private count: number\n    \n    constructor(maxSize: number = 1000, maxBytes: number = 10 * 1024 * 1024) { // 10MB default\n        this.maxSize = maxSize\n        this.maxBytes = maxBytes\n        this.buffer = new Array(maxSize)\n        this.totalBytes = 0\n        this.head = 0\n        this.tail = 0\n        this.count = 0\n    }\n    \n    /**\n     * Appends data to the buffer\n     */\n    append(data: string): void {\n        if (!data) return\n        \n        const bytes = Buffer.byteLength(data, 'utf8')\n        \n        // If single item exceeds max bytes, truncate it\n        if (bytes > this.maxBytes) {\n            data = data.slice(0, Math.floor(this.maxBytes / 2))\n        }\n        \n        // Remove old items if we exceed byte limit\n        while (this.totalBytes + bytes > this.maxBytes && this.count > 0) {\n            this.removeOldest()\n        }\n        \n        // Add new item\n        this.buffer[this.tail] = data\n        this.tail = (this.tail + 1) % this.maxSize\n        this.totalBytes += bytes\n        \n        if (this.count < this.maxSize) {\n            this.count++\n        } else {\n            // Overwriting oldest item\n            const oldBytes = Buffer.byteLength(this.buffer[this.head], 'utf8')\n            this.totalBytes -= oldBytes\n            this.head = (this.head + 1) % this.maxSize\n        }\n    }\n    \n    /**\n     * Removes the oldest item from the buffer\n     */\n    private removeOldest(): void {\n        if (this.count === 0) return\n        \n        const oldData = this.buffer[this.head]\n        if (oldData) {\n            this.totalBytes -= Buffer.byteLength(oldData, 'utf8')\n        }\n        \n        this.buffer[this.head] = undefined as any\n        this.head = (this.head + 1) % this.maxSize\n        this.count--\n    }\n    \n    /**\n     * Gets all data as a single string\n     */\n    toString(): string {\n        if (this.count === 0) return ''\n        \n        const result: string[] = []\n        let index = this.head\n        \n        for (let i = 0; i < this.count; i++) {\n            if (this.buffer[index]) {\n                result.push(this.buffer[index])\n            }\n            index = (index + 1) % this.maxSize\n        }\n        \n        return result.join('')\n    }\n    \n    /**\n     * Gets the last N characters\n     */\n    getLastChars(n: number): string {\n        const full = this.toString()\n        return full.slice(-n)\n    }\n    \n    /**\n     * Searches for a pattern in the buffer\n     */\n    includes(pattern: string): boolean {\n        return this.toString().includes(pattern)\n    }\n    \n    /**\n     * Tests a regex pattern against the buffer\n     */\n    test(pattern: RegExp): boolean {\n        return pattern.test(this.toString())\n    }\n    \n    /**\n     * Clears the buffer\n     */\n    clear(): void {\n        this.buffer = new Array(this.maxSize)\n        this.totalBytes = 0\n        this.head = 0\n        this.tail = 0\n        this.count = 0\n    }\n    \n    /**\n     * Gets buffer statistics\n     */\n    getStats(): {\n        count: number\n        bytes: number\n        maxSize: number\n        maxBytes: number\n        usage: number\n    } {\n        return {\n            count: this.count,\n            bytes: this.totalBytes,\n            maxSize: this.maxSize,\n            maxBytes: this.maxBytes,\n            usage: this.totalBytes / this.maxBytes,\n        }\n    }\n    \n    /**\n     * Creates a snapshot of the buffer\n     */\n    snapshot(): string[] {\n        const result: string[] = []\n        let index = this.head\n        \n        for (let i = 0; i < this.count; i++) {\n            if (this.buffer[index]) {\n                result.push(this.buffer[index])\n            }\n            index = (index + 1) % this.maxSize\n        }\n        \n        return result\n    }\n}\n\n/**\n * Specialized circular buffer for terminal output\n */\nexport class TerminalOutputBuffer extends CircularBuffer {\n    private ansiRegex = /\\x1b\\[[0-9;]*[mGKJH]/g\n    \n    /**\n     * Gets clean output without ANSI codes\n     */\n    getCleanOutput(): string {\n        return this.toString().replace(this.ansiRegex, '')\n    }\n    \n    /**\n     * Searches for pattern in clean output\n     */\n    includesClean(pattern: string): boolean {\n        return this.getCleanOutput().includes(pattern)\n    }\n    \n    /**\n     * Tests regex against clean output\n     */\n    testClean(pattern: RegExp): boolean {\n        return pattern.test(this.getCleanOutput())\n    }\n    \n    /**\n     * Gets the last N lines\n     */\n    getLastLines(n: number): string[] {\n        const full = this.toString()\n        const lines = full.split('\\n')\n        return lines.slice(-n)\n    }\n}\n\nexport default CircularBuffer","import type { InteractionHandler } from '../types/index.js'\n\nexport async function interact(this: any, handler: InteractionHandler): Promise<void> {\n    return new Promise((resolve, reject) => {\n        const timeout = setTimeout(() => {\n            this.log('error', 'Interaction timeout')\n            reject(new Error('Interaction timeout'))\n        }, this.options.timeout)\n        \n        const cleanup = () => {\n            clearTimeout(timeout)\n            this.pty.onData(() => {}) // Remove listener\n        }\n        \n        this.pty.onData(async (data: string) => {\n            try {\n                const response = await handler(data, this.output)\n                \n                if (response === null) {\n                    // End interaction\n                    cleanup()\n                    resolve()\n                } else if (response) {\n                    // Send input\n                    this.log('input', response)\n                    this.pty.write(response)\n                }\n            } catch (error) {\n                cleanup()\n                reject(error)\n            }\n        })\n    })\n}","import fs from 'fs'\nimport path from 'path'\nimport { Terminal } from 'terminal-kit'\n\nexport function screenshot(this: any, name?: string): string {\n    const timestamp = Date.now()\n    const filename = name || `screenshot-${timestamp}.txt`\n    const filepath = path.join(this.options.screenshotDir, filename)\n    \n    // Ensure directory exists\n    fs.mkdirSync(this.options.screenshotDir, { recursive: true })\n    \n    // Parse ANSI and create text representation\n    const screen = parseTerminalOutput(this.output)\n    \n    // Save raw output with ANSI codes\n    fs.writeFileSync(filepath, this.output)\n    \n    // Save clean text version\n    const cleanPath = filepath.replace('.txt', '-clean.txt')\n    fs.writeFileSync(cleanPath, screen.clean)\n    \n    // Save HTML version for visual inspection\n    const htmlPath = filepath.replace('.txt', '.html')\n    fs.writeFileSync(htmlPath, screen.html)\n    \n    // Record screenshot event\n    this.replay.record({\n        type: 'screenshot',\n        timestamp: 0,\n        data: { name: filename, path: filepath }\n    })\n    \n    this.screenshots.push(filepath)\n    this.log('info', `Screenshot saved: ${filepath}`)\n    \n    return filepath\n}\n\nfunction parseTerminalOutput(output: string): { clean: string; html: string } {\n    // Remove ANSI escape codes for clean version\n    const clean = output.replace(/\\x1b\\[[0-9;]*[mGKJH]/g, '')\n    \n    // Convert ANSI to HTML for visual version\n    const html = `\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <style>\n        body { \n            background: #000; \n            color: #fff; \n            font-family: 'Courier New', monospace;\n            white-space: pre;\n            padding: 20px;\n        }\n        .ansi-black { color: #000; }\n        .ansi-red { color: #f00; }\n        .ansi-green { color: #0f0; }\n        .ansi-yellow { color: #ff0; }\n        .ansi-blue { color: #00f; }\n        .ansi-magenta { color: #f0f; }\n        .ansi-cyan { color: #0ff; }\n        .ansi-white { color: #fff; }\n    </style>\n</head>\n<body>${convertAnsiToHtml(output)}</body>\n</html>`\n    \n    return { clean, html }\n}\n\nfunction convertAnsiToHtml(text: string): string {\n    // Simple ANSI to HTML converter\n    return text\n        .replace(/&/g, '&amp;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;')\n        .replace(/\\x1b\\[31m/g, '<span class=\"ansi-red\">')\n        .replace(/\\x1b\\[32m/g, '<span class=\"ansi-green\">')\n        .replace(/\\x1b\\[33m/g, '<span class=\"ansi-yellow\">')\n        .replace(/\\x1b\\[34m/g, '<span class=\"ansi-blue\">')\n        .replace(/\\x1b\\[35m/g, '<span class=\"ansi-magenta\">')\n        .replace(/\\x1b\\[36m/g, '<span class=\"ansi-cyan\">')\n        .replace(/\\x1b\\[0m/g, '</span>')\n        .replace(/\\n/g, '<br>')\n}","export async function expect(this: any, pattern: string | RegExp, timeout: number = 2000): Promise<boolean> {\n    const startTime = Date.now()\n    \n    while (Date.now() - startTime < timeout) {\n        // Use outputBuffer if available, otherwise fall back to output\n        const output = this.outputBuffer ? this.outputBuffer.toString() : this.output\n        const cleanOutput = output.replace(/\\x1b\\[[0-9;]*[mGKJH]/g, '')\n        \n        let matches = false\n        if (typeof pattern === 'string') {\n            matches = cleanOutput.includes(pattern)\n        } else {\n            matches = pattern.test(cleanOutput)\n        }\n        \n        if (matches) {\n            // Record expect event\n            this.replay.record({\n                type: 'expect',\n                timestamp: 0,\n                data: { pattern: pattern.toString(), matched: true }\n            })\n            \n            this.log('info', `Pattern matched: ${pattern}`)\n            return true\n        }\n        \n        // Wait a bit before checking again\n        await new Promise(resolve => setTimeout(resolve, 50))\n    }\n    \n    // Pattern not found after timeout\n    this.log('error', `Expected pattern not found after ${timeout}ms: ${pattern}`)\n    this.screenshot('expect-failure')\n    throw new Error(`Expected pattern not found: ${pattern}`)\n}\n\n/**\n * Detect any visual change in the output - Enhanced for TUI testing\n * @param timeout - How long to wait for any change\n * @returns true if visual change detected, false if timeout\n */\nexport async function expectVisualChange(this: any, timeout: number = 1000): Promise<boolean> {\n    const initialOutput = this.output\n    const initialLength = initialOutput.length\n    const startTime = Date.now()\n    \n    // More aggressive polling for visual changes\n    while (Date.now() - startTime < timeout) {\n        // Check for any new output\n        if (this.output.length !== initialLength) {\n            this.log('info', 'Visual change detected: output length changed')\n            return true\n        }\n        \n        // Check for cursor position changes (common in TUI)\n        if (this.output !== initialOutput) {\n            this.log('info', 'Visual change detected: output content changed')\n            return true\n        }\n        \n        // Very fast polling for immediate changes\n        await new Promise(resolve => setTimeout(resolve, 10))\n    }\n    \n    this.log('warn', `No visual change detected within ${timeout}ms`)\n    return false\n}\n\n/**\n * Send key and detect immediate response - Optimized for TUI navigation\n * @param key - Key to send\n * @param timeout - How long to wait for response\n * @returns true if response detected, false if no response\n */\nexport async function sendKeyAndDetectResponse(this: any, key: string, timeout: number = 500): Promise<boolean> {\n    const initialOutput = this.output\n    const initialLength = initialOutput.length\n    \n    // Send the key\n    await this.sendKey(key)\n    \n    const startTime = Date.now()\n    \n    // Wait for any response with very short polling\n    while (Date.now() - startTime < timeout) {\n        if (this.output.length !== initialLength || this.output !== initialOutput) {\n            this.log('info', `Key response detected for: ${key}`)\n            return true\n        }\n        \n        // Extremely fast polling for immediate TUI updates\n        await new Promise(resolve => setTimeout(resolve, 5))\n    }\n    \n    this.log('warn', `No response detected for key: ${key}`)\n    return false\n}","import fs from 'fs'\nimport path from 'path'\n\nexport function log(this: any, level: string, message: string): void {\n    const timestamp = new Date().toISOString()\n    const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\\n`\n    \n    // Ensure log directory exists\n    if (!fs.existsSync(this.options.logDir)) {\n        fs.mkdirSync(this.options.logDir, { recursive: true })\n    }\n    \n    // Write to log file\n    const logFile = path.join(this.options.logDir, `battle-${this.startTime}.log`)\n    fs.appendFileSync(logFile, logEntry)\n    \n    // Store in memory\n    this.logs.push(logEntry)\n    \n    // Console output if verbose\n    if (this.options.verbose && level !== 'output') {\n        console.log(logEntry.trim())\n    }\n}","export function cleanup(this: any): void {\n    if (this.pty) {\n        this.log('info', 'Cleaning up PTY process')\n        try {\n            this.pty.kill()\n        } catch (error) {\n            this.log('warn', `Failed to kill PTY: ${error}`)\n        }\n        this.pty = null\n    }\n}","import path from 'path'\nimport type { TestResult } from '../types/index.js'\n\nexport async function run(this: any, testFn: (battle: any) => Promise<void>): Promise<TestResult> {\n    const startTime = Date.now()\n    let success = false\n    let error: Error | null = null\n    \n    try {\n        this.log('info', 'Starting test')\n        await testFn(this)\n        success = true\n        this.log('info', 'Test completed successfully')\n    } catch (err) {\n        error = err as Error\n        this.log('error', `Test failed: ${error.message}`)\n        this.screenshot('test-failure')\n    } finally {\n        this.cleanup()\n    }\n    \n    const duration = Date.now() - startTime\n    \n    // Save replay\n    let replayPath: string | undefined\n    try {\n        const replayFilename = `replay-${Date.now()}.json`\n        replayPath = path.join(this.options.logDir || './logs', replayFilename)\n        this.replay.save(replayPath)\n        this.log('info', `Replay saved: ${replayPath}`)\n    } catch (e) {\n        this.log('error', `Failed to save replay: ${e}`)\n    }\n    \n    const result: TestResult = {\n        success,\n        duration,\n        output: this.output,\n        screenshots: this.screenshots,\n        logs: this.logs,\n        error: error?.message || null,\n        replayPath\n    }\n    \n    return result\n}","/**\n * Resize the terminal viewport dynamically\n * Tests how applications handle terminal size changes\n */\n\nexport function resize(this: any, cols: number, rows: number): void {\n    if (!this.pty) {\n        // If no PTY yet, just update the options for when spawn is called\n        this.options.cols = cols\n        this.options.rows = rows\n        this.log('info', `Terminal size set to: ${cols}x${rows} (will apply on spawn)`)\n        return\n    }\n    \n    this.log('info', `Resizing terminal: ${cols}x${rows}`)\n    \n    // Record resize event\n    this.replay.record({\n        type: 'resize',\n        timestamp: 0,\n        data: { cols, rows }\n    })\n    \n    // Store previous size for comparison\n    const previousCols = this.options.cols\n    const previousRows = this.options.rows\n    \n    // Update options\n    this.options.cols = cols\n    this.options.rows = rows\n    \n    // Resize the PTY\n    this.pty.resize(cols, rows)\n    \n    // Take screenshot after resize\n    setTimeout(() => {\n        this.screenshot(`resize-${cols}x${rows}`)\n    }, 100)\n    \n    this.log('info', `Terminal resized from ${previousCols}x${previousRows} to ${cols}x${rows}`)\n}","/**\n * Send special key sequences to the terminal\n * Supports arrow keys, function keys, control sequences, etc.\n */\n\nexport function sendKey(this: any, key: string): void {\n    if (!this.pty) {\n        throw new Error('No PTY process to send keys to')\n    }\n    \n    const keyMap: Record<string, string> = {\n        // Arrow keys\n        'up': '\\x1b[A',\n        'down': '\\x1b[B',\n        'right': '\\x1b[C',\n        'left': '\\x1b[D',\n        \n        // Function keys\n        'f1': '\\x1bOP',\n        'f2': '\\x1bOQ',\n        'f3': '\\x1bOR',\n        'f4': '\\x1bOS',\n        'f5': '\\x1b[15~',\n        'f6': '\\x1b[17~',\n        'f7': '\\x1b[18~',\n        'f8': '\\x1b[19~',\n        'f9': '\\x1b[20~',\n        'f10': '\\x1b[21~',\n        'f11': '\\x1b[23~',\n        'f12': '\\x1b[24~',\n        \n        // Control keys\n        'enter': '\\r',\n        'return': '\\r',\n        'tab': '\\t',\n        'backspace': '\\x7f',\n        'delete': '\\x1b[3~',\n        'escape': '\\x1b',\n        'esc': '\\x1b',\n        'home': '\\x1b[H',\n        'end': '\\x1b[F',\n        'pageup': '\\x1b[5~',\n        'pagedown': '\\x1b[6~',\n        \n        // Control combinations\n        'ctrl+c': '\\x03',\n        'ctrl+d': '\\x04',\n        'ctrl+z': '\\x1a',\n        'ctrl+a': '\\x01',\n        'ctrl+e': '\\x05',\n        'ctrl+k': '\\x0b',\n        'ctrl+l': '\\x0c',\n        'ctrl+u': '\\x15',\n        'ctrl+w': '\\x17',\n        \n        // Alt combinations\n        'alt+b': '\\x1bb',\n        'alt+f': '\\x1bf',\n        'alt+d': '\\x1bd',\n        \n        // Special\n        'space': ' ',\n        'clear': '\\x1b[2J\\x1b[H'\n    }\n    \n    const sequence = keyMap[key.toLowerCase()] || key\n    \n    // Record key event\n    this.replay.record({\n        type: 'key',\n        timestamp: 0,\n        data: key\n    })\n    \n    // Record as input too for accurate stdin recording\n    this.replay.record({\n        type: 'input',\n        timestamp: 0,\n        data: sequence\n    })\n    \n    this.log('input', `Sending key: ${key} (${sequence.replace(/\\x1b/g, '^[').replace(/\\r/g, '\\\\r').replace(/\\n/g, '\\\\n')})`)\n    this.pty.write(sequence)\n}","/**\n * Wait for a specific amount of time\n * Useful for timing-sensitive terminal operations\n */\n\nexport function wait(this: any, ms: number): Promise<void> {\n    this.log('info', `Waiting ${ms}ms`)\n    return new Promise(resolve => setTimeout(resolve, ms))\n}","export function write(this: any, data: string): void {\n    if (!this.pty) {\n        throw new Error('No PTY process running. Call spawn() first.')\n    }\n    \n    if (this.pty.killed) {\n        throw new Error('PTY process has already exited')\n    }\n    \n    this.log('input', `Writing: ${JSON.stringify(data)}`)\n    \n    // Record input event\n    this.replay.record({\n        type: 'input',\n        timestamp: 0,\n        data\n    })\n    \n    // Write directly to PTY\n    this.pty.write(data)\n}","/**\n * Get cursor position from terminal\n * Useful for testing cursor movement and positioning\n */\n\nexport async function getCursor(this: any): Promise<{ x: number; y: number } | null> {\n    if (!this.pty) {\n        throw new Error('No PTY process')\n    }\n    \n    return new Promise((resolve) => {\n        let response = ''\n        const timeout = setTimeout(() => {\n            this.log('warn', 'Cursor position request timeout')\n            resolve(null)\n        }, 1000)\n        \n        const handler = (data: string) => {\n            response += data\n            \n            // Parse cursor position response: ESC[row;colR\n            const match = response.match(/\\x1b\\[(\\d+);(\\d+)R/)\n            if (match) {\n                clearTimeout(timeout)\n                this.pty.onData(() => {}) // Remove handler\n                \n                const position = {\n                    x: parseInt(match[2]) - 1,  // Convert to 0-based\n                    y: parseInt(match[1]) - 1\n                }\n                \n                this.log('info', `Cursor position: x=${position.x}, y=${position.y}`)\n                resolve(position)\n            }\n        }\n        \n        this.pty.onData(handler)\n        \n        // Request cursor position\n        this.log('debug', 'Requesting cursor position')\n        this.pty.write('\\x1b[6n')\n    })\n}"]}