episoda 0.2.35 → 0.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/daemon/daemon-process.js +213 -504
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../core/src/git-validator.ts","../../../core/src/git-parser.ts","../../../core/src/git-executor.ts","../../../core/src/version.ts","../../../core/src/websocket-client.ts","../../../core/src/auth.ts","../../../core/src/errors.ts","../../../core/src/index.ts","../../src/utils/port-check.ts","../../package.json","../../src/daemon/machine-id.ts","../../src/daemon/project-tracker.ts","../../src/daemon/daemon-manager.ts","../../src/ipc/ipc-server.ts","../../src/daemon/daemon-process.ts","../../src/utils/update-checker.ts","../../src/daemon/handlers/file-handlers.ts","../../src/daemon/handlers/exec-handler.ts","../../src/daemon/handlers/stale-commit-cleanup.ts","../../src/tunnel/cloudflared-manager.ts","../../src/tunnel/tunnel-manager.ts","../../src/tunnel/tunnel-api.ts","../../src/agent/claude-binary.ts","../../src/agent/agent-manager.ts","../../src/utils/dev-server.ts","../../src/utils/env-cache.ts","../../src/utils/env-setup.ts","../../src/utils/port-detect.ts","../../src/daemon/worktree-manager.ts","../../src/utils/worktree.ts","../../src/utils/port-allocator.ts","../../src/framework-detector.ts"],"sourcesContent":["/**\n * Git Input Validation Utilities\n *\n * Validates git command inputs to prevent injection and ensure safety.\n * Interface-agnostic: Returns boolean/error codes, not formatted messages.\n */\n\nimport { ErrorCode } from './command-protocol'\n\n/**\n * Validate branch name format\n * Git branch naming rules:\n * - Cannot start with - or /\n * - Cannot contain .. or @{ or \\\n * - Cannot end with .lock or /\n * - Cannot contain spaces or control characters\n * - Cannot be just @\n */\nexport function validateBranchName(branchName: string): { valid: boolean; error?: ErrorCode } {\n if (!branchName || branchName.trim().length === 0) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Check for invalid characters and patterns\n const invalidPatterns = [\n /^\\-/, // Starts with -\n /^\\//, // Starts with /\n /\\.\\./, // Contains ..\n /@\\{/, // Contains @{\n /\\\\/, // Contains backslash\n /\\.lock$/, // Ends with .lock\n /\\/$/, // Ends with /\n /\\s/, // Contains whitespace\n /[\\x00-\\x1F\\x7F]/, // Contains control characters\n /[\\*\\?\\[\\]~\\^:]/, // Contains special characters\n ]\n\n for (const pattern of invalidPatterns) {\n if (pattern.test(branchName)) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n }\n\n // Branch name cannot be just @\n if (branchName === '@') {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Maximum reasonable length (Git allows up to 255, but let's be conservative)\n if (branchName.length > 200) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n return { valid: true }\n}\n\n/**\n * Validate commit message\n * Basic validation to ensure message is not empty\n */\nexport function validateCommitMessage(message: string): { valid: boolean; error?: ErrorCode } {\n if (!message || message.trim().length === 0) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Reasonable length check\n if (message.length > 10000) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n return { valid: true }\n}\n\n/**\n * Validate file paths\n * Basic validation to prevent directory traversal\n */\nexport function validateFilePaths(files: string[]): { valid: boolean; error?: ErrorCode } {\n if (!files || files.length === 0) {\n return { valid: true }\n }\n\n for (const file of files) {\n // Check for null bytes (command injection)\n if (file.includes('\\0')) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Check for control characters\n if (/[\\x00-\\x1F\\x7F]/.test(file)) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Files should not be empty\n if (file.trim().length === 0) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Sanitize command arguments to prevent injection\n * Returns sanitized array of arguments\n */\nexport function sanitizeArgs(args: string[]): string[] {\n return args.map(arg => {\n // Remove null bytes\n return arg.replace(/\\0/g, '')\n })\n}\n","/**\n * Git Command Output Parser\n *\n * Parses git command output and error messages into structured data.\n * Interface-agnostic: Returns structured objects, not formatted strings.\n */\n\nimport { ErrorCode } from './command-protocol'\n\n/**\n * Parse git status output to extract uncommitted files\n */\nexport function parseGitStatus(output: string): {\n uncommittedFiles: string[]\n isClean: boolean\n currentBranch?: string\n} {\n const lines = output.split('\\n')\n const uncommittedFiles: string[] = []\n let currentBranch: string | undefined\n\n for (const line of lines) {\n // Porcelain format branch line: ## branch-name or ## HEAD (no branch)\n if (line.startsWith('## ')) {\n const branchInfo = line.substring(3)\n // Extract branch name (handle \"branch...origin/branch\" format)\n const branchMatch = branchInfo.match(/^([^\\s.]+)/)\n if (branchMatch && branchMatch[1]) {\n currentBranch = branchMatch[1] === 'HEAD' ? 'HEAD (detached)' : branchMatch[1]\n }\n continue\n }\n\n // Parse modified/added/deleted files\n // Porcelain format: \"XY filename\" where X is staged, Y is unstaged\n // Format: \" M file.txt\" or \"M file.txt\" or \"?? file.txt\" or \"MM file.txt\"\n if (line.length >= 3) {\n const status = line.substring(0, 2)\n const filePath = line.substring(3).trim()\n\n // Check if there's any status (not all spaces)\n if (status.trim().length > 0 && filePath.length > 0) {\n uncommittedFiles.push(filePath)\n }\n }\n }\n\n const isClean = uncommittedFiles.length === 0\n\n return { uncommittedFiles, isClean, currentBranch }\n}\n\n/**\n * Parse merge conflict output to extract conflicting files\n */\nexport function parseMergeConflicts(output: string): string[] {\n const lines = output.split('\\n')\n const conflictingFiles: string[] = []\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Look for conflict markers\n if (trimmed.startsWith('CONFLICT')) {\n // Format: \"CONFLICT (content): Merge conflict in file.txt\"\n const match = trimmed.match(/CONFLICT.*in (.+)$/)\n if (match && match[1]) {\n conflictingFiles.push(match[1])\n }\n }\n\n // Also check for \"both modified\" status\n if (trimmed.startsWith('UU ')) {\n conflictingFiles.push(trimmed.substring(3).trim())\n }\n }\n\n return conflictingFiles\n}\n\n/**\n * Map git error output to ErrorCode\n */\nexport function parseGitError(stderr: string, stdout: string, exitCode: number): ErrorCode {\n const combinedOutput = `${stderr}\\n${stdout}`.toLowerCase()\n\n // Check for git not installed\n if (combinedOutput.includes('git: command not found') ||\n combinedOutput.includes(\"'git' is not recognized\")) {\n return 'GIT_NOT_INSTALLED'\n }\n\n // Check for not a git repository\n if (combinedOutput.includes('not a git repository') ||\n combinedOutput.includes('not a git repo')) {\n return 'NOT_GIT_REPO'\n }\n\n // Check for merge conflicts\n if (combinedOutput.includes('conflict') ||\n combinedOutput.includes('merge conflict') ||\n exitCode === 1 && combinedOutput.includes('automatic merge failed')) {\n return 'MERGE_CONFLICT'\n }\n\n // Check for uncommitted changes\n if (combinedOutput.includes('please commit your changes') ||\n combinedOutput.includes('would be overwritten') ||\n combinedOutput.includes('cannot checkout') && combinedOutput.includes('files would be overwritten')) {\n return 'UNCOMMITTED_CHANGES'\n }\n\n // Check for authentication failures\n if (combinedOutput.includes('authentication failed') ||\n combinedOutput.includes('could not read username') ||\n combinedOutput.includes('permission denied') ||\n combinedOutput.includes('could not read password') ||\n combinedOutput.includes('fatal: authentication failed')) {\n return 'AUTH_FAILURE'\n }\n\n // Check for network errors\n if (combinedOutput.includes('could not resolve host') ||\n combinedOutput.includes('failed to connect') ||\n combinedOutput.includes('network is unreachable') ||\n combinedOutput.includes('connection timed out') ||\n combinedOutput.includes('could not read from remote')) {\n return 'NETWORK_ERROR'\n }\n\n // Check for branch not found\n if (combinedOutput.includes('did not match any file') ||\n combinedOutput.includes('branch') && combinedOutput.includes('not found') ||\n combinedOutput.includes('pathspec') && combinedOutput.includes('did not match')) {\n return 'BRANCH_NOT_FOUND'\n }\n\n // Check for branch already exists\n if (combinedOutput.includes('already exists') ||\n combinedOutput.includes('a branch named') && combinedOutput.includes('already exists')) {\n return 'BRANCH_ALREADY_EXISTS'\n }\n\n // Check for push rejected\n if (combinedOutput.includes('push rejected') ||\n combinedOutput.includes('failed to push') ||\n combinedOutput.includes('rejected') && combinedOutput.includes('non-fast-forward') ||\n combinedOutput.includes('updates were rejected')) {\n return 'PUSH_REJECTED'\n }\n\n // Check for timeout (exit code 124 is GNU timeout, 143 is SIGTERM)\n if (exitCode === 124 || exitCode === 143) {\n return 'COMMAND_TIMEOUT'\n }\n\n // Default to unknown error\n return 'UNKNOWN_ERROR'\n}\n\n/**\n * Extract branch name from git output\n */\nexport function extractBranchName(output: string): string | undefined {\n const lines = output.split('\\n')\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Check for \"Switched to branch 'name'\"\n let match = trimmed.match(/Switched to (?:a new )?branch '(.+)'/)\n if (match && match[1]) {\n return match[1]\n }\n\n // Check for \"On branch name\"\n match = trimmed.match(/On branch (.+)/)\n if (match && match[1]) {\n return match[1]\n }\n }\n\n return undefined\n}\n\n/**\n * Check if output indicates detached HEAD state\n */\nexport function isDetachedHead(output: string): boolean {\n return output.toLowerCase().includes('head detached') ||\n output.toLowerCase().includes('you are in \\'detached head\\' state')\n}\n\n/**\n * Parse remote tracking information\n */\nexport function parseRemoteTracking(output: string): {\n hasUpstream: boolean\n remoteBranch?: string\n remote?: string\n} {\n const lines = output.split('\\n')\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Check for \"Branch 'name' set up to track remote branch 'remote/branch'\"\n const match = trimmed.match(/set up to track remote branch '(.+?)'(?: from '(.+?)')?/)\n if (match) {\n return {\n hasUpstream: true,\n remoteBranch: match[1],\n remote: match[2] || 'origin'\n }\n }\n\n // Check for existing upstream: \"Your branch is up to date with 'origin/main'\"\n const upstreamMatch = trimmed.match(/(?:up to date with|ahead of|behind) '(.+?)\\/(.+?)'/)\n if (upstreamMatch) {\n return {\n hasUpstream: true,\n remote: upstreamMatch[1],\n remoteBranch: upstreamMatch[2]\n }\n }\n }\n\n return { hasUpstream: false }\n}\n","/**\n * Git Command Executor\n *\n * Executes git commands with error handling and returns structured results.\n * Interface-agnostic: Returns structured data, not formatted strings.\n */\n\nimport { exec } from 'child_process'\nimport { promisify } from 'util'\nimport { GitCommand, ExecutionResult, ExecutionOptions, ErrorCode } from './command-protocol'\nimport {\n validateBranchName,\n validateCommitMessage,\n validateFilePaths,\n sanitizeArgs\n} from './git-validator'\nimport {\n parseGitError,\n parseGitStatus,\n parseMergeConflicts,\n extractBranchName,\n isDetachedHead,\n parseRemoteTracking\n} from './git-parser'\n\nconst execAsync = promisify(exec)\n\n/**\n * Executes git commands with error handling\n *\n * DESIGN PRINCIPLES:\n * - Interface-agnostic: Returns structured data, not formatted strings\n * - No console.log: Let the interface handle output\n * - Error codes: Return error codes, not messages\n * - Reusable: Can be used by CLI, MCP, or any other interface\n */\nexport class GitExecutor {\n /**\n * Execute a git command\n * @param command - The git command to execute\n * @param options - Execution options (timeout, cwd, etc.)\n * @returns Structured result with success/error details\n */\n async execute(\n command: GitCommand,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate git is installed\n const gitInstalled = await this.validateGitInstalled()\n if (!gitInstalled) {\n return {\n success: false,\n error: 'GIT_NOT_INSTALLED'\n }\n }\n\n // Determine working directory\n const cwd = options?.cwd || process.cwd()\n\n // Validate this is a git repository (except for init-like commands)\n const isGitRepo = await this.isGitRepository(cwd)\n if (!isGitRepo) {\n return {\n success: false,\n error: 'NOT_GIT_REPO'\n }\n }\n\n // Route to appropriate handler based on action\n switch (command.action) {\n case 'checkout':\n return await this.executeCheckout(command, cwd, options)\n case 'create_branch':\n return await this.executeCreateBranch(command, cwd, options)\n case 'commit':\n return await this.executeCommit(command, cwd, options)\n case 'push':\n return await this.executePush(command, cwd, options)\n case 'status':\n return await this.executeStatus(cwd, options)\n case 'pull':\n return await this.executePull(command, cwd, options)\n case 'delete_branch':\n return await this.executeDeleteBranch(command, cwd, options)\n // EP597: Read operations for production local dev mode\n case 'branch_exists':\n return await this.executeBranchExists(command, cwd, options)\n case 'branch_has_commits':\n return await this.executeBranchHasCommits(command, cwd, options)\n // EP831: Find branch by prefix pattern\n case 'find_branch_by_prefix':\n return await this.executeFindBranchByPrefix(command, cwd, options)\n // EP598: Main branch check for production\n case 'main_branch_check':\n return await this.executeMainBranchCheck(cwd, options)\n // EP599: Get commits for branch\n case 'get_commits':\n return await this.executeGetCommits(command, cwd, options)\n // EP599: Advanced operations for move-to-module and discard-main-changes\n case 'stash':\n return await this.executeStash(command, cwd, options)\n case 'reset':\n return await this.executeReset(command, cwd, options)\n case 'merge':\n return await this.executeMerge(command, cwd, options)\n case 'cherry_pick':\n return await this.executeCherryPick(command, cwd, options)\n case 'clean':\n return await this.executeClean(command, cwd, options)\n case 'add':\n return await this.executeAdd(command, cwd, options)\n case 'fetch':\n return await this.executeFetch(command, cwd, options)\n // EP599-3: Composite operations\n case 'move_to_module':\n return await this.executeMoveToModule(command, cwd, options)\n case 'discard_main_changes':\n return await this.executeDiscardMainChanges(cwd, options)\n // EP523: Branch sync operations\n case 'sync_status':\n return await this.executeSyncStatus(command, cwd, options)\n case 'sync_main':\n return await this.executeSyncMain(cwd, options)\n case 'rebase_branch':\n return await this.executeRebaseBranch(command, cwd, options)\n case 'rebase_abort':\n return await this.executeRebaseAbort(cwd, options)\n case 'rebase_continue':\n return await this.executeRebaseContinue(cwd, options)\n case 'rebase_status':\n return await this.executeRebaseStatus(cwd, options)\n // EP944: Worktree operations\n case 'worktree_add':\n return await this.executeWorktreeAdd(command, cwd, options)\n case 'worktree_remove':\n return await this.executeWorktreeRemove(command, cwd, options)\n case 'worktree_list':\n return await this.executeWorktreeList(cwd, options)\n case 'worktree_prune':\n return await this.executeWorktreePrune(cwd, options)\n case 'clone_bare':\n return await this.executeCloneBare(command, options)\n case 'project_info':\n return await this.executeProjectInfo(cwd, options)\n default:\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: 'Unknown command action'\n }\n }\n } catch (error) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error instanceof Error ? error.message : 'Unknown error occurred'\n }\n }\n }\n\n /**\n * Execute checkout command\n */\n private async executeCheckout(\n command: { branch: string; create?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Check for uncommitted changes first\n const statusResult = await this.executeStatus(cwd, options)\n if (statusResult.success && statusResult.details?.uncommittedFiles?.length) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n details: {\n uncommittedFiles: statusResult.details.uncommittedFiles\n }\n }\n }\n\n // Build command\n const args = ['checkout']\n if (command.create) {\n args.push('-b')\n }\n args.push(command.branch)\n\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * Execute create_branch command\n */\n private async executeCreateBranch(\n command: { branch: string; from?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Validate source branch if provided\n if (command.from) {\n const fromValidation = validateBranchName(command.from)\n if (!fromValidation.valid) {\n return {\n success: false,\n error: fromValidation.error || 'UNKNOWN_ERROR'\n }\n }\n }\n\n // Build command - use checkout -b to create AND checkout the branch\n // This ensures the user is on the new branch immediately\n const args = ['checkout', '-b', command.branch]\n if (command.from) {\n args.push(command.from)\n }\n\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * Execute commit command\n */\n private async executeCommit(\n command: { message: string; files?: string[] },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate commit message\n const validation = validateCommitMessage(command.message)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Validate file paths if provided\n if (command.files) {\n const fileValidation = validateFilePaths(command.files)\n if (!fileValidation.valid) {\n return {\n success: false,\n error: fileValidation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Stage specific files first\n for (const file of command.files) {\n const addResult = await this.runGitCommand(['add', file], cwd, options)\n if (!addResult.success) {\n return addResult\n }\n }\n } else {\n // Stage all changes\n const addResult = await this.runGitCommand(['add', '-A'], cwd, options)\n if (!addResult.success) {\n return addResult\n }\n }\n\n // Execute commit\n const args = ['commit', '-m', command.message]\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * Execute push command\n * EP769: Added force parameter for pushing rebased branches\n */\n private async executePush(\n command: { branch: string; setUpstream?: boolean; force?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Build command\n const args = ['push']\n // EP769: Add --force flag for rebased branches\n if (command.force) {\n args.push('--force')\n }\n if (command.setUpstream) {\n args.push('-u', 'origin', command.branch)\n } else {\n args.push('origin', command.branch)\n }\n\n // Configure git credential helper for GitHub token if provided\n const env = { ...process.env }\n if (options?.githubToken) {\n env.GIT_ASKPASS = 'echo'\n env.GIT_USERNAME = 'x-access-token'\n env.GIT_PASSWORD = options.githubToken\n }\n\n return await this.runGitCommand(args, cwd, { ...options, env })\n }\n\n /**\n * Execute status command\n */\n private async executeStatus(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // EP971: Check if this is a bare repo (git status doesn't work in bare repos)\n try {\n const isBareResult = await execAsync('git rev-parse --is-bare-repository', { cwd, timeout: 5000 })\n if (isBareResult.stdout.trim() === 'true') {\n // In bare repo, get current branch from HEAD ref\n const headResult = await execAsync('git symbolic-ref --short HEAD', { cwd, timeout: 5000 })\n const branchName = headResult.stdout.trim()\n return {\n success: true,\n output: `## ${branchName}`,\n details: {\n uncommittedFiles: [], // No working tree in bare repo\n branchName,\n currentBranch: branchName\n }\n }\n }\n } catch {\n // Not a bare repo or failed to check, continue with normal status\n }\n\n const result = await this.runGitCommand(['status', '--porcelain', '-b'], cwd, options)\n\n if (result.success && result.output) {\n const statusInfo = parseGitStatus(result.output)\n return {\n success: true,\n output: result.output,\n details: {\n uncommittedFiles: statusInfo.uncommittedFiles,\n branchName: statusInfo.currentBranch\n }\n }\n }\n\n return result\n }\n\n /**\n * Execute pull command\n */\n private async executePull(\n command: { branch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name if provided\n if (command.branch) {\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n }\n\n // Check for uncommitted changes first\n const statusResult = await this.executeStatus(cwd, options)\n if (statusResult.success && statusResult.details?.uncommittedFiles?.length) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n details: {\n uncommittedFiles: statusResult.details.uncommittedFiles\n }\n }\n }\n\n // Build command\n const args = ['pull']\n if (command.branch) {\n args.push('origin', command.branch)\n }\n\n const result = await this.runGitCommand(args, cwd, options)\n\n // Check for merge conflicts\n if (!result.success && result.output) {\n const conflicts = parseMergeConflicts(result.output)\n if (conflicts.length > 0) {\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: result.output,\n details: {\n conflictingFiles: conflicts\n }\n }\n }\n }\n\n return result\n }\n\n /**\n * Execute delete_branch command\n */\n private async executeDeleteBranch(\n command: { branch: string; force?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Build command\n const args = ['branch']\n args.push(command.force ? '-D' : '-d')\n args.push(command.branch)\n\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * EP597: Execute branch_exists command\n * Checks if a branch exists locally and/or remotely\n */\n private async executeBranchExists(\n command: { branch: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n try {\n let isLocal = false\n let isRemote = false\n\n // Check local branches\n try {\n const { stdout: localBranches } = await execAsync('git branch --list', { cwd, timeout: options?.timeout || 10000 })\n isLocal = localBranches.split('\\n').some(line => {\n const branchName = line.replace(/^\\*?\\s*/, '').trim()\n return branchName === command.branch\n })\n } catch {\n // Ignore errors - branch doesn't exist locally\n }\n\n // Check remote branches\n try {\n const { stdout: remoteBranches } = await execAsync(\n `git ls-remote --heads origin ${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n isRemote = remoteBranches.trim().length > 0\n } catch {\n // Ignore errors - can't check remote (might be network issue)\n }\n\n const branchExists = isLocal || isRemote\n\n return {\n success: true,\n output: branchExists ? `Branch ${command.branch} exists` : `Branch ${command.branch} does not exist`,\n details: {\n branchName: command.branch,\n branchExists,\n isLocal,\n isRemote\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check branch existence'\n }\n }\n }\n\n /**\n * EP597: Execute branch_has_commits command\n * Checks if a branch has commits ahead of the base branch (default: main)\n */\n private async executeBranchHasCommits(\n command: { branch: string; baseBranch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n const baseBranch = command.baseBranch || 'main'\n\n try {\n // Use git cherry to find commits unique to the branch\n // This shows commits on branch that aren't on base\n const { stdout } = await execAsync(\n `git cherry origin/${baseBranch} ${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n\n // git cherry shows lines starting with + for unique commits\n const uniqueCommits = stdout.trim().split('\\n').filter(line => line.startsWith('+'))\n const hasCommits = uniqueCommits.length > 0\n\n return {\n success: true,\n output: hasCommits\n ? `Branch ${command.branch} has ${uniqueCommits.length} commits ahead of ${baseBranch}`\n : `Branch ${command.branch} has no commits ahead of ${baseBranch}`,\n details: {\n branchName: command.branch,\n hasCommits\n }\n }\n } catch (error: any) {\n // If git cherry fails (branch not found, etc.), try alternative method\n try {\n // Alternative: count commits with rev-list\n const { stdout } = await execAsync(\n `git rev-list --count origin/${baseBranch}..${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n\n const commitCount = parseInt(stdout.trim(), 10)\n const hasCommits = commitCount > 0\n\n return {\n success: true,\n output: hasCommits\n ? `Branch ${command.branch} has ${commitCount} commits ahead of ${baseBranch}`\n : `Branch ${command.branch} has no commits ahead of ${baseBranch}`,\n details: {\n branchName: command.branch,\n hasCommits\n }\n }\n } catch {\n // Both methods failed - branch likely doesn't exist or isn't tracked\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: error.message || `Failed to check commits for branch ${command.branch}`\n }\n }\n }\n }\n\n /**\n * EP598: Execute main branch check - returns current branch, uncommitted files, and unpushed commits\n */\n\n /**\n * EP831: Find branch by prefix pattern\n * Searches local and remote branches for one matching the prefix\n */\n private async executeFindBranchByPrefix(\n command: { prefix: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const { stdout } = await execAsync(\n 'git branch -a',\n { cwd, timeout: options?.timeout || 10000 }\n )\n\n const prefix = command.prefix\n const branches = stdout.split('\\n')\n .map(line => line.replace(/^[\\s*]*/, '').replace('remotes/origin/', '').trim())\n .filter(branch => branch && !branch.includes('->'))\n\n const matchingBranch = branches.find(branch => branch.startsWith(prefix))\n\n return {\n success: true,\n output: matchingBranch || '',\n details: {\n branchName: matchingBranch || undefined,\n branchExists: !!matchingBranch\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to find branch'\n }\n }\n }\n\n private async executeMainBranchCheck(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Get current branch\n let currentBranch = ''\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, timeout: options?.timeout || 10000 })\n currentBranch = stdout.trim()\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to get current branch'\n }\n }\n\n // Get uncommitted files\n let uncommittedFiles: string[] = []\n try {\n const { stdout } = await execAsync('git status --porcelain', { cwd, timeout: options?.timeout || 10000 })\n if (stdout) {\n uncommittedFiles = stdout.split('\\n').filter(line => line.trim()).map(line => {\n const parts = line.trim().split(/\\s+/)\n return parts.slice(1).join(' ')\n })\n }\n } catch {\n // Ignore errors - just means no uncommitted changes\n }\n\n // Get unpushed commits (only if on main branch)\n let localCommits: Array<{ sha: string; message: string; author: string }> = []\n if (currentBranch === 'main') {\n try {\n const { stdout } = await execAsync('git log origin/main..HEAD --format=\"%H|%s|%an\"', { cwd, timeout: options?.timeout || 10000 })\n if (stdout) {\n localCommits = stdout.split('\\n').filter(line => line.trim()).map(line => {\n const [sha, message, author] = line.split('|')\n return {\n sha: sha ? sha.substring(0, 8) : '',\n message: message || '',\n author: author || ''\n }\n })\n }\n } catch {\n // Ignore errors - might not have origin/main or no remote\n }\n }\n\n return {\n success: true,\n output: `Branch: ${currentBranch}, Uncommitted: ${uncommittedFiles.length}, Unpushed: ${localCommits.length}`,\n details: {\n currentBranch,\n uncommittedFiles,\n localCommits\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check main branch'\n }\n }\n }\n\n /**\n * EP599: Execute get_commits command\n * Returns commits for a branch with pushed/unpushed status\n */\n private async executeGetCommits(\n command: { branch: string; limit?: number; baseBranch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n const limit = command.limit || 10\n const baseBranch = command.baseBranch || 'main'\n\n try {\n // Get commits unique to this branch (not in main)\n let stdout: string\n try {\n const result = await execAsync(\n `git log ${baseBranch}..\"${command.branch}\" --pretty=format:\"%H|%an|%ae|%aI|%s\" -n ${limit} --`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n stdout = result.stdout\n } catch (error) {\n // Fallback: if comparison fails, show all commits on this branch\n try {\n const result = await execAsync(\n `git log \"${command.branch}\" --pretty=format:\"%H|%an|%ae|%aI|%s\" -n ${limit} --`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n stdout = result.stdout\n } catch (branchError) {\n // Branch doesn't exist locally\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: `Branch ${command.branch} not found locally`\n }\n }\n }\n\n if (!stdout.trim()) {\n return {\n success: true,\n output: 'No commits found',\n details: {\n commits: []\n }\n }\n }\n\n // Parse commits\n const commitLines = stdout.trim().split('\\n')\n\n // Check which commits have been pushed to remote\n let remoteShas: Set<string> = new Set()\n try {\n const { stdout: remoteCommits } = await execAsync(\n `git log \"origin/${command.branch}\" --pretty=format:\"%H\" -n ${limit} --`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n remoteShas = new Set(remoteCommits.trim().split('\\n').filter(Boolean))\n } catch {\n // Remote branch doesn't exist - all commits are local/unpushed\n }\n\n const commits = commitLines.map((line) => {\n const [sha, authorName, authorEmail, date, ...messageParts] = line.split('|')\n const message = messageParts.join('|') // Handle pipes in commit messages\n const isPushed = remoteShas.has(sha)\n\n return {\n sha,\n message,\n authorName,\n authorEmail,\n date,\n isPushed\n }\n })\n\n return {\n success: true,\n output: `Found ${commits.length} commits`,\n details: {\n commits\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to get commits'\n }\n }\n }\n\n // ========================================\n // EP599: Advanced operations for move-to-module and discard-main-changes\n // ========================================\n\n /**\n * Execute git stash operations\n */\n private async executeStash(\n command: { operation: 'push' | 'pop' | 'drop' | 'list'; message?: string; includeUntracked?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args: string[] = ['stash']\n\n switch (command.operation) {\n case 'push':\n args.push('push')\n if (command.includeUntracked) {\n args.push('--include-untracked')\n }\n if (command.message) {\n args.push('-m', command.message)\n }\n break\n case 'pop':\n args.push('pop')\n break\n case 'drop':\n args.push('drop')\n break\n case 'list':\n args.push('list')\n break\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Stash operation failed'\n }\n }\n }\n\n /**\n * Execute git reset\n */\n private async executeReset(\n command: { mode: 'soft' | 'mixed' | 'hard'; target?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['reset', `--${command.mode}`]\n if (command.target) {\n args.push(command.target)\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Reset failed'\n }\n }\n }\n\n /**\n * Execute git merge\n */\n private async executeMerge(\n command: { branch: string; strategy?: 'ours' | 'theirs'; noEdit?: boolean; abort?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n if (command.abort) {\n return await this.runGitCommand(['merge', '--abort'], cwd, options)\n }\n\n // Validate branch name\n const branchValidation = validateBranchName(command.branch)\n if (!branchValidation.valid) {\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: branchValidation.error || 'Invalid branch name'\n }\n }\n\n const args = ['merge', command.branch]\n\n if (command.strategy === 'ours') {\n args.push('--strategy=ours')\n } else if (command.strategy === 'theirs') {\n args.push('--strategy-option=theirs')\n }\n\n if (command.noEdit) {\n args.push('--no-edit')\n }\n\n const result = await this.runGitCommand(args, cwd, options)\n\n // Check for merge conflicts\n if (!result.success && result.output?.includes('CONFLICT')) {\n result.details = result.details || {}\n result.details.mergeConflicts = true\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: error.message || 'Merge failed'\n }\n }\n }\n\n /**\n * Execute git cherry-pick\n */\n private async executeCherryPick(\n command: { sha: string; abort?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n if (command.abort) {\n return await this.runGitCommand(['cherry-pick', '--abort'], cwd, options)\n }\n\n const result = await this.runGitCommand(['cherry-pick', command.sha], cwd, options)\n\n // Check for cherry-pick conflicts\n if (!result.success && result.output?.includes('CONFLICT')) {\n result.details = result.details || {}\n result.details.cherryPickConflicts = true\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: error.message || 'Cherry-pick failed'\n }\n }\n }\n\n /**\n * Execute git clean\n */\n private async executeClean(\n command: { force?: boolean; directories?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['clean']\n\n if (command.force) {\n args.push('-f')\n }\n if (command.directories) {\n args.push('-d')\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Clean failed'\n }\n }\n }\n\n /**\n * Execute git add\n */\n private async executeAdd(\n command: { files?: string[]; all?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['add']\n\n if (command.all) {\n args.push('-A')\n } else if (command.files && command.files.length > 0) {\n // Validate file paths\n const validation = validateFilePaths(command.files)\n if (!validation.valid) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: validation.error || 'Invalid file paths'\n }\n }\n args.push(...command.files)\n } else {\n args.push('-A') // Default to all\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Add failed'\n }\n }\n }\n\n /**\n * Execute git fetch\n */\n private async executeFetch(\n command: { remote?: string; branch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['fetch']\n\n if (command.remote) {\n args.push(command.remote)\n if (command.branch) {\n args.push(command.branch)\n }\n } else {\n args.push('origin')\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: error.message || 'Fetch failed'\n }\n }\n }\n\n // ========================================\n // EP599-3: Composite operations\n // ========================================\n\n /**\n * Execute move_to_module - composite operation\n * Moves commits/changes to a module branch with conflict handling\n */\n private async executeMoveToModule(\n command: { targetBranch: string; commitShas?: string[]; conflictResolution?: 'ours' | 'theirs' },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n const { targetBranch, commitShas, conflictResolution } = command\n let hasStash = false\n const cherryPickedCommits: string[] = []\n\n try {\n // Step 1: Get current branch\n const { stdout: currentBranchOut } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd })\n const currentBranch = currentBranchOut.trim()\n\n // Step 2: Stash uncommitted changes\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd })\n if (statusOutput.trim()) {\n try {\n await execAsync('git add -A', { cwd })\n const { stdout: stashHash } = await execAsync('git stash create -m \"episoda-move-to-module\"', { cwd })\n if (stashHash && stashHash.trim()) {\n await execAsync(`git stash store -m \"episoda-move-to-module\" ${stashHash.trim()}`, { cwd })\n await execAsync('git reset --hard HEAD', { cwd })\n hasStash = true\n }\n } catch (stashError: any) {\n // Continue without stashing\n }\n }\n\n // Step 3: Switch to main if we have commits to move\n if (commitShas && commitShas.length > 0 && currentBranch !== 'main' && currentBranch !== 'master') {\n await execAsync('git checkout main', { cwd })\n }\n\n // Step 4: Create or checkout target branch\n let branchExists = false\n try {\n await execAsync(`git rev-parse --verify ${targetBranch}`, { cwd })\n branchExists = true\n } catch {\n branchExists = false\n }\n\n if (!branchExists) {\n await execAsync(`git checkout -b ${targetBranch}`, { cwd })\n } else {\n await execAsync(`git checkout ${targetBranch}`, { cwd })\n\n // Try to merge changes from main\n if (currentBranch === 'main' || currentBranch === 'master') {\n try {\n const mergeStrategy = conflictResolution === 'ours' ? '--strategy=ours' :\n conflictResolution === 'theirs' ? '--strategy-option=theirs' : ''\n await execAsync(`git merge ${currentBranch} ${mergeStrategy} --no-edit`, { cwd })\n } catch (mergeError: any) {\n // Check for conflicts\n const { stdout: conflictStatus } = await execAsync('git status --porcelain', { cwd })\n if (conflictStatus.includes('UU ') || conflictStatus.includes('AA ') || conflictStatus.includes('DD ')) {\n const { stdout: conflictFiles } = await execAsync('git diff --name-only --diff-filter=U', { cwd })\n const conflictedFiles = conflictFiles.trim().split('\\n').filter(Boolean)\n\n if (conflictResolution) {\n // Auto-resolve conflicts\n for (const file of conflictedFiles) {\n await execAsync(`git checkout --${conflictResolution} \"${file}\"`, { cwd })\n await execAsync(`git add \"${file}\"`, { cwd })\n }\n await execAsync('git commit --no-edit', { cwd })\n } else {\n // Abort merge and return conflict error\n await execAsync('git merge --abort', { cwd })\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: 'Merge conflicts detected',\n details: {\n hasConflicts: true,\n conflictedFiles,\n movedToBranch: targetBranch\n }\n }\n }\n }\n }\n }\n }\n\n // Step 5: Cherry-pick commits if provided\n if (commitShas && commitShas.length > 0 && (currentBranch === 'main' || currentBranch === 'master')) {\n for (const sha of commitShas) {\n try {\n // Check if commit already exists in branch\n const { stdout: logOutput } = await execAsync(\n `git log --format=%H ${targetBranch} | grep ${sha}`,\n { cwd }\n ).catch(() => ({ stdout: '' }))\n\n if (!logOutput.trim()) {\n await execAsync(`git cherry-pick ${sha}`, { cwd })\n cherryPickedCommits.push(sha)\n }\n } catch (err: any) {\n await execAsync('git cherry-pick --abort', { cwd }).catch(() => {})\n }\n }\n\n // Reset main to origin/main\n await execAsync('git checkout main', { cwd })\n await execAsync('git reset --hard origin/main', { cwd })\n await execAsync(`git checkout ${targetBranch}`, { cwd })\n }\n\n // Step 6: Apply stashed changes\n if (hasStash) {\n try {\n await execAsync('git stash pop', { cwd })\n } catch (stashError: any) {\n // Check for stash conflicts\n const { stdout: conflictStatus } = await execAsync('git status --porcelain', { cwd })\n if (conflictStatus.includes('UU ') || conflictStatus.includes('AA ')) {\n if (conflictResolution) {\n const { stdout: conflictFiles } = await execAsync('git diff --name-only --diff-filter=U', { cwd })\n const conflictedFiles = conflictFiles.trim().split('\\n').filter(Boolean)\n for (const file of conflictedFiles) {\n await execAsync(`git checkout --${conflictResolution} \"${file}\"`, { cwd })\n await execAsync(`git add \"${file}\"`, { cwd })\n }\n }\n }\n }\n }\n\n return {\n success: true,\n output: `Successfully moved to branch ${targetBranch}`,\n details: {\n movedToBranch: targetBranch,\n cherryPickedCommits,\n currentBranch: targetBranch\n }\n }\n } catch (error: any) {\n // Try to restore stash if something went wrong\n if (hasStash) {\n try {\n const { stdout: stashList } = await execAsync('git stash list', { cwd })\n if (stashList.includes('episoda-move-to-module')) {\n await execAsync('git stash pop', { cwd })\n }\n } catch (e) {\n // Ignore errors restoring stash\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Move to module failed'\n }\n }\n }\n\n /**\n * Execute discard_main_changes - composite operation\n * Discards all uncommitted files and local commits on main branch\n */\n private async executeDiscardMainChanges(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Step 1: Verify we're on main/master\n const { stdout: currentBranchOut } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd })\n const branch = currentBranchOut.trim()\n\n if (branch !== 'main' && branch !== 'master') {\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: `Cannot discard changes - not on main branch. Current branch: ${branch}`\n }\n }\n\n let discardedFiles = 0\n\n // Step 2: Stash uncommitted changes (will be dropped)\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd })\n if (statusOutput.trim()) {\n try {\n await execAsync('git stash --include-untracked', { cwd })\n discardedFiles = statusOutput.trim().split('\\n').length\n } catch (stashError: any) {\n // Continue - might be nothing to stash\n }\n }\n\n // Step 3: Fetch and reset to origin\n await execAsync('git fetch origin', { cwd })\n await execAsync(`git reset --hard origin/${branch}`, { cwd })\n\n // Step 4: Clean untracked files\n try {\n await execAsync('git clean -fd', { cwd })\n } catch (cleanError: any) {\n // Non-critical\n }\n\n // Step 5: Drop the stash\n try {\n await execAsync('git stash drop', { cwd })\n } catch (dropError: any) {\n // Stash might not exist\n }\n\n return {\n success: true,\n output: `Successfully discarded all changes and reset to origin/${branch}`,\n details: {\n currentBranch: branch,\n discardedFiles\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Discard main changes failed'\n }\n }\n }\n\n // ========================================\n // EP523: Branch sync operations\n // ========================================\n\n /**\n * EP523: Get sync status of a branch relative to main\n * Returns how many commits behind/ahead the branch is\n */\n private async executeSyncStatus(\n command: { branch: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Fetch latest from remote to get accurate counts\n try {\n await execAsync('git fetch origin', { cwd, timeout: options?.timeout || 30000 })\n } catch (fetchError: any) {\n // Network error - return what we can determine locally\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: 'Unable to fetch from remote. Check your network connection.'\n }\n }\n\n let commitsBehind = 0\n let commitsAhead = 0\n\n // Count commits the branch is BEHIND main (main has commits branch doesn't)\n try {\n const { stdout: behindOutput } = await execAsync(\n `git rev-list --count ${command.branch}..origin/main`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n commitsBehind = parseInt(behindOutput.trim(), 10) || 0\n } catch {\n // Branch might not exist or no common ancestor\n commitsBehind = 0\n }\n\n // Count commits the branch is AHEAD of main (branch has commits main doesn't)\n try {\n const { stdout: aheadOutput } = await execAsync(\n `git rev-list --count origin/main..${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n commitsAhead = parseInt(aheadOutput.trim(), 10) || 0\n } catch {\n // Branch might not exist or no common ancestor\n commitsAhead = 0\n }\n\n const isBehind = commitsBehind > 0\n const isAhead = commitsAhead > 0\n const needsSync = isBehind\n\n return {\n success: true,\n output: isBehind\n ? `Branch ${command.branch} is ${commitsBehind} commit(s) behind main`\n : `Branch ${command.branch} is up to date with main`,\n details: {\n branchName: command.branch,\n commitsBehind,\n commitsAhead,\n isBehind,\n isAhead,\n needsSync\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check sync status'\n }\n }\n }\n\n /**\n * EP523: Sync local main branch with remote\n * Used before creating new branches to ensure we branch from latest main\n */\n private async executeSyncMain(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Get current branch to restore later if needed\n let currentBranch = ''\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, timeout: 5000 })\n currentBranch = stdout.trim()\n } catch {\n // Ignore - might be detached HEAD\n }\n\n // Fetch latest from remote\n try {\n await execAsync('git fetch origin main', { cwd, timeout: options?.timeout || 30000 })\n } catch (fetchError: any) {\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: 'Unable to fetch from remote. Check your network connection.'\n }\n }\n\n // Check if we need to switch to main\n const needsSwitch = currentBranch !== 'main' && currentBranch !== ''\n\n if (needsSwitch) {\n // Check for uncommitted changes first\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd, timeout: 5000 })\n if (statusOutput.trim()) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n output: 'Cannot sync main: you have uncommitted changes. Commit or stash them first.',\n details: {\n uncommittedFiles: statusOutput.trim().split('\\n').map(line => line.slice(3))\n }\n }\n }\n\n // Switch to main\n await execAsync('git checkout main', { cwd, timeout: options?.timeout || 10000 })\n }\n\n // Pull latest main\n try {\n await execAsync('git pull origin main', { cwd, timeout: options?.timeout || 30000 })\n } catch (pullError: any) {\n // Check for conflicts\n if (pullError.message?.includes('CONFLICT') || pullError.stderr?.includes('CONFLICT')) {\n // Abort the merge\n await execAsync('git merge --abort', { cwd, timeout: 5000 }).catch(() => {})\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: 'Conflict while syncing main. This is unexpected - main should not have local commits.'\n }\n }\n throw pullError\n }\n\n // Switch back to original branch if we switched\n if (needsSwitch && currentBranch) {\n await execAsync(`git checkout \"${currentBranch}\"`, { cwd, timeout: options?.timeout || 10000 })\n }\n\n return {\n success: true,\n output: 'Successfully synced main with remote',\n details: {\n currentBranch: needsSwitch ? currentBranch : 'main'\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to sync main'\n }\n }\n }\n\n /**\n * EP523: Rebase a branch onto main\n * Used when resuming work on a branch that's behind main\n */\n private async executeRebaseBranch(\n command: { branch: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Check for uncommitted changes\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd, timeout: 5000 })\n if (statusOutput.trim()) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n output: 'Cannot rebase: you have uncommitted changes. Commit or stash them first.',\n details: {\n uncommittedFiles: statusOutput.trim().split('\\n').map(line => line.slice(3))\n }\n }\n }\n\n // Get current branch\n const { stdout: currentBranchOut } = await execAsync('git branch --show-current', { cwd, timeout: 5000 })\n const currentBranch = currentBranchOut.trim()\n\n // Ensure we're on the target branch\n if (currentBranch !== command.branch) {\n await execAsync(`git checkout \"${command.branch}\"`, { cwd, timeout: options?.timeout || 10000 })\n }\n\n // Fetch latest main\n await execAsync('git fetch origin main', { cwd, timeout: options?.timeout || 30000 })\n\n // Perform rebase\n try {\n await execAsync('git rebase origin/main', { cwd, timeout: options?.timeout || 60000 })\n } catch (rebaseError: any) {\n const errorOutput = (rebaseError.stderr || '') + (rebaseError.stdout || '')\n\n // Check for conflicts\n if (errorOutput.includes('CONFLICT') || errorOutput.includes('could not apply')) {\n // Get conflicting files\n let conflictFiles: string[] = []\n try {\n const { stdout: conflictOutput } = await execAsync(\n 'git diff --name-only --diff-filter=U',\n { cwd, timeout: 5000 }\n )\n conflictFiles = conflictOutput.trim().split('\\n').filter(Boolean)\n } catch {\n // Ignore - try alternate method\n try {\n const { stdout: statusOut } = await execAsync('git status --porcelain', { cwd, timeout: 5000 })\n conflictFiles = statusOut\n .trim()\n .split('\\n')\n .filter(line => line.startsWith('UU ') || line.startsWith('AA ') || line.startsWith('DD '))\n .map(line => line.slice(3))\n } catch {\n // Couldn't get conflict files\n }\n }\n\n return {\n success: false,\n error: 'REBASE_CONFLICT',\n output: `Rebase conflict in ${conflictFiles.length} file(s). Resolve conflicts then use rebase_continue, or use rebase_abort to cancel.`,\n details: {\n inRebase: true,\n rebaseConflicts: conflictFiles,\n hasConflicts: true,\n conflictedFiles: conflictFiles\n }\n }\n }\n\n throw rebaseError\n }\n\n return {\n success: true,\n output: `Successfully rebased ${command.branch} onto main`,\n details: {\n branchName: command.branch,\n inRebase: false\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Rebase failed'\n }\n }\n }\n\n /**\n * EP523: Abort an in-progress rebase\n * Returns to the state before rebase was started\n */\n private async executeRebaseAbort(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n await execAsync('git rebase --abort', { cwd, timeout: options?.timeout || 10000 })\n\n return {\n success: true,\n output: 'Rebase aborted. Your branch has been restored to its previous state.',\n details: {\n inRebase: false\n }\n }\n } catch (error: any) {\n // Check if there's no rebase in progress\n if (error.message?.includes('No rebase in progress') || error.stderr?.includes('No rebase in progress')) {\n return {\n success: true,\n output: 'No rebase in progress.',\n details: {\n inRebase: false\n }\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to abort rebase'\n }\n }\n }\n\n /**\n * EP523: Continue a paused rebase after conflicts are resolved\n */\n private async executeRebaseContinue(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Stage all resolved files\n await execAsync('git add -A', { cwd, timeout: 5000 })\n\n // Continue the rebase\n await execAsync('git rebase --continue', { cwd, timeout: options?.timeout || 60000 })\n\n return {\n success: true,\n output: 'Rebase continued successfully.',\n details: {\n inRebase: false\n }\n }\n } catch (error: any) {\n const errorOutput = (error.stderr || '') + (error.stdout || '')\n\n // Check if there are still conflicts\n if (errorOutput.includes('CONFLICT') || errorOutput.includes('could not apply')) {\n // Get remaining conflict files\n let conflictFiles: string[] = []\n try {\n const { stdout: conflictOutput } = await execAsync(\n 'git diff --name-only --diff-filter=U',\n { cwd, timeout: 5000 }\n )\n conflictFiles = conflictOutput.trim().split('\\n').filter(Boolean)\n } catch {\n // Ignore\n }\n\n return {\n success: false,\n error: 'REBASE_CONFLICT',\n output: 'More conflicts encountered. Resolve them and try again.',\n details: {\n inRebase: true,\n rebaseConflicts: conflictFiles,\n hasConflicts: true,\n conflictedFiles: conflictFiles\n }\n }\n }\n\n // Check if there's no rebase in progress\n if (errorOutput.includes('No rebase in progress')) {\n return {\n success: true,\n output: 'No rebase in progress.',\n details: {\n inRebase: false\n }\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to continue rebase'\n }\n }\n }\n\n /**\n * EP523: Check if a rebase is currently in progress\n */\n private async executeRebaseStatus(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Check for rebase-merge directory (indicates rebase in progress)\n let inRebase = false\n let rebaseConflicts: string[] = []\n\n try {\n const { stdout: gitDir } = await execAsync('git rev-parse --git-dir', { cwd, timeout: 5000 })\n const gitDirPath = gitDir.trim()\n\n // Check for rebase directories\n const fs = await import('fs').then(m => m.promises)\n const rebaseMergePath = `${gitDirPath}/rebase-merge`\n const rebaseApplyPath = `${gitDirPath}/rebase-apply`\n\n try {\n await fs.access(rebaseMergePath)\n inRebase = true\n } catch {\n try {\n await fs.access(rebaseApplyPath)\n inRebase = true\n } catch {\n inRebase = false\n }\n }\n } catch {\n // If we can't determine, check via status\n try {\n const { stdout: statusOutput } = await execAsync('git status', { cwd, timeout: 5000 })\n inRebase = statusOutput.includes('rebase in progress') ||\n statusOutput.includes('interactive rebase in progress') ||\n statusOutput.includes('You are currently rebasing')\n } catch {\n inRebase = false\n }\n }\n\n // If in rebase, get conflict files\n if (inRebase) {\n try {\n const { stdout: conflictOutput } = await execAsync(\n 'git diff --name-only --diff-filter=U',\n { cwd, timeout: 5000 }\n )\n rebaseConflicts = conflictOutput.trim().split('\\n').filter(Boolean)\n } catch {\n // No conflicts or couldn't get them\n }\n }\n\n return {\n success: true,\n output: inRebase\n ? `Rebase in progress with ${rebaseConflicts.length} conflicting file(s)`\n : 'No rebase in progress',\n details: {\n inRebase,\n rebaseConflicts,\n hasConflicts: rebaseConflicts.length > 0\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check rebase status'\n }\n }\n }\n\n // ========================================\n // EP944: Worktree operations\n // ========================================\n\n /**\n * EP944: Add a new worktree for a branch\n * Creates a new working tree at the specified path\n */\n private async executeWorktreeAdd(\n command: { path: string; branch: string; create?: boolean; startPoint?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Check if path already exists\n const fs = await import('fs').then(m => m.promises)\n try {\n await fs.access(command.path)\n return {\n success: false,\n error: 'WORKTREE_EXISTS',\n output: `Worktree already exists at path: ${command.path}`\n }\n } catch {\n // Path doesn't exist, which is what we want\n }\n\n // Fetch latest refs from remote to ensure branch exists\n // This is especially important for branches created after the initial clone\n try {\n await this.runGitCommand(['fetch', '--all', '--prune'], cwd, options)\n } catch {\n // Fetch failure is non-fatal - branch may be local or fetch may not be needed\n }\n\n // Build command\n const args = ['worktree', 'add']\n if (command.create) {\n args.push('-b', command.branch, command.path)\n // EP996: Add start point for new branch to ensure fresh base\n if (command.startPoint) {\n args.push(command.startPoint)\n }\n } else {\n args.push(command.path, command.branch)\n }\n\n const result = await this.runGitCommand(args, cwd, options)\n\n if (result.success) {\n return {\n success: true,\n output: `Created worktree at ${command.path} for branch ${command.branch}`,\n details: {\n worktreePath: command.path,\n branchName: command.branch\n }\n }\n }\n\n // Check for specific error conditions\n if (result.output?.includes('already checked out')) {\n return {\n success: false,\n error: 'BRANCH_IN_USE',\n output: `Branch '${command.branch}' is already checked out in another worktree`\n }\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to add worktree'\n }\n }\n }\n\n /**\n * EP944: Remove a worktree\n * Removes the working tree at the specified path\n */\n private async executeWorktreeRemove(\n command: { path: string; force?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Check if path exists\n const fs = await import('fs').then(m => m.promises)\n try {\n await fs.access(command.path)\n } catch {\n return {\n success: false,\n error: 'WORKTREE_NOT_FOUND',\n output: `Worktree not found at path: ${command.path}`\n }\n }\n\n // Check for uncommitted changes (unless force)\n if (!command.force) {\n try {\n const { stdout } = await execAsync('git status --porcelain', {\n cwd: command.path,\n timeout: options?.timeout || 10000\n })\n if (stdout.trim()) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n output: 'Worktree has uncommitted changes. Use force to remove anyway.',\n details: {\n uncommittedFiles: stdout.trim().split('\\n').map(line => line.slice(3))\n }\n }\n }\n } catch {\n // If we can't check status, continue with removal\n }\n }\n\n // Build command\n const args = ['worktree', 'remove']\n if (command.force) {\n args.push('--force')\n }\n args.push(command.path)\n\n const result = await this.runGitCommand(args, cwd, options)\n\n if (result.success) {\n return {\n success: true,\n output: `Removed worktree at ${command.path}`,\n details: {\n worktreePath: command.path\n }\n }\n }\n\n // Check for locked worktree\n if (result.output?.includes('locked')) {\n return {\n success: false,\n error: 'WORKTREE_LOCKED',\n output: `Worktree at ${command.path} is locked`\n }\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to remove worktree'\n }\n }\n }\n\n /**\n * EP944: List all worktrees\n * Returns information about all worktrees in the repository\n */\n private async executeWorktreeList(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const { stdout } = await execAsync('git worktree list --porcelain', {\n cwd,\n timeout: options?.timeout || 10000\n })\n\n const worktrees: Array<{\n path: string\n branch: string\n commit: string\n locked?: boolean\n prunable?: boolean\n }> = []\n\n // Parse porcelain output\n // Format:\n // worktree /path/to/worktree\n // HEAD abc123...\n // branch refs/heads/branch-name\n // (blank line)\n const lines = stdout.trim().split('\\n')\n let current: Partial<{\n path: string\n branch: string\n commit: string\n locked: boolean\n prunable: boolean\n }> = {}\n\n for (const line of lines) {\n if (line.startsWith('worktree ')) {\n current.path = line.slice(9)\n } else if (line.startsWith('HEAD ')) {\n current.commit = line.slice(5)\n } else if (line.startsWith('branch ')) {\n // Extract branch name from refs/heads/branch-name\n const refPath = line.slice(7)\n current.branch = refPath.replace('refs/heads/', '')\n } else if (line === 'locked') {\n current.locked = true\n } else if (line === 'prunable') {\n current.prunable = true\n } else if (line.startsWith('detached')) {\n current.branch = 'HEAD (detached)'\n } else if (line === '' && current.path) {\n // End of entry\n worktrees.push({\n path: current.path,\n branch: current.branch || 'unknown',\n commit: current.commit || '',\n locked: current.locked,\n prunable: current.prunable\n })\n current = {}\n }\n }\n\n // Don't forget the last entry if output doesn't end with blank line\n if (current.path) {\n worktrees.push({\n path: current.path,\n branch: current.branch || 'unknown',\n commit: current.commit || '',\n locked: current.locked,\n prunable: current.prunable\n })\n }\n\n return {\n success: true,\n output: `Found ${worktrees.length} worktree(s)`,\n details: {\n worktrees\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to list worktrees'\n }\n }\n }\n\n /**\n * EP944: Prune stale worktrees\n * Removes worktree administrative files for worktrees whose directories are missing\n */\n private async executeWorktreePrune(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // First get list of prunable worktrees\n const listResult = await this.executeWorktreeList(cwd, options)\n const prunableCount = listResult.details?.worktrees?.filter(w => w.prunable).length || 0\n\n // Run prune\n const result = await this.runGitCommand(['worktree', 'prune'], cwd, options)\n\n if (result.success) {\n return {\n success: true,\n output: prunableCount > 0\n ? `Pruned ${prunableCount} stale worktree(s)`\n : 'No stale worktrees to prune',\n details: {\n prunedCount: prunableCount\n }\n }\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to prune worktrees'\n }\n }\n }\n\n /**\n * EP944: Clone a repository as a bare repository\n * Used for worktree-based development setup\n */\n private async executeCloneBare(\n command: { url: string; path: string },\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const fs = await import('fs').then(m => m.promises)\n const path = await import('path')\n\n // Check if path already exists\n try {\n await fs.access(command.path)\n return {\n success: false,\n error: 'BRANCH_ALREADY_EXISTS', // Reusing for path exists\n output: `Directory already exists at path: ${command.path}`\n }\n } catch {\n // Path doesn't exist, which is what we want\n }\n\n // Create parent directory if needed\n const parentDir = path.dirname(command.path)\n try {\n await fs.mkdir(parentDir, { recursive: true })\n } catch {\n // Directory might already exist\n }\n\n // Clone as bare repository\n const { stdout, stderr } = await execAsync(\n `git clone --bare \"${command.url}\" \"${command.path}\"`,\n { timeout: options?.timeout || 120000 } // 2 minutes for clone\n )\n\n return {\n success: true,\n output: `Cloned bare repository to ${command.path}`,\n details: {\n worktreePath: command.path\n }\n }\n } catch (error: any) {\n // Check for auth errors\n if (error.message?.includes('Authentication') || error.message?.includes('Permission denied')) {\n return {\n success: false,\n error: 'AUTH_FAILURE',\n output: 'Authentication failed. Please check your credentials.'\n }\n }\n\n // Check for network errors\n if (error.message?.includes('Could not resolve') || error.message?.includes('unable to access')) {\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: 'Network error. Please check your connection.'\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to clone repository'\n }\n }\n }\n\n /**\n * EP944: Get project info including worktree mode\n * Returns information about the project configuration\n */\n private async executeProjectInfo(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const fs = await import('fs').then(m => m.promises)\n const path = await import('path')\n\n // EP971: All projects use worktree architecture\n // Walk up to find the project root with .bare and .episoda directories\n let currentPath = cwd\n let projectPath = cwd\n let bareRepoPath: string | undefined\n\n for (let i = 0; i < 10; i++) {\n const bareDir = path.join(currentPath, '.bare')\n const episodaDir = path.join(currentPath, '.episoda')\n\n try {\n await fs.access(bareDir)\n await fs.access(episodaDir)\n\n // Found project root\n projectPath = currentPath\n bareRepoPath = bareDir\n break\n } catch {\n // Not found at this level, check parent\n const parentPath = path.dirname(currentPath)\n if (parentPath === currentPath) {\n break // Reached filesystem root\n }\n currentPath = parentPath\n }\n }\n\n return {\n success: true,\n output: bareRepoPath ? 'Episoda project' : 'Git repository',\n details: {\n projectPath,\n bareRepoPath\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to get project info'\n }\n }\n }\n\n /**\n * Run a git command and return structured result\n */\n private async runGitCommand(\n args: string[],\n cwd: string,\n options?: ExecutionOptions & { env?: NodeJS.ProcessEnv }\n ): Promise<ExecutionResult> {\n try {\n // Sanitize arguments\n const sanitizedArgs = sanitizeArgs(args)\n\n // Build command\n const command = ['git', ...sanitizedArgs].join(' ')\n\n // Execute with timeout\n const timeout = options?.timeout || 30000 // 30 second default\n const execOptions = {\n cwd,\n timeout,\n env: options?.env || process.env,\n maxBuffer: 1024 * 1024 * 10 // 10MB buffer\n }\n\n const { stdout, stderr } = await execAsync(command, execOptions)\n\n // Combine output\n const output = (stdout + stderr).trim()\n\n // Extract additional details\n const details: ExecutionResult['details'] = {}\n\n // Try to extract branch name\n const branchName = extractBranchName(output)\n if (branchName) {\n details.branchName = branchName\n }\n\n // Check for detached HEAD\n if (isDetachedHead(output)) {\n details.branchName = 'HEAD (detached)'\n }\n\n return {\n success: true,\n output,\n details: Object.keys(details).length > 0 ? details : undefined\n }\n } catch (error: any) {\n // Parse error\n const stderr = error.stderr || ''\n const stdout = error.stdout || ''\n const exitCode = error.code || 1\n\n // Determine error code\n const errorCode = parseGitError(stderr, stdout, exitCode)\n\n // Extract additional details based on error type\n const details: ExecutionResult['details'] = {\n exitCode\n }\n\n // Parse conflicts if merge conflict\n if (errorCode === 'MERGE_CONFLICT') {\n const conflicts = parseMergeConflicts(stdout + stderr)\n if (conflicts.length > 0) {\n details.conflictingFiles = conflicts\n }\n }\n\n // Parse status for uncommitted changes\n if (errorCode === 'UNCOMMITTED_CHANGES') {\n try {\n const statusResult = await this.executeStatus(cwd, options)\n if (statusResult.details?.uncommittedFiles) {\n details.uncommittedFiles = statusResult.details.uncommittedFiles\n }\n } catch {\n // Ignore errors when getting status\n }\n }\n\n return {\n success: false,\n error: errorCode,\n output: (stdout + stderr).trim(),\n details\n }\n }\n }\n\n /**\n * Validate that git is installed\n */\n private async validateGitInstalled(): Promise<boolean> {\n try {\n await execAsync('git --version', { timeout: 5000 })\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Check if directory is a git repository\n */\n private async isGitRepository(cwd: string): Promise<boolean> {\n try {\n await execAsync('git rev-parse --git-dir', { cwd, timeout: 5000 })\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Detect the git working directory (repository root)\n * @returns Path to the git repository root\n */\n private async detectWorkingDirectory(startPath?: string): Promise<string | null> {\n try {\n const { stdout } = await execAsync('git rev-parse --show-toplevel', {\n cwd: startPath || process.cwd(),\n timeout: 5000\n })\n return stdout.trim()\n } catch {\n return null\n }\n }\n}\n","/**\n * Version management for @episoda/core\n *\n * EP812: Updated to be bundle-safe. When bundled into CLI by tsup,\n * the package.json path lookup fails because __dirname changes.\n * Now tries package.json first, falls back to hardcoded version.\n *\n * IMPORTANT: Keep FALLBACK_VERSION in sync with package.json version!\n * This should be updated when running `npm version` in the core package.\n */\n\nimport { readFileSync, existsSync } from 'fs'\nimport { join } from 'path'\n\n// Fallback version for bundled usage (update with package.json!)\nconst FALLBACK_VERSION = '0.1.11'\n\nfunction getVersion(): string {\n try {\n // Try to read from package.json (works when running from source)\n const packageJsonPath = join(__dirname, '..', 'package.json')\n if (existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))\n return packageJson.version\n }\n } catch {\n // Bundled or path doesn't exist - use fallback\n }\n return FALLBACK_VERSION\n}\n\nexport const VERSION: string = getVersion()\n","/**\n * Episoda WebSocket Client\n *\n * EP589-9: Implemented with comprehensive error handling and reconnection logic\n *\n * Provides reliable WebSocket connection to episoda.dev CLI gateway with:\n * - Automatic reconnection with exponential backoff\n * - Heartbeat/ping-pong to keep connection alive\n * - Event-driven architecture for handling server commands\n * - Graceful error handling and recovery\n */\n\nimport WS from 'ws'\nimport https from 'https'\nimport { ServerMessage, ClientMessage, ConnectionStatus } from './command-protocol'\nimport { VERSION } from './version'\n\n// EP801: Create HTTPS agent that forces IPv4 to avoid IPv6 timeout issues\n// Many networks have IPv6 configured but not fully routable, causing connection failures\nconst ipv4Agent = new https.Agent({ family: 4 })\n\n// EP701: Client-side events emitted by EpisodaClient\nexport type DisconnectEvent = {\n type: 'disconnected'\n code: number\n reason: string\n willReconnect: boolean\n}\n\nexport type ClientEvent = ServerMessage | DisconnectEvent\n\nexport type EventHandler = (event: ClientEvent) => void | Promise<void>\n\n// EP605: Reconnection configuration for long-term stability\nconst INITIAL_RECONNECT_DELAY = 1000 // 1 second\nconst MAX_RECONNECT_DELAY = 60000 // 60 seconds standard max\nconst IDLE_RECONNECT_DELAY = 600000 // 10 minutes when idle (no commands for 1+ hour)\nconst MAX_RETRY_DURATION = 6 * 60 * 60 * 1000 // 6 hours maximum retry duration\nconst IDLE_THRESHOLD = 60 * 60 * 1000 // 1 hour - after this, use slower retry\n\n// EP648: Rate limit and rapid reconnect prevention\nconst RATE_LIMIT_BACKOFF = 60000 // 60 seconds when rate limited\nconst RAPID_CLOSE_THRESHOLD = 2000 // If connection closes within 2s, it's likely an error\nconst RAPID_CLOSE_BACKOFF = 30000 // 30 seconds backoff for rapid close scenarios\n\n// EP605: Client-side heartbeat to detect dead connections\n// EP846-7: Aligned to 20s (was 45s) - server is 15s, so intervals don't collide\nconst CLIENT_HEARTBEAT_INTERVAL = 20000 // 20 seconds (offset from server's 15s)\nconst CLIENT_HEARTBEAT_TIMEOUT = 15000 // 15 seconds to wait for pong\n\n// EP606: Connection timeout for initial WebSocket connection\nconst CONNECTION_TIMEOUT = 15000 // 15 seconds to establish connection\n\n/**\n * WebSocket client for connecting to episoda.dev/cli gateway\n *\n * DESIGN PRINCIPLES:\n * - Reverse WebSocket: Client connects TO server (bypasses NAT/firewall)\n * - Automatic reconnection with exponential backoff\n * - Server-initiated heartbeat via WebSocket ping/pong (15s interval)\n * - Event-driven architecture for handling server commands\n */\nexport class EpisodaClient {\n private ws?: WS\n private eventHandlers: Map<string, EventHandler[]> = new Map()\n private reconnectAttempts = 0\n private reconnectTimeout?: NodeJS.Timeout\n private url = ''\n private token = ''\n private machineId?: string\n private hostname?: string\n private osPlatform?: string\n private osArch?: string\n private daemonPid?: number\n private isConnected = false\n private isDisconnecting = false\n private isGracefulShutdown = false // Track if shutdown was graceful (server-initiated)\n // EP605: Client-side heartbeat for connection health monitoring\n private heartbeatTimer?: NodeJS.Timeout\n private heartbeatTimeoutTimer?: NodeJS.Timeout\n // EP605: Activity and retry duration tracking\n private lastCommandTime = Date.now() // Track last command for idle detection\n private firstDisconnectTime?: number // Track when reconnection attempts started\n private isIntentionalDisconnect = false // Prevent reconnection after intentional disconnect\n // EP648: Rate limit and rapid reconnect tracking\n private rateLimitBackoffUntil?: number // Timestamp until which we should wait (rate limited)\n private lastConnectAttemptTime = 0 // Track when we last attempted to connect\n private lastErrorCode?: string // Track the last error code received\n\n /**\n * Connect to episoda.dev WebSocket gateway\n * @param url - WebSocket URL (wss://episoda.dev/cli)\n * @param token - OAuth access token\n * @param machineId - Optional machine identifier for multi-machine support\n * @param deviceInfo - Optional device information (hostname, OS, daemonPid)\n */\n async connect(\n url: string,\n token: string,\n machineId?: string,\n deviceInfo?: { hostname?: string; osPlatform?: string; osArch?: string; daemonPid?: number }\n ): Promise<void> {\n this.url = url\n this.token = token\n this.machineId = machineId\n this.hostname = deviceInfo?.hostname\n this.osPlatform = deviceInfo?.osPlatform\n this.osArch = deviceInfo?.osArch\n this.daemonPid = deviceInfo?.daemonPid\n this.isDisconnecting = false\n this.isGracefulShutdown = false // Reset graceful shutdown flag on new connection\n this.isIntentionalDisconnect = false // Allow reconnection on fresh connect\n this.lastConnectAttemptTime = Date.now() // EP648: Track when this connection attempt started\n this.lastErrorCode = undefined // EP648: Clear last error code\n\n // EP816: Close any existing WebSocket before creating a new one\n // This prevents orphaned connections when connect() is called multiple times\n if (this.ws) {\n try {\n this.ws.removeAllListeners() // Prevent close handler from triggering reconnect\n this.ws.terminate()\n } catch {\n // Ignore errors during cleanup\n }\n this.ws = undefined\n }\n\n // Clear any pending reconnect timer to prevent race conditions\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout)\n this.reconnectTimeout = undefined\n }\n\n return new Promise((resolve, reject) => {\n // EP606: Connection timeout to prevent hanging indefinitely\n const connectionTimeout = setTimeout(() => {\n if (this.ws) {\n this.ws.terminate()\n }\n reject(new Error(`Connection timeout after ${CONNECTION_TIMEOUT / 1000}s - server may be unreachable`))\n }, CONNECTION_TIMEOUT)\n\n try {\n // EP801: Use IPv4 agent to avoid IPv6 timeout issues on networks with broken IPv6\n this.ws = new WS(url, { agent: ipv4Agent })\n\n // Connection opened\n this.ws.on('open', () => {\n clearTimeout(connectionTimeout) // EP606: Clear timeout on successful connection\n console.log('[EpisodaClient] WebSocket connected')\n this.isConnected = true\n this.reconnectAttempts = 0\n this.firstDisconnectTime = undefined // EP605: Reset retry duration tracking\n this.lastCommandTime = Date.now() // EP605: Reset activity timer\n\n // Send auth message (K722: includes machineId, EP596: includes device info, EP601: includes daemonPid)\n this.send({\n type: 'auth',\n token,\n version: VERSION,\n machineId,\n hostname: this.hostname,\n osPlatform: this.osPlatform,\n osArch: this.osArch,\n daemonPid: this.daemonPid\n })\n\n // EP605: Start client-side heartbeat\n this.startHeartbeat()\n\n resolve()\n })\n\n // EP605: Handle pong response from server (for client-initiated pings)\n this.ws.on('pong', () => {\n // Clear the timeout - connection is alive\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n this.heartbeatTimeoutTimer = undefined\n }\n })\n\n // Message received\n this.ws.on('message', (data: WS.Data) => {\n try {\n const message = JSON.parse(data.toString()) as ServerMessage\n this.handleMessage(message)\n } catch (error) {\n console.error('[EpisodaClient] Failed to parse message:', error)\n }\n })\n\n // EP603: Log ping events for debugging connection health\n this.ws.on('ping', () => {\n console.log('[EpisodaClient] Received ping from server')\n })\n\n // Connection closed\n this.ws.on('close', (code: number, reason: Buffer) => {\n console.log(`[EpisodaClient] WebSocket closed: ${code} ${reason.toString()}`)\n this.isConnected = false\n\n const willReconnect = !this.isDisconnecting\n\n // EP701: Emit 'disconnected' event so daemon can clean up connection\n this.emit({\n type: 'disconnected',\n code,\n reason: reason.toString(),\n willReconnect\n })\n\n // Attempt reconnection if not intentional disconnect\n if (willReconnect) {\n this.scheduleReconnect()\n }\n })\n\n // Error occurred\n this.ws.on('error', (error: Error) => {\n console.error('[EpisodaClient] WebSocket error:', error)\n\n if (!this.isConnected) {\n // Connection failed, reject the promise\n clearTimeout(connectionTimeout) // EP606: Clear timeout on error\n reject(error)\n }\n })\n\n } catch (error) {\n clearTimeout(connectionTimeout) // EP606: Clear timeout on exception\n reject(error)\n }\n })\n }\n\n /**\n * Disconnect from the server\n * @param intentional - If true, prevents automatic reconnection (user-initiated disconnect)\n */\n async disconnect(intentional = true): Promise<void> {\n this.isDisconnecting = true\n this.isIntentionalDisconnect = intentional // EP605: Prevent reconnection if user intentionally disconnected\n\n // EP605: Clear all timers\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout)\n this.reconnectTimeout = undefined\n }\n\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = undefined\n }\n\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n this.heartbeatTimeoutTimer = undefined\n }\n\n if (this.ws) {\n this.ws.close()\n this.ws = undefined\n }\n\n this.isConnected = false\n }\n\n /**\n * Register an event handler\n * @param event - Event type ('command', 'ping', 'error', 'auth_success')\n * @param handler - Handler function\n */\n on(event: string, handler: EventHandler): void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, [])\n }\n this.eventHandlers.get(event)!.push(handler)\n }\n\n /**\n * EP812: Register a one-time event handler (removes itself after first call)\n * @param event - Event type\n * @param handler - Handler function\n */\n once(event: string, handler: EventHandler): void {\n const onceHandler: EventHandler = (message) => {\n this.off(event, onceHandler)\n handler(message)\n }\n this.on(event, onceHandler)\n }\n\n /**\n * EP812: Remove an event handler\n * @param event - Event type\n * @param handler - Handler function to remove\n */\n off(event: string, handler: EventHandler): void {\n const handlers = this.eventHandlers.get(event)\n if (handlers) {\n const index = handlers.indexOf(handler)\n if (index !== -1) {\n handlers.splice(index, 1)\n }\n }\n }\n\n /**\n * Send a message to the server\n * @param message - Client message to send\n */\n async send(message: ClientMessage): Promise<void> {\n if (!this.ws || !this.isConnected) {\n throw new Error('WebSocket not connected')\n }\n\n return new Promise((resolve, reject) => {\n this.ws!.send(JSON.stringify(message), (error) => {\n if (error) {\n console.error('[EpisodaClient] Failed to send message:', error)\n reject(error)\n } else {\n resolve()\n }\n })\n })\n }\n\n /**\n * Get current connection status\n */\n getStatus(): ConnectionStatus {\n return {\n connected: this.isConnected\n }\n }\n\n /**\n * EP605: Update last command time to reset idle detection\n * Call this when a command is received/executed\n */\n updateActivity(): void {\n this.lastCommandTime = Date.now()\n }\n\n /**\n * EP701: Emit a client-side event to registered handlers\n * Used for events like 'disconnected' that originate from the client, not server\n */\n private emit(event: ClientEvent): void {\n const handlers = this.eventHandlers.get(event.type) || []\n handlers.forEach(handler => {\n try {\n handler(event)\n } catch (error) {\n console.error(`[EpisodaClient] Handler error for ${event.type}:`, error)\n }\n })\n }\n\n /**\n * Handle incoming message from server\n */\n private handleMessage(message: ServerMessage): void {\n // Special handling for graceful shutdown messages from server\n if (message.type === 'shutdown') {\n console.log('[EpisodaClient] Received graceful shutdown message from server')\n this.isGracefulShutdown = true\n }\n\n // EP648: Handle error messages, especially rate limiting and rapid reconnect\n if (message.type === 'error') {\n const errorMessage = message as ServerMessage & { code?: string; retryAfter?: number }\n this.lastErrorCode = errorMessage.code\n\n if (errorMessage.code === 'RATE_LIMITED' || errorMessage.code === 'TOO_SOON') {\n // Use server-provided retryAfter or default to 60 seconds for rate limit, 5 seconds for too soon\n const defaultRetry = errorMessage.code === 'RATE_LIMITED' ? 60 : 5\n const retryAfterMs = (errorMessage.retryAfter || defaultRetry) * 1000\n this.rateLimitBackoffUntil = Date.now() + retryAfterMs\n console.log(`[EpisodaClient] ${errorMessage.code}: will retry after ${retryAfterMs / 1000}s`)\n }\n }\n\n const handlers = this.eventHandlers.get(message.type) || []\n\n // Execute all handlers for this message type\n handlers.forEach(handler => {\n try {\n handler(message)\n } catch (error) {\n console.error(`[EpisodaClient] Handler error for ${message.type}:`, error)\n }\n })\n }\n\n /**\n * Schedule reconnection with simplified retry logic\n *\n * EP843: Simplified from complex exponential backoff to fast-fail approach\n *\n * Strategy:\n * - For graceful shutdown (server restart): Quick retry (500ms, 1s, 2s) up to 3 attempts\n * - For other disconnects: 1 retry after 1 second, then stop\n * - Always respect rate limits from server\n * - Surface errors quickly so user can take action\n *\n * This replaces the previous 6-hour retry with exponential backoff,\n * which masked problems and delayed error visibility.\n */\n private scheduleReconnect(): void {\n // Don't reconnect if user intentionally disconnected\n if (this.isIntentionalDisconnect) {\n console.log('[EpisodaClient] Intentional disconnect - not reconnecting')\n return\n }\n\n // Don't schedule if reconnection is already pending\n if (this.reconnectTimeout) {\n console.log('[EpisodaClient] Reconnection already scheduled, skipping duplicate')\n return\n }\n\n // Clear heartbeat timers before reconnection attempt\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = undefined\n }\n\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n this.heartbeatTimeoutTimer = undefined\n }\n\n // Check if we're still in rate limit backoff period\n if (this.rateLimitBackoffUntil && Date.now() < this.rateLimitBackoffUntil) {\n const waitTime = this.rateLimitBackoffUntil - Date.now()\n console.log(`[EpisodaClient] Rate limited, waiting ${Math.round(waitTime / 1000)}s before retry`)\n this.reconnectAttempts++\n this.reconnectTimeout = setTimeout(() => {\n this.rateLimitBackoffUntil = undefined\n this.scheduleReconnect()\n }, waitTime)\n return\n }\n\n // EP843: Simplified retry logic\n let delay: number\n let shouldRetry = true\n\n if (this.isGracefulShutdown) {\n // Graceful shutdown (server restart): Quick retry up to 7 attempts\n // EP843: Increased from 3 to 7 to cover longer server restart windows (deploys, migrations)\n // Retry schedule: 500ms, 1s, 2s, 4s, 5s, 5s, 5s = ~22.5s total coverage\n if (this.reconnectAttempts >= 7) {\n console.error('[EpisodaClient] Server restart reconnection failed after 7 attempts. Run \"episoda dev\" to reconnect.')\n shouldRetry = false\n } else {\n // Exponential backoff capped at 5s: 500ms, 1s, 2s, 4s, 5s, 5s, 5s\n delay = Math.min(500 * Math.pow(2, this.reconnectAttempts), 5000)\n console.log(`[EpisodaClient] Server restarting, reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}/7)`)\n }\n } else {\n // EP956: Non-graceful disconnects (code 1006 etc): 5 retries with exponential backoff\n // Previously only 1 retry, which was too aggressive at failing\n // Retry schedule: 1s, 2s, 4s, 8s, 16s = ~31s total coverage\n const MAX_NON_GRACEFUL_RETRIES = 5\n if (this.reconnectAttempts >= MAX_NON_GRACEFUL_RETRIES) {\n // EP843: Improved error message with more context\n console.error(`[EpisodaClient] Connection lost. Reconnection failed after ${MAX_NON_GRACEFUL_RETRIES} attempts. Check server status or restart with \"episoda dev\".`)\n shouldRetry = false\n } else {\n // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 16000)\n console.log(`[EpisodaClient] Connection lost, retrying in ${delay / 1000}s... (attempt ${this.reconnectAttempts + 1}/${MAX_NON_GRACEFUL_RETRIES})`)\n }\n }\n\n if (!shouldRetry) {\n // Emit disconnected event so daemon can handle it\n this.emit({\n type: 'disconnected',\n code: 1006,\n reason: 'Reconnection attempts exhausted',\n willReconnect: false\n })\n return\n }\n\n this.reconnectAttempts++\n\n this.reconnectTimeout = setTimeout(() => {\n console.log('[EpisodaClient] Attempting reconnection...')\n this.connect(this.url, this.token, this.machineId, {\n hostname: this.hostname,\n osPlatform: this.osPlatform,\n osArch: this.osArch,\n daemonPid: this.daemonPid\n }).then(() => {\n console.log('[EpisodaClient] Reconnection successful')\n this.reconnectAttempts = 0\n this.isGracefulShutdown = false\n this.firstDisconnectTime = undefined\n this.rateLimitBackoffUntil = undefined\n }).catch(error => {\n console.error('[EpisodaClient] Reconnection failed:', error.message)\n // scheduleReconnect will be called again from the 'close' event\n })\n }, delay!)\n }\n\n /**\n * EP605: Start client-side heartbeat to detect dead connections\n *\n * Sends ping every 45 seconds and expects pong within 15 seconds.\n * If no pong received, terminates connection to trigger reconnection.\n */\n private startHeartbeat(): void {\n // Clear any existing heartbeat\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n }\n\n this.heartbeatTimer = setInterval(() => {\n if (!this.ws || this.ws.readyState !== WS.OPEN) {\n return\n }\n\n // Send ping\n try {\n this.ws.ping()\n\n // Clear any existing timeout before setting new one (prevents timer leak)\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n }\n\n // Set timeout for pong response\n this.heartbeatTimeoutTimer = setTimeout(() => {\n console.log('[EpisodaClient] Heartbeat timeout - no pong received, terminating connection')\n if (this.ws) {\n this.ws.terminate() // Force close to trigger reconnection\n }\n }, CLIENT_HEARTBEAT_TIMEOUT)\n } catch (error) {\n console.error('[EpisodaClient] Error sending heartbeat ping:', error)\n }\n }, CLIENT_HEARTBEAT_INTERVAL)\n }\n}\n","/**\n * Authentication utilities\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport { execSync } from 'child_process'\nimport { EpisodaConfig } from './command-protocol'\n\nconst DEFAULT_CONFIG_FILE = 'config.json'\n\n/**\n * Get the config directory path\n * Supports EPISODA_CONFIG_DIR env var for running multiple environments\n */\nexport function getConfigDir(): string {\n return process.env.EPISODA_CONFIG_DIR || path.join(os.homedir(), '.episoda')\n}\n\n/**\n * Get the full path to the config file\n */\nexport function getConfigPath(configPath?: string): string {\n if (configPath) {\n return configPath\n }\n return path.join(getConfigDir(), DEFAULT_CONFIG_FILE)\n}\n\n/**\n * Ensure the config directory exists\n *\n * EP798: Also excludes the directory from iCloud/Dropbox sync on macOS\n * to prevent machine-specific files (machine-id, daemon.sock) from syncing.\n */\nfunction ensureConfigDir(configPath: string): void {\n const dir = path.dirname(configPath)\n const isNew = !fs.existsSync(dir)\n\n if (isNew) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n }\n\n // EP798: Exclude from cloud sync on macOS\n // Only do this once when directory is created (or if .nosync doesn't exist)\n if (process.platform === 'darwin') {\n const nosyncPath = path.join(dir, '.nosync')\n if (isNew || !fs.existsSync(nosyncPath)) {\n try {\n // Create .nosync file (respected by some cloud services)\n fs.writeFileSync(nosyncPath, '', { mode: 0o600 })\n // Set iCloud-specific exclusion attribute\n execSync(`xattr -w com.apple.fileprovider.ignore 1 \"${dir}\"`, {\n stdio: 'ignore',\n timeout: 5000\n })\n } catch {\n // Ignore errors - xattr may not be available or dir may already be excluded\n }\n }\n }\n}\n\n/**\n * Load configuration from .episoda/config.json\n */\nexport async function loadConfig(configPath?: string): Promise<EpisodaConfig | null> {\n const fullPath = getConfigPath(configPath)\n\n if (!fs.existsSync(fullPath)) {\n return null\n }\n\n try {\n const content = fs.readFileSync(fullPath, 'utf8')\n const config = JSON.parse(content) as EpisodaConfig\n return config\n } catch (error) {\n console.error('Error loading config:', error)\n return null\n }\n}\n\n/**\n * Save configuration to .episoda/config.json\n */\nexport async function saveConfig(config: EpisodaConfig, configPath?: string): Promise<void> {\n const fullPath = getConfigPath(configPath)\n ensureConfigDir(fullPath)\n\n try {\n const content = JSON.stringify(config, null, 2)\n fs.writeFileSync(fullPath, content, { mode: 0o600 })\n } catch (error) {\n throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\n/**\n * Validate an access token\n */\nexport async function validateToken(token: string): Promise<boolean> {\n // For now, just check if token exists and is non-empty\n // In the future, we can make an API call to validate\n return Boolean(token && token.length > 0)\n}\n","/**\n * Error handling utilities\n */\n\nimport { ErrorCode } from './command-protocol'\n\n/**\n * Custom error class for Episoda operations\n */\nexport class EpisodaError extends Error {\n constructor(\n public code: ErrorCode,\n message: string,\n public details?: Record<string, any>\n ) {\n super(message)\n this.name = 'EpisodaError'\n }\n}\n\n// Note: parseGitError is exported from git-parser.ts\n\n/**\n * Create a user-friendly error message from error code\n * (For CLI use - MCP will format differently)\n */\nexport function formatErrorMessage(code: ErrorCode, details?: Record<string, any>): string {\n const messages: Record<ErrorCode, string> = {\n 'GIT_NOT_INSTALLED': 'Git is not installed or not in PATH',\n 'NOT_GIT_REPO': 'Not a git repository',\n 'MERGE_CONFLICT': 'Merge conflict detected',\n 'REBASE_CONFLICT': 'Rebase conflict detected - resolve conflicts or abort rebase',\n 'UNCOMMITTED_CHANGES': 'Uncommitted changes would be overwritten',\n 'NETWORK_ERROR': 'Network error occurred',\n 'AUTH_FAILURE': 'Authentication failed',\n 'BRANCH_NOT_FOUND': 'Branch not found',\n 'BRANCH_ALREADY_EXISTS': 'Branch already exists',\n 'PUSH_REJECTED': 'Push rejected by remote',\n 'COMMAND_TIMEOUT': 'Command timed out',\n // EP944: Worktree error messages\n 'WORKTREE_EXISTS': 'Worktree already exists at this path',\n 'WORKTREE_NOT_FOUND': 'Worktree not found at this path',\n 'WORKTREE_LOCKED': 'Worktree is locked',\n 'BRANCH_IN_USE': 'Branch is already checked out in another worktree',\n 'UNKNOWN_ERROR': 'Unknown error occurred'\n }\n\n let message = messages[code] || `Error: ${code}`\n\n // Add details if provided\n if (details) {\n if (details.uncommittedFiles && details.uncommittedFiles.length > 0) {\n message += `\\nUncommitted files: ${details.uncommittedFiles.join(', ')}`\n }\n if (details.conflictingFiles && details.conflictingFiles.length > 0) {\n message += `\\nConflicting files: ${details.conflictingFiles.join(', ')}`\n }\n if (details.branchName) {\n message += `\\nBranch: ${details.branchName}`\n }\n }\n\n return message\n}\n","/**\n * @episoda/core\n *\n * Reusable business logic for Episoda local development orchestration.\n * Used by both episoda CLI (current) and @episoda/mcp-local-dev (future).\n *\n * DESIGN PRINCIPLES:\n * - Interface-agnostic: No CLI or MCP-specific code\n * - Structured data: Return objects, not formatted strings\n * - Error codes: Return codes, not messages\n * - Reusable: 80% code reuse target for MCP conversion\n */\n\n// Core types (LOCKED for Phase 2)\nexport * from './command-protocol'\n\n// Business logic classes\nexport { GitExecutor } from './git-executor'\nexport { EpisodaClient, type DisconnectEvent, type ClientEvent, type EventHandler } from './websocket-client'\n\n// Utilities\nexport * from './auth'\nexport * from './errors'\nexport * from './git-validator'\nexport * from './git-parser'\n\n// Package version - single source of truth from package.json\nexport { VERSION } from './version'\n","/**\n * Port availability checker\n */\n\nimport * as net from 'net'\n\n/**\n * Check if a port is in use\n * @param port Port number to check\n * @returns true if port is in use, false otherwise\n */\nexport async function isPortInUse(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer()\n\n server.once('error', (err: any) => {\n if (err.code === 'EADDRINUSE') {\n resolve(true)\n } else {\n resolve(false)\n }\n })\n\n server.once('listening', () => {\n server.close()\n resolve(false)\n })\n\n // Don't specify host - bind to all interfaces to detect any listener\n // This matches how Next.js and other dev servers bind\n server.listen(port)\n })\n}\n\n/**\n * Get the server port from config or default\n * @returns Port number\n */\nexport function getServerPort(): number {\n // Check environment variable\n if (process.env.PORT) {\n return parseInt(process.env.PORT, 10)\n }\n\n // Default to 3000 (Next.js default)\n return 3000\n}\n","{\n \"name\": \"episoda\",\n \"version\": \"0.2.34\",\n \"description\": \"CLI tool for Episoda local development workflow orchestration\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"bin\": {\n \"episoda\": \"dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"clean\": \"rm -rf dist\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"keywords\": [\n \"episoda\",\n \"cli\",\n \"git\",\n \"workflow\",\n \"development\"\n ],\n \"author\": \"Episoda\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"chalk\": \"^4.1.2\",\n \"commander\": \"^11.1.0\",\n \"ora\": \"^5.4.1\",\n \"semver\": \"7.7.3\",\n \"tar\": \"7.5.2\",\n \"ws\": \"^8.18.0\",\n \"zod\": \"^4.0.10\"\n },\n \"optionalDependencies\": {\n \"@anthropic-ai/claude-code\": \"^1.0.0\"\n },\n \"devDependencies\": {\n \"@episoda/core\": \"*\",\n \"@types/node\": \"^20.11.24\",\n \"@types/semver\": \"7.7.1\",\n \"@types/tar\": \"6.1.13\",\n \"@types/ws\": \"^8.5.10\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"^5.3.3\"\n },\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/v20x/episoda.git\",\n \"directory\": \"packages/episoda\"\n }\n}\n","/**\n * Machine ID generation and management\n *\n * EP812: Changed to use UUID format for consistency across the system.\n * The machine ID is now a proper UUID that can be used directly as the\n * database local_machine.id, eliminating the need for separate TEXT and UUID IDs.\n *\n * Format: Standard UUID v4 (e.g., \"550e8400-e29b-41d4-a716-446655440000\")\n *\n * EP731: Derives UUID deterministically from hardware UUID to prevent duplicate\n * device registrations when ~/.episoda syncs between devices via iCloud/Dropbox.\n *\n * Properties:\n * - Stable across daemon restarts\n * - Unique per PHYSICAL machine (derived from hardware UUID)\n * - Standard UUID format (works as database PK)\n * - Survives OS reboots\n * - Cannot sync between devices (hardware-based)\n *\n * Migration: Existing TEXT machine IDs (e.g., \"hostname-a3f2b1c4\") are\n * automatically migrated to UUID format on first read.\n */\n\nimport * as os from 'os'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as crypto from 'crypto'\nimport { execSync } from 'child_process'\nimport { getConfigDir } from '@episoda/core'\n\n/**\n * Check if a string is a valid UUID format\n */\nfunction isValidUUID(str: string): boolean {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n return uuidRegex.test(str)\n}\n\n/**\n * Get or generate machine ID\n *\n * EP812: Now returns a UUID format machine ID that can be used directly\n * as the database local_machine.id.\n *\n * Reads from config directory's machine-id if exists, otherwise generates new one\n * and saves it for future use. Migrates old TEXT format IDs to UUID.\n *\n * @returns Machine ID as UUID string\n */\nexport async function getMachineId(): Promise<string> {\n const machineIdPath = path.join(getConfigDir(), 'machine-id')\n\n // Try to read existing machine ID\n try {\n if (fs.existsSync(machineIdPath)) {\n const existingId = fs.readFileSync(machineIdPath, 'utf-8').trim()\n if (existingId) {\n // EP812: Check if already UUID format\n if (isValidUUID(existingId)) {\n return existingId\n }\n // EP812: Migrate old TEXT format to UUID\n // Generate new UUID based on hardware to maintain device uniqueness\n console.log('[MachineId] Migrating legacy machine ID to UUID format...')\n const newUUID = generateMachineId()\n fs.writeFileSync(machineIdPath, newUUID, 'utf-8')\n console.log(`[MachineId] Migrated: ${existingId} → ${newUUID}`)\n return newUUID\n }\n }\n } catch (error) {\n // File doesn't exist or can't be read, generate new one\n }\n\n // Generate new machine ID (UUID format)\n const machineId = generateMachineId()\n\n // Save to file\n try {\n const dir = path.dirname(machineIdPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(machineIdPath, machineId, 'utf-8')\n } catch (error) {\n console.error('Warning: Could not save machine ID to disk:', error)\n // Continue anyway - machine ID will be regenerated next time\n }\n\n return machineId\n}\n\n/**\n * EP731: Get hardware UUID for the current machine\n *\n * Uses platform-specific methods to get a hardware identifier that is:\n * - Unique per physical machine\n * - Stable across reboots\n * - Cannot sync between devices\n *\n * Falls back to random UUID if hardware UUID cannot be obtained.\n *\n * @returns Hardware UUID string\n */\nfunction getHardwareUUID(): string {\n try {\n if (process.platform === 'darwin') {\n // macOS: Get IOPlatformUUID from ioreg\n const output = execSync(\n 'ioreg -d2 -c IOPlatformExpertDevice | awk -F\\\\\" \\'/IOPlatformUUID/{print $(NF-1)}\\'',\n { encoding: 'utf-8', timeout: 5000 }\n ).trim()\n if (output && output.length > 0) {\n return output\n }\n } else if (process.platform === 'linux') {\n // Linux: Read /etc/machine-id\n if (fs.existsSync('/etc/machine-id')) {\n const machineId = fs.readFileSync('/etc/machine-id', 'utf-8').trim()\n if (machineId && machineId.length > 0) {\n return machineId\n }\n }\n // Fallback: Try /var/lib/dbus/machine-id\n if (fs.existsSync('/var/lib/dbus/machine-id')) {\n const dbusId = fs.readFileSync('/var/lib/dbus/machine-id', 'utf-8').trim()\n if (dbusId && dbusId.length > 0) {\n return dbusId\n }\n }\n } else if (process.platform === 'win32') {\n // Windows: Get UUID from wmic\n const output = execSync('wmic csproduct get uuid', {\n encoding: 'utf-8',\n timeout: 5000\n })\n const lines = output.trim().split('\\n')\n if (lines.length >= 2) {\n const uuid = lines[1].trim()\n if (uuid && uuid.length > 0 && uuid !== 'UUID') {\n return uuid\n }\n }\n }\n } catch (error) {\n // Hardware UUID retrieval failed, will fall back to random\n console.warn('Could not get hardware UUID, using random fallback:', error)\n }\n\n // Fallback: Generate random UUID\n return crypto.randomUUID()\n}\n\n/**\n * Generate a new machine ID as UUID\n *\n * EP812: Now generates a proper UUID that can be used as database PK.\n * EP731: Derives UUID deterministically from hardware UUID to ensure uniqueness\n * per physical machine, even if ~/.episoda syncs between devices.\n *\n * Format: Standard UUID v4\n * Example: \"550e8400-e29b-41d4-a716-446655440000\"\n *\n * @returns Generated machine ID as UUID\n */\nfunction generateMachineId(): string {\n const hwUUID = getHardwareUUID()\n\n // If hardware UUID is already a valid UUID, use it directly\n if (isValidUUID(hwUUID)) {\n return hwUUID.toLowerCase()\n }\n\n // Otherwise, derive a deterministic UUID from the hardware identifier\n // This ensures the same physical machine always gets the same UUID\n const hash = crypto.createHash('sha256').update(hwUUID).digest('hex')\n\n // Format as UUID v4 (but deterministic based on hardware)\n // Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\n // where y is 8, 9, a, or b\n const uuid = [\n hash.slice(0, 8),\n hash.slice(8, 12),\n '4' + hash.slice(13, 16), // Version 4\n ((parseInt(hash.slice(16, 17), 16) & 0x3) | 0x8).toString(16) + hash.slice(17, 20), // Variant\n hash.slice(20, 32)\n ].join('-')\n\n return uuid.toLowerCase()\n}\n\n/**\n * Reset machine ID (force regeneration)\n *\n * Deletes the stored machine ID file, forcing a new ID to be generated\n * on next getMachineId() call.\n *\n * Use case: Developer explicitly wants to change their machine identity\n */\nexport function resetMachineId(): void {\n const machineIdPath = path.join(getConfigDir(), 'machine-id')\n try {\n if (fs.existsSync(machineIdPath)) {\n fs.unlinkSync(machineIdPath)\n }\n } catch (error) {\n throw new Error(`Failed to reset machine ID: ${error}`)\n }\n}\n","/**\n * Project tracking for daemon\n *\n * Manages ~/.episoda/projects.json which tracks all projects\n * the daemon is monitoring.\n *\n * Format:\n * {\n * \"projects\": [\n * {\n * \"id\": \"proj_abc123\",\n * \"path\": \"/Users/alan/Dev/my-project\",\n * \"name\": \"my-project\",\n * \"added_at\": \"2025-01-18T10:30:00.000Z\",\n * \"last_active\": \"2025-01-18T12:45:00.000Z\"\n * }\n * ]\n * }\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { getConfigDir } from '@episoda/core'\n\nexport interface TrackedProject {\n id: string // Supabase project ID\n path: string // Absolute path to project directory\n name: string // Project name (from directory)\n added_at: string // ISO timestamp when first added\n last_active: string // ISO timestamp of last activity\n // EP971: All projects use worktree architecture\n bareRepoPath?: string // Path to .bare/ directory\n}\n\ninterface ProjectsData {\n projects: TrackedProject[]\n}\n\n/**\n * Get path to projects.json file\n */\nfunction getProjectsFilePath(): string {\n return path.join(getConfigDir(), 'projects.json')\n}\n\n/**\n * Read projects from file\n *\n * @returns Projects data, or empty list if file doesn't exist\n */\nfunction readProjects(): ProjectsData {\n const projectsPath = getProjectsFilePath()\n\n try {\n if (!fs.existsSync(projectsPath)) {\n return { projects: [] }\n }\n\n const content = fs.readFileSync(projectsPath, 'utf-8')\n const data = JSON.parse(content) as ProjectsData\n\n // Validate structure\n if (!data.projects || !Array.isArray(data.projects)) {\n console.warn('Invalid projects.json structure, resetting')\n return { projects: [] }\n }\n\n return data\n } catch (error) {\n console.error('Error reading projects.json:', error)\n return { projects: [] }\n }\n}\n\n/**\n * Write projects to file\n */\nfunction writeProjects(data: ProjectsData): void {\n const projectsPath = getProjectsFilePath()\n\n try {\n // Ensure directory exists\n const dir = path.dirname(projectsPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n fs.writeFileSync(projectsPath, JSON.stringify(data, null, 2), 'utf-8')\n } catch (error) {\n throw new Error(`Failed to write projects.json: ${error}`)\n }\n}\n\n/**\n * Options for adding a project\n * EP971: All projects use worktree architecture\n */\nexport interface AddProjectOptions {\n bareRepoPath?: string // Path to .bare/ directory\n}\n\n/**\n * Add or update a project\n *\n * EP593: Now enforces one entry per projectId to prevent duplicate connections.\n * When adding a project:\n * - If same path exists, update last_active\n * - If same projectId exists with different path, REPLACE the old entry\n * (user wants git operations in the new directory)\n * - Otherwise, add new project\n *\n * EP971: All projects use worktree architecture.\n *\n * @param projectId Supabase project ID\n * @param projectPath Absolute path to project\n * @param options Optional settings (bareRepoPath)\n * @returns The tracked project\n */\nexport function addProject(\n projectId: string,\n projectPath: string,\n options?: AddProjectOptions\n): TrackedProject {\n const data = readProjects()\n const now = new Date().toISOString()\n\n // Check if project already exists by path (exact match)\n const existingByPath = data.projects.find(p => p.path === projectPath)\n\n if (existingByPath) {\n // Update existing project\n existingByPath.id = projectId // Update ID in case it changed\n existingByPath.last_active = now\n // EP971: Update bare repo path if specified\n if (options?.bareRepoPath) {\n existingByPath.bareRepoPath = options.bareRepoPath\n }\n writeProjects(data)\n return existingByPath\n }\n\n // EP593: Check if project exists by ID (different path)\n // This prevents multiple entries for the same project from different directories\n const existingByIdIndex = data.projects.findIndex(p => p.id === projectId)\n\n if (existingByIdIndex !== -1) {\n const existingById = data.projects[existingByIdIndex]\n console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`)\n\n // Remove the old entry\n data.projects.splice(existingByIdIndex, 1)\n }\n\n // Add new project\n const projectName = path.basename(projectPath)\n const newProject: TrackedProject = {\n id: projectId,\n path: projectPath,\n name: projectName,\n added_at: now,\n last_active: now,\n // EP971: Bare repo path for worktree architecture\n bareRepoPath: options?.bareRepoPath,\n }\n\n data.projects.push(newProject)\n writeProjects(data)\n\n return newProject\n}\n\n/**\n * Remove a project\n *\n * @param projectPath Absolute path to project\n * @returns true if removed, false if not found\n */\nexport function removeProject(projectPath: string): boolean {\n const data = readProjects()\n const initialLength = data.projects.length\n\n data.projects = data.projects.filter(p => p.path !== projectPath)\n\n if (data.projects.length < initialLength) {\n writeProjects(data)\n return true\n }\n\n return false\n}\n\n/**\n * Get a project by path\n *\n * @param projectPath Absolute path to project\n * @returns Project if found, null otherwise\n */\nexport function getProject(projectPath: string): TrackedProject | null {\n const data = readProjects()\n return data.projects.find(p => p.path === projectPath) || null\n}\n\n/**\n * Get a project by ID\n *\n * @param projectId Supabase project ID\n * @returns Project if found, null otherwise\n */\nexport function getProjectById(projectId: string): TrackedProject | null {\n const data = readProjects()\n return data.projects.find(p => p.id === projectId) || null\n}\n\n/**\n * Get all tracked projects\n *\n * @returns Array of tracked projects\n */\nexport function getAllProjects(): TrackedProject[] {\n const data = readProjects()\n return data.projects\n}\n\n/**\n * Update last active timestamp for a project\n *\n * @param projectPath Absolute path to project\n */\nexport function touchProject(projectPath: string): void {\n const data = readProjects()\n const project = data.projects.find(p => p.path === projectPath)\n\n if (project) {\n project.last_active = new Date().toISOString()\n writeProjects(data)\n }\n}\n\n/**\n * Clean up stale projects\n *\n * Removes projects that:\n * - Haven't been active in N days\n * - Directory no longer exists\n *\n * @param maxAgeDays Maximum age in days (default: 30)\n * @returns Number of projects removed\n */\nexport function cleanupStaleProjects(maxAgeDays: number = 30): number {\n const data = readProjects()\n const now = Date.now()\n const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000\n\n const initialLength = data.projects.length\n\n data.projects = data.projects.filter(project => {\n // Check if directory still exists\n if (!fs.existsSync(project.path)) {\n return false\n }\n\n // Check if too old\n const lastActive = new Date(project.last_active).getTime()\n const age = now - lastActive\n\n return age < maxAgeMs\n })\n\n const removedCount = initialLength - data.projects.length\n\n if (removedCount > 0) {\n writeProjects(data)\n }\n\n return removedCount\n}\n\n/**\n * Clear all projects\n *\n * USE WITH CAUTION - removes all tracked projects\n */\nexport function clearAllProjects(): void {\n writeProjects({ projects: [] })\n}\n","/**\n * Daemon lifecycle management\n *\n * Manages the Episoda daemon process lifecycle:\n * - Spawning daemon in detached mode\n * - Checking daemon status\n * - Stopping daemon gracefully\n * - PID file management\n *\n * Ensures only one daemon runs per user.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { spawn, execSync } from 'child_process'\nimport { getConfigDir } from '@episoda/core'\n\n/**\n * EP734: Kill all stale Episoda processes\n *\n * Finds and kills any existing episoda-related processes to ensure\n * a clean slate before starting a new daemon. This prevents:\n * - Multiple daemon processes running simultaneously\n * - Stale `episoda dev` foreground processes from previous sessions\n * - Orphaned node processes running daemon-process.js\n *\n * @returns Number of processes killed\n */\nexport function killAllEpisodaProcesses(): number {\n const currentPid = process.pid\n let killedCount = 0\n\n // EP734: Windows not supported for process cleanup - skip with warning\n if (process.platform === 'win32') {\n console.warn('[Cleanup] Process cleanup not supported on Windows - skipping')\n return 0\n }\n\n try {\n // Find all episoda-related processes using ps (Unix/macOS only)\n // Patterns: 'episoda dev', 'daemon-process.js', 'episoda status', 'ws-server.js'\n // EP797: Added ws-server.js to catch orphaned WebSocket server processes\n const psOutput = execSync(\n 'ps aux | grep -E \"(episoda (dev|status|stop)|daemon-process\\\\.js|node ws-server\\\\.js)\" | grep -v grep || true',\n { encoding: 'utf-8', timeout: 5000 }\n )\n\n const lines = psOutput.trim().split('\\n').filter(line => line.length > 0)\n\n for (const line of lines) {\n // Parse PID from ps aux output (second column)\n const parts = line.trim().split(/\\s+/)\n if (parts.length < 2) continue\n\n const pid = parseInt(parts[1], 10)\n if (isNaN(pid) || pid === currentPid) continue\n\n try {\n process.kill(pid, 'SIGTERM')\n killedCount++\n console.log(`[Cleanup] Killed stale process PID ${pid}`)\n } catch (err) {\n // Process might have already exited, ignore\n }\n }\n\n // Give processes a moment to terminate\n if (killedCount > 0) {\n execSync('sleep 0.5', { timeout: 2000 })\n }\n\n // Clean up stale files\n const configDir = getConfigDir()\n const pidPath = path.join(configDir, 'daemon.pid')\n const sockPath = path.join(configDir, 'daemon.sock')\n\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n if (fs.existsSync(sockPath)) {\n fs.unlinkSync(sockPath)\n }\n\n } catch (error) {\n // Non-fatal - best effort cleanup\n console.warn('[Cleanup] Error during process cleanup:', error instanceof Error ? error.message : error)\n }\n\n return killedCount\n}\n\n/**\n * Get path to daemon PID file\n */\nexport function getPidFilePath(): string {\n return path.join(getConfigDir(), 'daemon.pid')\n}\n\n/**\n * Check if daemon is running\n *\n * Reads PID from file and checks if process exists.\n *\n * @returns PID if running, null if not\n */\nexport function isDaemonRunning(): number | null {\n const pidPath = getPidFilePath()\n\n try {\n if (!fs.existsSync(pidPath)) {\n return null\n }\n\n const pidStr = fs.readFileSync(pidPath, 'utf-8').trim()\n const pid = parseInt(pidStr, 10)\n\n if (isNaN(pid)) {\n // Invalid PID, clean up file\n fs.unlinkSync(pidPath)\n return null\n }\n\n // Check if process exists\n try {\n // Sending signal 0 checks existence without killing\n process.kill(pid, 0)\n return pid\n } catch (error) {\n // Process doesn't exist, clean up stale PID file\n fs.unlinkSync(pidPath)\n return null\n }\n } catch (error) {\n console.error('Error checking daemon status:', error)\n return null\n }\n}\n\n/**\n * Start the daemon process\n *\n * Spawns daemon in detached mode. Daemon survives terminal close.\n *\n * @throws Error if daemon already running or spawn fails\n */\nexport async function startDaemon(): Promise<number> {\n // Check if already running\n const existingPid = isDaemonRunning()\n if (existingPid) {\n throw new Error(`Daemon already running (PID: ${existingPid})`)\n }\n\n // Ensure config directory exists\n const configDir = getConfigDir()\n if (!fs.existsSync(configDir)) {\n fs.mkdirSync(configDir, { recursive: true })\n }\n\n // Path to daemon entry point\n // When built: dist/daemon/daemon-process.js (tsup preserves directory structure)\n // __dirname is dist/ when running from bundled index.js\n const daemonScript = path.join(__dirname, 'daemon', 'daemon-process.js')\n\n if (!fs.existsSync(daemonScript)) {\n throw new Error(`Daemon script not found: ${daemonScript}. Make sure CLI is built.`)\n }\n\n // EP813: Debug - log daemon output to file for troubleshooting\n const logPath = path.join(configDir, 'daemon.log')\n const logFd = fs.openSync(logPath, 'a')\n\n // Spawn daemon as detached process\n const child = spawn('node', [daemonScript], {\n detached: true, // Run independently of parent\n stdio: ['ignore', logFd, logFd], // EP813: Redirect stdout/stderr to log file\n env: {\n ...process.env,\n EPISODA_DAEMON_MODE: '1', // Signal to daemon it's running in daemon mode\n },\n })\n\n // Detach from parent process\n child.unref()\n\n const pid = child.pid!\n\n // Save PID to file\n const pidPath = getPidFilePath()\n fs.writeFileSync(pidPath, pid.toString(), 'utf-8')\n\n // Give daemon a moment to start\n await new Promise(resolve => setTimeout(resolve, 500))\n\n // Verify daemon actually started\n const runningPid = isDaemonRunning()\n if (!runningPid) {\n throw new Error('Daemon failed to start')\n }\n\n return pid\n}\n\n/**\n * Stop the daemon process\n *\n * Sends SIGTERM for graceful shutdown. If daemon doesn't stop\n * within timeout, sends SIGKILL.\n *\n * @param timeout Milliseconds to wait before SIGKILL (default: 5000)\n * @returns true if stopped, false if wasn't running\n */\nexport async function stopDaemon(timeout: number = 5000): Promise<boolean> {\n const pid = isDaemonRunning()\n if (!pid) {\n // Clean up PID file just in case\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n return false\n }\n\n try {\n // Send SIGTERM for graceful shutdown\n process.kill(pid, 'SIGTERM')\n\n // Wait for process to exit\n const startTime = Date.now()\n while (Date.now() - startTime < timeout) {\n try {\n process.kill(pid, 0) // Check if still alive\n await new Promise(resolve => setTimeout(resolve, 100))\n } catch (error) {\n // Process exited\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n return true\n }\n }\n\n // Timeout reached, force kill\n console.warn(`Daemon didn't stop gracefully, forcing shutdown (PID: ${pid})`)\n process.kill(pid, 'SIGKILL')\n\n // Clean up PID file\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n\n return true\n } catch (error) {\n console.error('Error stopping daemon:', error)\n return false\n }\n}\n\n/**\n * Restart the daemon\n *\n * Stops existing daemon and starts new one.\n *\n * @returns PID of new daemon\n */\nexport async function restartDaemon(): Promise<number> {\n await stopDaemon()\n return startDaemon()\n}\n\n/**\n * Get daemon status information\n *\n * @returns Status object with running state and PID\n */\nexport function getDaemonStatus(): { running: boolean; pid: number | null } {\n const pid = isDaemonRunning()\n return {\n running: pid !== null,\n pid,\n }\n}\n","/**\n * IPC Server - Runs in daemon process\n *\n * Listens for commands from CLI via Unix domain socket.\n * Handles command routing and response sending.\n *\n * Socket location: ~/.episoda/daemon.sock\n */\n\nimport * as net from 'net'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { getConfigDir } from '@episoda/core'\n\nconst getSocketPath = () => path.join(getConfigDir(), 'daemon.sock')\n\nexport interface IPCRequest {\n id: string // Request ID for matching responses\n command: string // Command name (e.g., 'add-project', 'status')\n params?: any // Command parameters\n}\n\nexport interface IPCResponse {\n id: string // Matches request ID\n success: boolean // Success/failure\n data?: any // Response data\n error?: string // Error message if failed\n}\n\nexport type CommandHandler = (params: any) => Promise<any>\n\n/**\n * IPC Server\n *\n * Listens on Unix socket for CLI commands.\n */\nexport class IPCServer {\n private server: net.Server | null = null\n private handlers = new Map<string, CommandHandler>()\n\n /**\n * Register a command handler\n *\n * @param command Command name\n * @param handler Async function to handle command\n */\n on(command: string, handler: CommandHandler): void {\n this.handlers.set(command, handler)\n }\n\n /**\n * Start the IPC server\n *\n * Creates Unix socket and listens for connections.\n */\n async start(): Promise<void> {\n const socketPath = getSocketPath()\n\n // Clean up existing socket if it exists\n if (fs.existsSync(socketPath)) {\n fs.unlinkSync(socketPath)\n }\n\n // Ensure config directory exists\n const dir = path.dirname(socketPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n this.server = net.createServer(socket => {\n this.handleConnection(socket)\n })\n\n return new Promise((resolve, reject) => {\n this.server!.listen(socketPath, () => {\n // Set socket permissions (owner only)\n fs.chmodSync(socketPath, 0o600)\n resolve()\n })\n\n this.server!.on('error', reject)\n })\n }\n\n /**\n * Stop the IPC server\n */\n async stop(): Promise<void> {\n if (!this.server) return\n\n const socketPath = getSocketPath()\n return new Promise((resolve) => {\n this.server!.close(() => {\n // Clean up socket file\n if (fs.existsSync(socketPath)) {\n fs.unlinkSync(socketPath)\n }\n resolve()\n })\n })\n }\n\n /**\n * Handle incoming client connection\n */\n private handleConnection(socket: net.Socket): void {\n let buffer = ''\n\n socket.on('data', async (chunk) => {\n buffer += chunk.toString()\n\n // Check if we have a complete message (delimited by newline)\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex === -1) return\n\n // Extract message\n const message = buffer.slice(0, newlineIndex)\n buffer = buffer.slice(newlineIndex + 1)\n\n try {\n const request = JSON.parse(message) as IPCRequest\n const response = await this.handleRequest(request)\n\n // Send response\n socket.write(JSON.stringify(response) + '\\n')\n } catch (error) {\n const errorResponse: IPCResponse = {\n id: 'unknown',\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n socket.write(JSON.stringify(errorResponse) + '\\n')\n }\n })\n\n socket.on('error', (error) => {\n console.error('[IPC Server] Socket error:', error)\n })\n }\n\n /**\n * Handle IPC request\n */\n private async handleRequest(request: IPCRequest): Promise<IPCResponse> {\n const handler = this.handlers.get(request.command)\n\n if (!handler) {\n return {\n id: request.id,\n success: false,\n error: `Unknown command: ${request.command}`,\n }\n }\n\n try {\n const data = await handler(request.params)\n return {\n id: request.id,\n success: true,\n data,\n }\n } catch (error) {\n return {\n id: request.id,\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n }\n }\n}\n","/**\n * Episoda Daemon Process\n *\n * Main entry point for the persistent daemon that:\n * - Maintains WebSocket connections to multiple projects\n * - Listens for IPC commands from CLI\n * - Handles graceful shutdown\n * - Survives terminal close\n *\n * This file is spawned by daemon-manager.ts in detached mode.\n */\n\nimport { getMachineId } from './machine-id'\nimport { getAllProjects, addProject as trackProject, removeProject as untrackProject, touchProject } from './project-tracker'\nimport { getPidFilePath } from './daemon-manager'\nimport { IPCServer } from '../ipc/ipc-server'\nimport { loadConfig, saveConfig, EpisodaClient, GitCommand, ExecutionResult, GitExecutor, DisconnectEvent, EpisodaConfig, RemoteCommand, TunnelCommand, TunnelCommandResult, AgentCommand, AgentResult } from '@episoda/core'\nimport { checkForUpdates, performBackgroundUpdate } from '../utils/update-checker'\n// EP812: Removed IdentityServer - browser identity now uses cookie-based pairing\nimport { handleFileRead, handleFileWrite, handleFileEdit, handleFileDelete, handleFileMkdir, handleFileList, handleFileSearch, handleFileGrep, handleExec } from './handlers'\nimport { cleanupStaleCommits } from './handlers/stale-commit-cleanup'\nimport { getTunnelManager, clearTunnelUrl } from '../tunnel'\nimport { getAgentManager } from '../agent'\nimport { ensureDevServer, stopDevServer, restartDevServer, isDevServerHealthy, killProcessOnPort, getDevServerStatus } from '../utils/dev-server'\nimport { detectDevPort } from '../utils/port-detect'\n// EP957: Worktree manager for git worktree operations\nimport { WorktreeManager, findProjectRoot } from './worktree-manager'\n// EP988: Shared env setup utility\nimport { writeEnvFile } from '../utils/env-setup'\n// EP956: Worktree path resolution and port allocation for multi-tunnel mode\n// EP959-8: Added getProjectRootPath for worktree creation\nimport { getWorktreeInfoForModule, getProjectRootPath } from '../utils/worktree'\nimport { allocatePort, releasePort, clearAllPorts } from '../utils/port-allocator'\n// EP986: Import getInstallCommand for auto-dependency installation\nimport { getInstallCommand } from '../framework-detector'\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport * as path from 'path'\n\n// EP783: Get current version for update checking\nconst packageJson = require('../../package.json')\n\n// EP812: Removed identity server - browser identity now uses cookie-based pairing\n\n/**\n * EP904: Token refresh utility\n *\n * Checks if the access token is expired or about to expire, and refreshes it\n * using the refresh_token. Returns the (possibly updated) config.\n *\n * @param config Current config\n * @param bufferMs Buffer time before expiry to trigger refresh (default 5 minutes)\n * @returns Updated config with fresh token, or original if refresh not needed/failed\n */\nasync function ensureValidToken(\n config: EpisodaConfig,\n bufferMs: number = 5 * 60 * 1000\n): Promise<EpisodaConfig> {\n // Check if token is expired or about to expire\n const now = Date.now()\n const expiresAt = config.expires_at || 0\n\n if (expiresAt > now + bufferMs) {\n // Token is still valid\n return config\n }\n\n // Token needs refresh\n if (!config.refresh_token) {\n console.warn('[Daemon] EP904: Token expired but no refresh_token available')\n return config\n }\n\n console.log('[Daemon] EP904: Access token expired or expiring soon, refreshing...')\n\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetch(`${apiUrl}/api/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n refresh_token: config.refresh_token,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as { error?: string }\n console.error(`[Daemon] EP904: Token refresh failed: ${response.status} ${errorData.error || response.statusText}`)\n return config\n }\n\n const tokenResponse = await response.json() as {\n access_token: string\n refresh_token?: string\n expires_in: number\n }\n\n // Update config with new token\n const updatedConfig: EpisodaConfig = {\n ...config,\n access_token: tokenResponse.access_token,\n refresh_token: tokenResponse.refresh_token || config.refresh_token,\n expires_at: now + (tokenResponse.expires_in * 1000),\n }\n\n // Save to disk\n await saveConfig(updatedConfig)\n console.log('[Daemon] EP904: Access token refreshed successfully')\n\n return updatedConfig\n } catch (error) {\n console.error('[Daemon] EP904: Token refresh error:', error instanceof Error ? error.message : error)\n return config\n }\n}\n\n/**\n * EP904: Make an authenticated API request with automatic token refresh\n *\n * Wraps fetch with token refresh and retry logic.\n */\nasync function fetchWithAuth(\n url: string,\n options: RequestInit = {},\n retryOnUnauthorized: boolean = true\n): Promise<Response> {\n let config = await loadConfig()\n if (!config?.access_token) {\n throw new Error('No access token configured')\n }\n\n // Ensure token is valid before making request\n config = await ensureValidToken(config)\n\n const headers = {\n ...options.headers,\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json',\n }\n\n let response = await fetch(url, { ...options, headers })\n\n // If unauthorized and we have a refresh token, try refreshing and retry once\n if (response.status === 401 && retryOnUnauthorized && config.refresh_token) {\n console.log('[Daemon] EP904: Received 401, attempting token refresh and retry...')\n\n // Force token refresh\n const refreshedConfig = await ensureValidToken({ ...config, expires_at: 0 })\n\n if (refreshedConfig.access_token !== config.access_token) {\n // Token was refreshed, retry the request\n const retryHeaders = {\n ...options.headers,\n 'Authorization': `Bearer ${refreshedConfig.access_token}`,\n 'Content-Type': 'application/json',\n }\n response = await fetch(url, { ...options, headers: retryHeaders })\n }\n }\n\n return response\n}\n\n/**\n * EP973: Fetch decrypted environment variables from the server\n *\n * Calls the /api/cli/env-vars endpoint to get environment variables\n * for injection into .env files during worktree setup.\n *\n * @returns Record of key-value pairs, or empty object on error\n */\nasync function fetchEnvVars(): Promise<Record<string, string>> {\n try {\n const config = await loadConfig()\n if (!config?.project_id) {\n console.warn('[Daemon] EP973: No project_id in config, cannot fetch env vars')\n return {}\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetchWithAuth(`${apiUrl}/api/cli/env-vars`)\n\n if (!response.ok) {\n console.warn(`[Daemon] EP973: Failed to fetch env vars: ${response.status}`)\n return {}\n }\n\n const data = await response.json() as { env_vars?: Record<string, string> }\n const envVars = data.env_vars || {}\n\n console.log(`[Daemon] EP973: Fetched ${Object.keys(envVars).length} env vars from server`)\n return envVars\n } catch (error) {\n console.warn('[Daemon] EP973: Error fetching env vars:', error instanceof Error ? error.message : error)\n return {}\n }\n}\n\n/**\n * Active WebSocket connection\n */\ninterface ActiveConnection {\n projectId: string\n projectPath: string\n client: EpisodaClient\n gitExecutor: GitExecutor\n reconnectTimer?: NodeJS.Timeout\n}\n\n/**\n * Daemon state\n */\nclass Daemon {\n private machineId: string = ''\n private deviceId: string | null = null // EP726: Cached device UUID from server\n private deviceName: string | null = null // EP661: Cached device name from server\n private flyMachineId: string | null = null // EP735: Fly.io machine ID for sticky session routing\n private ipcServer: IPCServer\n // EP812: Removed identityServer - browser identity now uses cookie-based pairing\n private connections = new Map<string, ActiveConnection>() // projectPath -> connection\n // EP701: Track which connections are currently live (WebSocket open)\n // Updated by 'auth_success' (add) and 'disconnected' (remove) events\n private liveConnections = new Set<string>() // projectPath\n // EP813: Track connections that are still authenticating (in progress)\n // Prevents race condition between restoreConnections() and add-project IPC\n private pendingConnections = new Set<string>() // projectPath\n private shuttingDown = false\n // EP822: Periodic tunnel polling interval\n private tunnelPollInterval: NodeJS.Timeout | null = null\n private static readonly TUNNEL_POLL_INTERVAL_MS = 15000 // 15 seconds\n // EP822: Prevent concurrent tunnel syncs (backpressure guard)\n private tunnelSyncInProgress = false\n // EP833: Track consecutive health check failures per tunnel\n private tunnelHealthFailures = new Map<string, number>() // moduleUid -> consecutive failures\n private static readonly HEALTH_CHECK_FAILURE_THRESHOLD = 2 // Restart after 2 consecutive failures\n private static readonly HEALTH_CHECK_TIMEOUT_MS = 3000 // 3 second timeout for health checks\n // EP911: Track last reported health status to avoid unnecessary DB writes\n private lastReportedHealthStatus = new Map<string, 'healthy' | 'unhealthy' | 'unknown'>() // moduleUid -> status\n // EP837: Prevent concurrent commit syncs (backpressure guard)\n private commitSyncInProgress = false\n // EP843: Per-module mutex for tunnel operations\n // Prevents race conditions between autoStartTunnels and module_state_changed handler\n private tunnelOperationLocks = new Map<string, Promise<void>>() // moduleUid -> operation promise\n // EP929: Health check polling interval (restored from EP843 removal)\n // Health checks are orthogonal to push-based state sync - they detect dead tunnels\n private healthCheckInterval: NodeJS.Timeout | null = null\n private healthCheckInProgress = false\n private static readonly HEALTH_CHECK_INTERVAL_MS = 60000 // 60 seconds\n\n constructor() {\n this.ipcServer = new IPCServer()\n }\n\n /**\n * Start the daemon\n */\n async start(): Promise<void> {\n console.log('[Daemon] Starting Episoda daemon...')\n\n // Get machine ID\n this.machineId = await getMachineId()\n console.log(`[Daemon] Machine ID: ${this.machineId}`)\n\n // EP726: Load cached device ID from config if available\n const config = await loadConfig()\n if (config?.device_id) {\n this.deviceId = config.device_id\n console.log(`[Daemon] Loaded cached Device ID (UUID): ${this.deviceId}`)\n }\n\n // Start IPC server\n await this.ipcServer.start()\n console.log('[Daemon] IPC server started')\n\n // EP812: Removed identity server - browser identity now uses cookie-based pairing\n\n // Register IPC command handlers\n this.registerIPCHandlers()\n\n // Restore connections for tracked projects\n await this.restoreConnections()\n\n // EP822: Clean up orphaned tunnels from previous daemon runs\n await this.cleanupOrphanedTunnels()\n\n // EP957: Audit worktrees on startup to detect orphans from crashed sessions\n await this.auditWorktreesOnStartup()\n\n // EP843: Tunnel state sync polling removed - now using push-based state sync via module_state_changed events\n // this.startTunnelPolling()\n\n // EP929: Start health check polling (restored from EP843 removal)\n // Health checks are orthogonal to state sync - they detect when running tunnels die\n this.startHealthCheckPolling()\n\n // Setup graceful shutdown\n this.setupShutdownHandlers()\n\n console.log('[Daemon] Daemon started successfully')\n\n // EP783: Check for updates in background (non-blocking)\n this.checkAndNotifyUpdates()\n }\n\n /**\n * EP783: Check for CLI updates and auto-update in background\n * Non-blocking - runs after daemon starts, fails silently on errors\n */\n private async checkAndNotifyUpdates(): Promise<void> {\n try {\n const result = await checkForUpdates(packageJson.version)\n\n if (result.updateAvailable) {\n console.log(`\\n⬆️ Update available: ${result.currentVersion} → ${result.latestVersion}`)\n console.log(' Updating in background...\\n')\n performBackgroundUpdate()\n }\n } catch (error) {\n // Silently ignore - update check is non-critical\n }\n }\n\n // EP738: Removed startHttpServer - device info now flows through WebSocket broadcast + database\n\n /**\n * Register IPC command handlers\n */\n private registerIPCHandlers(): void {\n // Ping - health check\n this.ipcServer.on('ping', async () => {\n return { status: 'ok' }\n })\n\n // Status - get daemon status\n // EP726: Now includes deviceId (UUID) for unified device identification\n // EP738: Added hostname, platform, arch for status command (HTTP server removed)\n // EP776: Use liveConnections (actual WebSocket state) instead of connections Map\n this.ipcServer.on('status', async () => {\n const projects = getAllProjects().map(p => ({\n id: p.id,\n path: p.path,\n name: p.name,\n // EP843: Use actual WebSocket state instead of liveConnections Set\n // This is more reliable as it checks the real connection state\n connected: this.isWebSocketOpen(p.path),\n // Keep liveConnections for backwards compatibility and debugging\n liveConnectionsHas: this.liveConnections.has(p.path),\n }))\n\n return {\n running: true,\n machineId: this.machineId,\n deviceId: this.deviceId, // EP726: UUID for unified device identification\n hostname: os.hostname(),\n platform: os.platform(),\n arch: os.arch(),\n projects,\n }\n })\n\n // EP734: Add project - now blocking, waits for connection to complete\n // This eliminates the need for polling from the client side\n // EP813: Added retry with exponential backoff for reliability\n this.ipcServer.on('add-project', async (params: { projectId: string; projectPath: string }) => {\n const { projectId, projectPath } = params\n trackProject(projectId, projectPath)\n\n const MAX_RETRIES = 3\n const INITIAL_DELAY = 1000 // 1 second\n let lastError: string = ''\n\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n try {\n // Await connection - blocks until connected or fails\n await this.connectProject(projectId, projectPath)\n\n // EP805: Verify connection is actually healthy before returning success\n const isHealthy = this.isConnectionHealthy(projectPath)\n if (!isHealthy) {\n console.warn(`[Daemon] Connection completed but not healthy for ${projectPath}`)\n lastError = 'Connection established but not healthy'\n // Don't retry for this - it's a state issue, not a transient failure\n return { success: false, connected: false, error: lastError }\n }\n\n return { success: true, connected: true }\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] Connection attempt ${attempt}/${MAX_RETRIES} failed:`, lastError)\n\n if (attempt < MAX_RETRIES) {\n // EP813: Exponential backoff: 1s, 2s, 4s\n const delay = INITIAL_DELAY * Math.pow(2, attempt - 1)\n console.log(`[Daemon] Retrying in ${delay / 1000}s...`)\n await new Promise(resolve => setTimeout(resolve, delay))\n\n // Clean up before retry\n await this.disconnectProject(projectPath)\n }\n }\n }\n\n // All retries exhausted\n return { success: false, connected: false, error: `Failed after ${MAX_RETRIES} attempts: ${lastError}` }\n })\n\n // EP734: Removed connection-status handler - no longer needed with blocking add-project\n\n // Remove project\n this.ipcServer.on('remove-project', async (params: { projectPath: string }) => {\n const { projectPath } = params\n await this.disconnectProject(projectPath)\n untrackProject(projectPath)\n return { success: true }\n })\n\n // Connect project\n this.ipcServer.on('connect-project', async (params: { projectPath: string }) => {\n const { projectPath } = params\n const project = getAllProjects().find(p => p.path === projectPath)\n if (!project) {\n throw new Error('Project not tracked')\n }\n await this.connectProject(project.id, projectPath)\n return { success: true }\n })\n\n // Disconnect project\n this.ipcServer.on('disconnect-project', async (params: { projectPath: string }) => {\n const { projectPath } = params\n await this.disconnectProject(projectPath)\n return { success: true }\n })\n\n // Shutdown\n this.ipcServer.on('shutdown', async () => {\n console.log('[Daemon] Shutdown requested via IPC')\n await this.shutdown()\n return { success: true }\n })\n\n // EP805: Verify connection health - checks both Map and liveConnections Set\n // EP843: Also includes actual WebSocket state for more accurate diagnostics\n this.ipcServer.on('verify-health', async () => {\n const projects = getAllProjects().map(p => ({\n id: p.id,\n path: p.path,\n name: p.name,\n inConnectionsMap: this.connections.has(p.path),\n inLiveConnections: this.liveConnections.has(p.path),\n // EP843: Add actual WebSocket state\n wsOpen: this.isWebSocketOpen(p.path),\n isHealthy: this.isConnectionHealthy(p.path),\n }))\n\n const healthyCount = projects.filter(p => p.wsOpen).length // EP843: Use actual WS state\n const staleCount = projects.filter(p => p.inConnectionsMap && !p.wsOpen).length // EP843: Stale if in Map but WS not open\n\n return {\n totalProjects: projects.length,\n healthyConnections: healthyCount,\n staleConnections: staleCount,\n projects,\n }\n })\n\n // EP846-6: Verify connection with server - queries /api/cli/status\n // This is the authoritative check - local state can be stale\n this.ipcServer.on('verify-server-connection', async () => {\n const config = await loadConfig()\n if (!config?.access_token || !config?.api_url) {\n return {\n verified: false,\n error: 'No authentication configured',\n localConnected: false,\n serverConnected: false,\n }\n }\n\n // Check local state - EP843: Use actual WebSocket state\n const projects = getAllProjects()\n const localConnected = projects.some(p => this.isWebSocketOpen(p.path))\n\n // Query server for its view\n let serverConnected = false\n let serverMachineId: string | null = null\n let serverError: string | null = null\n\n try {\n const response = await fetch(`${config.api_url}/api/cli/status`, {\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (response.ok) {\n const data = await response.json() as { connected?: boolean; machine_id?: string }\n serverConnected = data.connected === true\n serverMachineId = data.machine_id || null\n } else {\n serverError = `Server returned ${response.status}`\n }\n } catch (err) {\n serverError = err instanceof Error ? err.message : 'Network error'\n }\n\n // Check if server sees THIS machine\n const machineMatch = serverMachineId === this.machineId\n\n return {\n verified: true,\n localConnected,\n serverConnected,\n machineMatch,\n machineId: this.machineId,\n serverMachineId,\n serverError,\n // Overall status: both local and server must agree\n actuallyConnected: localConnected && serverConnected && machineMatch,\n }\n })\n\n // EP823: Tunnel status - get all running tunnels\n // Returns tunnel info from in-memory TunnelManager\n this.ipcServer.on('tunnel-status', async () => {\n const tunnelManager = getTunnelManager()\n const tunnels = tunnelManager.getAllTunnels()\n return { tunnels }\n })\n\n // EP823: Tunnel stop - stop a specific tunnel by module UID\n // Stops the tunnel and clears the URL from the API\n this.ipcServer.on('tunnel-stop', async (params: { moduleUid: string }) => {\n const { moduleUid } = params\n\n // Validate input\n if (!moduleUid) {\n return { success: false, error: 'Module UID is required' }\n }\n\n const tunnelManager = getTunnelManager()\n\n // Check if tunnel exists\n if (!tunnelManager.hasTunnel(moduleUid)) {\n return { success: false, error: 'No tunnel found for this module' }\n }\n\n // Stop tunnel and dev server\n await tunnelManager.stopTunnel(moduleUid)\n await stopDevServer(moduleUid)\n\n // EP823: Clear tunnel URL from the API using shared utility\n await clearTunnelUrl(moduleUid)\n // EP911: Clear cached health status\n this.lastReportedHealthStatus.delete(moduleUid)\n this.tunnelHealthFailures.delete(moduleUid)\n console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`)\n\n return { success: true }\n })\n\n // EP932: Dev server restart - restart dev server for a module\n this.ipcServer.on('dev-server-restart', async (params: { moduleUid: string }) => {\n const { moduleUid } = params\n\n if (!moduleUid) {\n return { success: false, error: 'Module UID is required' }\n }\n\n console.log(`[Daemon] EP932: Dev server restart requested for ${moduleUid}`)\n\n const result = await restartDevServer(moduleUid)\n return result\n })\n\n // EP932: Dev server status - get status of all running dev servers\n this.ipcServer.on('dev-server-status', async () => {\n const status = getDevServerStatus()\n return { success: true, servers: status }\n })\n }\n\n /**\n * Restore WebSocket connections for tracked projects\n */\n private async restoreConnections(): Promise<void> {\n const projects = getAllProjects()\n\n for (const project of projects) {\n try {\n await this.connectProject(project.id, project.path)\n } catch (error) {\n console.error(`[Daemon] Failed to restore connection for ${project.name}:`, error)\n }\n }\n }\n\n /**\n * EP805: Check if a connection is healthy (exists AND is live)\n * A connection can exist in the Map but be dead if WebSocket disconnected\n */\n private isConnectionHealthy(projectPath: string): boolean {\n return this.connections.has(projectPath) && this.liveConnections.has(projectPath)\n }\n\n /**\n * EP843: Check if a connection's WebSocket is actually open\n *\n * This checks the actual WebSocket state, not just our tracking Sets.\n * More reliable than liveConnections Set which can become stale.\n *\n * @param projectPath - The project path to check\n * @returns true if WebSocket exists and is in OPEN state\n */\n private isWebSocketOpen(projectPath: string): boolean {\n const connection = this.connections.get(projectPath)\n if (!connection) return false\n\n // Check the actual WebSocket state via EpisodaClient.getStatus()\n return connection.client.getStatus().connected\n }\n\n /**\n * EP843: Acquire a per-module lock for tunnel operations\n *\n * Prevents race conditions between:\n * - autoStartTunnelsForProject() on auth_success\n * - module_state_changed event handler\n * - Multiple rapid state transitions\n *\n * @param moduleUid - The module UID to lock\n * @param operation - Async operation to run while holding the lock\n * @returns Result of the operation\n */\n private async withTunnelLock<T>(moduleUid: string, operation: () => Promise<T>): Promise<T | null> {\n // Check if there's already an operation in progress for this module\n const existingLock = this.tunnelOperationLocks.get(moduleUid)\n if (existingLock) {\n console.log(`[Daemon] EP843: Tunnel operation already in progress for ${moduleUid}, waiting...`)\n try {\n await existingLock\n } catch {\n // Previous operation failed, but we can proceed\n }\n }\n\n // Create a new lock for this operation\n let releaseLock: () => void\n const lockPromise = new Promise<void>(resolve => {\n releaseLock = resolve\n })\n this.tunnelOperationLocks.set(moduleUid, lockPromise)\n\n try {\n return await operation()\n } finally {\n releaseLock!()\n // Only delete if this is still our lock (prevents race with new lock)\n if (this.tunnelOperationLocks.get(moduleUid) === lockPromise) {\n this.tunnelOperationLocks.delete(moduleUid)\n }\n }\n }\n\n /**\n * Connect to a project's WebSocket\n */\n private async connectProject(projectId: string, projectPath: string): Promise<void> {\n // EP805: Check BOTH connections Map AND liveConnections Set\n // A stale connection (in Map but not in Set) means WebSocket died\n // EP813: Also check pendingConnections to avoid race condition with restoreConnections\n if (this.connections.has(projectPath)) {\n if (this.liveConnections.has(projectPath)) {\n console.log(`[Daemon] Already connected to ${projectPath}`)\n return\n }\n if (this.pendingConnections.has(projectPath)) {\n // EP813: Connection is still in progress (waiting for auth_success)\n // Wait for it to complete rather than killing it\n console.log(`[Daemon] Connection in progress for ${projectPath}, waiting...`)\n // Wait up to 35s for pending connection to complete (30s auth timeout + 5s buffer)\n const maxWait = 35000\n const startTime = Date.now()\n while (this.pendingConnections.has(projectPath) && Date.now() - startTime < maxWait) {\n await new Promise(resolve => setTimeout(resolve, 500))\n }\n // Check if it succeeded\n if (this.liveConnections.has(projectPath)) {\n console.log(`[Daemon] Pending connection succeeded for ${projectPath}`)\n return\n }\n // If still pending after timeout, treat as stale\n console.warn(`[Daemon] Pending connection timed out for ${projectPath}`)\n }\n // EP805: Stale connection detected - clean up and reconnect\n console.warn(`[Daemon] Stale connection detected for ${projectPath}, forcing reconnection`)\n await this.disconnectProject(projectPath)\n }\n\n // Load auth token from config\n const config = await loadConfig()\n if (!config || !config.access_token) {\n throw new Error('No access token found. Please run: episoda auth')\n }\n\n // EP734: Removed pre-flight health check - WebSocket will fail naturally if server is down\n // This saves ~200-500ms on every connection attempt\n\n // EP734: Determine server URL using cached settings (avoids network call)\n // Priority: 1. Cached local_server_url, 2. Global config api_url, 3. Default production\n let serverUrl = config.api_url || process.env.EPISODA_API_URL || 'https://episoda.dev'\n\n // Use cached local_server_url if available\n if (config.project_settings?.local_server_url) {\n serverUrl = config.project_settings.local_server_url\n console.log(`[Daemon] Using cached server URL: ${serverUrl}`)\n }\n\n // EP593: Connect to WebSocket server on port 3001 (standalone WebSocket server)\n const serverUrlObj = new URL(serverUrl)\n const wsProtocol = serverUrlObj.protocol === 'https:' ? 'wss:' : 'ws:'\n const wsPort = process.env.EPISODA_WS_PORT || '3001'\n const wsUrl = `${wsProtocol}//${serverUrlObj.hostname}:${wsPort}`\n console.log(`[Daemon] Connecting to ${wsUrl} for project ${projectId}...`)\n\n // Create EpisodaClient (handles auth, reconnection, heartbeat)\n const client = new EpisodaClient()\n\n // Create GitExecutor for this project\n const gitExecutor = new GitExecutor()\n\n // Store connection\n const connection: ActiveConnection = {\n projectId,\n projectPath,\n client,\n gitExecutor,\n }\n this.connections.set(projectPath, connection)\n // EP813: Mark as pending until auth_success is received\n this.pendingConnections.add(projectPath)\n\n // Register command handler\n client.on('command', async (message) => {\n if (message.type === 'command' && message.command) {\n console.log(`[Daemon] Received command for ${projectId}:`, message.command)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n try {\n // Execute git command with working directory\n // EP971: All projects use worktree architecture\n // - Commands with worktreePath run in that worktree\n // - Commands without worktreePath run in .bare/ (the git directory)\n const gitCmd = message.command as GitCommand\n const bareRepoPath = path.join(projectPath, '.bare')\n\n // Determine working directory: specific worktree or .bare/\n const cwd = gitCmd.worktreePath || bareRepoPath\n\n if (gitCmd.worktreePath) {\n console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`)\n } else {\n console.log(`[Daemon] Running git command in bare repo: ${bareRepoPath}`)\n }\n const result = await gitExecutor.execute(gitCmd, {\n cwd\n })\n\n // Send result back\n await client.send({\n type: 'result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] Command completed for ${projectId}:`, result.success ? 'success' : 'failed')\n\n // EP950: Cleanup stale commits after successful push to main\n if (result.success && gitCmd.action === 'push' && gitCmd.branch === 'main') {\n // Run cleanup asynchronously (don't block the response)\n cleanupStaleCommits(projectPath).then(cleanupResult => {\n if (cleanupResult.deleted_count > 0) {\n console.log(`[Daemon] EP950: Cleaned up ${cleanupResult.deleted_count} stale commit(s) after push to main`)\n }\n }).catch(err => {\n console.warn('[Daemon] EP950: Cleanup after push failed:', err.message)\n })\n }\n } catch (error) {\n // Send error result\n await client.send({\n type: 'result',\n commandId: message.id!,\n result: {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error instanceof Error ? error.message : String(error)\n }\n })\n\n console.error(`[Daemon] Command execution error for ${projectId}:`, error)\n }\n }\n })\n\n // EP808: Register remote command handler for file/exec operations\n client.on('remote_command', async (message) => {\n if (message.type === 'remote_command' && message.command) {\n const cmd = message.command as RemoteCommand\n console.log(`[Daemon] Received remote command for ${projectId}:`, cmd.action)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n try {\n let result\n switch (cmd.action) {\n case 'file:read':\n result = await handleFileRead(cmd, projectPath)\n break\n case 'file:write':\n result = await handleFileWrite(cmd, projectPath)\n break\n case 'file:edit':\n result = await handleFileEdit(cmd, projectPath)\n break\n case 'file:delete':\n result = await handleFileDelete(cmd, projectPath)\n break\n case 'file:mkdir':\n result = await handleFileMkdir(cmd, projectPath)\n break\n case 'file:list':\n result = await handleFileList(cmd, projectPath)\n break\n case 'file:search':\n result = await handleFileSearch(cmd, projectPath)\n break\n case 'file:grep':\n result = await handleFileGrep(cmd, projectPath)\n break\n case 'exec':\n result = await handleExec(cmd, projectPath)\n break\n default:\n result = {\n success: false,\n error: `Unknown remote command action: ${(cmd as any).action}`\n }\n }\n\n // Send result back\n await client.send({\n type: 'remote_result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] Remote command ${cmd.action} completed for ${projectId}:`, result.success ? 'success' : 'failed')\n } catch (error) {\n // Send error result\n await client.send({\n type: 'remote_result',\n commandId: message.id!,\n result: {\n success: false,\n error: error instanceof Error ? error.message : String(error)\n }\n })\n\n console.error(`[Daemon] Remote command execution error for ${projectId}:`, error)\n }\n }\n })\n\n // EP672: Register tunnel command handler for local dev preview\n client.on('tunnel_command', async (message) => {\n if (message.type === 'tunnel_command' && message.command) {\n const cmd = message.command as TunnelCommand\n console.log(`[Daemon] Received tunnel command for ${projectId}:`, cmd.action)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n try {\n const tunnelManager = getTunnelManager()\n let result: TunnelCommandResult\n\n if (cmd.action === 'start') {\n // EP818: Respond immediately - tunnel starts asynchronously (like cloud preview)\n // This prevents blocking the workflow transition\n\n // EP973: Resolve worktree path for this module (fix: was using projectPath which is project root)\n const worktree = await getWorktreeInfoForModule(cmd.moduleUid)\n if (!worktree) {\n console.error(`[Daemon] EP973: Cannot resolve worktree path for ${cmd.moduleUid}`)\n // Send error result immediately\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result: { success: false, error: 'Cannot resolve worktree path - missing config slugs' }\n })\n return\n }\n\n if (!worktree.exists) {\n console.error(`[Daemon] EP973: Worktree not found at ${worktree.path}`)\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result: { success: false, error: `Worktree not found at ${worktree.path}` }\n })\n return\n }\n\n console.log(`[Daemon] EP973: Using worktree path ${worktree.path} for ${cmd.moduleUid}`)\n\n // EP973: Use worktree path for port detection (was projectPath)\n const port = cmd.port || detectDevPort(worktree.path)\n const previewUrl = `https://${cmd.moduleUid.toLowerCase()}-${cmd.projectUid.toLowerCase()}.episoda.site`\n\n // EP818: Helper to report tunnel status to the API\n const reportTunnelStatus = async (data: {\n tunnel_url?: string | null\n tunnel_error?: string | null\n tunnel_started_at?: string | null\n }) => {\n const config = await loadConfig()\n if (config?.access_token) {\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetch(`${apiUrl}/api/modules/${cmd.moduleUid}/tunnel`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(data)\n })\n\n if (response.ok) {\n console.log(`[Daemon] Tunnel status reported for ${cmd.moduleUid}`)\n } else {\n console.warn(`[Daemon] Failed to report tunnel status: ${response.statusText}`)\n }\n } catch (reportError) {\n console.warn(`[Daemon] Error reporting tunnel status:`, reportError)\n }\n }\n }\n\n // Fire off async work - dev server + tunnel startup with retry\n // Don't await - let it run in background\n ;(async () => {\n const MAX_RETRIES = 3\n // EP953: Increased retry delay for more reliable tunnel startup after cleanup\n const RETRY_DELAY_MS = 3000\n\n // EP818: Report that tunnel is starting (sets tunnel_started_at for UI timeout)\n await reportTunnelStatus({\n tunnel_started_at: new Date().toISOString(),\n tunnel_error: null // Clear any previous error\n })\n\n try {\n // Initialize tunnel manager if needed\n await tunnelManager.initialize()\n\n // EP973: Get custom dev server script from project settings\n const devConfig = await loadConfig()\n const devServerScript = devConfig?.project_settings?.worktree_dev_server_script\n\n // EP818: Ensure dev server is running before starting tunnel\n // EP973: Use worktree.path instead of projectPath\n console.log(`[Daemon] EP973: Ensuring dev server is running in ${worktree.path} on port ${port}...`)\n const devServerResult = await ensureDevServer(worktree.path, port, cmd.moduleUid, devServerScript)\n if (!devServerResult.success) {\n const errorMsg = `Dev server failed to start: ${devServerResult.error}`\n console.error(`[Daemon] ${errorMsg}`)\n await reportTunnelStatus({ tunnel_error: errorMsg })\n return\n }\n console.log(`[Daemon] Dev server ready on port ${port}`)\n\n // EP818: Start tunnel with retry logic\n let lastError: string | undefined\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n console.log(`[Daemon] Starting tunnel (attempt ${attempt}/${MAX_RETRIES})...`)\n\n const startResult = await tunnelManager.startTunnel({\n moduleUid: cmd.moduleUid,\n port,\n onUrl: async (url) => {\n // EP672-9: Report URL on both initial start and reconnections\n console.log(`[Daemon] Tunnel URL for ${cmd.moduleUid}: ${url}`)\n await reportTunnelStatus({\n tunnel_url: url,\n tunnel_error: null // Clear error on success\n })\n },\n onStatusChange: (status, error) => {\n if (status === 'error') {\n console.error(`[Daemon] Tunnel error for ${cmd.moduleUid}: ${error}`)\n // Report error asynchronously\n reportTunnelStatus({ tunnel_error: error || 'Tunnel connection error' })\n } else if (status === 'reconnecting') {\n console.log(`[Daemon] Tunnel reconnecting for ${cmd.moduleUid}...`)\n }\n }\n })\n\n if (startResult.success) {\n console.log(`[Daemon] Tunnel started successfully for ${cmd.moduleUid}`)\n return // Success - exit retry loop\n }\n\n lastError = startResult.error\n console.warn(`[Daemon] Tunnel start attempt ${attempt} failed: ${lastError}`)\n\n if (attempt < MAX_RETRIES) {\n console.log(`[Daemon] Retrying in ${RETRY_DELAY_MS}ms...`)\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS))\n }\n }\n\n // All retries failed\n const errorMsg = `Tunnel failed after ${MAX_RETRIES} attempts: ${lastError}`\n console.error(`[Daemon] ${errorMsg}`)\n await reportTunnelStatus({ tunnel_error: errorMsg })\n\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] Async tunnel startup error:`, error)\n await reportTunnelStatus({ tunnel_error: `Unexpected error: ${errorMsg}` })\n }\n })()\n\n // Respond immediately with \"starting\" status\n result = {\n success: true,\n previewUrl,\n // Note: actual tunnel URL will be reported via API when ready\n }\n } else if (cmd.action === 'stop') {\n await tunnelManager.stopTunnel(cmd.moduleUid)\n\n // EP818: Stop the dev server we started for this module\n await stopDevServer(cmd.moduleUid)\n\n // Clear tunnel URL from the server\n const config = await loadConfig()\n if (config?.access_token) {\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n await fetch(`${apiUrl}/api/modules/${cmd.moduleUid}/tunnel`, {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`\n }\n })\n console.log(`[Daemon] Tunnel URL cleared for ${cmd.moduleUid}`)\n } catch {\n // Ignore cleanup errors\n }\n }\n\n result = { success: true }\n } else {\n result = {\n success: false,\n error: `Unknown tunnel action: ${(cmd as any).action}`\n }\n }\n\n // Send result back\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] Tunnel command ${cmd.action} completed for ${cmd.moduleUid}:`, result.success ? 'success' : 'failed')\n } catch (error) {\n // Send error result\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result: {\n success: false,\n error: error instanceof Error ? error.message : String(error)\n }\n })\n\n console.error(`[Daemon] Tunnel command execution error:`, error)\n }\n }\n })\n\n // EP912: Register agent command handler for local Claude Code execution\n client.on('agent_command', async (message) => {\n if (message.type === 'agent_command' && message.command) {\n const cmd = message.command as AgentCommand\n console.log(`[Daemon] EP912: Received agent command for ${projectId}:`, cmd.action)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n // Helper to create streaming callbacks - avoids duplication between start/message\n const createStreamingCallbacks = (sessionId: string, commandId: string) => ({\n onChunk: async (chunk: string) => {\n try {\n await client.send({\n type: 'agent_result',\n commandId,\n result: { success: true, status: 'chunk', sessionId, chunk }\n })\n } catch (sendError) {\n // EP912: Handle WebSocket disconnection mid-stream\n console.error(`[Daemon] EP912: Failed to send chunk (WebSocket may be disconnected):`, sendError)\n }\n },\n onComplete: async (claudeSessionId?: string) => {\n try {\n await client.send({\n type: 'agent_result',\n commandId,\n result: { success: true, status: 'complete', sessionId, claudeSessionId }\n })\n } catch (sendError) {\n console.error(`[Daemon] EP912: Failed to send complete (WebSocket may be disconnected):`, sendError)\n }\n },\n onError: async (error: string) => {\n try {\n await client.send({\n type: 'agent_result',\n commandId,\n result: { success: false, status: 'error', sessionId, error }\n })\n } catch (sendError) {\n console.error(`[Daemon] EP912: Failed to send error (WebSocket may be disconnected):`, sendError)\n }\n }\n })\n\n try {\n const agentManager = getAgentManager()\n await agentManager.initialize()\n\n let result: AgentResult\n\n if (cmd.action === 'start') {\n // Start a new agent session and send initial message\n const callbacks = createStreamingCallbacks(cmd.sessionId, message.id!)\n\n // EP959-10: Resolve worktree path - agent should run in module's worktree\n let agentWorkingDir = projectPath\n if (cmd.moduleUid) {\n const worktreeInfo = await getWorktreeInfoForModule(cmd.moduleUid)\n if (worktreeInfo?.exists) {\n agentWorkingDir = worktreeInfo.path\n console.log(`[Daemon] EP959: Agent for ${cmd.moduleUid} in worktree: ${agentWorkingDir}`)\n }\n }\n\n const startResult = await agentManager.startSession({\n sessionId: cmd.sessionId,\n moduleId: cmd.moduleId,\n moduleUid: cmd.moduleUid,\n projectPath: agentWorkingDir,\n message: cmd.message,\n credentials: cmd.credentials,\n systemPrompt: cmd.systemPrompt,\n ...callbacks\n })\n\n result = {\n success: startResult.success,\n status: startResult.success ? 'started' : 'error',\n sessionId: cmd.sessionId,\n error: startResult.error\n }\n\n } else if (cmd.action === 'message') {\n // Send message to existing session\n const callbacks = createStreamingCallbacks(cmd.sessionId, message.id!)\n const sendResult = await agentManager.sendMessage({\n sessionId: cmd.sessionId,\n message: cmd.message,\n isFirstMessage: false,\n claudeSessionId: cmd.claudeSessionId,\n ...callbacks\n })\n\n result = {\n success: sendResult.success,\n status: sendResult.success ? 'started' : 'error',\n sessionId: cmd.sessionId,\n error: sendResult.error\n }\n\n } else if (cmd.action === 'abort') {\n await agentManager.abortSession(cmd.sessionId)\n result = { success: true, status: 'aborted', sessionId: cmd.sessionId }\n\n } else if (cmd.action === 'stop') {\n await agentManager.stopSession(cmd.sessionId)\n result = { success: true, status: 'complete', sessionId: cmd.sessionId }\n\n } else {\n result = {\n success: false,\n status: 'error',\n sessionId: (cmd as any).sessionId || 'unknown',\n error: `Unknown agent action: ${(cmd as any).action}`\n }\n }\n\n // Send initial result back\n await client.send({\n type: 'agent_result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] EP912: Agent command ${cmd.action} completed for session ${cmd.action === 'start' || cmd.action === 'message' ? cmd.sessionId : (cmd as any).sessionId}`)\n } catch (error) {\n // Send error result\n try {\n await client.send({\n type: 'agent_result',\n commandId: message.id!,\n result: {\n success: false,\n status: 'error',\n sessionId: (cmd as any).sessionId || 'unknown',\n error: error instanceof Error ? error.message : String(error)\n }\n })\n } catch (sendError) {\n console.error(`[Daemon] EP912: Failed to send error result (WebSocket may be disconnected):`, sendError)\n }\n\n console.error(`[Daemon] EP912: Agent command execution error:`, error)\n }\n }\n })\n\n // Register shutdown handler - allows server to request graceful shutdown\n // EP613: Only exit on user-requested shutdowns, reconnect for server restarts\n client.on('shutdown', async (message) => {\n const shutdownMessage = message as { message?: string; reason?: string }\n const reason = shutdownMessage.reason || 'unknown'\n\n console.log(`[Daemon] Received shutdown request from server for ${projectId}`)\n console.log(`[Daemon] Reason: ${reason} - ${shutdownMessage.message || 'No message'}`)\n\n if (reason === 'user_requested') {\n // User explicitly requested disconnect - exit cleanly\n console.log(`[Daemon] User requested disconnect, shutting down...`)\n await this.cleanupAndExit()\n } else {\n // Server restart or deployment - don't exit, let WebSocket reconnection handle it\n console.log(`[Daemon] Server shutdown (${reason}), will reconnect automatically...`)\n // The WebSocket close event will trigger reconnection via EpisodaClient\n // We don't call process.exit() here - just let the connection close naturally\n }\n })\n\n // Register auth success handler\n client.on('auth_success', async (message) => {\n console.log(`[Daemon] Authenticated for project ${projectId}`)\n touchProject(projectPath)\n\n // EP701: Mark connection as live (for /device-info endpoint)\n this.liveConnections.add(projectPath)\n // EP813: No longer pending - auth succeeded\n this.pendingConnections.delete(projectPath)\n\n // EP812: Removed identity server status update - browser uses cookie-based pairing\n\n // EP595: Configure git with userId and workspaceId for post-checkout hook\n // EP655: Also configure machineId for device isolation\n // EP661: Cache deviceName for browser identification\n // EP726: Cache deviceId (UUID) for unified device identification\n // EP735: Cache flyMachineId for sticky session routing\n const authMessage = message as { userId?: string; workspaceId?: string; deviceName?: string; deviceId?: string; flyMachineId?: string }\n if (authMessage.userId && authMessage.workspaceId) {\n // EP726: Pass deviceId to configureGitUser so it's stored in git config\n await this.configureGitUser(projectPath, authMessage.userId, authMessage.workspaceId, this.machineId, projectId, authMessage.deviceId)\n // EP610: Install git hooks after configuring git user\n await this.installGitHooks(projectPath)\n }\n\n // EP661: Store device name for logging and config\n if (authMessage.deviceName) {\n this.deviceName = authMessage.deviceName\n console.log(`[Daemon] Device name: ${this.deviceName}`)\n }\n\n // EP726: Cache device UUID for unified device identification\n // Persist to config for future use\n if (authMessage.deviceId) {\n this.deviceId = authMessage.deviceId\n console.log(`[Daemon] Device ID (UUID): ${this.deviceId}`)\n\n // Persist deviceId to config file so it's available on daemon restart\n await this.cacheDeviceId(authMessage.deviceId)\n }\n\n // EP735: Cache Fly.io machine ID for sticky session routing\n // Only set when server is running on Fly.io, null otherwise\n if (authMessage.flyMachineId) {\n this.flyMachineId = authMessage.flyMachineId\n console.log(`[Daemon] Fly Machine ID: ${this.flyMachineId}`)\n }\n\n // EP964: Sync project settings (including worktree_env_vars) from server\n // Run in background to avoid blocking auth\n this.syncProjectSettings(projectId).catch(err => {\n console.warn('[Daemon] EP964: Settings sync failed:', err.message)\n })\n\n // EP995: Sync project path to server (local_machine.project_paths)\n // Run in background to avoid blocking auth\n this.syncMachineProjectPath(projectId, projectPath).catch(err => {\n console.warn('[Daemon] EP995: Project path sync failed:', err.message)\n })\n\n // EP819: Auto-start tunnels for active local modules on connect/reconnect\n // Run in background to avoid blocking the auth_success handler\n this.autoStartTunnelsForProject(projectPath, projectId).catch(error => {\n console.error(`[Daemon] EP819: Failed to auto-start tunnels:`, error)\n })\n\n // EP950: Cleanup stale commits on connect/reconnect\n // This catches pushes that happened outside the daemon (e.g., user ran git push directly)\n cleanupStaleCommits(projectPath).then(cleanupResult => {\n if (cleanupResult.deleted_count > 0) {\n console.log(`[Daemon] EP950: Cleaned up ${cleanupResult.deleted_count} stale commit(s) on connect`)\n }\n }).catch(err => {\n // Non-fatal - just log and continue\n console.warn('[Daemon] EP950: Cleanup on connect failed:', err.message)\n })\n\n // EP995: Reconcile worktrees on connect/reconnect\n // Catches modules that transitioned while daemon was disconnected\n this.reconcileWorktrees(projectId, projectPath).catch(err => {\n console.warn('[Daemon] EP995: Reconciliation failed:', err.message)\n })\n })\n\n // EP843: Register module state change handler for push-based tunnel management\n // Replaces polling-based tunnel sync with real-time event-driven approach\n client.on('module_state_changed', async (message) => {\n if (message.type === 'module_state_changed') {\n const { moduleUid, state, previousState, branchName, devMode, checkoutMachineId } = message\n\n console.log(`[Daemon] EP843: Module ${moduleUid} state changed: ${previousState} → ${state}`)\n\n // Only handle local dev mode tunnels\n if (devMode !== 'local') {\n console.log(`[Daemon] EP843: Skipping tunnel action for ${moduleUid} (mode: ${devMode || 'unknown'})`)\n return\n }\n\n // EP956: Validate checkout_machine_id - only handle modules checked out on this machine\n // checkoutMachineId is null for unassigned modules, which we can claim\n if (checkoutMachineId && checkoutMachineId !== this.deviceId) {\n console.log(`[Daemon] EP956: Skipping ${moduleUid} (checked out on different machine: ${checkoutMachineId})`)\n return\n }\n\n const tunnelManager = getTunnelManager()\n await tunnelManager.initialize()\n\n // EP843: Use mutex to prevent race conditions with autoStartTunnels\n await this.withTunnelLock(moduleUid, async () => {\n // EP933: Tunnel lifecycle - start on ready→doing, persist through ready/doing/review, stop only on →done\n // Active zone = ready, doing, or review (NOT backlog, NOT done)\n const isInActiveZone = state === 'ready' || state === 'doing' || state === 'review'\n const wasInActiveZone = previousState === 'ready' || previousState === 'doing' || previousState === 'review'\n // Start trigger: entering doing from ready (the primary start event)\n const startingWork = previousState === 'ready' && state === 'doing'\n // Crash recovery: in active zone but tunnel not running\n const tunnelNotRunning = !tunnelManager.hasTunnel(moduleUid)\n const needsCrashRecovery = isInActiveZone && tunnelNotRunning\n\n if (startingWork || needsCrashRecovery) {\n // Skip if tunnel is already running (prevents duplicate starts)\n if (tunnelManager.hasTunnel(moduleUid)) {\n console.log(`[Daemon] EP843: Tunnel already running for ${moduleUid}, skipping start`)\n return\n }\n\n console.log(`[Daemon] EP956: Starting tunnel for ${moduleUid} (${previousState} → ${state})`)\n\n try {\n // EP956: Get worktree path for this module\n let worktree = await getWorktreeInfoForModule(moduleUid)\n if (!worktree) {\n console.error(`[Daemon] EP956: Cannot resolve worktree path for ${moduleUid} (missing config slugs)`)\n return\n }\n\n // EP959-8: Auto-create worktree on ready→doing transition if it doesn't exist\n if (!worktree.exists && startingWork) {\n console.log(`[Daemon] EP959: Creating worktree for ${moduleUid} at ${worktree.path}`)\n\n const projectRoot = await getProjectRootPath()\n if (!projectRoot) {\n console.error(`[Daemon] EP959: Cannot determine project root for worktree creation`)\n return\n }\n\n const worktreeManager = new WorktreeManager(projectRoot)\n const initialized = await worktreeManager.initialize()\n if (!initialized) {\n console.error(`[Daemon] EP959: Failed to initialize WorktreeManager at ${projectRoot}`)\n return\n }\n\n // Use branch name from the state change event\n // EP962: New format is {UID}-{slug} instead of module/{UID}-{slug}\n const moduleBranchName = branchName || moduleUid\n const createResult = await worktreeManager.createWorktree(moduleUid, moduleBranchName, true)\n\n if (!createResult.success) {\n console.error(`[Daemon] EP959: Failed to create worktree for ${moduleUid}: ${createResult.error}`)\n return\n }\n\n console.log(`[Daemon] EP959: Worktree created for ${moduleUid} at ${createResult.worktreePath}`)\n\n // EP990: Claim ownership of worktree by updating checkout_machine_id\n // This ensures the module is associated with this machine immediately after creation\n if (this.deviceId) {\n try {\n const ownershipConfig = await loadConfig()\n const ownershipApiUrl = ownershipConfig?.api_url || 'https://episoda.dev'\n const ownershipResponse = await fetchWithAuth(`${ownershipApiUrl}/api/modules/${moduleUid}`, {\n method: 'PATCH',\n body: JSON.stringify({ checkout_machine_id: this.deviceId })\n })\n if (ownershipResponse.ok) {\n console.log(`[Daemon] EP990: Claimed ownership of ${moduleUid} for device ${this.deviceId}`)\n } else {\n console.warn(`[Daemon] EP990: Failed to claim ownership of ${moduleUid}: ${ownershipResponse.status}`)\n }\n } catch (ownershipError) {\n // Non-blocking - log but don't fail the transition\n console.warn(`[Daemon] EP990: Error claiming ownership of ${moduleUid}:`, ownershipError)\n }\n }\n\n // Refresh worktree info after creation\n worktree = await getWorktreeInfoForModule(moduleUid)\n if (!worktree || !worktree.exists) {\n console.error(`[Daemon] EP959: Worktree still not found after creation for ${moduleUid}`)\n return\n }\n\n // EP959-11: Run async worktree setup if configured\n // EP973: Fetch env vars from server API instead of config\n // EP986: Always run setup for dependency installation\n const worktreeConfig = await loadConfig()\n const setupConfig = worktreeConfig?.project_settings\n\n // EP973: Always fetch env vars from server (replaces worktree_env_vars in config)\n const envVars = await fetchEnvVars()\n const hasEnvVars = Object.keys(envVars).length > 0\n const hasSetupConfig = setupConfig?.worktree_copy_files?.length || setupConfig?.worktree_setup_script || hasEnvVars\n\n // EP986: Always run setup to install dependencies, even without other config\n {\n console.log(`[Daemon] EP986: Starting async worktree setup for ${moduleUid}${hasSetupConfig ? ' (with config)' : ' (for dependency installation)'}`)\n\n // Mark as setup pending (local + server)\n await worktreeManager.updateWorktreeStatus(moduleUid, 'pending')\n // EP995: Report pending status to server\n await this.updateModuleWorktreeStatus(moduleUid, 'pending', worktree.path)\n\n // Run setup asynchronously (don't block transition)\n // EP973: Pass server-fetched env vars instead of config-based ones\n this.runWorktreeSetupAsync(\n moduleUid,\n worktreeManager,\n setupConfig?.worktree_copy_files || [],\n setupConfig?.worktree_setup_script,\n worktree.path,\n envVars // EP973: Use server-fetched env vars\n ).then(() => {\n console.log(`[Daemon] EP959: Setup complete for ${moduleUid}, starting tunnel`)\n // After setup completes, start the tunnel\n this.startTunnelForModule(moduleUid, worktree!.path)\n }).catch(err => {\n console.error(`[Daemon] EP959: Setup failed for ${moduleUid}:`, err)\n })\n\n // Don't start tunnel yet - wait for setup to complete\n return\n }\n }\n\n if (!worktree.exists) {\n // Not a startingWork event, skip if worktree doesn't exist\n console.log(`[Daemon] EP956: No worktree for ${moduleUid} at ${worktree.path}, skipping tunnel`)\n return\n }\n\n // EP1001: Worktree exists (likely created by server orchestrator) - ensure worktree_path\n // is reported to database. This was missing, causing worktree_path to remain null\n // even though the tunnel would start successfully.\n await this.updateModuleWorktreeStatus(moduleUid, 'ready', worktree.path)\n\n // EP956: Allocate port for this module (3100-3199 range)\n const port = allocatePort(moduleUid)\n console.log(`[Daemon] EP956: Using worktree ${worktree.path} on port ${port}`)\n\n // EP959-m2: Get custom dev server script from project settings\n const devConfig = await loadConfig()\n const devServerScript = devConfig?.project_settings?.worktree_dev_server_script\n\n // Ensure dev server is running in worktree directory\n const devServerResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript)\n if (!devServerResult.success) {\n console.error(`[Daemon] EP956: Dev server failed for ${moduleUid}: ${devServerResult.error}`)\n releasePort(moduleUid) // Release port on failure\n return\n }\n\n // Start tunnel\n // EP904: Use fetchWithAuth for automatic token refresh\n const config = devConfig\n const apiUrl = config?.api_url || 'https://episoda.dev'\n const startResult = await tunnelManager.startTunnel({\n moduleUid,\n port,\n onUrl: async (url) => {\n console.log(`[Daemon] EP956: Tunnel URL for ${moduleUid}: ${url}`)\n // EP904: Report URL to API with token refresh\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'POST',\n body: JSON.stringify({ tunnel_url: url })\n })\n } catch (err) {\n console.warn(`[Daemon] EP956: Failed to report tunnel URL:`, err instanceof Error ? err.message : err)\n }\n },\n onStatusChange: (status, error) => {\n if (status === 'error') {\n console.error(`[Daemon] EP956: Tunnel error for ${moduleUid}: ${error}`)\n }\n }\n })\n\n if (startResult.success) {\n console.log(`[Daemon] EP956: Tunnel started for ${moduleUid}`)\n } else {\n console.error(`[Daemon] EP956: Tunnel failed for ${moduleUid}: ${startResult.error}`)\n releasePort(moduleUid) // Release port on failure\n }\n } catch (error) {\n console.error(`[Daemon] EP956: Error starting tunnel for ${moduleUid}:`, error)\n releasePort(moduleUid) // Release port on exception\n }\n }\n\n // EP933: Stop tunnel ONLY when entering done state (not when moving between active states)\n if (state === 'done' && wasInActiveZone) {\n console.log(`[Daemon] EP956: Stopping tunnel for ${moduleUid} (${previousState} → done)`)\n\n try {\n await tunnelManager.stopTunnel(moduleUid)\n // EP956: Release port when stopping tunnel\n releasePort(moduleUid)\n console.log(`[Daemon] EP956: Tunnel stopped and port released for ${moduleUid}`)\n\n // EP904: Clear tunnel URL in API with token refresh\n const config = await loadConfig()\n const apiUrl = config?.api_url || 'https://episoda.dev'\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'POST',\n body: JSON.stringify({ tunnel_url: null })\n })\n } catch (err) {\n console.warn(`[Daemon] EP956: Failed to clear tunnel URL:`, err instanceof Error ? err.message : err)\n }\n\n // EP956: Async cleanup - stop dev server and cleanup worktree\n // Run in background to reduce handler burden\n this.cleanupModuleWorktree(moduleUid).catch(err => {\n console.warn(`[Daemon] EP956: Async cleanup failed for ${moduleUid}:`, err instanceof Error ? err.message : err)\n })\n } catch (error) {\n console.error(`[Daemon] EP956: Error stopping tunnel for ${moduleUid}:`, error)\n // EP956: Still release port even if tunnel stop failed\n releasePort(moduleUid)\n }\n }\n })\n }\n })\n\n // Register error handler\n client.on('error', (message) => {\n console.error(`[Daemon] Server error for ${projectId}:`, message)\n })\n\n // EP701: Register disconnected handler to track connection state\n // Removes from liveConnections immediately so /device-info returns accurate state\n // Keeps entry in connections map for potential reconnection (client handles reconnect internally)\n client.on('disconnected', (event) => {\n const disconnectEvent = event as DisconnectEvent\n console.log(`[Daemon] Connection closed for ${projectId}: code=${disconnectEvent.code}, willReconnect=${disconnectEvent.willReconnect}`)\n\n // Always remove from liveConnections - connection is dead until reconnected\n this.liveConnections.delete(projectPath)\n\n // EP812: Removed identity server status update - browser uses cookie-based pairing\n\n // Only remove from connections map if we won't reconnect (intentional disconnect)\n if (!disconnectEvent.willReconnect) {\n this.connections.delete(projectPath)\n console.log(`[Daemon] Removed connection for ${projectPath} from map`)\n }\n })\n\n try {\n // EP601: Read daemon PID from file\n let daemonPid: number | undefined\n try {\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n const pidStr = fs.readFileSync(pidPath, 'utf-8').trim()\n daemonPid = parseInt(pidStr, 10)\n }\n } catch (pidError) {\n console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError)\n }\n\n // EP812: Create promise that resolves when auth_success is received\n // This ensures liveConnections is populated before connectProject returns\n // EP813: Increased timeout from 10s to 30s for reliability during server load\n const authSuccessPromise = new Promise<void>((resolve, reject) => {\n const AUTH_TIMEOUT = 30000 // 30 second timeout for auth\n const timeout = setTimeout(() => {\n reject(new Error('Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds.'))\n }, AUTH_TIMEOUT)\n\n // One-time handler for auth_success\n const authHandler = () => {\n clearTimeout(timeout)\n resolve()\n }\n client.once('auth_success', authHandler)\n\n // Also handle auth errors\n const errorHandler = (message: unknown) => {\n clearTimeout(timeout)\n const errorMsg = message as { message?: string }\n reject(new Error(errorMsg.message || 'Authentication failed'))\n }\n client.once('auth_error', errorHandler)\n })\n\n // Connect with OAuth token, machine ID, and device info (EP596, EP601: includes daemonPid)\n await client.connect(wsUrl, config.access_token, this.machineId, {\n hostname: os.hostname(),\n osPlatform: os.platform(),\n osArch: os.arch(),\n daemonPid\n })\n console.log(`[Daemon] Successfully connected to project ${projectId}`)\n\n // EP812: Wait for auth_success before returning\n // This ensures liveConnections.add() has been called\n await authSuccessPromise\n console.log(`[Daemon] Authentication complete for project ${projectId}`)\n } catch (error) {\n console.error(`[Daemon] Failed to connect to ${projectId}:`, error)\n this.connections.delete(projectPath)\n // EP813: No longer pending - connection failed\n this.pendingConnections.delete(projectPath)\n throw error\n }\n }\n\n /**\n * Disconnect from a project's WebSocket\n */\n private async disconnectProject(projectPath: string): Promise<void> {\n const connection = this.connections.get(projectPath)\n if (!connection) {\n return\n }\n\n // Clear reconnect timer\n if (connection.reconnectTimer) {\n clearTimeout(connection.reconnectTimer)\n }\n\n // Disconnect client (handles WebSocket close gracefully)\n await connection.client.disconnect()\n this.connections.delete(projectPath)\n this.liveConnections.delete(projectPath) // EP701: Also clean up liveConnections\n this.pendingConnections.delete(projectPath) // EP813: Also clean up pendingConnections\n\n console.log(`[Daemon] Disconnected from ${projectPath}`)\n }\n\n /**\n * Setup graceful shutdown handlers\n * EP613: Now uses cleanupAndExit to ensure PID file is removed\n */\n private setupShutdownHandlers(): void {\n const shutdownHandler = async (signal: string) => {\n console.log(`[Daemon] Received ${signal}, shutting down...`)\n await this.cleanupAndExit()\n }\n\n process.on('SIGTERM', () => shutdownHandler('SIGTERM'))\n process.on('SIGINT', () => shutdownHandler('SIGINT'))\n }\n\n /**\n * EP595: Configure git with user and workspace ID for post-checkout hook\n * EP655: Added machineId for device isolation in multi-device environments\n * EP725: Added projectId for main branch badge tracking\n * EP726: Added deviceId (UUID) for unified device identification\n *\n * This stores the IDs in .git/config so the post-checkout hook can\n * update module.checkout_* fields when git operations happen from terminal.\n */\n private async configureGitUser(projectPath: string, userId: string, workspaceId: string, machineId: string, projectId: string, deviceId?: string | null): Promise<void> {\n try {\n const { execSync } = await import('child_process')\n\n // Set git config values in the project's .git/config\n execSync(`git config episoda.userId ${userId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n execSync(`git config episoda.workspaceId ${workspaceId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n // EP655: Set machineId for device isolation in post-checkout hook\n execSync(`git config episoda.machineId ${machineId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n // EP725: Set projectId for main branch badge tracking\n // This ensures main branch checkouts are recorded with project_id\n execSync(`git config episoda.projectId ${projectId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n // EP726: Set deviceId (UUID) for unified device identification\n // This allows the post-checkout hook to pass the UUID to the database\n if (deviceId) {\n execSync(`git config episoda.deviceId ${deviceId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n }\n\n console.log(`[Daemon] Configured git for project: episoda.userId=${userId}, machineId=${machineId}, projectId=${projectId}${deviceId ? `, deviceId=${deviceId}` : ''}`)\n } catch (error) {\n // Non-fatal error - git hook just won't work\n console.warn(`[Daemon] Failed to configure git user for ${projectPath}:`, error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP610: Install git hooks from bundled files\n *\n * Installs post-checkout and pre-commit hooks to enable:\n * - Branch tracking (post-checkout updates module.checkout_* fields)\n * - Main branch protection (pre-commit blocks direct commits to main)\n */\n private async installGitHooks(projectPath: string): Promise<void> {\n // EP837: Added post-commit hook for machine-aware commit tracking\n const hooks = ['post-checkout', 'pre-commit', 'post-commit']\n const hooksDir = path.join(projectPath, '.git', 'hooks')\n\n // Ensure hooks directory exists\n if (!fs.existsSync(hooksDir)) {\n console.warn(`[Daemon] Hooks directory not found: ${hooksDir}`)\n return\n }\n\n for (const hookName of hooks) {\n try {\n const hookPath = path.join(hooksDir, hookName)\n\n // Read bundled hook content\n // __dirname in compiled code points to dist/daemon/, hooks are in dist/hooks/\n const bundledHookPath = path.join(__dirname, '..', 'hooks', hookName)\n\n if (!fs.existsSync(bundledHookPath)) {\n console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`)\n continue\n }\n\n const hookContent = fs.readFileSync(bundledHookPath, 'utf-8')\n\n // Check if hook already exists with same content\n if (fs.existsSync(hookPath)) {\n const existingContent = fs.readFileSync(hookPath, 'utf-8')\n if (existingContent === hookContent) {\n // Hook is up to date\n continue\n }\n }\n\n // Write hook file\n fs.writeFileSync(hookPath, hookContent, { mode: 0o755 })\n console.log(`[Daemon] Installed git hook: ${hookName}`)\n } catch (error) {\n // Non-fatal - just log warning\n console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error)\n }\n }\n }\n\n /**\n * EP726: Cache device UUID to config file\n *\n * Persists the device_id (UUID) received from the server so it's available\n * on daemon restart without needing to re-register the device.\n */\n private async cacheDeviceId(deviceId: string): Promise<void> {\n try {\n const config = await loadConfig()\n if (!config) {\n console.warn('[Daemon] Cannot cache device ID - no config found')\n return\n }\n\n // Only update if device_id has changed\n if (config.device_id === deviceId) {\n return\n }\n\n // Update config with device_id and machine_id\n const updatedConfig: EpisodaConfig = {\n ...config,\n device_id: deviceId,\n machine_id: this.machineId\n }\n\n await saveConfig(updatedConfig)\n console.log(`[Daemon] Cached device ID to config: ${deviceId}`)\n } catch (error) {\n console.warn('[Daemon] Failed to cache device ID:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP964: Sync project settings from server on connect/reconnect\n *\n * Fetches worktree_env_vars and other settings from the server\n * and caches them locally for use during worktree checkout.\n */\n private async syncProjectSettings(projectId: string): Promise<void> {\n try {\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetchWithAuth(`${apiUrl}/api/projects/${projectId}/settings`)\n\n if (!response.ok) {\n console.warn(`[Daemon] EP964: Failed to sync settings: ${response.status}`)\n return\n }\n\n // EP973: Settings response now includes project_slug and workspace_slug\n const data = await response.json() as {\n settings?: Record<string, unknown>\n project_slug?: string\n workspace_slug?: string\n }\n const serverSettings = data.settings as Record<string, unknown> | undefined\n\n if (serverSettings) {\n // EP973: Use slugs from settings response if missing locally\n const projectSlug = data.project_slug || config.project_slug\n const workspaceSlug = data.workspace_slug || config.workspace_slug\n\n if (data.project_slug && !config.project_slug) {\n console.log(`[Daemon] EP973: Synced project_slug: ${data.project_slug}`)\n }\n if (data.workspace_slug && !config.workspace_slug) {\n console.log(`[Daemon] EP973: Synced workspace_slug: ${data.workspace_slug}`)\n }\n\n // EP973: Removed worktree_env_vars - now fetched from /api/cli/env-vars on demand\n const updatedConfig: EpisodaConfig = {\n ...config,\n // EP973: Include synced slugs\n project_slug: projectSlug,\n workspace_slug: workspaceSlug,\n project_settings: {\n ...config.project_settings,\n worktree_setup_script: serverSettings.worktree_setup_script as string | undefined,\n worktree_cleanup_script: serverSettings.worktree_cleanup_script as string | undefined,\n worktree_dev_server_script: serverSettings.worktree_dev_server_script as string | undefined,\n // Keep deprecated field for backward compatibility\n worktree_copy_files: serverSettings.worktree_copy_files as string[] | undefined,\n cached_at: Date.now(),\n }\n }\n\n await saveConfig(updatedConfig)\n console.log(`[Daemon] EP973: Project settings synced (slugs: ${projectSlug}/${workspaceSlug})`)\n }\n } catch (error) {\n // Non-fatal - just log and continue\n console.warn('[Daemon] EP964: Failed to sync project settings:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP995: Sync project path to server (local_machine.project_paths)\n *\n * Reports the local filesystem path for this project to the server,\n * enabling server-side visibility into where projects are checked out.\n * Uses atomic RPC to prevent race conditions.\n */\n private async syncMachineProjectPath(projectId: string, projectPath: string): Promise<void> {\n try {\n if (!this.deviceId) {\n console.warn('[Daemon] EP995: Cannot sync project path - deviceId not available')\n return\n }\n\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n const response = await fetchWithAuth(`${apiUrl}/api/account/machines/${this.deviceId}`, {\n method: 'PATCH',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n project_id: projectId,\n project_path: projectPath\n })\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n console.warn(`[Daemon] EP995: Failed to sync project path: ${response.status}`, errorData)\n return\n }\n\n console.log(`[Daemon] EP995: Synced project path to server: ${projectPath}`)\n } catch (error) {\n // Non-fatal - just log and continue\n console.warn('[Daemon] EP995: Failed to sync project path:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP995: Update module worktree status on server\n *\n * Reports worktree setup progress to the server, enabling:\n * - Server-side visibility into worktree state\n * - UI progress display (compatible with EP978)\n * - Reconciliation queries on daemon reconnect\n */\n private async updateModuleWorktreeStatus(\n moduleUid: string,\n status: 'pending' | 'setup' | 'ready' | 'error',\n worktreePath?: string,\n errorMessage?: string\n ): Promise<void> {\n try {\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n const body: Record<string, unknown> = {\n worktree_status: status\n }\n\n if (worktreePath) {\n body.worktree_path = worktreePath\n }\n\n if (status === 'error' && errorMessage) {\n body.worktree_error = errorMessage\n }\n\n // Clear error when transitioning to non-error state\n if (status !== 'error') {\n body.worktree_error = null\n }\n\n const response = await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}`, {\n method: 'PATCH',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(body)\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n console.warn(`[Daemon] EP995: Failed to update worktree status: ${response.status}`, errorData)\n return\n }\n\n console.log(`[Daemon] EP995: Updated module ${moduleUid} worktree_status=${status}`)\n } catch (error) {\n // Non-fatal - local worktree still works\n console.warn('[Daemon] EP995: Failed to update worktree status:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP995: Reconcile worktrees on daemon connect/reconnect\n *\n * Figma-style \"fresh snapshot on reconnect\" approach:\n * 1. Query server for modules that should have worktrees on this machine\n * 2. For modules missing local worktrees, create and setup\n * 3. Log orphaned worktrees (local exists but module not in doing/review)\n *\n * This self-healing mechanism catches modules that transitioned\n * while the daemon was disconnected.\n */\n private async reconcileWorktrees(projectId: string, projectPath: string): Promise<void> {\n console.log(`[Daemon] EP995: Starting worktree reconciliation for project ${projectId}`)\n\n try {\n if (!this.deviceId) {\n console.log('[Daemon] EP995: Cannot reconcile - deviceId not available yet')\n return\n }\n\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n // Query server for modules that should have worktrees on this machine\n // Modules in doing/review with local dev mode and checked out on this machine\n const modulesResponse = await fetchWithAuth(\n `${apiUrl}/api/modules?state=doing,review&dev_mode=local&checkout_machine_id=${this.deviceId}&project_id=${projectId}`\n )\n\n if (!modulesResponse.ok) {\n console.warn(`[Daemon] EP995: Failed to fetch modules for reconciliation: ${modulesResponse.status}`)\n return\n }\n\n const modulesData = await modulesResponse.json() as { modules?: Array<{ id: string; uid: string; branch_name: string; worktree_status?: string }> }\n const modules = modulesData.modules || []\n\n if (modules.length === 0) {\n console.log('[Daemon] EP995: No modules need reconciliation')\n return\n }\n\n console.log(`[Daemon] EP995: Found ${modules.length} module(s) to check`)\n\n // Get local worktree info\n const worktreeManager = new WorktreeManager(projectPath)\n const initialized = await worktreeManager.initialize()\n if (!initialized) {\n console.error(`[Daemon] EP995: Failed to initialize WorktreeManager`)\n return\n }\n\n // Check each module\n for (const module of modules) {\n const moduleUid = module.uid\n const branchName = module.branch_name\n\n // Check if worktree exists locally\n const worktree = await getWorktreeInfoForModule(moduleUid)\n\n if (!worktree?.exists) {\n console.log(`[Daemon] EP995: Module ${moduleUid} missing local worktree - creating...`)\n\n // Create worktree (similar to module_state_changed handler)\n const moduleBranchName = branchName || moduleUid\n const createResult = await worktreeManager.createWorktree(moduleUid, moduleBranchName, true)\n\n if (!createResult.success) {\n console.error(`[Daemon] EP995: Failed to create worktree for ${moduleUid}: ${createResult.error}`)\n continue\n }\n\n console.log(`[Daemon] EP995: Created worktree for ${moduleUid} at ${createResult.worktreePath}`)\n\n // Claim ownership\n if (this.deviceId) {\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}`, {\n method: 'PATCH',\n body: JSON.stringify({ checkout_machine_id: this.deviceId })\n })\n console.log(`[Daemon] EP995: Claimed ownership of ${moduleUid}`)\n } catch (ownershipError) {\n console.warn(`[Daemon] EP995: Failed to claim ownership of ${moduleUid}`)\n }\n }\n\n // Get fresh worktree info\n const newWorktree = await getWorktreeInfoForModule(moduleUid)\n if (!newWorktree?.exists) {\n console.error(`[Daemon] EP995: Worktree still not found after creation`)\n continue\n }\n\n // Run setup\n const setupConfig = config.project_settings\n const envVars = await fetchEnvVars()\n\n console.log(`[Daemon] EP995: Starting setup for reconciled module ${moduleUid}`)\n\n // Mark as pending\n await worktreeManager.updateWorktreeStatus(moduleUid, 'pending')\n await this.updateModuleWorktreeStatus(moduleUid, 'pending', newWorktree.path)\n\n // Run setup async\n this.runWorktreeSetupAsync(\n moduleUid,\n worktreeManager,\n setupConfig?.worktree_copy_files || [],\n setupConfig?.worktree_setup_script,\n newWorktree.path,\n envVars\n ).then(() => {\n console.log(`[Daemon] EP995: Setup complete for reconciled ${moduleUid}`)\n this.startTunnelForModule(moduleUid, newWorktree.path)\n }).catch(err => {\n console.error(`[Daemon] EP995: Setup failed for reconciled ${moduleUid}:`, err)\n })\n\n } else {\n // Worktree exists - ensure worktree_path is reported and check tunnel\n // EP1001: Always update worktree_path on reconciliation to fix cases where\n // the module_state_changed event didn't reach the daemon\n await this.updateModuleWorktreeStatus(moduleUid, 'ready', worktree.path)\n\n const tunnelManager = getTunnelManager()\n await tunnelManager.initialize()\n\n if (!tunnelManager.hasTunnel(moduleUid)) {\n console.log(`[Daemon] EP995: Module ${moduleUid} has worktree but no tunnel - starting...`)\n await this.startTunnelForModule(moduleUid, worktree.path)\n } else {\n console.log(`[Daemon] EP995: Module ${moduleUid} OK - worktree and tunnel exist`)\n }\n }\n }\n\n console.log('[Daemon] EP995: Reconciliation complete')\n } catch (error) {\n console.error('[Daemon] EP995: Reconciliation error:', error instanceof Error ? error.message : error)\n throw error\n }\n }\n\n /**\n * EP956: Cleanup module worktree when module moves to done\n *\n * Runs asynchronously to reduce burden on the state change handler.\n * Steps:\n * 1. Stop dev server for the module\n * 2. Log worktree path for potential removal (actual removal TBD)\n *\n * Note: Worktree removal requires git worktree remove command, which\n * needs careful handling to avoid data loss. For now, just stop the\n * dev server and log the path.\n */\n private async cleanupModuleWorktree(moduleUid: string): Promise<void> {\n console.log(`[Daemon] EP956: Starting async cleanup for ${moduleUid}`)\n\n try {\n // Stop dev server for this module\n await stopDevServer(moduleUid)\n console.log(`[Daemon] EP956: Dev server stopped for ${moduleUid}`)\n\n // EP994: Actually remove the worktree\n const worktree = await getWorktreeInfoForModule(moduleUid)\n if (worktree?.exists && worktree.path) {\n console.log(`[Daemon] EP994: Removing worktree for ${moduleUid} at ${worktree.path}`)\n\n // Find project root from worktree path\n const projectRoot = await findProjectRoot(worktree.path)\n if (projectRoot) {\n const manager = new WorktreeManager(projectRoot)\n if (await manager.initialize()) {\n // Use force=true since module has transitioned to done\n const result = await manager.removeWorktree(moduleUid, true)\n if (result.success) {\n console.log(`[Daemon] EP994: Successfully removed worktree for ${moduleUid}`)\n } else {\n // Non-fatal - worktree may already be gone or have other issues\n console.warn(`[Daemon] EP994: Could not remove worktree for ${moduleUid}: ${result.error}`)\n }\n } else {\n console.warn(`[Daemon] EP994: Could not initialize WorktreeManager for ${moduleUid}`)\n }\n } else {\n console.warn(`[Daemon] EP994: Could not find project root for ${moduleUid} worktree`)\n }\n } else {\n console.log(`[Daemon] EP994: No worktree to remove for ${moduleUid}`)\n }\n\n // EP995: Clear worktree status on server when module moves to done\n // This allows the module to be picked up fresh if reverted\n try {\n const cleanupConfig = await loadConfig()\n const cleanupApiUrl = cleanupConfig?.api_url || 'https://episoda.dev'\n await fetchWithAuth(`${cleanupApiUrl}/api/modules/${moduleUid}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n worktree_status: null,\n worktree_path: null,\n worktree_error: null\n })\n })\n console.log(`[Daemon] EP995: Cleared worktree status for ${moduleUid}`)\n } catch (clearError) {\n // Non-fatal\n console.warn(`[Daemon] EP995: Failed to clear worktree status for ${moduleUid}:`, clearError)\n }\n\n console.log(`[Daemon] EP956: Async cleanup complete for ${moduleUid}`)\n } catch (error) {\n console.error(`[Daemon] EP956: Cleanup error for ${moduleUid}:`, error instanceof Error ? error.message : error)\n throw error // Re-throw for caller to handle\n }\n }\n\n /**\n * EP959-11: Run worktree setup asynchronously\n * EP964: Added envVars parameter to inject .env file\n * Writes .env, copies files, and runs setup script after worktree creation\n */\n private async runWorktreeSetupAsync(\n moduleUid: string,\n worktreeManager: WorktreeManager,\n copyFiles: string[],\n setupScript: string | undefined,\n worktreePath: string,\n envVars: Record<string, string> = {}\n ): Promise<void> {\n console.log(`[Daemon] EP959: Running async worktree setup for ${moduleUid}`)\n\n try {\n // Mark as running (local + server)\n await worktreeManager.updateWorktreeStatus(moduleUid, 'running')\n // EP995: Report setup status to server\n await this.updateModuleWorktreeStatus(moduleUid, 'setup', worktreePath)\n\n // EP988: Write .env file using shared utility\n if (Object.keys(envVars).length > 0) {\n console.log(`[Daemon] EP988: Writing .env with ${Object.keys(envVars).length} variables to ${moduleUid}`)\n writeEnvFile(worktreePath, envVars)\n }\n\n // Copy files from main worktree (deprecated - EP964)\n if (copyFiles.length > 0) {\n console.log(`[Daemon] EP964: DEPRECATED - Copying ${copyFiles.length} files to ${moduleUid}`)\n console.warn(`[Daemon] EP964: worktree_copy_files is deprecated. Use worktree_env_vars or worktree_setup_script instead.`)\n const copyResult = await worktreeManager.copyFilesFromMain(moduleUid, copyFiles)\n if (!copyResult.success) {\n // Non-fatal for deprecated feature\n console.warn(`[Daemon] EP964: File copy failed (non-fatal): ${copyResult.error}`)\n }\n }\n\n // EP986: Auto-install dependencies\n const installCmd = getInstallCommand(worktreePath)\n if (installCmd) {\n console.log(`[Daemon] EP986: ${installCmd.description} (detected from ${installCmd.detectedFrom})`)\n console.log(`[Daemon] EP986: Running: ${installCmd.command.join(' ')}`)\n\n try {\n const { execSync } = await import('child_process')\n execSync(installCmd.command.join(' '), {\n cwd: worktreePath,\n stdio: 'inherit',\n timeout: 10 * 60 * 1000, // 10 minute timeout\n env: { ...process.env, CI: 'true' } // CI=true for cleaner output\n })\n console.log(`[Daemon] EP986: Dependencies installed successfully for ${moduleUid}`)\n } catch (installError) {\n // Non-fatal - log warning but continue\n const errorMsg = installError instanceof Error ? installError.message : String(installError)\n console.warn(`[Daemon] EP986: Dependency installation failed (non-fatal): ${errorMsg}`)\n console.warn(`[Daemon] EP986: You may need to run '${installCmd.command.join(' ')}' manually`)\n }\n } else {\n console.log(`[Daemon] EP986: No package manager detected for ${moduleUid}, skipping dependency installation`)\n }\n\n // Run setup script\n if (setupScript) {\n console.log(`[Daemon] EP959: Running setup script for ${moduleUid}`)\n const scriptResult = await worktreeManager.runSetupScript(moduleUid, setupScript)\n if (!scriptResult.success) {\n throw new Error(`Setup script failed: ${scriptResult.error}`)\n }\n }\n\n // Mark as ready (local + server)\n await worktreeManager.updateWorktreeStatus(moduleUid, 'ready')\n // EP995: Report ready status to server\n await this.updateModuleWorktreeStatus(moduleUid, 'ready', worktreePath)\n console.log(`[Daemon] EP959: Worktree setup complete for ${moduleUid}`)\n\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] EP959: Worktree setup failed for ${moduleUid}:`, errorMessage)\n await worktreeManager.updateWorktreeStatus(moduleUid, 'error', errorMessage)\n // EP995: Report error status to server\n await this.updateModuleWorktreeStatus(moduleUid, 'error', worktreePath, errorMessage)\n throw error\n }\n }\n\n /**\n * EP959-11: Start tunnel for a module after setup completes\n */\n private async startTunnelForModule(moduleUid: string, worktreePath: string): Promise<void> {\n const tunnelManager = getTunnelManager()\n await tunnelManager.initialize()\n\n // Skip if tunnel already running\n if (tunnelManager.hasTunnel(moduleUid)) {\n console.log(`[Daemon] EP959: Tunnel already running for ${moduleUid}`)\n return\n }\n\n try {\n const config = await loadConfig()\n const apiUrl = config?.api_url || 'https://episoda.dev'\n // EP959-m2: Get custom dev server script from project settings\n const devServerScript = config?.project_settings?.worktree_dev_server_script\n\n // Allocate port\n const port = allocatePort(moduleUid)\n console.log(`[Daemon] EP959: Post-setup tunnel start for ${moduleUid} on port ${port}`)\n\n // Start dev server\n const devServerResult = await ensureDevServer(worktreePath, port, moduleUid, devServerScript)\n if (!devServerResult.success) {\n console.error(`[Daemon] EP959: Dev server failed for ${moduleUid}: ${devServerResult.error}`)\n releasePort(moduleUid)\n return\n }\n\n // Start tunnel\n const startResult = await tunnelManager.startTunnel({\n moduleUid,\n port,\n onUrl: async (url) => {\n console.log(`[Daemon] EP959: Tunnel URL for ${moduleUid}: ${url}`)\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'POST',\n body: JSON.stringify({ tunnel_url: url })\n })\n } catch (err) {\n console.warn(`[Daemon] EP959: Failed to report tunnel URL:`, err instanceof Error ? err.message : err)\n }\n },\n onStatusChange: (status, error) => {\n if (status === 'error') {\n console.error(`[Daemon] EP959: Tunnel error for ${moduleUid}: ${error}`)\n }\n }\n })\n\n if (startResult.success) {\n console.log(`[Daemon] EP959: Tunnel started for ${moduleUid}`)\n } else {\n console.error(`[Daemon] EP959: Tunnel failed for ${moduleUid}: ${startResult.error}`)\n releasePort(moduleUid)\n }\n } catch (error) {\n console.error(`[Daemon] EP959: Error starting tunnel for ${moduleUid}:`, error)\n releasePort(moduleUid)\n }\n }\n\n /**\n * EP819: Auto-start tunnels for active local modules on daemon connect/reconnect\n *\n * Queries for modules in doing/review state with dev_mode=local that don't have\n * an active tunnel_url, and starts tunnels for each.\n */\n private async autoStartTunnelsForProject(\n projectPath: string,\n projectUid: string\n ): Promise<void> {\n console.log(`[Daemon] EP819: Checking for active local modules to auto-start tunnels...`)\n\n try {\n const config = await loadConfig()\n if (!config?.access_token) {\n console.warn(`[Daemon] EP819: No access token, skipping tunnel auto-start`)\n return\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n // EP904: Fetch modules using fetchWithAuth for token refresh support\n // EP994: Only query doing/review - ready state modules don't have worktrees yet\n const response = await fetchWithAuth(\n `${apiUrl}/api/modules?state=doing,review&fields=id,uid,dev_mode,tunnel_url,checkout_machine_id`\n )\n\n if (!response.ok) {\n console.warn(`[Daemon] EP819: Failed to fetch modules: ${response.status}`)\n return\n }\n\n const data = await response.json() as { success?: boolean; modules?: any[] }\n const modules = data.modules || []\n\n // Initialize tunnel manager first to check local tunnel state\n const tunnelManager = getTunnelManager()\n await tunnelManager.initialize()\n\n // EP956: Reconcile - stop tunnels for modules that no longer need them\n // This handles modules that moved to done/backlog while daemon was disconnected\n const activeTunnelUids = tunnelManager.getActiveModuleUids()\n const validModuleUids = new Set(\n modules\n .filter((m: any) =>\n m.dev_mode === 'local' &&\n (!m.checkout_machine_id || m.checkout_machine_id === this.deviceId)\n )\n .map((m: any) => m.uid)\n )\n\n const orphanedTunnels = activeTunnelUids.filter(uid => !validModuleUids.has(uid))\n if (orphanedTunnels.length > 0) {\n console.log(`[Daemon] EP956: Found ${orphanedTunnels.length} orphaned tunnels to stop: ${orphanedTunnels.join(', ')}`)\n for (const orphanUid of orphanedTunnels) {\n try {\n await tunnelManager.stopTunnel(orphanUid)\n releasePort(orphanUid)\n console.log(`[Daemon] EP956: Stopped orphaned tunnel and released port for ${orphanUid}`)\n\n // Clear tunnel URL in API\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${orphanUid}/tunnel`, {\n method: 'POST',\n body: JSON.stringify({ tunnel_url: null })\n })\n } catch (err) {\n console.warn(`[Daemon] EP956: Failed to clear tunnel URL for ${orphanUid}:`, err instanceof Error ? err.message : err)\n }\n } catch (err) {\n console.error(`[Daemon] EP956: Failed to stop orphaned tunnel ${orphanUid}:`, err instanceof Error ? err.message : err)\n }\n }\n }\n\n // Filter for local dev modules that need tunnels:\n // - dev_mode is 'local'\n // - checkout_machine_id matches this machine (or is null - unassigned)\n // - Tunnel is NOT already running locally (handles stale tunnel_url in DB)\n // EP822: Use deviceId (UUID) for comparison - checkout_machine_id is a UUID foreign key\n const localModulesNeedingTunnel = modules.filter((m: any) =>\n m.dev_mode === 'local' &&\n (!m.checkout_machine_id || m.checkout_machine_id === this.deviceId) &&\n !tunnelManager.hasTunnel(m.uid)\n )\n\n if (localModulesNeedingTunnel.length === 0) {\n console.log(`[Daemon] EP819: No local modules need tunnel auto-start`)\n return\n }\n\n console.log(`[Daemon] EP956: Found ${localModulesNeedingTunnel.length} local modules needing tunnels`)\n\n // EP956: Start tunnels for ALL modules in parallel\n for (const module of localModulesNeedingTunnel) {\n const moduleUid = module.uid\n\n // EP956: Get worktree path for this module\n const worktree = await getWorktreeInfoForModule(moduleUid)\n if (!worktree) {\n console.warn(`[Daemon] EP956: Cannot resolve worktree for ${moduleUid} (missing config slugs)`)\n continue\n }\n if (!worktree.exists) {\n console.log(`[Daemon] EP956: No worktree for ${moduleUid} at ${worktree.path}, skipping`)\n continue\n }\n\n // EP956: Allocate port for this module\n const port = allocatePort(moduleUid)\n\n console.log(`[Daemon] EP956: Auto-starting tunnel for ${moduleUid} at ${worktree.path} on port ${port}`)\n\n // EP904: Helper to report tunnel status to the API with token refresh\n const reportTunnelStatus = async (statusData: {\n tunnel_url?: string | null\n tunnel_error?: string | null\n tunnel_started_at?: string | null\n }) => {\n try {\n const statusResponse = await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'POST',\n body: JSON.stringify(statusData)\n })\n\n if (statusResponse.ok) {\n console.log(`[Daemon] EP819: Tunnel status reported for ${moduleUid}`)\n } else {\n console.warn(`[Daemon] EP819: Failed to report tunnel status: ${statusResponse.statusText}`)\n }\n } catch (reportError) {\n console.warn(`[Daemon] EP819: Error reporting tunnel status:`, reportError)\n }\n }\n\n // Start tunnel asynchronously (don't block other modules)\n // EP843: Use mutex to prevent race condition with module_state_changed handler\n ;(async () => {\n await this.withTunnelLock(moduleUid, async () => {\n // Re-check if tunnel is already running (may have been started by module_state_changed)\n if (tunnelManager.hasTunnel(moduleUid)) {\n console.log(`[Daemon] EP956: Tunnel already running for ${moduleUid}, skipping auto-start`)\n // EP956: Port is idempotent - same port was already allocated, don't release\n return\n }\n\n const MAX_RETRIES = 3\n // EP953: Increased retry delay for more reliable tunnel startup after cleanup\n const RETRY_DELAY_MS = 3000\n\n // Report that tunnel is starting\n await reportTunnelStatus({\n tunnel_started_at: new Date().toISOString(),\n tunnel_error: null\n })\n\n try {\n // EP956: Ensure dev server is running in the module's worktree\n // EP959-m2: Use custom dev server script from project settings\n const devServerScript = config.project_settings?.worktree_dev_server_script\n console.log(`[Daemon] EP956: Ensuring dev server is running for ${moduleUid} at ${worktree.path}...`)\n const devServerResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript)\n if (!devServerResult.success) {\n const errorMsg = `Dev server failed to start: ${devServerResult.error}`\n console.error(`[Daemon] EP956: ${errorMsg}`)\n await reportTunnelStatus({ tunnel_error: errorMsg })\n // EP956: Release port on failure\n releasePort(moduleUid)\n return\n }\n console.log(`[Daemon] EP956: Dev server ready on port ${port}`)\n\n // Start tunnel with retry logic\n let lastError: string | undefined\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n console.log(`[Daemon] EP819: Starting tunnel for ${moduleUid} (attempt ${attempt}/${MAX_RETRIES})...`)\n\n const startResult = await tunnelManager.startTunnel({\n moduleUid,\n port,\n onUrl: async (url) => {\n console.log(`[Daemon] EP819: Tunnel URL for ${moduleUid}: ${url}`)\n await reportTunnelStatus({\n tunnel_url: url,\n tunnel_error: null\n })\n },\n onStatusChange: (status, error) => {\n if (status === 'error') {\n console.error(`[Daemon] EP819: Tunnel error for ${moduleUid}: ${error}`)\n reportTunnelStatus({ tunnel_error: error || 'Tunnel connection error' })\n } else if (status === 'reconnecting') {\n console.log(`[Daemon] EP819: Tunnel reconnecting for ${moduleUid}...`)\n }\n }\n })\n\n if (startResult.success) {\n console.log(`[Daemon] EP819: Tunnel started successfully for ${moduleUid}`)\n return\n }\n\n lastError = startResult.error\n console.warn(`[Daemon] EP819: Tunnel start attempt ${attempt} failed: ${lastError}`)\n\n if (attempt < MAX_RETRIES) {\n console.log(`[Daemon] EP819: Retrying in ${RETRY_DELAY_MS}ms...`)\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS))\n }\n }\n\n // All retries failed\n const errorMsg = `Tunnel failed after ${MAX_RETRIES} attempts: ${lastError}`\n console.error(`[Daemon] EP956: ${errorMsg}`)\n await reportTunnelStatus({ tunnel_error: errorMsg })\n // EP956: Release port on failure\n releasePort(moduleUid)\n\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] EP956: Async tunnel startup error:`, error)\n await reportTunnelStatus({ tunnel_error: errorMsg })\n // EP956: Release port on exception\n releasePort(moduleUid)\n }\n })\n })()\n }\n\n } catch (error) {\n console.error(`[Daemon] EP819: Error auto-starting tunnels:`, error)\n }\n }\n\n // EP843: startTunnelPolling() removed - replaced by push-based state sync\n // See module_state_changed handler for the new implementation\n\n /**\n * EP822: Stop periodic tunnel polling\n * EP843: Kept for cleanup during shutdown, but interval is never started\n */\n private stopTunnelPolling(): void {\n if (this.tunnelPollInterval) {\n clearInterval(this.tunnelPollInterval)\n this.tunnelPollInterval = null\n console.log('[Daemon] EP822: Tunnel polling stopped')\n }\n }\n\n /**\n * EP929: Start health check polling\n *\n * Restored from EP843 removal. Health checks run every 60 seconds to:\n * - Verify running tunnels are still responsive\n * - Detect dead tunnels that haven't been cleaned up\n * - Auto-restart unhealthy tunnels after consecutive failures\n *\n * This is orthogonal to the push-based state sync (module_state_changed events).\n * State sync handles start/stop based on module transitions.\n * Health checks handle detecting and recovering from tunnel crashes.\n */\n private startHealthCheckPolling(): void {\n if (this.healthCheckInterval) {\n console.log('[Daemon] EP929: Health check polling already running')\n return\n }\n\n console.log(`[Daemon] EP929: Starting health check polling (every ${Daemon.HEALTH_CHECK_INTERVAL_MS / 1000}s)`)\n\n this.healthCheckInterval = setInterval(async () => {\n // Guard against overlapping health checks\n if (this.healthCheckInProgress) {\n console.log('[Daemon] EP929: Health check still in progress, skipping')\n return\n }\n\n this.healthCheckInProgress = true\n try {\n const config = await loadConfig()\n if (config?.access_token) {\n await this.performHealthChecks(config)\n }\n } catch (error) {\n console.error('[Daemon] EP929: Health check error:', error instanceof Error ? error.message : error)\n } finally {\n this.healthCheckInProgress = false\n }\n }, Daemon.HEALTH_CHECK_INTERVAL_MS)\n }\n\n /**\n * EP929: Stop health check polling\n */\n private stopHealthCheckPolling(): void {\n if (this.healthCheckInterval) {\n clearInterval(this.healthCheckInterval)\n this.healthCheckInterval = null\n console.log('[Daemon] EP929: Health check polling stopped')\n }\n }\n\n /**\n * EP822: Clean up orphaned tunnels from previous daemon runs\n * EP904: Enhanced to aggressively clean ALL cloudflared processes then restart\n * tunnels for qualifying modules (doing/review state, dev_mode=local)\n *\n * When the daemon crashes or is killed, tunnels may continue running.\n * This method:\n * 1. Kills ALL cloudflared processes (aggressive cleanup for clean slate)\n * 2. Queries API for modules that should have tunnels\n * 3. Restarts tunnels for qualifying modules\n */\n private async cleanupOrphanedTunnels(): Promise<void> {\n try {\n const tunnelManager = getTunnelManager()\n await tunnelManager.initialize()\n\n // EP904: Clean up any in-memory tracked tunnels first\n const runningTunnels = tunnelManager.getAllTunnels()\n if (runningTunnels.length > 0) {\n console.log(`[Daemon] EP904: Stopping ${runningTunnels.length} tracked tunnel(s)...`)\n for (const tunnel of runningTunnels) {\n try {\n await tunnelManager.stopTunnel(tunnel.moduleUid)\n await stopDevServer(tunnel.moduleUid)\n } catch (error) {\n console.error(`[Daemon] EP904: Failed to stop tunnel for ${tunnel.moduleUid}:`, error)\n }\n }\n }\n\n // EP904: Use TunnelManager's orphan cleanup to kill ALL cloudflared processes\n // This catches processes not tracked in memory (from previous daemon instances)\n const cleanup = await tunnelManager.cleanupOrphanedProcesses()\n if (cleanup.cleaned > 0) {\n console.log(`[Daemon] EP904: Killed ${cleanup.cleaned} orphaned cloudflared process(es)`)\n }\n\n console.log('[Daemon] EP904: Orphaned tunnel cleanup complete - clean slate ready')\n\n // Note: Tunnel restart for qualifying modules happens in autoStartTunnelsForProject()\n // which is called from auth_success handler after this method completes\n } catch (error) {\n console.error('[Daemon] EP904: Failed to clean up orphaned tunnels:', error)\n }\n }\n\n /**\n * EP957: Audit worktrees on daemon startup to detect orphaned worktrees\n *\n * Compares local worktrees against active modules (doing/review state).\n * Logs orphaned worktrees for visibility but does NOT auto-cleanup - that's\n * the user's decision via `episoda release` or manual cleanup.\n *\n * This is a non-blocking diagnostic - failures are logged but don't affect startup.\n */\n private async auditWorktreesOnStartup(): Promise<void> {\n try {\n const projects = getAllProjects()\n\n for (const project of projects) {\n await this.auditProjectWorktrees(project.path)\n }\n } catch (error) {\n console.warn('[Daemon] EP957: Worktree audit failed (non-blocking):', error)\n }\n }\n\n /**\n * EP957: Audit worktrees for a single project\n */\n private async auditProjectWorktrees(projectPath: string): Promise<void> {\n try {\n // Find project root (may be in a worktree subdirectory)\n const projectRoot = await findProjectRoot(projectPath)\n if (!projectRoot) {\n return // Not a worktree project\n }\n\n const manager = new WorktreeManager(projectRoot)\n if (!await manager.initialize()) {\n return // Not a valid worktree project\n }\n\n const config = manager.getConfig()\n if (!config) {\n return\n }\n\n // Get list of worktrees\n const worktrees = manager.listWorktrees()\n if (worktrees.length === 0) {\n return // No worktrees to audit\n }\n\n // Fetch active modules from API to compare\n const activeModuleUids = await this.fetchActiveModuleUids(config.projectId)\n if (activeModuleUids === null) {\n return // Couldn't fetch, skip audit\n }\n\n // Audit worktrees\n const { orphaned } = manager.auditWorktrees(activeModuleUids)\n\n if (orphaned.length > 0) {\n console.log(`[Daemon] EP957: Found ${orphaned.length} orphaned worktree(s) in ${config.workspaceSlug}/${config.projectSlug}:`)\n for (const w of orphaned) {\n console.log(` - ${w.moduleUid} (branch: ${w.branchName})`)\n }\n console.log('[Daemon] EP957: Run \"episoda release <module>\" to clean up')\n }\n } catch (error) {\n // Non-blocking - just log\n console.warn(`[Daemon] EP957: Failed to audit ${projectPath}:`, error)\n }\n }\n\n /**\n * EP957: Fetch UIDs of active modules (doing/review state) for a project\n * Returns null if fetch fails (non-blocking)\n */\n private async fetchActiveModuleUids(projectId: string): Promise<string[] | null> {\n try {\n const config = await loadConfig()\n if (!config?.access_token || !config?.api_url) {\n return null\n }\n\n const url = `${config.api_url}/api/modules?project_id=${projectId}&state=doing,review`\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json'\n }\n })\n\n if (!response.ok) {\n return null\n }\n\n const data = await response.json() as { success?: boolean; modules?: Array<{ uid: string }> }\n if (!data.success || !data.modules) {\n return null\n }\n\n return data.modules.map(m => m.uid).filter((uid): uid is string => !!uid)\n } catch {\n return null\n }\n }\n\n // EP843: syncTunnelsWithActiveModules() removed - replaced by push-based state sync\n // See module_state_changed handler for the new implementation\n\n /**\n * EP833: Perform health checks on all running tunnels\n * Checks both tunnel URL and local dev server responsiveness\n */\n private async performHealthChecks(config: EpisodaConfig): Promise<void> {\n const tunnelManager = getTunnelManager()\n const runningTunnels = tunnelManager.getAllTunnels()\n\n if (runningTunnels.length === 0) {\n return\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n for (const tunnel of runningTunnels) {\n if (tunnel.status !== 'connected') {\n continue // Skip tunnels that aren't connected\n }\n\n const isHealthy = await this.checkTunnelHealth(tunnel)\n\n if (isHealthy) {\n // Reset failure counter on success\n this.tunnelHealthFailures.delete(tunnel.moduleUid)\n await this.reportTunnelHealth(tunnel.moduleUid, 'healthy', config)\n } else {\n // Increment failure counter\n const failures = (this.tunnelHealthFailures.get(tunnel.moduleUid) || 0) + 1\n this.tunnelHealthFailures.set(tunnel.moduleUid, failures)\n\n console.log(`[Daemon] EP833: Health check failed for ${tunnel.moduleUid} (${failures}/${Daemon.HEALTH_CHECK_FAILURE_THRESHOLD})`)\n\n if (failures >= Daemon.HEALTH_CHECK_FAILURE_THRESHOLD) {\n console.log(`[Daemon] EP833: Tunnel unhealthy for ${tunnel.moduleUid}, restarting...`)\n // EP929: Use mutex to prevent race condition with module_state_changed handler\n await this.withTunnelLock(tunnel.moduleUid, async () => {\n await this.restartTunnel(tunnel.moduleUid, tunnel.port)\n })\n this.tunnelHealthFailures.delete(tunnel.moduleUid)\n await this.reportTunnelHealth(tunnel.moduleUid, 'unhealthy', config)\n }\n }\n }\n }\n\n /**\n * EP833: Check if a tunnel is healthy\n * Verifies both the tunnel URL and local dev server respond\n */\n private async checkTunnelHealth(tunnel: { moduleUid: string; url: string; port: number }): Promise<boolean> {\n // Check 1: Tunnel URL responds\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), Daemon.HEALTH_CHECK_TIMEOUT_MS)\n\n const response = await fetch(tunnel.url, {\n method: 'HEAD',\n signal: controller.signal\n })\n clearTimeout(timeout)\n\n // 5xx errors indicate server problems\n if (response.status >= 500) {\n console.log(`[Daemon] EP833: Tunnel URL returned ${response.status} for ${tunnel.moduleUid}`)\n return false\n }\n } catch (error) {\n // Network error, tunnel is down\n console.log(`[Daemon] EP833: Tunnel URL unreachable for ${tunnel.moduleUid}:`, error instanceof Error ? error.message : error)\n return false\n }\n\n // Check 2: Dev server on localhost responds\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 2000) // 2s timeout for local\n\n const localResponse = await fetch(`http://localhost:${tunnel.port}`, {\n method: 'HEAD',\n signal: controller.signal\n })\n clearTimeout(timeout)\n\n if (localResponse.status >= 500) {\n console.log(`[Daemon] EP833: Local dev server returned ${localResponse.status} for ${tunnel.moduleUid}`)\n return false\n }\n } catch (error) {\n console.log(`[Daemon] EP833: Local dev server unreachable for ${tunnel.moduleUid}:`, error instanceof Error ? error.message : error)\n return false\n }\n\n return true\n }\n\n /**\n * EP833: Restart a failed tunnel\n * EP932: Now uses restartDevServer() for robust dev server restart with auto-restart\n */\n private async restartTunnel(moduleUid: string, port: number): Promise<void> {\n const tunnelManager = getTunnelManager()\n\n try {\n // Stop existing tunnel\n await tunnelManager.stopTunnel(moduleUid)\n\n // Load config for API access\n const config = await loadConfig()\n if (!config?.access_token) {\n console.error(`[Daemon] EP833: No access token for tunnel restart`)\n return\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n // EP932: Use restartDevServer() which handles:\n // - Stopping the current server\n // - Killing hung processes on the port\n // - Starting a fresh server with auto-restart enabled\n const devServerResult = await restartDevServer(moduleUid)\n\n if (!devServerResult.success) {\n // If no tracked server exists, we need to start fresh\n // EP833: Lookup module's project_id from API to find correct project path\n console.log(`[Daemon] EP932: No tracked server for ${moduleUid}, looking up project...`)\n\n let projectId: string | null = null\n try {\n const moduleResponse = await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}`)\n if (moduleResponse.ok) {\n const moduleData = await moduleResponse.json() as { moduleRecord?: { project_id?: string } }\n projectId = moduleData.moduleRecord?.project_id ?? null\n }\n } catch (e) {\n console.warn(`[Daemon] EP833: Failed to fetch module details for project lookup`)\n }\n\n // EP973: Resolve worktree path for this module (fix: was using project.path which is project root)\n const worktree = await getWorktreeInfoForModule(moduleUid)\n if (!worktree) {\n console.error(`[Daemon] EP973: Cannot resolve worktree path for ${moduleUid} - missing config slugs`)\n return\n }\n\n if (!worktree.exists) {\n console.error(`[Daemon] EP973: Worktree not found at ${worktree.path}`)\n return\n }\n\n // EP929: Check if port is in use by an untracked/hung process\n const { isPortInUse } = await import('../utils/port-check')\n if (await isPortInUse(port)) {\n console.log(`[Daemon] EP932: Port ${port} in use, checking health...`)\n const healthy = await isDevServerHealthy(port)\n if (!healthy) {\n console.log(`[Daemon] EP932: Dev server on port ${port} is not responding, killing process...`)\n await killProcessOnPort(port)\n }\n }\n\n // EP973: Get custom dev server script from project settings\n const devServerScript = config.project_settings?.worktree_dev_server_script\n\n // Start fresh dev server with auto-restart (EP973: use worktree.path, not project.path)\n const startResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript)\n if (!startResult.success) {\n console.error(`[Daemon] EP932: Failed to start dev server: ${startResult.error}`)\n return\n }\n }\n\n console.log(`[Daemon] EP932: Dev server ready, restarting tunnel for ${moduleUid}...`)\n\n // Restart tunnel\n const startResult = await tunnelManager.startTunnel({\n moduleUid,\n port,\n onUrl: async (url) => {\n console.log(`[Daemon] EP833: Tunnel restarted for ${moduleUid}: ${url}`)\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'POST',\n body: JSON.stringify({\n tunnel_url: url,\n tunnel_error: null\n })\n })\n } catch (e) {\n console.warn(`[Daemon] EP833: Failed to report restarted tunnel URL`)\n }\n }\n })\n\n if (startResult.success) {\n console.log(`[Daemon] EP833: Tunnel restart successful for ${moduleUid}`)\n } else {\n console.error(`[Daemon] EP833: Tunnel restart failed for ${moduleUid}: ${startResult.error}`)\n }\n } catch (error) {\n console.error(`[Daemon] EP833: Error restarting tunnel for ${moduleUid}:`, error)\n }\n }\n\n /**\n * EP833: Report tunnel health status to the API\n * EP904: Use fetchWithAuth for token refresh\n * EP911: Only report when status CHANGES to reduce DB writes\n */\n private async reportTunnelHealth(\n moduleUid: string,\n healthStatus: 'healthy' | 'unhealthy' | 'unknown',\n config: EpisodaConfig\n ): Promise<void> {\n if (!config.access_token) {\n return\n }\n\n // EP911: Skip update if status hasn't changed (reduces module table writes by ~90%)\n const lastStatus = this.lastReportedHealthStatus.get(moduleUid)\n if (lastStatus === healthStatus) {\n return // Status unchanged, no need to update DB\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/health`, {\n method: 'PATCH',\n body: JSON.stringify({\n tunnel_health_status: healthStatus,\n tunnel_last_health_check: new Date().toISOString()\n })\n })\n // EP911: Track the successfully reported status\n this.lastReportedHealthStatus.set(moduleUid, healthStatus)\n } catch (error) {\n // Non-critical, just log\n console.warn(`[Daemon] EP833: Failed to report health for ${moduleUid}:`, error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP833: Kill processes matching a pattern\n * Used to clean up orphaned cloudflared processes\n */\n private async killProcessesByPattern(pattern: string): Promise<number> {\n const { exec } = await import('child_process')\n const { promisify } = await import('util')\n const execAsync = promisify(exec)\n\n try {\n // Find PIDs matching pattern using pgrep\n const { stdout } = await execAsync(`pgrep -f \"${pattern}\"`)\n const pids = stdout.trim().split('\\n').filter(Boolean)\n\n if (pids.length === 0) {\n return 0\n }\n\n // Kill each process\n let killed = 0\n for (const pid of pids) {\n try {\n await execAsync(`kill ${pid}`)\n killed++\n } catch {\n // Process may have already exited\n }\n }\n\n if (killed > 0) {\n console.log(`[Daemon] EP833: Killed ${killed} process(es) matching: ${pattern}`)\n }\n return killed\n } catch {\n // No matching processes, which is fine\n return 0\n }\n }\n\n /**\n * EP833: Kill process using a specific port\n * Used to clean up dev servers when stopping tunnels\n */\n private async killProcessOnPort(port: number): Promise<boolean> {\n const { exec } = await import('child_process')\n const { promisify } = await import('util')\n const execAsync = promisify(exec)\n\n try {\n // Find PIDs using the port\n const { stdout } = await execAsync(`lsof -ti :${port}`)\n const pids = stdout.trim().split('\\n').filter(Boolean)\n\n if (pids.length === 0) {\n return false\n }\n\n // Kill each process\n let killed = false\n for (const pid of pids) {\n try {\n await execAsync(`kill ${pid}`)\n killed = true\n } catch {\n // Process may have already exited\n }\n }\n\n if (killed) {\n console.log(`[Daemon] EP833: Killed process(es) on port ${port}`)\n }\n return killed\n } catch {\n // No process on port, which is fine\n return false\n }\n }\n\n /**\n * EP833: Find orphaned cloudflared processes\n * Returns process info for cloudflared processes not tracked by TunnelManager\n */\n private async findOrphanedCloudflaredProcesses(): Promise<Array<{ pid: string; moduleUid?: string }>> {\n const { exec } = await import('child_process')\n const { promisify } = await import('util')\n const execAsync = promisify(exec)\n\n try {\n // Find all cloudflared processes with their command lines\n const { stdout } = await execAsync('ps aux | grep cloudflared | grep -v grep')\n const lines = stdout.trim().split('\\n').filter(Boolean)\n\n const tunnelManager = getTunnelManager()\n const trackedModules = new Set(tunnelManager.getAllTunnels().map(t => t.moduleUid))\n const orphaned: Array<{ pid: string; moduleUid?: string }> = []\n\n for (const line of lines) {\n const parts = line.trim().split(/\\s+/)\n const pid = parts[1]\n\n // Try to extract module UID from process command line\n // Pattern: cloudflared tunnel --url http://localhost:PORT\n // We can't easily get module UID from the process, but we can identify orphans\n // by comparing the number of processes to the number of tracked tunnels\n\n if (pid) {\n orphaned.push({ pid })\n }\n }\n\n // If we have more cloudflared processes than tracked tunnels, some are orphaned\n if (orphaned.length > trackedModules.size) {\n console.log(`[Daemon] EP833: Found ${orphaned.length} cloudflared processes but only ${trackedModules.size} tracked tunnels`)\n }\n\n return orphaned.length > trackedModules.size ? orphaned : []\n } catch {\n // No cloudflared processes found\n return []\n }\n }\n\n /**\n * EP833: Clean up orphaned cloudflared processes\n * Called during sync to ensure no zombie tunnels are running\n */\n private async cleanupOrphanedCloudflaredProcesses(): Promise<void> {\n const orphaned = await this.findOrphanedCloudflaredProcesses()\n\n if (orphaned.length === 0) {\n return\n }\n\n console.log(`[Daemon] EP833: Cleaning up ${orphaned.length} potentially orphaned cloudflared process(es)`)\n\n // Kill orphaned processes\n const killed = await this.killProcessesByPattern('cloudflared tunnel')\n\n if (killed > 0) {\n console.log(`[Daemon] EP833: Cleaned up ${killed} orphaned cloudflared process(es)`)\n }\n }\n\n // EP843: syncLocalCommits() removed - replaced by GitHub webhook push handler\n // See /api/webhooks/github handlePushEvent() for the new implementation\n\n /**\n * Gracefully shutdown daemon\n */\n private async shutdown(): Promise<void> {\n if (this.shuttingDown) return\n this.shuttingDown = true\n\n console.log('[Daemon] Shutting down...')\n\n // EP822: Stop tunnel polling\n this.stopTunnelPolling()\n\n // EP929: Stop health check polling\n this.stopHealthCheckPolling()\n\n // Close all WebSocket connections\n for (const [projectPath, connection] of this.connections) {\n if (connection.reconnectTimer) {\n clearTimeout(connection.reconnectTimer)\n }\n await connection.client.disconnect()\n }\n this.connections.clear()\n\n // EP672: Stop all active tunnels\n try {\n const tunnelManager = getTunnelManager()\n await tunnelManager.stopAllTunnels()\n console.log('[Daemon] All tunnels stopped')\n } catch (error) {\n console.error('[Daemon] Failed to stop tunnels:', error)\n }\n\n // EP956: Clear all port allocations (in-memory, resets on daemon restart)\n clearAllPorts()\n\n // EP912: Stop all active agent sessions\n try {\n const agentManager = getAgentManager()\n await agentManager.stopAllSessions()\n console.log('[Daemon] All agent sessions stopped')\n } catch (error) {\n console.error('[Daemon] Failed to stop agent sessions:', error)\n }\n\n // EP812: Removed identity server shutdown - browser uses cookie-based pairing\n\n // Stop IPC server\n await this.ipcServer.stop()\n\n console.log('[Daemon] Shutdown complete')\n }\n\n /**\n * EP613: Clean up PID file and exit gracefully\n * Called when user explicitly requests disconnect\n */\n private async cleanupAndExit(): Promise<void> {\n await this.shutdown()\n\n // Clean up PID file\n try {\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n console.log('[Daemon] PID file cleaned up')\n }\n } catch (error) {\n console.error('[Daemon] Failed to clean up PID file:', error)\n }\n\n console.log('[Daemon] Exiting...')\n process.exit(0)\n }\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n // Only run if explicitly in daemon mode\n if (!process.env.EPISODA_DAEMON_MODE) {\n console.error('This script should only be run by daemon-manager')\n process.exit(1)\n }\n\n const daemon = new Daemon()\n await daemon.start()\n\n // Keep process alive\n await new Promise(() => {\n // Never resolves - daemon runs until killed\n })\n}\n\n// Run daemon\nmain().catch(error => {\n console.error('[Daemon] Fatal error:', error)\n process.exit(1)\n})\n","/**\n * CLI Auto-Update Checker\n * EP783: Check for updates on daemon startup and auto-update in background\n *\n * This module provides non-blocking update checking and background updates\n * to ensure users always have the latest CLI version with correct git hooks.\n */\n\nimport { spawn, execSync } from 'child_process'\nimport * as semver from 'semver'\n\nconst PACKAGE_NAME = 'episoda'\nconst NPM_REGISTRY = 'https://registry.npmjs.org'\n\nexport interface UpdateCheckResult {\n currentVersion: string\n latestVersion: string\n updateAvailable: boolean\n /** EP989: True if check failed due to network issues (offline, timeout, etc.) */\n offline?: boolean\n}\n\n/**\n * Check npm registry for latest version\n * Non-blocking - fails silently on network errors\n */\nexport async function checkForUpdates(currentVersion: string): Promise<UpdateCheckResult> {\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 5000) // 5s timeout\n\n const response = await fetch(`${NPM_REGISTRY}/${PACKAGE_NAME}/latest`, {\n signal: controller.signal\n })\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n return { currentVersion, latestVersion: currentVersion, updateAvailable: false }\n }\n\n const data = await response.json() as { version: string }\n const latestVersion = data.version\n\n return {\n currentVersion,\n latestVersion,\n updateAvailable: semver.gt(latestVersion, currentVersion)\n }\n } catch (error) {\n // EP989: Detect network errors and report offline status\n // This includes timeouts (AbortError), DNS failures, connection refused, etc.\n return { currentVersion, latestVersion: currentVersion, updateAvailable: false, offline: true }\n }\n}\n\n/**\n * Run npm update in background (detached process)\n * The update runs independently of the daemon process\n */\nexport function performBackgroundUpdate(): void {\n try {\n const child = spawn('npm', ['update', '-g', PACKAGE_NAME], {\n detached: true,\n stdio: 'ignore',\n // Use shell on Windows for proper npm execution\n shell: process.platform === 'win32'\n })\n child.unref() // Allow parent to exit independently\n } catch (error) {\n // Silently ignore spawn errors\n }\n}\n\n/**\n * EP989: Get the currently installed version of the CLI from npm\n * Used for version verification after update\n */\nexport function getInstalledVersion(): string | null {\n try {\n const output = execSync(`npm list -g ${PACKAGE_NAME} --json`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n timeout: 10000\n }).toString()\n\n const data = JSON.parse(output)\n // npm list output structure: { dependencies: { episoda: { version: \"x.x.x\" } } }\n return data?.dependencies?.[PACKAGE_NAME]?.version || null\n } catch {\n // Package might not be installed globally or npm command failed\n return null\n }\n}\n","/**\n * EP808: File operation handlers for remote access\n *\n * These handlers enable Claude Code on one machine to read/write files\n * on another machine via WebSocket.\n *\n * All operations use Node.js native fs APIs for cross-platform compatibility\n * (works on macOS, Linux, and Windows).\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as readline from 'readline'\nimport { RemoteCommand, RemoteCommandResult } from '@episoda/core'\n\n// Maximum file size for read operations (20MB)\nconst DEFAULT_MAX_FILE_SIZE = 20 * 1024 * 1024\n\n/**\n * Validates a path to prevent directory traversal attacks\n * Returns the resolved absolute path if valid, null if invalid\n *\n * SECURITY: All paths MUST resolve to within the project directory.\n * This prevents access to system files like /etc/passwd even with absolute paths.\n */\nfunction validatePath(filePath: string, projectPath: string): string | null {\n // Normalize the project path for consistent comparison\n const normalizedProjectPath = path.resolve(projectPath)\n\n // Resolve to absolute path relative to project\n const absolutePath = path.isAbsolute(filePath)\n ? path.resolve(filePath)\n : path.resolve(projectPath, filePath)\n\n // Normalize to handle ../ etc\n const normalizedPath = path.normalize(absolutePath)\n\n // CRITICAL: Always verify path is within project boundaries\n // This blocks both traversal attacks (../) AND absolute paths outside project\n if (!normalizedPath.startsWith(normalizedProjectPath + path.sep) &&\n normalizedPath !== normalizedProjectPath) {\n return null\n }\n\n return normalizedPath\n}\n\n/**\n * Handle file:read command\n */\nexport async function handleFileRead(\n command: Extract<RemoteCommand, { action: 'file:read' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, encoding = 'base64', maxSize = DEFAULT_MAX_FILE_SIZE } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if file exists\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'File not found'\n }\n }\n\n // Check file stats\n const stats = fs.statSync(validPath)\n\n if (stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is a directory, not a file'\n }\n }\n\n if (stats.size > maxSize) {\n return {\n success: false,\n error: `File too large: ${stats.size} bytes exceeds limit of ${maxSize} bytes`\n }\n }\n\n // Read file\n const buffer = fs.readFileSync(validPath)\n let content: string\n\n if (encoding === 'base64') {\n content = buffer.toString('base64')\n } else {\n content = buffer.toString('utf8')\n }\n\n return {\n success: true,\n content,\n encoding,\n size: stats.size\n }\n } catch (error) {\n // Don't expose internal error details that might contain paths\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to read file'\n }\n }\n}\n\n/**\n * Handle file:write command\n */\nexport async function handleFileWrite(\n command: Extract<RemoteCommand, { action: 'file:write' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, content, encoding = 'utf8', createDirs = true } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Create parent directories if needed\n if (createDirs) {\n const dirPath = path.dirname(validPath)\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true })\n }\n }\n\n // Decode content\n let buffer: Buffer\n if (encoding === 'base64') {\n buffer = Buffer.from(content, 'base64')\n } else {\n buffer = Buffer.from(content, 'utf8')\n }\n\n // Write to temp file first, then rename (atomic write)\n const tempPath = `${validPath}.tmp.${Date.now()}`\n fs.writeFileSync(tempPath, buffer)\n fs.renameSync(tempPath, validPath)\n\n return {\n success: true,\n bytesWritten: buffer.length\n }\n } catch (error) {\n // Don't expose internal error details that might contain paths\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n const isDiskError = errMsg.includes('ENOSPC') || errMsg.includes('no space')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : isDiskError ? 'Disk full' : 'Failed to write file'\n }\n }\n}\n\n/**\n * Handle file:list command\n * Lists directory contents using Node.js fs (cross-platform)\n */\nexport async function handleFileList(\n command: Extract<RemoteCommand, { action: 'file:list' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: dirPath, recursive = false, includeHidden = false } = command\n\n // Validate path\n const validPath = validatePath(dirPath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if path exists and is a directory\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Directory not found'\n }\n }\n\n const stats = fs.statSync(validPath)\n if (!stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is not a directory'\n }\n }\n\n const entries: Array<{ name: string; type: 'file' | 'directory'; size: number }> = []\n\n if (recursive) {\n // Recursive listing\n await listDirectoryRecursive(validPath, validPath, entries, includeHidden)\n } else {\n // Single directory listing\n const dirEntries = await fs.promises.readdir(validPath, { withFileTypes: true })\n for (const entry of dirEntries) {\n if (!includeHidden && entry.name.startsWith('.')) continue\n\n const entryPath = path.join(validPath, entry.name)\n const entryStats = await fs.promises.stat(entryPath)\n\n entries.push({\n name: entry.name,\n type: entry.isDirectory() ? 'directory' : 'file',\n size: entryStats.size\n })\n }\n }\n\n return {\n success: true,\n entries\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to list directory'\n }\n }\n}\n\n/**\n * Recursively list directory contents\n */\nasync function listDirectoryRecursive(\n basePath: string,\n currentPath: string,\n entries: Array<{ name: string; type: 'file' | 'directory'; size: number }>,\n includeHidden: boolean\n): Promise<void> {\n const dirEntries = await fs.promises.readdir(currentPath, { withFileTypes: true })\n\n for (const entry of dirEntries) {\n if (!includeHidden && entry.name.startsWith('.')) continue\n\n const entryPath = path.join(currentPath, entry.name)\n const relativePath = path.relative(basePath, entryPath)\n\n try {\n const entryStats = await fs.promises.stat(entryPath)\n\n entries.push({\n name: relativePath,\n type: entry.isDirectory() ? 'directory' : 'file',\n size: entryStats.size\n })\n\n if (entry.isDirectory()) {\n await listDirectoryRecursive(basePath, entryPath, entries, includeHidden)\n }\n } catch {\n // Skip entries we can't stat (permission denied, etc.)\n }\n }\n}\n\n/**\n * Handle file:search command\n * Searches for files by glob pattern using Node.js (cross-platform)\n */\nexport async function handleFileSearch(\n command: Extract<RemoteCommand, { action: 'file:search' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { pattern, basePath, maxResults = 100 } = command\n const effectiveMaxResults = Math.min(Math.max(1, maxResults), 1000)\n\n // Validate path\n const validPath = validatePath(basePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Directory not found'\n }\n }\n\n const files: string[] = []\n const globRegex = globToRegex(pattern)\n\n await searchFilesRecursive(validPath, validPath, globRegex, files, effectiveMaxResults)\n\n return {\n success: true,\n files\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to search files'\n }\n }\n}\n\n/**\n * Convert a glob pattern to a regex\n *\n * Supports:\n * - * (any chars except /)\n * - ** (any chars including /)\n * - ? (single char except /)\n * - {a,b,c} (brace expansion - matches a OR b OR c)\n * - [abc] (character class - matches a, b, or c)\n * - [a-z] (character range - matches a through z)\n * - [!abc] or [^abc] (negated class - matches anything except a, b, c)\n */\nfunction globToRegex(pattern: string): RegExp {\n let i = 0\n let regexStr = ''\n\n while (i < pattern.length) {\n const char = pattern[i]\n\n // Handle ** (globstar - matches anything including /)\n if (char === '*' && pattern[i + 1] === '*') {\n regexStr += '.*'\n i += 2\n // Skip trailing / after **\n if (pattern[i] === '/') i++\n continue\n }\n\n // Handle * (matches any chars except /)\n if (char === '*') {\n regexStr += '[^/]*'\n i++\n continue\n }\n\n // Handle ? (matches single char except /)\n if (char === '?') {\n regexStr += '[^/]'\n i++\n continue\n }\n\n // Handle {a,b,c} (brace expansion)\n if (char === '{') {\n const closeIdx = pattern.indexOf('}', i)\n if (closeIdx !== -1) {\n const options = pattern.slice(i + 1, closeIdx).split(',')\n const escaped = options.map(opt => escapeRegexChars(opt))\n regexStr += `(?:${escaped.join('|')})`\n i = closeIdx + 1\n continue\n }\n }\n\n // Handle [...] (character class)\n if (char === '[') {\n const closeIdx = findClosingBracket(pattern, i)\n if (closeIdx !== -1) {\n let classContent = pattern.slice(i + 1, closeIdx)\n // Convert [!...] to [^...] (glob negation to regex negation)\n if (classContent.startsWith('!')) {\n classContent = '^' + classContent.slice(1)\n }\n regexStr += `[${classContent}]`\n i = closeIdx + 1\n continue\n }\n }\n\n // Escape special regex characters\n if ('.+^${}()|[]\\\\'.includes(char)) {\n regexStr += '\\\\' + char\n } else {\n regexStr += char\n }\n i++\n }\n\n return new RegExp(`^${regexStr}$`)\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegexChars(str: string): string {\n return str.replace(/[.+^${}()|[\\]\\\\*?]/g, '\\\\$&')\n}\n\n/**\n * Find the closing bracket for a character class, handling escapes\n */\nfunction findClosingBracket(pattern: string, start: number): number {\n let i = start + 1\n // Handle []] or [!]] - ] right after [ or [! is literal\n if (pattern[i] === '!' || pattern[i] === '^') i++\n if (pattern[i] === ']') i++\n\n while (i < pattern.length) {\n if (pattern[i] === ']') return i\n if (pattern[i] === '\\\\' && i + 1 < pattern.length) i++ // Skip escaped char\n i++\n }\n return -1 // No closing bracket found\n}\n\n/**\n * Recursively search for files matching a pattern\n */\nasync function searchFilesRecursive(\n basePath: string,\n currentPath: string,\n pattern: RegExp,\n files: string[],\n maxResults: number\n): Promise<void> {\n if (files.length >= maxResults) return\n\n const entries = await fs.promises.readdir(currentPath, { withFileTypes: true })\n\n for (const entry of entries) {\n if (files.length >= maxResults) return\n if (entry.name.startsWith('.')) continue // Skip hidden files\n\n const entryPath = path.join(currentPath, entry.name)\n const relativePath = path.relative(basePath, entryPath)\n\n try {\n if (entry.isDirectory()) {\n await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults)\n } else if (pattern.test(relativePath) || pattern.test(entry.name)) {\n files.push(relativePath)\n }\n } catch {\n // Skip entries we can't access\n }\n }\n}\n\n/**\n * Handle file:grep command\n * Searches for content in files using Node.js (cross-platform)\n */\nexport async function handleFileGrep(\n command: Extract<RemoteCommand, { action: 'file:grep' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const {\n pattern,\n path: searchPath,\n filePattern = '*',\n caseSensitive = true,\n maxResults = 100\n } = command\n const effectiveMaxResults = Math.min(Math.max(1, maxResults), 1000)\n\n // Validate path\n const validPath = validatePath(searchPath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Path not found'\n }\n }\n\n const matches: Array<{ file: string; line: number; content: string }> = []\n const searchRegex = new RegExp(pattern, caseSensitive ? '' : 'i')\n const fileGlobRegex = globToRegex(filePattern)\n\n const stats = fs.statSync(validPath)\n if (stats.isFile()) {\n // Search single file\n await grepFile(validPath, validPath, searchRegex, matches, effectiveMaxResults)\n } else {\n // Search directory recursively\n await grepDirectoryRecursive(validPath, validPath, searchRegex, fileGlobRegex, matches, effectiveMaxResults)\n }\n\n return {\n success: true,\n matches\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to search content'\n }\n }\n}\n\n/**\n * Search for pattern in a single file\n */\nasync function grepFile(\n basePath: string,\n filePath: string,\n pattern: RegExp,\n matches: Array<{ file: string; line: number; content: string }>,\n maxResults: number\n): Promise<void> {\n if (matches.length >= maxResults) return\n\n const relativePath = path.relative(basePath, filePath)\n\n // Read file line by line to handle large files\n const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' })\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity\n })\n\n let lineNumber = 0\n for await (const line of rl) {\n lineNumber++\n if (matches.length >= maxResults) {\n rl.close()\n break\n }\n\n if (pattern.test(line)) {\n matches.push({\n file: relativePath || path.basename(filePath),\n line: lineNumber,\n content: line.slice(0, 500) // Truncate long lines\n })\n }\n }\n}\n\n/**\n * Recursively grep files in a directory\n */\nasync function grepDirectoryRecursive(\n basePath: string,\n currentPath: string,\n searchPattern: RegExp,\n filePattern: RegExp,\n matches: Array<{ file: string; line: number; content: string }>,\n maxResults: number\n): Promise<void> {\n if (matches.length >= maxResults) return\n\n const entries = await fs.promises.readdir(currentPath, { withFileTypes: true })\n\n for (const entry of entries) {\n if (matches.length >= maxResults) return\n if (entry.name.startsWith('.')) continue // Skip hidden files/dirs\n\n const entryPath = path.join(currentPath, entry.name)\n\n try {\n if (entry.isDirectory()) {\n await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults)\n } else if (filePattern.test(entry.name)) {\n await grepFile(basePath, entryPath, searchPattern, matches, maxResults)\n }\n } catch {\n // Skip entries we can't access\n }\n }\n}\n\n/**\n * EP851: Handle file:edit command\n * Performs precise string replacement in a file\n */\nexport async function handleFileEdit(\n command: Extract<RemoteCommand, { action: 'file:edit' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, oldString, newString, replaceAll = false } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if file exists\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'File not found'\n }\n }\n\n // Check it's a file\n const stats = fs.statSync(validPath)\n if (stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is a directory, not a file'\n }\n }\n\n // Check file size (max 10MB for edit operations)\n const MAX_EDIT_SIZE = 10 * 1024 * 1024 // 10MB\n if (stats.size > MAX_EDIT_SIZE) {\n return {\n success: false,\n error: `File too large for edit operation: ${stats.size} bytes exceeds limit of ${MAX_EDIT_SIZE} bytes (10MB). Use write-file for large files.`\n }\n }\n\n // Read file content\n const content = fs.readFileSync(validPath, 'utf8')\n\n // Count occurrences\n const occurrences = content.split(oldString).length - 1\n\n if (occurrences === 0) {\n return {\n success: false,\n error: 'old_string not found in file. Make sure it matches exactly, including whitespace.'\n }\n }\n\n if (occurrences > 1 && !replaceAll) {\n return {\n success: false,\n error: `old_string found ${occurrences} times. Use replaceAll=true to replace all, or provide more context.`\n }\n }\n\n // Perform replacement\n // Use split/join for replaceAll to support older Node.js versions\n const newContent = replaceAll\n ? content.split(oldString).join(newString)\n : content.replace(oldString, newString)\n\n const replacements = replaceAll ? occurrences : 1\n\n // Write to temp file first, then rename (atomic write)\n const tempPath = `${validPath}.tmp.${Date.now()}`\n fs.writeFileSync(tempPath, newContent, 'utf8')\n fs.renameSync(tempPath, validPath)\n\n const newSize = Buffer.byteLength(newContent, 'utf8')\n\n return {\n success: true,\n replacements,\n newSize\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to edit file'\n }\n }\n}\n\n/**\n * EP851: Handle file:delete command\n * Deletes a file or directory\n */\nexport async function handleFileDelete(\n command: Extract<RemoteCommand, { action: 'file:delete' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, recursive = false } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n // Additional safety: don't allow deleting the project root\n const normalizedProjectPath = path.resolve(projectPath)\n if (validPath === normalizedProjectPath) {\n return {\n success: false,\n error: 'Cannot delete project root directory'\n }\n }\n\n try {\n // Check if path exists\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Path not found'\n }\n }\n\n const stats = fs.statSync(validPath)\n const isDirectory = stats.isDirectory()\n\n // Require recursive flag for directories\n if (isDirectory && !recursive) {\n return {\n success: false,\n error: 'Cannot delete directory without recursive=true'\n }\n }\n\n // Delete\n if (isDirectory) {\n fs.rmSync(validPath, { recursive: true, force: true })\n } else {\n fs.unlinkSync(validPath)\n }\n\n return {\n success: true,\n deleted: true,\n pathType: isDirectory ? 'directory' : 'file'\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to delete'\n }\n }\n}\n\n/**\n * EP851: Handle file:mkdir command\n * Creates a directory (with parent directories if needed)\n */\nexport async function handleFileMkdir(\n command: Extract<RemoteCommand, { action: 'file:mkdir' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: dirPath, mode = '0755' } = command\n\n // Validate path\n const validPath = validatePath(dirPath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if already exists\n if (fs.existsSync(validPath)) {\n const stats = fs.statSync(validPath)\n if (stats.isDirectory()) {\n return {\n success: true,\n created: false // Already exists\n }\n } else {\n return {\n success: false,\n error: 'Path exists but is a file, not a directory'\n }\n }\n }\n\n // Create directory with parents\n const modeNum = parseInt(mode, 8)\n fs.mkdirSync(validPath, { recursive: true, mode: modeNum })\n\n return {\n success: true,\n created: true\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to create directory'\n }\n }\n}\n","/**\n * EP808: Command execution handler for remote access\n *\n * Enables executing shell commands on the local machine via WebSocket.\n */\n\nimport { spawn } from 'child_process'\nimport { RemoteCommand, RemoteCommandResult } from '@episoda/core'\n\n// Default timeout: 30 seconds\nconst DEFAULT_TIMEOUT = 30000\n// Maximum timeout: 5 minutes (for long-running builds)\nconst MAX_TIMEOUT = 300000\n\n/**\n * Handle exec command\n */\nexport async function handleExec(\n command: Extract<RemoteCommand, { action: 'exec' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const {\n command: cmd,\n cwd = projectPath,\n timeout = DEFAULT_TIMEOUT,\n env = {}\n } = command\n\n // Validate timeout\n const effectiveTimeout = Math.min(Math.max(timeout, 1000), MAX_TIMEOUT)\n\n return new Promise((resolve) => {\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let resolved = false\n\n const done = (result: RemoteCommandResult) => {\n if (resolved) return\n resolved = true\n resolve(result)\n }\n\n try {\n // Spawn shell process\n const proc = spawn(cmd, {\n shell: true,\n cwd,\n env: { ...process.env, ...env },\n stdio: ['ignore', 'pipe', 'pipe']\n })\n\n // Setup timeout\n const timeoutId = setTimeout(() => {\n timedOut = true\n proc.kill('SIGTERM')\n // Give it 5 seconds to terminate gracefully, then SIGKILL\n setTimeout(() => {\n if (!resolved) {\n proc.kill('SIGKILL')\n }\n }, 5000)\n }, effectiveTimeout)\n\n // Collect stdout\n proc.stdout.on('data', (data: Buffer) => {\n stdout += data.toString()\n // Limit stdout to prevent memory issues (10MB)\n if (stdout.length > 10 * 1024 * 1024) {\n stdout = stdout.slice(-10 * 1024 * 1024)\n }\n })\n\n // Collect stderr\n proc.stderr.on('data', (data: Buffer) => {\n stderr += data.toString()\n // Limit stderr to prevent memory issues (10MB)\n if (stderr.length > 10 * 1024 * 1024) {\n stderr = stderr.slice(-10 * 1024 * 1024)\n }\n })\n\n // Handle process exit\n proc.on('close', (code, signal) => {\n clearTimeout(timeoutId)\n done({\n success: code === 0 && !timedOut,\n stdout,\n stderr,\n exitCode: code ?? -1,\n timedOut,\n error: timedOut\n ? `Command timed out after ${effectiveTimeout}ms`\n : code !== 0\n ? `Command exited with code ${code}`\n : undefined\n })\n })\n\n // Handle spawn errors\n proc.on('error', (error) => {\n clearTimeout(timeoutId)\n done({\n success: false,\n stdout,\n stderr,\n exitCode: -1,\n error: `Failed to execute command: ${error.message}`\n })\n })\n } catch (error) {\n done({\n success: false,\n stdout: '',\n stderr: '',\n exitCode: -1,\n error: `Failed to spawn process: ${error instanceof Error ? error.message : String(error)}`\n })\n }\n })\n}\n","/**\n * EP950: Stale Commit Cleanup Handler\n *\n * Cleans up local_commit records for commits that no longer exist in git history.\n * This happens when commits are rebased away - the old SHAs become orphaned in\n * the database but the new commits (with different SHAs) are what actually got pushed.\n *\n * Called after:\n * 1. Successful push to main branch\n * 2. CLI daemon connection (periodic cleanup)\n */\n\nimport { exec } from 'child_process'\nimport { promisify } from 'util'\nimport { loadConfig, EpisodaConfig } from '@episoda/core'\nimport { getMachineId } from '../machine-id'\n\nconst execAsync = promisify(exec)\n\n/**\n * Clean up stale commit records for the main branch.\n *\n * Compares local_commit records in the database with actual unpushed commits\n * from git, and removes any records for commits that no longer exist.\n *\n * @param projectPath - Path to the git repository\n * @returns Cleanup result with counts\n */\nexport async function cleanupStaleCommits(projectPath: string): Promise<{\n success: boolean\n deleted_count: number\n kept_count: number\n message: string\n}> {\n try {\n // Get machine ID\n const machineId = await getMachineId()\n\n // Load config for API access\n const config = await loadConfig()\n if (!config?.access_token) {\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: 'No access token available'\n }\n }\n\n // Fetch latest refs from origin\n try {\n await execAsync('git fetch origin', { cwd: projectPath, timeout: 30000 })\n } catch (fetchError) {\n // Non-fatal - continue with possibly stale refs\n console.warn('[EP950] Could not fetch origin:', fetchError)\n }\n\n // Get current branch\n let currentBranch = ''\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd: projectPath, timeout: 5000 })\n currentBranch = stdout.trim()\n } catch {\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: 'Could not determine current branch'\n }\n }\n\n // Only cleanup for main branch\n if (currentBranch !== 'main' && currentBranch !== 'master') {\n return {\n success: true,\n deleted_count: 0,\n kept_count: 0,\n message: `Not on main branch (on ${currentBranch}), skipping cleanup`\n }\n }\n\n // Get actual unpushed commits (these are valid, should be kept)\n let validShas: string[] = []\n try {\n const { stdout } = await execAsync(\n 'git log origin/main..HEAD --format=%H',\n { cwd: projectPath, timeout: 10000 }\n )\n validShas = stdout.trim().split('\\n').filter(Boolean)\n } catch {\n // No unpushed commits or origin/main doesn't exist\n validShas = []\n }\n\n // Call cleanup API\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetch(`${apiUrl}/api/commits/local/batch`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n machine_id: machineId,\n valid_shas: validShas,\n branch: 'main'\n })\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as { error?: { message?: string } }\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: `API error: ${errorData.error?.message || response.statusText}`\n }\n }\n\n const result = await response.json() as {\n success: boolean\n deleted_count?: number\n kept_count?: number\n message?: string\n }\n\n if (result.deleted_count && result.deleted_count > 0) {\n console.log(`[EP950] Cleaned up ${result.deleted_count} stale commit record(s)`)\n }\n\n return {\n success: true,\n deleted_count: result.deleted_count || 0,\n kept_count: result.kept_count || 0,\n message: result.message || 'Cleanup completed'\n }\n\n } catch (error: any) {\n console.error('[EP950] Error during stale commit cleanup:', error)\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: error.message || 'Cleanup failed'\n }\n }\n}\n\n/**\n * Check if we should run cleanup (on main branch)\n */\nexport async function shouldRunCleanup(projectPath: string): Promise<boolean> {\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd: projectPath, timeout: 5000 })\n const branch = stdout.trim()\n return branch === 'main' || branch === 'master'\n } catch {\n return false\n }\n}\n","/**\n * Cloudflared Binary Manager\n *\n * EP672: Manages the cloudflared binary for local tunnel functionality.\n * Downloads and installs cloudflared if not available in PATH.\n *\n * @module tunnel/cloudflared-manager\n */\n\nimport { execSync, spawnSync } from 'child_process'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport * as https from 'https'\nimport * as tar from 'tar'\n\n/**\n * Download URLs for cloudflared releases\n * Using GitHub releases for the latest stable version\n */\nconst DOWNLOAD_URLS: Record<string, Record<string, string>> = {\n darwin: {\n arm64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-arm64.tgz',\n x64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz'\n },\n linux: {\n arm64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64',\n x64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64'\n },\n win32: {\n x64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe',\n ia32: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-386.exe'\n }\n}\n\n/**\n * Get the path to the Episoda bin directory\n */\nfunction getEpisodaBinDir(): string {\n return path.join(os.homedir(), '.episoda', 'bin')\n}\n\n/**\n * Get the expected path for cloudflared in the Episoda bin directory\n */\nfunction getCloudflaredPath(): string {\n const binaryName = os.platform() === 'win32' ? 'cloudflared.exe' : 'cloudflared'\n return path.join(getEpisodaBinDir(), binaryName)\n}\n\n/**\n * Check if cloudflared is available in PATH\n */\nfunction isCloudflaredInPath(): string | null {\n try {\n // Use 'where' on Windows, 'which' on Unix\n const command = os.platform() === 'win32' ? 'where' : 'which'\n const binaryName = os.platform() === 'win32' ? 'cloudflared.exe' : 'cloudflared'\n const result = spawnSync(command, [binaryName], { encoding: 'utf-8' })\n if (result.status === 0 && result.stdout.trim()) {\n // 'where' on Windows may return multiple lines, take the first one\n return result.stdout.trim().split('\\n')[0].trim()\n }\n } catch {\n // Not found\n }\n return null\n}\n\n/**\n * Check if cloudflared is installed in Episoda bin directory\n */\nfunction isCloudflaredInstalled(): boolean {\n const cloudflaredPath = getCloudflaredPath()\n try {\n fs.accessSync(cloudflaredPath, fs.constants.X_OK)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Verify cloudflared binary works by checking version\n */\nfunction verifyCloudflared(binaryPath: string): boolean {\n try {\n const result = spawnSync(binaryPath, ['version'], { encoding: 'utf-8', timeout: 5000 })\n return result.status === 0 && result.stdout.includes('cloudflared')\n } catch {\n return false\n }\n}\n\n/**\n * Get the download URL for the current platform\n */\nfunction getDownloadUrl(): string | null {\n const platform = os.platform()\n const arch = os.arch()\n\n const platformUrls = DOWNLOAD_URLS[platform]\n if (!platformUrls) {\n return null\n }\n\n return platformUrls[arch] || null\n}\n\n/**\n * Download a file from URL with redirect support\n */\nasync function downloadFile(url: string, destPath: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const followRedirect = (currentUrl: string, redirectCount = 0) => {\n if (redirectCount > 5) {\n reject(new Error('Too many redirects'))\n return\n }\n\n const urlObj = new URL(currentUrl)\n const options = {\n hostname: urlObj.hostname,\n path: urlObj.pathname + urlObj.search,\n headers: {\n 'User-Agent': 'episoda-cli'\n }\n }\n\n https.get(options, (response) => {\n // Handle redirects\n if (response.statusCode === 301 || response.statusCode === 302) {\n const redirectUrl = response.headers.location\n if (redirectUrl) {\n followRedirect(redirectUrl, redirectCount + 1)\n return\n }\n }\n\n if (response.statusCode !== 200) {\n reject(new Error(`Failed to download: HTTP ${response.statusCode}`))\n return\n }\n\n const file = fs.createWriteStream(destPath)\n response.pipe(file)\n file.on('finish', () => {\n file.close()\n resolve()\n })\n file.on('error', (err) => {\n fs.unlinkSync(destPath)\n reject(err)\n })\n }).on('error', reject)\n }\n\n followRedirect(url)\n })\n}\n\n/**\n * Extract tgz archive\n */\nasync function extractTgz(archivePath: string, destDir: string): Promise<void> {\n return tar.x({\n file: archivePath,\n cwd: destDir\n })\n}\n\n/**\n * Download and install cloudflared\n */\nasync function downloadCloudflared(): Promise<string> {\n const url = getDownloadUrl()\n if (!url) {\n throw new Error(`Unsupported platform: ${os.platform()} ${os.arch()}`)\n }\n\n const binDir = getEpisodaBinDir()\n const cloudflaredPath = getCloudflaredPath()\n\n // Ensure bin directory exists\n fs.mkdirSync(binDir, { recursive: true })\n\n const isTgz = url.endsWith('.tgz')\n\n if (isTgz) {\n // Download to temp file and extract\n const tempFile = path.join(binDir, 'cloudflared.tgz')\n\n console.log(`[Tunnel] Downloading cloudflared from ${url}...`)\n await downloadFile(url, tempFile)\n\n console.log('[Tunnel] Extracting cloudflared...')\n await extractTgz(tempFile, binDir)\n\n // Clean up temp file\n fs.unlinkSync(tempFile)\n } else {\n // Direct binary download (Linux/Windows)\n console.log(`[Tunnel] Downloading cloudflared from ${url}...`)\n await downloadFile(url, cloudflaredPath)\n }\n\n // Make executable (not needed on Windows)\n if (os.platform() !== 'win32') {\n fs.chmodSync(cloudflaredPath, 0o755)\n }\n\n // Verify installation\n if (!verifyCloudflared(cloudflaredPath)) {\n throw new Error('Downloaded cloudflared binary failed verification')\n }\n\n console.log('[Tunnel] cloudflared installed successfully')\n return cloudflaredPath\n}\n\n/**\n * Ensure cloudflared is available\n * Returns the path to the cloudflared binary\n */\nexport async function ensureCloudflared(): Promise<string> {\n // Check PATH first\n const pathBinary = isCloudflaredInPath()\n if (pathBinary && verifyCloudflared(pathBinary)) {\n return pathBinary\n }\n\n // Check Episoda bin directory\n const episodaBinary = getCloudflaredPath()\n if (isCloudflaredInstalled() && verifyCloudflared(episodaBinary)) {\n return episodaBinary\n }\n\n // Download and install\n return downloadCloudflared()\n}\n\n/**\n * Get cloudflared version\n */\nexport function getCloudflaredVersion(binaryPath: string): string | null {\n try {\n const result = spawnSync(binaryPath, ['version'], { encoding: 'utf-8', timeout: 5000 })\n if (result.status === 0) {\n // Parse version from output like \"cloudflared version 2024.1.0 (built 2024-01-15T12:00:00Z)\"\n const match = result.stdout.match(/cloudflared version ([\\d.]+)/)\n return match ? match[1] : null\n }\n } catch {\n // Ignore errors\n }\n return null\n}\n\n/**\n * Check if cloudflared is available (without downloading)\n */\nexport function isCloudflaredAvailable(): boolean {\n const pathBinary = isCloudflaredInPath()\n if (pathBinary) return true\n\n return isCloudflaredInstalled()\n}\n","/**\n * Tunnel Manager\n *\n * EP672: Manages Cloudflare quick tunnels for local dev preview.\n * Handles starting, stopping, and monitoring tunnel processes.\n *\n * EP877: Added PID-based process tracking for reliable cleanup across restarts,\n * mutex locks to prevent concurrent tunnel starts, and orphan cleanup on init.\n *\n * @module tunnel/tunnel-manager\n */\n\nimport { spawn, ChildProcess, execSync } from 'child_process'\nimport { EventEmitter } from 'events'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport { ensureCloudflared } from './cloudflared-manager'\nimport type {\n TunnelInfo,\n TunnelStatus,\n StartTunnelOptions,\n StartTunnelResult,\n TunnelEvent\n} from './types'\n\n/**\n * EP877: Directory for storing tunnel PID files\n */\nconst TUNNEL_PID_DIR = path.join(os.homedir(), '.episoda', 'tunnels')\n\n/**\n * Regex to match tunnel URL from cloudflared output\n * Format: \"https://random-words-here.trycloudflare.com\"\n */\nconst TUNNEL_URL_REGEX = /https:\\/\\/[a-z0-9-]+\\.trycloudflare\\.com/i\n\n/**\n * EP672-9: Reconnection configuration\n */\ninterface ReconnectConfig {\n maxRetries: number\n initialDelayMs: number\n maxDelayMs: number\n backoffMultiplier: number\n}\n\nconst DEFAULT_RECONNECT_CONFIG: ReconnectConfig = {\n maxRetries: 5,\n initialDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2\n}\n\n/**\n * EP672-9: Internal tunnel state for reconnection tracking\n */\ninterface TunnelState {\n info: TunnelInfo & { process: ChildProcess }\n options: StartTunnelOptions\n intentionallyStopped: boolean\n retryCount: number\n retryTimeoutId: NodeJS.Timeout | null\n}\n\n/**\n * Manages Cloudflare quick tunnels for local development\n */\nexport class TunnelManager extends EventEmitter {\n private tunnelStates: Map<string, TunnelState> = new Map()\n private cloudflaredPath: string | null = null\n private reconnectConfig: ReconnectConfig\n\n /**\n * EP877: Mutex locks to prevent concurrent tunnel starts for the same module\n */\n private startLocks: Map<string, Promise<StartTunnelResult>> = new Map()\n\n constructor(config?: Partial<ReconnectConfig>) {\n super()\n this.reconnectConfig = { ...DEFAULT_RECONNECT_CONFIG, ...config }\n }\n\n /**\n * EP877: Ensure PID directory exists\n * EP904: Added proper error handling and logging\n */\n private ensurePidDir(): void {\n try {\n if (!fs.existsSync(TUNNEL_PID_DIR)) {\n console.log(`[Tunnel] EP904: Creating PID directory: ${TUNNEL_PID_DIR}`)\n fs.mkdirSync(TUNNEL_PID_DIR, { recursive: true })\n console.log(`[Tunnel] EP904: PID directory created successfully`)\n }\n } catch (error) {\n console.error(`[Tunnel] EP904: Failed to create PID directory ${TUNNEL_PID_DIR}:`, error)\n // Re-throw to make failures visible - don't swallow errors\n throw error\n }\n }\n\n /**\n * EP877: Get PID file path for a module\n */\n private getPidFilePath(moduleUid: string): string {\n return path.join(TUNNEL_PID_DIR, `${moduleUid}.pid`)\n }\n\n /**\n * EP877: Write PID to file for tracking across restarts\n * EP904: Enhanced logging and error visibility\n */\n private writePidFile(moduleUid: string, pid: number): void {\n try {\n this.ensurePidDir()\n const pidPath = this.getPidFilePath(moduleUid)\n fs.writeFileSync(pidPath, pid.toString(), 'utf8')\n console.log(`[Tunnel] EP904: Wrote PID ${pid} for ${moduleUid} to ${pidPath}`)\n } catch (error) {\n console.error(`[Tunnel] EP904: Failed to write PID file for ${moduleUid}:`, error)\n // Don't re-throw - PID file is for recovery, not critical path\n // But log prominently so we notice failures\n }\n }\n\n /**\n * EP877: Read PID from file\n */\n private readPidFile(moduleUid: string): number | null {\n try {\n const pidPath = this.getPidFilePath(moduleUid)\n if (!fs.existsSync(pidPath)) {\n return null\n }\n const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10)\n return isNaN(pid) ? null : pid\n } catch (error) {\n return null\n }\n }\n\n /**\n * EP877: Remove PID file\n */\n private removePidFile(moduleUid: string): void {\n try {\n const pidPath = this.getPidFilePath(moduleUid)\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n console.log(`[Tunnel] EP877: Removed PID file for ${moduleUid}`)\n }\n } catch (error) {\n console.error(`[Tunnel] EP877: Failed to remove PID file for ${moduleUid}:`, error)\n }\n }\n\n /**\n * EP877: Check if a process is running by PID\n */\n private isProcessRunning(pid: number): boolean {\n try {\n // Sending signal 0 checks if process exists without killing it\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n }\n\n /**\n * EP877: Kill a process by PID\n */\n private killByPid(pid: number, signal: NodeJS.Signals = 'SIGTERM'): boolean {\n try {\n process.kill(pid, signal)\n console.log(`[Tunnel] EP877: Sent ${signal} to PID ${pid}`)\n return true\n } catch (error) {\n // Process doesn't exist or we don't have permission\n return false\n }\n }\n\n /**\n * EP877: Find all cloudflared processes using pgrep\n */\n private findCloudflaredProcesses(): number[] {\n try {\n // Use pgrep to find cloudflared processes\n const output = execSync('pgrep -f cloudflared', { encoding: 'utf8' })\n return output.trim().split('\\n').map(pid => parseInt(pid, 10)).filter(pid => !isNaN(pid))\n } catch {\n // pgrep returns non-zero if no processes found\n return []\n }\n }\n\n /**\n * EP904: Get the port a cloudflared process is tunneling to\n * Extracts port from process command line arguments\n */\n private getProcessPort(pid: number): number | null {\n try {\n // Get the full command line for the process\n const output = execSync(`ps -p ${pid} -o args=`, { encoding: 'utf8' }).trim()\n\n // Match pattern: --url http://localhost:PORT\n const portMatch = output.match(/--url\\s+https?:\\/\\/localhost:(\\d+)/)\n if (portMatch) {\n return parseInt(portMatch[1], 10)\n }\n return null\n } catch {\n return null\n }\n }\n\n /**\n * EP904: Find cloudflared processes on a specific port\n */\n private findCloudflaredOnPort(port: number): number[] {\n const allProcesses = this.findCloudflaredProcesses()\n return allProcesses.filter(pid => this.getProcessPort(pid) === port)\n }\n\n /**\n * EP904: Kill all cloudflared processes on a specific port\n * Returns the PIDs that were killed\n */\n private async killCloudflaredOnPort(port: number): Promise<number[]> {\n const pidsOnPort = this.findCloudflaredOnPort(port)\n const killed: number[] = []\n\n for (const pid of pidsOnPort) {\n // Check if this PID is tracked by us (current TunnelManager instance)\n const isTracked = Array.from(this.tunnelStates.values()).some(s => s.info.pid === pid)\n\n console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`)\n\n this.killByPid(pid, 'SIGTERM')\n await new Promise(resolve => setTimeout(resolve, 500))\n\n if (this.isProcessRunning(pid)) {\n this.killByPid(pid, 'SIGKILL')\n await new Promise(resolve => setTimeout(resolve, 200))\n }\n\n killed.push(pid)\n }\n\n if (killed.length > 0) {\n console.log(`[Tunnel] EP904: Killed ${killed.length} cloudflared process(es) on port ${port}: ${killed.join(', ')}`)\n }\n\n return killed\n }\n\n /**\n * EP877: Cleanup orphaned cloudflared processes on startup\n * Kills any cloudflared processes that have PID files but aren't tracked in memory,\n * and any cloudflared processes that don't have corresponding PID files.\n */\n async cleanupOrphanedProcesses(): Promise<{ cleaned: number; pids: number[] }> {\n const cleaned: number[] = []\n\n try {\n this.ensurePidDir()\n\n // 1. Clean up based on PID files\n const pidFiles = fs.readdirSync(TUNNEL_PID_DIR).filter(f => f.endsWith('.pid'))\n\n for (const pidFile of pidFiles) {\n const moduleUid = pidFile.replace('.pid', '')\n const pid = this.readPidFile(moduleUid)\n\n if (pid && this.isProcessRunning(pid)) {\n // Process is running but not tracked - kill it\n if (!this.tunnelStates.has(moduleUid)) {\n console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`)\n this.killByPid(pid, 'SIGTERM')\n\n // Wait a bit then force kill if needed\n await new Promise(resolve => setTimeout(resolve, 1000))\n if (this.isProcessRunning(pid)) {\n this.killByPid(pid, 'SIGKILL')\n }\n\n cleaned.push(pid)\n }\n }\n\n // Remove stale PID file\n this.removePidFile(moduleUid)\n }\n\n // 2. Find any cloudflared processes not tracked by PID files\n const runningPids = this.findCloudflaredProcesses()\n const trackedPids = Array.from(this.tunnelStates.values())\n .map(s => s.info.pid)\n .filter((pid): pid is number => pid !== undefined)\n\n for (const pid of runningPids) {\n if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {\n console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`)\n this.killByPid(pid, 'SIGTERM')\n\n await new Promise(resolve => setTimeout(resolve, 500))\n if (this.isProcessRunning(pid)) {\n this.killByPid(pid, 'SIGKILL')\n }\n\n cleaned.push(pid)\n }\n }\n\n if (cleaned.length > 0) {\n console.log(`[Tunnel] EP877: Cleaned up ${cleaned.length} orphaned cloudflared processes: ${cleaned.join(', ')}`)\n }\n\n } catch (error) {\n console.error('[Tunnel] EP877: Error during orphan cleanup:', error)\n }\n\n return { cleaned: cleaned.length, pids: cleaned }\n }\n\n /**\n * Emit typed tunnel events\n */\n private emitEvent(event: TunnelEvent): void {\n this.emit('tunnel', event)\n }\n\n /**\n * Initialize the tunnel manager\n * Ensures cloudflared is available and cleans up orphaned processes\n *\n * EP877: Now includes orphaned process cleanup on startup\n */\n async initialize(): Promise<void> {\n this.cloudflaredPath = await ensureCloudflared()\n\n // EP877: Clean up any orphaned cloudflared processes from previous runs\n const cleanup = await this.cleanupOrphanedProcesses()\n if (cleanup.cleaned > 0) {\n console.log(`[Tunnel] EP877: Initialization cleaned up ${cleanup.cleaned} orphaned processes`)\n }\n }\n\n /**\n * EP672-9: Calculate delay for exponential backoff\n */\n private calculateBackoffDelay(retryCount: number): number {\n const delay = this.reconnectConfig.initialDelayMs *\n Math.pow(this.reconnectConfig.backoffMultiplier, retryCount)\n return Math.min(delay, this.reconnectConfig.maxDelayMs)\n }\n\n /**\n * EP672-9: Attempt to reconnect a crashed tunnel\n */\n private async attemptReconnect(moduleUid: string): Promise<void> {\n const state = this.tunnelStates.get(moduleUid)\n if (!state || state.intentionallyStopped) {\n return\n }\n\n if (state.retryCount >= this.reconnectConfig.maxRetries) {\n console.log(`[Tunnel] Max retries (${this.reconnectConfig.maxRetries}) reached for ${moduleUid}, giving up`)\n this.emitEvent({\n type: 'error',\n moduleUid,\n error: `Tunnel failed after ${this.reconnectConfig.maxRetries} reconnection attempts`\n })\n state.options.onStatusChange?.('error', 'Max reconnection attempts reached')\n this.tunnelStates.delete(moduleUid)\n return\n }\n\n const delay = this.calculateBackoffDelay(state.retryCount)\n console.log(`[Tunnel] Reconnecting ${moduleUid} in ${delay}ms (attempt ${state.retryCount + 1}/${this.reconnectConfig.maxRetries})`)\n\n this.emitEvent({ type: 'reconnecting', moduleUid })\n state.options.onStatusChange?.('reconnecting')\n\n state.retryTimeoutId = setTimeout(async () => {\n state.retryCount++\n\n // Start a new tunnel process (internal method that doesn't reset state)\n const result = await this.startTunnelProcess(state.options, state)\n if (result.success) {\n console.log(`[Tunnel] Reconnected ${moduleUid} successfully with new URL: ${result.url}`)\n state.retryCount = 0 // Reset on success\n }\n // If failed, the exit handler will trigger another reconnect attempt\n }, delay)\n }\n\n /**\n * EP672-9: Internal method to start the tunnel process\n * Separated from startTunnel to support reconnection\n */\n private async startTunnelProcess(\n options: StartTunnelOptions,\n existingState?: TunnelState\n ): Promise<StartTunnelResult> {\n const { moduleUid, port = 3000, onUrl, onStatusChange } = options\n\n // Ensure cloudflared is available\n if (!this.cloudflaredPath) {\n try {\n this.cloudflaredPath = await ensureCloudflared()\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return { success: false, error: `Failed to get cloudflared: ${errorMessage}` }\n }\n }\n\n return new Promise((resolve) => {\n const tunnelInfo: TunnelInfo & { process: ChildProcess } = {\n moduleUid,\n url: '',\n port,\n status: 'starting',\n startedAt: new Date(),\n process: null as any // Will be set below\n }\n\n // Start cloudflared quick tunnel\n // Command: cloudflared tunnel --url http://localhost:3000\n const process = spawn(this.cloudflaredPath!, [\n 'tunnel',\n '--url',\n `http://localhost:${port}`\n ], {\n stdio: ['ignore', 'pipe', 'pipe']\n })\n\n tunnelInfo.process = process\n tunnelInfo.pid = process.pid\n\n // EP877: Write PID file for tracking across restarts\n if (process.pid) {\n this.writePidFile(moduleUid, process.pid)\n }\n\n // Update or create state\n const state: TunnelState = existingState || {\n info: tunnelInfo,\n options,\n intentionallyStopped: false,\n retryCount: 0,\n retryTimeoutId: null\n }\n state.info = tunnelInfo\n this.tunnelStates.set(moduleUid, state)\n\n let urlFound = false\n let stdoutBuffer = ''\n let stderrBuffer = ''\n\n // Parse tunnel URL from stderr (cloudflared outputs info to stderr)\n const parseOutput = (data: string) => {\n if (urlFound) return\n\n const match = data.match(TUNNEL_URL_REGEX)\n if (match) {\n urlFound = true\n tunnelInfo.url = match[0]\n tunnelInfo.status = 'connected'\n\n onStatusChange?.('connected')\n onUrl?.(tunnelInfo.url)\n\n this.emitEvent({\n type: 'started',\n moduleUid,\n url: tunnelInfo.url\n })\n\n resolve({ success: true, url: tunnelInfo.url })\n }\n }\n\n process.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString()\n parseOutput(stdoutBuffer)\n })\n\n process.stderr?.on('data', (data: Buffer) => {\n stderrBuffer += data.toString()\n parseOutput(stderrBuffer)\n })\n\n // Handle process exit\n process.on('exit', (code, signal) => {\n const wasConnected = tunnelInfo.status === 'connected'\n tunnelInfo.status = 'disconnected'\n\n const currentState = this.tunnelStates.get(moduleUid)\n\n if (!urlFound) {\n // Process exited before we got a URL\n const errorMsg = `Tunnel process exited with code ${code}`\n tunnelInfo.status = 'error'\n tunnelInfo.error = errorMsg\n\n // EP672-9: Attempt reconnect if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('error', errorMsg)\n this.emitEvent({ type: 'error', moduleUid, error: errorMsg })\n }\n\n resolve({ success: false, error: errorMsg })\n } else if (wasConnected) {\n // Tunnel was running and then disconnected unexpectedly\n // EP672-9: Attempt reconnect if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n console.log(`[Tunnel] ${moduleUid} crashed unexpectedly, attempting reconnect...`)\n onStatusChange?.('reconnecting')\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('disconnected')\n this.emitEvent({ type: 'stopped', moduleUid })\n }\n }\n })\n\n process.on('error', (error) => {\n tunnelInfo.status = 'error'\n tunnelInfo.error = error.message\n\n const currentState = this.tunnelStates.get(moduleUid)\n\n // EP672-9: Attempt reconnect if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('error', error.message)\n this.emitEvent({ type: 'error', moduleUid, error: error.message })\n }\n\n if (!urlFound) {\n resolve({ success: false, error: error.message })\n }\n })\n\n // Timeout if tunnel doesn't start in 30 seconds\n setTimeout(() => {\n if (!urlFound) {\n process.kill()\n const errorMsg = 'Tunnel startup timed out after 30 seconds'\n\n tunnelInfo.status = 'error'\n tunnelInfo.error = errorMsg\n\n const currentState = this.tunnelStates.get(moduleUid)\n\n // EP672-9: Attempt reconnect on timeout if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('error', errorMsg)\n this.emitEvent({ type: 'error', moduleUid, error: errorMsg })\n }\n\n resolve({ success: false, error: errorMsg })\n }\n }, 30000)\n })\n }\n\n /**\n * Start a tunnel for a module\n *\n * EP877: Uses mutex lock to prevent concurrent starts for the same module\n */\n async startTunnel(options: StartTunnelOptions): Promise<StartTunnelResult> {\n const { moduleUid } = options\n\n // EP877: Check if there's already a pending start for this module\n const existingLock = this.startLocks.get(moduleUid)\n if (existingLock) {\n console.log(`[Tunnel] EP877: Waiting for existing start operation for ${moduleUid}`)\n return existingLock\n }\n\n // EP877: Create a lock for this module's start operation\n const startPromise = this.startTunnelWithLock(options)\n this.startLocks.set(moduleUid, startPromise)\n\n try {\n return await startPromise\n } finally {\n // EP877: Release the lock when done\n this.startLocks.delete(moduleUid)\n }\n }\n\n /**\n * EP877: Internal start implementation with lock already held\n * EP901: Enhanced to clean up ALL orphaned cloudflared processes before starting\n * EP904: Added port-based deduplication to prevent multiple tunnels on same port\n */\n private async startTunnelWithLock(options: StartTunnelOptions): Promise<StartTunnelResult> {\n const { moduleUid, port = 3000 } = options\n\n // Check if tunnel already exists for this module\n const existingState = this.tunnelStates.get(moduleUid)\n if (existingState) {\n if (existingState.info.status === 'connected') {\n return { success: true, url: existingState.info.url }\n }\n // Stop existing tunnel before starting new one\n await this.stopTunnel(moduleUid)\n }\n\n // EP877: Also check for orphaned process by PID file before starting\n const orphanPid = this.readPidFile(moduleUid)\n if (orphanPid && this.isProcessRunning(orphanPid)) {\n console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`)\n this.killByPid(orphanPid, 'SIGTERM')\n await new Promise(resolve => setTimeout(resolve, 500))\n if (this.isProcessRunning(orphanPid)) {\n this.killByPid(orphanPid, 'SIGKILL')\n }\n this.removePidFile(moduleUid)\n }\n\n // EP904: Port-based deduplication - kill any cloudflared processes on target port\n // This ensures only one tunnel per port, preventing the duplicate process issue\n const killedOnPort = await this.killCloudflaredOnPort(port)\n if (killedOnPort.length > 0) {\n console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`)\n // EP953: Increased delay to ensure ports are fully released after killing orphans\n // 300ms was often insufficient, causing \"exited with code null\" on new tunnel start\n await new Promise(resolve => setTimeout(resolve, 1000))\n }\n\n // EP901: Clean up ALL orphaned cloudflared processes before starting a new tunnel\n // This catches orphans from other modules that might interfere with the new tunnel\n const cleanup = await this.cleanupOrphanedProcesses()\n if (cleanup.cleaned > 0) {\n console.log(`[Tunnel] EP901: Pre-start cleanup removed ${cleanup.cleaned} orphaned processes`)\n }\n\n return this.startTunnelProcess(options)\n }\n\n /**\n * Stop a tunnel for a module\n *\n * EP877: Enhanced to handle cleanup via PID file when in-memory state is missing\n */\n async stopTunnel(moduleUid: string): Promise<void> {\n const state = this.tunnelStates.get(moduleUid)\n\n // EP877: Even if no in-memory state, check for orphaned process via PID file\n if (!state) {\n const orphanPid = this.readPidFile(moduleUid)\n if (orphanPid && this.isProcessRunning(orphanPid)) {\n console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`)\n this.killByPid(orphanPid, 'SIGTERM')\n await new Promise(resolve => setTimeout(resolve, 1000))\n if (this.isProcessRunning(orphanPid)) {\n this.killByPid(orphanPid, 'SIGKILL')\n }\n }\n this.removePidFile(moduleUid)\n return\n }\n\n // EP672-9: Mark as intentionally stopped to prevent reconnection\n state.intentionallyStopped = true\n\n // EP672-9: Clear any pending reconnect timeout\n if (state.retryTimeoutId) {\n clearTimeout(state.retryTimeoutId)\n state.retryTimeoutId = null\n }\n\n const tunnel = state.info\n\n // Kill the cloudflared process\n if (tunnel.process && !tunnel.process.killed) {\n tunnel.process.kill('SIGTERM')\n\n // Give it a moment to exit gracefully\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n if (tunnel.process && !tunnel.process.killed) {\n tunnel.process.kill('SIGKILL')\n }\n resolve()\n }, 3000)\n\n tunnel.process.once('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n // EP877: Remove PID file\n this.removePidFile(moduleUid)\n\n this.tunnelStates.delete(moduleUid)\n this.emitEvent({ type: 'stopped', moduleUid })\n }\n\n /**\n * Stop all active tunnels\n */\n async stopAllTunnels(): Promise<void> {\n const moduleUids = Array.from(this.tunnelStates.keys())\n await Promise.all(moduleUids.map(uid => this.stopTunnel(uid)))\n }\n\n /**\n * Get information about an active tunnel\n */\n getTunnel(moduleUid: string): TunnelInfo | null {\n const state = this.tunnelStates.get(moduleUid)\n if (!state) return null\n\n // Return without the process reference\n const { process, ...info } = state.info\n return info\n }\n\n /**\n * Get all active tunnels\n */\n getAllTunnels(): TunnelInfo[] {\n return Array.from(this.tunnelStates.values()).map(state => {\n const { process, ...info } = state.info\n return info\n })\n }\n\n /**\n * Check if a tunnel is active for a module\n */\n hasTunnel(moduleUid: string): boolean {\n return this.tunnelStates.has(moduleUid)\n }\n\n /**\n * EP956: Get all module UIDs with active tunnels\n */\n getActiveModuleUids(): string[] {\n return Array.from(this.tunnelStates.keys())\n }\n\n /**\n * Get the URL for an active tunnel\n */\n getTunnelUrl(moduleUid: string): string | null {\n return this.tunnelStates.get(moduleUid)?.info.url || null\n }\n}\n\n// Singleton instance\nlet tunnelManagerInstance: TunnelManager | null = null\n\n/**\n * Get the singleton TunnelManager instance\n */\nexport function getTunnelManager(): TunnelManager {\n if (!tunnelManagerInstance) {\n tunnelManagerInstance = new TunnelManager()\n }\n return tunnelManagerInstance\n}\n","/**\n * Tunnel API Utilities\n *\n * EP823: Shared functions for tunnel API interactions.\n * Consolidates duplicated tunnel URL cleanup logic.\n *\n * @module tunnel/tunnel-api\n */\n\nimport { loadConfig } from '@episoda/core'\n\n/**\n * Clear tunnel URL from the Episoda API\n *\n * Sends a DELETE request to clear tunnel_url, tunnel_error, and tunnel_started_at\n * from the module record. Fails silently on errors to avoid blocking tunnel operations.\n *\n * @param moduleUid - The module UID (e.g., \"EP823\")\n * @returns Promise that resolves when cleanup is complete (or fails silently)\n */\nexport async function clearTunnelUrl(moduleUid: string): Promise<void> {\n // Skip for LOCAL tunnels (not associated with a module)\n if (!moduleUid || moduleUid === 'LOCAL') {\n return\n }\n\n const config = await loadConfig()\n if (!config?.access_token) {\n return\n }\n\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n await fetch(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`\n }\n })\n } catch {\n // Ignore cleanup errors - don't block tunnel operations\n }\n}\n","/**\n * EP912: Claude Code Binary Detection\n *\n * Detection-first approach:\n * 1. Check if claude is in PATH (user's global install)\n * 2. Check if bundled in node_modules/.bin\n * 3. Return path to working binary, or throw clear error\n *\n * This ensures zero user action is required - Claude Code is available\n * either from their global install or from our bundled dependency.\n */\n\nimport { execSync } from 'child_process'\nimport * as path from 'path'\nimport * as fs from 'fs'\n\n/**\n * Cached binary path (resolved once per daemon lifetime)\n */\nlet cachedBinaryPath: string | null = null\n\n/**\n * Check if a binary path is executable and returns valid version\n */\nfunction isValidClaudeBinary(binaryPath: string): boolean {\n try {\n // Check if file exists and is executable\n fs.accessSync(binaryPath, fs.constants.X_OK)\n\n // Try to get version to verify it's actually Claude Code\n const version = execSync(`\"${binaryPath}\" --version`, {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe']\n }).trim()\n\n // Version should contain a semver-like pattern\n if (version && /\\d+\\.\\d+/.test(version)) {\n console.log(`[AgentManager] Found Claude Code at ${binaryPath}: v${version}`)\n return true\n }\n\n return false\n } catch {\n return false\n }\n}\n\n/**\n * Find the Claude Code binary using detection-first approach\n *\n * @returns Path to Claude Code binary\n * @throws Error if Claude Code is not found anywhere\n */\nexport async function ensureClaudeBinary(): Promise<string> {\n // Return cached path if already resolved\n if (cachedBinaryPath) {\n return cachedBinaryPath\n }\n\n // 1. Check PATH for global installation\n try {\n const pathResult = execSync('which claude', {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe']\n }).trim()\n\n if (pathResult && isValidClaudeBinary(pathResult)) {\n cachedBinaryPath = pathResult\n return cachedBinaryPath\n }\n } catch {\n // Not in PATH, continue checking\n }\n\n // 2. Check bundled location in this package's node_modules/.bin\n const bundledPaths = [\n // In production: node_modules/.bin/claude\n path.join(__dirname, '..', '..', 'node_modules', '.bin', 'claude'),\n // In monorepo development: packages/episoda/node_modules/.bin/claude\n path.join(__dirname, '..', '..', '..', '..', 'node_modules', '.bin', 'claude'),\n // Root monorepo node_modules\n path.join(__dirname, '..', '..', '..', '..', '..', 'node_modules', '.bin', 'claude'),\n ]\n\n for (const bundledPath of bundledPaths) {\n if (fs.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {\n cachedBinaryPath = bundledPath\n return cachedBinaryPath\n }\n }\n\n // 3. Check npx availability as last resort\n try {\n const npxResult = execSync('npx --yes @anthropic-ai/claude-code --version', {\n encoding: 'utf-8',\n timeout: 30000, // npx might need to download\n stdio: ['pipe', 'pipe', 'pipe']\n }).trim()\n\n if (npxResult && /\\d+\\.\\d+/.test(npxResult)) {\n // Return a special marker that indicates npx should be used\n cachedBinaryPath = 'npx:@anthropic-ai/claude-code'\n console.log(`[AgentManager] Using npx to run Claude Code: v${npxResult}`)\n return cachedBinaryPath\n }\n } catch {\n // npx failed too\n }\n\n throw new Error(\n 'Claude Code not found. Please install it globally with: npm install -g @anthropic-ai/claude-code'\n )\n}\n\n/**\n * Get the cached binary path without performing detection\n * Returns null if not yet resolved\n */\nexport function getCachedClaudeBinary(): string | null {\n return cachedBinaryPath\n}\n\n/**\n * Clear the cached binary path (useful for testing)\n */\nexport function clearCachedClaudeBinary(): void {\n cachedBinaryPath = null\n}\n\n/**\n * Check if Claude Code binary is available without throwing\n */\nexport async function isClaudeCodeAvailable(): Promise<boolean> {\n try {\n await ensureClaudeBinary()\n return true\n } catch {\n return false\n }\n}\n","/**\n * EP912: Agent Manager\n *\n * Manages local Claude Code agent sessions for dev_mode: local modules.\n * Follows the TunnelManager pattern for consistency.\n *\n * Responsibilities:\n * - Track active agent sessions\n * - Spawn Claude Code processes with stream-json output\n * - Forward streaming output via callbacks\n * - Handle abort/stop with SIGINT\n * - Clean up orphaned processes on daemon restart\n */\n\nimport { ChildProcess, spawn } from 'child_process'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport { ensureClaudeBinary, getCachedClaudeBinary } from './claude-binary'\nimport type {\n AgentSession,\n AgentSessionStatus,\n StartAgentOptions,\n SendMessageOptions,\n StartAgentResult,\n SendMessageResult\n} from './types'\n\n/**\n * Extended session with credentials (stored in memory only, never persisted to disk)\n * EP936: Changed from apiKey to oauthToken - Claude Code uses OAuth credentials\n * written to ~/.claude/.credentials.json, not ANTHROPIC_API_KEY env var\n */\ninterface AgentSessionWithCredentials extends AgentSession {\n credentials: {\n oauthToken: string // OAuth access_token from claude.ai (NOT an API key)\n }\n systemPrompt?: string\n}\n\n/**\n * Singleton AgentManager instance\n */\nlet instance: AgentManager | null = null\n\n/**\n * Get the singleton AgentManager instance\n */\nexport function getAgentManager(): AgentManager {\n if (!instance) {\n instance = new AgentManager()\n }\n return instance\n}\n\n/**\n * AgentManager class - manages local Claude Code agent sessions\n */\nexport class AgentManager {\n private sessions = new Map<string, AgentSessionWithCredentials>()\n private processes = new Map<string, ChildProcess>()\n private initialized = false\n private pidDir: string\n\n constructor() {\n this.pidDir = path.join(os.homedir(), '.episoda', 'agent-pids')\n }\n\n /**\n * Initialize the agent manager\n * - Ensure Claude Code is available\n * - Clean up any orphaned processes from previous daemon runs\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n return\n }\n\n console.log('[AgentManager] Initializing...')\n\n // Ensure PID directory exists\n if (!fs.existsSync(this.pidDir)) {\n fs.mkdirSync(this.pidDir, { recursive: true })\n }\n\n // Clean up orphaned processes from previous daemon runs\n await this.cleanupOrphanedProcesses()\n\n // Pre-warm binary detection\n try {\n await ensureClaudeBinary()\n console.log('[AgentManager] Claude Code binary verified')\n } catch (error) {\n console.warn('[AgentManager] Claude Code not available:', error instanceof Error ? error.message : error)\n // Don't fail initialization - we'll handle this when start is called\n }\n\n this.initialized = true\n console.log('[AgentManager] Initialized')\n }\n\n /**\n * Start a new agent session\n *\n * Creates the session record but doesn't spawn the process yet.\n * The process is spawned on the first message.\n */\n async startSession(options: StartAgentOptions): Promise<StartAgentResult> {\n const { sessionId, moduleId, moduleUid, projectPath, message, credentials, systemPrompt, onChunk, onComplete, onError } = options\n\n // Check if session already exists\n if (this.sessions.has(sessionId)) {\n return { success: false, error: 'Session already exists' }\n }\n\n // Validate credentials - EP936: Accept both oauthToken (new) and apiKey (legacy) for backward compatibility\n // The token is actually an OAuth access_token from claude.ai, written to ~/.claude/.credentials.json\n const oauthToken = credentials?.oauthToken || (credentials as any)?.apiKey\n if (!oauthToken) {\n return { success: false, error: 'Missing OAuth token in credentials. Please connect your Claude account in Settings.' }\n }\n // Store the resolved token for use later\n (credentials as any).oauthToken = oauthToken\n\n // Verify Claude Code is available\n try {\n await ensureClaudeBinary()\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Claude Code not available'\n }\n }\n\n // Create session record with credentials (stored in memory only)\n const session: AgentSessionWithCredentials = {\n sessionId,\n moduleId,\n moduleUid,\n projectPath,\n credentials,\n systemPrompt,\n status: 'starting',\n startedAt: new Date(),\n lastActivityAt: new Date()\n }\n\n this.sessions.set(sessionId, session)\n console.log(`[AgentManager] Started session ${sessionId} for ${moduleUid}`)\n\n // Immediately send the first message (spawns Claude Code process)\n return this.sendMessage({\n sessionId,\n message,\n isFirstMessage: true,\n onChunk,\n onComplete,\n onError\n })\n }\n\n /**\n * Send a message to an agent session\n *\n * Spawns a new Claude Code process for each message.\n * Uses --print for non-interactive mode and --output-format stream-json for structured output.\n * Subsequent messages use --resume with the claudeSessionId for conversation continuity.\n */\n async sendMessage(options: SendMessageOptions): Promise<SendMessageResult> {\n const { sessionId, message, isFirstMessage, claudeSessionId, onChunk, onComplete, onError } = options\n\n const session = this.sessions.get(sessionId)\n if (!session) {\n return { success: false, error: 'Session not found' }\n }\n\n // Update activity timestamp\n session.lastActivityAt = new Date()\n session.status = 'running'\n\n try {\n // Get Claude binary path\n const binaryPath = await ensureClaudeBinary()\n\n // Build command arguments\n // Note: --verbose is required when using --print with --output-format stream-json\n const args: string[] = [\n '--print', // Non-interactive mode\n '--output-format', 'stream-json', // Structured streaming output\n '--verbose', // Required for stream-json with --print\n ]\n\n // Add system prompt if provided (only on first message)\n if (isFirstMessage && session.systemPrompt) {\n args.push('--system-prompt', session.systemPrompt)\n }\n\n // Add resume flag for conversation continuity\n if (claudeSessionId) {\n args.push('--resume', claudeSessionId)\n session.claudeSessionId = claudeSessionId\n }\n\n // Add the user message\n args.push('--', message)\n\n console.log(`[AgentManager] Spawning Claude Code for session ${sessionId}`)\n console.log(`[AgentManager] Command: ${binaryPath} ${args.join(' ').substring(0, 100)}...`)\n\n // Determine how to spawn (direct path or via npx)\n let spawnCmd: string\n let spawnArgs: string[]\n\n if (binaryPath.startsWith('npx:')) {\n // Use npx\n spawnCmd = 'npx'\n spawnArgs = ['--yes', binaryPath.replace('npx:', ''), ...args]\n } else {\n // Direct binary path\n spawnCmd = binaryPath\n spawnArgs = args\n }\n\n // EP936: Write OAuth credentials to ~/.claude/.credentials.json\n // Claude Code reads this file for authentication with claude.ai OAuth\n const claudeDir = path.join(os.homedir(), '.claude')\n const credentialsPath = path.join(claudeDir, '.credentials.json')\n\n // Ensure .claude directory exists\n if (!fs.existsSync(claudeDir)) {\n fs.mkdirSync(claudeDir, { recursive: true })\n }\n\n // Write OAuth credentials in the format Claude Code expects\n const credentialsContent = JSON.stringify({\n claudeAiOauth: {\n accessToken: session.credentials.oauthToken\n }\n }, null, 2)\n fs.writeFileSync(credentialsPath, credentialsContent, { mode: 0o600 })\n console.log('[AgentManager] EP936: Wrote OAuth credentials to ~/.claude/.credentials.json')\n\n // Spawn process - no ANTHROPIC_API_KEY needed, uses credentials file\n const childProcess = spawn(spawnCmd, spawnArgs, {\n cwd: session.projectPath,\n env: {\n ...process.env,\n // Disable color output for cleaner JSON parsing\n NO_COLOR: '1',\n FORCE_COLOR: '0',\n },\n stdio: ['pipe', 'pipe', 'pipe']\n })\n\n // Track the process\n this.processes.set(sessionId, childProcess)\n\n // EP942: Close stdin immediately - Claude Code in --print mode doesn't need stdin\n // and will hang waiting for EOF if we don't close it\n childProcess.stdin?.end()\n\n // Write PID file for orphan cleanup\n if (childProcess.pid) {\n session.pid = childProcess.pid\n this.writePidFile(sessionId, childProcess.pid)\n }\n\n // Log stderr for debugging\n childProcess.stderr?.on('data', (data: Buffer) => {\n console.error(`[AgentManager] stderr: ${data.toString()}`)\n })\n\n // Log spawn errors\n childProcess.on('error', (error) => {\n console.error(`[AgentManager] Process spawn error:`, error)\n })\n\n // Buffer for incomplete JSON lines\n let stdoutBuffer = ''\n let extractedSessionId: string | undefined\n\n // Handle stdout (stream-json output)\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString()\n\n // Process complete lines\n const lines = stdoutBuffer.split('\\n')\n stdoutBuffer = lines.pop() || '' // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.trim()) continue\n\n try {\n const parsed = JSON.parse(line)\n\n // Handle different message types from Claude Code stream-json format\n // Types: init, assistant, result, error, etc.\n switch (parsed.type) {\n case 'assistant':\n // Assistant message chunk\n if (parsed.message?.content) {\n // Content is an array of content blocks\n for (const block of parsed.message.content) {\n if (block.type === 'text' && block.text) {\n onChunk(block.text)\n }\n }\n }\n break\n\n case 'content_block_delta':\n // Streaming delta\n if (parsed.delta?.text) {\n onChunk(parsed.delta.text)\n }\n break\n\n case 'result':\n // Final result - extract session ID for resume\n if (parsed.session_id) {\n extractedSessionId = parsed.session_id\n session.claudeSessionId = extractedSessionId\n }\n // Also check for subagent format\n if (parsed.result?.session_id) {\n extractedSessionId = parsed.result.session_id\n session.claudeSessionId = extractedSessionId\n }\n break\n\n case 'system':\n // System messages (init, session info)\n if (parsed.session_id) {\n extractedSessionId = parsed.session_id\n session.claudeSessionId = extractedSessionId\n }\n break\n\n case 'error':\n // Error from Claude Code\n onError(parsed.error?.message || parsed.message || 'Unknown error from Claude Code')\n break\n\n default:\n // Log unknown types for debugging\n console.log(`[AgentManager] Unknown stream-json type: ${parsed.type}`)\n }\n } catch (parseError) {\n // Non-JSON line - might be plain text output, forward it\n if (line.trim()) {\n onChunk(line + '\\n')\n }\n }\n }\n })\n\n // Handle stderr\n let stderrBuffer = ''\n childProcess.stderr?.on('data', (data: Buffer) => {\n stderrBuffer += data.toString()\n })\n\n // Handle process exit\n childProcess.on('exit', (code, signal) => {\n console.log(`[AgentManager] Claude Code exited for session ${sessionId}: code=${code}, signal=${signal}`)\n\n // Clean up\n this.processes.delete(sessionId)\n this.removePidFile(sessionId)\n\n if (code === 0) {\n session.status = 'stopped'\n onComplete(extractedSessionId || session.claudeSessionId)\n } else if (signal === 'SIGINT') {\n session.status = 'stopped'\n // Aborted by user - still complete but without session ID\n onComplete(extractedSessionId || session.claudeSessionId)\n } else {\n session.status = 'error'\n const errorMsg = stderrBuffer.trim() || `Process exited with code ${code}`\n onError(errorMsg)\n }\n })\n\n // Handle process errors\n childProcess.on('error', (error) => {\n console.error(`[AgentManager] Process error for session ${sessionId}:`, error)\n session.status = 'error'\n this.processes.delete(sessionId)\n this.removePidFile(sessionId)\n onError(error.message)\n })\n\n return { success: true }\n } catch (error) {\n session.status = 'error'\n const errorMsg = error instanceof Error ? error.message : String(error)\n onError(errorMsg)\n return { success: false, error: errorMsg }\n }\n }\n\n /**\n * Abort an agent session (SIGINT)\n *\n * Sends SIGINT to the Claude Code process to abort the current operation.\n */\n async abortSession(sessionId: string): Promise<void> {\n const process = this.processes.get(sessionId)\n if (process && !process.killed) {\n console.log(`[AgentManager] Aborting session ${sessionId} with SIGINT`)\n process.kill('SIGINT')\n }\n\n const session = this.sessions.get(sessionId)\n if (session) {\n session.status = 'stopping'\n }\n }\n\n /**\n * Stop an agent session gracefully\n *\n * Sends SIGINT and waits for process to exit.\n * If it doesn't exit within 5 seconds, sends SIGTERM.\n */\n async stopSession(sessionId: string): Promise<void> {\n const process = this.processes.get(sessionId)\n const session = this.sessions.get(sessionId)\n\n if (session) {\n session.status = 'stopping'\n }\n\n if (process && !process.killed) {\n console.log(`[AgentManager] Stopping session ${sessionId}`)\n\n // Send SIGINT first\n process.kill('SIGINT')\n\n // Wait up to 5 seconds for graceful exit\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n if (!process.killed) {\n console.log(`[AgentManager] Force killing session ${sessionId}`)\n process.kill('SIGTERM')\n }\n resolve()\n }, 5000)\n\n process.once('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n // Clean up session\n this.sessions.delete(sessionId)\n this.processes.delete(sessionId)\n this.removePidFile(sessionId)\n\n console.log(`[AgentManager] Session ${sessionId} stopped`)\n }\n\n /**\n * Stop all active sessions\n */\n async stopAllSessions(): Promise<void> {\n const sessionIds = Array.from(this.sessions.keys())\n await Promise.all(sessionIds.map(id => this.stopSession(id)))\n }\n\n /**\n * Get session info\n */\n getSession(sessionId: string): AgentSession | undefined {\n return this.sessions.get(sessionId)\n }\n\n /**\n * Get all active sessions\n */\n getAllSessions(): AgentSession[] {\n return Array.from(this.sessions.values())\n }\n\n /**\n * Check if a session exists\n */\n hasSession(sessionId: string): boolean {\n return this.sessions.has(sessionId)\n }\n\n /**\n * Clean up orphaned processes from previous daemon runs\n *\n * Reads PID files from ~/.episoda/agent-pids/ and kills any\n * processes that are still running.\n */\n async cleanupOrphanedProcesses(): Promise<{ cleaned: number }> {\n let cleaned = 0\n\n if (!fs.existsSync(this.pidDir)) {\n return { cleaned }\n }\n\n const pidFiles = fs.readdirSync(this.pidDir).filter(f => f.endsWith('.pid'))\n\n for (const pidFile of pidFiles) {\n const pidPath = path.join(this.pidDir, pidFile)\n\n try {\n const pidStr = fs.readFileSync(pidPath, 'utf-8').trim()\n const pid = parseInt(pidStr, 10)\n\n if (!isNaN(pid)) {\n // Check if process is still running\n try {\n process.kill(pid, 0) // Signal 0 just checks if process exists\n // Process is running, kill it\n console.log(`[AgentManager] Killing orphaned process ${pid}`)\n process.kill(pid, 'SIGTERM')\n cleaned++\n } catch {\n // Process not running, just clean up the file\n }\n }\n\n // Remove PID file\n fs.unlinkSync(pidPath)\n } catch (error) {\n // Ignore errors reading/cleaning PID files\n console.warn(`[AgentManager] Error cleaning PID file ${pidFile}:`, error)\n }\n }\n\n if (cleaned > 0) {\n console.log(`[AgentManager] Cleaned up ${cleaned} orphaned process(es)`)\n }\n\n return { cleaned }\n }\n\n /**\n * Write PID file for session tracking\n */\n private writePidFile(sessionId: string, pid: number): void {\n const pidPath = path.join(this.pidDir, `${sessionId}.pid`)\n fs.writeFileSync(pidPath, pid.toString())\n }\n\n /**\n * Remove PID file for session\n */\n private removePidFile(sessionId: string): void {\n const pidPath = path.join(this.pidDir, `${sessionId}.pid`)\n try {\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n } catch {\n // Ignore errors\n }\n }\n}\n","/**\n * Dev Server Manager\n *\n * EP818: Manages local dev server for tunnel preview.\n * EP932: Enhanced with auto-restart, memory limits, and logging.\n */\n\nimport { spawn, ChildProcess, execSync } from 'child_process'\nimport { isPortInUse } from './port-check'\nimport { getConfigDir, loadConfig } from '@episoda/core'\nimport { fetchEnvVarsWithCache } from './env-cache'\nimport http from 'http'\nimport * as fs from 'fs'\nimport * as path from 'path'\n\n/**\n * EP932: Configuration constants\n */\nconst MAX_RESTART_ATTEMPTS = 5\nconst INITIAL_RESTART_DELAY_MS = 2000\nconst MAX_RESTART_DELAY_MS = 30000\nconst MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024 // 5MB\nconst NODE_MEMORY_LIMIT_MB = 2048\n\n/**\n * EP932: Extended server info with metadata\n * EP959-m2: Added customCommand for worktree_dev_server_script support\n * EP998: Added injectedEnvVars for runtime injection\n */\ninterface ServerInfo {\n process: ChildProcess\n moduleUid: string\n projectPath: string\n port: number\n startedAt: Date\n restartCount: number\n lastRestartAt: Date | null\n autoRestartEnabled: boolean\n logFile: string | null\n customCommand?: string // EP959-m2: Custom dev server command from project settings\n injectedEnvVars?: Record<string, string> // EP998: Env vars from database\n}\n\n/**\n * Dev server process tracking\n * EP932: Now stores extended ServerInfo instead of just ChildProcess\n */\nconst activeServers: Map<string, ServerInfo> = new Map()\n\n/**\n * EP932: Get the logs directory, creating if needed\n */\nfunction getLogsDir(): string {\n const logsDir = path.join(getConfigDir(), 'logs')\n if (!fs.existsSync(logsDir)) {\n fs.mkdirSync(logsDir, { recursive: true })\n }\n return logsDir\n}\n\n/**\n * EP932: Get log file path for a module\n */\nfunction getLogFilePath(moduleUid: string): string {\n return path.join(getLogsDir(), `dev-${moduleUid}.log`)\n}\n\n/**\n * EP932: Rotate log file if it exceeds max size\n */\nfunction rotateLogIfNeeded(logPath: string): void {\n try {\n if (fs.existsSync(logPath)) {\n const stats = fs.statSync(logPath)\n if (stats.size > MAX_LOG_SIZE_BYTES) {\n // Keep one backup\n const backupPath = `${logPath}.1`\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath)\n }\n fs.renameSync(logPath, backupPath)\n console.log(`[DevServer] EP932: Rotated log file for ${path.basename(logPath)}`)\n }\n }\n } catch (error) {\n console.warn(`[DevServer] EP932: Failed to rotate log:`, error)\n }\n}\n\n/**\n * EP932: Write to log file with timestamp\n */\nfunction writeToLog(logPath: string, line: string, isError: boolean = false): void {\n try {\n const timestamp = new Date().toISOString()\n const prefix = isError ? 'ERR' : 'OUT'\n const logLine = `[${timestamp}] [${prefix}] ${line}\\n`\n fs.appendFileSync(logPath, logLine)\n } catch {\n // Ignore log write errors\n }\n}\n\n/**\n * EP929: Check if dev server is healthy (actually responding to requests)\n * This is different from isPortInUse() which only checks if the port is bound.\n * A hung process can have the port bound but not respond to HTTP requests.\n */\nexport async function isDevServerHealthy(port: number, timeoutMs: number = 5000): Promise<boolean> {\n return new Promise((resolve) => {\n const req = http.request(\n {\n hostname: 'localhost',\n port,\n path: '/',\n method: 'HEAD',\n timeout: timeoutMs\n },\n (res) => {\n // Any response (even 404/500) means server is responding\n resolve(true)\n }\n )\n\n req.on('error', () => {\n resolve(false)\n })\n\n req.on('timeout', () => {\n req.destroy()\n resolve(false)\n })\n\n req.end()\n })\n}\n\n/**\n * EP929: Kill any process using the specified port\n * Used to clean up hung dev servers that aren't responding.\n */\nexport async function killProcessOnPort(port: number): Promise<boolean> {\n try {\n // Find PID using the port (macOS/Linux compatible)\n const result = execSync(`lsof -ti:${port} 2>/dev/null || true`, { encoding: 'utf8' }).trim()\n\n if (!result) {\n console.log(`[DevServer] EP929: No process found on port ${port}`)\n return true\n }\n\n const pids = result.split('\\n').filter(Boolean)\n console.log(`[DevServer] EP929: Found ${pids.length} process(es) on port ${port}: ${pids.join(', ')}`)\n\n for (const pid of pids) {\n try {\n // First try SIGTERM for graceful shutdown\n execSync(`kill -15 ${pid} 2>/dev/null || true`, { encoding: 'utf8' })\n console.log(`[DevServer] EP929: Sent SIGTERM to PID ${pid}`)\n } catch {\n // Ignore errors, process may have already exited\n }\n }\n\n // Wait briefly for graceful shutdown\n await new Promise(resolve => setTimeout(resolve, 1000))\n\n // Check if any processes still running and force kill\n for (const pid of pids) {\n try {\n // Check if process still exists\n execSync(`kill -0 ${pid} 2>/dev/null`, { encoding: 'utf8' })\n // Still running, force kill\n execSync(`kill -9 ${pid} 2>/dev/null || true`, { encoding: 'utf8' })\n console.log(`[DevServer] EP929: Force killed PID ${pid}`)\n } catch {\n // Process already exited, good\n }\n }\n\n // Wait for port to be released\n await new Promise(resolve => setTimeout(resolve, 500))\n\n const stillInUse = await isPortInUse(port)\n if (stillInUse) {\n console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`)\n return false\n }\n\n console.log(`[DevServer] EP929: Successfully freed port ${port}`)\n return true\n } catch (error) {\n console.error(`[DevServer] EP929: Error killing process on port ${port}:`, error)\n return false\n }\n}\n\n/**\n * Wait for a port to become available\n */\nasync function waitForPort(port: number, timeoutMs: number = 30000): Promise<boolean> {\n const startTime = Date.now()\n const checkInterval = 500\n\n while (Date.now() - startTime < timeoutMs) {\n if (await isPortInUse(port)) {\n return true\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval))\n }\n\n return false\n}\n\n/**\n * EP932: Calculate restart delay with exponential backoff\n */\nfunction calculateRestartDelay(restartCount: number): number {\n const delay = INITIAL_RESTART_DELAY_MS * Math.pow(2, restartCount)\n return Math.min(delay, MAX_RESTART_DELAY_MS)\n}\n\n/**\n * EP932: Internal function to spawn the dev server process\n * EP959-m2: Now accepts custom command from project settings\n * EP998: Now accepts injected env vars from database\n */\nfunction spawnDevServerProcess(\n projectPath: string,\n port: number,\n moduleUid: string,\n logPath: string,\n customCommand?: string,\n injectedEnvVars?: Record<string, string>\n): ChildProcess {\n // Rotate log if needed before starting\n rotateLogIfNeeded(logPath)\n\n // EP932: Add memory limit via NODE_OPTIONS\n const nodeOptions = process.env.NODE_OPTIONS || ''\n const memoryFlag = `--max-old-space-size=${NODE_MEMORY_LIMIT_MB}`\n const enhancedNodeOptions = nodeOptions.includes('max-old-space-size')\n ? nodeOptions\n : `${nodeOptions} ${memoryFlag}`.trim()\n\n // EP959-m2: Use custom command if provided, otherwise default to 'npm run dev'\n const command = customCommand || 'npm run dev'\n const [cmd, ...args] = command.split(' ')\n console.log(`[DevServer] EP959: Starting with command: ${command}`)\n\n // EP998: Merge process.env with injected env vars (injected vars take precedence)\n const mergedEnv = {\n ...process.env,\n ...injectedEnvVars,\n PORT: String(port),\n NODE_OPTIONS: enhancedNodeOptions\n }\n\n const injectedCount = injectedEnvVars ? Object.keys(injectedEnvVars).length : 0\n if (injectedCount > 0) {\n console.log(`[DevServer] EP998: Injecting ${injectedCount} env vars from database`)\n }\n\n const devProcess = spawn(cmd, args, {\n cwd: projectPath,\n env: mergedEnv,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n shell: true // EP959: Use shell to handle complex commands\n })\n\n // Log to file and console\n devProcess.stdout?.on('data', (data: Buffer) => {\n const line = data.toString().trim()\n if (line) {\n console.log(`[DevServer:${moduleUid}] ${line}`)\n writeToLog(logPath, line, false)\n }\n })\n\n devProcess.stderr?.on('data', (data: Buffer) => {\n const line = data.toString().trim()\n if (line) {\n console.error(`[DevServer:${moduleUid}] ${line}`)\n writeToLog(logPath, line, true)\n }\n })\n\n return devProcess\n}\n\n/**\n * EP932: Handle process exit with auto-restart logic\n */\nasync function handleProcessExit(\n moduleUid: string,\n code: number | null,\n signal: NodeJS.Signals | null\n): Promise<void> {\n const serverInfo = activeServers.get(moduleUid)\n if (!serverInfo) {\n return\n }\n\n const exitReason = signal ? `signal ${signal}` : `code ${code}`\n console.log(`[DevServer] EP932: Process for ${moduleUid} exited with ${exitReason}`)\n writeToLog(serverInfo.logFile || '', `Process exited with ${exitReason}`, true)\n\n // Check if auto-restart is enabled and we haven't exceeded max attempts\n if (!serverInfo.autoRestartEnabled) {\n console.log(`[DevServer] EP932: Auto-restart disabled for ${moduleUid}`)\n activeServers.delete(moduleUid)\n return\n }\n\n if (serverInfo.restartCount >= MAX_RESTART_ATTEMPTS) {\n console.error(`[DevServer] EP932: Max restart attempts (${MAX_RESTART_ATTEMPTS}) reached for ${moduleUid}`)\n writeToLog(serverInfo.logFile || '', `Max restart attempts reached, giving up`, true)\n activeServers.delete(moduleUid)\n return\n }\n\n // Calculate delay with exponential backoff\n const delay = calculateRestartDelay(serverInfo.restartCount)\n console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`)\n writeToLog(serverInfo.logFile || '', `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false)\n\n await new Promise(resolve => setTimeout(resolve, delay))\n\n // Double-check we still want to restart (module might have been stopped manually)\n if (!activeServers.has(moduleUid)) {\n console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`)\n return\n }\n\n // Spawn new process\n // EP959-m2: Pass custom command for restart\n // EP998: Pass injected env vars for restart\n const logPath = serverInfo.logFile || getLogFilePath(moduleUid)\n const newProcess = spawnDevServerProcess(serverInfo.projectPath, serverInfo.port, moduleUid, logPath, serverInfo.customCommand, serverInfo.injectedEnvVars)\n\n // Update server info\n const updatedInfo: ServerInfo = {\n ...serverInfo,\n process: newProcess,\n restartCount: serverInfo.restartCount + 1,\n lastRestartAt: new Date()\n }\n activeServers.set(moduleUid, updatedInfo)\n\n // Set up exit handler for new process\n newProcess.on('exit', (newCode, newSignal) => {\n handleProcessExit(moduleUid, newCode, newSignal)\n })\n\n newProcess.on('error', (error) => {\n console.error(`[DevServer] EP932: Process error for ${moduleUid}:`, error)\n writeToLog(logPath, `Process error: ${error.message}`, true)\n })\n\n // Wait for server to be ready\n const serverReady = await waitForPort(serverInfo.port, 60000)\n if (serverReady) {\n console.log(`[DevServer] EP932: Server ${moduleUid} restarted successfully`)\n writeToLog(logPath, `Server restarted successfully`, false)\n // Reset restart count on successful start\n updatedInfo.restartCount = 0\n } else {\n console.error(`[DevServer] EP932: Server ${moduleUid} failed to restart`)\n writeToLog(logPath, `Server failed to restart within timeout`, true)\n }\n}\n\n/**\n * Start the dev server for a project\n * EP932: Now with auto-restart, memory limits, and logging\n * EP959-m2: Added customCommand option for worktree_dev_server_script\n * EP998: Added runtime env var injection from database\n */\nexport async function startDevServer(\n projectPath: string,\n port: number = 3000,\n moduleUid: string = 'default',\n options: { autoRestart?: boolean; customCommand?: string } = {}\n): Promise<{ success: boolean; error?: string; alreadyRunning?: boolean }> {\n const autoRestart = options.autoRestart ?? true // Default to auto-restart enabled\n const customCommand = options.customCommand\n\n // Check if server is already running\n if (await isPortInUse(port)) {\n console.log(`[DevServer] Server already running on port ${port}`)\n return { success: true, alreadyRunning: true }\n }\n\n // Check if we already have a process for this module\n if (activeServers.has(moduleUid)) {\n const existing = activeServers.get(moduleUid)\n if (existing && !existing.process.killed) {\n console.log(`[DevServer] Process already exists for ${moduleUid}`)\n return { success: true, alreadyRunning: true }\n }\n }\n\n console.log(`[DevServer] EP932: Starting dev server for ${moduleUid} on port ${port} (auto-restart: ${autoRestart})...`)\n\n // EP998: Fetch env vars from database for runtime injection\n let injectedEnvVars: Record<string, string> = {}\n try {\n const config = await loadConfig()\n if (config?.access_token && config?.project_id) {\n const apiUrl = config.api_url || 'https://episoda.dev'\n const result = await fetchEnvVarsWithCache(apiUrl, config.access_token, {\n projectId: config.project_id,\n cacheTtl: 300 // 5 minute cache for daemon\n })\n injectedEnvVars = result.envVars\n console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? 'cache' : 'server'})`)\n } else {\n console.log(`[DevServer] EP998: No auth config, skipping env var injection`)\n }\n } catch (error) {\n console.warn(`[DevServer] EP998: Failed to fetch env vars:`, error instanceof Error ? error.message : error)\n // Continue without injected env vars - better to start than fail\n }\n\n try {\n const logPath = getLogFilePath(moduleUid)\n // EP959-m2: Pass custom command to spawnDevServerProcess\n // EP998: Pass injected env vars\n const devProcess = spawnDevServerProcess(projectPath, port, moduleUid, logPath, customCommand, injectedEnvVars)\n\n // Create server info\n const serverInfo: ServerInfo = {\n process: devProcess,\n moduleUid,\n projectPath,\n port,\n startedAt: new Date(),\n restartCount: 0,\n lastRestartAt: null,\n autoRestartEnabled: autoRestart,\n logFile: logPath,\n customCommand, // EP959-m2: Store for restarts\n injectedEnvVars // EP998: Store for restarts\n }\n activeServers.set(moduleUid, serverInfo)\n\n writeToLog(logPath, `Starting dev server on port ${port}`, false)\n\n // Set up exit handler for auto-restart\n devProcess.on('exit', (code, signal) => {\n handleProcessExit(moduleUid, code, signal)\n })\n\n devProcess.on('error', (error) => {\n console.error(`[DevServer] Process error for ${moduleUid}:`, error)\n writeToLog(logPath, `Process error: ${error.message}`, true)\n })\n\n // Wait for the server to start\n console.log(`[DevServer] Waiting for server to start on port ${port}...`)\n const serverReady = await waitForPort(port, 60000) // 60 second timeout for npm install + start\n\n if (!serverReady) {\n devProcess.kill()\n activeServers.delete(moduleUid)\n writeToLog(logPath, `Failed to start within timeout`, true)\n return { success: false, error: 'Dev server failed to start within timeout' }\n }\n\n console.log(`[DevServer] Server started successfully on port ${port}`)\n writeToLog(logPath, `Server started successfully`, false)\n return { success: true }\n\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n console.error(`[DevServer] Failed to start:`, error)\n return { success: false, error: errorMsg }\n }\n}\n\n/**\n * Stop the dev server for a module\n * EP932: Also disables auto-restart to prevent immediate restart\n */\nexport async function stopDevServer(moduleUid: string): Promise<void> {\n const serverInfo = activeServers.get(moduleUid)\n if (!serverInfo) {\n return\n }\n\n // Disable auto-restart before stopping\n serverInfo.autoRestartEnabled = false\n\n if (!serverInfo.process.killed) {\n console.log(`[DevServer] Stopping server for ${moduleUid}`)\n if (serverInfo.logFile) {\n writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false)\n }\n serverInfo.process.kill('SIGTERM')\n\n // Wait a moment for graceful shutdown\n await new Promise(resolve => setTimeout(resolve, 2000))\n\n if (!serverInfo.process.killed) {\n serverInfo.process.kill('SIGKILL')\n }\n }\n\n activeServers.delete(moduleUid)\n}\n\n/**\n * EP932: Restart the dev server for a module\n * Stops the current server and starts a new one with fresh restart count\n */\nexport async function restartDevServer(\n moduleUid: string\n): Promise<{ success: boolean; error?: string }> {\n const serverInfo = activeServers.get(moduleUid)\n if (!serverInfo) {\n return { success: false, error: `No dev server found for ${moduleUid}` }\n }\n\n const { projectPath, port, autoRestartEnabled, logFile } = serverInfo\n\n console.log(`[DevServer] EP932: Restarting server for ${moduleUid}...`)\n if (logFile) {\n writeToLog(logFile, `Manual restart requested`, false)\n }\n\n // Stop current server (disables auto-restart temporarily)\n await stopDevServer(moduleUid)\n\n // Wait for port to be released\n await new Promise(resolve => setTimeout(resolve, 1000))\n\n // Kill any lingering process on the port\n if (await isPortInUse(port)) {\n await killProcessOnPort(port)\n }\n\n // Start fresh\n return startDevServer(projectPath, port, moduleUid, { autoRestart: autoRestartEnabled })\n}\n\n/**\n * Stop all dev servers\n */\nexport async function stopAllDevServers(): Promise<void> {\n const moduleUids = Array.from(activeServers.keys())\n await Promise.all(moduleUids.map(uid => stopDevServer(uid)))\n}\n\n/**\n * Check if a dev server is running for a module\n */\nexport function isDevServerRunning(moduleUid: string): boolean {\n const serverInfo = activeServers.get(moduleUid)\n return !!serverInfo && !serverInfo.process.killed\n}\n\n/**\n * EP932: Get status of all running dev servers\n */\nexport function getDevServerStatus(): Array<{\n moduleUid: string\n port: number\n pid: number | undefined\n startedAt: Date\n uptime: number // seconds\n restartCount: number\n lastRestartAt: Date | null\n autoRestartEnabled: boolean\n logFile: string | null\n}> {\n const now = Date.now()\n return Array.from(activeServers.values()).map(info => ({\n moduleUid: info.moduleUid,\n port: info.port,\n pid: info.process.pid,\n startedAt: info.startedAt,\n uptime: Math.floor((now - info.startedAt.getTime()) / 1000),\n restartCount: info.restartCount,\n lastRestartAt: info.lastRestartAt,\n autoRestartEnabled: info.autoRestartEnabled,\n logFile: info.logFile\n }))\n}\n\n/**\n * EP932: Get status of a specific dev server\n */\nexport function getDevServerInfo(moduleUid: string): ServerInfo | undefined {\n return activeServers.get(moduleUid)\n}\n\n/**\n * Ensure dev server is running, starting if needed\n * EP932: Now passes autoRestart option\n * EP959-m2: Added customCommand for worktree_dev_server_script support\n */\nexport async function ensureDevServer(\n projectPath: string,\n port: number = 3000,\n moduleUid: string = 'default',\n customCommand?: string\n): Promise<{ success: boolean; error?: string }> {\n // First check if something is already on the port\n if (await isPortInUse(port)) {\n return { success: true }\n }\n\n // Start the dev server with auto-restart enabled\n // EP959-m2: Pass custom command if provided\n return startDevServer(projectPath, port, moduleUid, { autoRestart: true, customCommand })\n}\n","/**\n * EP998: Environment Variable Caching\n *\n * Provides caching for fetched environment variables to reduce API calls\n * and enable offline fallback.\n *\n * Cache location: ~/.episoda/cache/env-vars-{projectId}.json\n * Default TTL: 60 seconds\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport { fetchEnvVars } from './env-setup'\n\nexport interface EnvCacheOptions {\n noCache?: boolean // Force fresh fetch, ignore cache\n cacheTtl?: number // Cache TTL in seconds (default: 60)\n offline?: boolean // Use cache only, don't fetch\n projectId?: string // Project ID for cache file naming\n}\n\nexport interface EnvCacheResult {\n envVars: Record<string, string>\n fromCache: boolean\n cacheAge?: number // Age of cache in milliseconds (if from cache)\n}\n\ninterface CacheData {\n vars: Record<string, string>\n fetchedAt: number // Unix timestamp in milliseconds\n projectId: string\n}\n\nconst DEFAULT_CACHE_TTL = 60 // seconds\nconst CACHE_DIR = path.join(os.homedir(), '.episoda', 'cache')\n\n/**\n * Get the cache file path for a project\n */\nfunction getCacheFilePath(projectId: string): string {\n return path.join(CACHE_DIR, `env-vars-${projectId}.json`)\n}\n\n/**\n * Ensure cache directory exists\n */\nfunction ensureCacheDir(): void {\n if (!fs.existsSync(CACHE_DIR)) {\n fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 })\n }\n}\n\n/**\n * Read cached environment variables\n */\nfunction readCache(projectId: string): CacheData | null {\n try {\n const cacheFile = getCacheFilePath(projectId)\n if (!fs.existsSync(cacheFile)) {\n return null\n }\n\n const content = fs.readFileSync(cacheFile, 'utf-8')\n const data = JSON.parse(content) as CacheData\n\n // Validate cache data structure\n if (!data.vars || typeof data.vars !== 'object' || !data.fetchedAt) {\n return null\n }\n\n return data\n } catch {\n return null\n }\n}\n\n/**\n * Write environment variables to cache\n */\nfunction writeCache(projectId: string, vars: Record<string, string>): void {\n try {\n ensureCacheDir()\n const cacheFile = getCacheFilePath(projectId)\n const data: CacheData = {\n vars,\n fetchedAt: Date.now(),\n projectId\n }\n fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 0o600 })\n } catch (error) {\n // Silently fail on cache write errors\n console.warn('[env-cache] Failed to write cache:', error instanceof Error ? error.message : error)\n }\n}\n\n/**\n * Check if cache is still valid (within TTL)\n */\nfunction isCacheValid(cache: CacheData, ttlSeconds: number): boolean {\n const ageMs = Date.now() - cache.fetchedAt\n return ageMs < ttlSeconds * 1000\n}\n\n/**\n * Fetch environment variables with caching support\n *\n * @param apiUrl - Base API URL\n * @param accessToken - OAuth access token\n * @param options - Cache options\n * @returns Environment variables and cache status\n */\nexport async function fetchEnvVarsWithCache(\n apiUrl: string,\n accessToken: string,\n options: EnvCacheOptions = {}\n): Promise<EnvCacheResult> {\n const {\n noCache = false,\n cacheTtl = DEFAULT_CACHE_TTL,\n offline = false,\n projectId = 'default'\n } = options\n\n // Offline mode: only use cache\n if (offline) {\n const cache = readCache(projectId)\n if (cache && Object.keys(cache.vars).length > 0) {\n return {\n envVars: cache.vars,\n fromCache: true,\n cacheAge: Date.now() - cache.fetchedAt\n }\n }\n throw new Error(\n 'Offline mode requires cached env vars, but no cache found.\\n' +\n 'Run without --offline first to populate the cache.'\n )\n }\n\n // Check cache (unless noCache is set)\n if (!noCache) {\n const cache = readCache(projectId)\n if (cache && isCacheValid(cache, cacheTtl)) {\n return {\n envVars: cache.vars,\n fromCache: true,\n cacheAge: Date.now() - cache.fetchedAt\n }\n }\n }\n\n // Fetch from server\n try {\n const envVars = await fetchEnvVars(apiUrl, accessToken)\n\n // Update cache (even if fetch returned empty, to record the attempt)\n if (Object.keys(envVars).length > 0) {\n writeCache(projectId, envVars)\n }\n\n return {\n envVars,\n fromCache: false\n }\n } catch (error) {\n // On fetch error, try to use stale cache as fallback\n const cache = readCache(projectId)\n if (cache && Object.keys(cache.vars).length > 0) {\n const cacheAge = Date.now() - cache.fetchedAt\n console.warn(\n `[env-cache] Failed to fetch env vars, using stale cache (${Math.round(cacheAge / 1000)}s old)`\n )\n return {\n envVars: cache.vars,\n fromCache: true,\n cacheAge\n }\n }\n\n // No cache available, rethrow error\n throw new Error(\n `Failed to fetch environment variables: ${error instanceof Error ? error.message : error}\\n` +\n 'No cached values available as fallback.'\n )\n }\n}\n\n/**\n * Clear the env var cache for a project\n */\nexport function clearEnvCache(projectId: string): boolean {\n try {\n const cacheFile = getCacheFilePath(projectId)\n if (fs.existsSync(cacheFile)) {\n fs.unlinkSync(cacheFile)\n return true\n }\n return false\n } catch {\n return false\n }\n}\n\n/**\n * Get cache info for a project\n */\nexport function getCacheInfo(projectId: string): { exists: boolean; age?: number; varCount?: number } {\n const cache = readCache(projectId)\n if (!cache) {\n return { exists: false }\n }\n return {\n exists: true,\n age: Date.now() - cache.fetchedAt,\n varCount: Object.keys(cache.vars).length\n }\n}\n","/**\n * EP988: Environment Variable Setup Utility\n *\n * Shared utility for fetching and writing environment variables to worktrees.\n * Used by both CLI checkout and daemon checkout_module handler.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\n/**\n * Fetch environment variables from the server\n *\n * @param apiUrl - Base API URL (e.g., https://episoda.dev)\n * @param accessToken - OAuth access token\n * @returns Key-value map of env vars, or empty object on error\n */\nexport async function fetchEnvVars(\n apiUrl: string,\n accessToken: string\n): Promise<Record<string, string>> {\n try {\n const url = `${apiUrl}/api/cli/env-vars`\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${accessToken}`,\n 'Content-Type': 'application/json'\n }\n })\n\n if (!response.ok) {\n console.warn(`[env-setup] Failed to fetch env vars: ${response.status}`)\n return {}\n }\n\n const data = await response.json() as { env_vars?: Record<string, string> }\n const envVars = data.env_vars || {}\n\n console.log(`[env-setup] Fetched ${Object.keys(envVars).length} env vars from server`)\n return envVars\n } catch (error) {\n console.warn('[env-setup] Error fetching env vars:', error instanceof Error ? error.message : error)\n return {}\n }\n}\n\n/**\n * Write environment variables to .env file in a directory\n *\n * @param targetPath - Directory path to write .env file to\n * @param envVars - Key-value map of environment variables\n */\nexport function writeEnvFile(\n targetPath: string,\n envVars: Record<string, string>\n): void {\n if (Object.keys(envVars).length === 0) {\n return\n }\n\n const envContent = Object.entries(envVars)\n .map(([key, value]) => {\n // Quote values containing spaces, quotes, newlines, or shell special chars\n if (/[\\s'\"#$`\\\\]/.test(value) || value.includes('\\n')) {\n // Escape backslashes, quotes, and newlines for .env compatibility\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n return `${key}=\"${escaped}\"`\n }\n return `${key}=${value}`\n })\n .join('\\n') + '\\n'\n\n const envPath = path.join(targetPath, '.env')\n fs.writeFileSync(envPath, envContent, { mode: 0o600 })\n console.log(`[env-setup] Wrote ${Object.keys(envVars).length} env vars to ${envPath}`)\n}\n\n/**\n * Complete env setup for a worktree: fetch from server and write to .env\n *\n * @param worktreePath - Path to the worktree directory\n * @param apiUrl - Base API URL\n * @param accessToken - OAuth access token\n * @returns true if env vars were written, false otherwise\n */\nexport async function setupWorktreeEnv(\n worktreePath: string,\n apiUrl: string,\n accessToken: string\n): Promise<boolean> {\n const envVars = await fetchEnvVars(apiUrl, accessToken)\n\n if (Object.keys(envVars).length === 0) {\n return false\n }\n\n writeEnvFile(worktreePath, envVars)\n return true\n}\n","/**\n * Port Detection Utility\n *\n * EP818: Auto-detect dev server port from project configuration.\n * Checks .env for PORT variable, then package.json dev script for --port flag.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nconst DEFAULT_PORT = 3000\n\n/**\n * Detect the dev server port for a project\n */\nexport function detectDevPort(projectPath: string): number {\n // 1. Check .env file for PORT variable\n const envPort = getPortFromEnv(projectPath)\n if (envPort) {\n console.log(`[PortDetect] Found PORT=${envPort} in .env`)\n return envPort\n }\n\n // 2. Check package.json dev script for --port flag\n const scriptPort = getPortFromPackageJson(projectPath)\n if (scriptPort) {\n console.log(`[PortDetect] Found port ${scriptPort} in package.json dev script`)\n return scriptPort\n }\n\n // 3. Default to 3000\n console.log(`[PortDetect] Using default port ${DEFAULT_PORT}`)\n return DEFAULT_PORT\n}\n\n/**\n * Read PORT from .env file\n */\nfunction getPortFromEnv(projectPath: string): number | null {\n const envPaths = [\n path.join(projectPath, '.env'),\n path.join(projectPath, '.env.local'),\n path.join(projectPath, '.env.development'),\n path.join(projectPath, '.env.development.local')\n ]\n\n for (const envPath of envPaths) {\n try {\n if (!fs.existsSync(envPath)) continue\n\n const content = fs.readFileSync(envPath, 'utf-8')\n const lines = content.split('\\n')\n\n for (const line of lines) {\n // Match PORT=3000 or PORT = 3000 (with optional quotes)\n const match = line.match(/^\\s*PORT\\s*=\\s*[\"']?(\\d+)[\"']?\\s*(?:#.*)?$/)\n if (match) {\n const port = parseInt(match[1], 10)\n if (port > 0 && port < 65536) {\n return port\n }\n }\n }\n } catch {\n // Ignore read errors, try next file\n }\n }\n\n return null\n}\n\n/**\n * Read port from package.json dev script\n */\nfunction getPortFromPackageJson(projectPath: string): number | null {\n const packageJsonPath = path.join(projectPath, 'package.json')\n\n try {\n if (!fs.existsSync(packageJsonPath)) return null\n\n const content = fs.readFileSync(packageJsonPath, 'utf-8')\n const pkg = JSON.parse(content)\n\n // Check scripts.dev for port flags\n const devScript = pkg.scripts?.dev\n if (!devScript) return null\n\n // Match various port flag formats:\n // --port 3001, --port=3001, -p 3001, -p=3001\n const portMatch = devScript.match(/(?:--port[=\\s]|-p[=\\s])(\\d+)/)\n if (portMatch) {\n const port = parseInt(portMatch[1], 10)\n if (port > 0 && port < 65536) {\n return port\n }\n }\n\n // Also check for PORT= in the script itself (e.g., PORT=3001 npm run dev)\n const envMatch = devScript.match(/PORT[=\\s](\\d+)/)\n if (envMatch) {\n const port = parseInt(envMatch[1], 10)\n if (port > 0 && port < 65536) {\n return port\n }\n }\n } catch {\n // Ignore parse errors\n }\n\n return null\n}\n","/**\n * Worktree Manager\n *\n * EP944: Manages git worktrees for multi-module development.\n * Each module gets its own worktree, enabling parallel development.\n *\n * Directory Structure:\n * ~/episoda/{workspace}/{project}/\n * ├── .bare/ # Bare git clone\n * ├── .episoda/\n * │ └── config.json # Project worktree config\n * ├── EP100/ # Module worktree\n * └── EP101/ # Another module worktree\n *\n * EP995 EVALUATION: Local worktrees[] array vs Server-side state\n * ---------------------------------------------------------------\n * With EP995, the server now stores:\n * - module.worktree_path - filesystem path on checkout_machine_id\n * - module.worktree_status - pending/setup/ready/error\n * - module.worktree_error - error message if setup failed\n *\n * The daemon does reconciliation on connect (reconcileWorktrees), making\n * the local worktrees[] array mostly redundant.\n *\n * RECOMMENDATION: Keep local worktrees[] for now because:\n * 1. It provides instant local lookup without network call\n * 2. Acts as fallback if server unreachable during worktree operations\n * 3. Stores setupStatus for local progress tracking during setup\n * 4. No breaking change - gradual migration is safer\n *\n * FUTURE: Consider removing in a follow-up module when:\n * - EP978 progress infrastructure is complete (UI can show server-side status)\n * - Offline support requirements are clarified\n * - Migration path for existing installations is defined\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { GitExecutor, ExecutionResult } from '@episoda/core'\n\n/**\n * EP957: Validate module UID to prevent path traversal attacks\n *\n * UIDs are server-generated in EPxxx format, but this provides defense-in-depth\n * in case a malformed UID ever reaches the worktree system.\n *\n * @param moduleUid - The module UID to validate\n * @returns true if valid, false if potentially dangerous\n */\nexport function validateModuleUid(moduleUid: string): boolean {\n // Reject empty, null, or whitespace-only\n if (!moduleUid || typeof moduleUid !== 'string' || !moduleUid.trim()) {\n return false\n }\n\n // Reject path traversal attempts\n if (moduleUid.includes('/') || moduleUid.includes('\\\\') || moduleUid.includes('..')) {\n return false\n }\n\n // Reject null bytes (could truncate paths in some systems)\n if (moduleUid.includes('\\0')) {\n return false\n }\n\n // Reject if it would resolve to a hidden directory\n if (moduleUid.startsWith('.')) {\n return false\n }\n\n return true\n}\n\n/**\n * Information about an active worktree\n */\nexport interface WorktreeInfo {\n moduleUid: string // Module UID (e.g., EP100)\n branchName: string // Git branch name\n worktreePath: string // Absolute path to worktree\n createdAt: string // ISO timestamp\n lastAccessed: string // ISO timestamp of last access\n // EP959-11: Setup status tracking\n setupStatus?: 'pending' | 'running' | 'ready' | 'error'\n setupStartedAt?: string // ISO timestamp\n setupCompletedAt?: string // ISO timestamp\n setupError?: string // Error message if setup failed\n}\n\n/**\n * Worktree project configuration\n * Stored in {projectRoot}/.episoda/config.json\n * EP971: All projects use worktree architecture\n */\nexport interface WorktreeProjectConfig {\n projectId: string\n workspaceSlug: string\n projectSlug: string\n bareRepoPath: string // Path to .bare/ directory\n createdAt: string\n worktrees: WorktreeInfo[]\n}\n\n/**\n * Result of a worktree operation\n */\nexport interface WorktreeOperationResult {\n success: boolean\n error?: string\n worktreePath?: string\n worktreeInfo?: WorktreeInfo\n}\n\n/**\n * Manages worktrees for a single project\n */\nexport class WorktreeManager {\n private projectRoot: string\n private bareRepoPath: string\n private configPath: string\n private gitExecutor: GitExecutor\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot\n this.bareRepoPath = path.join(projectRoot, '.bare')\n this.configPath = path.join(projectRoot, '.episoda', 'config.json')\n this.gitExecutor = new GitExecutor()\n }\n\n /**\n * Initialize worktree manager from existing project root\n * EP971: All projects use worktree architecture\n * @returns true if valid project, false otherwise\n */\n async initialize(): Promise<boolean> {\n // Check if .bare directory exists\n if (!fs.existsSync(this.bareRepoPath)) {\n return false\n }\n\n // Check if config exists\n if (!fs.existsSync(this.configPath)) {\n return false\n }\n\n try {\n const config = this.readConfig()\n return config !== null\n } catch {\n return false\n }\n }\n\n /**\n * Create a new worktree project from scratch\n */\n static async createProject(\n projectRoot: string,\n repoUrl: string,\n projectId: string,\n workspaceSlug: string,\n projectSlug: string\n ): Promise<WorktreeManager> {\n const manager = new WorktreeManager(projectRoot)\n\n // Create project directory structure\n const episodaDir = path.join(projectRoot, '.episoda')\n fs.mkdirSync(episodaDir, { recursive: true })\n\n // Clone as bare repository\n const cloneResult = await manager.gitExecutor.execute({\n action: 'clone_bare',\n url: repoUrl,\n path: manager.bareRepoPath\n })\n\n if (!cloneResult.success) {\n throw new Error(`Failed to clone repository: ${cloneResult.output}`)\n }\n\n // Create initial config (EP971: all projects use worktree architecture)\n const config: WorktreeProjectConfig = {\n projectId,\n workspaceSlug,\n projectSlug,\n bareRepoPath: manager.bareRepoPath,\n createdAt: new Date().toISOString(),\n worktrees: []\n }\n\n manager.writeConfig(config)\n\n return manager\n }\n\n /**\n * Create a worktree for a module\n * The entire operation is locked to prevent race conditions\n */\n async createWorktree(\n moduleUid: string,\n branchName: string,\n createBranch: boolean = false\n ): Promise<WorktreeOperationResult> {\n // EP957: Validate module UID to prevent path traversal\n if (!validateModuleUid(moduleUid)) {\n return {\n success: false,\n error: `Invalid module UID: \"${moduleUid}\" - contains disallowed characters`\n }\n }\n\n const worktreePath = path.join(this.projectRoot, moduleUid)\n\n // Acquire lock for the entire check-and-create operation\n const lockAcquired = await this.acquireLock()\n if (!lockAcquired) {\n return {\n success: false,\n error: 'Could not acquire lock for worktree creation'\n }\n }\n\n try {\n // Check if worktree already exists (inside lock to prevent race)\n const existing = this.getWorktreeByModuleUid(moduleUid)\n if (existing) {\n return {\n success: true,\n worktreePath: existing.worktreePath,\n worktreeInfo: existing\n }\n }\n\n // EP945-11: Fetch latest refs from origin before creating worktree\n // This ensures new worktrees are based on current main, not stale local refs\n const fetchResult = await this.gitExecutor.execute({\n action: 'fetch',\n remote: 'origin'\n }, { cwd: this.bareRepoPath })\n\n // EP996: Fail if fetch fails when creating a new branch\n // Without a fresh fetch, the worktree would be based on stale refs\n if (!fetchResult.success && createBranch) {\n console.error('[worktree-manager] Failed to fetch from origin:', fetchResult.output)\n return {\n success: false,\n error: 'Failed to fetch latest refs from origin. Cannot create worktree with stale refs.'\n }\n }\n\n // Create worktree via git\n // EP996: Use origin/main as start point when creating a new branch\n // This ensures the branch is based on the latest remote state, not stale local HEAD\n const result = await this.gitExecutor.execute({\n action: 'worktree_add',\n path: worktreePath,\n branch: branchName,\n create: createBranch,\n startPoint: createBranch ? 'origin/main' : undefined\n }, { cwd: this.bareRepoPath })\n\n if (!result.success) {\n return {\n success: false,\n error: result.output || 'Failed to create worktree'\n }\n }\n\n // Record worktree in config\n const worktreeInfo: WorktreeInfo = {\n moduleUid,\n branchName,\n worktreePath,\n createdAt: new Date().toISOString(),\n lastAccessed: new Date().toISOString()\n }\n\n const config = this.readConfig()\n if (config) {\n config.worktrees.push(worktreeInfo)\n this.writeConfig(config)\n }\n\n return {\n success: true,\n worktreePath,\n worktreeInfo\n }\n } finally {\n this.releaseLock()\n }\n }\n\n /**\n * Remove a worktree for a module\n * P1-2: Wrapped entire operation in lock to prevent race with createWorktree\n */\n async removeWorktree(\n moduleUid: string,\n force: boolean = false\n ): Promise<WorktreeOperationResult> {\n // EP957: Validate module UID to prevent path traversal\n if (!validateModuleUid(moduleUid)) {\n return {\n success: false,\n error: `Invalid module UID: \"${moduleUid}\" - contains disallowed characters`\n }\n }\n\n // Acquire lock for the entire check-and-remove operation\n const lockAcquired = await this.acquireLock()\n if (!lockAcquired) {\n return {\n success: false,\n error: 'Could not acquire lock for worktree removal'\n }\n }\n\n try {\n const existing = this.getWorktreeByModuleUid(moduleUid)\n if (!existing) {\n return {\n success: false,\n error: `No worktree found for module ${moduleUid}`\n }\n }\n\n // Remove worktree via git\n const result = await this.gitExecutor.execute({\n action: 'worktree_remove',\n path: existing.worktreePath,\n force\n }, { cwd: this.bareRepoPath })\n\n if (!result.success) {\n return {\n success: false,\n error: result.output || 'Failed to remove worktree'\n }\n }\n\n // Remove from config (already inside lock, so just read-modify-write)\n const config = this.readConfig()\n if (config) {\n config.worktrees = config.worktrees.filter(w => w.moduleUid !== moduleUid)\n this.writeConfig(config)\n }\n\n return {\n success: true,\n worktreePath: existing.worktreePath\n }\n } finally {\n this.releaseLock()\n }\n }\n\n /**\n * Get worktree path for a module\n */\n getWorktreePath(moduleUid: string): string | null {\n // EP957: Validate module UID for defense-in-depth\n if (!validateModuleUid(moduleUid)) {\n return null\n }\n const worktree = this.getWorktreeByModuleUid(moduleUid)\n return worktree?.worktreePath || null\n }\n\n /**\n * Get worktree info by module UID\n */\n getWorktreeByModuleUid(moduleUid: string): WorktreeInfo | null {\n const config = this.readConfig()\n return config?.worktrees.find(w => w.moduleUid === moduleUid) || null\n }\n\n /**\n * Get worktree info by branch name\n */\n getWorktreeByBranch(branchName: string): WorktreeInfo | null {\n const config = this.readConfig()\n return config?.worktrees.find(w => w.branchName === branchName) || null\n }\n\n /**\n * List all active worktrees\n */\n listWorktrees(): WorktreeInfo[] {\n const config = this.readConfig()\n return config?.worktrees || []\n }\n\n /**\n * EP957: Audit worktrees against known active module UIDs\n *\n * Compares local worktrees against a list of module UIDs that should be active.\n * Used by daemon startup to detect orphaned worktrees from crashed sessions.\n *\n * @param activeModuleUids - UIDs of modules currently in doing/review state\n * @returns Object with orphaned and valid worktree lists\n */\n auditWorktrees(activeModuleUids: string[]): {\n orphaned: WorktreeInfo[]\n valid: WorktreeInfo[]\n } {\n const allWorktrees = this.listWorktrees()\n const activeSet = new Set(activeModuleUids)\n\n const orphaned = allWorktrees.filter(w => !activeSet.has(w.moduleUid))\n const valid = allWorktrees.filter(w => activeSet.has(w.moduleUid))\n\n return { orphaned, valid }\n }\n\n /**\n * Update last accessed timestamp for a worktree\n */\n async touchWorktree(moduleUid: string): Promise<void> {\n await this.updateConfigSafe(config => {\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (worktree) {\n worktree.lastAccessed = new Date().toISOString()\n }\n return config\n })\n }\n\n /**\n * Prune stale worktrees (directories that no longer exist)\n */\n async pruneStaleWorktrees(): Promise<number> {\n // First, run git worktree prune\n await this.gitExecutor.execute({\n action: 'worktree_prune'\n }, { cwd: this.bareRepoPath })\n\n // Then sync our config with reality (with locking)\n let prunedCount = 0\n await this.updateConfigSafe(config => {\n const initialCount = config.worktrees.length\n config.worktrees = config.worktrees.filter(w => fs.existsSync(w.worktreePath))\n prunedCount = initialCount - config.worktrees.length\n return config\n })\n\n return prunedCount\n }\n\n /**\n * Validate all worktrees and sync with git\n */\n async validateWorktrees(): Promise<{\n valid: WorktreeInfo[]\n stale: WorktreeInfo[]\n orphaned: string[]\n }> {\n const config = this.readConfig()\n const valid: WorktreeInfo[] = []\n const stale: WorktreeInfo[] = []\n const orphaned: string[] = []\n\n // Get actual worktrees from git\n const listResult = await this.gitExecutor.execute({\n action: 'worktree_list'\n }, { cwd: this.bareRepoPath })\n\n const actualWorktrees = new Set(\n listResult.details?.worktrees?.map(w => w.path) || []\n )\n\n // Check config worktrees\n for (const worktree of config?.worktrees || []) {\n if (actualWorktrees.has(worktree.worktreePath)) {\n valid.push(worktree)\n actualWorktrees.delete(worktree.worktreePath)\n } else {\n stale.push(worktree)\n }\n }\n\n // Any remaining are orphaned (exist in git but not in config)\n // Filter out the bare repo itself\n for (const wpath of actualWorktrees) {\n if (wpath !== this.bareRepoPath) {\n orphaned.push(wpath)\n }\n }\n\n return { valid, stale, orphaned }\n }\n\n /**\n * Get project configuration\n */\n getConfig(): WorktreeProjectConfig | null {\n return this.readConfig()\n }\n\n /**\n * Get the bare repo path\n */\n getBareRepoPath(): string {\n return this.bareRepoPath\n }\n\n /**\n * Get the project root path\n */\n getProjectRoot(): string {\n return this.projectRoot\n }\n\n // ============================================================\n // Private methods\n // ============================================================\n\n private lockPath: string = ''\n\n private getLockPath(): string {\n if (!this.lockPath) {\n this.lockPath = this.configPath + '.lock'\n }\n return this.lockPath\n }\n\n /**\n * Check if a process is still running\n */\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0) // Signal 0 = check if process exists without killing\n return true\n } catch {\n return false // Process doesn't exist or no permission\n }\n }\n\n /**\n * Acquire a file lock with timeout\n * Uses atomic file creation to ensure only one process holds the lock\n * P1-1: Added PID verification before removing stale locks to prevent race conditions\n */\n private async acquireLock(timeoutMs: number = 5000): Promise<boolean> {\n const lockPath = this.getLockPath()\n const startTime = Date.now()\n const retryInterval = 50\n\n while (Date.now() - startTime < timeoutMs) {\n try {\n // wx flag = create exclusive (fails if file exists)\n fs.writeFileSync(lockPath, String(process.pid), { flag: 'wx' })\n return true\n } catch (err: any) {\n if (err.code === 'EEXIST') {\n // Lock exists, check if it's stale (older than 30 seconds)\n try {\n const stats = fs.statSync(lockPath)\n const lockAge = Date.now() - stats.mtimeMs\n if (lockAge > 30000) {\n // P1-1: Verify the lock holder process is actually dead before removing\n // This prevents race conditions where multiple processes detect stale lock\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf-8').trim()\n const lockPid = parseInt(lockContent, 10)\n if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {\n // Process still running despite old lock - don't remove, just wait\n // The lock holder might be doing a long operation\n await new Promise(resolve => setTimeout(resolve, retryInterval))\n continue\n }\n } catch {\n // Can't read PID, proceed with removal attempt\n }\n // Stale lock from dead process, remove it\n try {\n fs.unlinkSync(lockPath)\n } catch {\n // Another process may have removed it - that's fine, retry\n }\n continue\n }\n } catch {\n // Lock file disappeared, retry\n continue\n }\n // Wait and retry\n await new Promise(resolve => setTimeout(resolve, retryInterval))\n continue\n }\n throw err\n }\n }\n return false\n }\n\n /**\n * Release the file lock\n */\n private releaseLock(): void {\n try {\n fs.unlinkSync(this.getLockPath())\n } catch {\n // Ignore errors (lock may not exist)\n }\n }\n\n private readConfig(): WorktreeProjectConfig | null {\n try {\n if (!fs.existsSync(this.configPath)) {\n return null\n }\n const content = fs.readFileSync(this.configPath, 'utf-8')\n return JSON.parse(content) as WorktreeProjectConfig\n } catch (error) {\n console.error('[WorktreeManager] Failed to read config:', error)\n return null\n }\n }\n\n private writeConfig(config: WorktreeProjectConfig): void {\n try {\n const dir = path.dirname(this.configPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8')\n } catch (error) {\n console.error('[WorktreeManager] Failed to write config:', error)\n throw error\n }\n }\n\n /**\n * Read-modify-write with file locking for safe concurrent access\n */\n private async updateConfigSafe(\n updater: (config: WorktreeProjectConfig) => WorktreeProjectConfig\n ): Promise<boolean> {\n const lockAcquired = await this.acquireLock()\n if (!lockAcquired) {\n console.error('[WorktreeManager] Failed to acquire lock for config update')\n return false\n }\n\n try {\n const config = this.readConfig()\n if (!config) {\n return false\n }\n const updated = updater(config)\n this.writeConfig(updated)\n return true\n } finally {\n this.releaseLock()\n }\n }\n\n // EP959-11: Worktree setup methods\n\n /**\n * Update the setup status of a worktree\n */\n async updateWorktreeStatus(\n moduleUid: string,\n status: 'pending' | 'running' | 'ready' | 'error',\n error?: string\n ): Promise<boolean> {\n return this.updateConfigSafe((config) => {\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (worktree) {\n worktree.setupStatus = status\n if (status === 'running') {\n worktree.setupStartedAt = new Date().toISOString()\n } else if (status === 'ready' || status === 'error') {\n worktree.setupCompletedAt = new Date().toISOString()\n }\n if (error) {\n worktree.setupError = error\n }\n }\n return config\n })\n }\n\n /**\n * Get worktree info including setup status\n */\n getWorktreeStatus(moduleUid: string): WorktreeInfo | null {\n const config = this.readConfig()\n if (!config) return null\n return config.worktrees.find(w => w.moduleUid === moduleUid) || null\n }\n\n /**\n * Copy files from main worktree to module worktree\n *\n * @deprecated EP964: This function is deprecated. Use worktree_env_vars for\n * environment variables or worktree_setup_script for other file operations.\n * This function now returns success (no-op) when main/ worktree doesn't exist.\n */\n async copyFilesFromMain(moduleUid: string, files: string[]): Promise<{ success: boolean; error?: string }> {\n // EP964: Always log deprecation warning\n console.warn(`[WorktreeManager] EP964: copyFilesFromMain is DEPRECATED.`)\n console.warn(`[WorktreeManager] EP964: Use worktree_env_vars for .env or worktree_setup_script for other files.`)\n\n const config = this.readConfig()\n if (!config) {\n return { success: false, error: 'Config not found' }\n }\n\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (!worktree) {\n return { success: false, error: `Worktree not found for ${moduleUid}` }\n }\n\n // EP964: Return success (no-op) when main/ doesn't exist\n const mainWorktree = config.worktrees.find(w => w.moduleUid === 'main')\n if (!mainWorktree) {\n console.warn(`[WorktreeManager] EP964: No 'main' worktree - skipping file copy (this is expected).`)\n return { success: true } // No-op success instead of failure\n }\n\n // Backward compatibility: still copy if main/ exists\n try {\n for (const file of files) {\n const srcPath = path.join(mainWorktree.worktreePath, file)\n const destPath = path.join(worktree.worktreePath, file)\n\n if (fs.existsSync(srcPath)) {\n const destDir = path.dirname(destPath)\n if (!fs.existsSync(destDir)) {\n fs.mkdirSync(destDir, { recursive: true })\n }\n fs.copyFileSync(srcPath, destPath)\n console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`)\n } else {\n console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`)\n }\n }\n return { success: true }\n } catch (error) {\n return { success: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /**\n * Run worktree setup script\n * EP959-M1: Enhanced logging with working directory, timeout info, and script preview\n */\n async runSetupScript(moduleUid: string, script: string): Promise<{ success: boolean; error?: string }> {\n const config = this.readConfig()\n if (!config) {\n return { success: false, error: 'Config not found' }\n }\n\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (!worktree) {\n return { success: false, error: `Worktree not found for ${moduleUid}` }\n }\n\n // EP959-M1: Enhanced logging - show script preview (truncate if very long)\n const TIMEOUT_MINUTES = 10\n const scriptPreview = script.length > 200 ? script.slice(0, 200) + '...' : script\n console.log(`[WorktreeManager] EP959: Running setup script for ${moduleUid}`)\n console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`)\n console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`)\n console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`)\n\n try {\n const { execSync } = require('child_process')\n\n execSync(script, {\n cwd: worktree.worktreePath,\n stdio: 'inherit',\n timeout: TIMEOUT_MINUTES * 60 * 1000,\n env: { ...process.env, NODE_ENV: 'development' }\n })\n\n console.log(`[WorktreeManager] EP959: Setup script completed successfully for ${moduleUid}`)\n return { success: true }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`[WorktreeManager] EP959: Setup script failed for ${moduleUid}:`, errorMessage)\n return { success: false, error: errorMessage }\n }\n }\n\n /**\n * Run worktree cleanup script before removal\n * EP959-m3: Execute cleanup script when worktree is being released\n */\n async runCleanupScript(moduleUid: string, script: string): Promise<{ success: boolean; error?: string }> {\n const config = this.readConfig()\n if (!config) {\n return { success: false, error: 'Config not found' }\n }\n\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (!worktree) {\n return { success: false, error: `Worktree not found for ${moduleUid}` }\n }\n\n const TIMEOUT_MINUTES = 5\n const scriptPreview = script.length > 200 ? script.slice(0, 200) + '...' : script\n console.log(`[WorktreeManager] EP959: Running cleanup script for ${moduleUid}`)\n console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`)\n console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`)\n console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`)\n\n try {\n const { execSync } = require('child_process')\n\n execSync(script, {\n cwd: worktree.worktreePath,\n stdio: 'inherit',\n timeout: TIMEOUT_MINUTES * 60 * 1000,\n env: { ...process.env, NODE_ENV: 'development' }\n })\n\n console.log(`[WorktreeManager] EP959: Cleanup script completed successfully for ${moduleUid}`)\n return { success: true }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n // Cleanup failures are logged but don't block worktree removal\n console.warn(`[WorktreeManager] EP959: Cleanup script failed for ${moduleUid} (non-blocking):`, errorMessage)\n return { success: false, error: errorMessage }\n }\n }\n}\n\n/**\n * Get the default episoda root directory\n * Can be overridden with EPISODA_ROOT environment variable\n */\nexport function getEpisodaRoot(): string {\n return process.env.EPISODA_ROOT || path.join(require('os').homedir(), 'episoda')\n}\n\n/**\n * Get the project path for a workspace/project combination\n */\nexport function getProjectPath(workspaceSlug: string, projectSlug: string): string {\n return path.join(getEpisodaRoot(), workspaceSlug, projectSlug)\n}\n\n/**\n * Check if a path is a valid worktree project\n */\nexport async function isWorktreeProject(projectRoot: string): Promise<boolean> {\n const manager = new WorktreeManager(projectRoot)\n return manager.initialize()\n}\n\n/**\n * Find the project root by looking for .bare directory\n * Searches current directory and walks up to 5 levels looking for a valid worktree project.\n *\n * @param startPath - Directory to start searching from\n * @returns Absolute path to project root, or null if not found\n */\nexport async function findProjectRoot(startPath: string): Promise<string | null> {\n let current = path.resolve(startPath)\n const episodaRoot = getEpisodaRoot()\n\n // Must be under ~/episoda to be a worktree project\n if (!current.startsWith(episodaRoot)) {\n return null\n }\n\n // Walk up to find .bare directory (support up to 10 levels deep)\n for (let i = 0; i < 10; i++) {\n const bareDir = path.join(current, '.bare')\n const episodaDir = path.join(current, '.episoda')\n\n if (fs.existsSync(bareDir) && fs.existsSync(episodaDir)) {\n // Verify it's a valid worktree project\n if (await isWorktreeProject(current)) {\n return current\n }\n }\n\n // Move up one directory\n const parent = path.dirname(current)\n if (parent === current) {\n // Reached filesystem root\n break\n }\n current = parent\n }\n\n return null\n}\n","/**\n * EP956: Worktree Path Resolution Utilities\n * EP960: Added server-synced EPISODA_ROOT support\n *\n * Provides functions to resolve worktree paths for modules.\n * Worktrees follow the structure: ~/episoda/{workspace_slug}/{project_slug}/{module_uid}/\n */\n\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport { loadConfig, type EpisodaConfig } from '@episoda/core'\nimport { fetchEpisodaRoot } from '../api/machine-settings'\n\n/**\n * Information about a module's worktree\n */\nexport interface WorktreeInfo {\n /** Full path to worktree directory (e.g., ~/episoda/acme/website/EP956) */\n path: string\n /** Whether the worktree directory exists on disk */\n exists: boolean\n /** Module UID this worktree is for */\n moduleUid: string\n}\n\n/**\n * Get the episoda root directory where all worktrees are stored.\n * Default: ~/episoda\n * Override: Set EPISODA_ROOT environment variable\n *\n * Note: This is the synchronous version for backward compatibility.\n * Use getEpisodaRootAsync() to also check server-synced settings.\n */\nexport function getEpisodaRoot(): string {\n return process.env.EPISODA_ROOT || path.join(os.homedir(), 'episoda')\n}\n\n/**\n * EP960: Get the episoda root directory with server-synced settings.\n *\n * Priority order:\n * 1. EPISODA_ROOT environment variable (highest - allows local override)\n * 2. Server-synced value from local_machine.episoda_root\n * 3. Default: 'episoda' (~/episoda)\n *\n * @param config - Optional EpisodaConfig, will load from file if not provided\n * @returns Absolute path to episoda root directory\n */\nexport async function getEpisodaRootAsync(config?: EpisodaConfig): Promise<string> {\n // 1. Environment variable override (highest priority)\n if (process.env.EPISODA_ROOT) {\n return process.env.EPISODA_ROOT\n }\n\n // 2. Try to get server-synced value\n const cfg = config || await loadConfig()\n if (cfg?.device_id && cfg?.access_token) {\n try {\n const serverRoot = await fetchEpisodaRoot(cfg)\n if (serverRoot && serverRoot !== 'episoda') {\n // Server has a custom value - use it (relative to home)\n return path.join(os.homedir(), serverRoot)\n }\n } catch {\n // Server fetch failed - fall through to default\n }\n }\n\n // 3. Default\n return path.join(os.homedir(), 'episoda')\n}\n\n/**\n * Get worktree info for a module given explicit slugs.\n * Synchronous version - uses local EPISODA_ROOT only.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @param workspaceSlug - Workspace slug (e.g., \"acme\")\n * @param projectSlug - Project slug (e.g., \"website\")\n * @returns WorktreeInfo with path and existence status\n */\nexport function getWorktreeInfo(\n moduleUid: string,\n workspaceSlug: string,\n projectSlug: string\n): WorktreeInfo {\n const root = getEpisodaRoot()\n const worktreePath = path.join(root, workspaceSlug, projectSlug, moduleUid)\n\n return {\n path: worktreePath,\n exists: fs.existsSync(worktreePath),\n moduleUid\n }\n}\n\n/**\n * EP960: Get worktree info with server-synced EPISODA_ROOT.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @param workspaceSlug - Workspace slug (e.g., \"acme\")\n * @param projectSlug - Project slug (e.g., \"website\")\n * @param config - Optional EpisodaConfig, will load from file if not provided\n * @returns WorktreeInfo with path and existence status\n */\nexport async function getWorktreeInfoAsync(\n moduleUid: string,\n workspaceSlug: string,\n projectSlug: string,\n config?: EpisodaConfig\n): Promise<WorktreeInfo> {\n const root = await getEpisodaRootAsync(config)\n const worktreePath = path.join(root, workspaceSlug, projectSlug, moduleUid)\n\n return {\n path: worktreePath,\n exists: fs.existsSync(worktreePath),\n moduleUid\n }\n}\n\n/**\n * Get worktree info for a module using slugs from the global config.\n * Loads workspace_slug and project_slug from ~/.episoda/config.json.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @returns WorktreeInfo or null if config is missing required slugs\n */\nexport async function getWorktreeInfoForModule(\n moduleUid: string\n): Promise<WorktreeInfo | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n console.warn('[Worktree] Missing workspace_slug or project_slug in config')\n return null\n }\n\n return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug)\n}\n\n/**\n * Get the bare repo path for the current project.\n * Structure: ~/episoda/{workspace_slug}/{project_slug}/.bare/\n * Synchronous version - uses local EPISODA_ROOT only.\n *\n * @returns Path to .bare directory or null if config missing\n */\nexport async function getBareRepoPath(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n return path.join(\n getEpisodaRoot(),\n config.workspace_slug,\n config.project_slug,\n '.bare'\n )\n}\n\n/**\n * EP960: Get the bare repo path with server-synced EPISODA_ROOT.\n * Structure: {episoda_root}/{workspace_slug}/{project_slug}/.bare/\n *\n * @returns Path to .bare directory or null if config missing\n */\nexport async function getBareRepoPathAsync(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n const root = await getEpisodaRootAsync(config)\n return path.join(\n root,\n config.workspace_slug,\n config.project_slug,\n '.bare'\n )\n}\n\n/**\n * Get the project root path (parent of .bare and all worktrees).\n * Structure: ~/episoda/{workspace_slug}/{project_slug}/\n * Synchronous version - uses local EPISODA_ROOT only.\n *\n * @returns Path to project root or null if config missing\n */\nexport async function getProjectRootPath(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n return path.join(\n getEpisodaRoot(),\n config.workspace_slug,\n config.project_slug\n )\n}\n\n/**\n * EP960: Get the project root path with server-synced EPISODA_ROOT.\n * Structure: {episoda_root}/{workspace_slug}/{project_slug}/\n *\n * @returns Path to project root or null if config missing\n */\nexport async function getProjectRootPathAsync(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n const root = await getEpisodaRootAsync(config)\n return path.join(\n root,\n config.workspace_slug,\n config.project_slug\n )\n}\n","/**\n * EP956: In-Memory Port Allocator\n *\n * Allocates unique ports to modules for dev servers.\n * Ports are stored in memory only - no database persistence.\n *\n * Port Range: 3100-3199 (100 concurrent modules max)\n * Port 3000 is reserved for legacy/main app usage.\n */\n\nconst PORT_RANGE_START = 3100\nconst PORT_RANGE_END = 3199\nconst PORT_WARNING_THRESHOLD = 80 // Warn when 80% of ports are used\n\n/** In-memory port assignments: moduleUid -> port */\nconst portAssignments = new Map<string, number>()\n\n/**\n * Allocate a port for a module.\n * Idempotent: returns existing assignment if already allocated.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @returns Allocated port number\n * @throws Error if all 100 ports are exhausted\n */\nexport function allocatePort(moduleUid: string): number {\n // Return existing assignment (idempotent)\n const existing = portAssignments.get(moduleUid)\n if (existing) {\n return existing\n }\n\n // Find used ports\n const usedPorts = new Set(portAssignments.values())\n\n // Warn if approaching limit\n if (usedPorts.size >= PORT_WARNING_THRESHOLD) {\n console.warn(\n `[PortAllocator] Warning: ${usedPorts.size}/${PORT_RANGE_END - PORT_RANGE_START + 1} ports allocated`\n )\n }\n\n // Find first available port\n for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {\n if (!usedPorts.has(port)) {\n portAssignments.set(moduleUid, port)\n console.log(`[PortAllocator] Assigned port ${port} to ${moduleUid}`)\n return port\n }\n }\n\n throw new Error(\n `No available ports in range ${PORT_RANGE_START}-${PORT_RANGE_END}. ` +\n `${portAssignments.size} modules are using all available ports.`\n )\n}\n\n/**\n * Release a port assignment when a module is done.\n * Frees the port for reuse by another module.\n *\n * @param moduleUid - Module UID to release\n */\nexport function releasePort(moduleUid: string): void {\n const port = portAssignments.get(moduleUid)\n if (port) {\n portAssignments.delete(moduleUid)\n console.log(`[PortAllocator] Released port ${port} from ${moduleUid}`)\n }\n}\n\n/**\n * Get the currently assigned port for a module.\n *\n * @param moduleUid - Module UID to check\n * @returns Assigned port or null if not allocated\n */\nexport function getAssignedPort(moduleUid: string): number | null {\n return portAssignments.get(moduleUid) || null\n}\n\n/**\n * Get the number of currently allocated ports.\n * Useful for monitoring and debugging.\n *\n * @returns Number of allocated ports\n */\nexport function getPortUsageCount(): number {\n return portAssignments.size\n}\n\n/**\n * Get all current port assignments.\n * Returns a copy to prevent external modification.\n *\n * @returns Map of moduleUid -> port\n */\nexport function getAllPortAssignments(): Map<string, number> {\n return new Map(portAssignments)\n}\n\n/**\n * Clear all port assignments.\n * Used for testing and cleanup.\n */\nexport function clearAllPorts(): void {\n const count = portAssignments.size\n portAssignments.clear()\n if (count > 0) {\n console.log(`[PortAllocator] Cleared ${count} port assignments`)\n }\n}\n","/**\n * Framework Detection\n *\n * Detects the framework/stack based on project files and suggests\n * appropriate dev server command.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nexport interface FrameworkDetection {\n framework: string\n command: string[]\n confidence: 'high' | 'medium' | 'low'\n detectedFrom: string\n}\n\n/**\n * EP986: Install command detection result\n */\nexport interface InstallCommand {\n command: string[]\n description: string\n detectedFrom: string\n}\n\n/**\n * Detect framework from project files\n * @param cwd - Working directory to search (defaults to process.cwd())\n * @returns Detection result or null if no framework detected\n */\nexport async function detectFramework(cwd: string = process.cwd()): Promise<FrameworkDetection | null> {\n // Check for package.json (Node.js projects)\n const packageJsonPath = path.join(cwd, 'package.json')\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))\n\n // Check scripts first\n const scripts = packageJson.scripts || {}\n\n // Next.js detection\n if (packageJson.dependencies?.next || packageJson.devDependencies?.next) {\n if (scripts.dev) {\n return {\n framework: 'Next.js',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (next dependency + dev script)'\n }\n }\n return {\n framework: 'Next.js',\n command: ['npx', 'next', 'dev'],\n confidence: 'medium',\n detectedFrom: 'package.json (next dependency)'\n }\n }\n\n // React (Create React App, Vite, etc.)\n if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {\n if (scripts.dev) {\n return {\n framework: 'React',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (react + dev script)'\n }\n }\n if (scripts.start) {\n return {\n framework: 'React',\n command: ['npm', 'start'],\n confidence: 'high',\n detectedFrom: 'package.json (react + start script)'\n }\n }\n }\n\n // Express\n if (packageJson.dependencies?.express) {\n if (scripts.dev) {\n return {\n framework: 'Express',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (express + dev script)'\n }\n }\n if (scripts.start) {\n return {\n framework: 'Express',\n command: ['npm', 'start'],\n confidence: 'medium',\n detectedFrom: 'package.json (express + start script)'\n }\n }\n }\n\n // Vue\n if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {\n if (scripts.dev) {\n return {\n framework: 'Vue',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (vue + dev script)'\n }\n }\n if (scripts.serve) {\n return {\n framework: 'Vue',\n command: ['npm', 'run', 'serve'],\n confidence: 'high',\n detectedFrom: 'package.json (vue + serve script)'\n }\n }\n }\n\n // Generic Node.js (fallback)\n if (scripts.dev) {\n return {\n framework: 'Node.js',\n command: ['npm', 'run', 'dev'],\n confidence: 'medium',\n detectedFrom: 'package.json (dev script)'\n }\n }\n if (scripts.start) {\n return {\n framework: 'Node.js',\n command: ['npm', 'start'],\n confidence: 'low',\n detectedFrom: 'package.json (start script)'\n }\n }\n }\n\n // Check for requirements.txt (Python projects)\n const requirementsPath = path.join(cwd, 'requirements.txt')\n if (fs.existsSync(requirementsPath)) {\n const requirements = fs.readFileSync(requirementsPath, 'utf-8')\n\n // Django\n if (requirements.includes('Django') || requirements.includes('django')) {\n const managePy = path.join(cwd, 'manage.py')\n if (fs.existsSync(managePy)) {\n return {\n framework: 'Django',\n command: ['python', 'manage.py', 'runserver'],\n confidence: 'high',\n detectedFrom: 'requirements.txt (Django) + manage.py'\n }\n }\n return {\n framework: 'Django',\n command: ['python', 'manage.py', 'runserver'],\n confidence: 'medium',\n detectedFrom: 'requirements.txt (Django)'\n }\n }\n\n // Flask\n if (requirements.includes('Flask') || requirements.includes('flask')) {\n // Look for common Flask app files\n const appFiles = ['app.py', 'application.py', 'wsgi.py']\n for (const file of appFiles) {\n if (fs.existsSync(path.join(cwd, file))) {\n return {\n framework: 'Flask',\n command: ['flask', 'run'],\n confidence: 'high',\n detectedFrom: `requirements.txt (Flask) + ${file}`\n }\n }\n }\n return {\n framework: 'Flask',\n command: ['flask', 'run'],\n confidence: 'medium',\n detectedFrom: 'requirements.txt (Flask)'\n }\n }\n\n // FastAPI / Uvicorn\n if (requirements.includes('fastapi') || requirements.includes('uvicorn')) {\n return {\n framework: 'FastAPI',\n command: ['uvicorn', 'main:app', '--reload'],\n confidence: 'medium',\n detectedFrom: 'requirements.txt (fastapi/uvicorn)'\n }\n }\n }\n\n // Check for Gemfile (Ruby projects)\n const gemfilePath = path.join(cwd, 'Gemfile')\n if (fs.existsSync(gemfilePath)) {\n const gemfile = fs.readFileSync(gemfilePath, 'utf-8')\n\n // Rails\n if (gemfile.includes('rails')) {\n return {\n framework: 'Rails',\n command: ['rails', 'server'],\n confidence: 'high',\n detectedFrom: 'Gemfile (rails)'\n }\n }\n\n // Sinatra\n if (gemfile.includes('sinatra')) {\n return {\n framework: 'Sinatra',\n command: ['ruby', 'app.rb'],\n confidence: 'medium',\n detectedFrom: 'Gemfile (sinatra)'\n }\n }\n }\n\n // Check for go.mod (Go projects)\n const goModPath = path.join(cwd, 'go.mod')\n if (fs.existsSync(goModPath)) {\n return {\n framework: 'Go',\n command: ['go', 'run', '.'],\n confidence: 'medium',\n detectedFrom: 'go.mod'\n }\n }\n\n // Check for Cargo.toml (Rust projects)\n const cargoTomlPath = path.join(cwd, 'Cargo.toml')\n if (fs.existsSync(cargoTomlPath)) {\n return {\n framework: 'Rust',\n command: ['cargo', 'run'],\n confidence: 'medium',\n detectedFrom: 'Cargo.toml'\n }\n }\n\n // No framework detected\n return null\n}\n\n/**\n * EP986: Detect and return the appropriate dependency install command\n *\n * Detection priority (lock files first for exact versions):\n * 1. bun.lockb → bun install\n * 2. pnpm-lock.yaml → pnpm install\n * 3. yarn.lock → yarn install\n * 4. package-lock.json → npm ci (exact versions)\n * 5. package.json → npm install (fallback)\n * 6. Pipfile.lock → pipenv install\n * 7. poetry.lock → poetry install\n * 8. requirements.txt → pip install -r requirements.txt\n * 9. Gemfile.lock / Gemfile → bundle install\n * 10. go.sum / go.mod → go mod download\n * 11. Cargo.lock / Cargo.toml → cargo build\n *\n * @param cwd - Working directory to search\n * @returns Install command info or null if no package manager detected\n */\nexport function getInstallCommand(cwd: string): InstallCommand | null {\n // Node.js - check lock files first for specific package manager\n // Bun first (fastest runtime)\n if (fs.existsSync(path.join(cwd, 'bun.lockb'))) {\n return {\n command: ['bun', 'install'],\n description: 'Installing dependencies with bun',\n detectedFrom: 'bun.lockb'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {\n return {\n command: ['pnpm', 'install'],\n description: 'Installing dependencies with pnpm',\n detectedFrom: 'pnpm-lock.yaml'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {\n return {\n command: ['yarn', 'install'],\n description: 'Installing dependencies with yarn',\n detectedFrom: 'yarn.lock'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'package-lock.json'))) {\n return {\n command: ['npm', 'ci'],\n description: 'Installing dependencies with npm ci',\n detectedFrom: 'package-lock.json'\n }\n }\n\n // Fallback to npm install if only package.json exists\n if (fs.existsSync(path.join(cwd, 'package.json'))) {\n return {\n command: ['npm', 'install'],\n description: 'Installing dependencies with npm',\n detectedFrom: 'package.json'\n }\n }\n\n // Python - check lock files first\n if (fs.existsSync(path.join(cwd, 'Pipfile.lock')) || fs.existsSync(path.join(cwd, 'Pipfile'))) {\n return {\n command: ['pipenv', 'install'],\n description: 'Installing dependencies with pipenv',\n detectedFrom: fs.existsSync(path.join(cwd, 'Pipfile.lock')) ? 'Pipfile.lock' : 'Pipfile'\n }\n }\n\n // Poetry detection: check lock file first, then pyproject.toml\n if (fs.existsSync(path.join(cwd, 'poetry.lock'))) {\n return {\n command: ['poetry', 'install'],\n description: 'Installing dependencies with poetry',\n detectedFrom: 'poetry.lock'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'pyproject.toml'))) {\n const pyprojectPath = path.join(cwd, 'pyproject.toml')\n const content = fs.readFileSync(pyprojectPath, 'utf-8')\n if (content.includes('[tool.poetry]')) {\n return {\n command: ['poetry', 'install'],\n description: 'Installing dependencies with poetry',\n detectedFrom: 'pyproject.toml'\n }\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'requirements.txt'))) {\n return {\n command: ['pip', 'install', '-r', 'requirements.txt'],\n description: 'Installing dependencies with pip',\n detectedFrom: 'requirements.txt'\n }\n }\n\n // Ruby\n if (fs.existsSync(path.join(cwd, 'Gemfile.lock')) || fs.existsSync(path.join(cwd, 'Gemfile'))) {\n return {\n command: ['bundle', 'install'],\n description: 'Installing dependencies with bundler',\n detectedFrom: fs.existsSync(path.join(cwd, 'Gemfile.lock')) ? 'Gemfile.lock' : 'Gemfile'\n }\n }\n\n // Go\n if (fs.existsSync(path.join(cwd, 'go.sum')) || fs.existsSync(path.join(cwd, 'go.mod'))) {\n return {\n command: ['go', 'mod', 'download'],\n description: 'Downloading Go modules',\n detectedFrom: fs.existsSync(path.join(cwd, 'go.sum')) ? 'go.sum' : 'go.mod'\n }\n }\n\n // Rust\n if (fs.existsSync(path.join(cwd, 'Cargo.lock')) || fs.existsSync(path.join(cwd, 'Cargo.toml'))) {\n return {\n command: ['cargo', 'build'],\n description: 'Building Rust project (downloads dependencies)',\n detectedFrom: fs.existsSync(path.join(cwd, 'Cargo.lock')) ? 'Cargo.lock' : 'Cargo.toml'\n }\n }\n\n // No package manager detected\n return null\n}\n\n/**\n * Suggest command based on user-provided command or auto-detection\n * @param providedCommand - Command provided by user (e.g., [\"npm\", \"run\", \"dev\"])\n * @param cwd - Working directory\n * @returns Final command to use\n */\nexport async function resolveDevCommand(\n providedCommand: string[] | null,\n cwd: string = process.cwd()\n): Promise<{ command: string[]; detection: FrameworkDetection | null }> {\n // If user provided command, use it as-is\n if (providedCommand && providedCommand.length > 0) {\n return { command: providedCommand, detection: null }\n }\n\n // Auto-detect framework\n const detection = await detectFramework(cwd)\n if (detection) {\n return { command: detection.command, detection }\n }\n\n // No detection - default to npm run dev\n return {\n command: ['npm', 'run', 'dev'],\n detection: {\n framework: 'Unknown',\n command: ['npm', 'run', 'dev'],\n confidence: 'low',\n detectedFrom: 'default fallback'\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBA,IAAAA,SAAA,qBAAA;AA0CA,IAAAA,SAAA,wBAAA;AAiBA,IAAAA,SAAA,oBAAA;AA6BA,IAAAA,SAAA,eAAA;AAxFA,aAAgB,mBAAmB,YAAkB;AACnD,UAAI,CAAC,cAAc,WAAW,KAAI,EAAG,WAAW,GAAG;AACjD,eAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;MAC/C;AAGA,YAAM,kBAAkB;QACtB;;QACA;;QACA;;QACA;;QACA;;QACA;;QACA;;QACA;;QACA;;QACA;;;AAGF,iBAAW,WAAW,iBAAiB;AACrC,YAAI,QAAQ,KAAK,UAAU,GAAG;AAC5B,iBAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;QAC/C;MACF;AAGA,UAAI,eAAe,KAAK;AACtB,eAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;MAC/C;AAGA,UAAI,WAAW,SAAS,KAAK;AAC3B,eAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;MAC/C;AAEA,aAAO,EAAE,OAAO,KAAI;IACtB;AAMA,aAAgB,sBAAsB,SAAe;AACnD,UAAI,CAAC,WAAW,QAAQ,KAAI,EAAG,WAAW,GAAG;AAC3C,eAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;MAC/C;AAGA,UAAI,QAAQ,SAAS,KAAO;AAC1B,eAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;MAC/C;AAEA,aAAO,EAAE,OAAO,KAAI;IACtB;AAMA,aAAgB,kBAAkB,OAAe;AAC/C,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,eAAO,EAAE,OAAO,KAAI;MACtB;AAEA,iBAAW,QAAQ,OAAO;AAExB,YAAI,KAAK,SAAS,IAAI,GAAG;AACvB,iBAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;QAC/C;AAGA,YAAI,kBAAkB,KAAK,IAAI,GAAG;AAChC,iBAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;QAC/C;AAGA,YAAI,KAAK,KAAI,EAAG,WAAW,GAAG;AAC5B,iBAAO,EAAE,OAAO,OAAO,OAAO,gBAAe;QAC/C;MACF;AAEA,aAAO,EAAE,OAAO,KAAI;IACtB;AAMA,aAAgB,aAAa,MAAc;AACzC,aAAO,KAAK,IAAI,SAAM;AAEpB,eAAO,IAAI,QAAQ,OAAO,EAAE;MAC9B,CAAC;IACH;;;;;;;;;ACnGA,IAAAC,SAAA,iBAAA;AA2CA,IAAAA,SAAA,sBAAA;AA4BA,IAAAA,SAAA,gBAAA;AAgFA,IAAAA,SAAA,oBAAA;AAyBA,IAAAA,SAAA,iBAAA;AAQA,IAAAA,SAAA,sBAAA;AAxLA,aAAgB,eAAe,QAAc;AAK3C,YAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,YAAM,mBAA6B,CAAA;AACnC,UAAI;AAEJ,iBAAW,QAAQ,OAAO;AAExB,YAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,gBAAM,aAAa,KAAK,UAAU,CAAC;AAEnC,gBAAM,cAAc,WAAW,MAAM,YAAY;AACjD,cAAI,eAAe,YAAY,CAAC,GAAG;AACjC,4BAAgB,YAAY,CAAC,MAAM,SAAS,oBAAoB,YAAY,CAAC;UAC/E;AACA;QACF;AAKA,YAAI,KAAK,UAAU,GAAG;AACpB,gBAAM,SAAS,KAAK,UAAU,GAAG,CAAC;AAClC,gBAAM,WAAW,KAAK,UAAU,CAAC,EAAE,KAAI;AAGvC,cAAI,OAAO,KAAI,EAAG,SAAS,KAAK,SAAS,SAAS,GAAG;AACnD,6BAAiB,KAAK,QAAQ;UAChC;QACF;MACF;AAEA,YAAM,UAAU,iBAAiB,WAAW;AAE5C,aAAO,EAAE,kBAAkB,SAAS,cAAa;IACnD;AAKA,aAAgB,oBAAoB,QAAc;AAChD,YAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,YAAM,mBAA6B,CAAA;AAEnC,iBAAW,QAAQ,OAAO;AACxB,cAAM,UAAU,KAAK,KAAI;AAGzB,YAAI,QAAQ,WAAW,UAAU,GAAG;AAElC,gBAAM,QAAQ,QAAQ,MAAM,oBAAoB;AAChD,cAAI,SAAS,MAAM,CAAC,GAAG;AACrB,6BAAiB,KAAK,MAAM,CAAC,CAAC;UAChC;QACF;AAGA,YAAI,QAAQ,WAAW,KAAK,GAAG;AAC7B,2BAAiB,KAAK,QAAQ,UAAU,CAAC,EAAE,KAAI,CAAE;QACnD;MACF;AAEA,aAAO;IACT;AAKA,aAAgB,cAAc,QAAgB,QAAgB,UAAgB;AAC5E,YAAM,iBAAiB,GAAG,MAAM;EAAK,MAAM,GAAG,YAAW;AAGzD,UAAI,eAAe,SAAS,wBAAwB,KAChD,eAAe,SAAS,yBAAyB,GAAG;AACtD,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,sBAAsB,KAC9C,eAAe,SAAS,gBAAgB,GAAG;AAC7C,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,UAAU,KAClC,eAAe,SAAS,gBAAgB,KACxC,aAAa,KAAK,eAAe,SAAS,wBAAwB,GAAG;AACvE,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,4BAA4B,KACpD,eAAe,SAAS,sBAAsB,KAC9C,eAAe,SAAS,iBAAiB,KAAK,eAAe,SAAS,4BAA4B,GAAG;AACvG,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,uBAAuB,KAC/C,eAAe,SAAS,yBAAyB,KACjD,eAAe,SAAS,mBAAmB,KAC3C,eAAe,SAAS,yBAAyB,KACjD,eAAe,SAAS,8BAA8B,GAAG;AAC3D,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,wBAAwB,KAChD,eAAe,SAAS,mBAAmB,KAC3C,eAAe,SAAS,wBAAwB,KAChD,eAAe,SAAS,sBAAsB,KAC9C,eAAe,SAAS,4BAA4B,GAAG;AACzD,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,wBAAwB,KAChD,eAAe,SAAS,QAAQ,KAAK,eAAe,SAAS,WAAW,KACxE,eAAe,SAAS,UAAU,KAAK,eAAe,SAAS,eAAe,GAAG;AACnF,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,gBAAgB,KACxC,eAAe,SAAS,gBAAgB,KAAK,eAAe,SAAS,gBAAgB,GAAG;AAC1F,eAAO;MACT;AAGA,UAAI,eAAe,SAAS,eAAe,KACvC,eAAe,SAAS,gBAAgB,KACxC,eAAe,SAAS,UAAU,KAAK,eAAe,SAAS,kBAAkB,KACjF,eAAe,SAAS,uBAAuB,GAAG;AACpD,eAAO;MACT;AAGA,UAAI,aAAa,OAAO,aAAa,KAAK;AACxC,eAAO;MACT;AAGA,aAAO;IACT;AAKA,aAAgB,kBAAkB,QAAc;AAC9C,YAAM,QAAQ,OAAO,MAAM,IAAI;AAE/B,iBAAW,QAAQ,OAAO;AACxB,cAAM,UAAU,KAAK,KAAI;AAGzB,YAAI,QAAQ,QAAQ,MAAM,sCAAsC;AAChE,YAAI,SAAS,MAAM,CAAC,GAAG;AACrB,iBAAO,MAAM,CAAC;QAChB;AAGA,gBAAQ,QAAQ,MAAM,gBAAgB;AACtC,YAAI,SAAS,MAAM,CAAC,GAAG;AACrB,iBAAO,MAAM,CAAC;QAChB;MACF;AAEA,aAAO;IACT;AAKA,aAAgB,eAAe,QAAc;AAC3C,aAAO,OAAO,YAAW,EAAG,SAAS,eAAe,KAC7C,OAAO,YAAW,EAAG,SAAS,kCAAoC;IAC3E;AAKA,aAAgB,oBAAoB,QAAc;AAKhD,YAAM,QAAQ,OAAO,MAAM,IAAI;AAE/B,iBAAW,QAAQ,OAAO;AACxB,cAAM,UAAU,KAAK,KAAI;AAGzB,cAAM,QAAQ,QAAQ,MAAM,yDAAyD;AACrF,YAAI,OAAO;AACT,iBAAO;YACL,aAAa;YACb,cAAc,MAAM,CAAC;YACrB,QAAQ,MAAM,CAAC,KAAK;;QAExB;AAGA,cAAM,gBAAgB,QAAQ,MAAM,oDAAoD;AACxF,YAAI,eAAe;AACjB,iBAAO;YACL,aAAa;YACb,QAAQ,cAAc,CAAC;YACvB,cAAc,cAAc,CAAC;;QAEjC;MACF;AAEA,aAAO,EAAE,aAAa,MAAK;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7NA,QAAA,kBAAA,QAAA,eAAA;AACA,QAAA,SAAA,QAAA,MAAA;AAEA,QAAA,kBAAA;AAMA,QAAA,eAAA;AASA,QAAMC,cAAY,GAAA,OAAA,WAAU,gBAAA,IAAI;AAWhC,QAAaC,eAAb,MAAwB;;;;;;;MAOtB,MAAM,QACJ,SACA,SAA0B;AAE1B,YAAI;AAEF,gBAAM,eAAe,MAAM,KAAK,qBAAoB;AACpD,cAAI,CAAC,cAAc;AACjB,mBAAO;cACL,SAAS;cACT,OAAO;;UAEX;AAGA,gBAAM,MAAM,SAAS,OAAO,QAAQ,IAAG;AAGvC,gBAAM,YAAY,MAAM,KAAK,gBAAgB,GAAG;AAChD,cAAI,CAAC,WAAW;AACd,mBAAO;cACL,SAAS;cACT,OAAO;;UAEX;AAGA,kBAAQ,QAAQ,QAAQ;YACtB,KAAK;AACH,qBAAO,MAAM,KAAK,gBAAgB,SAAS,KAAK,OAAO;YACzD,KAAK;AACH,qBAAO,MAAM,KAAK,oBAAoB,SAAS,KAAK,OAAO;YAC7D,KAAK;AACH,qBAAO,MAAM,KAAK,cAAc,SAAS,KAAK,OAAO;YACvD,KAAK;AACH,qBAAO,MAAM,KAAK,YAAY,SAAS,KAAK,OAAO;YACrD,KAAK;AACH,qBAAO,MAAM,KAAK,cAAc,KAAK,OAAO;YAC9C,KAAK;AACH,qBAAO,MAAM,KAAK,YAAY,SAAS,KAAK,OAAO;YACrD,KAAK;AACH,qBAAO,MAAM,KAAK,oBAAoB,SAAS,KAAK,OAAO;;YAE7D,KAAK;AACH,qBAAO,MAAM,KAAK,oBAAoB,SAAS,KAAK,OAAO;YAC7D,KAAK;AACH,qBAAO,MAAM,KAAK,wBAAwB,SAAS,KAAK,OAAO;;YAEjE,KAAK;AACH,qBAAO,MAAM,KAAK,0BAA0B,SAAS,KAAK,OAAO;;YAEnE,KAAK;AACH,qBAAO,MAAM,KAAK,uBAAuB,KAAK,OAAO;;YAEvD,KAAK;AACH,qBAAO,MAAM,KAAK,kBAAkB,SAAS,KAAK,OAAO;;YAE3D,KAAK;AACH,qBAAO,MAAM,KAAK,aAAa,SAAS,KAAK,OAAO;YACtD,KAAK;AACH,qBAAO,MAAM,KAAK,aAAa,SAAS,KAAK,OAAO;YACtD,KAAK;AACH,qBAAO,MAAM,KAAK,aAAa,SAAS,KAAK,OAAO;YACtD,KAAK;AACH,qBAAO,MAAM,KAAK,kBAAkB,SAAS,KAAK,OAAO;YAC3D,KAAK;AACH,qBAAO,MAAM,KAAK,aAAa,SAAS,KAAK,OAAO;YACtD,KAAK;AACH,qBAAO,MAAM,KAAK,WAAW,SAAS,KAAK,OAAO;YACpD,KAAK;AACH,qBAAO,MAAM,KAAK,aAAa,SAAS,KAAK,OAAO;;YAEtD,KAAK;AACH,qBAAO,MAAM,KAAK,oBAAoB,SAAS,KAAK,OAAO;YAC7D,KAAK;AACH,qBAAO,MAAM,KAAK,0BAA0B,KAAK,OAAO;;YAE1D,KAAK;AACH,qBAAO,MAAM,KAAK,kBAAkB,SAAS,KAAK,OAAO;YAC3D,KAAK;AACH,qBAAO,MAAM,KAAK,gBAAgB,KAAK,OAAO;YAChD,KAAK;AACH,qBAAO,MAAM,KAAK,oBAAoB,SAAS,KAAK,OAAO;YAC7D,KAAK;AACH,qBAAO,MAAM,KAAK,mBAAmB,KAAK,OAAO;YACnD,KAAK;AACH,qBAAO,MAAM,KAAK,sBAAsB,KAAK,OAAO;YACtD,KAAK;AACH,qBAAO,MAAM,KAAK,oBAAoB,KAAK,OAAO;;YAEpD,KAAK;AACH,qBAAO,MAAM,KAAK,mBAAmB,SAAS,KAAK,OAAO;YAC5D,KAAK;AACH,qBAAO,MAAM,KAAK,sBAAsB,SAAS,KAAK,OAAO;YAC/D,KAAK;AACH,qBAAO,MAAM,KAAK,oBAAoB,KAAK,OAAO;YACpD,KAAK;AACH,qBAAO,MAAM,KAAK,qBAAqB,KAAK,OAAO;YACrD,KAAK;AACH,qBAAO,MAAM,KAAK,iBAAiB,SAAS,OAAO;YACrD,KAAK;AACH,qBAAO,MAAM,KAAK,mBAAmB,KAAK,OAAO;YACnD;AACE,qBAAO;gBACL,SAAS;gBACT,OAAO;gBACP,QAAQ;;UAEd;QACF,SAAS,OAAO;AACd,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,iBAAiB,QAAQ,MAAM,UAAU;;QAErD;MACF;;;;MAKQ,MAAM,gBACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAGA,cAAM,eAAe,MAAM,KAAK,cAAc,KAAK,OAAO;AAC1D,YAAI,aAAa,WAAW,aAAa,SAAS,kBAAkB,QAAQ;AAC1E,iBAAO;YACL,SAAS;YACT,OAAO;YACP,SAAS;cACP,kBAAkB,aAAa,QAAQ;;;QAG7C;AAGA,cAAM,OAAO,CAAC,UAAU;AACxB,YAAI,QAAQ,QAAQ;AAClB,eAAK,KAAK,IAAI;QAChB;AACA,aAAK,KAAK,QAAQ,MAAM;AAExB,eAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;MACpD;;;;MAKQ,MAAM,oBACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAGA,YAAI,QAAQ,MAAM;AAChB,gBAAM,kBAAiB,GAAA,gBAAA,oBAAmB,QAAQ,IAAI;AACtD,cAAI,CAAC,eAAe,OAAO;AACzB,mBAAO;cACL,SAAS;cACT,OAAO,eAAe,SAAS;;UAEnC;QACF;AAIA,cAAM,OAAO,CAAC,YAAY,MAAM,QAAQ,MAAM;AAC9C,YAAI,QAAQ,MAAM;AAChB,eAAK,KAAK,QAAQ,IAAI;QACxB;AAEA,eAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;MACpD;;;;MAKQ,MAAM,cACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,uBAAsB,QAAQ,OAAO;AACxD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAGA,YAAI,QAAQ,OAAO;AACjB,gBAAM,kBAAiB,GAAA,gBAAA,mBAAkB,QAAQ,KAAK;AACtD,cAAI,CAAC,eAAe,OAAO;AACzB,mBAAO;cACL,SAAS;cACT,OAAO,eAAe,SAAS;;UAEnC;AAGA,qBAAW,QAAQ,QAAQ,OAAO;AAChC,kBAAM,YAAY,MAAM,KAAK,cAAc,CAAC,OAAO,IAAI,GAAG,KAAK,OAAO;AACtE,gBAAI,CAAC,UAAU,SAAS;AACtB,qBAAO;YACT;UACF;QACF,OAAO;AAEL,gBAAM,YAAY,MAAM,KAAK,cAAc,CAAC,OAAO,IAAI,GAAG,KAAK,OAAO;AACtE,cAAI,CAAC,UAAU,SAAS;AACtB,mBAAO;UACT;QACF;AAGA,cAAM,OAAO,CAAC,UAAU,MAAM,QAAQ,OAAO;AAC7C,eAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;MACpD;;;;;MAMQ,MAAM,YACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAGA,cAAM,OAAO,CAAC,MAAM;AAEpB,YAAI,QAAQ,OAAO;AACjB,eAAK,KAAK,SAAS;QACrB;AACA,YAAI,QAAQ,aAAa;AACvB,eAAK,KAAK,MAAM,UAAU,QAAQ,MAAM;QAC1C,OAAO;AACL,eAAK,KAAK,UAAU,QAAQ,MAAM;QACpC;AAGA,cAAM,MAAM,EAAE,GAAG,QAAQ,IAAG;AAC5B,YAAI,SAAS,aAAa;AACxB,cAAI,cAAc;AAClB,cAAI,eAAe;AACnB,cAAI,eAAe,QAAQ;QAC7B;AAEA,eAAO,MAAM,KAAK,cAAc,MAAM,KAAK,EAAE,GAAG,SAAS,IAAG,CAAE;MAChE;;;;MAKQ,MAAM,cACZ,KACA,SAA0B;AAG1B,YAAI;AACF,gBAAM,eAAe,MAAMD,WAAU,sCAAsC,EAAE,KAAK,SAAS,IAAI,CAAE;AACjG,cAAI,aAAa,OAAO,KAAI,MAAO,QAAQ;AAEzC,kBAAM,aAAa,MAAMA,WAAU,iCAAiC,EAAE,KAAK,SAAS,IAAI,CAAE;AAC1F,kBAAM,aAAa,WAAW,OAAO,KAAI;AACzC,mBAAO;cACL,SAAS;cACT,QAAQ,MAAM,UAAU;cACxB,SAAS;gBACP,kBAAkB,CAAA;;gBAClB;gBACA,eAAe;;;UAGrB;QACF,QAAQ;QAER;AAEA,cAAM,SAAS,MAAM,KAAK,cAAc,CAAC,UAAU,eAAe,IAAI,GAAG,KAAK,OAAO;AAErF,YAAI,OAAO,WAAW,OAAO,QAAQ;AACnC,gBAAM,cAAa,GAAA,aAAA,gBAAe,OAAO,MAAM;AAC/C,iBAAO;YACL,SAAS;YACT,QAAQ,OAAO;YACf,SAAS;cACP,kBAAkB,WAAW;cAC7B,YAAY,WAAW;;;QAG7B;AAEA,eAAO;MACT;;;;MAKQ,MAAM,YACZ,SACA,KACA,SAA0B;AAG1B,YAAI,QAAQ,QAAQ;AAClB,gBAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,cAAI,CAAC,WAAW,OAAO;AACrB,mBAAO;cACL,SAAS;cACT,OAAO,WAAW,SAAS;;UAE/B;QACF;AAGA,cAAM,eAAe,MAAM,KAAK,cAAc,KAAK,OAAO;AAC1D,YAAI,aAAa,WAAW,aAAa,SAAS,kBAAkB,QAAQ;AAC1E,iBAAO;YACL,SAAS;YACT,OAAO;YACP,SAAS;cACP,kBAAkB,aAAa,QAAQ;;;QAG7C;AAGA,cAAM,OAAO,CAAC,MAAM;AACpB,YAAI,QAAQ,QAAQ;AAClB,eAAK,KAAK,UAAU,QAAQ,MAAM;QACpC;AAEA,cAAM,SAAS,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;AAG1D,YAAI,CAAC,OAAO,WAAW,OAAO,QAAQ;AACpC,gBAAM,aAAY,GAAA,aAAA,qBAAoB,OAAO,MAAM;AACnD,cAAI,UAAU,SAAS,GAAG;AACxB,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,OAAO;cACf,SAAS;gBACP,kBAAkB;;;UAGxB;QACF;AAEA,eAAO;MACT;;;;MAKQ,MAAM,oBACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAGA,cAAM,OAAO,CAAC,QAAQ;AACtB,aAAK,KAAK,QAAQ,QAAQ,OAAO,IAAI;AACrC,aAAK,KAAK,QAAQ,MAAM;AAExB,eAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;MACpD;;;;;MAMQ,MAAM,oBACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAEA,YAAI;AACF,cAAI,UAAU;AACd,cAAI,WAAW;AAGf,cAAI;AACF,kBAAM,EAAE,QAAQ,cAAa,IAAK,MAAMA,WAAU,qBAAqB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAClH,sBAAU,cAAc,MAAM,IAAI,EAAE,KAAK,UAAO;AAC9C,oBAAM,aAAa,KAAK,QAAQ,WAAW,EAAE,EAAE,KAAI;AACnD,qBAAO,eAAe,QAAQ;YAChC,CAAC;UACH,QAAQ;UAER;AAGA,cAAI;AACF,kBAAM,EAAE,QAAQ,eAAc,IAAK,MAAMA,WACvC,gCAAgC,QAAQ,MAAM,IAC9C,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAE7C,uBAAW,eAAe,KAAI,EAAG,SAAS;UAC5C,QAAQ;UAER;AAEA,gBAAM,eAAe,WAAW;AAEhC,iBAAO;YACL,SAAS;YACT,QAAQ,eAAe,UAAU,QAAQ,MAAM,YAAY,UAAU,QAAQ,MAAM;YACnF,SAAS;cACP,YAAY,QAAQ;cACpB;cACA;cACA;;;QAGN,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,wBACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAEA,cAAM,aAAa,QAAQ,cAAc;AAEzC,YAAI;AAGF,gBAAM,EAAE,OAAM,IAAK,MAAMA,WACvB,qBAAqB,UAAU,IAAI,QAAQ,MAAM,IACjD,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAI7C,gBAAM,gBAAgB,OAAO,KAAI,EAAG,MAAM,IAAI,EAAE,OAAO,UAAQ,KAAK,WAAW,GAAG,CAAC;AACnF,gBAAM,aAAa,cAAc,SAAS;AAE1C,iBAAO;YACL,SAAS;YACT,QAAQ,aACJ,UAAU,QAAQ,MAAM,QAAQ,cAAc,MAAM,qBAAqB,UAAU,KACnF,UAAU,QAAQ,MAAM,4BAA4B,UAAU;YAClE,SAAS;cACP,YAAY,QAAQ;cACpB;;;QAGN,SAAS,OAAY;AAEnB,cAAI;AAEF,kBAAM,EAAE,OAAM,IAAK,MAAMA,WACvB,+BAA+B,UAAU,KAAK,QAAQ,MAAM,IAC5D,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAG7C,kBAAM,cAAc,SAAS,OAAO,KAAI,GAAI,EAAE;AAC9C,kBAAM,aAAa,cAAc;AAEjC,mBAAO;cACL,SAAS;cACT,QAAQ,aACJ,UAAU,QAAQ,MAAM,QAAQ,WAAW,qBAAqB,UAAU,KAC1E,UAAU,QAAQ,MAAM,4BAA4B,UAAU;cAClE,SAAS;gBACP,YAAY,QAAQ;gBACpB;;;UAGN,QAAQ;AAEN,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,MAAM,WAAW,sCAAsC,QAAQ,MAAM;;UAEjF;QACF;MACF;;;;;;;;MAUQ,MAAM,0BACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAM,EAAE,OAAM,IAAK,MAAMA,WACvB,iBACA,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAG7C,gBAAM,SAAS,QAAQ;AACvB,gBAAM,WAAW,OAAO,MAAM,IAAI,EAC/B,IAAI,UAAQ,KAAK,QAAQ,WAAW,EAAE,EAAE,QAAQ,mBAAmB,EAAE,EAAE,KAAI,CAAE,EAC7E,OAAO,YAAU,UAAU,CAAC,OAAO,SAAS,IAAI,CAAC;AAEpD,gBAAM,iBAAiB,SAAS,KAAK,YAAU,OAAO,WAAW,MAAM,CAAC;AAExE,iBAAO;YACL,SAAS;YACT,QAAQ,kBAAkB;YAC1B,SAAS;cACP,YAAY,kBAAkB;cAC9B,cAAc,CAAC,CAAC;;;QAGtB,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;MAEQ,MAAM,uBACZ,KACA,SAA0B;AAE1B,YAAI;AAEF,cAAI,gBAAgB;AACpB,cAAI;AACF,kBAAM,EAAE,OAAM,IAAK,MAAMA,WAAU,6BAA6B,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAC3G,4BAAgB,OAAO,KAAI;UAC7B,SAAS,OAAY;AACnB,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,MAAM,WAAW;;UAE7B;AAGA,cAAI,mBAA6B,CAAA;AACjC,cAAI;AACF,kBAAM,EAAE,OAAM,IAAK,MAAMA,WAAU,0BAA0B,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AACxG,gBAAI,QAAQ;AACV,iCAAmB,OAAO,MAAM,IAAI,EAAE,OAAO,UAAQ,KAAK,KAAI,CAAE,EAAE,IAAI,UAAO;AAC3E,sBAAM,QAAQ,KAAK,KAAI,EAAG,MAAM,KAAK;AACrC,uBAAO,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG;cAChC,CAAC;YACH;UACF,QAAQ;UAER;AAGA,cAAI,eAAwE,CAAA;AAC5E,cAAI,kBAAkB,QAAQ;AAC5B,gBAAI;AACF,oBAAM,EAAE,OAAM,IAAK,MAAMA,WAAU,kDAAkD,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAChI,kBAAI,QAAQ;AACV,+BAAe,OAAO,MAAM,IAAI,EAAE,OAAO,UAAQ,KAAK,KAAI,CAAE,EAAE,IAAI,UAAO;AACvE,wBAAM,CAAC,KAAK,SAAS,MAAM,IAAI,KAAK,MAAM,GAAG;AAC7C,yBAAO;oBACL,KAAK,MAAM,IAAI,UAAU,GAAG,CAAC,IAAI;oBACjC,SAAS,WAAW;oBACpB,QAAQ,UAAU;;gBAEtB,CAAC;cACH;YACF,QAAQ;YAER;UACF;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ,WAAW,aAAa,kBAAkB,iBAAiB,MAAM,eAAe,aAAa,MAAM;YAC3G,SAAS;cACP;cACA;cACA;;;QAGN,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,kBACZ,SACA,KACA,SAA0B;AAG1B,cAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,YAAI,CAAC,WAAW,OAAO;AACrB,iBAAO;YACL,SAAS;YACT,OAAO,WAAW,SAAS;;QAE/B;AAEA,cAAM,QAAQ,QAAQ,SAAS;AAC/B,cAAM,aAAa,QAAQ,cAAc;AAEzC,YAAI;AAEF,cAAI;AACJ,cAAI;AACF,kBAAM,SAAS,MAAMA,WACnB,WAAW,UAAU,MAAM,QAAQ,MAAM,4CAA4C,KAAK,OAC1F,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAE7C,qBAAS,OAAO;UAClB,SAAS,OAAO;AAEd,gBAAI;AACF,oBAAM,SAAS,MAAMA,WACnB,YAAY,QAAQ,MAAM,4CAA4C,KAAK,OAC3E,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAE7C,uBAAS,OAAO;YAClB,SAAS,aAAa;AAEpB,qBAAO;gBACL,SAAS;gBACT,OAAO;gBACP,QAAQ,UAAU,QAAQ,MAAM;;YAEpC;UACF;AAEA,cAAI,CAAC,OAAO,KAAI,GAAI;AAClB,mBAAO;cACL,SAAS;cACT,QAAQ;cACR,SAAS;gBACP,SAAS,CAAA;;;UAGf;AAGA,gBAAM,cAAc,OAAO,KAAI,EAAG,MAAM,IAAI;AAG5C,cAAI,aAA0B,oBAAI,IAAG;AACrC,cAAI;AACF,kBAAM,EAAE,QAAQ,cAAa,IAAK,MAAMA,WACtC,mBAAmB,QAAQ,MAAM,6BAA6B,KAAK,OACnE,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAE7C,yBAAa,IAAI,IAAI,cAAc,KAAI,EAAG,MAAM,IAAI,EAAE,OAAO,OAAO,CAAC;UACvE,QAAQ;UAER;AAEA,gBAAM,UAAU,YAAY,IAAI,CAAC,SAAQ;AACvC,kBAAM,CAAC,KAAK,YAAY,aAAa,MAAM,GAAG,YAAY,IAAI,KAAK,MAAM,GAAG;AAC5E,kBAAM,UAAU,aAAa,KAAK,GAAG;AACrC,kBAAM,WAAW,WAAW,IAAI,GAAG;AAEnC,mBAAO;cACL;cACA;cACA;cACA;cACA;cACA;;UAEJ,CAAC;AAED,iBAAO;YACL,SAAS;YACT,QAAQ,SAAS,QAAQ,MAAM;YAC/B,SAAS;cACP;;;QAGN,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;;;MASQ,MAAM,aACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAM,OAAiB,CAAC,OAAO;AAE/B,kBAAQ,QAAQ,WAAW;YACzB,KAAK;AACH,mBAAK,KAAK,MAAM;AAChB,kBAAI,QAAQ,kBAAkB;AAC5B,qBAAK,KAAK,qBAAqB;cACjC;AACA,kBAAI,QAAQ,SAAS;AACnB,qBAAK,KAAK,MAAM,QAAQ,OAAO;cACjC;AACA;YACF,KAAK;AACH,mBAAK,KAAK,KAAK;AACf;YACF,KAAK;AACH,mBAAK,KAAK,MAAM;AAChB;YACF,KAAK;AACH,mBAAK,KAAK,MAAM;AAChB;UACJ;AAEA,iBAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;QACpD,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,aACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAM,OAAO,CAAC,SAAS,KAAK,QAAQ,IAAI,EAAE;AAC1C,cAAI,QAAQ,QAAQ;AAClB,iBAAK,KAAK,QAAQ,MAAM;UAC1B;AAEA,iBAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;QACpD,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,aACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,cAAI,QAAQ,OAAO;AACjB,mBAAO,MAAM,KAAK,cAAc,CAAC,SAAS,SAAS,GAAG,KAAK,OAAO;UACpE;AAGA,gBAAM,oBAAmB,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AAC1D,cAAI,CAAC,iBAAiB,OAAO;AAC3B,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,iBAAiB,SAAS;;UAEtC;AAEA,gBAAM,OAAO,CAAC,SAAS,QAAQ,MAAM;AAErC,cAAI,QAAQ,aAAa,QAAQ;AAC/B,iBAAK,KAAK,iBAAiB;UAC7B,WAAW,QAAQ,aAAa,UAAU;AACxC,iBAAK,KAAK,0BAA0B;UACtC;AAEA,cAAI,QAAQ,QAAQ;AAClB,iBAAK,KAAK,WAAW;UACvB;AAEA,gBAAM,SAAS,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;AAG1D,cAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,SAAS,UAAU,GAAG;AAC1D,mBAAO,UAAU,OAAO,WAAW,CAAA;AACnC,mBAAO,QAAQ,iBAAiB;UAClC;AAEA,iBAAO;QACT,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,kBACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,cAAI,QAAQ,OAAO;AACjB,mBAAO,MAAM,KAAK,cAAc,CAAC,eAAe,SAAS,GAAG,KAAK,OAAO;UAC1E;AAEA,gBAAM,SAAS,MAAM,KAAK,cAAc,CAAC,eAAe,QAAQ,GAAG,GAAG,KAAK,OAAO;AAGlF,cAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,SAAS,UAAU,GAAG;AAC1D,mBAAO,UAAU,OAAO,WAAW,CAAA;AACnC,mBAAO,QAAQ,sBAAsB;UACvC;AAEA,iBAAO;QACT,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,aACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAM,OAAO,CAAC,OAAO;AAErB,cAAI,QAAQ,OAAO;AACjB,iBAAK,KAAK,IAAI;UAChB;AACA,cAAI,QAAQ,aAAa;AACvB,iBAAK,KAAK,IAAI;UAChB;AAEA,iBAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;QACpD,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,WACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAM,OAAO,CAAC,KAAK;AAEnB,cAAI,QAAQ,KAAK;AACf,iBAAK,KAAK,IAAI;UAChB,WAAW,QAAQ,SAAS,QAAQ,MAAM,SAAS,GAAG;AAEpD,kBAAM,cAAa,GAAA,gBAAA,mBAAkB,QAAQ,KAAK;AAClD,gBAAI,CAAC,WAAW,OAAO;AACrB,qBAAO;gBACL,SAAS;gBACT,OAAO;gBACP,QAAQ,WAAW,SAAS;;YAEhC;AACA,iBAAK,KAAK,GAAG,QAAQ,KAAK;UAC5B,OAAO;AACL,iBAAK,KAAK,IAAI;UAChB;AAEA,iBAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;QACpD,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,aACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAM,OAAO,CAAC,OAAO;AAErB,cAAI,QAAQ,QAAQ;AAClB,iBAAK,KAAK,QAAQ,MAAM;AACxB,gBAAI,QAAQ,QAAQ;AAClB,mBAAK,KAAK,QAAQ,MAAM;YAC1B;UACF,OAAO;AACL,iBAAK,KAAK,QAAQ;UACpB;AAEA,iBAAO,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;QACpD,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;;;;MAUQ,MAAM,oBACZ,SACA,KACA,SAA0B;AAE1B,cAAM,EAAE,cAAc,YAAY,mBAAkB,IAAK;AACzD,YAAI,WAAW;AACf,cAAM,sBAAgC,CAAA;AAEtC,YAAI;AAEF,gBAAM,EAAE,QAAQ,iBAAgB,IAAK,MAAMA,WAAU,mCAAmC,EAAE,IAAG,CAAE;AAC/F,gBAAM,gBAAgB,iBAAiB,KAAI;AAG3C,gBAAM,EAAE,QAAQ,aAAY,IAAK,MAAMA,WAAU,0BAA0B,EAAE,IAAG,CAAE;AAClF,cAAI,aAAa,KAAI,GAAI;AACvB,gBAAI;AACF,oBAAMA,WAAU,cAAc,EAAE,IAAG,CAAE;AACrC,oBAAM,EAAE,QAAQ,UAAS,IAAK,MAAMA,WAAU,gDAAgD,EAAE,IAAG,CAAE;AACrG,kBAAI,aAAa,UAAU,KAAI,GAAI;AACjC,sBAAMA,WAAU,+CAA+C,UAAU,KAAI,CAAE,IAAI,EAAE,IAAG,CAAE;AAC1F,sBAAMA,WAAU,yBAAyB,EAAE,IAAG,CAAE;AAChD,2BAAW;cACb;YACF,SAAS,YAAiB;YAE1B;UACF;AAGA,cAAI,cAAc,WAAW,SAAS,KAAK,kBAAkB,UAAU,kBAAkB,UAAU;AACjG,kBAAMA,WAAU,qBAAqB,EAAE,IAAG,CAAE;UAC9C;AAGA,cAAI,eAAe;AACnB,cAAI;AACF,kBAAMA,WAAU,0BAA0B,YAAY,IAAI,EAAE,IAAG,CAAE;AACjE,2BAAe;UACjB,QAAQ;AACN,2BAAe;UACjB;AAEA,cAAI,CAAC,cAAc;AACjB,kBAAMA,WAAU,mBAAmB,YAAY,IAAI,EAAE,IAAG,CAAE;UAC5D,OAAO;AACL,kBAAMA,WAAU,gBAAgB,YAAY,IAAI,EAAE,IAAG,CAAE;AAGvD,gBAAI,kBAAkB,UAAU,kBAAkB,UAAU;AAC1D,kBAAI;AACF,sBAAM,gBAAgB,uBAAuB,SAAS,oBACjC,uBAAuB,WAAW,6BAA6B;AACpF,sBAAMA,WAAU,aAAa,aAAa,IAAI,aAAa,cAAc,EAAE,IAAG,CAAE;cAClF,SAAS,YAAiB;AAExB,sBAAM,EAAE,QAAQ,eAAc,IAAK,MAAMA,WAAU,0BAA0B,EAAE,IAAG,CAAE;AACpF,oBAAI,eAAe,SAAS,KAAK,KAAK,eAAe,SAAS,KAAK,KAAK,eAAe,SAAS,KAAK,GAAG;AACtG,wBAAM,EAAE,QAAQ,cAAa,IAAK,MAAMA,WAAU,wCAAwC,EAAE,IAAG,CAAE;AACjG,wBAAM,kBAAkB,cAAc,KAAI,EAAG,MAAM,IAAI,EAAE,OAAO,OAAO;AAEvE,sBAAI,oBAAoB;AAEtB,+BAAW,QAAQ,iBAAiB;AAClC,4BAAMA,WAAU,kBAAkB,kBAAkB,KAAK,IAAI,KAAK,EAAE,IAAG,CAAE;AACzE,4BAAMA,WAAU,YAAY,IAAI,KAAK,EAAE,IAAG,CAAE;oBAC9C;AACA,0BAAMA,WAAU,wBAAwB,EAAE,IAAG,CAAE;kBACjD,OAAO;AAEL,0BAAMA,WAAU,qBAAqB,EAAE,IAAG,CAAE;AAC5C,2BAAO;sBACL,SAAS;sBACT,OAAO;sBACP,QAAQ;sBACR,SAAS;wBACP,cAAc;wBACd;wBACA,eAAe;;;kBAGrB;gBACF;cACF;YACF;UACF;AAGA,cAAI,cAAc,WAAW,SAAS,MAAM,kBAAkB,UAAU,kBAAkB,WAAW;AACnG,uBAAW,OAAO,YAAY;AAC5B,kBAAI;AAEF,sBAAM,EAAE,QAAQ,UAAS,IAAK,MAAMA,WAClC,uBAAuB,YAAY,WAAW,GAAG,IACjD,EAAE,IAAG,CAAE,EACP,MAAM,OAAO,EAAE,QAAQ,GAAE,EAAG;AAE9B,oBAAI,CAAC,UAAU,KAAI,GAAI;AACrB,wBAAMA,WAAU,mBAAmB,GAAG,IAAI,EAAE,IAAG,CAAE;AACjD,sCAAoB,KAAK,GAAG;gBAC9B;cACF,SAAS,KAAU;AACjB,sBAAMA,WAAU,2BAA2B,EAAE,IAAG,CAAE,EAAE,MAAM,MAAK;gBAAE,CAAC;cACpE;YACF;AAGA,kBAAMA,WAAU,qBAAqB,EAAE,IAAG,CAAE;AAC5C,kBAAMA,WAAU,gCAAgC,EAAE,IAAG,CAAE;AACvD,kBAAMA,WAAU,gBAAgB,YAAY,IAAI,EAAE,IAAG,CAAE;UACzD;AAGA,cAAI,UAAU;AACZ,gBAAI;AACF,oBAAMA,WAAU,iBAAiB,EAAE,IAAG,CAAE;YAC1C,SAAS,YAAiB;AAExB,oBAAM,EAAE,QAAQ,eAAc,IAAK,MAAMA,WAAU,0BAA0B,EAAE,IAAG,CAAE;AACpF,kBAAI,eAAe,SAAS,KAAK,KAAK,eAAe,SAAS,KAAK,GAAG;AACpE,oBAAI,oBAAoB;AACtB,wBAAM,EAAE,QAAQ,cAAa,IAAK,MAAMA,WAAU,wCAAwC,EAAE,IAAG,CAAE;AACjG,wBAAM,kBAAkB,cAAc,KAAI,EAAG,MAAM,IAAI,EAAE,OAAO,OAAO;AACvE,6BAAW,QAAQ,iBAAiB;AAClC,0BAAMA,WAAU,kBAAkB,kBAAkB,KAAK,IAAI,KAAK,EAAE,IAAG,CAAE;AACzE,0BAAMA,WAAU,YAAY,IAAI,KAAK,EAAE,IAAG,CAAE;kBAC9C;gBACF;cACF;YACF;UACF;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ,gCAAgC,YAAY;YACpD,SAAS;cACP,eAAe;cACf;cACA,eAAe;;;QAGrB,SAAS,OAAY;AAEnB,cAAI,UAAU;AACZ,gBAAI;AACF,oBAAM,EAAE,QAAQ,UAAS,IAAK,MAAMA,WAAU,kBAAkB,EAAE,IAAG,CAAE;AACvE,kBAAI,UAAU,SAAS,wBAAwB,GAAG;AAChD,sBAAMA,WAAU,iBAAiB,EAAE,IAAG,CAAE;cAC1C;YACF,SAAS,GAAG;YAEZ;UACF;AAEA,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,0BACZ,KACA,SAA0B;AAE1B,YAAI;AAEF,gBAAM,EAAE,QAAQ,iBAAgB,IAAK,MAAMA,WAAU,mCAAmC,EAAE,IAAG,CAAE;AAC/F,gBAAM,SAAS,iBAAiB,KAAI;AAEpC,cAAI,WAAW,UAAU,WAAW,UAAU;AAC5C,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,gEAAgE,MAAM;;UAElF;AAEA,cAAI,iBAAiB;AAGrB,gBAAM,EAAE,QAAQ,aAAY,IAAK,MAAMA,WAAU,0BAA0B,EAAE,IAAG,CAAE;AAClF,cAAI,aAAa,KAAI,GAAI;AACvB,gBAAI;AACF,oBAAMA,WAAU,iCAAiC,EAAE,IAAG,CAAE;AACxD,+BAAiB,aAAa,KAAI,EAAG,MAAM,IAAI,EAAE;YACnD,SAAS,YAAiB;YAE1B;UACF;AAGA,gBAAMA,WAAU,oBAAoB,EAAE,IAAG,CAAE;AAC3C,gBAAMA,WAAU,2BAA2B,MAAM,IAAI,EAAE,IAAG,CAAE;AAG5D,cAAI;AACF,kBAAMA,WAAU,iBAAiB,EAAE,IAAG,CAAE;UAC1C,SAAS,YAAiB;UAE1B;AAGA,cAAI;AACF,kBAAMA,WAAU,kBAAkB,EAAE,IAAG,CAAE;UAC3C,SAAS,WAAgB;UAEzB;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ,0DAA0D,MAAM;YACxE,SAAS;cACP,eAAe;cACf;;;QAGN,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;;;;MAUQ,MAAM,kBACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AAEF,gBAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,cAAI,CAAC,WAAW,OAAO;AACrB,mBAAO;cACL,SAAS;cACT,OAAO,WAAW,SAAS;;UAE/B;AAGA,cAAI;AACF,kBAAMA,WAAU,oBAAoB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;UACjF,SAAS,YAAiB;AAExB,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ;;UAEZ;AAEA,cAAI,gBAAgB;AACpB,cAAI,eAAe;AAGnB,cAAI;AACF,kBAAM,EAAE,QAAQ,aAAY,IAAK,MAAMA,WACrC,wBAAwB,QAAQ,MAAM,iBACtC,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAE7C,4BAAgB,SAAS,aAAa,KAAI,GAAI,EAAE,KAAK;UACvD,QAAQ;AAEN,4BAAgB;UAClB;AAGA,cAAI;AACF,kBAAM,EAAE,QAAQ,YAAW,IAAK,MAAMA,WACpC,qCAAqC,QAAQ,MAAM,IACnD,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAE7C,2BAAe,SAAS,YAAY,KAAI,GAAI,EAAE,KAAK;UACrD,QAAQ;AAEN,2BAAe;UACjB;AAEA,gBAAM,WAAW,gBAAgB;AACjC,gBAAM,UAAU,eAAe;AAC/B,gBAAM,YAAY;AAElB,iBAAO;YACL,SAAS;YACT,QAAQ,WACJ,UAAU,QAAQ,MAAM,OAAO,aAAa,2BAC5C,UAAU,QAAQ,MAAM;YAC5B,SAAS;cACP,YAAY,QAAQ;cACpB;cACA;cACA;cACA;cACA;;;QAGN,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,gBACZ,KACA,SAA0B;AAE1B,YAAI;AAEF,cAAI,gBAAgB;AACpB,cAAI;AACF,kBAAM,EAAE,OAAM,IAAK,MAAMA,WAAU,6BAA6B,EAAE,KAAK,SAAS,IAAI,CAAE;AACtF,4BAAgB,OAAO,KAAI;UAC7B,QAAQ;UAER;AAGA,cAAI;AACF,kBAAMA,WAAU,yBAAyB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;UACtF,SAAS,YAAiB;AACxB,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ;;UAEZ;AAGA,gBAAM,cAAc,kBAAkB,UAAU,kBAAkB;AAElE,cAAI,aAAa;AAEf,kBAAM,EAAE,QAAQ,aAAY,IAAK,MAAMA,WAAU,0BAA0B,EAAE,KAAK,SAAS,IAAI,CAAE;AACjG,gBAAI,aAAa,KAAI,GAAI;AACvB,qBAAO;gBACL,SAAS;gBACT,OAAO;gBACP,QAAQ;gBACR,SAAS;kBACP,kBAAkB,aAAa,KAAI,EAAG,MAAM,IAAI,EAAE,IAAI,UAAQ,KAAK,MAAM,CAAC,CAAC;;;YAGjF;AAGA,kBAAMA,WAAU,qBAAqB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;UAClF;AAGA,cAAI;AACF,kBAAMA,WAAU,wBAAwB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;UACrF,SAAS,WAAgB;AAEvB,gBAAI,UAAU,SAAS,SAAS,UAAU,KAAK,UAAU,QAAQ,SAAS,UAAU,GAAG;AAErF,oBAAMA,WAAU,qBAAqB,EAAE,KAAK,SAAS,IAAI,CAAE,EAAE,MAAM,MAAK;cAAE,CAAC;AAC3E,qBAAO;gBACL,SAAS;gBACT,OAAO;gBACP,QAAQ;;YAEZ;AACA,kBAAM;UACR;AAGA,cAAI,eAAe,eAAe;AAChC,kBAAMA,WAAU,iBAAiB,aAAa,KAAK,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;UAChG;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ;YACR,SAAS;cACP,eAAe,cAAc,gBAAgB;;;QAGnD,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,oBACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AAEF,gBAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,cAAI,CAAC,WAAW,OAAO;AACrB,mBAAO;cACL,SAAS;cACT,OAAO,WAAW,SAAS;;UAE/B;AAGA,gBAAM,EAAE,QAAQ,aAAY,IAAK,MAAMA,WAAU,0BAA0B,EAAE,KAAK,SAAS,IAAI,CAAE;AACjG,cAAI,aAAa,KAAI,GAAI;AACvB,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ;cACR,SAAS;gBACP,kBAAkB,aAAa,KAAI,EAAG,MAAM,IAAI,EAAE,IAAI,UAAQ,KAAK,MAAM,CAAC,CAAC;;;UAGjF;AAGA,gBAAM,EAAE,QAAQ,iBAAgB,IAAK,MAAMA,WAAU,6BAA6B,EAAE,KAAK,SAAS,IAAI,CAAE;AACxG,gBAAM,gBAAgB,iBAAiB,KAAI;AAG3C,cAAI,kBAAkB,QAAQ,QAAQ;AACpC,kBAAMA,WAAU,iBAAiB,QAAQ,MAAM,KAAK,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;UACjG;AAGA,gBAAMA,WAAU,yBAAyB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAGpF,cAAI;AACF,kBAAMA,WAAU,0BAA0B,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;UACvF,SAAS,aAAkB;AACzB,kBAAM,eAAe,YAAY,UAAU,OAAO,YAAY,UAAU;AAGxE,gBAAI,YAAY,SAAS,UAAU,KAAK,YAAY,SAAS,iBAAiB,GAAG;AAE/E,kBAAI,gBAA0B,CAAA;AAC9B,kBAAI;AACF,sBAAM,EAAE,QAAQ,eAAc,IAAK,MAAMA,WACvC,wCACA,EAAE,KAAK,SAAS,IAAI,CAAE;AAExB,gCAAgB,eAAe,KAAI,EAAG,MAAM,IAAI,EAAE,OAAO,OAAO;cAClE,QAAQ;AAEN,oBAAI;AACF,wBAAM,EAAE,QAAQ,UAAS,IAAK,MAAMA,WAAU,0BAA0B,EAAE,KAAK,SAAS,IAAI,CAAE;AAC9F,kCAAgB,UACb,KAAI,EACJ,MAAM,IAAI,EACV,OAAO,UAAQ,KAAK,WAAW,KAAK,KAAK,KAAK,WAAW,KAAK,KAAK,KAAK,WAAW,KAAK,CAAC,EACzF,IAAI,UAAQ,KAAK,MAAM,CAAC,CAAC;gBAC9B,QAAQ;gBAER;cACF;AAEA,qBAAO;gBACL,SAAS;gBACT,OAAO;gBACP,QAAQ,sBAAsB,cAAc,MAAM;gBAClD,SAAS;kBACP,UAAU;kBACV,iBAAiB;kBACjB,cAAc;kBACd,iBAAiB;;;YAGvB;AAEA,kBAAM;UACR;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ,wBAAwB,QAAQ,MAAM;YAC9C,SAAS;cACP,YAAY,QAAQ;cACpB,UAAU;;;QAGhB,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,mBACZ,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAMA,WAAU,sBAAsB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAEjF,iBAAO;YACL,SAAS;YACT,QAAQ;YACR,SAAS;cACP,UAAU;;;QAGhB,SAAS,OAAY;AAEnB,cAAI,MAAM,SAAS,SAAS,uBAAuB,KAAK,MAAM,QAAQ,SAAS,uBAAuB,GAAG;AACvG,mBAAO;cACL,SAAS;cACT,QAAQ;cACR,SAAS;gBACP,UAAU;;;UAGhB;AAEA,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,sBACZ,KACA,SAA0B;AAE1B,YAAI;AAEF,gBAAMA,WAAU,cAAc,EAAE,KAAK,SAAS,IAAI,CAAE;AAGpD,gBAAMA,WAAU,yBAAyB,EAAE,KAAK,SAAS,SAAS,WAAW,IAAK,CAAE;AAEpF,iBAAO;YACL,SAAS;YACT,QAAQ;YACR,SAAS;cACP,UAAU;;;QAGhB,SAAS,OAAY;AACnB,gBAAM,eAAe,MAAM,UAAU,OAAO,MAAM,UAAU;AAG5D,cAAI,YAAY,SAAS,UAAU,KAAK,YAAY,SAAS,iBAAiB,GAAG;AAE/E,gBAAI,gBAA0B,CAAA;AAC9B,gBAAI;AACF,oBAAM,EAAE,QAAQ,eAAc,IAAK,MAAMA,WACvC,wCACA,EAAE,KAAK,SAAS,IAAI,CAAE;AAExB,8BAAgB,eAAe,KAAI,EAAG,MAAM,IAAI,EAAE,OAAO,OAAO;YAClE,QAAQ;YAER;AAEA,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ;cACR,SAAS;gBACP,UAAU;gBACV,iBAAiB;gBACjB,cAAc;gBACd,iBAAiB;;;UAGvB;AAGA,cAAI,YAAY,SAAS,uBAAuB,GAAG;AACjD,mBAAO;cACL,SAAS;cACT,QAAQ;cACR,SAAS;gBACP,UAAU;;;UAGhB;AAEA,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,oBACZ,KACA,SAA0B;AAE1B,YAAI;AAEF,cAAI,WAAW;AACf,cAAI,kBAA4B,CAAA;AAEhC,cAAI;AACF,kBAAM,EAAE,QAAQ,OAAM,IAAK,MAAMA,WAAU,2BAA2B,EAAE,KAAK,SAAS,IAAI,CAAE;AAC5F,kBAAM,aAAa,OAAO,KAAI;AAG9B,kBAAME,OAAK,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,aAAA,QAAO,IAAI,CAAA,CAAA,EAAE,KAAK,OAAK,EAAE,QAAQ;AAClD,kBAAM,kBAAkB,GAAG,UAAU;AACrC,kBAAM,kBAAkB,GAAG,UAAU;AAErC,gBAAI;AACF,oBAAMA,KAAG,OAAO,eAAe;AAC/B,yBAAW;YACb,QAAQ;AACN,kBAAI;AACF,sBAAMA,KAAG,OAAO,eAAe;AAC/B,2BAAW;cACb,QAAQ;AACN,2BAAW;cACb;YACF;UACF,QAAQ;AAEN,gBAAI;AACF,oBAAM,EAAE,QAAQ,aAAY,IAAK,MAAMF,WAAU,cAAc,EAAE,KAAK,SAAS,IAAI,CAAE;AACrF,yBAAW,aAAa,SAAS,oBAAoB,KAC1C,aAAa,SAAS,gCAAgC,KACtD,aAAa,SAAS,4BAA4B;YAC/D,QAAQ;AACN,yBAAW;YACb;UACF;AAGA,cAAI,UAAU;AACZ,gBAAI;AACF,oBAAM,EAAE,QAAQ,eAAc,IAAK,MAAMA,WACvC,wCACA,EAAE,KAAK,SAAS,IAAI,CAAE;AAExB,gCAAkB,eAAe,KAAI,EAAG,MAAM,IAAI,EAAE,OAAO,OAAO;YACpE,QAAQ;YAER;UACF;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ,WACJ,2BAA2B,gBAAgB,MAAM,yBACjD;YACJ,SAAS;cACP;cACA;cACA,cAAc,gBAAgB,SAAS;;;QAG7C,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;;;;MAUQ,MAAM,mBACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AAEF,gBAAM,cAAa,GAAA,gBAAA,oBAAmB,QAAQ,MAAM;AACpD,cAAI,CAAC,WAAW,OAAO;AACrB,mBAAO;cACL,SAAS;cACT,OAAO,WAAW,SAAS;;UAE/B;AAGA,gBAAME,OAAK,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,aAAA,QAAO,IAAI,CAAA,CAAA,EAAE,KAAK,OAAK,EAAE,QAAQ;AAClD,cAAI;AACF,kBAAMA,KAAG,OAAO,QAAQ,IAAI;AAC5B,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,oCAAoC,QAAQ,IAAI;;UAE5D,QAAQ;UAER;AAIA,cAAI;AACF,kBAAM,KAAK,cAAc,CAAC,SAAS,SAAS,SAAS,GAAG,KAAK,OAAO;UACtE,QAAQ;UAER;AAGA,gBAAM,OAAO,CAAC,YAAY,KAAK;AAC/B,cAAI,QAAQ,QAAQ;AAClB,iBAAK,KAAK,MAAM,QAAQ,QAAQ,QAAQ,IAAI;AAE5C,gBAAI,QAAQ,YAAY;AACtB,mBAAK,KAAK,QAAQ,UAAU;YAC9B;UACF,OAAO;AACL,iBAAK,KAAK,QAAQ,MAAM,QAAQ,MAAM;UACxC;AAEA,gBAAM,SAAS,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;AAE1D,cAAI,OAAO,SAAS;AAClB,mBAAO;cACL,SAAS;cACT,QAAQ,uBAAuB,QAAQ,IAAI,eAAe,QAAQ,MAAM;cACxE,SAAS;gBACP,cAAc,QAAQ;gBACtB,YAAY,QAAQ;;;UAG1B;AAGA,cAAI,OAAO,QAAQ,SAAS,qBAAqB,GAAG;AAClD,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,WAAW,QAAQ,MAAM;;UAErC;AAEA,iBAAO;QACT,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,sBACZ,SACA,KACA,SAA0B;AAE1B,YAAI;AAEF,gBAAMA,OAAK,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,aAAA,QAAO,IAAI,CAAA,CAAA,EAAE,KAAK,OAAK,EAAE,QAAQ;AAClD,cAAI;AACF,kBAAMA,KAAG,OAAO,QAAQ,IAAI;UAC9B,QAAQ;AACN,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,+BAA+B,QAAQ,IAAI;;UAEvD;AAGA,cAAI,CAAC,QAAQ,OAAO;AAClB,gBAAI;AACF,oBAAM,EAAE,OAAM,IAAK,MAAMF,WAAU,0BAA0B;gBAC3D,KAAK,QAAQ;gBACb,SAAS,SAAS,WAAW;eAC9B;AACD,kBAAI,OAAO,KAAI,GAAI;AACjB,uBAAO;kBACL,SAAS;kBACT,OAAO;kBACP,QAAQ;kBACR,SAAS;oBACP,kBAAkB,OAAO,KAAI,EAAG,MAAM,IAAI,EAAE,IAAI,UAAQ,KAAK,MAAM,CAAC,CAAC;;;cAG3E;YACF,QAAQ;YAER;UACF;AAGA,gBAAM,OAAO,CAAC,YAAY,QAAQ;AAClC,cAAI,QAAQ,OAAO;AACjB,iBAAK,KAAK,SAAS;UACrB;AACA,eAAK,KAAK,QAAQ,IAAI;AAEtB,gBAAM,SAAS,MAAM,KAAK,cAAc,MAAM,KAAK,OAAO;AAE1D,cAAI,OAAO,SAAS;AAClB,mBAAO;cACL,SAAS;cACT,QAAQ,uBAAuB,QAAQ,IAAI;cAC3C,SAAS;gBACP,cAAc,QAAQ;;;UAG5B;AAGA,cAAI,OAAO,QAAQ,SAAS,QAAQ,GAAG;AACrC,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ,eAAe,QAAQ,IAAI;;UAEvC;AAEA,iBAAO;QACT,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,oBACZ,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAM,EAAE,OAAM,IAAK,MAAMA,WAAU,iCAAiC;YAClE;YACA,SAAS,SAAS,WAAW;WAC9B;AAED,gBAAM,YAMD,CAAA;AAQL,gBAAM,QAAQ,OAAO,KAAI,EAAG,MAAM,IAAI;AACtC,cAAI,UAMC,CAAA;AAEL,qBAAW,QAAQ,OAAO;AACxB,gBAAI,KAAK,WAAW,WAAW,GAAG;AAChC,sBAAQ,OAAO,KAAK,MAAM,CAAC;YAC7B,WAAW,KAAK,WAAW,OAAO,GAAG;AACnC,sBAAQ,SAAS,KAAK,MAAM,CAAC;YAC/B,WAAW,KAAK,WAAW,SAAS,GAAG;AAErC,oBAAM,UAAU,KAAK,MAAM,CAAC;AAC5B,sBAAQ,SAAS,QAAQ,QAAQ,eAAe,EAAE;YACpD,WAAW,SAAS,UAAU;AAC5B,sBAAQ,SAAS;YACnB,WAAW,SAAS,YAAY;AAC9B,sBAAQ,WAAW;YACrB,WAAW,KAAK,WAAW,UAAU,GAAG;AACtC,sBAAQ,SAAS;YACnB,WAAW,SAAS,MAAM,QAAQ,MAAM;AAEtC,wBAAU,KAAK;gBACb,MAAM,QAAQ;gBACd,QAAQ,QAAQ,UAAU;gBAC1B,QAAQ,QAAQ,UAAU;gBAC1B,QAAQ,QAAQ;gBAChB,UAAU,QAAQ;eACnB;AACD,wBAAU,CAAA;YACZ;UACF;AAGA,cAAI,QAAQ,MAAM;AAChB,sBAAU,KAAK;cACb,MAAM,QAAQ;cACd,QAAQ,QAAQ,UAAU;cAC1B,QAAQ,QAAQ,UAAU;cAC1B,QAAQ,QAAQ;cAChB,UAAU,QAAQ;aACnB;UACH;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ,SAAS,UAAU,MAAM;YACjC,SAAS;cACP;;;QAGN,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,qBACZ,KACA,SAA0B;AAE1B,YAAI;AAEF,gBAAM,aAAa,MAAM,KAAK,oBAAoB,KAAK,OAAO;AAC9D,gBAAM,gBAAgB,WAAW,SAAS,WAAW,OAAO,OAAK,EAAE,QAAQ,EAAE,UAAU;AAGvF,gBAAM,SAAS,MAAM,KAAK,cAAc,CAAC,YAAY,OAAO,GAAG,KAAK,OAAO;AAE3E,cAAI,OAAO,SAAS;AAClB,mBAAO;cACL,SAAS;cACT,QAAQ,gBAAgB,IACpB,UAAU,aAAa,uBACvB;cACJ,SAAS;gBACP,aAAa;;;UAGnB;AAEA,iBAAO;QACT,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,iBACZ,SACA,SAA0B;AAE1B,YAAI;AACF,gBAAME,OAAK,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,aAAA,QAAO,IAAI,CAAA,CAAA,EAAE,KAAK,OAAK,EAAE,QAAQ;AAClD,gBAAMC,SAAO,MAAA,QAAA,QAAA,EAAA,KAAA,MAAA,aAAA,QAAa,MAAM,CAAA,CAAA;AAGhC,cAAI;AACF,kBAAMD,KAAG,OAAO,QAAQ,IAAI;AAC5B,mBAAO;cACL,SAAS;cACT,OAAO;;cACP,QAAQ,qCAAqC,QAAQ,IAAI;;UAE7D,QAAQ;UAER;AAGA,gBAAM,YAAYC,OAAK,QAAQ,QAAQ,IAAI;AAC3C,cAAI;AACF,kBAAMD,KAAG,MAAM,WAAW,EAAE,WAAW,KAAI,CAAE;UAC/C,QAAQ;UAER;AAGA,gBAAM,EAAE,QAAQ,OAAM,IAAK,MAAMF;YAC/B,qBAAqB,QAAQ,GAAG,MAAM,QAAQ,IAAI;YAClD,EAAE,SAAS,SAAS,WAAW,KAAM;;;AAGvC,iBAAO;YACL,SAAS;YACT,QAAQ,6BAA6B,QAAQ,IAAI;YACjD,SAAS;cACP,cAAc,QAAQ;;;QAG5B,SAAS,OAAY;AAEnB,cAAI,MAAM,SAAS,SAAS,gBAAgB,KAAK,MAAM,SAAS,SAAS,mBAAmB,GAAG;AAC7F,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ;;UAEZ;AAGA,cAAI,MAAM,SAAS,SAAS,mBAAmB,KAAK,MAAM,SAAS,SAAS,kBAAkB,GAAG;AAC/F,mBAAO;cACL,SAAS;cACT,OAAO;cACP,QAAQ;;UAEZ;AAEA,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;;MAMQ,MAAM,mBACZ,KACA,SAA0B;AAE1B,YAAI;AACF,gBAAME,OAAK,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,aAAA,QAAO,IAAI,CAAA,CAAA,EAAE,KAAK,OAAK,EAAE,QAAQ;AAClD,gBAAMC,SAAO,MAAA,QAAA,QAAA,EAAA,KAAA,MAAA,aAAA,QAAa,MAAM,CAAA,CAAA;AAIhC,cAAI,cAAc;AAClB,cAAI,cAAc;AAClB,cAAI;AAEJ,mBAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,kBAAM,UAAUA,OAAK,KAAK,aAAa,OAAO;AAC9C,kBAAM,aAAaA,OAAK,KAAK,aAAa,UAAU;AAEpD,gBAAI;AACF,oBAAMD,KAAG,OAAO,OAAO;AACvB,oBAAMA,KAAG,OAAO,UAAU;AAG1B,4BAAc;AACd,6BAAe;AACf;YACF,QAAQ;AAEN,oBAAM,aAAaC,OAAK,QAAQ,WAAW;AAC3C,kBAAI,eAAe,aAAa;AAC9B;cACF;AACA,4BAAc;YAChB;UACF;AAEA,iBAAO;YACL,SAAS;YACT,QAAQ,eAAe,oBAAoB;YAC3C,SAAS;cACP;cACA;;;QAGN,SAAS,OAAY;AACnB,iBAAO;YACL,SAAS;YACT,OAAO;YACP,QAAQ,MAAM,WAAW;;QAE7B;MACF;;;;MAKQ,MAAM,cACZ,MACA,KACA,SAAwD;AAExD,YAAI;AAEF,gBAAM,iBAAgB,GAAA,gBAAA,cAAa,IAAI;AAGvC,gBAAM,UAAU,CAAC,OAAO,GAAG,aAAa,EAAE,KAAK,GAAG;AAGlD,gBAAM,UAAU,SAAS,WAAW;AACpC,gBAAM,cAAc;YAClB;YACA;YACA,KAAK,SAAS,OAAO,QAAQ;YAC7B,WAAW,OAAO,OAAO;;;AAG3B,gBAAM,EAAE,QAAQ,OAAM,IAAK,MAAMH,WAAU,SAAS,WAAW;AAG/D,gBAAM,UAAU,SAAS,QAAQ,KAAI;AAGrC,gBAAM,UAAsC,CAAA;AAG5C,gBAAM,cAAa,GAAA,aAAA,mBAAkB,MAAM;AAC3C,cAAI,YAAY;AACd,oBAAQ,aAAa;UACvB;AAGA,eAAI,GAAA,aAAA,gBAAe,MAAM,GAAG;AAC1B,oBAAQ,aAAa;UACvB;AAEA,iBAAO;YACL,SAAS;YACT;YACA,SAAS,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,UAAU;;QAEzD,SAAS,OAAY;AAEnB,gBAAM,SAAS,MAAM,UAAU;AAC/B,gBAAM,SAAS,MAAM,UAAU;AAC/B,gBAAM,WAAW,MAAM,QAAQ;AAG/B,gBAAM,aAAY,GAAA,aAAA,eAAc,QAAQ,QAAQ,QAAQ;AAGxD,gBAAM,UAAsC;YAC1C;;AAIF,cAAI,cAAc,kBAAkB;AAClC,kBAAM,aAAY,GAAA,aAAA,qBAAoB,SAAS,MAAM;AACrD,gBAAI,UAAU,SAAS,GAAG;AACxB,sBAAQ,mBAAmB;YAC7B;UACF;AAGA,cAAI,cAAc,uBAAuB;AACvC,gBAAI;AACF,oBAAM,eAAe,MAAM,KAAK,cAAc,KAAK,OAAO;AAC1D,kBAAI,aAAa,SAAS,kBAAkB;AAC1C,wBAAQ,mBAAmB,aAAa,QAAQ;cAClD;YACF,QAAQ;YAER;UACF;AAEA,iBAAO;YACL,SAAS;YACT,OAAO;YACP,SAAS,SAAS,QAAQ,KAAI;YAC9B;;QAEJ;MACF;;;;MAKQ,MAAM,uBAAoB;AAChC,YAAI;AACF,gBAAMA,WAAU,iBAAiB,EAAE,SAAS,IAAI,CAAE;AAClD,iBAAO;QACT,QAAQ;AACN,iBAAO;QACT;MACF;;;;MAKQ,MAAM,gBAAgB,KAAW;AACvC,YAAI;AACF,gBAAMA,WAAU,2BAA2B,EAAE,KAAK,SAAS,IAAI,CAAE;AACjE,iBAAO;QACT,QAAQ;AACN,iBAAO;QACT;MACF;;;;;MAMQ,MAAM,uBAAuB,WAAkB;AACrD,YAAI;AACF,gBAAM,EAAE,OAAM,IAAK,MAAMA,WAAU,iCAAiC;YAClE,KAAK,aAAa,QAAQ,IAAG;YAC7B,SAAS;WACV;AACD,iBAAO,OAAO,KAAI;QACpB,QAAQ;AACN,iBAAO;QACT;MACF;;AAnvEF,IAAAI,SAAA,cAAAH;;;;;;;;;;ACzBA,QAAA,OAAA,QAAA,IAAA;AACA,QAAA,SAAA,QAAA,MAAA;AAGA,QAAM,mBAAmB;AAEzB,aAAS,aAAU;AACjB,UAAI;AAEF,cAAM,mBAAkB,GAAA,OAAA,MAAK,WAAW,MAAM,cAAc;AAC5D,aAAI,GAAA,KAAA,YAAW,eAAe,GAAG;AAC/B,gBAAMI,eAAc,KAAK,OAAM,GAAA,KAAA,cAAa,iBAAiB,OAAO,CAAC;AACrE,iBAAOA,aAAY;QACrB;MACF,QAAQ;MAER;AACA,aAAO;IACT;AAEa,IAAAC,SAAA,UAAkB,WAAU;;;;;;;;;;;;;ACnBzC,QAAA,OAAA,gBAAA,QAAA,IAAA,CAAA;AACA,QAAA,UAAA,gBAAA,QAAA,OAAA,CAAA;AAEA,QAAA,YAAA;AAIA,QAAM,YAAY,IAAI,QAAA,QAAM,MAAM,EAAE,QAAQ,EAAC,CAAE;AAkB/C,QAAM,qBAAqB,IAAI,KAAK,KAAK;AACzC,QAAM,iBAAiB,KAAK,KAAK;AASjC,QAAM,4BAA4B;AAClC,QAAM,2BAA2B;AAGjC,QAAM,qBAAqB;AAW3B,QAAaC,iBAAb,MAA0B;MAA1B,cAAA;AAEU,aAAA,gBAA6C,oBAAI,IAAG;AACpD,aAAA,oBAAoB;AAEpB,aAAA,MAAM;AACN,aAAA,QAAQ;AAMR,aAAA,cAAc;AACd,aAAA,kBAAkB;AAClB,aAAA,qBAAqB;AAKrB,aAAA,kBAAkB,KAAK,IAAG;AAE1B,aAAA,0BAA0B;AAG1B,aAAA,yBAAyB;MAgdnC;;;;;;;;MAtcE,MAAM,QACJ,KACA,OACA,WACA,YAA4F;AAE5F,aAAK,MAAM;AACX,aAAK,QAAQ;AACb,aAAK,YAAY;AACjB,aAAK,WAAW,YAAY;AAC5B,aAAK,aAAa,YAAY;AAC9B,aAAK,SAAS,YAAY;AAC1B,aAAK,YAAY,YAAY;AAC7B,aAAK,kBAAkB;AACvB,aAAK,qBAAqB;AAC1B,aAAK,0BAA0B;AAC/B,aAAK,yBAAyB,KAAK,IAAG;AACtC,aAAK,gBAAgB;AAIrB,YAAI,KAAK,IAAI;AACX,cAAI;AACF,iBAAK,GAAG,mBAAkB;AAC1B,iBAAK,GAAG,UAAS;UACnB,QAAQ;UAER;AACA,eAAK,KAAK;QACZ;AAGA,YAAI,KAAK,kBAAkB;AACzB,uBAAa,KAAK,gBAAgB;AAClC,eAAK,mBAAmB;QAC1B;AAEA,eAAO,IAAI,QAAQ,CAACC,UAAS,WAAU;AAErC,gBAAM,oBAAoB,WAAW,MAAK;AACxC,gBAAI,KAAK,IAAI;AACX,mBAAK,GAAG,UAAS;YACnB;AACA,mBAAO,IAAI,MAAM,4BAA4B,qBAAqB,GAAI,+BAA+B,CAAC;UACxG,GAAG,kBAAkB;AAErB,cAAI;AAEF,iBAAK,KAAK,IAAI,KAAA,QAAG,KAAK,EAAE,OAAO,UAAS,CAAE;AAG1C,iBAAK,GAAG,GAAG,QAAQ,MAAK;AACtB,2BAAa,iBAAiB;AAC9B,sBAAQ,IAAI,qCAAqC;AACjD,mBAAK,cAAc;AACnB,mBAAK,oBAAoB;AACzB,mBAAK,sBAAsB;AAC3B,mBAAK,kBAAkB,KAAK,IAAG;AAG/B,mBAAK,KAAK;gBACR,MAAM;gBACN;gBACA,SAAS,UAAA;gBACT;gBACA,UAAU,KAAK;gBACf,YAAY,KAAK;gBACjB,QAAQ,KAAK;gBACb,WAAW,KAAK;eACjB;AAGD,mBAAK,eAAc;AAEnB,cAAAA,SAAO;YACT,CAAC;AAGD,iBAAK,GAAG,GAAG,QAAQ,MAAK;AAEtB,kBAAI,KAAK,uBAAuB;AAC9B,6BAAa,KAAK,qBAAqB;AACvC,qBAAK,wBAAwB;cAC/B;YACF,CAAC;AAGD,iBAAK,GAAG,GAAG,WAAW,CAAC,SAAiB;AACtC,kBAAI;AACF,sBAAM,UAAU,KAAK,MAAM,KAAK,SAAQ,CAAE;AAC1C,qBAAK,cAAc,OAAO;cAC5B,SAAS,OAAO;AACd,wBAAQ,MAAM,4CAA4C,KAAK;cACjE;YACF,CAAC;AAGD,iBAAK,GAAG,GAAG,QAAQ,MAAK;AACtB,sBAAQ,IAAI,2CAA2C;YACzD,CAAC;AAGD,iBAAK,GAAG,GAAG,SAAS,CAAC,MAAc,WAAkB;AACnD,sBAAQ,IAAI,qCAAqC,IAAI,IAAI,OAAO,SAAQ,CAAE,EAAE;AAC5E,mBAAK,cAAc;AAEnB,oBAAM,gBAAgB,CAAC,KAAK;AAG5B,mBAAK,KAAK;gBACR,MAAM;gBACN;gBACA,QAAQ,OAAO,SAAQ;gBACvB;eACD;AAGD,kBAAI,eAAe;AACjB,qBAAK,kBAAiB;cACxB;YACF,CAAC;AAGD,iBAAK,GAAG,GAAG,SAAS,CAAC,UAAgB;AACnC,sBAAQ,MAAM,oCAAoC,KAAK;AAEvD,kBAAI,CAAC,KAAK,aAAa;AAErB,6BAAa,iBAAiB;AAC9B,uBAAO,KAAK;cACd;YACF,CAAC;UAEH,SAAS,OAAO;AACd,yBAAa,iBAAiB;AAC9B,mBAAO,KAAK;UACd;QACF,CAAC;MACH;;;;;MAMA,MAAM,WAAW,cAAc,MAAI;AACjC,aAAK,kBAAkB;AACvB,aAAK,0BAA0B;AAG/B,YAAI,KAAK,kBAAkB;AACzB,uBAAa,KAAK,gBAAgB;AAClC,eAAK,mBAAmB;QAC1B;AAEA,YAAI,KAAK,gBAAgB;AACvB,wBAAc,KAAK,cAAc;AACjC,eAAK,iBAAiB;QACxB;AAEA,YAAI,KAAK,uBAAuB;AAC9B,uBAAa,KAAK,qBAAqB;AACvC,eAAK,wBAAwB;QAC/B;AAEA,YAAI,KAAK,IAAI;AACX,eAAK,GAAG,MAAK;AACb,eAAK,KAAK;QACZ;AAEA,aAAK,cAAc;MACrB;;;;;;MAOA,GAAG,OAAe,SAAqB;AACrC,YAAI,CAAC,KAAK,cAAc,IAAI,KAAK,GAAG;AAClC,eAAK,cAAc,IAAI,OAAO,CAAA,CAAE;QAClC;AACA,aAAK,cAAc,IAAI,KAAK,EAAG,KAAK,OAAO;MAC7C;;;;;;MAOA,KAAK,OAAe,SAAqB;AACvC,cAAM,cAA4B,CAAC,YAAW;AAC5C,eAAK,IAAI,OAAO,WAAW;AAC3B,kBAAQ,OAAO;QACjB;AACA,aAAK,GAAG,OAAO,WAAW;MAC5B;;;;;;MAOA,IAAI,OAAe,SAAqB;AACtC,cAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,YAAI,UAAU;AACZ,gBAAM,QAAQ,SAAS,QAAQ,OAAO;AACtC,cAAI,UAAU,IAAI;AAChB,qBAAS,OAAO,OAAO,CAAC;UAC1B;QACF;MACF;;;;;MAMA,MAAM,KAAK,SAAsB;AAC/B,YAAI,CAAC,KAAK,MAAM,CAAC,KAAK,aAAa;AACjC,gBAAM,IAAI,MAAM,yBAAyB;QAC3C;AAEA,eAAO,IAAI,QAAQ,CAACA,UAAS,WAAU;AACrC,eAAK,GAAI,KAAK,KAAK,UAAU,OAAO,GAAG,CAAC,UAAS;AAC/C,gBAAI,OAAO;AACT,sBAAQ,MAAM,2CAA2C,KAAK;AAC9D,qBAAO,KAAK;YACd,OAAO;AACL,cAAAA,SAAO;YACT;UACF,CAAC;QACH,CAAC;MACH;;;;MAKA,YAAS;AACP,eAAO;UACL,WAAW,KAAK;;MAEpB;;;;;MAMA,iBAAc;AACZ,aAAK,kBAAkB,KAAK,IAAG;MACjC;;;;;MAMQ,KAAK,OAAkB;AAC7B,cAAM,WAAW,KAAK,cAAc,IAAI,MAAM,IAAI,KAAK,CAAA;AACvD,iBAAS,QAAQ,aAAU;AACzB,cAAI;AACF,oBAAQ,KAAK;UACf,SAAS,OAAO;AACd,oBAAQ,MAAM,qCAAqC,MAAM,IAAI,KAAK,KAAK;UACzE;QACF,CAAC;MACH;;;;MAKQ,cAAc,SAAsB;AAE1C,YAAI,QAAQ,SAAS,YAAY;AAC/B,kBAAQ,IAAI,gEAAgE;AAC5E,eAAK,qBAAqB;QAC5B;AAGA,YAAI,QAAQ,SAAS,SAAS;AAC5B,gBAAM,eAAe;AACrB,eAAK,gBAAgB,aAAa;AAElC,cAAI,aAAa,SAAS,kBAAkB,aAAa,SAAS,YAAY;AAE5E,kBAAM,eAAe,aAAa,SAAS,iBAAiB,KAAK;AACjE,kBAAM,gBAAgB,aAAa,cAAc,gBAAgB;AACjE,iBAAK,wBAAwB,KAAK,IAAG,IAAK;AAC1C,oBAAQ,IAAI,mBAAmB,aAAa,IAAI,sBAAsB,eAAe,GAAI,GAAG;UAC9F;QACF;AAEA,cAAM,WAAW,KAAK,cAAc,IAAI,QAAQ,IAAI,KAAK,CAAA;AAGzD,iBAAS,QAAQ,aAAU;AACzB,cAAI;AACF,oBAAQ,OAAO;UACjB,SAAS,OAAO;AACd,oBAAQ,MAAM,qCAAqC,QAAQ,IAAI,KAAK,KAAK;UAC3E;QACF,CAAC;MACH;;;;;;;;;;;;;;;MAgBQ,oBAAiB;AAEvB,YAAI,KAAK,yBAAyB;AAChC,kBAAQ,IAAI,2DAA2D;AACvE;QACF;AAGA,YAAI,KAAK,kBAAkB;AACzB,kBAAQ,IAAI,oEAAoE;AAChF;QACF;AAGA,YAAI,KAAK,gBAAgB;AACvB,wBAAc,KAAK,cAAc;AACjC,eAAK,iBAAiB;QACxB;AAEA,YAAI,KAAK,uBAAuB;AAC9B,uBAAa,KAAK,qBAAqB;AACvC,eAAK,wBAAwB;QAC/B;AAGA,YAAI,KAAK,yBAAyB,KAAK,IAAG,IAAK,KAAK,uBAAuB;AACzE,gBAAM,WAAW,KAAK,wBAAwB,KAAK,IAAG;AACtD,kBAAQ,IAAI,yCAAyC,KAAK,MAAM,WAAW,GAAI,CAAC,gBAAgB;AAChG,eAAK;AACL,eAAK,mBAAmB,WAAW,MAAK;AACtC,iBAAK,wBAAwB;AAC7B,iBAAK,kBAAiB;UACxB,GAAG,QAAQ;AACX;QACF;AAGA,YAAI;AACJ,YAAI,cAAc;AAElB,YAAI,KAAK,oBAAoB;AAI3B,cAAI,KAAK,qBAAqB,GAAG;AAC/B,oBAAQ,MAAM,sGAAsG;AACpH,0BAAc;UAChB,OAAO;AAEL,oBAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,iBAAiB,GAAG,GAAI;AAChE,oBAAQ,IAAI,sDAAsD,KAAK,eAAe,KAAK,oBAAoB,CAAC,KAAK;UACvH;QACF,OAAO;AAIL,gBAAM,2BAA2B;AACjC,cAAI,KAAK,qBAAqB,0BAA0B;AAEtD,oBAAQ,MAAM,8DAA8D,wBAAwB,+DAA+D;AACnK,0BAAc;UAChB,OAAO;AAEL,oBAAQ,KAAK,IAAI,MAAO,KAAK,IAAI,GAAG,KAAK,iBAAiB,GAAG,IAAK;AAClE,oBAAQ,IAAI,gDAAgD,QAAQ,GAAI,iBAAiB,KAAK,oBAAoB,CAAC,IAAI,wBAAwB,GAAG;UACpJ;QACF;AAEA,YAAI,CAAC,aAAa;AAEhB,eAAK,KAAK;YACR,MAAM;YACN,MAAM;YACN,QAAQ;YACR,eAAe;WAChB;AACD;QACF;AAEA,aAAK;AAEL,aAAK,mBAAmB,WAAW,MAAK;AACtC,kBAAQ,IAAI,4CAA4C;AACxD,eAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,KAAK,WAAW;YACjD,UAAU,KAAK;YACf,YAAY,KAAK;YACjB,QAAQ,KAAK;YACb,WAAW,KAAK;WACjB,EAAE,KAAK,MAAK;AACX,oBAAQ,IAAI,yCAAyC;AACrD,iBAAK,oBAAoB;AACzB,iBAAK,qBAAqB;AAC1B,iBAAK,sBAAsB;AAC3B,iBAAK,wBAAwB;UAC/B,CAAC,EAAE,MAAM,WAAQ;AACf,oBAAQ,MAAM,wCAAwC,MAAM,OAAO;UAErE,CAAC;QACH,GAAG,KAAM;MACX;;;;;;;MAQQ,iBAAc;AAEpB,YAAI,KAAK,gBAAgB;AACvB,wBAAc,KAAK,cAAc;QACnC;AAEA,aAAK,iBAAiB,YAAY,MAAK;AACrC,cAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,KAAA,QAAG,MAAM;AAC9C;UACF;AAGA,cAAI;AACF,iBAAK,GAAG,KAAI;AAGZ,gBAAI,KAAK,uBAAuB;AAC9B,2BAAa,KAAK,qBAAqB;YACzC;AAGA,iBAAK,wBAAwB,WAAW,MAAK;AAC3C,sBAAQ,IAAI,8EAA8E;AAC1F,kBAAI,KAAK,IAAI;AACX,qBAAK,GAAG,UAAS;cACnB;YACF,GAAG,wBAAwB;UAC7B,SAAS,OAAO;AACd,oBAAQ,MAAM,iDAAiD,KAAK;UACtE;QACF,GAAG,yBAAyB;MAC9B;;AAveF,IAAAC,SAAA,gBAAAF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9CA,IAAAG,SAAA,eAAAC;AAOA,IAAAD,SAAA,gBAAA;AA4CA,IAAAA,SAAA,aAAAE;AAoBA,IAAAF,SAAA,aAAAG;AAeA,IAAAH,SAAA,gBAAA;AAlGA,QAAAI,OAAA,aAAA,QAAA,IAAA,CAAA;AACA,QAAAC,SAAA,aAAA,QAAA,MAAA,CAAA;AACA,QAAAC,MAAA,aAAA,QAAA,IAAA,CAAA;AACA,QAAA,kBAAA,QAAA,eAAA;AAGA,QAAM,sBAAsB;AAM5B,aAAgBL,gBAAY;AAC1B,aAAO,QAAQ,IAAI,sBAAsBI,OAAK,KAAKC,IAAG,QAAO,GAAI,UAAU;IAC7E;AAKA,aAAgB,cAAc,YAAmB;AAC/C,UAAI,YAAY;AACd,eAAO;MACT;AACA,aAAOD,OAAK,KAAKJ,cAAY,GAAI,mBAAmB;IACtD;AAQA,aAAS,gBAAgB,YAAkB;AACzC,YAAM,MAAMI,OAAK,QAAQ,UAAU;AACnC,YAAM,QAAQ,CAACD,KAAG,WAAW,GAAG;AAEhC,UAAI,OAAO;AACT,QAAAA,KAAG,UAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAK,CAAE;MACpD;AAIA,UAAI,QAAQ,aAAa,UAAU;AACjC,cAAM,aAAaC,OAAK,KAAK,KAAK,SAAS;AAC3C,YAAI,SAAS,CAACD,KAAG,WAAW,UAAU,GAAG;AACvC,cAAI;AAEF,YAAAA,KAAG,cAAc,YAAY,IAAI,EAAE,MAAM,IAAK,CAAE;AAEhD,aAAA,GAAA,gBAAA,UAAS,6CAA6C,GAAG,KAAK;cAC5D,OAAO;cACP,SAAS;aACV;UACH,QAAQ;UAER;QACF;MACF;IACF;AAKO,mBAAeF,YAAW,YAAmB;AAClD,YAAM,WAAW,cAAc,UAAU;AAEzC,UAAI,CAACE,KAAG,WAAW,QAAQ,GAAG;AAC5B,eAAO;MACT;AAEA,UAAI;AACF,cAAM,UAAUA,KAAG,aAAa,UAAU,MAAM;AAChD,cAAM,SAAS,KAAK,MAAM,OAAO;AACjC,eAAO;MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,yBAAyB,KAAK;AAC5C,eAAO;MACT;IACF;AAKO,mBAAeD,YAAW,QAAuB,YAAmB;AACzE,YAAM,WAAW,cAAc,UAAU;AACzC,sBAAgB,QAAQ;AAExB,UAAI;AACF,cAAM,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC;AAC9C,QAAAC,KAAG,cAAc,UAAU,SAAS,EAAE,MAAM,IAAK,CAAE;MACrD,SAAS,OAAO;AACd,cAAM,IAAI,MAAM,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;MACpG;IACF;AAKO,mBAAe,cAAc,OAAa;AAG/C,aAAO,QAAQ,SAAS,MAAM,SAAS,CAAC;IAC1C;;;;;;;;;;AChFA,IAAAG,SAAA,qBAAA;AAjBA,QAAa,eAAb,cAAkC,MAAK;MACrC,YACS,MACP,SACO,SAA6B;AAEpC,cAAM,OAAO;AAJN,aAAA,OAAA;AAEA,aAAA,UAAA;AAGP,aAAK,OAAO;MACd;;AARF,IAAAA,SAAA,eAAA;AAiBA,aAAgB,mBAAmB,MAAiB,SAA6B;AAC/E,YAAM,WAAsC;QAC1C,qBAAqB;QACrB,gBAAgB;QAChB,kBAAkB;QAClB,mBAAmB;QACnB,uBAAuB;QACvB,iBAAiB;QACjB,gBAAgB;QAChB,oBAAoB;QACpB,yBAAyB;QACzB,iBAAiB;QACjB,mBAAmB;;QAEnB,mBAAmB;QACnB,sBAAsB;QACtB,mBAAmB;QACnB,iBAAiB;QACjB,iBAAiB;;AAGnB,UAAI,UAAU,SAAS,IAAI,KAAK,UAAU,IAAI;AAG9C,UAAI,SAAS;AACX,YAAI,QAAQ,oBAAoB,QAAQ,iBAAiB,SAAS,GAAG;AACnE,qBAAW;qBAAwB,QAAQ,iBAAiB,KAAK,IAAI,CAAC;QACxE;AACA,YAAI,QAAQ,oBAAoB,QAAQ,iBAAiB,SAAS,GAAG;AACnE,qBAAW;qBAAwB,QAAQ,iBAAiB,KAAK,IAAI,CAAC;QACxE;AACA,YAAI,QAAQ,YAAY;AACtB,qBAAW;UAAa,QAAQ,UAAU;QAC5C;MACF;AAEA,aAAO;IACT;;;;;;;;;;;;;;;;;;;;;;;;;;ACjDA,iBAAA,4BAAAC,QAAA;AAGA,QAAA,iBAAA;AAAS,WAAA,eAAAA,UAAA,eAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,eAAA;IAAW,EAAA,CAAA;AACpB,QAAA,qBAAA;AAAS,WAAA,eAAAA,UAAA,iBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,mBAAA;IAAa,EAAA,CAAA;AAGtB,iBAAA,gBAAAA,QAAA;AACA,iBAAA,kBAAAA,QAAA;AACA,iBAAA,yBAAAA,QAAA;AACA,iBAAA,sBAAAA,QAAA;AAGA,QAAA,YAAA;AAAS,WAAA,eAAAA,UAAA,WAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,UAAA;IAAO,EAAA,CAAA;;;;;AC3BhB;AAAA;AAAA;AAAA;AAAA;AAWA,eAAsB,YAAY,MAAgC;AAChE,SAAO,IAAI,QAAQ,CAACC,aAAY;AAC9B,UAAM,SAAa,kBAAa;AAEhC,WAAO,KAAK,SAAS,CAAC,QAAa;AACjC,UAAI,IAAI,SAAS,cAAc;AAC7B,QAAAA,SAAQ,IAAI;AAAA,MACd,OAAO;AACL,QAAAA,SAAQ,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AAED,WAAO,KAAK,aAAa,MAAM;AAC7B,aAAO,MAAM;AACb,MAAAA,SAAQ,KAAK;AAAA,IACf,CAAC;AAID,WAAO,OAAO,IAAI;AAAA,EACpB,CAAC;AACH;AAMO,SAAS,gBAAwB;AAEtC,MAAI,QAAQ,IAAI,MAAM;AACpB,WAAO,SAAS,QAAQ,IAAI,MAAM,EAAE;AAAA,EACtC;AAGA,SAAO;AACT;AA9CA,IAIAC;AAJA;AAAA;AAAA;AAIA,IAAAA,OAAqB;AAAA;AAAA;;;ACJrB;AAAA,iBAAAC,UAAAC,SAAA;AAAA,IAAAA,QAAA;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,aAAe;AAAA,MACf,MAAQ;AAAA,MACR,OAAS;AAAA,MACT,KAAO;AAAA,QACL,SAAW;AAAA,MACb;AAAA,MACA,SAAW;AAAA,QACT,OAAS;AAAA,QACT,KAAO;AAAA,QACP,OAAS;AAAA,QACT,WAAa;AAAA,MACf;AAAA,MACA,UAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,QAAU;AAAA,MACV,SAAW;AAAA,MACX,cAAgB;AAAA,QACd,OAAS;AAAA,QACT,WAAa;AAAA,QACb,KAAO;AAAA,QACP,QAAU;AAAA,QACV,KAAO;AAAA,QACP,IAAM;AAAA,QACN,KAAO;AAAA,MACT;AAAA,MACA,sBAAwB;AAAA,QACtB,6BAA6B;AAAA,MAC/B;AAAA,MACA,iBAAmB;AAAA,QACjB,iBAAiB;AAAA,QACjB,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,cAAc;AAAA,QACd,aAAa;AAAA,QACb,MAAQ;AAAA,QACR,YAAc;AAAA,MAChB;AAAA,MACA,SAAW;AAAA,QACT,MAAQ;AAAA,MACV;AAAA,MACA,OAAS;AAAA,QACP;AAAA,QACA;AAAA,MACF;AAAA,MACA,YAAc;AAAA,QACZ,MAAQ;AAAA,QACR,KAAO;AAAA,QACP,WAAa;AAAA,MACf;AAAA,IACF;AAAA;AAAA;;;ACjCA,SAAoB;AACpB,WAAsB;AACtB,aAAwB;AACxB,2BAAyB;AACzB,kBAA6B;AAK7B,SAAS,YAAY,KAAsB;AACzC,QAAM,YAAY;AAClB,SAAO,UAAU,KAAK,GAAG;AAC3B;AAaA,eAAsB,eAAgC;AACpD,QAAM,gBAAqB,cAAK,0BAAa,GAAG,YAAY;AAG5D,MAAI;AACF,QAAO,cAAW,aAAa,GAAG;AAChC,YAAM,aAAgB,gBAAa,eAAe,OAAO,EAAE,KAAK;AAChE,UAAI,YAAY;AAEd,YAAI,YAAY,UAAU,GAAG;AAC3B,iBAAO;AAAA,QACT;AAGA,gBAAQ,IAAI,2DAA2D;AACvE,cAAM,UAAU,kBAAkB;AAClC,QAAG,iBAAc,eAAe,SAAS,OAAO;AAChD,gBAAQ,IAAI,yBAAyB,UAAU,WAAM,OAAO,EAAE;AAC9D,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AAAA,EAEhB;AAGA,QAAM,YAAY,kBAAkB;AAGpC,MAAI;AACF,UAAM,MAAW,aAAQ,aAAa;AACtC,QAAI,CAAI,cAAW,GAAG,GAAG;AACvB,MAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AACA,IAAG,iBAAc,eAAe,WAAW,OAAO;AAAA,EACpD,SAAS,OAAO;AACd,YAAQ,MAAM,+CAA+C,KAAK;AAAA,EAEpE;AAEA,SAAO;AACT;AAcA,SAAS,kBAA0B;AACjC,MAAI;AACF,QAAI,QAAQ,aAAa,UAAU;AAEjC,YAAM,aAAS;AAAA,QACb;AAAA,QACA,EAAE,UAAU,SAAS,SAAS,IAAK;AAAA,MACrC,EAAE,KAAK;AACP,UAAI,UAAU,OAAO,SAAS,GAAG;AAC/B,eAAO;AAAA,MACT;AAAA,IACF,WAAW,QAAQ,aAAa,SAAS;AAEvC,UAAO,cAAW,iBAAiB,GAAG;AACpC,cAAM,YAAe,gBAAa,mBAAmB,OAAO,EAAE,KAAK;AACnE,YAAI,aAAa,UAAU,SAAS,GAAG;AACrC,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,UAAO,cAAW,0BAA0B,GAAG;AAC7C,cAAM,SAAY,gBAAa,4BAA4B,OAAO,EAAE,KAAK;AACzE,YAAI,UAAU,OAAO,SAAS,GAAG;AAC/B,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF,WAAW,QAAQ,aAAa,SAAS;AAEvC,YAAM,aAAS,+BAAS,2BAA2B;AAAA,QACjD,UAAU;AAAA,QACV,SAAS;AAAA,MACX,CAAC;AACD,YAAM,QAAQ,OAAO,KAAK,EAAE,MAAM,IAAI;AACtC,UAAI,MAAM,UAAU,GAAG;AACrB,cAAM,OAAO,MAAM,CAAC,EAAE,KAAK;AAC3B,YAAI,QAAQ,KAAK,SAAS,KAAK,SAAS,QAAQ;AAC9C,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AAEd,YAAQ,KAAK,uDAAuD,KAAK;AAAA,EAC3E;AAGA,SAAc,kBAAW;AAC3B;AAcA,SAAS,oBAA4B;AACnC,QAAM,SAAS,gBAAgB;AAG/B,MAAI,YAAY,MAAM,GAAG;AACvB,WAAO,OAAO,YAAY;AAAA,EAC5B;AAIA,QAAM,OAAc,kBAAW,QAAQ,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AAKpE,QAAM,OAAO;AAAA,IACX,KAAK,MAAM,GAAG,CAAC;AAAA,IACf,KAAK,MAAM,GAAG,EAAE;AAAA,IAChB,MAAM,KAAK,MAAM,IAAI,EAAE;AAAA;AAAA,KACrB,SAAS,KAAK,MAAM,IAAI,EAAE,GAAG,EAAE,IAAI,IAAO,GAAK,SAAS,EAAE,IAAI,KAAK,MAAM,IAAI,EAAE;AAAA;AAAA,IACjF,KAAK,MAAM,IAAI,EAAE;AAAA,EACnB,EAAE,KAAK,GAAG;AAEV,SAAO,KAAK,YAAY;AAC1B;;;ACzKA,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,IAAAC,eAA6B;AAmB7B,SAAS,sBAA8B;AACrC,SAAY,eAAK,2BAAa,GAAG,eAAe;AAClD;AAOA,SAAS,eAA6B;AACpC,QAAM,eAAe,oBAAoB;AAEzC,MAAI;AACF,QAAI,CAAI,eAAW,YAAY,GAAG;AAChC,aAAO,EAAE,UAAU,CAAC,EAAE;AAAA,IACxB;AAEA,UAAM,UAAa,iBAAa,cAAc,OAAO;AACrD,UAAM,OAAO,KAAK,MAAM,OAAO;AAG/B,QAAI,CAAC,KAAK,YAAY,CAAC,MAAM,QAAQ,KAAK,QAAQ,GAAG;AACnD,cAAQ,KAAK,4CAA4C;AACzD,aAAO,EAAE,UAAU,CAAC,EAAE;AAAA,IACxB;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,gCAAgC,KAAK;AACnD,WAAO,EAAE,UAAU,CAAC,EAAE;AAAA,EACxB;AACF;AAKA,SAAS,cAAc,MAA0B;AAC/C,QAAM,eAAe,oBAAoB;AAEzC,MAAI;AAEF,UAAM,MAAW,cAAQ,YAAY;AACrC,QAAI,CAAI,eAAW,GAAG,GAAG;AACvB,MAAG,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AAEA,IAAG,kBAAc,cAAc,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AAAA,EACvE,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,kCAAkC,KAAK,EAAE;AAAA,EAC3D;AACF;AA2BO,SAAS,WACd,WACA,aACA,SACgB;AAChB,QAAM,OAAO,aAAa;AAC1B,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAGnC,QAAM,iBAAiB,KAAK,SAAS,KAAK,OAAK,EAAE,SAAS,WAAW;AAErE,MAAI,gBAAgB;AAElB,mBAAe,KAAK;AACpB,mBAAe,cAAc;AAE7B,QAAI,SAAS,cAAc;AACzB,qBAAe,eAAe,QAAQ;AAAA,IACxC;AACA,kBAAc,IAAI;AAClB,WAAO;AAAA,EACT;AAIA,QAAM,oBAAoB,KAAK,SAAS,UAAU,OAAK,EAAE,OAAO,SAAS;AAEzE,MAAI,sBAAsB,IAAI;AAC5B,UAAM,eAAe,KAAK,SAAS,iBAAiB;AACpD,YAAQ,IAAI,6CAA6C,aAAa,IAAI,OAAO,WAAW,EAAE;AAG9F,SAAK,SAAS,OAAO,mBAAmB,CAAC;AAAA,EAC3C;AAGA,QAAM,cAAmB,eAAS,WAAW;AAC7C,QAAM,aAA6B;AAAA,IACjC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA;AAAA,IAEb,cAAc,SAAS;AAAA,EACzB;AAEA,OAAK,SAAS,KAAK,UAAU;AAC7B,gBAAc,IAAI;AAElB,SAAO;AACT;AAQO,SAAS,cAAc,aAA8B;AAC1D,QAAM,OAAO,aAAa;AAC1B,QAAM,gBAAgB,KAAK,SAAS;AAEpC,OAAK,WAAW,KAAK,SAAS,OAAO,OAAK,EAAE,SAAS,WAAW;AAEhE,MAAI,KAAK,SAAS,SAAS,eAAe;AACxC,kBAAc,IAAI;AAClB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AA6BO,SAAS,iBAAmC;AACjD,QAAM,OAAO,aAAa;AAC1B,SAAO,KAAK;AACd;AAOO,SAAS,aAAa,aAA2B;AACtD,QAAM,OAAO,aAAa;AAC1B,QAAM,UAAU,KAAK,SAAS,KAAK,OAAK,EAAE,SAAS,WAAW;AAE9D,MAAI,SAAS;AACX,YAAQ,eAAc,oBAAI,KAAK,GAAE,YAAY;AAC7C,kBAAc,IAAI;AAAA,EACpB;AACF;;;AC/NA,IAAAC,QAAsB;AAEtB,IAAAC,eAA6B;AA+EtB,SAAS,iBAAyB;AACvC,SAAY,eAAK,2BAAa,GAAG,YAAY;AAC/C;;;ACvFA,UAAqB;AACrB,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,IAAAC,eAA6B;AAE7B,IAAM,gBAAgB,MAAW,eAAK,2BAAa,GAAG,aAAa;AAsB5D,IAAM,YAAN,MAAgB;AAAA,EAAhB;AACL,SAAQ,SAA4B;AACpC,SAAQ,WAAW,oBAAI,IAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnD,GAAG,SAAiB,SAA+B;AACjD,SAAK,SAAS,IAAI,SAAS,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAuB;AAC3B,UAAM,aAAa,cAAc;AAGjC,QAAO,eAAW,UAAU,GAAG;AAC7B,MAAG,eAAW,UAAU;AAAA,IAC1B;AAGA,UAAM,MAAW,cAAQ,UAAU;AACnC,QAAI,CAAI,eAAW,GAAG,GAAG;AACvB,MAAG,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AAEA,SAAK,SAAa,iBAAa,YAAU;AACvC,WAAK,iBAAiB,MAAM;AAAA,IAC9B,CAAC;AAED,WAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,WAAK,OAAQ,OAAO,YAAY,MAAM;AAEpC,QAAG,cAAU,YAAY,GAAK;AAC9B,QAAAA,SAAQ;AAAA,MACV,CAAC;AAED,WAAK,OAAQ,GAAG,SAAS,MAAM;AAAA,IACjC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,OAAQ;AAElB,UAAM,aAAa,cAAc;AACjC,WAAO,IAAI,QAAQ,CAACA,aAAY;AAC9B,WAAK,OAAQ,MAAM,MAAM;AAEvB,YAAO,eAAW,UAAU,GAAG;AAC7B,UAAG,eAAW,UAAU;AAAA,QAC1B;AACA,QAAAA,SAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,QAA0B;AACjD,QAAI,SAAS;AAEb,WAAO,GAAG,QAAQ,OAAO,UAAU;AACjC,gBAAU,MAAM,SAAS;AAGzB,YAAM,eAAe,OAAO,QAAQ,IAAI;AACxC,UAAI,iBAAiB,GAAI;AAGzB,YAAM,UAAU,OAAO,MAAM,GAAG,YAAY;AAC5C,eAAS,OAAO,MAAM,eAAe,CAAC;AAEtC,UAAI;AACF,cAAM,UAAU,KAAK,MAAM,OAAO;AAClC,cAAM,WAAW,MAAM,KAAK,cAAc,OAAO;AAGjD,eAAO,MAAM,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,MAC9C,SAAS,OAAO;AACd,cAAM,gBAA6B;AAAA,UACjC,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAClD;AACA,eAAO,MAAM,KAAK,UAAU,aAAa,IAAI,IAAI;AAAA,MACnD;AAAA,IACF,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,cAAQ,MAAM,8BAA8B,KAAK;AAAA,IACnD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAc,SAA2C;AACrE,UAAM,UAAU,KAAK,SAAS,IAAI,QAAQ,OAAO;AAEjD,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,QACL,IAAI,QAAQ;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,oBAAoB,QAAQ,OAAO;AAAA,MAC5C;AAAA,IACF;AAEA,QAAI;AACF,YAAM,OAAO,MAAM,QAAQ,QAAQ,MAAM;AACzC,aAAO;AAAA,QACL,IAAI,QAAQ;AAAA,QACZ,SAAS;AAAA,QACT;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL,IAAI,QAAQ;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACF;;;ACzJA,IAAAC,gBAA8M;;;ACR9M,IAAAC,wBAAgC;AAChC,aAAwB;AAExB,IAAM,eAAe;AACrB,IAAM,eAAe;AAcrB,eAAsB,gBAAgB,gBAAoD;AACxF,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,GAAI;AAE3D,UAAM,WAAW,MAAM,MAAM,GAAG,YAAY,IAAI,YAAY,WAAW;AAAA,MACrE,QAAQ,WAAW;AAAA,IACrB,CAAC;AACD,iBAAa,SAAS;AAEtB,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,EAAE,gBAAgB,eAAe,gBAAgB,iBAAiB,MAAM;AAAA,IACjF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,gBAAgB,KAAK;AAE3B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,iBAAwB,UAAG,eAAe,cAAc;AAAA,IAC1D;AAAA,EACF,SAAS,OAAO;AAGd,WAAO,EAAE,gBAAgB,eAAe,gBAAgB,iBAAiB,OAAO,SAAS,KAAK;AAAA,EAChG;AACF;AAMO,SAAS,0BAAgC;AAC9C,MAAI;AACF,UAAM,YAAQ,6BAAM,OAAO,CAAC,UAAU,MAAM,YAAY,GAAG;AAAA,MACzD,UAAU;AAAA,MACV,OAAO;AAAA;AAAA,MAEP,OAAO,QAAQ,aAAa;AAAA,IAC9B,CAAC;AACD,UAAM,MAAM;AAAA,EACd,SAAS,OAAO;AAAA,EAEhB;AACF;;;AC7DA,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,eAA0B;AAI1B,IAAM,wBAAwB,KAAK,OAAO;AAS1C,SAAS,aAAa,UAAkB,aAAoC;AAE1E,QAAM,wBAA6B,cAAQ,WAAW;AAGtD,QAAM,eAAoB,iBAAW,QAAQ,IACpC,cAAQ,QAAQ,IAChB,cAAQ,aAAa,QAAQ;AAGtC,QAAM,iBAAsB,gBAAU,YAAY;AAIlD,MAAI,CAAC,eAAe,WAAW,wBAA6B,SAAG,KAC3D,mBAAmB,uBAAuB;AAC5C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAKA,eAAsB,eACpB,SACA,aAC8B;AAC9B,QAAM,EAAE,MAAM,UAAU,WAAW,UAAU,UAAU,sBAAsB,IAAI;AAGjF,QAAM,YAAY,aAAa,UAAU,WAAW;AACpD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AAEF,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,QAAW,aAAS,SAAS;AAEnC,QAAI,MAAM,YAAY,GAAG;AACvB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,MAAM,OAAO,SAAS;AACxB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,mBAAmB,MAAM,IAAI,2BAA2B,OAAO;AAAA,MACxE;AAAA,IACF;AAGA,UAAM,SAAY,iBAAa,SAAS;AACxC,QAAI;AAEJ,QAAI,aAAa,UAAU;AACzB,gBAAU,OAAO,SAAS,QAAQ;AAAA,IACpC,OAAO;AACL,gBAAU,OAAO,SAAS,MAAM;AAAA,IAClC;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,MAAM,MAAM;AAAA,IACd;AAAA,EACF,SAAS,OAAO;AAEd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;AAKA,eAAsB,gBACpB,SACA,aAC8B;AAC9B,QAAM,EAAE,MAAM,UAAU,SAAS,WAAW,QAAQ,aAAa,KAAK,IAAI;AAG1E,QAAM,YAAY,aAAa,UAAU,WAAW;AACpD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AAEF,QAAI,YAAY;AACd,YAAM,UAAe,cAAQ,SAAS;AACtC,UAAI,CAAI,eAAW,OAAO,GAAG;AAC3B,QAAG,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,MAC3C;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,aAAa,UAAU;AACzB,eAAS,OAAO,KAAK,SAAS,QAAQ;AAAA,IACxC,OAAO;AACL,eAAS,OAAO,KAAK,SAAS,MAAM;AAAA,IACtC;AAGA,UAAM,WAAW,GAAG,SAAS,QAAQ,KAAK,IAAI,CAAC;AAC/C,IAAG,kBAAc,UAAU,MAAM;AACjC,IAAG,eAAW,UAAU,SAAS;AAEjC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,cAAc,OAAO;AAAA,IACvB;AAAA,EACF,SAAS,OAAO;AAEd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,UAAM,cAAc,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,UAAU;AAC3E,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB,cAAc,cAAc;AAAA,IAC/E;AAAA,EACF;AACF;AAMA,eAAsB,eACpB,SACA,aAC8B;AAC9B,QAAM,EAAE,MAAM,SAAS,YAAY,OAAO,gBAAgB,MAAM,IAAI;AAGpE,QAAM,YAAY,aAAa,SAAS,WAAW;AACnD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AAEF,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,QAAW,aAAS,SAAS;AACnC,QAAI,CAAC,MAAM,YAAY,GAAG;AACxB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,UAA6E,CAAC;AAEpF,QAAI,WAAW;AAEb,YAAM,uBAAuB,WAAW,WAAW,SAAS,aAAa;AAAA,IAC3E,OAAO;AAEL,YAAM,aAAa,MAAS,aAAS,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAC/E,iBAAW,SAAS,YAAY;AAC9B,YAAI,CAAC,iBAAiB,MAAM,KAAK,WAAW,GAAG,EAAG;AAElD,cAAM,YAAiB,WAAK,WAAW,MAAM,IAAI;AACjD,cAAM,aAAa,MAAS,aAAS,KAAK,SAAS;AAEnD,gBAAQ,KAAK;AAAA,UACX,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM,YAAY,IAAI,cAAc;AAAA,UAC1C,MAAM,WAAW;AAAA,QACnB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;AAKA,eAAe,uBACb,UACA,aACA,SACA,eACe;AACf,QAAM,aAAa,MAAS,aAAS,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC;AAEjF,aAAW,SAAS,YAAY;AAC9B,QAAI,CAAC,iBAAiB,MAAM,KAAK,WAAW,GAAG,EAAG;AAElD,UAAM,YAAiB,WAAK,aAAa,MAAM,IAAI;AACnD,UAAM,eAAoB,eAAS,UAAU,SAAS;AAEtD,QAAI;AACF,YAAM,aAAa,MAAS,aAAS,KAAK,SAAS;AAEnD,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,MAAM,MAAM,YAAY,IAAI,cAAc;AAAA,QAC1C,MAAM,WAAW;AAAA,MACnB,CAAC;AAED,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,uBAAuB,UAAU,WAAW,SAAS,aAAa;AAAA,MAC1E;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAMA,eAAsB,iBACpB,SACA,aAC8B;AAC9B,QAAM,EAAE,SAAS,UAAU,aAAa,IAAI,IAAI;AAChD,QAAM,sBAAsB,KAAK,IAAI,KAAK,IAAI,GAAG,UAAU,GAAG,GAAI;AAGlE,QAAM,YAAY,aAAa,UAAU,WAAW;AACpD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AACF,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,QAAkB,CAAC;AACzB,UAAM,YAAY,YAAY,OAAO;AAErC,UAAM,qBAAqB,WAAW,WAAW,WAAW,OAAO,mBAAmB;AAEtF,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;AAcA,SAAS,YAAY,SAAyB;AAC5C,MAAI,IAAI;AACR,MAAI,WAAW;AAEf,SAAO,IAAI,QAAQ,QAAQ;AACzB,UAAM,OAAO,QAAQ,CAAC;AAGtB,QAAI,SAAS,OAAO,QAAQ,IAAI,CAAC,MAAM,KAAK;AAC1C,kBAAY;AACZ,WAAK;AAEL,UAAI,QAAQ,CAAC,MAAM,IAAK;AACxB;AAAA,IACF;AAGA,QAAI,SAAS,KAAK;AAChB,kBAAY;AACZ;AACA;AAAA,IACF;AAGA,QAAI,SAAS,KAAK;AAChB,kBAAY;AACZ;AACA;AAAA,IACF;AAGA,QAAI,SAAS,KAAK;AAChB,YAAM,WAAW,QAAQ,QAAQ,KAAK,CAAC;AACvC,UAAI,aAAa,IAAI;AACnB,cAAM,UAAU,QAAQ,MAAM,IAAI,GAAG,QAAQ,EAAE,MAAM,GAAG;AACxD,cAAM,UAAU,QAAQ,IAAI,SAAO,iBAAiB,GAAG,CAAC;AACxD,oBAAY,MAAM,QAAQ,KAAK,GAAG,CAAC;AACnC,YAAI,WAAW;AACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,SAAS,KAAK;AAChB,YAAM,WAAW,mBAAmB,SAAS,CAAC;AAC9C,UAAI,aAAa,IAAI;AACnB,YAAI,eAAe,QAAQ,MAAM,IAAI,GAAG,QAAQ;AAEhD,YAAI,aAAa,WAAW,GAAG,GAAG;AAChC,yBAAe,MAAM,aAAa,MAAM,CAAC;AAAA,QAC3C;AACA,oBAAY,IAAI,YAAY;AAC5B,YAAI,WAAW;AACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,gBAAgB,SAAS,IAAI,GAAG;AAClC,kBAAY,OAAO;AAAA,IACrB,OAAO;AACL,kBAAY;AAAA,IACd;AACA;AAAA,EACF;AAEA,SAAO,IAAI,OAAO,IAAI,QAAQ,GAAG;AACnC;AAKA,SAAS,iBAAiB,KAAqB;AAC7C,SAAO,IAAI,QAAQ,uBAAuB,MAAM;AAClD;AAKA,SAAS,mBAAmB,SAAiB,OAAuB;AAClE,MAAI,IAAI,QAAQ;AAEhB,MAAI,QAAQ,CAAC,MAAM,OAAO,QAAQ,CAAC,MAAM,IAAK;AAC9C,MAAI,QAAQ,CAAC,MAAM,IAAK;AAExB,SAAO,IAAI,QAAQ,QAAQ;AACzB,QAAI,QAAQ,CAAC,MAAM,IAAK,QAAO;AAC/B,QAAI,QAAQ,CAAC,MAAM,QAAQ,IAAI,IAAI,QAAQ,OAAQ;AACnD;AAAA,EACF;AACA,SAAO;AACT;AAKA,eAAe,qBACb,UACA,aACA,SACA,OACA,YACe;AACf,MAAI,MAAM,UAAU,WAAY;AAEhC,QAAM,UAAU,MAAS,aAAS,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC;AAE9E,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,UAAU,WAAY;AAChC,QAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAEhC,UAAM,YAAiB,WAAK,aAAa,MAAM,IAAI;AACnD,UAAM,eAAoB,eAAS,UAAU,SAAS;AAEtD,QAAI;AACF,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,qBAAqB,UAAU,WAAW,SAAS,OAAO,UAAU;AAAA,MAC5E,WAAW,QAAQ,KAAK,YAAY,KAAK,QAAQ,KAAK,MAAM,IAAI,GAAG;AACjE,cAAM,KAAK,YAAY;AAAA,MACzB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAMA,eAAsB,eACpB,SACA,aAC8B;AAC9B,QAAM;AAAA,IACJ;AAAA,IACA,MAAM;AAAA,IACN,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf,IAAI;AACJ,QAAM,sBAAsB,KAAK,IAAI,KAAK,IAAI,GAAG,UAAU,GAAG,GAAI;AAGlE,QAAM,YAAY,aAAa,YAAY,WAAW;AACtD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AACF,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,UAAkE,CAAC;AACzE,UAAM,cAAc,IAAI,OAAO,SAAS,gBAAgB,KAAK,GAAG;AAChE,UAAM,gBAAgB,YAAY,WAAW;AAE7C,UAAM,QAAW,aAAS,SAAS;AACnC,QAAI,MAAM,OAAO,GAAG;AAElB,YAAM,SAAS,WAAW,WAAW,aAAa,SAAS,mBAAmB;AAAA,IAChF,OAAO;AAEL,YAAM,uBAAuB,WAAW,WAAW,aAAa,eAAe,SAAS,mBAAmB;AAAA,IAC7G;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;AAKA,eAAe,SACb,UACA,UACA,SACA,SACA,YACe;AACf,MAAI,QAAQ,UAAU,WAAY;AAElC,QAAM,eAAoB,eAAS,UAAU,QAAQ;AAGrD,QAAM,aAAgB,qBAAiB,UAAU,EAAE,UAAU,OAAO,CAAC;AACrE,QAAM,KAAc,yBAAgB;AAAA,IAClC,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AAED,MAAI,aAAa;AACjB,mBAAiB,QAAQ,IAAI;AAC3B;AACA,QAAI,QAAQ,UAAU,YAAY;AAChC,SAAG,MAAM;AACT;AAAA,IACF;AAEA,QAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,cAAQ,KAAK;AAAA,QACX,MAAM,gBAAqB,eAAS,QAAQ;AAAA,QAC5C,MAAM;AAAA,QACN,SAAS,KAAK,MAAM,GAAG,GAAG;AAAA;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAKA,eAAe,uBACb,UACA,aACA,eACA,aACA,SACA,YACe;AACf,MAAI,QAAQ,UAAU,WAAY;AAElC,QAAM,UAAU,MAAS,aAAS,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC;AAE9E,aAAW,SAAS,SAAS;AAC3B,QAAI,QAAQ,UAAU,WAAY;AAClC,QAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAEhC,UAAM,YAAiB,WAAK,aAAa,MAAM,IAAI;AAEnD,QAAI;AACF,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,uBAAuB,UAAU,WAAW,eAAe,aAAa,SAAS,UAAU;AAAA,MACnG,WAAW,YAAY,KAAK,MAAM,IAAI,GAAG;AACvC,cAAM,SAAS,UAAU,WAAW,eAAe,SAAS,UAAU;AAAA,MACxE;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAMA,eAAsB,eACpB,SACA,aAC8B;AAC9B,QAAM,EAAE,MAAM,UAAU,WAAW,WAAW,aAAa,MAAM,IAAI;AAGrE,QAAM,YAAY,aAAa,UAAU,WAAW;AACpD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AAEF,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,QAAW,aAAS,SAAS;AACnC,QAAI,MAAM,YAAY,GAAG;AACvB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,gBAAgB,KAAK,OAAO;AAClC,QAAI,MAAM,OAAO,eAAe;AAC9B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,sCAAsC,MAAM,IAAI,2BAA2B,aAAa;AAAA,MACjG;AAAA,IACF;AAGA,UAAM,UAAa,iBAAa,WAAW,MAAM;AAGjD,UAAM,cAAc,QAAQ,MAAM,SAAS,EAAE,SAAS;AAEtD,QAAI,gBAAgB,GAAG;AACrB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,cAAc,KAAK,CAAC,YAAY;AAClC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,oBAAoB,WAAW;AAAA,MACxC;AAAA,IACF;AAIA,UAAM,aAAa,aACf,QAAQ,MAAM,SAAS,EAAE,KAAK,SAAS,IACvC,QAAQ,QAAQ,WAAW,SAAS;AAExC,UAAM,eAAe,aAAa,cAAc;AAGhD,UAAM,WAAW,GAAG,SAAS,QAAQ,KAAK,IAAI,CAAC;AAC/C,IAAG,kBAAc,UAAU,YAAY,MAAM;AAC7C,IAAG,eAAW,UAAU,SAAS;AAEjC,UAAM,UAAU,OAAO,WAAW,YAAY,MAAM;AAEpD,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;AAMA,eAAsB,iBACpB,SACA,aAC8B;AAC9B,QAAM,EAAE,MAAM,UAAU,YAAY,MAAM,IAAI;AAG9C,QAAM,YAAY,aAAa,UAAU,WAAW;AACpD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,wBAA6B,cAAQ,WAAW;AACtD,MAAI,cAAc,uBAAuB;AACvC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AAEF,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,QAAW,aAAS,SAAS;AACnC,UAAM,cAAc,MAAM,YAAY;AAGtC,QAAI,eAAe,CAAC,WAAW;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,aAAa;AACf,MAAG,WAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACvD,OAAO;AACL,MAAG,eAAW,SAAS;AAAA,IACzB;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,UAAU,cAAc,cAAc;AAAA,IACxC;AAAA,EACF,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;AAMA,eAAsB,gBACpB,SACA,aAC8B;AAC9B,QAAM,EAAE,MAAM,SAAS,OAAO,OAAO,IAAI;AAGzC,QAAM,YAAY,aAAa,SAAS,WAAW;AACnD,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AAEF,QAAO,eAAW,SAAS,GAAG;AAC5B,YAAM,QAAW,aAAS,SAAS;AACnC,UAAI,MAAM,YAAY,GAAG;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,SAAS;AAAA;AAAA,QACX;AAAA,MACF,OAAO;AACL,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,SAAS,MAAM,CAAC;AAChC,IAAG,cAAU,WAAW,EAAE,WAAW,MAAM,MAAM,QAAQ,CAAC;AAE1D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,EACF,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,oBAAoB,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,YAAY;AACnF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oBAAoB,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;;;AC3yBA,IAAAC,wBAAsB;AAItB,IAAM,kBAAkB;AAExB,IAAM,cAAc;AAKpB,eAAsB,WACpB,SACA,aAC8B;AAC9B,QAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,CAAC;AAAA,EACT,IAAI;AAGJ,QAAM,mBAAmB,KAAK,IAAI,KAAK,IAAI,SAAS,GAAI,GAAG,WAAW;AAEtE,SAAO,IAAI,QAAQ,CAACC,aAAY;AAC9B,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,WAAW;AACf,QAAI,WAAW;AAEf,UAAM,OAAO,CAAC,WAAgC;AAC5C,UAAI,SAAU;AACd,iBAAW;AACX,MAAAA,SAAQ,MAAM;AAAA,IAChB;AAEA,QAAI;AAEF,YAAM,WAAO,6BAAM,KAAK;AAAA,QACtB,OAAO;AAAA,QACP;AAAA,QACA,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,IAAI;AAAA,QAC9B,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,MAClC,CAAC;AAGD,YAAM,YAAY,WAAW,MAAM;AACjC,mBAAW;AACX,aAAK,KAAK,SAAS;AAEnB,mBAAW,MAAM;AACf,cAAI,CAAC,UAAU;AACb,iBAAK,KAAK,SAAS;AAAA,UACrB;AAAA,QACF,GAAG,GAAI;AAAA,MACT,GAAG,gBAAgB;AAGnB,WAAK,OAAO,GAAG,QAAQ,CAAC,SAAiB;AACvC,kBAAU,KAAK,SAAS;AAExB,YAAI,OAAO,SAAS,KAAK,OAAO,MAAM;AACpC,mBAAS,OAAO,MAAM,MAAM,OAAO,IAAI;AAAA,QACzC;AAAA,MACF,CAAC;AAGD,WAAK,OAAO,GAAG,QAAQ,CAAC,SAAiB;AACvC,kBAAU,KAAK,SAAS;AAExB,YAAI,OAAO,SAAS,KAAK,OAAO,MAAM;AACpC,mBAAS,OAAO,MAAM,MAAM,OAAO,IAAI;AAAA,QACzC;AAAA,MACF,CAAC;AAGD,WAAK,GAAG,SAAS,CAAC,MAAM,WAAW;AACjC,qBAAa,SAAS;AACtB,aAAK;AAAA,UACH,SAAS,SAAS,KAAK,CAAC;AAAA,UACxB;AAAA,UACA;AAAA,UACA,UAAU,QAAQ;AAAA,UAClB;AAAA,UACA,OAAO,WACH,2BAA2B,gBAAgB,OAC3C,SAAS,IACP,4BAA4B,IAAI,KAChC;AAAA,QACR,CAAC;AAAA,MACH,CAAC;AAGD,WAAK,GAAG,SAAS,CAAC,UAAU;AAC1B,qBAAa,SAAS;AACtB,aAAK;AAAA,UACH,SAAS;AAAA,UACT;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV,OAAO,8BAA8B,MAAM,OAAO;AAAA,QACpD,CAAC;AAAA,MACH,CAAC;AAAA,IACH,SAAS,OAAO;AACd,WAAK;AAAA,QACH,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,OAAO,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAC3F,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;;;AC5GA,IAAAC,wBAAqB;AACrB,kBAA0B;AAC1B,IAAAC,eAA0C;AAG1C,IAAM,gBAAY,uBAAU,0BAAI;AAWhC,eAAsB,oBAAoB,aAKvC;AACD,MAAI;AAEF,UAAM,YAAY,MAAM,aAAa;AAGrC,UAAM,SAAS,UAAM,yBAAW;AAChC,QAAI,CAAC,QAAQ,cAAc;AACzB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,SAAS;AAAA,MACX;AAAA,IACF;AAGA,QAAI;AACF,YAAM,UAAU,oBAAoB,EAAE,KAAK,aAAa,SAAS,IAAM,CAAC;AAAA,IAC1E,SAAS,YAAY;AAEnB,cAAQ,KAAK,mCAAmC,UAAU;AAAA,IAC5D;AAGA,QAAI,gBAAgB;AACpB,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,UAAU,6BAA6B,EAAE,KAAK,aAAa,SAAS,IAAK,CAAC;AACnG,sBAAgB,OAAO,KAAK;AAAA,IAC9B,QAAQ;AACN,aAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,SAAS;AAAA,MACX;AAAA,IACF;AAGA,QAAI,kBAAkB,UAAU,kBAAkB,UAAU;AAC1D,aAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,SAAS,0BAA0B,aAAa;AAAA,MAClD;AAAA,IACF;AAGA,QAAI,YAAsB,CAAC;AAC3B,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM;AAAA,QACvB;AAAA,QACA,EAAE,KAAK,aAAa,SAAS,IAAM;AAAA,MACrC;AACA,kBAAY,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AAAA,IACtD,QAAQ;AAEN,kBAAY,CAAC;AAAA,IACf;AAGA,UAAM,SAAS,OAAO,WAAW;AACjC,UAAM,WAAW,MAAM,MAAM,GAAG,MAAM,4BAA4B;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,iBAAiB,UAAU,OAAO,YAAY;AAAA,QAC9C,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,QAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,SAAS,cAAc,UAAU,OAAO,WAAW,SAAS,UAAU;AAAA,MACxE;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,SAAS,KAAK;AAOnC,QAAI,OAAO,iBAAiB,OAAO,gBAAgB,GAAG;AACpD,cAAQ,IAAI,sBAAsB,OAAO,aAAa,yBAAyB;AAAA,IACjF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,eAAe,OAAO,iBAAiB;AAAA,MACvC,YAAY,OAAO,cAAc;AAAA,MACjC,SAAS,OAAO,WAAW;AAAA,IAC7B;AAAA,EAEF,SAAS,OAAY;AACnB,YAAQ,MAAM,8CAA8C,KAAK;AACjE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,SAAS,MAAM,WAAW;AAAA,IAC5B;AAAA,EACF;AACF;;;ACzIA,IAAAC,wBAAoC;AACpC,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,SAAoB;AACpB,YAAuB;AACvB,UAAqB;AAMrB,IAAM,gBAAwD;AAAA,EAC5D,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA,OAAO;AAAA,IACL,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA,OAAO;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AACF;AAKA,SAAS,mBAA2B;AAClC,SAAY,WAAQ,WAAQ,GAAG,YAAY,KAAK;AAClD;AAKA,SAAS,qBAA6B;AACpC,QAAM,aAAgB,YAAS,MAAM,UAAU,oBAAoB;AACnE,SAAY,WAAK,iBAAiB,GAAG,UAAU;AACjD;AAKA,SAAS,sBAAqC;AAC5C,MAAI;AAEF,UAAM,UAAa,YAAS,MAAM,UAAU,UAAU;AACtD,UAAM,aAAgB,YAAS,MAAM,UAAU,oBAAoB;AACnE,UAAM,aAAS,iCAAU,SAAS,CAAC,UAAU,GAAG,EAAE,UAAU,QAAQ,CAAC;AACrE,QAAI,OAAO,WAAW,KAAK,OAAO,OAAO,KAAK,GAAG;AAE/C,aAAO,OAAO,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,EAAE,KAAK;AAAA,IAClD;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAKA,SAAS,yBAAkC;AACzC,QAAM,kBAAkB,mBAAmB;AAC3C,MAAI;AACF,IAAG,eAAW,iBAAoB,cAAU,IAAI;AAChD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,kBAAkB,YAA6B;AACtD,MAAI;AACF,UAAM,aAAS,iCAAU,YAAY,CAAC,SAAS,GAAG,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC;AACtF,WAAO,OAAO,WAAW,KAAK,OAAO,OAAO,SAAS,aAAa;AAAA,EACpE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,iBAAgC;AACvC,QAAMC,YAAc,YAAS;AAC7B,QAAMC,QAAU,QAAK;AAErB,QAAM,eAAe,cAAcD,SAAQ;AAC3C,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AAEA,SAAO,aAAaC,KAAI,KAAK;AAC/B;AAKA,eAAe,aAAa,KAAa,UAAiC;AACxE,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,UAAM,iBAAiB,CAAC,YAAoB,gBAAgB,MAAM;AAChE,UAAI,gBAAgB,GAAG;AACrB,eAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC;AAAA,MACF;AAEA,YAAM,SAAS,IAAI,IAAI,UAAU;AACjC,YAAM,UAAU;AAAA,QACd,UAAU,OAAO;AAAA,QACjB,MAAM,OAAO,WAAW,OAAO;AAAA,QAC/B,SAAS;AAAA,UACP,cAAc;AAAA,QAChB;AAAA,MACF;AAEA,MAAM,UAAI,SAAS,CAAC,aAAa;AAE/B,YAAI,SAAS,eAAe,OAAO,SAAS,eAAe,KAAK;AAC9D,gBAAM,cAAc,SAAS,QAAQ;AACrC,cAAI,aAAa;AACf,2BAAe,aAAa,gBAAgB,CAAC;AAC7C;AAAA,UACF;AAAA,QACF;AAEA,YAAI,SAAS,eAAe,KAAK;AAC/B,iBAAO,IAAI,MAAM,4BAA4B,SAAS,UAAU,EAAE,CAAC;AACnE;AAAA,QACF;AAEA,cAAM,OAAU,sBAAkB,QAAQ;AAC1C,iBAAS,KAAK,IAAI;AAClB,aAAK,GAAG,UAAU,MAAM;AACtB,eAAK,MAAM;AACX,UAAAA,SAAQ;AAAA,QACV,CAAC;AACD,aAAK,GAAG,SAAS,CAAC,QAAQ;AACxB,UAAG,eAAW,QAAQ;AACtB,iBAAO,GAAG;AAAA,QACZ,CAAC;AAAA,MACH,CAAC,EAAE,GAAG,SAAS,MAAM;AAAA,IACvB;AAEA,mBAAe,GAAG;AAAA,EACpB,CAAC;AACH;AAKA,eAAe,WAAW,aAAqB,SAAgC;AAC7E,SAAW,MAAE;AAAA,IACX,MAAM;AAAA,IACN,KAAK;AAAA,EACP,CAAC;AACH;AAKA,eAAe,sBAAuC;AACpD,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yBAA4B,YAAS,CAAC,IAAO,QAAK,CAAC,EAAE;AAAA,EACvE;AAEA,QAAM,SAAS,iBAAiB;AAChC,QAAM,kBAAkB,mBAAmB;AAG3C,EAAG,cAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAExC,QAAM,QAAQ,IAAI,SAAS,MAAM;AAEjC,MAAI,OAAO;AAET,UAAM,WAAgB,WAAK,QAAQ,iBAAiB;AAEpD,YAAQ,IAAI,yCAAyC,GAAG,KAAK;AAC7D,UAAM,aAAa,KAAK,QAAQ;AAEhC,YAAQ,IAAI,oCAAoC;AAChD,UAAM,WAAW,UAAU,MAAM;AAGjC,IAAG,eAAW,QAAQ;AAAA,EACxB,OAAO;AAEL,YAAQ,IAAI,yCAAyC,GAAG,KAAK;AAC7D,UAAM,aAAa,KAAK,eAAe;AAAA,EACzC;AAGA,MAAO,YAAS,MAAM,SAAS;AAC7B,IAAG,cAAU,iBAAiB,GAAK;AAAA,EACrC;AAGA,MAAI,CAAC,kBAAkB,eAAe,GAAG;AACvC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAEA,UAAQ,IAAI,6CAA6C;AACzD,SAAO;AACT;AAMA,eAAsB,oBAAqC;AAEzD,QAAM,aAAa,oBAAoB;AACvC,MAAI,cAAc,kBAAkB,UAAU,GAAG;AAC/C,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB,mBAAmB;AACzC,MAAI,uBAAuB,KAAK,kBAAkB,aAAa,GAAG;AAChE,WAAO;AAAA,EACT;AAGA,SAAO,oBAAoB;AAC7B;;;ACnOA,IAAAC,wBAA8C;AAC9C,oBAA6B;AAC7B,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,IAAAC,MAAoB;AAapB,IAAM,iBAAsB,WAAQ,YAAQ,GAAG,YAAY,SAAS;AAMpE,IAAM,mBAAmB;AAYzB,IAAM,2BAA4C;AAAA,EAChD,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,mBAAmB;AACrB;AAgBO,IAAM,gBAAN,cAA4B,2BAAa;AAAA,EAU9C,YAAY,QAAmC;AAC7C,UAAM;AAVR,SAAQ,eAAyC,oBAAI,IAAI;AACzD,SAAQ,kBAAiC;AAMzC;AAAA;AAAA;AAAA,SAAQ,aAAsD,oBAAI,IAAI;AAIpE,SAAK,kBAAkB,EAAE,GAAG,0BAA0B,GAAG,OAAO;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAqB;AAC3B,QAAI;AACF,UAAI,CAAI,eAAW,cAAc,GAAG;AAClC,gBAAQ,IAAI,2CAA2C,cAAc,EAAE;AACvE,QAAG,cAAU,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAQ,IAAI,oDAAoD;AAAA,MAClE;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,kDAAkD,cAAc,KAAK,KAAK;AAExF,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,WAA2B;AAChD,WAAY,WAAK,gBAAgB,GAAG,SAAS,MAAM;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,WAAmB,KAAmB;AACzD,QAAI;AACF,WAAK,aAAa;AAClB,YAAM,UAAU,KAAK,eAAe,SAAS;AAC7C,MAAG,kBAAc,SAAS,IAAI,SAAS,GAAG,MAAM;AAChD,cAAQ,IAAI,6BAA6B,GAAG,QAAQ,SAAS,OAAO,OAAO,EAAE;AAAA,IAC/E,SAAS,OAAO;AACd,cAAQ,MAAM,gDAAgD,SAAS,KAAK,KAAK;AAAA,IAGnF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,WAAkC;AACpD,QAAI;AACF,YAAM,UAAU,KAAK,eAAe,SAAS;AAC7C,UAAI,CAAI,eAAW,OAAO,GAAG;AAC3B,eAAO;AAAA,MACT;AACA,YAAM,MAAM,SAAY,iBAAa,SAAS,MAAM,EAAE,KAAK,GAAG,EAAE;AAChE,aAAO,MAAM,GAAG,IAAI,OAAO;AAAA,IAC7B,SAAS,OAAO;AACd,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,WAAyB;AAC7C,QAAI;AACF,YAAM,UAAU,KAAK,eAAe,SAAS;AAC7C,UAAO,eAAW,OAAO,GAAG;AAC1B,QAAG,eAAW,OAAO;AACrB,gBAAQ,IAAI,wCAAwC,SAAS,EAAE;AAAA,MACjE;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,iDAAiD,SAAS,KAAK,KAAK;AAAA,IACpF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,KAAsB;AAC7C,QAAI;AAEF,cAAQ,KAAK,KAAK,CAAC;AACnB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,KAAa,SAAyB,WAAoB;AAC1E,QAAI;AACF,cAAQ,KAAK,KAAK,MAAM;AACxB,cAAQ,IAAI,wBAAwB,MAAM,WAAW,GAAG,EAAE;AAC1D,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,2BAAqC;AAC3C,QAAI;AAEF,YAAM,aAAS,gCAAS,wBAAwB,EAAE,UAAU,OAAO,CAAC;AACpE,aAAO,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,IAAI,SAAO,SAAS,KAAK,EAAE,CAAC,EAAE,OAAO,SAAO,CAAC,MAAM,GAAG,CAAC;AAAA,IAC1F,QAAQ;AAEN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,KAA4B;AACjD,QAAI;AAEF,YAAM,aAAS,gCAAS,SAAS,GAAG,aAAa,EAAE,UAAU,OAAO,CAAC,EAAE,KAAK;AAG5E,YAAM,YAAY,OAAO,MAAM,oCAAoC;AACnE,UAAI,WAAW;AACb,eAAO,SAAS,UAAU,CAAC,GAAG,EAAE;AAAA,MAClC;AACA,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,MAAwB;AACpD,UAAM,eAAe,KAAK,yBAAyB;AACnD,WAAO,aAAa,OAAO,SAAO,KAAK,eAAe,GAAG,MAAM,IAAI;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,sBAAsB,MAAiC;AACnE,UAAM,aAAa,KAAK,sBAAsB,IAAI;AAClD,UAAM,SAAmB,CAAC;AAE1B,eAAW,OAAO,YAAY;AAE5B,YAAM,YAAY,MAAM,KAAK,KAAK,aAAa,OAAO,CAAC,EAAE,KAAK,OAAK,EAAE,KAAK,QAAQ,GAAG;AAErF,cAAQ,IAAI,yCAAyC,GAAG,YAAY,IAAI,cAAc,SAAS,GAAG;AAElG,WAAK,UAAU,KAAK,SAAS;AAC7B,YAAM,IAAI,QAAQ,CAAAC,aAAW,WAAWA,UAAS,GAAG,CAAC;AAErD,UAAI,KAAK,iBAAiB,GAAG,GAAG;AAC9B,aAAK,UAAU,KAAK,SAAS;AAC7B,cAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAG,CAAC;AAAA,MACvD;AAEA,aAAO,KAAK,GAAG;AAAA,IACjB;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,IAAI,0BAA0B,OAAO,MAAM,oCAAoC,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,IACrH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,2BAAyE;AAC7E,UAAM,UAAoB,CAAC;AAE3B,QAAI;AACF,WAAK,aAAa;AAGlB,YAAM,WAAc,gBAAY,cAAc,EAAE,OAAO,OAAK,EAAE,SAAS,MAAM,CAAC;AAE9E,iBAAW,WAAW,UAAU;AAC9B,cAAM,YAAY,QAAQ,QAAQ,QAAQ,EAAE;AAC5C,cAAM,MAAM,KAAK,YAAY,SAAS;AAEtC,YAAI,OAAO,KAAK,iBAAiB,GAAG,GAAG;AAErC,cAAI,CAAC,KAAK,aAAa,IAAI,SAAS,GAAG;AACrC,oBAAQ,IAAI,8CAA8C,GAAG,QAAQ,SAAS,cAAc;AAC5F,iBAAK,UAAU,KAAK,SAAS;AAG7B,kBAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAI,CAAC;AACtD,gBAAI,KAAK,iBAAiB,GAAG,GAAG;AAC9B,mBAAK,UAAU,KAAK,SAAS;AAAA,YAC/B;AAEA,oBAAQ,KAAK,GAAG;AAAA,UAClB;AAAA,QACF;AAGA,aAAK,cAAc,SAAS;AAAA,MAC9B;AAGA,YAAM,cAAc,KAAK,yBAAyB;AAClD,YAAM,cAAc,MAAM,KAAK,KAAK,aAAa,OAAO,CAAC,EACtD,IAAI,OAAK,EAAE,KAAK,GAAG,EACnB,OAAO,CAAC,QAAuB,QAAQ,MAAS;AAEnD,iBAAW,OAAO,aAAa;AAC7B,YAAI,CAAC,YAAY,SAAS,GAAG,KAAK,CAAC,QAAQ,SAAS,GAAG,GAAG;AACxD,kBAAQ,IAAI,2DAA2D,GAAG,cAAc;AACxF,eAAK,UAAU,KAAK,SAAS;AAE7B,gBAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAG,CAAC;AACrD,cAAI,KAAK,iBAAiB,GAAG,GAAG;AAC9B,iBAAK,UAAU,KAAK,SAAS;AAAA,UAC/B;AAEA,kBAAQ,KAAK,GAAG;AAAA,QAClB;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,GAAG;AACtB,gBAAQ,IAAI,8BAA8B,QAAQ,MAAM,oCAAoC,QAAQ,KAAK,IAAI,CAAC,EAAE;AAAA,MAClH;AAAA,IAEF,SAAS,OAAO;AACd,cAAQ,MAAM,gDAAgD,KAAK;AAAA,IACrE;AAEA,WAAO,EAAE,SAAS,QAAQ,QAAQ,MAAM,QAAQ;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,OAA0B;AAC1C,SAAK,KAAK,UAAU,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAA4B;AAChC,SAAK,kBAAkB,MAAM,kBAAkB;AAG/C,UAAM,UAAU,MAAM,KAAK,yBAAyB;AACpD,QAAI,QAAQ,UAAU,GAAG;AACvB,cAAQ,IAAI,6CAA6C,QAAQ,OAAO,qBAAqB;AAAA,IAC/F;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,YAA4B;AACxD,UAAM,QAAQ,KAAK,gBAAgB,iBACjC,KAAK,IAAI,KAAK,gBAAgB,mBAAmB,UAAU;AAC7D,WAAO,KAAK,IAAI,OAAO,KAAK,gBAAgB,UAAU;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAiB,WAAkC;AAC/D,UAAM,QAAQ,KAAK,aAAa,IAAI,SAAS;AAC7C,QAAI,CAAC,SAAS,MAAM,sBAAsB;AACxC;AAAA,IACF;AAEA,QAAI,MAAM,cAAc,KAAK,gBAAgB,YAAY;AACvD,cAAQ,IAAI,yBAAyB,KAAK,gBAAgB,UAAU,iBAAiB,SAAS,aAAa;AAC3G,WAAK,UAAU;AAAA,QACb,MAAM;AAAA,QACN;AAAA,QACA,OAAO,uBAAuB,KAAK,gBAAgB,UAAU;AAAA,MAC/D,CAAC;AACD,YAAM,QAAQ,iBAAiB,SAAS,mCAAmC;AAC3E,WAAK,aAAa,OAAO,SAAS;AAClC;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,sBAAsB,MAAM,UAAU;AACzD,YAAQ,IAAI,yBAAyB,SAAS,OAAO,KAAK,eAAe,MAAM,aAAa,CAAC,IAAI,KAAK,gBAAgB,UAAU,GAAG;AAEnI,SAAK,UAAU,EAAE,MAAM,gBAAgB,UAAU,CAAC;AAClD,UAAM,QAAQ,iBAAiB,cAAc;AAE7C,UAAM,iBAAiB,WAAW,YAAY;AAC5C,YAAM;AAGN,YAAM,SAAS,MAAM,KAAK,mBAAmB,MAAM,SAAS,KAAK;AACjE,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAI,wBAAwB,SAAS,+BAA+B,OAAO,GAAG,EAAE;AACxF,cAAM,aAAa;AAAA,MACrB;AAAA,IAEF,GAAG,KAAK;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,mBACZ,SACA,eAC4B;AAC5B,UAAM,EAAE,WAAW,OAAO,KAAM,OAAO,eAAe,IAAI;AAG1D,QAAI,CAAC,KAAK,iBAAiB;AACzB,UAAI;AACF,aAAK,kBAAkB,MAAM,kBAAkB;AAAA,MACjD,SAAS,OAAO;AACd,cAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,eAAO,EAAE,SAAS,OAAO,OAAO,8BAA8B,YAAY,GAAG;AAAA,MAC/E;AAAA,IACF;AAEA,WAAO,IAAI,QAAQ,CAACA,aAAY;AAC9B,YAAM,aAAqD;AAAA,QACzD;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,QACR,WAAW,oBAAI,KAAK;AAAA,QACpB,SAAS;AAAA;AAAA,MACX;AAIA,YAAMC,eAAU,6BAAM,KAAK,iBAAkB;AAAA,QAC3C;AAAA,QACA;AAAA,QACA,oBAAoB,IAAI;AAAA,MAC1B,GAAG;AAAA,QACD,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,MAClC,CAAC;AAED,iBAAW,UAAUA;AACrB,iBAAW,MAAMA,SAAQ;AAGzB,UAAIA,SAAQ,KAAK;AACf,aAAK,aAAa,WAAWA,SAAQ,GAAG;AAAA,MAC1C;AAGA,YAAM,QAAqB,iBAAiB;AAAA,QAC1C,MAAM;AAAA,QACN;AAAA,QACA,sBAAsB;AAAA,QACtB,YAAY;AAAA,QACZ,gBAAgB;AAAA,MAClB;AACA,YAAM,OAAO;AACb,WAAK,aAAa,IAAI,WAAW,KAAK;AAEtC,UAAI,WAAW;AACf,UAAI,eAAe;AACnB,UAAI,eAAe;AAGnB,YAAM,cAAc,CAAC,SAAiB;AACpC,YAAI,SAAU;AAEd,cAAM,QAAQ,KAAK,MAAM,gBAAgB;AACzC,YAAI,OAAO;AACT,qBAAW;AACX,qBAAW,MAAM,MAAM,CAAC;AACxB,qBAAW,SAAS;AAEpB,2BAAiB,WAAW;AAC5B,kBAAQ,WAAW,GAAG;AAEtB,eAAK,UAAU;AAAA,YACb,MAAM;AAAA,YACN;AAAA,YACA,KAAK,WAAW;AAAA,UAClB,CAAC;AAED,UAAAD,SAAQ,EAAE,SAAS,MAAM,KAAK,WAAW,IAAI,CAAC;AAAA,QAChD;AAAA,MACF;AAEA,MAAAC,SAAQ,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAC3C,wBAAgB,KAAK,SAAS;AAC9B,oBAAY,YAAY;AAAA,MAC1B,CAAC;AAED,MAAAA,SAAQ,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAC3C,wBAAgB,KAAK,SAAS;AAC9B,oBAAY,YAAY;AAAA,MAC1B,CAAC;AAGD,MAAAA,SAAQ,GAAG,QAAQ,CAAC,MAAM,WAAW;AACnC,cAAM,eAAe,WAAW,WAAW;AAC3C,mBAAW,SAAS;AAEpB,cAAM,eAAe,KAAK,aAAa,IAAI,SAAS;AAEpD,YAAI,CAAC,UAAU;AAEb,gBAAM,WAAW,mCAAmC,IAAI;AACxD,qBAAW,SAAS;AACpB,qBAAW,QAAQ;AAGnB,cAAI,gBAAgB,CAAC,aAAa,sBAAsB;AACtD,iBAAK,iBAAiB,SAAS;AAAA,UACjC,OAAO;AACL,iBAAK,aAAa,OAAO,SAAS;AAClC,6BAAiB,SAAS,QAAQ;AAClC,iBAAK,UAAU,EAAE,MAAM,SAAS,WAAW,OAAO,SAAS,CAAC;AAAA,UAC9D;AAEA,UAAAD,SAAQ,EAAE,SAAS,OAAO,OAAO,SAAS,CAAC;AAAA,QAC7C,WAAW,cAAc;AAGvB,cAAI,gBAAgB,CAAC,aAAa,sBAAsB;AACtD,oBAAQ,IAAI,YAAY,SAAS,gDAAgD;AACjF,6BAAiB,cAAc;AAC/B,iBAAK,iBAAiB,SAAS;AAAA,UACjC,OAAO;AACL,iBAAK,aAAa,OAAO,SAAS;AAClC,6BAAiB,cAAc;AAC/B,iBAAK,UAAU,EAAE,MAAM,WAAW,UAAU,CAAC;AAAA,UAC/C;AAAA,QACF;AAAA,MACF,CAAC;AAED,MAAAC,SAAQ,GAAG,SAAS,CAAC,UAAU;AAC7B,mBAAW,SAAS;AACpB,mBAAW,QAAQ,MAAM;AAEzB,cAAM,eAAe,KAAK,aAAa,IAAI,SAAS;AAGpD,YAAI,gBAAgB,CAAC,aAAa,sBAAsB;AACtD,eAAK,iBAAiB,SAAS;AAAA,QACjC,OAAO;AACL,eAAK,aAAa,OAAO,SAAS;AAClC,2BAAiB,SAAS,MAAM,OAAO;AACvC,eAAK,UAAU,EAAE,MAAM,SAAS,WAAW,OAAO,MAAM,QAAQ,CAAC;AAAA,QACnE;AAEA,YAAI,CAAC,UAAU;AACb,UAAAD,SAAQ,EAAE,SAAS,OAAO,OAAO,MAAM,QAAQ,CAAC;AAAA,QAClD;AAAA,MACF,CAAC;AAGD,iBAAW,MAAM;AACf,YAAI,CAAC,UAAU;AACb,UAAAC,SAAQ,KAAK;AACb,gBAAM,WAAW;AAEjB,qBAAW,SAAS;AACpB,qBAAW,QAAQ;AAEnB,gBAAM,eAAe,KAAK,aAAa,IAAI,SAAS;AAGpD,cAAI,gBAAgB,CAAC,aAAa,sBAAsB;AACtD,iBAAK,iBAAiB,SAAS;AAAA,UACjC,OAAO;AACL,iBAAK,aAAa,OAAO,SAAS;AAClC,6BAAiB,SAAS,QAAQ;AAClC,iBAAK,UAAU,EAAE,MAAM,SAAS,WAAW,OAAO,SAAS,CAAC;AAAA,UAC9D;AAEA,UAAAD,SAAQ,EAAE,SAAS,OAAO,OAAO,SAAS,CAAC;AAAA,QAC7C;AAAA,MACF,GAAG,GAAK;AAAA,IACV,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,SAAyD;AACzE,UAAM,EAAE,UAAU,IAAI;AAGtB,UAAM,eAAe,KAAK,WAAW,IAAI,SAAS;AAClD,QAAI,cAAc;AAChB,cAAQ,IAAI,4DAA4D,SAAS,EAAE;AACnF,aAAO;AAAA,IACT;AAGA,UAAM,eAAe,KAAK,oBAAoB,OAAO;AACrD,SAAK,WAAW,IAAI,WAAW,YAAY;AAE3C,QAAI;AACF,aAAO,MAAM;AAAA,IACf,UAAE;AAEA,WAAK,WAAW,OAAO,SAAS;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,oBAAoB,SAAyD;AACzF,UAAM,EAAE,WAAW,OAAO,IAAK,IAAI;AAGnC,UAAM,gBAAgB,KAAK,aAAa,IAAI,SAAS;AACrD,QAAI,eAAe;AACjB,UAAI,cAAc,KAAK,WAAW,aAAa;AAC7C,eAAO,EAAE,SAAS,MAAM,KAAK,cAAc,KAAK,IAAI;AAAA,MACtD;AAEA,YAAM,KAAK,WAAW,SAAS;AAAA,IACjC;AAGA,UAAM,YAAY,KAAK,YAAY,SAAS;AAC5C,QAAI,aAAa,KAAK,iBAAiB,SAAS,GAAG;AACjD,cAAQ,IAAI,4CAA4C,SAAS,QAAQ,SAAS,6BAA6B;AAC/G,WAAK,UAAU,WAAW,SAAS;AACnC,YAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAG,CAAC;AACrD,UAAI,KAAK,iBAAiB,SAAS,GAAG;AACpC,aAAK,UAAU,WAAW,SAAS;AAAA,MACrC;AACA,WAAK,cAAc,SAAS;AAAA,IAC9B;AAIA,UAAM,eAAe,MAAM,KAAK,sBAAsB,IAAI;AAC1D,QAAI,aAAa,SAAS,GAAG;AAC3B,cAAQ,IAAI,iDAAiD,aAAa,MAAM,wBAAwB,IAAI,EAAE;AAG9G,YAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAI,CAAC;AAAA,IACxD;AAIA,UAAM,UAAU,MAAM,KAAK,yBAAyB;AACpD,QAAI,QAAQ,UAAU,GAAG;AACvB,cAAQ,IAAI,6CAA6C,QAAQ,OAAO,qBAAqB;AAAA,IAC/F;AAEA,WAAO,KAAK,mBAAmB,OAAO;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WAAW,WAAkC;AACjD,UAAM,QAAQ,KAAK,aAAa,IAAI,SAAS;AAG7C,QAAI,CAAC,OAAO;AACV,YAAM,YAAY,KAAK,YAAY,SAAS;AAC5C,UAAI,aAAa,KAAK,iBAAiB,SAAS,GAAG;AACjD,gBAAQ,IAAI,6CAA6C,SAAS,QAAQ,SAAS,eAAe;AAClG,aAAK,UAAU,WAAW,SAAS;AACnC,cAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAI,CAAC;AACtD,YAAI,KAAK,iBAAiB,SAAS,GAAG;AACpC,eAAK,UAAU,WAAW,SAAS;AAAA,QACrC;AAAA,MACF;AACA,WAAK,cAAc,SAAS;AAC5B;AAAA,IACF;AAGA,UAAM,uBAAuB;AAG7B,QAAI,MAAM,gBAAgB;AACxB,mBAAa,MAAM,cAAc;AACjC,YAAM,iBAAiB;AAAA,IACzB;AAEA,UAAM,SAAS,MAAM;AAGrB,QAAI,OAAO,WAAW,CAAC,OAAO,QAAQ,QAAQ;AAC5C,aAAO,QAAQ,KAAK,SAAS;AAG7B,YAAM,IAAI,QAAc,CAACA,aAAY;AACnC,cAAM,UAAU,WAAW,MAAM;AAC/B,cAAI,OAAO,WAAW,CAAC,OAAO,QAAQ,QAAQ;AAC5C,mBAAO,QAAQ,KAAK,SAAS;AAAA,UAC/B;AACA,UAAAA,SAAQ;AAAA,QACV,GAAG,GAAI;AAEP,eAAO,QAAQ,KAAK,QAAQ,MAAM;AAChC,uBAAa,OAAO;AACpB,UAAAA,SAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAGA,SAAK,cAAc,SAAS;AAE5B,SAAK,aAAa,OAAO,SAAS;AAClC,SAAK,UAAU,EAAE,MAAM,WAAW,UAAU,CAAC;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAgC;AACpC,UAAM,aAAa,MAAM,KAAK,KAAK,aAAa,KAAK,CAAC;AACtD,UAAM,QAAQ,IAAI,WAAW,IAAI,SAAO,KAAK,WAAW,GAAG,CAAC,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,WAAsC;AAC9C,UAAM,QAAQ,KAAK,aAAa,IAAI,SAAS;AAC7C,QAAI,CAAC,MAAO,QAAO;AAGnB,UAAM,EAAE,SAAAC,UAAS,GAAG,KAAK,IAAI,MAAM;AACnC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,gBAA8B;AAC5B,WAAO,MAAM,KAAK,KAAK,aAAa,OAAO,CAAC,EAAE,IAAI,WAAS;AACzD,YAAM,EAAE,SAAAA,UAAS,GAAG,KAAK,IAAI,MAAM;AACnC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,WAA4B;AACpC,WAAO,KAAK,aAAa,IAAI,SAAS;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAgC;AAC9B,WAAO,MAAM,KAAK,KAAK,aAAa,KAAK,CAAC;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,WAAkC;AAC7C,WAAO,KAAK,aAAa,IAAI,SAAS,GAAG,KAAK,OAAO;AAAA,EACvD;AACF;AAGA,IAAI,wBAA8C;AAK3C,SAAS,mBAAkC;AAChD,MAAI,CAAC,uBAAuB;AAC1B,4BAAwB,IAAI,cAAc;AAAA,EAC5C;AACA,SAAO;AACT;;;AClwBA,IAAAC,eAA2B;AAW3B,eAAsB,eAAe,WAAkC;AAErE,MAAI,CAAC,aAAa,cAAc,SAAS;AACvC;AAAA,EACF;AAEA,QAAM,SAAS,UAAM,yBAAW;AAChC,MAAI,CAAC,QAAQ,cAAc;AACzB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,SAAS,OAAO,WAAW;AACjC,UAAM,MAAM,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,iBAAiB,UAAU,OAAO,YAAY;AAAA,MAChD;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;;;AC9BA,IAAAC,wBAAyB;AACzB,IAAAC,QAAsB;AACtB,IAAAC,MAAoB;AAKpB,IAAI,mBAAkC;AAKtC,SAAS,oBAAoB,YAA6B;AACxD,MAAI;AAEF,IAAG,eAAW,YAAe,cAAU,IAAI;AAG3C,UAAM,cAAU,gCAAS,IAAI,UAAU,eAAe;AAAA,MACpD,UAAU;AAAA,MACV,SAAS;AAAA,MACT,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC,EAAE,KAAK;AAGR,QAAI,WAAW,WAAW,KAAK,OAAO,GAAG;AACvC,cAAQ,IAAI,uCAAuC,UAAU,MAAM,OAAO,EAAE;AAC5E,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,qBAAsC;AAE1D,MAAI,kBAAkB;AACpB,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,iBAAa,gCAAS,gBAAgB;AAAA,MAC1C,UAAU;AAAA,MACV,SAAS;AAAA,MACT,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC,EAAE,KAAK;AAER,QAAI,cAAc,oBAAoB,UAAU,GAAG;AACjD,yBAAmB;AACnB,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,eAAe;AAAA;AAAA,IAEd,WAAK,WAAW,MAAM,MAAM,gBAAgB,QAAQ,QAAQ;AAAA;AAAA,IAE5D,WAAK,WAAW,MAAM,MAAM,MAAM,MAAM,gBAAgB,QAAQ,QAAQ;AAAA;AAAA,IAExE,WAAK,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,gBAAgB,QAAQ,QAAQ;AAAA,EACrF;AAEA,aAAW,eAAe,cAAc;AACtC,QAAO,eAAW,WAAW,KAAK,oBAAoB,WAAW,GAAG;AAClE,yBAAmB;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI;AACF,UAAM,gBAAY,gCAAS,iDAAiD;AAAA,MAC1E,UAAU;AAAA,MACV,SAAS;AAAA;AAAA,MACT,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC,EAAE,KAAK;AAER,QAAI,aAAa,WAAW,KAAK,SAAS,GAAG;AAE3C,yBAAmB;AACnB,cAAQ,IAAI,iDAAiD,SAAS,EAAE;AACxE,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;;;ACpGA,IAAAC,wBAAoC;AACpC,IAAAC,QAAsB;AACtB,IAAAC,MAAoB;AACpB,IAAAC,MAAoB;AA0BpB,IAAI,WAAgC;AAK7B,SAAS,kBAAgC;AAC9C,MAAI,CAAC,UAAU;AACb,eAAW,IAAI,aAAa;AAAA,EAC9B;AACA,SAAO;AACT;AAKO,IAAM,eAAN,MAAmB;AAAA,EAMxB,cAAc;AALd,SAAQ,WAAW,oBAAI,IAAyC;AAChE,SAAQ,YAAY,oBAAI,IAA0B;AAClD,SAAQ,cAAc;AAIpB,SAAK,SAAc,WAAQ,YAAQ,GAAG,YAAY,YAAY;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAA4B;AAChC,QAAI,KAAK,aAAa;AACpB;AAAA,IACF;AAEA,YAAQ,IAAI,gCAAgC;AAG5C,QAAI,CAAI,eAAW,KAAK,MAAM,GAAG;AAC/B,MAAG,cAAU,KAAK,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,IAC/C;AAGA,UAAM,KAAK,yBAAyB;AAGpC,QAAI;AACF,YAAM,mBAAmB;AACzB,cAAQ,IAAI,4CAA4C;AAAA,IAC1D,SAAS,OAAO;AACd,cAAQ,KAAK,6CAA6C,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IAE1G;AAEA,SAAK,cAAc;AACnB,YAAQ,IAAI,4BAA4B;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,SAAuD;AACxE,UAAM,EAAE,WAAW,UAAU,WAAW,aAAa,SAAS,aAAa,cAAc,SAAS,YAAY,QAAQ,IAAI;AAG1H,QAAI,KAAK,SAAS,IAAI,SAAS,GAAG;AAChC,aAAO,EAAE,SAAS,OAAO,OAAO,yBAAyB;AAAA,IAC3D;AAIA,UAAM,aAAa,aAAa,cAAe,aAAqB;AACpE,QAAI,CAAC,YAAY;AACf,aAAO,EAAE,SAAS,OAAO,OAAO,sFAAsF;AAAA,IACxH;AAEA,IAAC,YAAoB,aAAa;AAGlC,QAAI;AACF,YAAM,mBAAmB;AAAA,IAC3B,SAAS,OAAO;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAClD;AAAA,IACF;AAGA,UAAM,UAAuC;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,WAAW,oBAAI,KAAK;AAAA,MACpB,gBAAgB,oBAAI,KAAK;AAAA,IAC3B;AAEA,SAAK,SAAS,IAAI,WAAW,OAAO;AACpC,YAAQ,IAAI,kCAAkC,SAAS,QAAQ,SAAS,EAAE;AAG1E,WAAO,KAAK,YAAY;AAAA,MACtB;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YAAY,SAAyD;AACzE,UAAM,EAAE,WAAW,SAAS,gBAAgB,iBAAiB,SAAS,YAAY,QAAQ,IAAI;AAE9F,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,SAAS,OAAO,OAAO,oBAAoB;AAAA,IACtD;AAGA,YAAQ,iBAAiB,oBAAI,KAAK;AAClC,YAAQ,SAAS;AAEjB,QAAI;AAEF,YAAM,aAAa,MAAM,mBAAmB;AAI5C,YAAM,OAAiB;AAAA,QACrB;AAAA;AAAA,QACA;AAAA,QAAmB;AAAA;AAAA,QACnB;AAAA;AAAA,MACF;AAGA,UAAI,kBAAkB,QAAQ,cAAc;AAC1C,aAAK,KAAK,mBAAmB,QAAQ,YAAY;AAAA,MACnD;AAGA,UAAI,iBAAiB;AACnB,aAAK,KAAK,YAAY,eAAe;AACrC,gBAAQ,kBAAkB;AAAA,MAC5B;AAGA,WAAK,KAAK,MAAM,OAAO;AAEvB,cAAQ,IAAI,mDAAmD,SAAS,EAAE;AAC1E,cAAQ,IAAI,2BAA2B,UAAU,IAAI,KAAK,KAAK,GAAG,EAAE,UAAU,GAAG,GAAG,CAAC,KAAK;AAG1F,UAAI;AACJ,UAAI;AAEJ,UAAI,WAAW,WAAW,MAAM,GAAG;AAEjC,mBAAW;AACX,oBAAY,CAAC,SAAS,WAAW,QAAQ,QAAQ,EAAE,GAAG,GAAG,IAAI;AAAA,MAC/D,OAAO;AAEL,mBAAW;AACX,oBAAY;AAAA,MACd;AAIA,YAAM,YAAiB,WAAQ,YAAQ,GAAG,SAAS;AACnD,YAAM,kBAAuB,WAAK,WAAW,mBAAmB;AAGhE,UAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,QAAG,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,MAC7C;AAGA,YAAM,qBAAqB,KAAK,UAAU;AAAA,QACxC,eAAe;AAAA,UACb,aAAa,QAAQ,YAAY;AAAA,QACnC;AAAA,MACF,GAAG,MAAM,CAAC;AACV,MAAG,kBAAc,iBAAiB,oBAAoB,EAAE,MAAM,IAAM,CAAC;AACrE,cAAQ,IAAI,8EAA8E;AAG1F,YAAM,mBAAe,6BAAM,UAAU,WAAW;AAAA,QAC9C,KAAK,QAAQ;AAAA,QACb,KAAK;AAAA,UACH,GAAG,QAAQ;AAAA;AAAA,UAEX,UAAU;AAAA,UACV,aAAa;AAAA,QACf;AAAA,QACA,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAChC,CAAC;AAGD,WAAK,UAAU,IAAI,WAAW,YAAY;AAI1C,mBAAa,OAAO,IAAI;AAGxB,UAAI,aAAa,KAAK;AACpB,gBAAQ,MAAM,aAAa;AAC3B,aAAK,aAAa,WAAW,aAAa,GAAG;AAAA,MAC/C;AAGA,mBAAa,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,gBAAQ,MAAM,0BAA0B,KAAK,SAAS,CAAC,EAAE;AAAA,MAC3D,CAAC;AAGD,mBAAa,GAAG,SAAS,CAAC,UAAU;AAClC,gBAAQ,MAAM,uCAAuC,KAAK;AAAA,MAC5D,CAAC;AAGD,UAAI,eAAe;AACnB,UAAI;AAGJ,mBAAa,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,wBAAgB,KAAK,SAAS;AAG9B,cAAM,QAAQ,aAAa,MAAM,IAAI;AACrC,uBAAe,MAAM,IAAI,KAAK;AAE9B,mBAAW,QAAQ,OAAO;AACxB,cAAI,CAAC,KAAK,KAAK,EAAG;AAElB,cAAI;AACF,kBAAM,SAAS,KAAK,MAAM,IAAI;AAI9B,oBAAQ,OAAO,MAAM;AAAA,cACnB,KAAK;AAEH,oBAAI,OAAO,SAAS,SAAS;AAE3B,6BAAW,SAAS,OAAO,QAAQ,SAAS;AAC1C,wBAAI,MAAM,SAAS,UAAU,MAAM,MAAM;AACvC,8BAAQ,MAAM,IAAI;AAAA,oBACpB;AAAA,kBACF;AAAA,gBACF;AACA;AAAA,cAEF,KAAK;AAEH,oBAAI,OAAO,OAAO,MAAM;AACtB,0BAAQ,OAAO,MAAM,IAAI;AAAA,gBAC3B;AACA;AAAA,cAEF,KAAK;AAEH,oBAAI,OAAO,YAAY;AACrB,uCAAqB,OAAO;AAC5B,0BAAQ,kBAAkB;AAAA,gBAC5B;AAEA,oBAAI,OAAO,QAAQ,YAAY;AAC7B,uCAAqB,OAAO,OAAO;AACnC,0BAAQ,kBAAkB;AAAA,gBAC5B;AACA;AAAA,cAEF,KAAK;AAEH,oBAAI,OAAO,YAAY;AACrB,uCAAqB,OAAO;AAC5B,0BAAQ,kBAAkB;AAAA,gBAC5B;AACA;AAAA,cAEF,KAAK;AAEH,wBAAQ,OAAO,OAAO,WAAW,OAAO,WAAW,gCAAgC;AACnF;AAAA,cAEF;AAEE,wBAAQ,IAAI,4CAA4C,OAAO,IAAI,EAAE;AAAA,YACzE;AAAA,UACF,SAAS,YAAY;AAEnB,gBAAI,KAAK,KAAK,GAAG;AACf,sBAAQ,OAAO,IAAI;AAAA,YACrB;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAGD,UAAI,eAAe;AACnB,mBAAa,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,wBAAgB,KAAK,SAAS;AAAA,MAChC,CAAC;AAGD,mBAAa,GAAG,QAAQ,CAAC,MAAM,WAAW;AACxC,gBAAQ,IAAI,iDAAiD,SAAS,UAAU,IAAI,YAAY,MAAM,EAAE;AAGxG,aAAK,UAAU,OAAO,SAAS;AAC/B,aAAK,cAAc,SAAS;AAE5B,YAAI,SAAS,GAAG;AACd,kBAAQ,SAAS;AACjB,qBAAW,sBAAsB,QAAQ,eAAe;AAAA,QAC1D,WAAW,WAAW,UAAU;AAC9B,kBAAQ,SAAS;AAEjB,qBAAW,sBAAsB,QAAQ,eAAe;AAAA,QAC1D,OAAO;AACL,kBAAQ,SAAS;AACjB,gBAAM,WAAW,aAAa,KAAK,KAAK,4BAA4B,IAAI;AACxE,kBAAQ,QAAQ;AAAA,QAClB;AAAA,MACF,CAAC;AAGD,mBAAa,GAAG,SAAS,CAAC,UAAU;AAClC,gBAAQ,MAAM,4CAA4C,SAAS,KAAK,KAAK;AAC7E,gBAAQ,SAAS;AACjB,aAAK,UAAU,OAAO,SAAS;AAC/B,aAAK,cAAc,SAAS;AAC5B,gBAAQ,MAAM,OAAO;AAAA,MACvB,CAAC;AAED,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,cAAQ,SAAS;AACjB,YAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,cAAQ,QAAQ;AAChB,aAAO,EAAE,SAAS,OAAO,OAAO,SAAS;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,WAAkC;AACnD,UAAMC,WAAU,KAAK,UAAU,IAAI,SAAS;AAC5C,QAAIA,YAAW,CAACA,SAAQ,QAAQ;AAC9B,cAAQ,IAAI,mCAAmC,SAAS,cAAc;AACtE,MAAAA,SAAQ,KAAK,QAAQ;AAAA,IACvB;AAEA,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,SAAS;AACX,cAAQ,SAAS;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,YAAY,WAAkC;AAClD,UAAMA,WAAU,KAAK,UAAU,IAAI,SAAS;AAC5C,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAE3C,QAAI,SAAS;AACX,cAAQ,SAAS;AAAA,IACnB;AAEA,QAAIA,YAAW,CAACA,SAAQ,QAAQ;AAC9B,cAAQ,IAAI,mCAAmC,SAAS,EAAE;AAG1D,MAAAA,SAAQ,KAAK,QAAQ;AAGrB,YAAM,IAAI,QAAc,CAACC,aAAY;AACnC,cAAM,UAAU,WAAW,MAAM;AAC/B,cAAI,CAACD,SAAQ,QAAQ;AACnB,oBAAQ,IAAI,wCAAwC,SAAS,EAAE;AAC/D,YAAAA,SAAQ,KAAK,SAAS;AAAA,UACxB;AACA,UAAAC,SAAQ;AAAA,QACV,GAAG,GAAI;AAEP,QAAAD,SAAQ,KAAK,QAAQ,MAAM;AACzB,uBAAa,OAAO;AACpB,UAAAC,SAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAGA,SAAK,SAAS,OAAO,SAAS;AAC9B,SAAK,UAAU,OAAO,SAAS;AAC/B,SAAK,cAAc,SAAS;AAE5B,YAAQ,IAAI,0BAA0B,SAAS,UAAU;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAiC;AACrC,UAAM,aAAa,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;AAClD,UAAM,QAAQ,IAAI,WAAW,IAAI,QAAM,KAAK,YAAY,EAAE,CAAC,CAAC;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAA6C;AACtD,WAAO,KAAK,SAAS,IAAI,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiC;AAC/B,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAA4B;AACrC,WAAO,KAAK,SAAS,IAAI,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,2BAAyD;AAC7D,QAAI,UAAU;AAEd,QAAI,CAAI,eAAW,KAAK,MAAM,GAAG;AAC/B,aAAO,EAAE,QAAQ;AAAA,IACnB;AAEA,UAAM,WAAc,gBAAY,KAAK,MAAM,EAAE,OAAO,OAAK,EAAE,SAAS,MAAM,CAAC;AAE3E,eAAW,WAAW,UAAU;AAC9B,YAAM,UAAe,WAAK,KAAK,QAAQ,OAAO;AAE9C,UAAI;AACF,cAAM,SAAY,iBAAa,SAAS,OAAO,EAAE,KAAK;AACtD,cAAM,MAAM,SAAS,QAAQ,EAAE;AAE/B,YAAI,CAAC,MAAM,GAAG,GAAG;AAEf,cAAI;AACF,oBAAQ,KAAK,KAAK,CAAC;AAEnB,oBAAQ,IAAI,2CAA2C,GAAG,EAAE;AAC5D,oBAAQ,KAAK,KAAK,SAAS;AAC3B;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAGA,QAAG,eAAW,OAAO;AAAA,MACvB,SAAS,OAAO;AAEd,gBAAQ,KAAK,0CAA0C,OAAO,KAAK,KAAK;AAAA,MAC1E;AAAA,IACF;AAEA,QAAI,UAAU,GAAG;AACf,cAAQ,IAAI,6BAA6B,OAAO,uBAAuB;AAAA,IACzE;AAEA,WAAO,EAAE,QAAQ;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,WAAmB,KAAmB;AACzD,UAAM,UAAe,WAAK,KAAK,QAAQ,GAAG,SAAS,MAAM;AACzD,IAAG,kBAAc,SAAS,IAAI,SAAS,CAAC;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,WAAyB;AAC7C,UAAM,UAAe,WAAK,KAAK,QAAQ,GAAG,SAAS,MAAM;AACzD,QAAI;AACF,UAAO,eAAW,OAAO,GAAG;AAC1B,QAAG,eAAW,OAAO;AAAA,MACvB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AC9iBA,IAAAC,wBAA8C;AAC9C;AACA,IAAAC,eAAyC;;;ACCzC,IAAAC,OAAoB;AACpB,IAAAC,SAAsB;AACtB,IAAAC,MAAoB;;;ACLpB,IAAAC,MAAoB;AACpB,IAAAC,SAAsB;AAStB,eAAsB,aACpB,QACA,aACiC;AACjC,MAAI;AACF,UAAM,MAAM,GAAG,MAAM;AAErB,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,iBAAiB,UAAU,WAAW;AAAA,QACtC,gBAAgB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,KAAK,yCAAyC,SAAS,MAAM,EAAE;AACvE,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,UAAU,KAAK,YAAY,CAAC;AAElC,YAAQ,IAAI,uBAAuB,OAAO,KAAK,OAAO,EAAE,MAAM,uBAAuB;AACrF,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,KAAK,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACnG,WAAO,CAAC;AAAA,EACV;AACF;AAQO,SAAS,aACd,YACA,SACM;AACN,MAAI,OAAO,KAAK,OAAO,EAAE,WAAW,GAAG;AACrC;AAAA,EACF;AAEA,QAAM,aAAa,OAAO,QAAQ,OAAO,EACtC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAErB,QAAI,cAAc,KAAK,KAAK,KAAK,MAAM,SAAS,IAAI,GAAG;AAErD,YAAM,UAAU,MACb,QAAQ,OAAO,MAAM,EACrB,QAAQ,MAAM,KAAK,EACnB,QAAQ,OAAO,KAAK;AACvB,aAAO,GAAG,GAAG,KAAK,OAAO;AAAA,IAC3B;AACA,WAAO,GAAG,GAAG,IAAI,KAAK;AAAA,EACxB,CAAC,EACA,KAAK,IAAI,IAAI;AAEhB,QAAM,UAAe,YAAK,YAAY,MAAM;AAC5C,EAAG,kBAAc,SAAS,YAAY,EAAE,MAAM,IAAM,CAAC;AACrD,UAAQ,IAAI,qBAAqB,OAAO,KAAK,OAAO,EAAE,MAAM,gBAAgB,OAAO,EAAE;AACvF;;;AD9CA,IAAM,oBAAoB;AAC1B,IAAM,YAAiB,YAAQ,YAAQ,GAAG,YAAY,OAAO;AAK7D,SAAS,iBAAiB,WAA2B;AACnD,SAAY,YAAK,WAAW,YAAY,SAAS,OAAO;AAC1D;AAKA,SAAS,iBAAuB;AAC9B,MAAI,CAAI,gBAAW,SAAS,GAAG;AAC7B,IAAG,eAAU,WAAW,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EAC1D;AACF;AAKA,SAAS,UAAU,WAAqC;AACtD,MAAI;AACF,UAAM,YAAY,iBAAiB,SAAS;AAC5C,QAAI,CAAI,gBAAW,SAAS,GAAG;AAC7B,aAAO;AAAA,IACT;AAEA,UAAM,UAAa,kBAAa,WAAW,OAAO;AAClD,UAAM,OAAO,KAAK,MAAM,OAAO;AAG/B,QAAI,CAAC,KAAK,QAAQ,OAAO,KAAK,SAAS,YAAY,CAAC,KAAK,WAAW;AAClE,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,WAAW,WAAmB,MAAoC;AACzE,MAAI;AACF,mBAAe;AACf,UAAM,YAAY,iBAAiB,SAAS;AAC5C,UAAM,OAAkB;AAAA,MACtB;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,IACF;AACA,IAAG,mBAAc,WAAW,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAAA,EAC5E,SAAS,OAAO;AAEd,YAAQ,KAAK,sCAAsC,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,EACnG;AACF;AAKA,SAAS,aAAa,OAAkB,YAA6B;AACnE,QAAM,QAAQ,KAAK,IAAI,IAAI,MAAM;AACjC,SAAO,QAAQ,aAAa;AAC9B;AAUA,eAAsB,sBACpB,QACA,aACA,UAA2B,CAAC,GACH;AACzB,QAAM;AAAA,IACJ,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,EACd,IAAI;AAGJ,MAAI,SAAS;AACX,UAAM,QAAQ,UAAU,SAAS;AACjC,QAAI,SAAS,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS,GAAG;AAC/C,aAAO;AAAA,QACL,SAAS,MAAM;AAAA,QACf,WAAW;AAAA,QACX,UAAU,KAAK,IAAI,IAAI,MAAM;AAAA,MAC/B;AAAA,IACF;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAGA,MAAI,CAAC,SAAS;AACZ,UAAM,QAAQ,UAAU,SAAS;AACjC,QAAI,SAAS,aAAa,OAAO,QAAQ,GAAG;AAC1C,aAAO;AAAA,QACL,SAAS,MAAM;AAAA,QACf,WAAW;AAAA,QACX,UAAU,KAAK,IAAI,IAAI,MAAM;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,aAAa,QAAQ,WAAW;AAGtD,QAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,iBAAW,WAAW,OAAO;AAAA,IAC/B;AAEA,WAAO;AAAA,MACL;AAAA,MACA,WAAW;AAAA,IACb;AAAA,EACF,SAAS,OAAO;AAEd,UAAM,QAAQ,UAAU,SAAS;AACjC,QAAI,SAAS,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS,GAAG;AAC/C,YAAM,WAAW,KAAK,IAAI,IAAI,MAAM;AACpC,cAAQ;AAAA,QACN,4DAA4D,KAAK,MAAM,WAAW,GAAI,CAAC;AAAA,MACzF;AACA,aAAO;AAAA,QACL,SAAS,MAAM;AAAA,QACf,WAAW;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAGA,UAAM,IAAI;AAAA,MACR,0CAA0C,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA;AAAA,IAE1F;AAAA,EACF;AACF;;;AD/KA,kBAAiB;AACjB,IAAAC,OAAoB;AACpB,IAAAC,SAAsB;AAKtB,IAAM,uBAAuB;AAC7B,IAAM,2BAA2B;AACjC,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB,IAAI,OAAO;AACtC,IAAM,uBAAuB;AAyB7B,IAAM,gBAAyC,oBAAI,IAAI;AAKvD,SAAS,aAAqB;AAC5B,QAAM,UAAe,gBAAK,2BAAa,GAAG,MAAM;AAChD,MAAI,CAAI,gBAAW,OAAO,GAAG;AAC3B,IAAG,eAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;AAKA,SAAS,eAAe,WAA2B;AACjD,SAAY,YAAK,WAAW,GAAG,OAAO,SAAS,MAAM;AACvD;AAKA,SAAS,kBAAkB,SAAuB;AAChD,MAAI;AACF,QAAO,gBAAW,OAAO,GAAG;AAC1B,YAAM,QAAW,cAAS,OAAO;AACjC,UAAI,MAAM,OAAO,oBAAoB;AAEnC,cAAM,aAAa,GAAG,OAAO;AAC7B,YAAO,gBAAW,UAAU,GAAG;AAC7B,UAAG,gBAAW,UAAU;AAAA,QAC1B;AACA,QAAG,gBAAW,SAAS,UAAU;AACjC,gBAAQ,IAAI,2CAAgD,gBAAS,OAAO,CAAC,EAAE;AAAA,MACjF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,KAAK,4CAA4C,KAAK;AAAA,EAChE;AACF;AAKA,SAAS,WAAW,SAAiB,MAAc,UAAmB,OAAa;AACjF,MAAI;AACF,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,UAAM,SAAS,UAAU,QAAQ;AACjC,UAAM,UAAU,IAAI,SAAS,MAAM,MAAM,KAAK,IAAI;AAAA;AAClD,IAAG,oBAAe,SAAS,OAAO;AAAA,EACpC,QAAQ;AAAA,EAER;AACF;AAOA,eAAsB,mBAAmB,MAAc,YAAoB,KAAwB;AACjG,SAAO,IAAI,QAAQ,CAACC,aAAY;AAC9B,UAAM,MAAM,YAAAC,QAAK;AAAA,MACf;AAAA,QACE,UAAU;AAAA,QACV;AAAA,QACA,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,MACX;AAAA,MACA,CAAC,QAAQ;AAEP,QAAAD,SAAQ,IAAI;AAAA,MACd;AAAA,IACF;AAEA,QAAI,GAAG,SAAS,MAAM;AACpB,MAAAA,SAAQ,KAAK;AAAA,IACf,CAAC;AAED,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,MAAAA,SAAQ,KAAK;AAAA,IACf,CAAC;AAED,QAAI,IAAI;AAAA,EACV,CAAC;AACH;AAMA,eAAsB,kBAAkB,MAAgC;AACtE,MAAI;AAEF,UAAM,aAAS,gCAAS,YAAY,IAAI,wBAAwB,EAAE,UAAU,OAAO,CAAC,EAAE,KAAK;AAE3F,QAAI,CAAC,QAAQ;AACX,cAAQ,IAAI,+CAA+C,IAAI,EAAE;AACjE,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,OAAO,MAAM,IAAI,EAAE,OAAO,OAAO;AAC9C,YAAQ,IAAI,4BAA4B,KAAK,MAAM,wBAAwB,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC,EAAE;AAErG,eAAW,OAAO,MAAM;AACtB,UAAI;AAEF,4CAAS,YAAY,GAAG,wBAAwB,EAAE,UAAU,OAAO,CAAC;AACpE,gBAAQ,IAAI,0CAA0C,GAAG,EAAE;AAAA,MAC7D,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,UAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAI,CAAC;AAGtD,eAAW,OAAO,MAAM;AACtB,UAAI;AAEF,4CAAS,WAAW,GAAG,gBAAgB,EAAE,UAAU,OAAO,CAAC;AAE3D,4CAAS,WAAW,GAAG,wBAAwB,EAAE,UAAU,OAAO,CAAC;AACnE,gBAAQ,IAAI,uCAAuC,GAAG,EAAE;AAAA,MAC1D,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,UAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAG,CAAC;AAErD,UAAM,aAAa,MAAM,YAAY,IAAI;AACzC,QAAI,YAAY;AACd,cAAQ,MAAM,2BAA2B,IAAI,mCAAmC;AAChF,aAAO;AAAA,IACT;AAEA,YAAQ,IAAI,8CAA8C,IAAI,EAAE;AAChE,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,oDAAoD,IAAI,KAAK,KAAK;AAChF,WAAO;AAAA,EACT;AACF;AAKA,eAAe,YAAY,MAAc,YAAoB,KAAyB;AACpF,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,gBAAgB;AAEtB,SAAO,KAAK,IAAI,IAAI,YAAY,WAAW;AACzC,QAAI,MAAM,YAAY,IAAI,GAAG;AAC3B,aAAO;AAAA,IACT;AACA,UAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,aAAa,CAAC;AAAA,EACjE;AAEA,SAAO;AACT;AAKA,SAAS,sBAAsB,cAA8B;AAC3D,QAAM,QAAQ,2BAA2B,KAAK,IAAI,GAAG,YAAY;AACjE,SAAO,KAAK,IAAI,OAAO,oBAAoB;AAC7C;AAOA,SAAS,sBACP,aACA,MACA,WACA,SACA,eACA,iBACc;AAEd,oBAAkB,OAAO;AAGzB,QAAM,cAAc,QAAQ,IAAI,gBAAgB;AAChD,QAAM,aAAa,wBAAwB,oBAAoB;AAC/D,QAAM,sBAAsB,YAAY,SAAS,oBAAoB,IACjE,cACA,GAAG,WAAW,IAAI,UAAU,GAAG,KAAK;AAGxC,QAAM,UAAU,iBAAiB;AACjC,QAAM,CAAC,KAAK,GAAG,IAAI,IAAI,QAAQ,MAAM,GAAG;AACxC,UAAQ,IAAI,6CAA6C,OAAO,EAAE;AAGlE,QAAM,YAAY;AAAA,IAChB,GAAG,QAAQ;AAAA,IACX,GAAG;AAAA,IACH,MAAM,OAAO,IAAI;AAAA,IACjB,cAAc;AAAA,EAChB;AAEA,QAAM,gBAAgB,kBAAkB,OAAO,KAAK,eAAe,EAAE,SAAS;AAC9E,MAAI,gBAAgB,GAAG;AACrB,YAAQ,IAAI,gCAAgC,aAAa,yBAAyB;AAAA,EACpF;AAEA,QAAM,iBAAa,6BAAM,KAAK,MAAM;AAAA,IAClC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAChC,UAAU;AAAA,IACV,OAAO;AAAA;AAAA,EACT,CAAC;AAGD,aAAW,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAC9C,UAAM,OAAO,KAAK,SAAS,EAAE,KAAK;AAClC,QAAI,MAAM;AACR,cAAQ,IAAI,cAAc,SAAS,KAAK,IAAI,EAAE;AAC9C,iBAAW,SAAS,MAAM,KAAK;AAAA,IACjC;AAAA,EACF,CAAC;AAED,aAAW,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAC9C,UAAM,OAAO,KAAK,SAAS,EAAE,KAAK;AAClC,QAAI,MAAM;AACR,cAAQ,MAAM,cAAc,SAAS,KAAK,IAAI,EAAE;AAChD,iBAAW,SAAS,MAAM,IAAI;AAAA,IAChC;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAKA,eAAe,kBACb,WACA,MACA,QACe;AACf,QAAM,aAAa,cAAc,IAAI,SAAS;AAC9C,MAAI,CAAC,YAAY;AACf;AAAA,EACF;AAEA,QAAM,aAAa,SAAS,UAAU,MAAM,KAAK,QAAQ,IAAI;AAC7D,UAAQ,IAAI,kCAAkC,SAAS,gBAAgB,UAAU,EAAE;AACnF,aAAW,WAAW,WAAW,IAAI,uBAAuB,UAAU,IAAI,IAAI;AAG9E,MAAI,CAAC,WAAW,oBAAoB;AAClC,YAAQ,IAAI,gDAAgD,SAAS,EAAE;AACvE,kBAAc,OAAO,SAAS;AAC9B;AAAA,EACF;AAEA,MAAI,WAAW,gBAAgB,sBAAsB;AACnD,YAAQ,MAAM,4CAA4C,oBAAoB,iBAAiB,SAAS,EAAE;AAC1G,eAAW,WAAW,WAAW,IAAI,2CAA2C,IAAI;AACpF,kBAAc,OAAO,SAAS;AAC9B;AAAA,EACF;AAGA,QAAM,QAAQ,sBAAsB,WAAW,YAAY;AAC3D,UAAQ,IAAI,iCAAiC,SAAS,OAAO,KAAK,eAAe,WAAW,eAAe,CAAC,IAAI,oBAAoB,GAAG;AACvI,aAAW,WAAW,WAAW,IAAI,yBAAyB,KAAK,eAAe,WAAW,eAAe,CAAC,KAAK,KAAK;AAEvH,QAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,KAAK,CAAC;AAGvD,MAAI,CAAC,cAAc,IAAI,SAAS,GAAG;AACjC,YAAQ,IAAI,6BAA6B,SAAS,qDAAqD;AACvG;AAAA,EACF;AAKA,QAAM,UAAU,WAAW,WAAW,eAAe,SAAS;AAC9D,QAAM,aAAa,sBAAsB,WAAW,aAAa,WAAW,MAAM,WAAW,SAAS,WAAW,eAAe,WAAW,eAAe;AAG1J,QAAM,cAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,SAAS;AAAA,IACT,cAAc,WAAW,eAAe;AAAA,IACxC,eAAe,oBAAI,KAAK;AAAA,EAC1B;AACA,gBAAc,IAAI,WAAW,WAAW;AAGxC,aAAW,GAAG,QAAQ,CAAC,SAAS,cAAc;AAC5C,sBAAkB,WAAW,SAAS,SAAS;AAAA,EACjD,CAAC;AAED,aAAW,GAAG,SAAS,CAAC,UAAU;AAChC,YAAQ,MAAM,wCAAwC,SAAS,KAAK,KAAK;AACzE,eAAW,SAAS,kBAAkB,MAAM,OAAO,IAAI,IAAI;AAAA,EAC7D,CAAC;AAGD,QAAM,cAAc,MAAM,YAAY,WAAW,MAAM,GAAK;AAC5D,MAAI,aAAa;AACf,YAAQ,IAAI,6BAA6B,SAAS,yBAAyB;AAC3E,eAAW,SAAS,iCAAiC,KAAK;AAE1D,gBAAY,eAAe;AAAA,EAC7B,OAAO;AACL,YAAQ,MAAM,6BAA6B,SAAS,oBAAoB;AACxE,eAAW,SAAS,2CAA2C,IAAI;AAAA,EACrE;AACF;AAQA,eAAsB,eACpB,aACA,OAAe,KACf,YAAoB,WACpB,UAA6D,CAAC,GACW;AACzE,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,gBAAgB,QAAQ;AAG9B,MAAI,MAAM,YAAY,IAAI,GAAG;AAC3B,YAAQ,IAAI,8CAA8C,IAAI,EAAE;AAChE,WAAO,EAAE,SAAS,MAAM,gBAAgB,KAAK;AAAA,EAC/C;AAGA,MAAI,cAAc,IAAI,SAAS,GAAG;AAChC,UAAM,WAAW,cAAc,IAAI,SAAS;AAC5C,QAAI,YAAY,CAAC,SAAS,QAAQ,QAAQ;AACxC,cAAQ,IAAI,0CAA0C,SAAS,EAAE;AACjE,aAAO,EAAE,SAAS,MAAM,gBAAgB,KAAK;AAAA,IAC/C;AAAA,EACF;AAEA,UAAQ,IAAI,8CAA8C,SAAS,YAAY,IAAI,mBAAmB,WAAW,MAAM;AAGvH,MAAI,kBAA0C,CAAC;AAC/C,MAAI;AACF,UAAM,SAAS,UAAM,yBAAW;AAChC,QAAI,QAAQ,gBAAgB,QAAQ,YAAY;AAC9C,YAAM,SAAS,OAAO,WAAW;AACjC,YAAM,SAAS,MAAM,sBAAsB,QAAQ,OAAO,cAAc;AAAA,QACtE,WAAW,OAAO;AAAA,QAClB,UAAU;AAAA;AAAA,MACZ,CAAC;AACD,wBAAkB,OAAO;AACzB,cAAQ,IAAI,6BAA6B,OAAO,KAAK,eAAe,EAAE,MAAM,mBAAmB,OAAO,YAAY,UAAU,QAAQ,GAAG;AAAA,IACzI,OAAO;AACL,cAAQ,IAAI,+DAA+D;AAAA,IAC7E;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,KAAK,gDAAgD,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,EAE7G;AAEA,MAAI;AACF,UAAM,UAAU,eAAe,SAAS;AAGxC,UAAM,aAAa,sBAAsB,aAAa,MAAM,WAAW,SAAS,eAAe,eAAe;AAG9G,UAAM,aAAyB;AAAA,MAC7B,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,KAAK;AAAA,MACpB,cAAc;AAAA,MACd,eAAe;AAAA,MACf,oBAAoB;AAAA,MACpB,SAAS;AAAA,MACT;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AACA,kBAAc,IAAI,WAAW,UAAU;AAEvC,eAAW,SAAS,+BAA+B,IAAI,IAAI,KAAK;AAGhE,eAAW,GAAG,QAAQ,CAAC,MAAM,WAAW;AACtC,wBAAkB,WAAW,MAAM,MAAM;AAAA,IAC3C,CAAC;AAED,eAAW,GAAG,SAAS,CAAC,UAAU;AAChC,cAAQ,MAAM,iCAAiC,SAAS,KAAK,KAAK;AAClE,iBAAW,SAAS,kBAAkB,MAAM,OAAO,IAAI,IAAI;AAAA,IAC7D,CAAC;AAGD,YAAQ,IAAI,mDAAmD,IAAI,KAAK;AACxE,UAAM,cAAc,MAAM,YAAY,MAAM,GAAK;AAEjD,QAAI,CAAC,aAAa;AAChB,iBAAW,KAAK;AAChB,oBAAc,OAAO,SAAS;AAC9B,iBAAW,SAAS,kCAAkC,IAAI;AAC1D,aAAO,EAAE,SAAS,OAAO,OAAO,4CAA4C;AAAA,IAC9E;AAEA,YAAQ,IAAI,mDAAmD,IAAI,EAAE;AACrE,eAAW,SAAS,+BAA+B,KAAK;AACxD,WAAO,EAAE,SAAS,KAAK;AAAA,EAEzB,SAAS,OAAO;AACd,UAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,YAAQ,MAAM,gCAAgC,KAAK;AACnD,WAAO,EAAE,SAAS,OAAO,OAAO,SAAS;AAAA,EAC3C;AACF;AAMA,eAAsB,cAAc,WAAkC;AACpE,QAAM,aAAa,cAAc,IAAI,SAAS;AAC9C,MAAI,CAAC,YAAY;AACf;AAAA,EACF;AAGA,aAAW,qBAAqB;AAEhC,MAAI,CAAC,WAAW,QAAQ,QAAQ;AAC9B,YAAQ,IAAI,mCAAmC,SAAS,EAAE;AAC1D,QAAI,WAAW,SAAS;AACtB,iBAAW,WAAW,SAAS,iCAAiC,KAAK;AAAA,IACvE;AACA,eAAW,QAAQ,KAAK,SAAS;AAGjC,UAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAI,CAAC;AAEtD,QAAI,CAAC,WAAW,QAAQ,QAAQ;AAC9B,iBAAW,QAAQ,KAAK,SAAS;AAAA,IACnC;AAAA,EACF;AAEA,gBAAc,OAAO,SAAS;AAChC;AAMA,eAAsB,iBACpB,WAC+C;AAC/C,QAAM,aAAa,cAAc,IAAI,SAAS;AAC9C,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,SAAS,OAAO,OAAO,2BAA2B,SAAS,GAAG;AAAA,EACzE;AAEA,QAAM,EAAE,aAAa,MAAM,oBAAoB,QAAQ,IAAI;AAE3D,UAAQ,IAAI,4CAA4C,SAAS,KAAK;AACtE,MAAI,SAAS;AACX,eAAW,SAAS,4BAA4B,KAAK;AAAA,EACvD;AAGA,QAAM,cAAc,SAAS;AAG7B,QAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAI,CAAC;AAGtD,MAAI,MAAM,YAAY,IAAI,GAAG;AAC3B,UAAM,kBAAkB,IAAI;AAAA,EAC9B;AAGA,SAAO,eAAe,aAAa,MAAM,WAAW,EAAE,aAAa,mBAAmB,CAAC;AACzF;AAqBO,SAAS,qBAUb;AACD,QAAM,MAAM,KAAK,IAAI;AACrB,SAAO,MAAM,KAAK,cAAc,OAAO,CAAC,EAAE,IAAI,WAAS;AAAA,IACrD,WAAW,KAAK;AAAA,IAChB,MAAM,KAAK;AAAA,IACX,KAAK,KAAK,QAAQ;AAAA,IAClB,WAAW,KAAK;AAAA,IAChB,QAAQ,KAAK,OAAO,MAAM,KAAK,UAAU,QAAQ,KAAK,GAAI;AAAA,IAC1D,cAAc,KAAK;AAAA,IACnB,eAAe,KAAK;AAAA,IACpB,oBAAoB,KAAK;AAAA,IACzB,SAAS,KAAK;AAAA,EAChB,EAAE;AACJ;AAcA,eAAsB,gBACpB,aACA,OAAe,KACf,YAAoB,WACpB,eAC+C;AAE/C,MAAI,MAAM,YAAY,IAAI,GAAG;AAC3B,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAIA,SAAO,eAAe,aAAa,MAAM,WAAW,EAAE,aAAa,MAAM,cAAc,CAAC;AAC1F;;;AGjmBA,IAAAE,OAAoB;AACpB,IAAAC,SAAsB;AAEtB,IAAM,eAAe;AAKd,SAAS,cAAc,aAA6B;AAEzD,QAAM,UAAU,eAAe,WAAW;AAC1C,MAAI,SAAS;AACX,YAAQ,IAAI,2BAA2B,OAAO,UAAU;AACxD,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,uBAAuB,WAAW;AACrD,MAAI,YAAY;AACd,YAAQ,IAAI,2BAA2B,UAAU,6BAA6B;AAC9E,WAAO;AAAA,EACT;AAGA,UAAQ,IAAI,mCAAmC,YAAY,EAAE;AAC7D,SAAO;AACT;AAKA,SAAS,eAAe,aAAoC;AAC1D,QAAM,WAAW;AAAA,IACV,YAAK,aAAa,MAAM;AAAA,IACxB,YAAK,aAAa,YAAY;AAAA,IAC9B,YAAK,aAAa,kBAAkB;AAAA,IACpC,YAAK,aAAa,wBAAwB;AAAA,EACjD;AAEA,aAAW,WAAW,UAAU;AAC9B,QAAI;AACF,UAAI,CAAI,gBAAW,OAAO,EAAG;AAE7B,YAAM,UAAa,kBAAa,SAAS,OAAO;AAChD,YAAM,QAAQ,QAAQ,MAAM,IAAI;AAEhC,iBAAW,QAAQ,OAAO;AAExB,cAAM,QAAQ,KAAK,MAAM,4CAA4C;AACrE,YAAI,OAAO;AACT,gBAAM,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAClC,cAAI,OAAO,KAAK,OAAO,OAAO;AAC5B,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,uBAAuB,aAAoC;AAClE,QAAM,kBAAuB,YAAK,aAAa,cAAc;AAE7D,MAAI;AACF,QAAI,CAAI,gBAAW,eAAe,EAAG,QAAO;AAE5C,UAAM,UAAa,kBAAa,iBAAiB,OAAO;AACxD,UAAM,MAAM,KAAK,MAAM,OAAO;AAG9B,UAAM,YAAY,IAAI,SAAS;AAC/B,QAAI,CAAC,UAAW,QAAO;AAIvB,UAAM,YAAY,UAAU,MAAM,8BAA8B;AAChE,QAAI,WAAW;AACb,YAAM,OAAO,SAAS,UAAU,CAAC,GAAG,EAAE;AACtC,UAAI,OAAO,KAAK,OAAO,OAAO;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,WAAW,UAAU,MAAM,gBAAgB;AACjD,QAAI,UAAU;AACZ,YAAM,OAAO,SAAS,SAAS,CAAC,GAAG,EAAE;AACrC,UAAI,OAAO,KAAK,OAAO,OAAO;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;;;AC1EA,IAAAC,OAAoB;AACpB,IAAAC,SAAsB;AACtB,IAAAC,eAA6C;AAWtC,SAAS,kBAAkB,WAA4B;AAE5D,MAAI,CAAC,aAAa,OAAO,cAAc,YAAY,CAAC,UAAU,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,SAAS,GAAG,KAAK,UAAU,SAAS,IAAI,KAAK,UAAU,SAAS,IAAI,GAAG;AACnF,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,SAAS,IAAI,GAAG;AAC5B,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,WAAW,GAAG,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AA6CO,IAAM,kBAAN,MAAM,iBAAgB;AAAA,EAM3B,YAAY,aAAqB;AA4YjC;AAAA;AAAA;AAAA,SAAQ,WAAmB;AA3YzB,SAAK,cAAc;AACnB,SAAK,eAAoB,YAAK,aAAa,OAAO;AAClD,SAAK,aAAkB,YAAK,aAAa,YAAY,aAAa;AAClE,SAAK,cAAc,IAAI,yBAAY;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAA+B;AAEnC,QAAI,CAAI,gBAAW,KAAK,YAAY,GAAG;AACrC,aAAO;AAAA,IACT;AAGA,QAAI,CAAI,gBAAW,KAAK,UAAU,GAAG;AACnC,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,SAAS,KAAK,WAAW;AAC/B,aAAO,WAAW;AAAA,IACpB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,cACX,aACA,SACA,WACA,eACA,aAC0B;AAC1B,UAAM,UAAU,IAAI,iBAAgB,WAAW;AAG/C,UAAM,aAAkB,YAAK,aAAa,UAAU;AACpD,IAAG,eAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAG5C,UAAM,cAAc,MAAM,QAAQ,YAAY,QAAQ;AAAA,MACpD,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,MAAM,QAAQ;AAAA,IAChB,CAAC;AAED,QAAI,CAAC,YAAY,SAAS;AACxB,YAAM,IAAI,MAAM,+BAA+B,YAAY,MAAM,EAAE;AAAA,IACrE;AAGA,UAAM,SAAgC;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,QAAQ;AAAA,MACtB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,WAAW,CAAC;AAAA,IACd;AAEA,YAAQ,YAAY,MAAM;AAE1B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eACJ,WACA,YACA,eAAwB,OACU;AAElC,QAAI,CAAC,kBAAkB,SAAS,GAAG;AACjC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,wBAAwB,SAAS;AAAA,MAC1C;AAAA,IACF;AAEA,UAAM,eAAoB,YAAK,KAAK,aAAa,SAAS;AAG1D,UAAM,eAAe,MAAM,KAAK,YAAY;AAC5C,QAAI,CAAC,cAAc;AACjB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI;AAEF,YAAM,WAAW,KAAK,uBAAuB,SAAS;AACtD,UAAI,UAAU;AACZ,eAAO;AAAA,UACL,SAAS;AAAA,UACT,cAAc,SAAS;AAAA,UACvB,cAAc;AAAA,QAChB;AAAA,MACF;AAIA,YAAM,cAAc,MAAM,KAAK,YAAY,QAAQ;AAAA,QACjD,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,GAAG,EAAE,KAAK,KAAK,aAAa,CAAC;AAI7B,UAAI,CAAC,YAAY,WAAW,cAAc;AACxC,gBAAQ,MAAM,mDAAmD,YAAY,MAAM;AACnF,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AAKA,YAAM,SAAS,MAAM,KAAK,YAAY,QAAQ;AAAA,QAC5C,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,YAAY,eAAe,gBAAgB;AAAA,MAC7C,GAAG,EAAE,KAAK,KAAK,aAAa,CAAC;AAE7B,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,OAAO,UAAU;AAAA,QAC1B;AAAA,MACF;AAGA,YAAM,eAA6B;AAAA,QACjC;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,MACvC;AAEA,YAAM,SAAS,KAAK,WAAW;AAC/B,UAAI,QAAQ;AACV,eAAO,UAAU,KAAK,YAAY;AAClC,aAAK,YAAY,MAAM;AAAA,MACzB;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,IACF,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eACJ,WACA,QAAiB,OACiB;AAElC,QAAI,CAAC,kBAAkB,SAAS,GAAG;AACjC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,wBAAwB,SAAS;AAAA,MAC1C;AAAA,IACF;AAGA,UAAM,eAAe,MAAM,KAAK,YAAY;AAC5C,QAAI,CAAC,cAAc;AACjB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI;AACF,YAAM,WAAW,KAAK,uBAAuB,SAAS;AACtD,UAAI,CAAC,UAAU;AACb,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,gCAAgC,SAAS;AAAA,QAClD;AAAA,MACF;AAGA,YAAM,SAAS,MAAM,KAAK,YAAY,QAAQ;AAAA,QAC5C,QAAQ;AAAA,QACR,MAAM,SAAS;AAAA,QACf;AAAA,MACF,GAAG,EAAE,KAAK,KAAK,aAAa,CAAC;AAE7B,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,OAAO,UAAU;AAAA,QAC1B;AAAA,MACF;AAGA,YAAM,SAAS,KAAK,WAAW;AAC/B,UAAI,QAAQ;AACV,eAAO,YAAY,OAAO,UAAU,OAAO,OAAK,EAAE,cAAc,SAAS;AACzE,aAAK,YAAY,MAAM;AAAA,MACzB;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,cAAc,SAAS;AAAA,MACzB;AAAA,IACF,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,WAAkC;AAEhD,QAAI,CAAC,kBAAkB,SAAS,GAAG;AACjC,aAAO;AAAA,IACT;AACA,UAAM,WAAW,KAAK,uBAAuB,SAAS;AACtD,WAAO,UAAU,gBAAgB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAuB,WAAwC;AAC7D,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,QAAQ,UAAU,KAAK,OAAK,EAAE,cAAc,SAAS,KAAK;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,YAAyC;AAC3D,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,QAAQ,UAAU,KAAK,OAAK,EAAE,eAAe,UAAU,KAAK;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgC;AAC9B,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,QAAQ,aAAa,CAAC;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,eAAe,kBAGb;AACA,UAAM,eAAe,KAAK,cAAc;AACxC,UAAM,YAAY,IAAI,IAAI,gBAAgB;AAE1C,UAAM,WAAW,aAAa,OAAO,OAAK,CAAC,UAAU,IAAI,EAAE,SAAS,CAAC;AACrE,UAAM,QAAQ,aAAa,OAAO,OAAK,UAAU,IAAI,EAAE,SAAS,CAAC;AAEjE,WAAO,EAAE,UAAU,MAAM;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,WAAkC;AACpD,UAAM,KAAK,iBAAiB,YAAU;AACpC,YAAM,WAAW,OAAO,UAAU,KAAK,OAAK,EAAE,cAAc,SAAS;AACrE,UAAI,UAAU;AACZ,iBAAS,gBAAe,oBAAI,KAAK,GAAE,YAAY;AAAA,MACjD;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,sBAAuC;AAE3C,UAAM,KAAK,YAAY,QAAQ;AAAA,MAC7B,QAAQ;AAAA,IACV,GAAG,EAAE,KAAK,KAAK,aAAa,CAAC;AAG7B,QAAI,cAAc;AAClB,UAAM,KAAK,iBAAiB,YAAU;AACpC,YAAM,eAAe,OAAO,UAAU;AACtC,aAAO,YAAY,OAAO,UAAU,OAAO,OAAQ,gBAAW,EAAE,YAAY,CAAC;AAC7E,oBAAc,eAAe,OAAO,UAAU;AAC9C,aAAO;AAAA,IACT,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAIH;AACD,UAAM,SAAS,KAAK,WAAW;AAC/B,UAAM,QAAwB,CAAC;AAC/B,UAAM,QAAwB,CAAC;AAC/B,UAAM,WAAqB,CAAC;AAG5B,UAAM,aAAa,MAAM,KAAK,YAAY,QAAQ;AAAA,MAChD,QAAQ;AAAA,IACV,GAAG,EAAE,KAAK,KAAK,aAAa,CAAC;AAE7B,UAAM,kBAAkB,IAAI;AAAA,MAC1B,WAAW,SAAS,WAAW,IAAI,OAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IACtD;AAGA,eAAW,YAAY,QAAQ,aAAa,CAAC,GAAG;AAC9C,UAAI,gBAAgB,IAAI,SAAS,YAAY,GAAG;AAC9C,cAAM,KAAK,QAAQ;AACnB,wBAAgB,OAAO,SAAS,YAAY;AAAA,MAC9C,OAAO;AACL,cAAM,KAAK,QAAQ;AAAA,MACrB;AAAA,IACF;AAIA,eAAW,SAAS,iBAAiB;AACnC,UAAI,UAAU,KAAK,cAAc;AAC/B,iBAAS,KAAK,KAAK;AAAA,MACrB;AAAA,IACF;AAEA,WAAO,EAAE,OAAO,OAAO,SAAS;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,YAA0C;AACxC,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA0B;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAQQ,cAAsB;AAC5B,QAAI,CAAC,KAAK,UAAU;AAClB,WAAK,WAAW,KAAK,aAAa;AAAA,IACpC;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,KAAsB;AAC7C,QAAI;AACF,cAAQ,KAAK,KAAK,CAAC;AACnB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAY,YAAoB,KAAwB;AACpE,UAAM,WAAW,KAAK,YAAY;AAClC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,gBAAgB;AAEtB,WAAO,KAAK,IAAI,IAAI,YAAY,WAAW;AACzC,UAAI;AAEF,QAAG,mBAAc,UAAU,OAAO,QAAQ,GAAG,GAAG,EAAE,MAAM,KAAK,CAAC;AAC9D,eAAO;AAAA,MACT,SAAS,KAAU;AACjB,YAAI,IAAI,SAAS,UAAU;AAEzB,cAAI;AACF,kBAAM,QAAW,cAAS,QAAQ;AAClC,kBAAM,UAAU,KAAK,IAAI,IAAI,MAAM;AACnC,gBAAI,UAAU,KAAO;AAGnB,kBAAI;AACF,sBAAM,cAAiB,kBAAa,UAAU,OAAO,EAAE,KAAK;AAC5D,sBAAM,UAAU,SAAS,aAAa,EAAE;AACxC,oBAAI,CAAC,MAAM,OAAO,KAAK,KAAK,iBAAiB,OAAO,GAAG;AAGrD,wBAAM,IAAI,QAAQ,CAAAC,aAAW,WAAWA,UAAS,aAAa,CAAC;AAC/D;AAAA,gBACF;AAAA,cACF,QAAQ;AAAA,cAER;AAEA,kBAAI;AACF,gBAAG,gBAAW,QAAQ;AAAA,cACxB,QAAQ;AAAA,cAER;AACA;AAAA,YACF;AAAA,UACF,QAAQ;AAEN;AAAA,UACF;AAEA,gBAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,aAAa,CAAC;AAC/D;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC1B,QAAI;AACF,MAAG,gBAAW,KAAK,YAAY,CAAC;AAAA,IAClC,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,aAA2C;AACjD,QAAI;AACF,UAAI,CAAI,gBAAW,KAAK,UAAU,GAAG;AACnC,eAAO;AAAA,MACT;AACA,YAAM,UAAa,kBAAa,KAAK,YAAY,OAAO;AACxD,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,OAAO;AACd,cAAQ,MAAM,4CAA4C,KAAK;AAC/D,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,YAAY,QAAqC;AACvD,QAAI;AACF,YAAM,MAAW,eAAQ,KAAK,UAAU;AACxC,UAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,QAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,MACvC;AACA,MAAG,mBAAc,KAAK,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AAAA,IAC5E,SAAS,OAAO;AACd,cAAQ,MAAM,6CAA6C,KAAK;AAChE,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBACZ,SACkB;AAClB,UAAM,eAAe,MAAM,KAAK,YAAY;AAC5C,QAAI,CAAC,cAAc;AACjB,cAAQ,MAAM,4DAA4D;AAC1E,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,SAAS,KAAK,WAAW;AAC/B,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AACA,YAAM,UAAU,QAAQ,MAAM;AAC9B,WAAK,YAAY,OAAO;AACxB,aAAO;AAAA,IACT,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,qBACJ,WACA,QACA,OACkB;AAClB,WAAO,KAAK,iBAAiB,CAAC,WAAW;AACvC,YAAM,WAAW,OAAO,UAAU,KAAK,OAAK,EAAE,cAAc,SAAS;AACrE,UAAI,UAAU;AACZ,iBAAS,cAAc;AACvB,YAAI,WAAW,WAAW;AACxB,mBAAS,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAAA,QACnD,WAAW,WAAW,WAAW,WAAW,SAAS;AACnD,mBAAS,oBAAmB,oBAAI,KAAK,GAAE,YAAY;AAAA,QACrD;AACA,YAAI,OAAO;AACT,mBAAS,aAAa;AAAA,QACxB;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,WAAwC;AACxD,UAAM,SAAS,KAAK,WAAW;AAC/B,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,OAAO,UAAU,KAAK,OAAK,EAAE,cAAc,SAAS,KAAK;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,kBAAkB,WAAmB,OAAgE;AAEzG,YAAQ,KAAK,2DAA2D;AACxE,YAAQ,KAAK,mGAAmG;AAEhH,UAAM,SAAS,KAAK,WAAW;AAC/B,QAAI,CAAC,QAAQ;AACX,aAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB;AAAA,IACrD;AAEA,UAAM,WAAW,OAAO,UAAU,KAAK,OAAK,EAAE,cAAc,SAAS;AACrE,QAAI,CAAC,UAAU;AACb,aAAO,EAAE,SAAS,OAAO,OAAO,0BAA0B,SAAS,GAAG;AAAA,IACxE;AAGA,UAAM,eAAe,OAAO,UAAU,KAAK,OAAK,EAAE,cAAc,MAAM;AACtE,QAAI,CAAC,cAAc;AACjB,cAAQ,KAAK,sFAAsF;AACnG,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAGA,QAAI;AACF,iBAAW,QAAQ,OAAO;AACxB,cAAM,UAAe,YAAK,aAAa,cAAc,IAAI;AACzD,cAAM,WAAgB,YAAK,SAAS,cAAc,IAAI;AAEtD,YAAO,gBAAW,OAAO,GAAG;AAC1B,gBAAM,UAAe,eAAQ,QAAQ;AACrC,cAAI,CAAI,gBAAW,OAAO,GAAG;AAC3B,YAAG,eAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,UAC3C;AACA,UAAG,kBAAa,SAAS,QAAQ;AACjC,kBAAQ,IAAI,mCAAmC,IAAI,OAAO,SAAS,eAAe;AAAA,QACpF,OAAO;AACL,kBAAQ,IAAI,oCAAoC,IAAI,sBAAsB;AAAA,QAC5E;AAAA,MACF;AACA,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,aAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,IACzF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAe,WAAmB,QAA+D;AACrG,UAAM,SAAS,KAAK,WAAW;AAC/B,QAAI,CAAC,QAAQ;AACX,aAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB;AAAA,IACrD;AAEA,UAAM,WAAW,OAAO,UAAU,KAAK,OAAK,EAAE,cAAc,SAAS;AACrE,QAAI,CAAC,UAAU;AACb,aAAO,EAAE,SAAS,OAAO,OAAO,0BAA0B,SAAS,GAAG;AAAA,IACxE;AAGA,UAAM,kBAAkB;AACxB,UAAM,gBAAgB,OAAO,SAAS,MAAM,OAAO,MAAM,GAAG,GAAG,IAAI,QAAQ;AAC3E,YAAQ,IAAI,qDAAqD,SAAS,EAAE;AAC5E,YAAQ,IAAI,iDAAiD,SAAS,YAAY,EAAE;AACpF,YAAQ,IAAI,uCAAuC,eAAe,UAAU;AAC5E,YAAQ,IAAI,sCAAsC,aAAa,EAAE;AAEjE,QAAI;AACF,YAAM,EAAE,UAAAC,UAAS,IAAI,QAAQ,eAAe;AAE5C,MAAAA,UAAS,QAAQ;AAAA,QACf,KAAK,SAAS;AAAA,QACd,OAAO;AAAA,QACP,SAAS,kBAAkB,KAAK;AAAA,QAChC,KAAK,EAAE,GAAG,QAAQ,KAAK,UAAU,cAAc;AAAA,MACjD,CAAC;AAED,cAAQ,IAAI,oEAAoE,SAAS,EAAE;AAC3F,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,cAAQ,MAAM,oDAAoD,SAAS,KAAK,YAAY;AAC5F,aAAO,EAAE,SAAS,OAAO,OAAO,aAAa;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAiB,WAAmB,QAA+D;AACvG,UAAM,SAAS,KAAK,WAAW;AAC/B,QAAI,CAAC,QAAQ;AACX,aAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB;AAAA,IACrD;AAEA,UAAM,WAAW,OAAO,UAAU,KAAK,OAAK,EAAE,cAAc,SAAS;AACrE,QAAI,CAAC,UAAU;AACb,aAAO,EAAE,SAAS,OAAO,OAAO,0BAA0B,SAAS,GAAG;AAAA,IACxE;AAEA,UAAM,kBAAkB;AACxB,UAAM,gBAAgB,OAAO,SAAS,MAAM,OAAO,MAAM,GAAG,GAAG,IAAI,QAAQ;AAC3E,YAAQ,IAAI,uDAAuD,SAAS,EAAE;AAC9E,YAAQ,IAAI,iDAAiD,SAAS,YAAY,EAAE;AACpF,YAAQ,IAAI,uCAAuC,eAAe,UAAU;AAC5E,YAAQ,IAAI,sCAAsC,aAAa,EAAE;AAEjE,QAAI;AACF,YAAM,EAAE,UAAAA,UAAS,IAAI,QAAQ,eAAe;AAE5C,MAAAA,UAAS,QAAQ;AAAA,QACf,KAAK,SAAS;AAAA,QACd,OAAO;AAAA,QACP,SAAS,kBAAkB,KAAK;AAAA,QAChC,KAAK,EAAE,GAAG,QAAQ,KAAK,UAAU,cAAc;AAAA,MACjD,CAAC;AAED,cAAQ,IAAI,sEAAsE,SAAS,EAAE;AAC7F,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAE1E,cAAQ,KAAK,sDAAsD,SAAS,oBAAoB,YAAY;AAC5G,aAAO,EAAE,SAAS,OAAO,OAAO,aAAa;AAAA,IAC/C;AAAA,EACF;AACF;AAMO,SAAS,iBAAyB;AACvC,SAAO,QAAQ,IAAI,gBAAqB,YAAK,QAAQ,IAAI,EAAE,QAAQ,GAAG,SAAS;AACjF;AAYA,eAAsB,kBAAkB,aAAuC;AAC7E,QAAM,UAAU,IAAI,gBAAgB,WAAW;AAC/C,SAAO,QAAQ,WAAW;AAC5B;AASA,eAAsB,gBAAgB,WAA2C;AAC/E,MAAI,UAAe,eAAQ,SAAS;AACpC,QAAM,cAAc,eAAe;AAGnC,MAAI,CAAC,QAAQ,WAAW,WAAW,GAAG;AACpC,WAAO;AAAA,EACT;AAGA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,UAAe,YAAK,SAAS,OAAO;AAC1C,UAAM,aAAkB,YAAK,SAAS,UAAU;AAEhD,QAAO,gBAAW,OAAO,KAAQ,gBAAW,UAAU,GAAG;AAEvD,UAAI,MAAM,kBAAkB,OAAO,GAAG;AACpC,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,SAAc,eAAQ,OAAO;AACnC,QAAI,WAAW,SAAS;AAEtB;AAAA,IACF;AACA,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;;;ACr3BA,IAAAC,SAAsB;AACtB,IAAAC,OAAoB;AACpB,IAAAC,MAAoB;AACpB,IAAAC,eAA+C;AAuBxC,SAASC,kBAAyB;AACvC,SAAO,QAAQ,IAAI,gBAAqB,YAAQ,YAAQ,GAAG,SAAS;AACtE;AA8CO,SAAS,gBACd,WACA,eACA,aACc;AACd,QAAM,OAAOC,gBAAe;AAC5B,QAAM,eAAoB,YAAK,MAAM,eAAe,aAAa,SAAS;AAE1E,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAW,gBAAW,YAAY;AAAA,IAClC;AAAA,EACF;AACF;AAkCA,eAAsB,yBACpB,WAC8B;AAC9B,QAAM,SAAS,UAAM,yBAAW;AAEhC,MAAI,CAAC,QAAQ,kBAAkB,CAAC,QAAQ,cAAc;AACpD,YAAQ,KAAK,6DAA6D;AAC1E,WAAO;AAAA,EACT;AAEA,SAAO,gBAAgB,WAAW,OAAO,gBAAgB,OAAO,YAAY;AAC9E;AAqDA,eAAsB,qBAA6C;AACjE,QAAM,SAAS,UAAM,yBAAW;AAEhC,MAAI,CAAC,QAAQ,kBAAkB,CAAC,QAAQ,cAAc;AACpD,WAAO;AAAA,EACT;AAEA,SAAY;AAAA,IACVC,gBAAe;AAAA,IACf,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AACF;;;ACnMA,IAAM,mBAAmB;AACzB,IAAM,iBAAiB;AACvB,IAAM,yBAAyB;AAG/B,IAAM,kBAAkB,oBAAI,IAAoB;AAUzC,SAAS,aAAa,WAA2B;AAEtD,QAAM,WAAW,gBAAgB,IAAI,SAAS;AAC9C,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,IAAI,IAAI,gBAAgB,OAAO,CAAC;AAGlD,MAAI,UAAU,QAAQ,wBAAwB;AAC5C,YAAQ;AAAA,MACN,4BAA4B,UAAU,IAAI,IAAI,iBAAiB,mBAAmB,CAAC;AAAA,IACrF;AAAA,EACF;AAGA,WAAS,OAAO,kBAAkB,QAAQ,gBAAgB,QAAQ;AAChE,QAAI,CAAC,UAAU,IAAI,IAAI,GAAG;AACxB,sBAAgB,IAAI,WAAW,IAAI;AACnC,cAAQ,IAAI,iCAAiC,IAAI,OAAO,SAAS,EAAE;AACnE,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,+BAA+B,gBAAgB,IAAI,cAAc,KAC9D,gBAAgB,IAAI;AAAA,EACzB;AACF;AAQO,SAAS,YAAY,WAAyB;AACnD,QAAM,OAAO,gBAAgB,IAAI,SAAS;AAC1C,MAAI,MAAM;AACR,oBAAgB,OAAO,SAAS;AAChC,YAAQ,IAAI,iCAAiC,IAAI,SAAS,SAAS,EAAE;AAAA,EACvE;AACF;AAoCO,SAAS,gBAAsB;AACpC,QAAM,QAAQ,gBAAgB;AAC9B,kBAAgB,MAAM;AACtB,MAAI,QAAQ,GAAG;AACb,YAAQ,IAAI,2BAA2B,KAAK,mBAAmB;AAAA,EACjE;AACF;;;ACxGA,IAAAC,OAAoB;AACpB,IAAAC,SAAsB;AAiQf,SAAS,kBAAkB,KAAoC;AAGpE,MAAO,gBAAgB,YAAK,KAAK,WAAW,CAAC,GAAG;AAC9C,WAAO;AAAA,MACL,SAAS,CAAC,OAAO,SAAS;AAAA,MAC1B,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,MAAO,gBAAgB,YAAK,KAAK,gBAAgB,CAAC,GAAG;AACnD,WAAO;AAAA,MACL,SAAS,CAAC,QAAQ,SAAS;AAAA,MAC3B,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,MAAO,gBAAgB,YAAK,KAAK,WAAW,CAAC,GAAG;AAC9C,WAAO;AAAA,MACL,SAAS,CAAC,QAAQ,SAAS;AAAA,MAC3B,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,MAAO,gBAAgB,YAAK,KAAK,mBAAmB,CAAC,GAAG;AACtD,WAAO;AAAA,MACL,SAAS,CAAC,OAAO,IAAI;AAAA,MACrB,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAGA,MAAO,gBAAgB,YAAK,KAAK,cAAc,CAAC,GAAG;AACjD,WAAO;AAAA,MACL,SAAS,CAAC,OAAO,SAAS;AAAA,MAC1B,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAGA,MAAO,gBAAgB,YAAK,KAAK,cAAc,CAAC,KAAQ,gBAAgB,YAAK,KAAK,SAAS,CAAC,GAAG;AAC7F,WAAO;AAAA,MACL,SAAS,CAAC,UAAU,SAAS;AAAA,MAC7B,aAAa;AAAA,MACb,cAAiB,gBAAgB,YAAK,KAAK,cAAc,CAAC,IAAI,iBAAiB;AAAA,IACjF;AAAA,EACF;AAGA,MAAO,gBAAgB,YAAK,KAAK,aAAa,CAAC,GAAG;AAChD,WAAO;AAAA,MACL,SAAS,CAAC,UAAU,SAAS;AAAA,MAC7B,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,MAAO,gBAAgB,YAAK,KAAK,gBAAgB,CAAC,GAAG;AACnD,UAAM,gBAAqB,YAAK,KAAK,gBAAgB;AACrD,UAAM,UAAa,kBAAa,eAAe,OAAO;AACtD,QAAI,QAAQ,SAAS,eAAe,GAAG;AACrC,aAAO;AAAA,QACL,SAAS,CAAC,UAAU,SAAS;AAAA,QAC7B,aAAa;AAAA,QACb,cAAc;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,MAAO,gBAAgB,YAAK,KAAK,kBAAkB,CAAC,GAAG;AACrD,WAAO;AAAA,MACL,SAAS,CAAC,OAAO,WAAW,MAAM,kBAAkB;AAAA,MACpD,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAGA,MAAO,gBAAgB,YAAK,KAAK,cAAc,CAAC,KAAQ,gBAAgB,YAAK,KAAK,SAAS,CAAC,GAAG;AAC7F,WAAO;AAAA,MACL,SAAS,CAAC,UAAU,SAAS;AAAA,MAC7B,aAAa;AAAA,MACb,cAAiB,gBAAgB,YAAK,KAAK,cAAc,CAAC,IAAI,iBAAiB;AAAA,IACjF;AAAA,EACF;AAGA,MAAO,gBAAgB,YAAK,KAAK,QAAQ,CAAC,KAAQ,gBAAgB,YAAK,KAAK,QAAQ,CAAC,GAAG;AACtF,WAAO;AAAA,MACL,SAAS,CAAC,MAAM,OAAO,UAAU;AAAA,MACjC,aAAa;AAAA,MACb,cAAiB,gBAAgB,YAAK,KAAK,QAAQ,CAAC,IAAI,WAAW;AAAA,IACrE;AAAA,EACF;AAGA,MAAO,gBAAgB,YAAK,KAAK,YAAY,CAAC,KAAQ,gBAAgB,YAAK,KAAK,YAAY,CAAC,GAAG;AAC9F,WAAO;AAAA,MACL,SAAS,CAAC,SAAS,OAAO;AAAA,MAC1B,aAAa;AAAA,MACb,cAAiB,gBAAgB,YAAK,KAAK,YAAY,CAAC,IAAI,eAAe;AAAA,IAC7E;AAAA,EACF;AAGA,SAAO;AACT;;;AjBrVA,IAAAC,OAAoB;AACpB,IAAAC,MAAoB;AACpB,IAAAC,SAAsB;AAGtB,IAAM,cAAc;AAcpB,eAAe,iBACb,QACA,WAAmB,IAAI,KAAK,KACJ;AAExB,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,YAAY,OAAO,cAAc;AAEvC,MAAI,YAAY,MAAM,UAAU;AAE9B,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,OAAO,eAAe;AACzB,YAAQ,KAAK,8DAA8D;AAC3E,WAAO;AAAA,EACT;AAEA,UAAQ,IAAI,sEAAsE;AAElF,MAAI;AACF,UAAM,SAAS,OAAO,WAAW;AACjC,UAAM,WAAW,MAAM,MAAM,GAAG,MAAM,oBAAoB;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY;AAAA,QACZ,eAAe,OAAO;AAAA,MACxB,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,cAAQ,MAAM,yCAAyC,SAAS,MAAM,IAAI,UAAU,SAAS,SAAS,UAAU,EAAE;AAClH,aAAO;AAAA,IACT;AAEA,UAAM,gBAAgB,MAAM,SAAS,KAAK;AAO1C,UAAM,gBAA+B;AAAA,MACnC,GAAG;AAAA,MACH,cAAc,cAAc;AAAA,MAC5B,eAAe,cAAc,iBAAiB,OAAO;AAAA,MACrD,YAAY,MAAO,cAAc,aAAa;AAAA,IAChD;AAGA,cAAM,0BAAW,aAAa;AAC9B,YAAQ,IAAI,qDAAqD;AAEjE,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACpG,WAAO;AAAA,EACT;AACF;AAOA,eAAe,cACb,KACA,UAAuB,CAAC,GACxB,sBAA+B,MACZ;AACnB,MAAI,SAAS,UAAM,0BAAW;AAC9B,MAAI,CAAC,QAAQ,cAAc;AACzB,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAGA,WAAS,MAAM,iBAAiB,MAAM;AAEtC,QAAM,UAAU;AAAA,IACd,GAAG,QAAQ;AAAA,IACX,iBAAiB,UAAU,OAAO,YAAY;AAAA,IAC9C,gBAAgB;AAAA,EAClB;AAEA,MAAI,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,QAAQ,CAAC;AAGvD,MAAI,SAAS,WAAW,OAAO,uBAAuB,OAAO,eAAe;AAC1E,YAAQ,IAAI,qEAAqE;AAGjF,UAAM,kBAAkB,MAAM,iBAAiB,EAAE,GAAG,QAAQ,YAAY,EAAE,CAAC;AAE3E,QAAI,gBAAgB,iBAAiB,OAAO,cAAc;AAExD,YAAM,eAAe;AAAA,QACnB,GAAG,QAAQ;AAAA,QACX,iBAAiB,UAAU,gBAAgB,YAAY;AAAA,QACvD,gBAAgB;AAAA,MAClB;AACA,iBAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,SAAS,aAAa,CAAC;AAAA,IACnE;AAAA,EACF;AAEA,SAAO;AACT;AAUA,eAAeC,gBAAgD;AAC7D,MAAI;AACF,UAAM,SAAS,UAAM,0BAAW;AAChC,QAAI,CAAC,QAAQ,YAAY;AACvB,cAAQ,KAAK,gEAAgE;AAC7E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,SAAS,OAAO,WAAW;AACjC,UAAM,WAAW,MAAM,cAAc,GAAG,MAAM,mBAAmB;AAEjE,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,KAAK,6CAA6C,SAAS,MAAM,EAAE;AAC3E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,UAAU,KAAK,YAAY,CAAC;AAElC,YAAQ,IAAI,2BAA2B,OAAO,KAAK,OAAO,EAAE,MAAM,uBAAuB;AACzF,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,KAAK,4CAA4C,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACvG,WAAO,CAAC;AAAA,EACV;AACF;AAgBA,IAAM,SAAN,MAAM,QAAO;AAAA;AAAA,EAqCX,cAAc;AApCd,SAAQ,YAAoB;AAC5B,SAAQ,WAA0B;AAClC;AAAA,SAAQ,aAA4B;AACpC;AAAA,SAAQ,eAA8B;AAGtC;AAAA,SAAQ,cAAc,oBAAI,IAA8B;AAGxD;AAAA;AAAA;AAAA,SAAQ,kBAAkB,oBAAI,IAAY;AAG1C;AAAA;AAAA;AAAA,SAAQ,qBAAqB,oBAAI,IAAY;AAC7C;AAAA,SAAQ,eAAe;AAEvB;AAAA,SAAQ,qBAA4C;AAGpD;AAAA;AAAA,SAAQ,uBAAuB;AAE/B;AAAA,SAAQ,uBAAuB,oBAAI,IAAoB;AAIvD;AAAA;AAAA,SAAQ,2BAA2B,oBAAI,IAAiD;AAExF;AAAA;AAAA,SAAQ,uBAAuB;AAG/B;AAAA;AAAA,SAAQ,uBAAuB,oBAAI,IAA2B;AAG9D;AAAA;AAAA;AAAA,SAAQ,sBAA6C;AACrD,SAAQ,wBAAwB;AAI9B,SAAK,YAAY,IAAI,UAAU;AAAA,EACjC;AAAA,EAtBA;AAAA,SAAwB,0BAA0B;AAAA;AAAA,EAKlD;AAAA;AAAA,SAAwB,iCAAiC;AAAA;AAAA,EACzD;AAAA;AAAA,SAAwB,0BAA0B;AAAA;AAAA,EAYlD;AAAA,SAAwB,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA,EASnD,MAAM,QAAuB;AAC3B,YAAQ,IAAI,qCAAqC;AAGjD,SAAK,YAAY,MAAM,aAAa;AACpC,YAAQ,IAAI,wBAAwB,KAAK,SAAS,EAAE;AAGpD,UAAM,SAAS,UAAM,0BAAW;AAChC,QAAI,QAAQ,WAAW;AACrB,WAAK,WAAW,OAAO;AACvB,cAAQ,IAAI,4CAA4C,KAAK,QAAQ,EAAE;AAAA,IACzE;AAGA,UAAM,KAAK,UAAU,MAAM;AAC3B,YAAQ,IAAI,6BAA6B;AAKzC,SAAK,oBAAoB;AAGzB,UAAM,KAAK,mBAAmB;AAG9B,UAAM,KAAK,uBAAuB;AAGlC,UAAM,KAAK,wBAAwB;AAOnC,SAAK,wBAAwB;AAG7B,SAAK,sBAAsB;AAE3B,YAAQ,IAAI,sCAAsC;AAGlD,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,wBAAuC;AACnD,QAAI;AACF,YAAM,SAAS,MAAM,gBAAgB,YAAY,OAAO;AAExD,UAAI,OAAO,iBAAiB;AAC1B,gBAAQ,IAAI;AAAA,kCAA2B,OAAO,cAAc,WAAM,OAAO,aAAa,EAAE;AACxF,gBAAQ,IAAI,gCAAgC;AAC5C,gCAAwB;AAAA,MAC1B;AAAA,IACF,SAAS,OAAO;AAAA,IAEhB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBAA4B;AAElC,SAAK,UAAU,GAAG,QAAQ,YAAY;AACpC,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB,CAAC;AAMD,SAAK,UAAU,GAAG,UAAU,YAAY;AACtC,YAAM,WAAW,eAAe,EAAE,IAAI,QAAM;AAAA,QAC1C,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,MAAM,EAAE;AAAA;AAAA;AAAA,QAGR,WAAW,KAAK,gBAAgB,EAAE,IAAI;AAAA;AAAA,QAEtC,oBAAoB,KAAK,gBAAgB,IAAI,EAAE,IAAI;AAAA,MACrD,EAAE;AAEF,aAAO;AAAA,QACL,SAAS;AAAA,QACT,WAAW,KAAK;AAAA,QAChB,UAAU,KAAK;AAAA;AAAA,QACf,UAAa,aAAS;AAAA,QACtB,UAAa,aAAS;AAAA,QACtB,MAAS,SAAK;AAAA,QACd;AAAA,MACF;AAAA,IACF,CAAC;AAKD,SAAK,UAAU,GAAG,eAAe,OAAO,WAAuD;AAC7F,YAAM,EAAE,WAAW,YAAY,IAAI;AACnC,iBAAa,WAAW,WAAW;AAEnC,YAAM,cAAc;AACpB,YAAM,gBAAgB;AACtB,UAAI,YAAoB;AAExB,eAAS,UAAU,GAAG,WAAW,aAAa,WAAW;AACvD,YAAI;AAEF,gBAAM,KAAK,eAAe,WAAW,WAAW;AAGhD,gBAAM,YAAY,KAAK,oBAAoB,WAAW;AACtD,cAAI,CAAC,WAAW;AACd,oBAAQ,KAAK,qDAAqD,WAAW,EAAE;AAC/E,wBAAY;AAEZ,mBAAO,EAAE,SAAS,OAAO,WAAW,OAAO,OAAO,UAAU;AAAA,UAC9D;AAEA,iBAAO,EAAE,SAAS,MAAM,WAAW,KAAK;AAAA,QAC1C,SAAS,OAAO;AACd,sBAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,kBAAQ,MAAM,+BAA+B,OAAO,IAAI,WAAW,YAAY,SAAS;AAExF,cAAI,UAAU,aAAa;AAEzB,kBAAM,QAAQ,gBAAgB,KAAK,IAAI,GAAG,UAAU,CAAC;AACrD,oBAAQ,IAAI,wBAAwB,QAAQ,GAAI,MAAM;AACtD,kBAAM,IAAI,QAAQ,CAAAC,aAAW,WAAWA,UAAS,KAAK,CAAC;AAGvD,kBAAM,KAAK,kBAAkB,WAAW;AAAA,UAC1C;AAAA,QACF;AAAA,MACF;AAGA,aAAO,EAAE,SAAS,OAAO,WAAW,OAAO,OAAO,gBAAgB,WAAW,cAAc,SAAS,GAAG;AAAA,IACzG,CAAC;AAKD,SAAK,UAAU,GAAG,kBAAkB,OAAO,WAAoC;AAC7E,YAAM,EAAE,YAAY,IAAI;AACxB,YAAM,KAAK,kBAAkB,WAAW;AACxC,oBAAe,WAAW;AAC1B,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,CAAC;AAGD,SAAK,UAAU,GAAG,mBAAmB,OAAO,WAAoC;AAC9E,YAAM,EAAE,YAAY,IAAI;AACxB,YAAM,UAAU,eAAe,EAAE,KAAK,OAAK,EAAE,SAAS,WAAW;AACjE,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI,MAAM,qBAAqB;AAAA,MACvC;AACA,YAAM,KAAK,eAAe,QAAQ,IAAI,WAAW;AACjD,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,CAAC;AAGD,SAAK,UAAU,GAAG,sBAAsB,OAAO,WAAoC;AACjF,YAAM,EAAE,YAAY,IAAI;AACxB,YAAM,KAAK,kBAAkB,WAAW;AACxC,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,CAAC;AAGD,SAAK,UAAU,GAAG,YAAY,YAAY;AACxC,cAAQ,IAAI,qCAAqC;AACjD,YAAM,KAAK,SAAS;AACpB,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,CAAC;AAID,SAAK,UAAU,GAAG,iBAAiB,YAAY;AAC7C,YAAM,WAAW,eAAe,EAAE,IAAI,QAAM;AAAA,QAC1C,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,MAAM,EAAE;AAAA,QACR,kBAAkB,KAAK,YAAY,IAAI,EAAE,IAAI;AAAA,QAC7C,mBAAmB,KAAK,gBAAgB,IAAI,EAAE,IAAI;AAAA;AAAA,QAElD,QAAQ,KAAK,gBAAgB,EAAE,IAAI;AAAA,QACnC,WAAW,KAAK,oBAAoB,EAAE,IAAI;AAAA,MAC5C,EAAE;AAEF,YAAM,eAAe,SAAS,OAAO,OAAK,EAAE,MAAM,EAAE;AACpD,YAAM,aAAa,SAAS,OAAO,OAAK,EAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE;AAEzE,aAAO;AAAA,QACL,eAAe,SAAS;AAAA,QACxB,oBAAoB;AAAA,QACpB,kBAAkB;AAAA,QAClB;AAAA,MACF;AAAA,IACF,CAAC;AAID,SAAK,UAAU,GAAG,4BAA4B,YAAY;AACxD,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,SAAS;AAC7C,eAAO;AAAA,UACL,UAAU;AAAA,UACV,OAAO;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,QACnB;AAAA,MACF;AAGA,YAAM,WAAW,eAAe;AAChC,YAAM,iBAAiB,SAAS,KAAK,OAAK,KAAK,gBAAgB,EAAE,IAAI,CAAC;AAGtE,UAAI,kBAAkB;AACtB,UAAI,kBAAiC;AACrC,UAAI,cAA6B;AAEjC,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,GAAG,OAAO,OAAO,mBAAmB;AAAA,UAC/D,SAAS;AAAA,YACP,iBAAiB,UAAU,OAAO,YAAY;AAAA,YAC9C,gBAAgB;AAAA,UAClB;AAAA,QACF,CAAC;AAED,YAAI,SAAS,IAAI;AACf,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,4BAAkB,KAAK,cAAc;AACrC,4BAAkB,KAAK,cAAc;AAAA,QACvC,OAAO;AACL,wBAAc,mBAAmB,SAAS,MAAM;AAAA,QAClD;AAAA,MACF,SAAS,KAAK;AACZ,sBAAc,eAAe,QAAQ,IAAI,UAAU;AAAA,MACrD;AAGA,YAAM,eAAe,oBAAoB,KAAK;AAE9C,aAAO;AAAA,QACL,UAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,KAAK;AAAA,QAChB;AAAA,QACA;AAAA;AAAA,QAEA,mBAAmB,kBAAkB,mBAAmB;AAAA,MAC1D;AAAA,IACF,CAAC;AAID,SAAK,UAAU,GAAG,iBAAiB,YAAY;AAC7C,YAAM,gBAAgB,iBAAiB;AACvC,YAAM,UAAU,cAAc,cAAc;AAC5C,aAAO,EAAE,QAAQ;AAAA,IACnB,CAAC;AAID,SAAK,UAAU,GAAG,eAAe,OAAO,WAAkC;AACxE,YAAM,EAAE,UAAU,IAAI;AAGtB,UAAI,CAAC,WAAW;AACd,eAAO,EAAE,SAAS,OAAO,OAAO,yBAAyB;AAAA,MAC3D;AAEA,YAAM,gBAAgB,iBAAiB;AAGvC,UAAI,CAAC,cAAc,UAAU,SAAS,GAAG;AACvC,eAAO,EAAE,SAAS,OAAO,OAAO,kCAAkC;AAAA,MACpE;AAGA,YAAM,cAAc,WAAW,SAAS;AACxC,YAAM,cAAc,SAAS;AAG7B,YAAM,eAAe,SAAS;AAE9B,WAAK,yBAAyB,OAAO,SAAS;AAC9C,WAAK,qBAAqB,OAAO,SAAS;AAC1C,cAAQ,IAAI,sCAAsC,SAAS,EAAE;AAE7D,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,CAAC;AAGD,SAAK,UAAU,GAAG,sBAAsB,OAAO,WAAkC;AAC/E,YAAM,EAAE,UAAU,IAAI;AAEtB,UAAI,CAAC,WAAW;AACd,eAAO,EAAE,SAAS,OAAO,OAAO,yBAAyB;AAAA,MAC3D;AAEA,cAAQ,IAAI,oDAAoD,SAAS,EAAE;AAE3E,YAAM,SAAS,MAAM,iBAAiB,SAAS;AAC/C,aAAO;AAAA,IACT,CAAC;AAGD,SAAK,UAAU,GAAG,qBAAqB,YAAY;AACjD,YAAM,SAAS,mBAAmB;AAClC,aAAO,EAAE,SAAS,MAAM,SAAS,OAAO;AAAA,IAC1C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBAAoC;AAChD,UAAM,WAAW,eAAe;AAEhC,eAAW,WAAW,UAAU;AAC9B,UAAI;AACF,cAAM,KAAK,eAAe,QAAQ,IAAI,QAAQ,IAAI;AAAA,MACpD,SAAS,OAAO;AACd,gBAAQ,MAAM,6CAA6C,QAAQ,IAAI,KAAK,KAAK;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoB,aAA8B;AACxD,WAAO,KAAK,YAAY,IAAI,WAAW,KAAK,KAAK,gBAAgB,IAAI,WAAW;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,gBAAgB,aAA8B;AACpD,UAAM,aAAa,KAAK,YAAY,IAAI,WAAW;AACnD,QAAI,CAAC,WAAY,QAAO;AAGxB,WAAO,WAAW,OAAO,UAAU,EAAE;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAc,eAAkB,WAAmB,WAAgD;AAEjG,UAAM,eAAe,KAAK,qBAAqB,IAAI,SAAS;AAC5D,QAAI,cAAc;AAChB,cAAQ,IAAI,4DAA4D,SAAS,cAAc;AAC/F,UAAI;AACF,cAAM;AAAA,MACR,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACJ,UAAM,cAAc,IAAI,QAAc,CAAAA,aAAW;AAC/C,oBAAcA;AAAA,IAChB,CAAC;AACD,SAAK,qBAAqB,IAAI,WAAW,WAAW;AAEpD,QAAI;AACF,aAAO,MAAM,UAAU;AAAA,IACzB,UAAE;AACA,kBAAa;AAEb,UAAI,KAAK,qBAAqB,IAAI,SAAS,MAAM,aAAa;AAC5D,aAAK,qBAAqB,OAAO,SAAS;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,WAAmB,aAAoC;AAIlF,QAAI,KAAK,YAAY,IAAI,WAAW,GAAG;AACrC,UAAI,KAAK,gBAAgB,IAAI,WAAW,GAAG;AACzC,gBAAQ,IAAI,iCAAiC,WAAW,EAAE;AAC1D;AAAA,MACF;AACA,UAAI,KAAK,mBAAmB,IAAI,WAAW,GAAG;AAG5C,gBAAQ,IAAI,uCAAuC,WAAW,cAAc;AAE5E,cAAM,UAAU;AAChB,cAAM,YAAY,KAAK,IAAI;AAC3B,eAAO,KAAK,mBAAmB,IAAI,WAAW,KAAK,KAAK,IAAI,IAAI,YAAY,SAAS;AACnF,gBAAM,IAAI,QAAQ,CAAAA,aAAW,WAAWA,UAAS,GAAG,CAAC;AAAA,QACvD;AAEA,YAAI,KAAK,gBAAgB,IAAI,WAAW,GAAG;AACzC,kBAAQ,IAAI,6CAA6C,WAAW,EAAE;AACtE;AAAA,QACF;AAEA,gBAAQ,KAAK,6CAA6C,WAAW,EAAE;AAAA,MACzE;AAEA,cAAQ,KAAK,0CAA0C,WAAW,wBAAwB;AAC1F,YAAM,KAAK,kBAAkB,WAAW;AAAA,IAC1C;AAGA,UAAM,SAAS,UAAM,0BAAW;AAChC,QAAI,CAAC,UAAU,CAAC,OAAO,cAAc;AACnC,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AAOA,QAAI,YAAY,OAAO,WAAW,QAAQ,IAAI,mBAAmB;AAGjE,QAAI,OAAO,kBAAkB,kBAAkB;AAC7C,kBAAY,OAAO,iBAAiB;AACpC,cAAQ,IAAI,qCAAqC,SAAS,EAAE;AAAA,IAC9D;AAGA,UAAM,eAAe,IAAI,IAAI,SAAS;AACtC,UAAM,aAAa,aAAa,aAAa,WAAW,SAAS;AACjE,UAAM,SAAS,QAAQ,IAAI,mBAAmB;AAC9C,UAAM,QAAQ,GAAG,UAAU,KAAK,aAAa,QAAQ,IAAI,MAAM;AAC/D,YAAQ,IAAI,0BAA0B,KAAK,gBAAgB,SAAS,KAAK;AAGzE,UAAM,SAAS,IAAI,4BAAc;AAGjC,UAAM,cAAc,IAAI,0BAAY;AAGpC,UAAM,aAA+B;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,SAAK,YAAY,IAAI,aAAa,UAAU;AAE5C,SAAK,mBAAmB,IAAI,WAAW;AAGvC,WAAO,GAAG,WAAW,OAAO,YAAY;AACtC,UAAI,QAAQ,SAAS,aAAa,QAAQ,SAAS;AACjD,gBAAQ,IAAI,iCAAiC,SAAS,KAAK,QAAQ,OAAO;AAG1E,eAAO,eAAe;AAEtB,YAAI;AAKF,gBAAM,SAAS,QAAQ;AACvB,gBAAM,eAAoB,YAAK,aAAa,OAAO;AAGnD,gBAAM,MAAM,OAAO,gBAAgB;AAEnC,cAAI,OAAO,cAAc;AACvB,oBAAQ,IAAI,yCAAyC,OAAO,YAAY,EAAE;AAAA,UAC5E,OAAO;AACL,oBAAQ,IAAI,8CAA8C,YAAY,EAAE;AAAA,UAC1E;AACA,gBAAM,SAAS,MAAM,YAAY,QAAQ,QAAQ;AAAA,YAC/C;AAAA,UACF,CAAC;AAGD,gBAAM,OAAO,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB;AAAA,UACF,CAAC;AAED,kBAAQ,IAAI,kCAAkC,SAAS,KAAK,OAAO,UAAU,YAAY,QAAQ;AAGjG,cAAI,OAAO,WAAW,OAAO,WAAW,UAAU,OAAO,WAAW,QAAQ;AAE1E,gCAAoB,WAAW,EAAE,KAAK,mBAAiB;AACrD,kBAAI,cAAc,gBAAgB,GAAG;AACnC,wBAAQ,IAAI,8BAA8B,cAAc,aAAa,qCAAqC;AAAA,cAC5G;AAAA,YACF,CAAC,EAAE,MAAM,SAAO;AACd,sBAAQ,KAAK,8CAA8C,IAAI,OAAO;AAAA,YACxE,CAAC;AAAA,UACH;AAAA,QACF,SAAS,OAAO;AAEd,gBAAM,OAAO,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB,QAAQ;AAAA,cACN,SAAS;AAAA,cACT,OAAO;AAAA,cACP,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YAC/D;AAAA,UACF,CAAC;AAED,kBAAQ,MAAM,wCAAwC,SAAS,KAAK,KAAK;AAAA,QAC3E;AAAA,MACF;AAAA,IACF,CAAC;AAGD,WAAO,GAAG,kBAAkB,OAAO,YAAY;AAC7C,UAAI,QAAQ,SAAS,oBAAoB,QAAQ,SAAS;AACxD,cAAM,MAAM,QAAQ;AACpB,gBAAQ,IAAI,wCAAwC,SAAS,KAAK,IAAI,MAAM;AAG5E,eAAO,eAAe;AAEtB,YAAI;AACF,cAAI;AACJ,kBAAQ,IAAI,QAAQ;AAAA,YAClB,KAAK;AACH,uBAAS,MAAM,eAAe,KAAK,WAAW;AAC9C;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,gBAAgB,KAAK,WAAW;AAC/C;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,eAAe,KAAK,WAAW;AAC9C;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,iBAAiB,KAAK,WAAW;AAChD;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,gBAAgB,KAAK,WAAW;AAC/C;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,eAAe,KAAK,WAAW;AAC9C;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,iBAAiB,KAAK,WAAW;AAChD;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,eAAe,KAAK,WAAW;AAC9C;AAAA,YACF,KAAK;AACH,uBAAS,MAAM,WAAW,KAAK,WAAW;AAC1C;AAAA,YACF;AACE,uBAAS;AAAA,gBACP,SAAS;AAAA,gBACT,OAAO,kCAAmC,IAAY,MAAM;AAAA,cAC9D;AAAA,UACJ;AAGA,gBAAM,OAAO,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB;AAAA,UACF,CAAC;AAED,kBAAQ,IAAI,2BAA2B,IAAI,MAAM,kBAAkB,SAAS,KAAK,OAAO,UAAU,YAAY,QAAQ;AAAA,QACxH,SAAS,OAAO;AAEd,gBAAM,OAAO,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB,QAAQ;AAAA,cACN,SAAS;AAAA,cACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YAC9D;AAAA,UACF,CAAC;AAED,kBAAQ,MAAM,+CAA+C,SAAS,KAAK,KAAK;AAAA,QAClF;AAAA,MACF;AAAA,IACF,CAAC;AAGD,WAAO,GAAG,kBAAkB,OAAO,YAAY;AAC7C,UAAI,QAAQ,SAAS,oBAAoB,QAAQ,SAAS;AACxD,cAAM,MAAM,QAAQ;AACpB,gBAAQ,IAAI,wCAAwC,SAAS,KAAK,IAAI,MAAM;AAG5E,eAAO,eAAe;AAEtB,YAAI;AACF,gBAAM,gBAAgB,iBAAiB;AACvC,cAAI;AAEJ,cAAI,IAAI,WAAW,SAAS;AAK1B,kBAAM,WAAW,MAAM,yBAAyB,IAAI,SAAS;AAC7D,gBAAI,CAAC,UAAU;AACb,sBAAQ,MAAM,oDAAoD,IAAI,SAAS,EAAE;AAEjF,oBAAM,OAAO,KAAK;AAAA,gBAChB,MAAM;AAAA,gBACN,WAAW,QAAQ;AAAA,gBACnB,QAAQ,EAAE,SAAS,OAAO,OAAO,sDAAsD;AAAA,cACzF,CAAC;AACD;AAAA,YACF;AAEA,gBAAI,CAAC,SAAS,QAAQ;AACpB,sBAAQ,MAAM,yCAAyC,SAAS,IAAI,EAAE;AACtE,oBAAM,OAAO,KAAK;AAAA,gBAChB,MAAM;AAAA,gBACN,WAAW,QAAQ;AAAA,gBACnB,QAAQ,EAAE,SAAS,OAAO,OAAO,yBAAyB,SAAS,IAAI,GAAG;AAAA,cAC5E,CAAC;AACD;AAAA,YACF;AAEA,oBAAQ,IAAI,uCAAuC,SAAS,IAAI,QAAQ,IAAI,SAAS,EAAE;AAGvF,kBAAM,OAAO,IAAI,QAAQ,cAAc,SAAS,IAAI;AACpD,kBAAM,aAAa,WAAW,IAAI,UAAU,YAAY,CAAC,IAAI,IAAI,WAAW,YAAY,CAAC;AAGzF,kBAAM,qBAAqB,OAAO,SAI5B;AACJ,oBAAMC,UAAS,UAAM,0BAAW;AAChC,kBAAIA,SAAQ,cAAc;AACxB,oBAAI;AACF,wBAAM,SAASA,QAAO,WAAW;AACjC,wBAAM,WAAW,MAAM,MAAM,GAAG,MAAM,gBAAgB,IAAI,SAAS,WAAW;AAAA,oBAC5E,QAAQ;AAAA,oBACR,SAAS;AAAA,sBACP,iBAAiB,UAAUA,QAAO,YAAY;AAAA,sBAC9C,gBAAgB;AAAA,oBAClB;AAAA,oBACA,MAAM,KAAK,UAAU,IAAI;AAAA,kBAC3B,CAAC;AAED,sBAAI,SAAS,IAAI;AACf,4BAAQ,IAAI,uCAAuC,IAAI,SAAS,EAAE;AAAA,kBACpE,OAAO;AACL,4BAAQ,KAAK,4CAA4C,SAAS,UAAU,EAAE;AAAA,kBAChF;AAAA,gBACF,SAAS,aAAa;AACpB,0BAAQ,KAAK,2CAA2C,WAAW;AAAA,gBACrE;AAAA,cACF;AAAA,YACF;AAIC,aAAC,YAAY;AACZ,oBAAM,cAAc;AAEpB,oBAAM,iBAAiB;AAGvB,oBAAM,mBAAmB;AAAA,gBACvB,oBAAmB,oBAAI,KAAK,GAAE,YAAY;AAAA,gBAC1C,cAAc;AAAA;AAAA,cAChB,CAAC;AAED,kBAAI;AAEF,sBAAM,cAAc,WAAW;AAG/B,sBAAM,YAAY,UAAM,0BAAW;AACnC,sBAAM,kBAAkB,WAAW,kBAAkB;AAIrD,wBAAQ,IAAI,qDAAqD,SAAS,IAAI,YAAY,IAAI,KAAK;AACnG,sBAAM,kBAAkB,MAAM,gBAAgB,SAAS,MAAM,MAAM,IAAI,WAAW,eAAe;AACjG,oBAAI,CAAC,gBAAgB,SAAS;AAC5B,wBAAMC,YAAW,+BAA+B,gBAAgB,KAAK;AACrE,0BAAQ,MAAM,YAAYA,SAAQ,EAAE;AACpC,wBAAM,mBAAmB,EAAE,cAAcA,UAAS,CAAC;AACnD;AAAA,gBACF;AACA,wBAAQ,IAAI,qCAAqC,IAAI,EAAE;AAGvD,oBAAI;AACJ,yBAAS,UAAU,GAAG,WAAW,aAAa,WAAW;AACvD,0BAAQ,IAAI,qCAAqC,OAAO,IAAI,WAAW,MAAM;AAE7E,wBAAM,cAAc,MAAM,cAAc,YAAY;AAAA,oBAClD,WAAW,IAAI;AAAA,oBACf;AAAA,oBACA,OAAO,OAAO,QAAQ;AAEpB,8BAAQ,IAAI,2BAA2B,IAAI,SAAS,KAAK,GAAG,EAAE;AAC9D,4BAAM,mBAAmB;AAAA,wBACvB,YAAY;AAAA,wBACZ,cAAc;AAAA;AAAA,sBAChB,CAAC;AAAA,oBACH;AAAA,oBACA,gBAAgB,CAAC,QAAQ,UAAU;AACjC,0BAAI,WAAW,SAAS;AACtB,gCAAQ,MAAM,6BAA6B,IAAI,SAAS,KAAK,KAAK,EAAE;AAEpE,2CAAmB,EAAE,cAAc,SAAS,0BAA0B,CAAC;AAAA,sBACzE,WAAW,WAAW,gBAAgB;AACpC,gCAAQ,IAAI,oCAAoC,IAAI,SAAS,KAAK;AAAA,sBACpE;AAAA,oBACF;AAAA,kBACF,CAAC;AAED,sBAAI,YAAY,SAAS;AACvB,4BAAQ,IAAI,4CAA4C,IAAI,SAAS,EAAE;AACvE;AAAA,kBACF;AAEA,8BAAY,YAAY;AACxB,0BAAQ,KAAK,iCAAiC,OAAO,YAAY,SAAS,EAAE;AAE5E,sBAAI,UAAU,aAAa;AACzB,4BAAQ,IAAI,wBAAwB,cAAc,OAAO;AACzD,0BAAM,IAAI,QAAQ,CAAAF,aAAW,WAAWA,UAAS,cAAc,CAAC;AAAA,kBAClE;AAAA,gBACF;AAGA,sBAAM,WAAW,uBAAuB,WAAW,cAAc,SAAS;AAC1E,wBAAQ,MAAM,YAAY,QAAQ,EAAE;AACpC,sBAAM,mBAAmB,EAAE,cAAc,SAAS,CAAC;AAAA,cAErD,SAAS,OAAO;AACd,sBAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,wBAAQ,MAAM,wCAAwC,KAAK;AAC3D,sBAAM,mBAAmB,EAAE,cAAc,qBAAqB,QAAQ,GAAG,CAAC;AAAA,cAC5E;AAAA,YACF,GAAG;AAGH,qBAAS;AAAA,cACP,SAAS;AAAA,cACT;AAAA;AAAA,YAEF;AAAA,UACF,WAAW,IAAI,WAAW,QAAQ;AAChC,kBAAM,cAAc,WAAW,IAAI,SAAS;AAG5C,kBAAM,cAAc,IAAI,SAAS;AAGjC,kBAAMC,UAAS,UAAM,0BAAW;AAChC,gBAAIA,SAAQ,cAAc;AACxB,kBAAI;AACF,sBAAM,SAASA,QAAO,WAAW;AACjC,sBAAM,MAAM,GAAG,MAAM,gBAAgB,IAAI,SAAS,WAAW;AAAA,kBAC3D,QAAQ;AAAA,kBACR,SAAS;AAAA,oBACP,iBAAiB,UAAUA,QAAO,YAAY;AAAA,kBAChD;AAAA,gBACF,CAAC;AACD,wBAAQ,IAAI,mCAAmC,IAAI,SAAS,EAAE;AAAA,cAChE,QAAQ;AAAA,cAER;AAAA,YACF;AAEA,qBAAS,EAAE,SAAS,KAAK;AAAA,UAC3B,OAAO;AACL,qBAAS;AAAA,cACP,SAAS;AAAA,cACT,OAAO,0BAA2B,IAAY,MAAM;AAAA,YACtD;AAAA,UACF;AAGA,gBAAM,OAAO,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB;AAAA,UACF,CAAC;AAED,kBAAQ,IAAI,2BAA2B,IAAI,MAAM,kBAAkB,IAAI,SAAS,KAAK,OAAO,UAAU,YAAY,QAAQ;AAAA,QAC5H,SAAS,OAAO;AAEd,gBAAM,OAAO,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB,QAAQ;AAAA,cACN,SAAS;AAAA,cACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YAC9D;AAAA,UACF,CAAC;AAED,kBAAQ,MAAM,4CAA4C,KAAK;AAAA,QACjE;AAAA,MACF;AAAA,IACF,CAAC;AAGD,WAAO,GAAG,iBAAiB,OAAO,YAAY;AAC5C,UAAI,QAAQ,SAAS,mBAAmB,QAAQ,SAAS;AACvD,cAAM,MAAM,QAAQ;AACpB,gBAAQ,IAAI,8CAA8C,SAAS,KAAK,IAAI,MAAM;AAGlF,eAAO,eAAe;AAGtB,cAAM,2BAA2B,CAAC,WAAmB,eAAuB;AAAA,UAC1E,SAAS,OAAO,UAAkB;AAChC,gBAAI;AACF,oBAAM,OAAO,KAAK;AAAA,gBAChB,MAAM;AAAA,gBACN;AAAA,gBACA,QAAQ,EAAE,SAAS,MAAM,QAAQ,SAAS,WAAW,MAAM;AAAA,cAC7D,CAAC;AAAA,YACH,SAAS,WAAW;AAElB,sBAAQ,MAAM,yEAAyE,SAAS;AAAA,YAClG;AAAA,UACF;AAAA,UACA,YAAY,OAAO,oBAA6B;AAC9C,gBAAI;AACF,oBAAM,OAAO,KAAK;AAAA,gBAChB,MAAM;AAAA,gBACN;AAAA,gBACA,QAAQ,EAAE,SAAS,MAAM,QAAQ,YAAY,WAAW,gBAAgB;AAAA,cAC1E,CAAC;AAAA,YACH,SAAS,WAAW;AAClB,sBAAQ,MAAM,4EAA4E,SAAS;AAAA,YACrG;AAAA,UACF;AAAA,UACA,SAAS,OAAO,UAAkB;AAChC,gBAAI;AACF,oBAAM,OAAO,KAAK;AAAA,gBAChB,MAAM;AAAA,gBACN;AAAA,gBACA,QAAQ,EAAE,SAAS,OAAO,QAAQ,SAAS,WAAW,MAAM;AAAA,cAC9D,CAAC;AAAA,YACH,SAAS,WAAW;AAClB,sBAAQ,MAAM,yEAAyE,SAAS;AAAA,YAClG;AAAA,UACF;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,eAAe,gBAAgB;AACrC,gBAAM,aAAa,WAAW;AAE9B,cAAI;AAEJ,cAAI,IAAI,WAAW,SAAS;AAE1B,kBAAM,YAAY,yBAAyB,IAAI,WAAW,QAAQ,EAAG;AAGrE,gBAAI,kBAAkB;AACtB,gBAAI,IAAI,WAAW;AACjB,oBAAM,eAAe,MAAM,yBAAyB,IAAI,SAAS;AACjE,kBAAI,cAAc,QAAQ;AACxB,kCAAkB,aAAa;AAC/B,wBAAQ,IAAI,6BAA6B,IAAI,SAAS,iBAAiB,eAAe,EAAE;AAAA,cAC1F;AAAA,YACF;AAEA,kBAAM,cAAc,MAAM,aAAa,aAAa;AAAA,cAClD,WAAW,IAAI;AAAA,cACf,UAAU,IAAI;AAAA,cACd,WAAW,IAAI;AAAA,cACf,aAAa;AAAA,cACb,SAAS,IAAI;AAAA,cACb,aAAa,IAAI;AAAA,cACjB,cAAc,IAAI;AAAA,cAClB,GAAG;AAAA,YACL,CAAC;AAED,qBAAS;AAAA,cACP,SAAS,YAAY;AAAA,cACrB,QAAQ,YAAY,UAAU,YAAY;AAAA,cAC1C,WAAW,IAAI;AAAA,cACf,OAAO,YAAY;AAAA,YACrB;AAAA,UAEF,WAAW,IAAI,WAAW,WAAW;AAEnC,kBAAM,YAAY,yBAAyB,IAAI,WAAW,QAAQ,EAAG;AACrE,kBAAM,aAAa,MAAM,aAAa,YAAY;AAAA,cAChD,WAAW,IAAI;AAAA,cACf,SAAS,IAAI;AAAA,cACb,gBAAgB;AAAA,cAChB,iBAAiB,IAAI;AAAA,cACrB,GAAG;AAAA,YACL,CAAC;AAED,qBAAS;AAAA,cACP,SAAS,WAAW;AAAA,cACpB,QAAQ,WAAW,UAAU,YAAY;AAAA,cACzC,WAAW,IAAI;AAAA,cACf,OAAO,WAAW;AAAA,YACpB;AAAA,UAEF,WAAW,IAAI,WAAW,SAAS;AACjC,kBAAM,aAAa,aAAa,IAAI,SAAS;AAC7C,qBAAS,EAAE,SAAS,MAAM,QAAQ,WAAW,WAAW,IAAI,UAAU;AAAA,UAExE,WAAW,IAAI,WAAW,QAAQ;AAChC,kBAAM,aAAa,YAAY,IAAI,SAAS;AAC5C,qBAAS,EAAE,SAAS,MAAM,QAAQ,YAAY,WAAW,IAAI,UAAU;AAAA,UAEzE,OAAO;AACL,qBAAS;AAAA,cACP,SAAS;AAAA,cACT,QAAQ;AAAA,cACR,WAAY,IAAY,aAAa;AAAA,cACrC,OAAO,yBAA0B,IAAY,MAAM;AAAA,YACrD;AAAA,UACF;AAGA,gBAAM,OAAO,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB;AAAA,UACF,CAAC;AAED,kBAAQ,IAAI,iCAAiC,IAAI,MAAM,0BAA0B,IAAI,WAAW,WAAW,IAAI,WAAW,YAAY,IAAI,YAAa,IAAY,SAAS,EAAE;AAAA,QAChL,SAAS,OAAO;AAEd,cAAI;AACF,kBAAM,OAAO,KAAK;AAAA,cAChB,MAAM;AAAA,cACN,WAAW,QAAQ;AAAA,cACnB,QAAQ;AAAA,gBACN,SAAS;AAAA,gBACT,QAAQ;AAAA,gBACR,WAAY,IAAY,aAAa;AAAA,gBACrC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,cAC9D;AAAA,YACF,CAAC;AAAA,UACH,SAAS,WAAW;AAClB,oBAAQ,MAAM,gFAAgF,SAAS;AAAA,UACzG;AAEA,kBAAQ,MAAM,kDAAkD,KAAK;AAAA,QACvE;AAAA,MACF;AAAA,IACF,CAAC;AAID,WAAO,GAAG,YAAY,OAAO,YAAY;AACvC,YAAM,kBAAkB;AACxB,YAAM,SAAS,gBAAgB,UAAU;AAEzC,cAAQ,IAAI,sDAAsD,SAAS,EAAE;AAC7E,cAAQ,IAAI,oBAAoB,MAAM,MAAM,gBAAgB,WAAW,YAAY,EAAE;AAErF,UAAI,WAAW,kBAAkB;AAE/B,gBAAQ,IAAI,sDAAsD;AAClE,cAAM,KAAK,eAAe;AAAA,MAC5B,OAAO;AAEL,gBAAQ,IAAI,6BAA6B,MAAM,oCAAoC;AAAA,MAGrF;AAAA,IACF,CAAC;AAGD,WAAO,GAAG,gBAAgB,OAAO,YAAY;AAC3C,cAAQ,IAAI,sCAAsC,SAAS,EAAE;AAC7D,mBAAa,WAAW;AAGxB,WAAK,gBAAgB,IAAI,WAAW;AAEpC,WAAK,mBAAmB,OAAO,WAAW;AAS1C,YAAM,cAAc;AACpB,UAAI,YAAY,UAAU,YAAY,aAAa;AAEjD,cAAM,KAAK,iBAAiB,aAAa,YAAY,QAAQ,YAAY,aAAa,KAAK,WAAW,WAAW,YAAY,QAAQ;AAErI,cAAM,KAAK,gBAAgB,WAAW;AAAA,MACxC;AAGA,UAAI,YAAY,YAAY;AAC1B,aAAK,aAAa,YAAY;AAC9B,gBAAQ,IAAI,yBAAyB,KAAK,UAAU,EAAE;AAAA,MACxD;AAIA,UAAI,YAAY,UAAU;AACxB,aAAK,WAAW,YAAY;AAC5B,gBAAQ,IAAI,8BAA8B,KAAK,QAAQ,EAAE;AAGzD,cAAM,KAAK,cAAc,YAAY,QAAQ;AAAA,MAC/C;AAIA,UAAI,YAAY,cAAc;AAC5B,aAAK,eAAe,YAAY;AAChC,gBAAQ,IAAI,4BAA4B,KAAK,YAAY,EAAE;AAAA,MAC7D;AAIA,WAAK,oBAAoB,SAAS,EAAE,MAAM,SAAO;AAC/C,gBAAQ,KAAK,yCAAyC,IAAI,OAAO;AAAA,MACnE,CAAC;AAID,WAAK,uBAAuB,WAAW,WAAW,EAAE,MAAM,SAAO;AAC/D,gBAAQ,KAAK,6CAA6C,IAAI,OAAO;AAAA,MACvE,CAAC;AAID,WAAK,2BAA2B,aAAa,SAAS,EAAE,MAAM,WAAS;AACrE,gBAAQ,MAAM,iDAAiD,KAAK;AAAA,MACtE,CAAC;AAID,0BAAoB,WAAW,EAAE,KAAK,mBAAiB;AACrD,YAAI,cAAc,gBAAgB,GAAG;AACnC,kBAAQ,IAAI,8BAA8B,cAAc,aAAa,6BAA6B;AAAA,QACpG;AAAA,MACF,CAAC,EAAE,MAAM,SAAO;AAEd,gBAAQ,KAAK,8CAA8C,IAAI,OAAO;AAAA,MACxE,CAAC;AAID,WAAK,mBAAmB,WAAW,WAAW,EAAE,MAAM,SAAO;AAC3D,gBAAQ,KAAK,0CAA0C,IAAI,OAAO;AAAA,MACpE,CAAC;AAAA,IACH,CAAC;AAID,WAAO,GAAG,wBAAwB,OAAO,YAAY;AACnD,UAAI,QAAQ,SAAS,wBAAwB;AAC3C,cAAM,EAAE,WAAW,OAAO,eAAe,YAAY,SAAS,kBAAkB,IAAI;AAEpF,gBAAQ,IAAI,0BAA0B,SAAS,mBAAmB,aAAa,WAAM,KAAK,EAAE;AAG5F,YAAI,YAAY,SAAS;AACvB,kBAAQ,IAAI,8CAA8C,SAAS,WAAW,WAAW,SAAS,GAAG;AACrG;AAAA,QACF;AAIA,YAAI,qBAAqB,sBAAsB,KAAK,UAAU;AAC5D,kBAAQ,IAAI,4BAA4B,SAAS,uCAAuC,iBAAiB,GAAG;AAC5G;AAAA,QACF;AAEA,cAAM,gBAAgB,iBAAiB;AACvC,cAAM,cAAc,WAAW;AAG/B,cAAM,KAAK,eAAe,WAAW,YAAY;AAG/C,gBAAM,iBAAiB,UAAU,WAAW,UAAU,WAAW,UAAU;AAC3E,gBAAM,kBAAkB,kBAAkB,WAAW,kBAAkB,WAAW,kBAAkB;AAEpG,gBAAM,eAAe,kBAAkB,WAAW,UAAU;AAE5D,gBAAM,mBAAmB,CAAC,cAAc,UAAU,SAAS;AAC3D,gBAAM,qBAAqB,kBAAkB;AAE7C,cAAI,gBAAgB,oBAAoB;AAEtC,gBAAI,cAAc,UAAU,SAAS,GAAG;AACtC,sBAAQ,IAAI,8CAA8C,SAAS,kBAAkB;AACrF;AAAA,YACF;AAEA,oBAAQ,IAAI,uCAAuC,SAAS,KAAK,aAAa,WAAM,KAAK,GAAG;AAE5F,gBAAI;AAEF,kBAAI,WAAW,MAAM,yBAAyB,SAAS;AACvD,kBAAI,CAAC,UAAU;AACb,wBAAQ,MAAM,oDAAoD,SAAS,yBAAyB;AACpG;AAAA,cACF;AAGA,kBAAI,CAAC,SAAS,UAAU,cAAc;AACpC,wBAAQ,IAAI,yCAAyC,SAAS,OAAO,SAAS,IAAI,EAAE;AAEpF,sBAAM,cAAc,MAAM,mBAAmB;AAC7C,oBAAI,CAAC,aAAa;AAChB,0BAAQ,MAAM,qEAAqE;AACnF;AAAA,gBACF;AAEA,sBAAM,kBAAkB,IAAI,gBAAgB,WAAW;AACvD,sBAAM,cAAc,MAAM,gBAAgB,WAAW;AACrD,oBAAI,CAAC,aAAa;AAChB,0BAAQ,MAAM,2DAA2D,WAAW,EAAE;AACtF;AAAA,gBACF;AAIA,sBAAM,mBAAmB,cAAc;AACvC,sBAAM,eAAe,MAAM,gBAAgB,eAAe,WAAW,kBAAkB,IAAI;AAE3F,oBAAI,CAAC,aAAa,SAAS;AACzB,0BAAQ,MAAM,iDAAiD,SAAS,KAAK,aAAa,KAAK,EAAE;AACjG;AAAA,gBACF;AAEA,wBAAQ,IAAI,wCAAwC,SAAS,OAAO,aAAa,YAAY,EAAE;AAI/F,oBAAI,KAAK,UAAU;AACjB,sBAAI;AACF,0BAAM,kBAAkB,UAAM,0BAAW;AACzC,0BAAM,kBAAkB,iBAAiB,WAAW;AACpD,0BAAM,oBAAoB,MAAM,cAAc,GAAG,eAAe,gBAAgB,SAAS,IAAI;AAAA,sBAC3F,QAAQ;AAAA,sBACR,MAAM,KAAK,UAAU,EAAE,qBAAqB,KAAK,SAAS,CAAC;AAAA,oBAC7D,CAAC;AACD,wBAAI,kBAAkB,IAAI;AACxB,8BAAQ,IAAI,wCAAwC,SAAS,eAAe,KAAK,QAAQ,EAAE;AAAA,oBAC7F,OAAO;AACL,8BAAQ,KAAK,gDAAgD,SAAS,KAAK,kBAAkB,MAAM,EAAE;AAAA,oBACvG;AAAA,kBACF,SAAS,gBAAgB;AAEvB,4BAAQ,KAAK,+CAA+C,SAAS,KAAK,cAAc;AAAA,kBAC1F;AAAA,gBACF;AAGA,2BAAW,MAAM,yBAAyB,SAAS;AACnD,oBAAI,CAAC,YAAY,CAAC,SAAS,QAAQ;AACjC,0BAAQ,MAAM,+DAA+D,SAAS,EAAE;AACxF;AAAA,gBACF;AAKA,sBAAM,iBAAiB,UAAM,0BAAW;AACxC,sBAAM,cAAc,gBAAgB;AAGpC,sBAAM,UAAU,MAAMF,cAAa;AACnC,sBAAM,aAAa,OAAO,KAAK,OAAO,EAAE,SAAS;AACjD,sBAAM,iBAAiB,aAAa,qBAAqB,UAAU,aAAa,yBAAyB;AAGzG;AACE,0BAAQ,IAAI,qDAAqD,SAAS,GAAG,iBAAiB,mBAAmB,gCAAgC,EAAE;AAGnJ,wBAAM,gBAAgB,qBAAqB,WAAW,SAAS;AAE/D,wBAAM,KAAK,2BAA2B,WAAW,WAAW,SAAS,IAAI;AAIzE,uBAAK;AAAA,oBACH;AAAA,oBACA;AAAA,oBACA,aAAa,uBAAuB,CAAC;AAAA,oBACrC,aAAa;AAAA,oBACb,SAAS;AAAA,oBACT;AAAA;AAAA,kBACF,EAAE,KAAK,MAAM;AACX,4BAAQ,IAAI,sCAAsC,SAAS,mBAAmB;AAE9E,yBAAK,qBAAqB,WAAW,SAAU,IAAI;AAAA,kBACrD,CAAC,EAAE,MAAM,SAAO;AACd,4BAAQ,MAAM,oCAAoC,SAAS,KAAK,GAAG;AAAA,kBACrE,CAAC;AAGD;AAAA,gBACF;AAAA,cACF;AAEA,kBAAI,CAAC,SAAS,QAAQ;AAEpB,wBAAQ,IAAI,mCAAmC,SAAS,OAAO,SAAS,IAAI,mBAAmB;AAC/F;AAAA,cACF;AAKA,oBAAM,KAAK,2BAA2B,WAAW,SAAS,SAAS,IAAI;AAGvE,oBAAM,OAAO,aAAa,SAAS;AACnC,sBAAQ,IAAI,kCAAkC,SAAS,IAAI,YAAY,IAAI,EAAE;AAG7E,oBAAM,YAAY,UAAM,0BAAW;AACnC,oBAAM,kBAAkB,WAAW,kBAAkB;AAGrD,oBAAM,kBAAkB,MAAM,gBAAgB,SAAS,MAAM,MAAM,WAAW,eAAe;AAC7F,kBAAI,CAAC,gBAAgB,SAAS;AAC5B,wBAAQ,MAAM,yCAAyC,SAAS,KAAK,gBAAgB,KAAK,EAAE;AAC5F,4BAAY,SAAS;AACrB;AAAA,cACF;AAIA,oBAAME,UAAS;AACf,oBAAM,SAASA,SAAQ,WAAW;AAClC,oBAAM,cAAc,MAAM,cAAc,YAAY;AAAA,gBAClD;AAAA,gBACA;AAAA,gBACA,OAAO,OAAO,QAAQ;AACpB,0BAAQ,IAAI,kCAAkC,SAAS,KAAK,GAAG,EAAE;AAEjE,sBAAI;AACF,0BAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,sBAC/D,QAAQ;AAAA,sBACR,MAAM,KAAK,UAAU,EAAE,YAAY,IAAI,CAAC;AAAA,oBAC1C,CAAC;AAAA,kBACH,SAAS,KAAK;AACZ,4BAAQ,KAAK,gDAAgD,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,kBACvG;AAAA,gBACF;AAAA,gBACA,gBAAgB,CAAC,QAAQ,UAAU;AACjC,sBAAI,WAAW,SAAS;AACtB,4BAAQ,MAAM,oCAAoC,SAAS,KAAK,KAAK,EAAE;AAAA,kBACzE;AAAA,gBACF;AAAA,cACF,CAAC;AAED,kBAAI,YAAY,SAAS;AACvB,wBAAQ,IAAI,sCAAsC,SAAS,EAAE;AAAA,cAC/D,OAAO;AACL,wBAAQ,MAAM,qCAAqC,SAAS,KAAK,YAAY,KAAK,EAAE;AACpF,4BAAY,SAAS;AAAA,cACvB;AAAA,YACF,SAAS,OAAO;AACd,sBAAQ,MAAM,6CAA6C,SAAS,KAAK,KAAK;AAC9E,0BAAY,SAAS;AAAA,YACvB;AAAA,UACF;AAGA,cAAI,UAAU,UAAU,iBAAiB;AACvC,oBAAQ,IAAI,uCAAuC,SAAS,KAAK,aAAa,eAAU;AAExF,gBAAI;AACF,oBAAM,cAAc,WAAW,SAAS;AAExC,0BAAY,SAAS;AACrB,sBAAQ,IAAI,wDAAwD,SAAS,EAAE;AAG/E,oBAAMA,UAAS,UAAM,0BAAW;AAChC,oBAAM,SAASA,SAAQ,WAAW;AAClC,kBAAI;AACF,sBAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,kBAC/D,QAAQ;AAAA,kBACR,MAAM,KAAK,UAAU,EAAE,YAAY,KAAK,CAAC;AAAA,gBAC3C,CAAC;AAAA,cACH,SAAS,KAAK;AACZ,wBAAQ,KAAK,+CAA+C,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,cACtG;AAIA,mBAAK,sBAAsB,SAAS,EAAE,MAAM,SAAO;AACjD,wBAAQ,KAAK,4CAA4C,SAAS,KAAK,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,cACjH,CAAC;AAAA,YACH,SAAS,OAAO;AACd,sBAAQ,MAAM,6CAA6C,SAAS,KAAK,KAAK;AAE9E,0BAAY,SAAS;AAAA,YACvB;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,WAAO,GAAG,SAAS,CAAC,YAAY;AAC9B,cAAQ,MAAM,6BAA6B,SAAS,KAAK,OAAO;AAAA,IAClE,CAAC;AAKD,WAAO,GAAG,gBAAgB,CAAC,UAAU;AACnC,YAAM,kBAAkB;AACxB,cAAQ,IAAI,kCAAkC,SAAS,UAAU,gBAAgB,IAAI,mBAAmB,gBAAgB,aAAa,EAAE;AAGvI,WAAK,gBAAgB,OAAO,WAAW;AAKvC,UAAI,CAAC,gBAAgB,eAAe;AAClC,aAAK,YAAY,OAAO,WAAW;AACnC,gBAAQ,IAAI,mCAAmC,WAAW,WAAW;AAAA,MACvE;AAAA,IACF,CAAC;AAED,QAAI;AAEF,UAAI;AACJ,UAAI;AACF,cAAM,UAAU,eAAe;AAC/B,YAAO,gBAAW,OAAO,GAAG;AAC1B,gBAAM,SAAY,kBAAa,SAAS,OAAO,EAAE,KAAK;AACtD,sBAAY,SAAS,QAAQ,EAAE;AAAA,QACjC;AAAA,MACF,SAAS,UAAU;AACjB,gBAAQ,KAAK,uCAAuC,oBAAoB,QAAQ,SAAS,UAAU,QAAQ;AAAA,MAC7G;AAKA,YAAM,qBAAqB,IAAI,QAAc,CAACD,UAAS,WAAW;AAChE,cAAM,eAAe;AACrB,cAAM,UAAU,WAAW,MAAM;AAC/B,iBAAO,IAAI,MAAM,gGAAgG,CAAC;AAAA,QACpH,GAAG,YAAY;AAGf,cAAM,cAAc,MAAM;AACxB,uBAAa,OAAO;AACpB,UAAAA,SAAQ;AAAA,QACV;AACA,eAAO,KAAK,gBAAgB,WAAW;AAGvC,cAAM,eAAe,CAAC,YAAqB;AACzC,uBAAa,OAAO;AACpB,gBAAM,WAAW;AACjB,iBAAO,IAAI,MAAM,SAAS,WAAW,uBAAuB,CAAC;AAAA,QAC/D;AACA,eAAO,KAAK,cAAc,YAAY;AAAA,MACxC,CAAC;AAGD,YAAM,OAAO,QAAQ,OAAO,OAAO,cAAc,KAAK,WAAW;AAAA,QAC/D,UAAa,aAAS;AAAA,QACtB,YAAe,aAAS;AAAA,QACxB,QAAW,SAAK;AAAA,QAChB;AAAA,MACF,CAAC;AACD,cAAQ,IAAI,8CAA8C,SAAS,EAAE;AAIrE,YAAM;AACN,cAAQ,IAAI,gDAAgD,SAAS,EAAE;AAAA,IACzE,SAAS,OAAO;AACd,cAAQ,MAAM,iCAAiC,SAAS,KAAK,KAAK;AAClE,WAAK,YAAY,OAAO,WAAW;AAEnC,WAAK,mBAAmB,OAAO,WAAW;AAC1C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB,aAAoC;AAClE,UAAM,aAAa,KAAK,YAAY,IAAI,WAAW;AACnD,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AAGA,QAAI,WAAW,gBAAgB;AAC7B,mBAAa,WAAW,cAAc;AAAA,IACxC;AAGA,UAAM,WAAW,OAAO,WAAW;AACnC,SAAK,YAAY,OAAO,WAAW;AACnC,SAAK,gBAAgB,OAAO,WAAW;AACvC,SAAK,mBAAmB,OAAO,WAAW;AAE1C,YAAQ,IAAI,8BAA8B,WAAW,EAAE;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,wBAA8B;AACpC,UAAM,kBAAkB,OAAO,WAAmB;AAChD,cAAQ,IAAI,qBAAqB,MAAM,oBAAoB;AAC3D,YAAM,KAAK,eAAe;AAAA,IAC5B;AAEA,YAAQ,GAAG,WAAW,MAAM,gBAAgB,SAAS,CAAC;AACtD,YAAQ,GAAG,UAAU,MAAM,gBAAgB,QAAQ,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,iBAAiB,aAAqB,QAAgB,aAAqB,WAAmB,WAAmB,UAAyC;AACtK,QAAI;AACF,YAAM,EAAE,UAAAG,UAAS,IAAI,MAAM,OAAO,eAAe;AAGjD,MAAAA,UAAS,6BAA6B,MAAM,IAAI;AAAA,QAC9C,KAAK;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAED,MAAAA,UAAS,kCAAkC,WAAW,IAAI;AAAA,QACxD,KAAK;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAGD,MAAAA,UAAS,gCAAgC,SAAS,IAAI;AAAA,QACpD,KAAK;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAID,MAAAA,UAAS,gCAAgC,SAAS,IAAI;AAAA,QACpD,KAAK;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAID,UAAI,UAAU;AACZ,QAAAA,UAAS,+BAA+B,QAAQ,IAAI;AAAA,UAClD,KAAK;AAAA,UACL,UAAU;AAAA,UACV,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAEA,cAAQ,IAAI,uDAAuD,MAAM,eAAe,SAAS,eAAe,SAAS,GAAG,WAAW,cAAc,QAAQ,KAAK,EAAE,EAAE;AAAA,IACxK,SAAS,OAAO;AAEd,cAAQ,KAAK,6CAA6C,WAAW,KAAK,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IAC1H;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,gBAAgB,aAAoC;AAEhE,UAAM,QAAQ,CAAC,iBAAiB,cAAc,aAAa;AAC3D,UAAM,WAAgB,YAAK,aAAa,QAAQ,OAAO;AAGvD,QAAI,CAAI,gBAAW,QAAQ,GAAG;AAC5B,cAAQ,KAAK,uCAAuC,QAAQ,EAAE;AAC9D;AAAA,IACF;AAEA,eAAW,YAAY,OAAO;AAC5B,UAAI;AACF,cAAM,WAAgB,YAAK,UAAU,QAAQ;AAI7C,cAAM,kBAAuB,YAAK,WAAW,MAAM,SAAS,QAAQ;AAEpE,YAAI,CAAI,gBAAW,eAAe,GAAG;AACnC,kBAAQ,KAAK,oCAAoC,eAAe,EAAE;AAClE;AAAA,QACF;AAEA,cAAM,cAAiB,kBAAa,iBAAiB,OAAO;AAG5D,YAAO,gBAAW,QAAQ,GAAG;AAC3B,gBAAM,kBAAqB,kBAAa,UAAU,OAAO;AACzD,cAAI,oBAAoB,aAAa;AAEnC;AAAA,UACF;AAAA,QACF;AAGA,QAAG,mBAAc,UAAU,aAAa,EAAE,MAAM,IAAM,CAAC;AACvD,gBAAQ,IAAI,gCAAgC,QAAQ,EAAE;AAAA,MACxD,SAAS,OAAO;AAEd,gBAAQ,KAAK,8BAA8B,QAAQ,UAAU,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,MAC7G;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,cAAc,UAAiC;AAC3D,QAAI;AACF,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,QAAQ;AACX,gBAAQ,KAAK,mDAAmD;AAChE;AAAA,MACF;AAGA,UAAI,OAAO,cAAc,UAAU;AACjC;AAAA,MACF;AAGA,YAAM,gBAA+B;AAAA,QACnC,GAAG;AAAA,QACH,WAAW;AAAA,QACX,YAAY,KAAK;AAAA,MACnB;AAEA,gBAAM,0BAAW,aAAa;AAC9B,cAAQ,IAAI,wCAAwC,QAAQ,EAAE;AAAA,IAChE,SAAS,OAAO;AACd,cAAQ,KAAK,uCAAuC,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IACpG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,oBAAoB,WAAkC;AAClE,QAAI;AACF,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,OAAQ;AAEb,YAAM,SAAS,OAAO,WAAW;AACjC,YAAM,WAAW,MAAM,cAAc,GAAG,MAAM,iBAAiB,SAAS,WAAW;AAEnF,UAAI,CAAC,SAAS,IAAI;AAChB,gBAAQ,KAAK,4CAA4C,SAAS,MAAM,EAAE;AAC1E;AAAA,MACF;AAGA,YAAM,OAAO,MAAM,SAAS,KAAK;AAKjC,YAAM,iBAAiB,KAAK;AAE5B,UAAI,gBAAgB;AAElB,cAAM,cAAc,KAAK,gBAAgB,OAAO;AAChD,cAAM,gBAAgB,KAAK,kBAAkB,OAAO;AAEpD,YAAI,KAAK,gBAAgB,CAAC,OAAO,cAAc;AAC7C,kBAAQ,IAAI,wCAAwC,KAAK,YAAY,EAAE;AAAA,QACzE;AACA,YAAI,KAAK,kBAAkB,CAAC,OAAO,gBAAgB;AACjD,kBAAQ,IAAI,0CAA0C,KAAK,cAAc,EAAE;AAAA,QAC7E;AAGA,cAAM,gBAA+B;AAAA,UACnC,GAAG;AAAA;AAAA,UAEH,cAAc;AAAA,UACd,gBAAgB;AAAA,UAChB,kBAAkB;AAAA,YAChB,GAAG,OAAO;AAAA,YACV,uBAAuB,eAAe;AAAA,YACtC,yBAAyB,eAAe;AAAA,YACxC,4BAA4B,eAAe;AAAA;AAAA,YAE3C,qBAAqB,eAAe;AAAA,YACpC,WAAW,KAAK,IAAI;AAAA,UACtB;AAAA,QACF;AAEA,kBAAM,0BAAW,aAAa;AAC9B,gBAAQ,IAAI,mDAAmD,WAAW,IAAI,aAAa,GAAG;AAAA,MAChG;AAAA,IACF,SAAS,OAAO;AAEd,cAAQ,KAAK,oDAAoD,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IACjH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,uBAAuB,WAAmB,aAAoC;AAC1F,QAAI;AACF,UAAI,CAAC,KAAK,UAAU;AAClB,gBAAQ,KAAK,mEAAmE;AAChF;AAAA,MACF;AAEA,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,OAAQ;AAEb,YAAM,SAAS,OAAO,WAAW;AAEjC,YAAM,WAAW,MAAM,cAAc,GAAG,MAAM,yBAAyB,KAAK,QAAQ,IAAI;AAAA,QACtF,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,YAAY;AAAA,UACZ,cAAc;AAAA,QAChB,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,gBAAQ,KAAK,gDAAgD,SAAS,MAAM,IAAI,SAAS;AACzF;AAAA,MACF;AAEA,cAAQ,IAAI,kDAAkD,WAAW,EAAE;AAAA,IAC7E,SAAS,OAAO;AAEd,cAAQ,KAAK,gDAAgD,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IAC7G;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,2BACZ,WACA,QACA,cACA,cACe;AACf,QAAI;AACF,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,OAAQ;AAEb,YAAM,SAAS,OAAO,WAAW;AAEjC,YAAM,OAAgC;AAAA,QACpC,iBAAiB;AAAA,MACnB;AAEA,UAAI,cAAc;AAChB,aAAK,gBAAgB;AAAA,MACvB;AAEA,UAAI,WAAW,WAAW,cAAc;AACtC,aAAK,iBAAiB;AAAA,MACxB;AAGA,UAAI,WAAW,SAAS;AACtB,aAAK,iBAAiB;AAAA,MACxB;AAEA,YAAM,WAAW,MAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,IAAI;AAAA,QACzE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,gBAAQ,KAAK,qDAAqD,SAAS,MAAM,IAAI,SAAS;AAC9F;AAAA,MACF;AAEA,cAAQ,IAAI,kCAAkC,SAAS,oBAAoB,MAAM,EAAE;AAAA,IACrF,SAAS,OAAO;AAEd,cAAQ,KAAK,qDAAqD,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IAClH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,mBAAmB,WAAmB,aAAoC;AACtF,YAAQ,IAAI,gEAAgE,SAAS,EAAE;AAEvF,QAAI;AACF,UAAI,CAAC,KAAK,UAAU;AAClB,gBAAQ,IAAI,+DAA+D;AAC3E;AAAA,MACF;AAEA,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,OAAQ;AAEb,YAAM,SAAS,OAAO,WAAW;AAIjC,YAAM,kBAAkB,MAAM;AAAA,QAC5B,GAAG,MAAM,sEAAsE,KAAK,QAAQ,eAAe,SAAS;AAAA,MACtH;AAEA,UAAI,CAAC,gBAAgB,IAAI;AACvB,gBAAQ,KAAK,+DAA+D,gBAAgB,MAAM,EAAE;AACpG;AAAA,MACF;AAEA,YAAM,cAAc,MAAM,gBAAgB,KAAK;AAC/C,YAAM,UAAU,YAAY,WAAW,CAAC;AAExC,UAAI,QAAQ,WAAW,GAAG;AACxB,gBAAQ,IAAI,gDAAgD;AAC5D;AAAA,MACF;AAEA,cAAQ,IAAI,yBAAyB,QAAQ,MAAM,qBAAqB;AAGxE,YAAM,kBAAkB,IAAI,gBAAgB,WAAW;AACvD,YAAM,cAAc,MAAM,gBAAgB,WAAW;AACrD,UAAI,CAAC,aAAa;AAChB,gBAAQ,MAAM,sDAAsD;AACpE;AAAA,MACF;AAGA,iBAAWC,WAAU,SAAS;AAC5B,cAAM,YAAYA,QAAO;AACzB,cAAM,aAAaA,QAAO;AAG1B,cAAM,WAAW,MAAM,yBAAyB,SAAS;AAEzD,YAAI,CAAC,UAAU,QAAQ;AACrB,kBAAQ,IAAI,0BAA0B,SAAS,uCAAuC;AAGtF,gBAAM,mBAAmB,cAAc;AACvC,gBAAM,eAAe,MAAM,gBAAgB,eAAe,WAAW,kBAAkB,IAAI;AAE3F,cAAI,CAAC,aAAa,SAAS;AACzB,oBAAQ,MAAM,iDAAiD,SAAS,KAAK,aAAa,KAAK,EAAE;AACjG;AAAA,UACF;AAEA,kBAAQ,IAAI,wCAAwC,SAAS,OAAO,aAAa,YAAY,EAAE;AAG/F,cAAI,KAAK,UAAU;AACjB,gBAAI;AACF,oBAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,IAAI;AAAA,gBACxD,QAAQ;AAAA,gBACR,MAAM,KAAK,UAAU,EAAE,qBAAqB,KAAK,SAAS,CAAC;AAAA,cAC7D,CAAC;AACD,sBAAQ,IAAI,wCAAwC,SAAS,EAAE;AAAA,YACjE,SAAS,gBAAgB;AACvB,sBAAQ,KAAK,gDAAgD,SAAS,EAAE;AAAA,YAC1E;AAAA,UACF;AAGA,gBAAM,cAAc,MAAM,yBAAyB,SAAS;AAC5D,cAAI,CAAC,aAAa,QAAQ;AACxB,oBAAQ,MAAM,yDAAyD;AACvE;AAAA,UACF;AAGA,gBAAM,cAAc,OAAO;AAC3B,gBAAM,UAAU,MAAML,cAAa;AAEnC,kBAAQ,IAAI,wDAAwD,SAAS,EAAE;AAG/E,gBAAM,gBAAgB,qBAAqB,WAAW,SAAS;AAC/D,gBAAM,KAAK,2BAA2B,WAAW,WAAW,YAAY,IAAI;AAG5E,eAAK;AAAA,YACH;AAAA,YACA;AAAA,YACA,aAAa,uBAAuB,CAAC;AAAA,YACrC,aAAa;AAAA,YACb,YAAY;AAAA,YACZ;AAAA,UACF,EAAE,KAAK,MAAM;AACX,oBAAQ,IAAI,iDAAiD,SAAS,EAAE;AACxE,iBAAK,qBAAqB,WAAW,YAAY,IAAI;AAAA,UACvD,CAAC,EAAE,MAAM,SAAO;AACd,oBAAQ,MAAM,+CAA+C,SAAS,KAAK,GAAG;AAAA,UAChF,CAAC;AAAA,QAEH,OAAO;AAIL,gBAAM,KAAK,2BAA2B,WAAW,SAAS,SAAS,IAAI;AAEvE,gBAAM,gBAAgB,iBAAiB;AACvC,gBAAM,cAAc,WAAW;AAE/B,cAAI,CAAC,cAAc,UAAU,SAAS,GAAG;AACvC,oBAAQ,IAAI,0BAA0B,SAAS,2CAA2C;AAC1F,kBAAM,KAAK,qBAAqB,WAAW,SAAS,IAAI;AAAA,UAC1D,OAAO;AACL,oBAAQ,IAAI,0BAA0B,SAAS,iCAAiC;AAAA,UAClF;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,IAAI,yCAAyC;AAAA,IACvD,SAAS,OAAO;AACd,cAAQ,MAAM,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACrG,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAc,sBAAsB,WAAkC;AACpE,YAAQ,IAAI,8CAA8C,SAAS,EAAE;AAErE,QAAI;AAEF,YAAM,cAAc,SAAS;AAC7B,cAAQ,IAAI,0CAA0C,SAAS,EAAE;AAGjE,YAAM,WAAW,MAAM,yBAAyB,SAAS;AACzD,UAAI,UAAU,UAAU,SAAS,MAAM;AACrC,gBAAQ,IAAI,yCAAyC,SAAS,OAAO,SAAS,IAAI,EAAE;AAGpF,cAAM,cAAc,MAAM,gBAAgB,SAAS,IAAI;AACvD,YAAI,aAAa;AACf,gBAAM,UAAU,IAAI,gBAAgB,WAAW;AAC/C,cAAI,MAAM,QAAQ,WAAW,GAAG;AAE9B,kBAAM,SAAS,MAAM,QAAQ,eAAe,WAAW,IAAI;AAC3D,gBAAI,OAAO,SAAS;AAClB,sBAAQ,IAAI,qDAAqD,SAAS,EAAE;AAAA,YAC9E,OAAO;AAEL,sBAAQ,KAAK,iDAAiD,SAAS,KAAK,OAAO,KAAK,EAAE;AAAA,YAC5F;AAAA,UACF,OAAO;AACL,oBAAQ,KAAK,4DAA4D,SAAS,EAAE;AAAA,UACtF;AAAA,QACF,OAAO;AACL,kBAAQ,KAAK,mDAAmD,SAAS,WAAW;AAAA,QACtF;AAAA,MACF,OAAO;AACL,gBAAQ,IAAI,6CAA6C,SAAS,EAAE;AAAA,MACtE;AAIA,UAAI;AACF,cAAM,gBAAgB,UAAM,0BAAW;AACvC,cAAM,gBAAgB,eAAe,WAAW;AAChD,cAAM,cAAc,GAAG,aAAa,gBAAgB,SAAS,IAAI;AAAA,UAC/D,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,iBAAiB;AAAA,YACjB,eAAe;AAAA,YACf,gBAAgB;AAAA,UAClB,CAAC;AAAA,QACH,CAAC;AACD,gBAAQ,IAAI,+CAA+C,SAAS,EAAE;AAAA,MACxE,SAAS,YAAY;AAEnB,gBAAQ,KAAK,uDAAuD,SAAS,KAAK,UAAU;AAAA,MAC9F;AAEA,cAAQ,IAAI,8CAA8C,SAAS,EAAE;AAAA,IACvE,SAAS,OAAO;AACd,cAAQ,MAAM,qCAAqC,SAAS,KAAK,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAC/G,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,sBACZ,WACA,iBACA,WACA,aACA,cACA,UAAkC,CAAC,GACpB;AACf,YAAQ,IAAI,oDAAoD,SAAS,EAAE;AAE3E,QAAI;AAEF,YAAM,gBAAgB,qBAAqB,WAAW,SAAS;AAE/D,YAAM,KAAK,2BAA2B,WAAW,SAAS,YAAY;AAGtE,UAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,gBAAQ,IAAI,qCAAqC,OAAO,KAAK,OAAO,EAAE,MAAM,iBAAiB,SAAS,EAAE;AACxG,qBAAa,cAAc,OAAO;AAAA,MACpC;AAGA,UAAI,UAAU,SAAS,GAAG;AACxB,gBAAQ,IAAI,wCAAwC,UAAU,MAAM,aAAa,SAAS,EAAE;AAC5F,gBAAQ,KAAK,4GAA4G;AACzH,cAAM,aAAa,MAAM,gBAAgB,kBAAkB,WAAW,SAAS;AAC/E,YAAI,CAAC,WAAW,SAAS;AAEvB,kBAAQ,KAAK,iDAAiD,WAAW,KAAK,EAAE;AAAA,QAClF;AAAA,MACF;AAGA,YAAM,aAAa,kBAAkB,YAAY;AACjD,UAAI,YAAY;AACd,gBAAQ,IAAI,mBAAmB,WAAW,WAAW,mBAAmB,WAAW,YAAY,GAAG;AAClG,gBAAQ,IAAI,4BAA4B,WAAW,QAAQ,KAAK,GAAG,CAAC,EAAE;AAEtE,YAAI;AACF,gBAAM,EAAE,UAAAI,UAAS,IAAI,MAAM,OAAO,eAAe;AACjD,UAAAA,UAAS,WAAW,QAAQ,KAAK,GAAG,GAAG;AAAA,YACrC,KAAK;AAAA,YACL,OAAO;AAAA,YACP,SAAS,KAAK,KAAK;AAAA;AAAA,YACnB,KAAK,EAAE,GAAG,QAAQ,KAAK,IAAI,OAAO;AAAA;AAAA,UACpC,CAAC;AACD,kBAAQ,IAAI,2DAA2D,SAAS,EAAE;AAAA,QACpF,SAAS,cAAc;AAErB,gBAAM,WAAW,wBAAwB,QAAQ,aAAa,UAAU,OAAO,YAAY;AAC3F,kBAAQ,KAAK,+DAA+D,QAAQ,EAAE;AACtF,kBAAQ,KAAK,wCAAwC,WAAW,QAAQ,KAAK,GAAG,CAAC,YAAY;AAAA,QAC/F;AAAA,MACF,OAAO;AACL,gBAAQ,IAAI,mDAAmD,SAAS,oCAAoC;AAAA,MAC9G;AAGA,UAAI,aAAa;AACf,gBAAQ,IAAI,4CAA4C,SAAS,EAAE;AACnE,cAAM,eAAe,MAAM,gBAAgB,eAAe,WAAW,WAAW;AAChF,YAAI,CAAC,aAAa,SAAS;AACzB,gBAAM,IAAI,MAAM,wBAAwB,aAAa,KAAK,EAAE;AAAA,QAC9D;AAAA,MACF;AAGA,YAAM,gBAAgB,qBAAqB,WAAW,OAAO;AAE7D,YAAM,KAAK,2BAA2B,WAAW,SAAS,YAAY;AACtE,cAAQ,IAAI,+CAA+C,SAAS,EAAE;AAAA,IAExE,SAAS,OAAO;AACd,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,cAAQ,MAAM,6CAA6C,SAAS,KAAK,YAAY;AACrF,YAAM,gBAAgB,qBAAqB,WAAW,SAAS,YAAY;AAE3E,YAAM,KAAK,2BAA2B,WAAW,SAAS,cAAc,YAAY;AACpF,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBAAqB,WAAmB,cAAqC;AACzF,UAAM,gBAAgB,iBAAiB;AACvC,UAAM,cAAc,WAAW;AAG/B,QAAI,cAAc,UAAU,SAAS,GAAG;AACtC,cAAQ,IAAI,8CAA8C,SAAS,EAAE;AACrE;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,UAAM,0BAAW;AAChC,YAAM,SAAS,QAAQ,WAAW;AAElC,YAAM,kBAAkB,QAAQ,kBAAkB;AAGlD,YAAM,OAAO,aAAa,SAAS;AACnC,cAAQ,IAAI,+CAA+C,SAAS,YAAY,IAAI,EAAE;AAGtF,YAAM,kBAAkB,MAAM,gBAAgB,cAAc,MAAM,WAAW,eAAe;AAC5F,UAAI,CAAC,gBAAgB,SAAS;AAC5B,gBAAQ,MAAM,yCAAyC,SAAS,KAAK,gBAAgB,KAAK,EAAE;AAC5F,oBAAY,SAAS;AACrB;AAAA,MACF;AAGA,YAAM,cAAc,MAAM,cAAc,YAAY;AAAA,QAClD;AAAA,QACA;AAAA,QACA,OAAO,OAAO,QAAQ;AACpB,kBAAQ,IAAI,kCAAkC,SAAS,KAAK,GAAG,EAAE;AACjE,cAAI;AACF,kBAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,cAC/D,QAAQ;AAAA,cACR,MAAM,KAAK,UAAU,EAAE,YAAY,IAAI,CAAC;AAAA,YAC1C,CAAC;AAAA,UACH,SAAS,KAAK;AACZ,oBAAQ,KAAK,gDAAgD,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,UACvG;AAAA,QACF;AAAA,QACA,gBAAgB,CAAC,QAAQ,UAAU;AACjC,cAAI,WAAW,SAAS;AACtB,oBAAQ,MAAM,oCAAoC,SAAS,KAAK,KAAK,EAAE;AAAA,UACzE;AAAA,QACF;AAAA,MACF,CAAC;AAED,UAAI,YAAY,SAAS;AACvB,gBAAQ,IAAI,sCAAsC,SAAS,EAAE;AAAA,MAC/D,OAAO;AACL,gBAAQ,MAAM,qCAAqC,SAAS,KAAK,YAAY,KAAK,EAAE;AACpF,oBAAY,SAAS;AAAA,MACvB;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,6CAA6C,SAAS,KAAK,KAAK;AAC9E,kBAAY,SAAS;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,2BACZ,aACA,YACe;AACf,YAAQ,IAAI,4EAA4E;AAExF,QAAI;AACF,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,QAAQ,cAAc;AACzB,gBAAQ,KAAK,6DAA6D;AAC1E;AAAA,MACF;AAEA,YAAM,SAAS,OAAO,WAAW;AAIjC,YAAM,WAAW,MAAM;AAAA,QACrB,GAAG,MAAM;AAAA,MACX;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,gBAAQ,KAAK,4CAA4C,SAAS,MAAM,EAAE;AAC1E;AAAA,MACF;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,YAAM,UAAU,KAAK,WAAW,CAAC;AAGjC,YAAM,gBAAgB,iBAAiB;AACvC,YAAM,cAAc,WAAW;AAI/B,YAAM,mBAAmB,cAAc,oBAAoB;AAC3D,YAAM,kBAAkB,IAAI;AAAA,QAC1B,QACG;AAAA,UAAO,CAAC,MACP,EAAE,aAAa,YACd,CAAC,EAAE,uBAAuB,EAAE,wBAAwB,KAAK;AAAA,QAC5D,EACC,IAAI,CAAC,MAAW,EAAE,GAAG;AAAA,MAC1B;AAEA,YAAM,kBAAkB,iBAAiB,OAAO,SAAO,CAAC,gBAAgB,IAAI,GAAG,CAAC;AAChF,UAAI,gBAAgB,SAAS,GAAG;AAC9B,gBAAQ,IAAI,yBAAyB,gBAAgB,MAAM,8BAA8B,gBAAgB,KAAK,IAAI,CAAC,EAAE;AACrH,mBAAW,aAAa,iBAAiB;AACvC,cAAI;AACF,kBAAM,cAAc,WAAW,SAAS;AACxC,wBAAY,SAAS;AACrB,oBAAQ,IAAI,iEAAiE,SAAS,EAAE;AAGxF,gBAAI;AACF,oBAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,gBAC/D,QAAQ;AAAA,gBACR,MAAM,KAAK,UAAU,EAAE,YAAY,KAAK,CAAC;AAAA,cAC3C,CAAC;AAAA,YACH,SAAS,KAAK;AACZ,sBAAQ,KAAK,kDAAkD,SAAS,KAAK,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,YACvH;AAAA,UACF,SAAS,KAAK;AACZ,oBAAQ,MAAM,kDAAkD,SAAS,KAAK,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,UACxH;AAAA,QACF;AAAA,MACF;AAOA,YAAM,4BAA4B,QAAQ;AAAA,QAAO,CAAC,MAChD,EAAE,aAAa,YACd,CAAC,EAAE,uBAAuB,EAAE,wBAAwB,KAAK,aAC1D,CAAC,cAAc,UAAU,EAAE,GAAG;AAAA,MAChC;AAEA,UAAI,0BAA0B,WAAW,GAAG;AAC1C,gBAAQ,IAAI,yDAAyD;AACrE;AAAA,MACF;AAEA,cAAQ,IAAI,yBAAyB,0BAA0B,MAAM,gCAAgC;AAGrG,iBAAWC,WAAU,2BAA2B;AAC9C,cAAM,YAAYA,QAAO;AAGzB,cAAM,WAAW,MAAM,yBAAyB,SAAS;AACzD,YAAI,CAAC,UAAU;AACb,kBAAQ,KAAK,+CAA+C,SAAS,yBAAyB;AAC9F;AAAA,QACF;AACA,YAAI,CAAC,SAAS,QAAQ;AACpB,kBAAQ,IAAI,mCAAmC,SAAS,OAAO,SAAS,IAAI,YAAY;AACxF;AAAA,QACF;AAGA,cAAM,OAAO,aAAa,SAAS;AAEnC,gBAAQ,IAAI,4CAA4C,SAAS,OAAO,SAAS,IAAI,YAAY,IAAI,EAAE;AAGvG,cAAM,qBAAqB,OAAO,eAI5B;AACJ,cAAI;AACF,kBAAM,iBAAiB,MAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,cACtF,QAAQ;AAAA,cACR,MAAM,KAAK,UAAU,UAAU;AAAA,YACjC,CAAC;AAED,gBAAI,eAAe,IAAI;AACrB,sBAAQ,IAAI,8CAA8C,SAAS,EAAE;AAAA,YACvE,OAAO;AACL,sBAAQ,KAAK,mDAAmD,eAAe,UAAU,EAAE;AAAA,YAC7F;AAAA,UACF,SAAS,aAAa;AACpB,oBAAQ,KAAK,kDAAkD,WAAW;AAAA,UAC5E;AAAA,QACF;AAIC,SAAC,YAAY;AACZ,gBAAM,KAAK,eAAe,WAAW,YAAY;AAE/C,gBAAI,cAAc,UAAU,SAAS,GAAG;AACtC,sBAAQ,IAAI,8CAA8C,SAAS,uBAAuB;AAE1F;AAAA,YACF;AAEA,kBAAM,cAAc;AAEpB,kBAAM,iBAAiB;AAGvB,kBAAM,mBAAmB;AAAA,cACvB,oBAAmB,oBAAI,KAAK,GAAE,YAAY;AAAA,cAC1C,cAAc;AAAA,YAChB,CAAC;AAED,gBAAI;AAGF,oBAAM,kBAAkB,OAAO,kBAAkB;AACjD,sBAAQ,IAAI,sDAAsD,SAAS,OAAO,SAAS,IAAI,KAAK;AACpG,oBAAM,kBAAkB,MAAM,gBAAgB,SAAS,MAAM,MAAM,WAAW,eAAe;AAC7F,kBAAI,CAAC,gBAAgB,SAAS;AAC5B,sBAAMF,YAAW,+BAA+B,gBAAgB,KAAK;AACrE,wBAAQ,MAAM,mBAAmBA,SAAQ,EAAE;AAC3C,sBAAM,mBAAmB,EAAE,cAAcA,UAAS,CAAC;AAEnD,4BAAY,SAAS;AACrB;AAAA,cACF;AACA,sBAAQ,IAAI,4CAA4C,IAAI,EAAE;AAG9D,kBAAI;AACJ,uBAAS,UAAU,GAAG,WAAW,aAAa,WAAW;AACvD,wBAAQ,IAAI,uCAAuC,SAAS,aAAa,OAAO,IAAI,WAAW,MAAM;AAErG,sBAAM,cAAc,MAAM,cAAc,YAAY;AAAA,kBAClD;AAAA,kBACA;AAAA,kBACA,OAAO,OAAO,QAAQ;AACpB,4BAAQ,IAAI,kCAAkC,SAAS,KAAK,GAAG,EAAE;AACjE,0BAAM,mBAAmB;AAAA,sBACvB,YAAY;AAAA,sBACZ,cAAc;AAAA,oBAChB,CAAC;AAAA,kBACH;AAAA,kBACA,gBAAgB,CAAC,QAAQ,UAAU;AACjC,wBAAI,WAAW,SAAS;AACtB,8BAAQ,MAAM,oCAAoC,SAAS,KAAK,KAAK,EAAE;AACvE,yCAAmB,EAAE,cAAc,SAAS,0BAA0B,CAAC;AAAA,oBACzE,WAAW,WAAW,gBAAgB;AACpC,8BAAQ,IAAI,2CAA2C,SAAS,KAAK;AAAA,oBACvE;AAAA,kBACF;AAAA,gBACF,CAAC;AAED,oBAAI,YAAY,SAAS;AACvB,0BAAQ,IAAI,mDAAmD,SAAS,EAAE;AAC1E;AAAA,gBACF;AAEA,4BAAY,YAAY;AACxB,wBAAQ,KAAK,wCAAwC,OAAO,YAAY,SAAS,EAAE;AAEnF,oBAAI,UAAU,aAAa;AACzB,0BAAQ,IAAI,+BAA+B,cAAc,OAAO;AAChE,wBAAM,IAAI,QAAQ,CAAAF,aAAW,WAAWA,UAAS,cAAc,CAAC;AAAA,gBAClE;AAAA,cACF;AAGA,oBAAM,WAAW,uBAAuB,WAAW,cAAc,SAAS;AAC1E,sBAAQ,MAAM,mBAAmB,QAAQ,EAAE;AAC3C,oBAAM,mBAAmB,EAAE,cAAc,SAAS,CAAC;AAEnD,0BAAY,SAAS;AAAA,YAEvB,SAAS,OAAO;AACd,oBAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,sBAAQ,MAAM,+CAA+C,KAAK;AAClE,oBAAM,mBAAmB,EAAE,cAAc,SAAS,CAAC;AAEnD,0BAAY,SAAS;AAAA,YACvB;AAAA,UACF,CAAC;AAAA,QACH,GAAG;AAAA,MACL;AAAA,IAEF,SAAS,OAAO;AACd,cAAQ,MAAM,gDAAgD,KAAK;AAAA,IACrE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,oBAA0B;AAChC,QAAI,KAAK,oBAAoB;AAC3B,oBAAc,KAAK,kBAAkB;AACrC,WAAK,qBAAqB;AAC1B,cAAQ,IAAI,wCAAwC;AAAA,IACtD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,0BAAgC;AACtC,QAAI,KAAK,qBAAqB;AAC5B,cAAQ,IAAI,sDAAsD;AAClE;AAAA,IACF;AAEA,YAAQ,IAAI,wDAAwD,QAAO,2BAA2B,GAAI,IAAI;AAE9G,SAAK,sBAAsB,YAAY,YAAY;AAEjD,UAAI,KAAK,uBAAuB;AAC9B,gBAAQ,IAAI,0DAA0D;AACtE;AAAA,MACF;AAEA,WAAK,wBAAwB;AAC7B,UAAI;AACF,cAAM,SAAS,UAAM,0BAAW;AAChC,YAAI,QAAQ,cAAc;AACxB,gBAAM,KAAK,oBAAoB,MAAM;AAAA,QACvC;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,MAAM,uCAAuC,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,MACrG,UAAE;AACA,aAAK,wBAAwB;AAAA,MAC/B;AAAA,IACF,GAAG,QAAO,wBAAwB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAA+B;AACrC,QAAI,KAAK,qBAAqB;AAC5B,oBAAc,KAAK,mBAAmB;AACtC,WAAK,sBAAsB;AAC3B,cAAQ,IAAI,8CAA8C;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,yBAAwC;AACpD,QAAI;AACF,YAAM,gBAAgB,iBAAiB;AACvC,YAAM,cAAc,WAAW;AAG/B,YAAM,iBAAiB,cAAc,cAAc;AACnD,UAAI,eAAe,SAAS,GAAG;AAC7B,gBAAQ,IAAI,4BAA4B,eAAe,MAAM,uBAAuB;AACpF,mBAAW,UAAU,gBAAgB;AACnC,cAAI;AACF,kBAAM,cAAc,WAAW,OAAO,SAAS;AAC/C,kBAAM,cAAc,OAAO,SAAS;AAAA,UACtC,SAAS,OAAO;AACd,oBAAQ,MAAM,6CAA6C,OAAO,SAAS,KAAK,KAAK;AAAA,UACvF;AAAA,QACF;AAAA,MACF;AAIA,YAAM,UAAU,MAAM,cAAc,yBAAyB;AAC7D,UAAI,QAAQ,UAAU,GAAG;AACvB,gBAAQ,IAAI,0BAA0B,QAAQ,OAAO,mCAAmC;AAAA,MAC1F;AAEA,cAAQ,IAAI,sEAAsE;AAAA,IAIpF,SAAS,OAAO;AACd,cAAQ,MAAM,wDAAwD,KAAK;AAAA,IAC7E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,0BAAyC;AACrD,QAAI;AACF,YAAM,WAAW,eAAe;AAEhC,iBAAW,WAAW,UAAU;AAC9B,cAAM,KAAK,sBAAsB,QAAQ,IAAI;AAAA,MAC/C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,KAAK,yDAAyD,KAAK;AAAA,IAC7E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,sBAAsB,aAAoC;AACtE,QAAI;AAEF,YAAM,cAAc,MAAM,gBAAgB,WAAW;AACrD,UAAI,CAAC,aAAa;AAChB;AAAA,MACF;AAEA,YAAM,UAAU,IAAI,gBAAgB,WAAW;AAC/C,UAAI,CAAC,MAAM,QAAQ,WAAW,GAAG;AAC/B;AAAA,MACF;AAEA,YAAM,SAAS,QAAQ,UAAU;AACjC,UAAI,CAAC,QAAQ;AACX;AAAA,MACF;AAGA,YAAM,YAAY,QAAQ,cAAc;AACxC,UAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,MACF;AAGA,YAAM,mBAAmB,MAAM,KAAK,sBAAsB,OAAO,SAAS;AAC1E,UAAI,qBAAqB,MAAM;AAC7B;AAAA,MACF;AAGA,YAAM,EAAE,SAAS,IAAI,QAAQ,eAAe,gBAAgB;AAE5D,UAAI,SAAS,SAAS,GAAG;AACvB,gBAAQ,IAAI,yBAAyB,SAAS,MAAM,4BAA4B,OAAO,aAAa,IAAI,OAAO,WAAW,GAAG;AAC7H,mBAAW,KAAK,UAAU;AACxB,kBAAQ,IAAI,OAAO,EAAE,SAAS,aAAa,EAAE,UAAU,GAAG;AAAA,QAC5D;AACA,gBAAQ,IAAI,4DAA4D;AAAA,MAC1E;AAAA,IACF,SAAS,OAAO;AAEd,cAAQ,KAAK,mCAAmC,WAAW,KAAK,KAAK;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,sBAAsB,WAA6C;AAC/E,QAAI;AACF,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,SAAS;AAC7C,eAAO;AAAA,MACT;AAEA,YAAM,MAAM,GAAG,OAAO,OAAO,2BAA2B,SAAS;AACjE,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,SAAS;AAAA,UACP,iBAAiB,UAAU,OAAO,YAAY;AAAA,UAC9C,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,eAAO;AAAA,MACT;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAI,CAAC,KAAK,WAAW,CAAC,KAAK,SAAS;AAClC,eAAO;AAAA,MACT;AAEA,aAAO,KAAK,QAAQ,IAAI,OAAK,EAAE,GAAG,EAAE,OAAO,CAAC,QAAuB,CAAC,CAAC,GAAG;AAAA,IAC1E,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,oBAAoB,QAAsC;AACtE,UAAM,gBAAgB,iBAAiB;AACvC,UAAM,iBAAiB,cAAc,cAAc;AAEnD,QAAI,eAAe,WAAW,GAAG;AAC/B;AAAA,IACF;AAEA,UAAM,SAAS,OAAO,WAAW;AAEjC,eAAW,UAAU,gBAAgB;AACnC,UAAI,OAAO,WAAW,aAAa;AACjC;AAAA,MACF;AAEA,YAAM,YAAY,MAAM,KAAK,kBAAkB,MAAM;AAErD,UAAI,WAAW;AAEb,aAAK,qBAAqB,OAAO,OAAO,SAAS;AACjD,cAAM,KAAK,mBAAmB,OAAO,WAAW,WAAW,MAAM;AAAA,MACnE,OAAO;AAEL,cAAM,YAAY,KAAK,qBAAqB,IAAI,OAAO,SAAS,KAAK,KAAK;AAC1E,aAAK,qBAAqB,IAAI,OAAO,WAAW,QAAQ;AAExD,gBAAQ,IAAI,2CAA2C,OAAO,SAAS,KAAK,QAAQ,IAAI,QAAO,8BAA8B,GAAG;AAEhI,YAAI,YAAY,QAAO,gCAAgC;AACrD,kBAAQ,IAAI,wCAAwC,OAAO,SAAS,iBAAiB;AAErF,gBAAM,KAAK,eAAe,OAAO,WAAW,YAAY;AACtD,kBAAM,KAAK,cAAc,OAAO,WAAW,OAAO,IAAI;AAAA,UACxD,CAAC;AACD,eAAK,qBAAqB,OAAO,OAAO,SAAS;AACjD,gBAAM,KAAK,mBAAmB,OAAO,WAAW,aAAa,MAAM;AAAA,QACrE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,kBAAkB,QAA4E;AAE1G,QAAI;AACF,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,QAAO,uBAAuB;AAEnF,YAAM,WAAW,MAAM,MAAM,OAAO,KAAK;AAAA,QACvC,QAAQ;AAAA,QACR,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,mBAAa,OAAO;AAGpB,UAAI,SAAS,UAAU,KAAK;AAC1B,gBAAQ,IAAI,uCAAuC,SAAS,MAAM,QAAQ,OAAO,SAAS,EAAE;AAC5F,eAAO;AAAA,MACT;AAAA,IACF,SAAS,OAAO;AAEd,cAAQ,IAAI,8CAA8C,OAAO,SAAS,KAAK,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAC7H,aAAO;AAAA,IACT;AAGA,QAAI;AACF,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,GAAI;AAEzD,YAAM,gBAAgB,MAAM,MAAM,oBAAoB,OAAO,IAAI,IAAI;AAAA,QACnE,QAAQ;AAAA,QACR,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,mBAAa,OAAO;AAEpB,UAAI,cAAc,UAAU,KAAK;AAC/B,gBAAQ,IAAI,6CAA6C,cAAc,MAAM,QAAQ,OAAO,SAAS,EAAE;AACvG,eAAO;AAAA,MACT;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,IAAI,oDAAoD,OAAO,SAAS,KAAK,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACnI,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cAAc,WAAmB,MAA6B;AAC1E,UAAM,gBAAgB,iBAAiB;AAEvC,QAAI;AAEF,YAAM,cAAc,WAAW,SAAS;AAGxC,YAAM,SAAS,UAAM,0BAAW;AAChC,UAAI,CAAC,QAAQ,cAAc;AACzB,gBAAQ,MAAM,oDAAoD;AAClE;AAAA,MACF;AAEA,YAAM,SAAS,OAAO,WAAW;AAMjC,YAAM,kBAAkB,MAAM,iBAAiB,SAAS;AAExD,UAAI,CAAC,gBAAgB,SAAS;AAG5B,gBAAQ,IAAI,yCAAyC,SAAS,yBAAyB;AAEvF,YAAI,YAA2B;AAC/B,YAAI;AACF,gBAAM,iBAAiB,MAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,EAAE;AAC/E,cAAI,eAAe,IAAI;AACrB,kBAAM,aAAa,MAAM,eAAe,KAAK;AAC7C,wBAAY,WAAW,cAAc,cAAc;AAAA,UACrD;AAAA,QACF,SAAS,GAAG;AACV,kBAAQ,KAAK,mEAAmE;AAAA,QAClF;AAGA,cAAM,WAAW,MAAM,yBAAyB,SAAS;AACzD,YAAI,CAAC,UAAU;AACb,kBAAQ,MAAM,oDAAoD,SAAS,yBAAyB;AACpG;AAAA,QACF;AAEA,YAAI,CAAC,SAAS,QAAQ;AACpB,kBAAQ,MAAM,yCAAyC,SAAS,IAAI,EAAE;AACtE;AAAA,QACF;AAGA,cAAM,EAAE,aAAAK,aAAY,IAAI,MAAM;AAC9B,YAAI,MAAMA,aAAY,IAAI,GAAG;AAC3B,kBAAQ,IAAI,wBAAwB,IAAI,6BAA6B;AACrE,gBAAM,UAAU,MAAM,mBAAmB,IAAI;AAC7C,cAAI,CAAC,SAAS;AACZ,oBAAQ,IAAI,sCAAsC,IAAI,wCAAwC;AAC9F,kBAAM,kBAAkB,IAAI;AAAA,UAC9B;AAAA,QACF;AAGA,cAAM,kBAAkB,OAAO,kBAAkB;AAGjD,cAAMC,eAAc,MAAM,gBAAgB,SAAS,MAAM,MAAM,WAAW,eAAe;AACzF,YAAI,CAACA,aAAY,SAAS;AACxB,kBAAQ,MAAM,+CAA+CA,aAAY,KAAK,EAAE;AAChF;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,IAAI,2DAA2D,SAAS,KAAK;AAGrF,YAAM,cAAc,MAAM,cAAc,YAAY;AAAA,QAClD;AAAA,QACA;AAAA,QACA,OAAO,OAAO,QAAQ;AACpB,kBAAQ,IAAI,wCAAwC,SAAS,KAAK,GAAG,EAAE;AACvE,cAAI;AACF,kBAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,cAC/D,QAAQ;AAAA,cACR,MAAM,KAAK,UAAU;AAAA,gBACnB,YAAY;AAAA,gBACZ,cAAc;AAAA,cAChB,CAAC;AAAA,YACH,CAAC;AAAA,UACH,SAAS,GAAG;AACV,oBAAQ,KAAK,uDAAuD;AAAA,UACtE;AAAA,QACF;AAAA,MACF,CAAC;AAED,UAAI,YAAY,SAAS;AACvB,gBAAQ,IAAI,iDAAiD,SAAS,EAAE;AAAA,MAC1E,OAAO;AACL,gBAAQ,MAAM,6CAA6C,SAAS,KAAK,YAAY,KAAK,EAAE;AAAA,MAC9F;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,+CAA+C,SAAS,KAAK,KAAK;AAAA,IAClF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,mBACZ,WACA,cACA,QACe;AACf,QAAI,CAAC,OAAO,cAAc;AACxB;AAAA,IACF;AAGA,UAAM,aAAa,KAAK,yBAAyB,IAAI,SAAS;AAC9D,QAAI,eAAe,cAAc;AAC/B;AAAA,IACF;AAEA,UAAM,SAAS,OAAO,WAAW;AAEjC,QAAI;AACF,YAAM,cAAc,GAAG,MAAM,gBAAgB,SAAS,WAAW;AAAA,QAC/D,QAAQ;AAAA,QACR,MAAM,KAAK,UAAU;AAAA,UACnB,sBAAsB;AAAA,UACtB,2BAA0B,oBAAI,KAAK,GAAE,YAAY;AAAA,QACnD,CAAC;AAAA,MACH,CAAC;AAED,WAAK,yBAAyB,IAAI,WAAW,YAAY;AAAA,IAC3D,SAAS,OAAO;AAEd,cAAQ,KAAK,+CAA+C,SAAS,KAAK,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IAC1H;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,uBAAuB,SAAkC;AACrE,UAAM,EAAE,MAAAC,MAAK,IAAI,MAAM,OAAO,eAAe;AAC7C,UAAM,EAAE,WAAAC,WAAU,IAAI,MAAM,OAAO,MAAM;AACzC,UAAMC,aAAYD,WAAUD,KAAI;AAEhC,QAAI;AAEF,YAAM,EAAE,OAAO,IAAI,MAAME,WAAU,aAAa,OAAO,GAAG;AAC1D,YAAM,OAAO,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AAErD,UAAI,KAAK,WAAW,GAAG;AACrB,eAAO;AAAA,MACT;AAGA,UAAI,SAAS;AACb,iBAAW,OAAO,MAAM;AACtB,YAAI;AACF,gBAAMA,WAAU,QAAQ,GAAG,EAAE;AAC7B;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,UAAI,SAAS,GAAG;AACd,gBAAQ,IAAI,0BAA0B,MAAM,0BAA0B,OAAO,EAAE;AAAA,MACjF;AACA,aAAO;AAAA,IACT,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,kBAAkB,MAAgC;AAC9D,UAAM,EAAE,MAAAF,MAAK,IAAI,MAAM,OAAO,eAAe;AAC7C,UAAM,EAAE,WAAAC,WAAU,IAAI,MAAM,OAAO,MAAM;AACzC,UAAMC,aAAYD,WAAUD,KAAI;AAEhC,QAAI;AAEF,YAAM,EAAE,OAAO,IAAI,MAAME,WAAU,aAAa,IAAI,EAAE;AACtD,YAAM,OAAO,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AAErD,UAAI,KAAK,WAAW,GAAG;AACrB,eAAO;AAAA,MACT;AAGA,UAAI,SAAS;AACb,iBAAW,OAAO,MAAM;AACtB,YAAI;AACF,gBAAMA,WAAU,QAAQ,GAAG,EAAE;AAC7B,mBAAS;AAAA,QACX,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,UAAI,QAAQ;AACV,gBAAQ,IAAI,8CAA8C,IAAI,EAAE;AAAA,MAClE;AACA,aAAO;AAAA,IACT,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,mCAAwF;AACpG,UAAM,EAAE,MAAAF,MAAK,IAAI,MAAM,OAAO,eAAe;AAC7C,UAAM,EAAE,WAAAC,WAAU,IAAI,MAAM,OAAO,MAAM;AACzC,UAAMC,aAAYD,WAAUD,KAAI;AAEhC,QAAI;AAEF,YAAM,EAAE,OAAO,IAAI,MAAME,WAAU,0CAA0C;AAC7E,YAAM,QAAQ,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AAEtD,YAAM,gBAAgB,iBAAiB;AACvC,YAAM,iBAAiB,IAAI,IAAI,cAAc,cAAc,EAAE,IAAI,OAAK,EAAE,SAAS,CAAC;AAClF,YAAM,WAAuD,CAAC;AAE9D,iBAAW,QAAQ,OAAO;AACxB,cAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK;AACrC,cAAM,MAAM,MAAM,CAAC;AAOnB,YAAI,KAAK;AACP,mBAAS,KAAK,EAAE,IAAI,CAAC;AAAA,QACvB;AAAA,MACF;AAGA,UAAI,SAAS,SAAS,eAAe,MAAM;AACzC,gBAAQ,IAAI,yBAAyB,SAAS,MAAM,mCAAmC,eAAe,IAAI,kBAAkB;AAAA,MAC9H;AAEA,aAAO,SAAS,SAAS,eAAe,OAAO,WAAW,CAAC;AAAA,IAC7D,QAAQ;AAEN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,sCAAqD;AACjE,UAAM,WAAW,MAAM,KAAK,iCAAiC;AAE7D,QAAI,SAAS,WAAW,GAAG;AACzB;AAAA,IACF;AAEA,YAAQ,IAAI,+BAA+B,SAAS,MAAM,+CAA+C;AAGzG,UAAM,SAAS,MAAM,KAAK,uBAAuB,oBAAoB;AAErE,QAAI,SAAS,GAAG;AACd,cAAQ,IAAI,8BAA8B,MAAM,mCAAmC;AAAA,IACrF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,WAA0B;AACtC,QAAI,KAAK,aAAc;AACvB,SAAK,eAAe;AAEpB,YAAQ,IAAI,2BAA2B;AAGvC,SAAK,kBAAkB;AAGvB,SAAK,uBAAuB;AAG5B,eAAW,CAAC,aAAa,UAAU,KAAK,KAAK,aAAa;AACxD,UAAI,WAAW,gBAAgB;AAC7B,qBAAa,WAAW,cAAc;AAAA,MACxC;AACA,YAAM,WAAW,OAAO,WAAW;AAAA,IACrC;AACA,SAAK,YAAY,MAAM;AAGvB,QAAI;AACF,YAAM,gBAAgB,iBAAiB;AACvC,YAAM,cAAc,eAAe;AACnC,cAAQ,IAAI,8BAA8B;AAAA,IAC5C,SAAS,OAAO;AACd,cAAQ,MAAM,oCAAoC,KAAK;AAAA,IACzD;AAGA,kBAAc;AAGd,QAAI;AACF,YAAM,eAAe,gBAAgB;AACrC,YAAM,aAAa,gBAAgB;AACnC,cAAQ,IAAI,qCAAqC;AAAA,IACnD,SAAS,OAAO;AACd,cAAQ,MAAM,2CAA2C,KAAK;AAAA,IAChE;AAKA,UAAM,KAAK,UAAU,KAAK;AAE1B,YAAQ,IAAI,4BAA4B;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,iBAAgC;AAC5C,UAAM,KAAK,SAAS;AAGpB,QAAI;AACF,YAAM,UAAU,eAAe;AAC/B,UAAO,gBAAW,OAAO,GAAG;AAC1B,QAAG,gBAAW,OAAO;AACrB,gBAAQ,IAAI,8BAA8B;AAAA,MAC5C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,yCAAyC,KAAK;AAAA,IAC9D;AAEA,YAAQ,IAAI,qBAAqB;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAKA,eAAe,OAAO;AAEpB,MAAI,CAAC,QAAQ,IAAI,qBAAqB;AACpC,YAAQ,MAAM,kDAAkD;AAChE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,OAAO,MAAM;AAGnB,QAAM,IAAI,QAAQ,MAAM;AAAA,EAExB,CAAC;AACH;AAGA,KAAK,EAAE,MAAM,WAAS;AACpB,UAAQ,MAAM,yBAAyB,KAAK;AAC5C,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["exports","exports","execAsync","GitExecutor","fs","path","exports","packageJson","exports","EpisodaClient","resolve","exports","exports","getConfigDir","loadConfig","saveConfig","fs","path","os","exports","exports","resolve","net","exports","module","fs","path","import_core","path","import_core","fs","path","import_core","resolve","import_core","import_child_process","fs","path","import_child_process","resolve","import_child_process","import_core","import_child_process","fs","path","platform","arch","resolve","import_child_process","fs","path","os","resolve","process","import_core","import_child_process","path","fs","import_child_process","path","fs","os","process","resolve","import_child_process","import_core","fs","path","os","fs","path","fs","path","resolve","http","fs","path","fs","path","import_core","resolve","execSync","path","fs","os","import_core","getEpisodaRoot","getEpisodaRoot","getEpisodaRoot","fs","path","fs","os","path","fetchEnvVars","resolve","config","errorMsg","execSync","module","isPortInUse","startResult","exec","promisify","execAsync"]}
|
|
1
|
+
{"version":3,"sources":["../../../core/src/git-validator.ts","../../../core/src/git-parser.ts","../../../core/src/git-executor.ts","../../../core/src/version.ts","../../../core/src/websocket-client.ts","../../../core/src/auth.ts","../../../core/src/errors.ts","../../../core/src/index.ts","../../src/utils/port-check.ts","../../package.json","../../src/daemon/machine-id.ts","../../src/daemon/project-tracker.ts","../../src/daemon/daemon-manager.ts","../../src/ipc/ipc-server.ts","../../src/daemon/daemon-process.ts","../../src/utils/update-checker.ts","../../src/daemon/handlers/file-handlers.ts","../../src/daemon/handlers/exec-handler.ts","../../src/daemon/handlers/stale-commit-cleanup.ts","../../src/tunnel/cloudflared-manager.ts","../../src/tunnel/tunnel-manager.ts","../../src/tunnel/tunnel-api.ts","../../src/agent/claude-binary.ts","../../src/agent/agent-manager.ts","../../src/utils/dev-server.ts","../../src/utils/env-cache.ts","../../src/utils/env-setup.ts","../../src/utils/port-detect.ts","../../src/daemon/worktree-manager.ts","../../src/utils/worktree.ts","../../src/utils/port-allocator.ts","../../src/framework-detector.ts"],"sourcesContent":["/**\n * Git Input Validation Utilities\n *\n * Validates git command inputs to prevent injection and ensure safety.\n * Interface-agnostic: Returns boolean/error codes, not formatted messages.\n */\n\nimport { ErrorCode } from './command-protocol'\n\n/**\n * Validate branch name format\n * Git branch naming rules:\n * - Cannot start with - or /\n * - Cannot contain .. or @{ or \\\n * - Cannot end with .lock or /\n * - Cannot contain spaces or control characters\n * - Cannot be just @\n */\nexport function validateBranchName(branchName: string): { valid: boolean; error?: ErrorCode } {\n if (!branchName || branchName.trim().length === 0) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Check for invalid characters and patterns\n const invalidPatterns = [\n /^\\-/, // Starts with -\n /^\\//, // Starts with /\n /\\.\\./, // Contains ..\n /@\\{/, // Contains @{\n /\\\\/, // Contains backslash\n /\\.lock$/, // Ends with .lock\n /\\/$/, // Ends with /\n /\\s/, // Contains whitespace\n /[\\x00-\\x1F\\x7F]/, // Contains control characters\n /[\\*\\?\\[\\]~\\^:]/, // Contains special characters\n ]\n\n for (const pattern of invalidPatterns) {\n if (pattern.test(branchName)) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n }\n\n // Branch name cannot be just @\n if (branchName === '@') {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Maximum reasonable length (Git allows up to 255, but let's be conservative)\n if (branchName.length > 200) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n return { valid: true }\n}\n\n/**\n * Validate commit message\n * Basic validation to ensure message is not empty\n */\nexport function validateCommitMessage(message: string): { valid: boolean; error?: ErrorCode } {\n if (!message || message.trim().length === 0) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Reasonable length check\n if (message.length > 10000) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n return { valid: true }\n}\n\n/**\n * Validate file paths\n * Basic validation to prevent directory traversal\n */\nexport function validateFilePaths(files: string[]): { valid: boolean; error?: ErrorCode } {\n if (!files || files.length === 0) {\n return { valid: true }\n }\n\n for (const file of files) {\n // Check for null bytes (command injection)\n if (file.includes('\\0')) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Check for control characters\n if (/[\\x00-\\x1F\\x7F]/.test(file)) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n\n // Files should not be empty\n if (file.trim().length === 0) {\n return { valid: false, error: 'UNKNOWN_ERROR' }\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Sanitize command arguments to prevent injection\n * Returns sanitized array of arguments\n */\nexport function sanitizeArgs(args: string[]): string[] {\n return args.map(arg => {\n // Remove null bytes\n return arg.replace(/\\0/g, '')\n })\n}\n","/**\n * Git Command Output Parser\n *\n * Parses git command output and error messages into structured data.\n * Interface-agnostic: Returns structured objects, not formatted strings.\n */\n\nimport { ErrorCode } from './command-protocol'\n\n/**\n * Parse git status output to extract uncommitted files\n */\nexport function parseGitStatus(output: string): {\n uncommittedFiles: string[]\n isClean: boolean\n currentBranch?: string\n} {\n const lines = output.split('\\n')\n const uncommittedFiles: string[] = []\n let currentBranch: string | undefined\n\n for (const line of lines) {\n // Porcelain format branch line: ## branch-name or ## HEAD (no branch)\n if (line.startsWith('## ')) {\n const branchInfo = line.substring(3)\n // Extract branch name (handle \"branch...origin/branch\" format)\n const branchMatch = branchInfo.match(/^([^\\s.]+)/)\n if (branchMatch && branchMatch[1]) {\n currentBranch = branchMatch[1] === 'HEAD' ? 'HEAD (detached)' : branchMatch[1]\n }\n continue\n }\n\n // Parse modified/added/deleted files\n // Porcelain format: \"XY filename\" where X is staged, Y is unstaged\n // Format: \" M file.txt\" or \"M file.txt\" or \"?? file.txt\" or \"MM file.txt\"\n if (line.length >= 3) {\n const status = line.substring(0, 2)\n const filePath = line.substring(3).trim()\n\n // Check if there's any status (not all spaces)\n if (status.trim().length > 0 && filePath.length > 0) {\n uncommittedFiles.push(filePath)\n }\n }\n }\n\n const isClean = uncommittedFiles.length === 0\n\n return { uncommittedFiles, isClean, currentBranch }\n}\n\n/**\n * Parse merge conflict output to extract conflicting files\n */\nexport function parseMergeConflicts(output: string): string[] {\n const lines = output.split('\\n')\n const conflictingFiles: string[] = []\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Look for conflict markers\n if (trimmed.startsWith('CONFLICT')) {\n // Format: \"CONFLICT (content): Merge conflict in file.txt\"\n const match = trimmed.match(/CONFLICT.*in (.+)$/)\n if (match && match[1]) {\n conflictingFiles.push(match[1])\n }\n }\n\n // Also check for \"both modified\" status\n if (trimmed.startsWith('UU ')) {\n conflictingFiles.push(trimmed.substring(3).trim())\n }\n }\n\n return conflictingFiles\n}\n\n/**\n * Map git error output to ErrorCode\n */\nexport function parseGitError(stderr: string, stdout: string, exitCode: number): ErrorCode {\n const combinedOutput = `${stderr}\\n${stdout}`.toLowerCase()\n\n // Check for git not installed\n if (combinedOutput.includes('git: command not found') ||\n combinedOutput.includes(\"'git' is not recognized\")) {\n return 'GIT_NOT_INSTALLED'\n }\n\n // Check for not a git repository\n if (combinedOutput.includes('not a git repository') ||\n combinedOutput.includes('not a git repo')) {\n return 'NOT_GIT_REPO'\n }\n\n // Check for merge conflicts\n if (combinedOutput.includes('conflict') ||\n combinedOutput.includes('merge conflict') ||\n exitCode === 1 && combinedOutput.includes('automatic merge failed')) {\n return 'MERGE_CONFLICT'\n }\n\n // Check for uncommitted changes\n if (combinedOutput.includes('please commit your changes') ||\n combinedOutput.includes('would be overwritten') ||\n combinedOutput.includes('cannot checkout') && combinedOutput.includes('files would be overwritten')) {\n return 'UNCOMMITTED_CHANGES'\n }\n\n // Check for authentication failures\n if (combinedOutput.includes('authentication failed') ||\n combinedOutput.includes('could not read username') ||\n combinedOutput.includes('permission denied') ||\n combinedOutput.includes('could not read password') ||\n combinedOutput.includes('fatal: authentication failed')) {\n return 'AUTH_FAILURE'\n }\n\n // Check for network errors\n if (combinedOutput.includes('could not resolve host') ||\n combinedOutput.includes('failed to connect') ||\n combinedOutput.includes('network is unreachable') ||\n combinedOutput.includes('connection timed out') ||\n combinedOutput.includes('could not read from remote')) {\n return 'NETWORK_ERROR'\n }\n\n // Check for branch not found\n if (combinedOutput.includes('did not match any file') ||\n combinedOutput.includes('branch') && combinedOutput.includes('not found') ||\n combinedOutput.includes('pathspec') && combinedOutput.includes('did not match')) {\n return 'BRANCH_NOT_FOUND'\n }\n\n // Check for branch already exists\n if (combinedOutput.includes('already exists') ||\n combinedOutput.includes('a branch named') && combinedOutput.includes('already exists')) {\n return 'BRANCH_ALREADY_EXISTS'\n }\n\n // Check for push rejected\n if (combinedOutput.includes('push rejected') ||\n combinedOutput.includes('failed to push') ||\n combinedOutput.includes('rejected') && combinedOutput.includes('non-fast-forward') ||\n combinedOutput.includes('updates were rejected')) {\n return 'PUSH_REJECTED'\n }\n\n // Check for timeout (exit code 124 is GNU timeout, 143 is SIGTERM)\n if (exitCode === 124 || exitCode === 143) {\n return 'COMMAND_TIMEOUT'\n }\n\n // Default to unknown error\n return 'UNKNOWN_ERROR'\n}\n\n/**\n * Extract branch name from git output\n */\nexport function extractBranchName(output: string): string | undefined {\n const lines = output.split('\\n')\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Check for \"Switched to branch 'name'\"\n let match = trimmed.match(/Switched to (?:a new )?branch '(.+)'/)\n if (match && match[1]) {\n return match[1]\n }\n\n // Check for \"On branch name\"\n match = trimmed.match(/On branch (.+)/)\n if (match && match[1]) {\n return match[1]\n }\n }\n\n return undefined\n}\n\n/**\n * Check if output indicates detached HEAD state\n */\nexport function isDetachedHead(output: string): boolean {\n return output.toLowerCase().includes('head detached') ||\n output.toLowerCase().includes('you are in \\'detached head\\' state')\n}\n\n/**\n * Parse remote tracking information\n */\nexport function parseRemoteTracking(output: string): {\n hasUpstream: boolean\n remoteBranch?: string\n remote?: string\n} {\n const lines = output.split('\\n')\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Check for \"Branch 'name' set up to track remote branch 'remote/branch'\"\n const match = trimmed.match(/set up to track remote branch '(.+?)'(?: from '(.+?)')?/)\n if (match) {\n return {\n hasUpstream: true,\n remoteBranch: match[1],\n remote: match[2] || 'origin'\n }\n }\n\n // Check for existing upstream: \"Your branch is up to date with 'origin/main'\"\n const upstreamMatch = trimmed.match(/(?:up to date with|ahead of|behind) '(.+?)\\/(.+?)'/)\n if (upstreamMatch) {\n return {\n hasUpstream: true,\n remote: upstreamMatch[1],\n remoteBranch: upstreamMatch[2]\n }\n }\n }\n\n return { hasUpstream: false }\n}\n","/**\n * Git Command Executor\n *\n * Executes git commands with error handling and returns structured results.\n * Interface-agnostic: Returns structured data, not formatted strings.\n */\n\nimport { exec } from 'child_process'\nimport { promisify } from 'util'\nimport { GitCommand, ExecutionResult, ExecutionOptions, ErrorCode } from './command-protocol'\nimport {\n validateBranchName,\n validateCommitMessage,\n validateFilePaths,\n sanitizeArgs\n} from './git-validator'\nimport {\n parseGitError,\n parseGitStatus,\n parseMergeConflicts,\n extractBranchName,\n isDetachedHead,\n parseRemoteTracking\n} from './git-parser'\n\nconst execAsync = promisify(exec)\n\n/**\n * Executes git commands with error handling\n *\n * DESIGN PRINCIPLES:\n * - Interface-agnostic: Returns structured data, not formatted strings\n * - No console.log: Let the interface handle output\n * - Error codes: Return error codes, not messages\n * - Reusable: Can be used by CLI, MCP, or any other interface\n */\nexport class GitExecutor {\n /**\n * Execute a git command\n * @param command - The git command to execute\n * @param options - Execution options (timeout, cwd, etc.)\n * @returns Structured result with success/error details\n */\n async execute(\n command: GitCommand,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate git is installed\n const gitInstalled = await this.validateGitInstalled()\n if (!gitInstalled) {\n return {\n success: false,\n error: 'GIT_NOT_INSTALLED'\n }\n }\n\n // Determine working directory\n const cwd = options?.cwd || process.cwd()\n\n // Validate this is a git repository (except for init-like commands)\n const isGitRepo = await this.isGitRepository(cwd)\n if (!isGitRepo) {\n return {\n success: false,\n error: 'NOT_GIT_REPO'\n }\n }\n\n // Route to appropriate handler based on action\n switch (command.action) {\n case 'checkout':\n return await this.executeCheckout(command, cwd, options)\n case 'create_branch':\n return await this.executeCreateBranch(command, cwd, options)\n case 'commit':\n return await this.executeCommit(command, cwd, options)\n case 'push':\n return await this.executePush(command, cwd, options)\n case 'status':\n return await this.executeStatus(cwd, options)\n case 'pull':\n return await this.executePull(command, cwd, options)\n case 'delete_branch':\n return await this.executeDeleteBranch(command, cwd, options)\n // EP597: Read operations for production local dev mode\n case 'branch_exists':\n return await this.executeBranchExists(command, cwd, options)\n case 'branch_has_commits':\n return await this.executeBranchHasCommits(command, cwd, options)\n // EP831: Find branch by prefix pattern\n case 'find_branch_by_prefix':\n return await this.executeFindBranchByPrefix(command, cwd, options)\n // EP598: Main branch check for production\n case 'main_branch_check':\n return await this.executeMainBranchCheck(cwd, options)\n // EP599: Get commits for branch\n case 'get_commits':\n return await this.executeGetCommits(command, cwd, options)\n // EP599: Advanced operations for move-to-module and discard-main-changes\n case 'stash':\n return await this.executeStash(command, cwd, options)\n case 'reset':\n return await this.executeReset(command, cwd, options)\n case 'merge':\n return await this.executeMerge(command, cwd, options)\n case 'cherry_pick':\n return await this.executeCherryPick(command, cwd, options)\n case 'clean':\n return await this.executeClean(command, cwd, options)\n case 'add':\n return await this.executeAdd(command, cwd, options)\n case 'fetch':\n return await this.executeFetch(command, cwd, options)\n // EP599-3: Composite operations\n case 'move_to_module':\n return await this.executeMoveToModule(command, cwd, options)\n case 'discard_main_changes':\n return await this.executeDiscardMainChanges(cwd, options)\n // EP523: Branch sync operations\n case 'sync_status':\n return await this.executeSyncStatus(command, cwd, options)\n case 'sync_main':\n return await this.executeSyncMain(cwd, options)\n case 'rebase_branch':\n return await this.executeRebaseBranch(command, cwd, options)\n case 'rebase_abort':\n return await this.executeRebaseAbort(cwd, options)\n case 'rebase_continue':\n return await this.executeRebaseContinue(cwd, options)\n case 'rebase_status':\n return await this.executeRebaseStatus(cwd, options)\n // EP944: Worktree operations\n case 'worktree_add':\n return await this.executeWorktreeAdd(command, cwd, options)\n case 'worktree_remove':\n return await this.executeWorktreeRemove(command, cwd, options)\n case 'worktree_list':\n return await this.executeWorktreeList(cwd, options)\n case 'worktree_prune':\n return await this.executeWorktreePrune(cwd, options)\n // EP1002: Worktree setup command for unified orchestration\n case 'worktree_setup':\n return await this.executeWorktreeSetup(command, cwd, options)\n case 'clone_bare':\n return await this.executeCloneBare(command, options)\n case 'project_info':\n return await this.executeProjectInfo(cwd, options)\n default:\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: 'Unknown command action'\n }\n }\n } catch (error) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error instanceof Error ? error.message : 'Unknown error occurred'\n }\n }\n }\n\n /**\n * Execute checkout command\n */\n private async executeCheckout(\n command: { branch: string; create?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Check for uncommitted changes first\n const statusResult = await this.executeStatus(cwd, options)\n if (statusResult.success && statusResult.details?.uncommittedFiles?.length) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n details: {\n uncommittedFiles: statusResult.details.uncommittedFiles\n }\n }\n }\n\n // Build command\n const args = ['checkout']\n if (command.create) {\n args.push('-b')\n }\n args.push(command.branch)\n\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * Execute create_branch command\n */\n private async executeCreateBranch(\n command: { branch: string; from?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Validate source branch if provided\n if (command.from) {\n const fromValidation = validateBranchName(command.from)\n if (!fromValidation.valid) {\n return {\n success: false,\n error: fromValidation.error || 'UNKNOWN_ERROR'\n }\n }\n }\n\n // Build command - use checkout -b to create AND checkout the branch\n // This ensures the user is on the new branch immediately\n const args = ['checkout', '-b', command.branch]\n if (command.from) {\n args.push(command.from)\n }\n\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * Execute commit command\n */\n private async executeCommit(\n command: { message: string; files?: string[] },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate commit message\n const validation = validateCommitMessage(command.message)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Validate file paths if provided\n if (command.files) {\n const fileValidation = validateFilePaths(command.files)\n if (!fileValidation.valid) {\n return {\n success: false,\n error: fileValidation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Stage specific files first\n for (const file of command.files) {\n const addResult = await this.runGitCommand(['add', file], cwd, options)\n if (!addResult.success) {\n return addResult\n }\n }\n } else {\n // Stage all changes\n const addResult = await this.runGitCommand(['add', '-A'], cwd, options)\n if (!addResult.success) {\n return addResult\n }\n }\n\n // Execute commit\n const args = ['commit', '-m', command.message]\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * Execute push command\n * EP769: Added force parameter for pushing rebased branches\n */\n private async executePush(\n command: { branch: string; setUpstream?: boolean; force?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Build command\n const args = ['push']\n // EP769: Add --force flag for rebased branches\n if (command.force) {\n args.push('--force')\n }\n if (command.setUpstream) {\n args.push('-u', 'origin', command.branch)\n } else {\n args.push('origin', command.branch)\n }\n\n // Configure git credential helper for GitHub token if provided\n const env = { ...process.env }\n if (options?.githubToken) {\n env.GIT_ASKPASS = 'echo'\n env.GIT_USERNAME = 'x-access-token'\n env.GIT_PASSWORD = options.githubToken\n }\n\n return await this.runGitCommand(args, cwd, { ...options, env })\n }\n\n /**\n * Execute status command\n */\n private async executeStatus(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // EP971: Check if this is a bare repo (git status doesn't work in bare repos)\n try {\n const isBareResult = await execAsync('git rev-parse --is-bare-repository', { cwd, timeout: 5000 })\n if (isBareResult.stdout.trim() === 'true') {\n // In bare repo, get current branch from HEAD ref\n const headResult = await execAsync('git symbolic-ref --short HEAD', { cwd, timeout: 5000 })\n const branchName = headResult.stdout.trim()\n return {\n success: true,\n output: `## ${branchName}`,\n details: {\n uncommittedFiles: [], // No working tree in bare repo\n branchName,\n currentBranch: branchName\n }\n }\n }\n } catch {\n // Not a bare repo or failed to check, continue with normal status\n }\n\n const result = await this.runGitCommand(['status', '--porcelain', '-b'], cwd, options)\n\n if (result.success && result.output) {\n const statusInfo = parseGitStatus(result.output)\n return {\n success: true,\n output: result.output,\n details: {\n uncommittedFiles: statusInfo.uncommittedFiles,\n branchName: statusInfo.currentBranch\n }\n }\n }\n\n return result\n }\n\n /**\n * Execute pull command\n */\n private async executePull(\n command: { branch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name if provided\n if (command.branch) {\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n }\n\n // Check for uncommitted changes first\n const statusResult = await this.executeStatus(cwd, options)\n if (statusResult.success && statusResult.details?.uncommittedFiles?.length) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n details: {\n uncommittedFiles: statusResult.details.uncommittedFiles\n }\n }\n }\n\n // Build command\n const args = ['pull']\n if (command.branch) {\n args.push('origin', command.branch)\n }\n\n const result = await this.runGitCommand(args, cwd, options)\n\n // Check for merge conflicts\n if (!result.success && result.output) {\n const conflicts = parseMergeConflicts(result.output)\n if (conflicts.length > 0) {\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: result.output,\n details: {\n conflictingFiles: conflicts\n }\n }\n }\n }\n\n return result\n }\n\n /**\n * Execute delete_branch command\n */\n private async executeDeleteBranch(\n command: { branch: string; force?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Build command\n const args = ['branch']\n args.push(command.force ? '-D' : '-d')\n args.push(command.branch)\n\n return await this.runGitCommand(args, cwd, options)\n }\n\n /**\n * EP597: Execute branch_exists command\n * Checks if a branch exists locally and/or remotely\n */\n private async executeBranchExists(\n command: { branch: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n try {\n let isLocal = false\n let isRemote = false\n\n // Check local branches\n try {\n const { stdout: localBranches } = await execAsync('git branch --list', { cwd, timeout: options?.timeout || 10000 })\n isLocal = localBranches.split('\\n').some(line => {\n const branchName = line.replace(/^\\*?\\s*/, '').trim()\n return branchName === command.branch\n })\n } catch {\n // Ignore errors - branch doesn't exist locally\n }\n\n // Check remote branches\n try {\n const { stdout: remoteBranches } = await execAsync(\n `git ls-remote --heads origin ${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n isRemote = remoteBranches.trim().length > 0\n } catch {\n // Ignore errors - can't check remote (might be network issue)\n }\n\n const branchExists = isLocal || isRemote\n\n return {\n success: true,\n output: branchExists ? `Branch ${command.branch} exists` : `Branch ${command.branch} does not exist`,\n details: {\n branchName: command.branch,\n branchExists,\n isLocal,\n isRemote\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check branch existence'\n }\n }\n }\n\n /**\n * EP597: Execute branch_has_commits command\n * Checks if a branch has commits ahead of the base branch (default: main)\n */\n private async executeBranchHasCommits(\n command: { branch: string; baseBranch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n const baseBranch = command.baseBranch || 'main'\n\n try {\n // Use git cherry to find commits unique to the branch\n // This shows commits on branch that aren't on base\n const { stdout } = await execAsync(\n `git cherry origin/${baseBranch} ${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n\n // git cherry shows lines starting with + for unique commits\n const uniqueCommits = stdout.trim().split('\\n').filter(line => line.startsWith('+'))\n const hasCommits = uniqueCommits.length > 0\n\n return {\n success: true,\n output: hasCommits\n ? `Branch ${command.branch} has ${uniqueCommits.length} commits ahead of ${baseBranch}`\n : `Branch ${command.branch} has no commits ahead of ${baseBranch}`,\n details: {\n branchName: command.branch,\n hasCommits\n }\n }\n } catch (error: any) {\n // If git cherry fails (branch not found, etc.), try alternative method\n try {\n // Alternative: count commits with rev-list\n const { stdout } = await execAsync(\n `git rev-list --count origin/${baseBranch}..${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n\n const commitCount = parseInt(stdout.trim(), 10)\n const hasCommits = commitCount > 0\n\n return {\n success: true,\n output: hasCommits\n ? `Branch ${command.branch} has ${commitCount} commits ahead of ${baseBranch}`\n : `Branch ${command.branch} has no commits ahead of ${baseBranch}`,\n details: {\n branchName: command.branch,\n hasCommits\n }\n }\n } catch {\n // Both methods failed - branch likely doesn't exist or isn't tracked\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: error.message || `Failed to check commits for branch ${command.branch}`\n }\n }\n }\n }\n\n /**\n * EP598: Execute main branch check - returns current branch, uncommitted files, and unpushed commits\n */\n\n /**\n * EP831: Find branch by prefix pattern\n * Searches local and remote branches for one matching the prefix\n */\n private async executeFindBranchByPrefix(\n command: { prefix: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const { stdout } = await execAsync(\n 'git branch -a',\n { cwd, timeout: options?.timeout || 10000 }\n )\n\n const prefix = command.prefix\n const branches = stdout.split('\\n')\n .map(line => line.replace(/^[\\s*]*/, '').replace('remotes/origin/', '').trim())\n .filter(branch => branch && !branch.includes('->'))\n\n const matchingBranch = branches.find(branch => branch.startsWith(prefix))\n\n return {\n success: true,\n output: matchingBranch || '',\n details: {\n branchName: matchingBranch || undefined,\n branchExists: !!matchingBranch\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to find branch'\n }\n }\n }\n\n private async executeMainBranchCheck(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Get current branch\n let currentBranch = ''\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, timeout: options?.timeout || 10000 })\n currentBranch = stdout.trim()\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to get current branch'\n }\n }\n\n // Get uncommitted files\n let uncommittedFiles: string[] = []\n try {\n const { stdout } = await execAsync('git status --porcelain', { cwd, timeout: options?.timeout || 10000 })\n if (stdout) {\n uncommittedFiles = stdout.split('\\n').filter(line => line.trim()).map(line => {\n const parts = line.trim().split(/\\s+/)\n return parts.slice(1).join(' ')\n })\n }\n } catch {\n // Ignore errors - just means no uncommitted changes\n }\n\n // Get unpushed commits (only if on main branch)\n let localCommits: Array<{ sha: string; message: string; author: string }> = []\n if (currentBranch === 'main') {\n try {\n const { stdout } = await execAsync('git log origin/main..HEAD --format=\"%H|%s|%an\"', { cwd, timeout: options?.timeout || 10000 })\n if (stdout) {\n localCommits = stdout.split('\\n').filter(line => line.trim()).map(line => {\n const [sha, message, author] = line.split('|')\n return {\n sha: sha ? sha.substring(0, 8) : '',\n message: message || '',\n author: author || ''\n }\n })\n }\n } catch {\n // Ignore errors - might not have origin/main or no remote\n }\n }\n\n return {\n success: true,\n output: `Branch: ${currentBranch}, Uncommitted: ${uncommittedFiles.length}, Unpushed: ${localCommits.length}`,\n details: {\n currentBranch,\n uncommittedFiles,\n localCommits\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check main branch'\n }\n }\n }\n\n /**\n * EP599: Execute get_commits command\n * Returns commits for a branch with pushed/unpushed status\n */\n private async executeGetCommits(\n command: { branch: string; limit?: number; baseBranch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n const limit = command.limit || 10\n const baseBranch = command.baseBranch || 'main'\n\n try {\n // Get commits unique to this branch (not in main)\n let stdout: string\n try {\n const result = await execAsync(\n `git log ${baseBranch}..\"${command.branch}\" --pretty=format:\"%H|%an|%ae|%aI|%s\" -n ${limit} --`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n stdout = result.stdout\n } catch (error) {\n // Fallback: if comparison fails, show all commits on this branch\n try {\n const result = await execAsync(\n `git log \"${command.branch}\" --pretty=format:\"%H|%an|%ae|%aI|%s\" -n ${limit} --`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n stdout = result.stdout\n } catch (branchError) {\n // Branch doesn't exist locally\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: `Branch ${command.branch} not found locally`\n }\n }\n }\n\n if (!stdout.trim()) {\n return {\n success: true,\n output: 'No commits found',\n details: {\n commits: []\n }\n }\n }\n\n // Parse commits\n const commitLines = stdout.trim().split('\\n')\n\n // Check which commits have been pushed to remote\n let remoteShas: Set<string> = new Set()\n try {\n const { stdout: remoteCommits } = await execAsync(\n `git log \"origin/${command.branch}\" --pretty=format:\"%H\" -n ${limit} --`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n remoteShas = new Set(remoteCommits.trim().split('\\n').filter(Boolean))\n } catch {\n // Remote branch doesn't exist - all commits are local/unpushed\n }\n\n const commits = commitLines.map((line) => {\n const [sha, authorName, authorEmail, date, ...messageParts] = line.split('|')\n const message = messageParts.join('|') // Handle pipes in commit messages\n const isPushed = remoteShas.has(sha)\n\n return {\n sha,\n message,\n authorName,\n authorEmail,\n date,\n isPushed\n }\n })\n\n return {\n success: true,\n output: `Found ${commits.length} commits`,\n details: {\n commits\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to get commits'\n }\n }\n }\n\n // ========================================\n // EP599: Advanced operations for move-to-module and discard-main-changes\n // ========================================\n\n /**\n * Execute git stash operations\n */\n private async executeStash(\n command: { operation: 'push' | 'pop' | 'drop' | 'list'; message?: string; includeUntracked?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args: string[] = ['stash']\n\n switch (command.operation) {\n case 'push':\n args.push('push')\n if (command.includeUntracked) {\n args.push('--include-untracked')\n }\n if (command.message) {\n args.push('-m', command.message)\n }\n break\n case 'pop':\n args.push('pop')\n break\n case 'drop':\n args.push('drop')\n break\n case 'list':\n args.push('list')\n break\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Stash operation failed'\n }\n }\n }\n\n /**\n * Execute git reset\n */\n private async executeReset(\n command: { mode: 'soft' | 'mixed' | 'hard'; target?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['reset', `--${command.mode}`]\n if (command.target) {\n args.push(command.target)\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Reset failed'\n }\n }\n }\n\n /**\n * Execute git merge\n */\n private async executeMerge(\n command: { branch: string; strategy?: 'ours' | 'theirs'; noEdit?: boolean; abort?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n if (command.abort) {\n return await this.runGitCommand(['merge', '--abort'], cwd, options)\n }\n\n // Validate branch name\n const branchValidation = validateBranchName(command.branch)\n if (!branchValidation.valid) {\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: branchValidation.error || 'Invalid branch name'\n }\n }\n\n const args = ['merge', command.branch]\n\n if (command.strategy === 'ours') {\n args.push('--strategy=ours')\n } else if (command.strategy === 'theirs') {\n args.push('--strategy-option=theirs')\n }\n\n if (command.noEdit) {\n args.push('--no-edit')\n }\n\n const result = await this.runGitCommand(args, cwd, options)\n\n // Check for merge conflicts\n if (!result.success && result.output?.includes('CONFLICT')) {\n result.details = result.details || {}\n result.details.mergeConflicts = true\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: error.message || 'Merge failed'\n }\n }\n }\n\n /**\n * Execute git cherry-pick\n */\n private async executeCherryPick(\n command: { sha: string; abort?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n if (command.abort) {\n return await this.runGitCommand(['cherry-pick', '--abort'], cwd, options)\n }\n\n const result = await this.runGitCommand(['cherry-pick', command.sha], cwd, options)\n\n // Check for cherry-pick conflicts\n if (!result.success && result.output?.includes('CONFLICT')) {\n result.details = result.details || {}\n result.details.cherryPickConflicts = true\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: error.message || 'Cherry-pick failed'\n }\n }\n }\n\n /**\n * Execute git clean\n */\n private async executeClean(\n command: { force?: boolean; directories?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['clean']\n\n if (command.force) {\n args.push('-f')\n }\n if (command.directories) {\n args.push('-d')\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Clean failed'\n }\n }\n }\n\n /**\n * Execute git add\n */\n private async executeAdd(\n command: { files?: string[]; all?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['add']\n\n if (command.all) {\n args.push('-A')\n } else if (command.files && command.files.length > 0) {\n // Validate file paths\n const validation = validateFilePaths(command.files)\n if (!validation.valid) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: validation.error || 'Invalid file paths'\n }\n }\n args.push(...command.files)\n } else {\n args.push('-A') // Default to all\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Add failed'\n }\n }\n }\n\n /**\n * Execute git fetch\n */\n private async executeFetch(\n command: { remote?: string; branch?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const args = ['fetch']\n\n if (command.remote) {\n args.push(command.remote)\n if (command.branch) {\n args.push(command.branch)\n }\n } else {\n args.push('origin')\n }\n\n return await this.runGitCommand(args, cwd, options)\n } catch (error: any) {\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: error.message || 'Fetch failed'\n }\n }\n }\n\n // ========================================\n // EP599-3: Composite operations\n // ========================================\n\n /**\n * Execute move_to_module - composite operation\n * Moves commits/changes to a module branch with conflict handling\n */\n private async executeMoveToModule(\n command: { targetBranch: string; commitShas?: string[]; conflictResolution?: 'ours' | 'theirs' },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n const { targetBranch, commitShas, conflictResolution } = command\n let hasStash = false\n const cherryPickedCommits: string[] = []\n\n try {\n // Step 1: Get current branch\n const { stdout: currentBranchOut } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd })\n const currentBranch = currentBranchOut.trim()\n\n // Step 2: Stash uncommitted changes\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd })\n if (statusOutput.trim()) {\n try {\n await execAsync('git add -A', { cwd })\n const { stdout: stashHash } = await execAsync('git stash create -m \"episoda-move-to-module\"', { cwd })\n if (stashHash && stashHash.trim()) {\n await execAsync(`git stash store -m \"episoda-move-to-module\" ${stashHash.trim()}`, { cwd })\n await execAsync('git reset --hard HEAD', { cwd })\n hasStash = true\n }\n } catch (stashError: any) {\n // Continue without stashing\n }\n }\n\n // Step 3: Switch to main if we have commits to move\n if (commitShas && commitShas.length > 0 && currentBranch !== 'main' && currentBranch !== 'master') {\n await execAsync('git checkout main', { cwd })\n }\n\n // Step 4: Create or checkout target branch\n let branchExists = false\n try {\n await execAsync(`git rev-parse --verify ${targetBranch}`, { cwd })\n branchExists = true\n } catch {\n branchExists = false\n }\n\n if (!branchExists) {\n await execAsync(`git checkout -b ${targetBranch}`, { cwd })\n } else {\n await execAsync(`git checkout ${targetBranch}`, { cwd })\n\n // Try to merge changes from main\n if (currentBranch === 'main' || currentBranch === 'master') {\n try {\n const mergeStrategy = conflictResolution === 'ours' ? '--strategy=ours' :\n conflictResolution === 'theirs' ? '--strategy-option=theirs' : ''\n await execAsync(`git merge ${currentBranch} ${mergeStrategy} --no-edit`, { cwd })\n } catch (mergeError: any) {\n // Check for conflicts\n const { stdout: conflictStatus } = await execAsync('git status --porcelain', { cwd })\n if (conflictStatus.includes('UU ') || conflictStatus.includes('AA ') || conflictStatus.includes('DD ')) {\n const { stdout: conflictFiles } = await execAsync('git diff --name-only --diff-filter=U', { cwd })\n const conflictedFiles = conflictFiles.trim().split('\\n').filter(Boolean)\n\n if (conflictResolution) {\n // Auto-resolve conflicts\n for (const file of conflictedFiles) {\n await execAsync(`git checkout --${conflictResolution} \"${file}\"`, { cwd })\n await execAsync(`git add \"${file}\"`, { cwd })\n }\n await execAsync('git commit --no-edit', { cwd })\n } else {\n // Abort merge and return conflict error\n await execAsync('git merge --abort', { cwd })\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: 'Merge conflicts detected',\n details: {\n hasConflicts: true,\n conflictedFiles,\n movedToBranch: targetBranch\n }\n }\n }\n }\n }\n }\n }\n\n // Step 5: Cherry-pick commits if provided\n if (commitShas && commitShas.length > 0 && (currentBranch === 'main' || currentBranch === 'master')) {\n for (const sha of commitShas) {\n try {\n // Check if commit already exists in branch\n const { stdout: logOutput } = await execAsync(\n `git log --format=%H ${targetBranch} | grep ${sha}`,\n { cwd }\n ).catch(() => ({ stdout: '' }))\n\n if (!logOutput.trim()) {\n await execAsync(`git cherry-pick ${sha}`, { cwd })\n cherryPickedCommits.push(sha)\n }\n } catch (err: any) {\n await execAsync('git cherry-pick --abort', { cwd }).catch(() => {})\n }\n }\n\n // Reset main to origin/main\n await execAsync('git checkout main', { cwd })\n await execAsync('git reset --hard origin/main', { cwd })\n await execAsync(`git checkout ${targetBranch}`, { cwd })\n }\n\n // Step 6: Apply stashed changes\n if (hasStash) {\n try {\n await execAsync('git stash pop', { cwd })\n } catch (stashError: any) {\n // Check for stash conflicts\n const { stdout: conflictStatus } = await execAsync('git status --porcelain', { cwd })\n if (conflictStatus.includes('UU ') || conflictStatus.includes('AA ')) {\n if (conflictResolution) {\n const { stdout: conflictFiles } = await execAsync('git diff --name-only --diff-filter=U', { cwd })\n const conflictedFiles = conflictFiles.trim().split('\\n').filter(Boolean)\n for (const file of conflictedFiles) {\n await execAsync(`git checkout --${conflictResolution} \"${file}\"`, { cwd })\n await execAsync(`git add \"${file}\"`, { cwd })\n }\n }\n }\n }\n }\n\n return {\n success: true,\n output: `Successfully moved to branch ${targetBranch}`,\n details: {\n movedToBranch: targetBranch,\n cherryPickedCommits,\n currentBranch: targetBranch\n }\n }\n } catch (error: any) {\n // Try to restore stash if something went wrong\n if (hasStash) {\n try {\n const { stdout: stashList } = await execAsync('git stash list', { cwd })\n if (stashList.includes('episoda-move-to-module')) {\n await execAsync('git stash pop', { cwd })\n }\n } catch (e) {\n // Ignore errors restoring stash\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Move to module failed'\n }\n }\n }\n\n /**\n * Execute discard_main_changes - composite operation\n * Discards all uncommitted files and local commits on main branch\n */\n private async executeDiscardMainChanges(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Step 1: Verify we're on main/master\n const { stdout: currentBranchOut } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd })\n const branch = currentBranchOut.trim()\n\n if (branch !== 'main' && branch !== 'master') {\n return {\n success: false,\n error: 'BRANCH_NOT_FOUND',\n output: `Cannot discard changes - not on main branch. Current branch: ${branch}`\n }\n }\n\n let discardedFiles = 0\n\n // Step 2: Stash uncommitted changes (will be dropped)\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd })\n if (statusOutput.trim()) {\n try {\n await execAsync('git stash --include-untracked', { cwd })\n discardedFiles = statusOutput.trim().split('\\n').length\n } catch (stashError: any) {\n // Continue - might be nothing to stash\n }\n }\n\n // Step 3: Fetch and reset to origin\n await execAsync('git fetch origin', { cwd })\n await execAsync(`git reset --hard origin/${branch}`, { cwd })\n\n // Step 4: Clean untracked files\n try {\n await execAsync('git clean -fd', { cwd })\n } catch (cleanError: any) {\n // Non-critical\n }\n\n // Step 5: Drop the stash\n try {\n await execAsync('git stash drop', { cwd })\n } catch (dropError: any) {\n // Stash might not exist\n }\n\n return {\n success: true,\n output: `Successfully discarded all changes and reset to origin/${branch}`,\n details: {\n currentBranch: branch,\n discardedFiles\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Discard main changes failed'\n }\n }\n }\n\n // ========================================\n // EP523: Branch sync operations\n // ========================================\n\n /**\n * EP523: Get sync status of a branch relative to main\n * Returns how many commits behind/ahead the branch is\n */\n private async executeSyncStatus(\n command: { branch: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Fetch latest from remote to get accurate counts\n try {\n await execAsync('git fetch origin', { cwd, timeout: options?.timeout || 30000 })\n } catch (fetchError: any) {\n // Network error - return what we can determine locally\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: 'Unable to fetch from remote. Check your network connection.'\n }\n }\n\n let commitsBehind = 0\n let commitsAhead = 0\n\n // Count commits the branch is BEHIND main (main has commits branch doesn't)\n try {\n const { stdout: behindOutput } = await execAsync(\n `git rev-list --count ${command.branch}..origin/main`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n commitsBehind = parseInt(behindOutput.trim(), 10) || 0\n } catch {\n // Branch might not exist or no common ancestor\n commitsBehind = 0\n }\n\n // Count commits the branch is AHEAD of main (branch has commits main doesn't)\n try {\n const { stdout: aheadOutput } = await execAsync(\n `git rev-list --count origin/main..${command.branch}`,\n { cwd, timeout: options?.timeout || 10000 }\n )\n commitsAhead = parseInt(aheadOutput.trim(), 10) || 0\n } catch {\n // Branch might not exist or no common ancestor\n commitsAhead = 0\n }\n\n const isBehind = commitsBehind > 0\n const isAhead = commitsAhead > 0\n const needsSync = isBehind\n\n return {\n success: true,\n output: isBehind\n ? `Branch ${command.branch} is ${commitsBehind} commit(s) behind main`\n : `Branch ${command.branch} is up to date with main`,\n details: {\n branchName: command.branch,\n commitsBehind,\n commitsAhead,\n isBehind,\n isAhead,\n needsSync\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check sync status'\n }\n }\n }\n\n /**\n * EP523: Sync local main branch with remote\n * Used before creating new branches to ensure we branch from latest main\n */\n private async executeSyncMain(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Get current branch to restore later if needed\n let currentBranch = ''\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, timeout: 5000 })\n currentBranch = stdout.trim()\n } catch {\n // Ignore - might be detached HEAD\n }\n\n // Fetch latest from remote\n try {\n await execAsync('git fetch origin main', { cwd, timeout: options?.timeout || 30000 })\n } catch (fetchError: any) {\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: 'Unable to fetch from remote. Check your network connection.'\n }\n }\n\n // Check if we need to switch to main\n const needsSwitch = currentBranch !== 'main' && currentBranch !== ''\n\n if (needsSwitch) {\n // Check for uncommitted changes first\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd, timeout: 5000 })\n if (statusOutput.trim()) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n output: 'Cannot sync main: you have uncommitted changes. Commit or stash them first.',\n details: {\n uncommittedFiles: statusOutput.trim().split('\\n').map(line => line.slice(3))\n }\n }\n }\n\n // Switch to main\n await execAsync('git checkout main', { cwd, timeout: options?.timeout || 10000 })\n }\n\n // Pull latest main\n try {\n await execAsync('git pull origin main', { cwd, timeout: options?.timeout || 30000 })\n } catch (pullError: any) {\n // Check for conflicts\n if (pullError.message?.includes('CONFLICT') || pullError.stderr?.includes('CONFLICT')) {\n // Abort the merge\n await execAsync('git merge --abort', { cwd, timeout: 5000 }).catch(() => {})\n return {\n success: false,\n error: 'MERGE_CONFLICT',\n output: 'Conflict while syncing main. This is unexpected - main should not have local commits.'\n }\n }\n throw pullError\n }\n\n // Switch back to original branch if we switched\n if (needsSwitch && currentBranch) {\n await execAsync(`git checkout \"${currentBranch}\"`, { cwd, timeout: options?.timeout || 10000 })\n }\n\n return {\n success: true,\n output: 'Successfully synced main with remote',\n details: {\n currentBranch: needsSwitch ? currentBranch : 'main'\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to sync main'\n }\n }\n }\n\n /**\n * EP523: Rebase a branch onto main\n * Used when resuming work on a branch that's behind main\n */\n private async executeRebaseBranch(\n command: { branch: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Check for uncommitted changes\n const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd, timeout: 5000 })\n if (statusOutput.trim()) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n output: 'Cannot rebase: you have uncommitted changes. Commit or stash them first.',\n details: {\n uncommittedFiles: statusOutput.trim().split('\\n').map(line => line.slice(3))\n }\n }\n }\n\n // Get current branch\n const { stdout: currentBranchOut } = await execAsync('git branch --show-current', { cwd, timeout: 5000 })\n const currentBranch = currentBranchOut.trim()\n\n // Ensure we're on the target branch\n if (currentBranch !== command.branch) {\n await execAsync(`git checkout \"${command.branch}\"`, { cwd, timeout: options?.timeout || 10000 })\n }\n\n // Fetch latest main\n await execAsync('git fetch origin main', { cwd, timeout: options?.timeout || 30000 })\n\n // Perform rebase\n try {\n await execAsync('git rebase origin/main', { cwd, timeout: options?.timeout || 60000 })\n } catch (rebaseError: any) {\n const errorOutput = (rebaseError.stderr || '') + (rebaseError.stdout || '')\n\n // Check for conflicts\n if (errorOutput.includes('CONFLICT') || errorOutput.includes('could not apply')) {\n // Get conflicting files\n let conflictFiles: string[] = []\n try {\n const { stdout: conflictOutput } = await execAsync(\n 'git diff --name-only --diff-filter=U',\n { cwd, timeout: 5000 }\n )\n conflictFiles = conflictOutput.trim().split('\\n').filter(Boolean)\n } catch {\n // Ignore - try alternate method\n try {\n const { stdout: statusOut } = await execAsync('git status --porcelain', { cwd, timeout: 5000 })\n conflictFiles = statusOut\n .trim()\n .split('\\n')\n .filter(line => line.startsWith('UU ') || line.startsWith('AA ') || line.startsWith('DD '))\n .map(line => line.slice(3))\n } catch {\n // Couldn't get conflict files\n }\n }\n\n return {\n success: false,\n error: 'REBASE_CONFLICT',\n output: `Rebase conflict in ${conflictFiles.length} file(s). Resolve conflicts then use rebase_continue, or use rebase_abort to cancel.`,\n details: {\n inRebase: true,\n rebaseConflicts: conflictFiles,\n hasConflicts: true,\n conflictedFiles: conflictFiles\n }\n }\n }\n\n throw rebaseError\n }\n\n return {\n success: true,\n output: `Successfully rebased ${command.branch} onto main`,\n details: {\n branchName: command.branch,\n inRebase: false\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Rebase failed'\n }\n }\n }\n\n /**\n * EP523: Abort an in-progress rebase\n * Returns to the state before rebase was started\n */\n private async executeRebaseAbort(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n await execAsync('git rebase --abort', { cwd, timeout: options?.timeout || 10000 })\n\n return {\n success: true,\n output: 'Rebase aborted. Your branch has been restored to its previous state.',\n details: {\n inRebase: false\n }\n }\n } catch (error: any) {\n // Check if there's no rebase in progress\n if (error.message?.includes('No rebase in progress') || error.stderr?.includes('No rebase in progress')) {\n return {\n success: true,\n output: 'No rebase in progress.',\n details: {\n inRebase: false\n }\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to abort rebase'\n }\n }\n }\n\n /**\n * EP523: Continue a paused rebase after conflicts are resolved\n */\n private async executeRebaseContinue(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Stage all resolved files\n await execAsync('git add -A', { cwd, timeout: 5000 })\n\n // Continue the rebase\n await execAsync('git rebase --continue', { cwd, timeout: options?.timeout || 60000 })\n\n return {\n success: true,\n output: 'Rebase continued successfully.',\n details: {\n inRebase: false\n }\n }\n } catch (error: any) {\n const errorOutput = (error.stderr || '') + (error.stdout || '')\n\n // Check if there are still conflicts\n if (errorOutput.includes('CONFLICT') || errorOutput.includes('could not apply')) {\n // Get remaining conflict files\n let conflictFiles: string[] = []\n try {\n const { stdout: conflictOutput } = await execAsync(\n 'git diff --name-only --diff-filter=U',\n { cwd, timeout: 5000 }\n )\n conflictFiles = conflictOutput.trim().split('\\n').filter(Boolean)\n } catch {\n // Ignore\n }\n\n return {\n success: false,\n error: 'REBASE_CONFLICT',\n output: 'More conflicts encountered. Resolve them and try again.',\n details: {\n inRebase: true,\n rebaseConflicts: conflictFiles,\n hasConflicts: true,\n conflictedFiles: conflictFiles\n }\n }\n }\n\n // Check if there's no rebase in progress\n if (errorOutput.includes('No rebase in progress')) {\n return {\n success: true,\n output: 'No rebase in progress.',\n details: {\n inRebase: false\n }\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to continue rebase'\n }\n }\n }\n\n /**\n * EP523: Check if a rebase is currently in progress\n */\n private async executeRebaseStatus(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Check for rebase-merge directory (indicates rebase in progress)\n let inRebase = false\n let rebaseConflicts: string[] = []\n\n try {\n const { stdout: gitDir } = await execAsync('git rev-parse --git-dir', { cwd, timeout: 5000 })\n const gitDirPath = gitDir.trim()\n\n // Check for rebase directories\n const fs = await import('fs').then(m => m.promises)\n const rebaseMergePath = `${gitDirPath}/rebase-merge`\n const rebaseApplyPath = `${gitDirPath}/rebase-apply`\n\n try {\n await fs.access(rebaseMergePath)\n inRebase = true\n } catch {\n try {\n await fs.access(rebaseApplyPath)\n inRebase = true\n } catch {\n inRebase = false\n }\n }\n } catch {\n // If we can't determine, check via status\n try {\n const { stdout: statusOutput } = await execAsync('git status', { cwd, timeout: 5000 })\n inRebase = statusOutput.includes('rebase in progress') ||\n statusOutput.includes('interactive rebase in progress') ||\n statusOutput.includes('You are currently rebasing')\n } catch {\n inRebase = false\n }\n }\n\n // If in rebase, get conflict files\n if (inRebase) {\n try {\n const { stdout: conflictOutput } = await execAsync(\n 'git diff --name-only --diff-filter=U',\n { cwd, timeout: 5000 }\n )\n rebaseConflicts = conflictOutput.trim().split('\\n').filter(Boolean)\n } catch {\n // No conflicts or couldn't get them\n }\n }\n\n return {\n success: true,\n output: inRebase\n ? `Rebase in progress with ${rebaseConflicts.length} conflicting file(s)`\n : 'No rebase in progress',\n details: {\n inRebase,\n rebaseConflicts,\n hasConflicts: rebaseConflicts.length > 0\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to check rebase status'\n }\n }\n }\n\n // ========================================\n // EP944: Worktree operations\n // ========================================\n\n /**\n * EP944: Add a new worktree for a branch\n * Creates a new working tree at the specified path\n */\n private async executeWorktreeAdd(\n command: { path: string; branch: string; create?: boolean; startPoint?: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Validate branch name\n const validation = validateBranchName(command.branch)\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error || 'UNKNOWN_ERROR'\n }\n }\n\n // Check if path already exists\n const fs = await import('fs').then(m => m.promises)\n try {\n await fs.access(command.path)\n return {\n success: false,\n error: 'WORKTREE_EXISTS',\n output: `Worktree already exists at path: ${command.path}`\n }\n } catch {\n // Path doesn't exist, which is what we want\n }\n\n // Fetch latest refs from remote to ensure branch exists\n // This is especially important for branches created after the initial clone\n try {\n await this.runGitCommand(['fetch', '--all', '--prune'], cwd, options)\n } catch {\n // Fetch failure is non-fatal - branch may be local or fetch may not be needed\n }\n\n // Build command\n const args = ['worktree', 'add']\n if (command.create) {\n args.push('-b', command.branch, command.path)\n // EP996: Add start point for new branch to ensure fresh base\n if (command.startPoint) {\n args.push(command.startPoint)\n }\n } else {\n args.push(command.path, command.branch)\n }\n\n const result = await this.runGitCommand(args, cwd, options)\n\n if (result.success) {\n return {\n success: true,\n output: `Created worktree at ${command.path} for branch ${command.branch}`,\n details: {\n worktreePath: command.path,\n branchName: command.branch\n }\n }\n }\n\n // Check for specific error conditions\n if (result.output?.includes('already checked out')) {\n return {\n success: false,\n error: 'BRANCH_IN_USE',\n output: `Branch '${command.branch}' is already checked out in another worktree`\n }\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to add worktree'\n }\n }\n }\n\n /**\n * EP944: Remove a worktree\n * Removes the working tree at the specified path\n */\n private async executeWorktreeRemove(\n command: { path: string; force?: boolean },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // Check if path exists\n const fs = await import('fs').then(m => m.promises)\n try {\n await fs.access(command.path)\n } catch {\n return {\n success: false,\n error: 'WORKTREE_NOT_FOUND',\n output: `Worktree not found at path: ${command.path}`\n }\n }\n\n // Check for uncommitted changes (unless force)\n if (!command.force) {\n try {\n const { stdout } = await execAsync('git status --porcelain', {\n cwd: command.path,\n timeout: options?.timeout || 10000\n })\n if (stdout.trim()) {\n return {\n success: false,\n error: 'UNCOMMITTED_CHANGES',\n output: 'Worktree has uncommitted changes. Use force to remove anyway.',\n details: {\n uncommittedFiles: stdout.trim().split('\\n').map(line => line.slice(3))\n }\n }\n }\n } catch {\n // If we can't check status, continue with removal\n }\n }\n\n // Build command\n const args = ['worktree', 'remove']\n if (command.force) {\n args.push('--force')\n }\n args.push(command.path)\n\n const result = await this.runGitCommand(args, cwd, options)\n\n if (result.success) {\n // EP1002: Clean up any remaining gitignored files (e.g., .next/, dist/, node_modules/)\n // git worktree remove only removes git-tracked files, leaving gitignored directories behind\n try {\n await fs.rm(command.path, { recursive: true, force: true })\n } catch {\n // Directory may already be gone if git removed it completely, ignore errors\n }\n\n return {\n success: true,\n output: `Removed worktree at ${command.path}`,\n details: {\n worktreePath: command.path\n }\n }\n }\n\n // Check for locked worktree\n if (result.output?.includes('locked')) {\n return {\n success: false,\n error: 'WORKTREE_LOCKED',\n output: `Worktree at ${command.path} is locked`\n }\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to remove worktree'\n }\n }\n }\n\n /**\n * EP944: List all worktrees\n * Returns information about all worktrees in the repository\n */\n private async executeWorktreeList(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const { stdout } = await execAsync('git worktree list --porcelain', {\n cwd,\n timeout: options?.timeout || 10000\n })\n\n const worktrees: Array<{\n path: string\n branch: string\n commit: string\n locked?: boolean\n prunable?: boolean\n }> = []\n\n // Parse porcelain output\n // Format:\n // worktree /path/to/worktree\n // HEAD abc123...\n // branch refs/heads/branch-name\n // (blank line)\n const lines = stdout.trim().split('\\n')\n let current: Partial<{\n path: string\n branch: string\n commit: string\n locked: boolean\n prunable: boolean\n }> = {}\n\n for (const line of lines) {\n if (line.startsWith('worktree ')) {\n current.path = line.slice(9)\n } else if (line.startsWith('HEAD ')) {\n current.commit = line.slice(5)\n } else if (line.startsWith('branch ')) {\n // Extract branch name from refs/heads/branch-name\n const refPath = line.slice(7)\n current.branch = refPath.replace('refs/heads/', '')\n } else if (line === 'locked') {\n current.locked = true\n } else if (line === 'prunable') {\n current.prunable = true\n } else if (line.startsWith('detached')) {\n current.branch = 'HEAD (detached)'\n } else if (line === '' && current.path) {\n // End of entry\n worktrees.push({\n path: current.path,\n branch: current.branch || 'unknown',\n commit: current.commit || '',\n locked: current.locked,\n prunable: current.prunable\n })\n current = {}\n }\n }\n\n // Don't forget the last entry if output doesn't end with blank line\n if (current.path) {\n worktrees.push({\n path: current.path,\n branch: current.branch || 'unknown',\n commit: current.commit || '',\n locked: current.locked,\n prunable: current.prunable\n })\n }\n\n return {\n success: true,\n output: `Found ${worktrees.length} worktree(s)`,\n details: {\n worktrees\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to list worktrees'\n }\n }\n }\n\n /**\n * EP944: Prune stale worktrees\n * Removes worktree administrative files for worktrees whose directories are missing\n */\n private async executeWorktreePrune(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n // First get list of prunable worktrees\n const listResult = await this.executeWorktreeList(cwd, options)\n const prunableCount = listResult.details?.worktrees?.filter(w => w.prunable).length || 0\n\n // Run prune\n const result = await this.runGitCommand(['worktree', 'prune'], cwd, options)\n\n if (result.success) {\n return {\n success: true,\n output: prunableCount > 0\n ? `Pruned ${prunableCount} stale worktree(s)`\n : 'No stale worktrees to prune',\n details: {\n prunedCount: prunableCount\n }\n }\n }\n\n return result\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to prune worktrees'\n }\n }\n }\n\n /**\n * EP1002: Worktree setup command stub\n * The actual implementation is in the daemon which intercepts this command.\n * This stub exists for type checking and as a fallback.\n */\n private async executeWorktreeSetup(\n command: { path: string; moduleUid: string },\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n // This should be intercepted by the daemon before reaching here\n // If we get here, it means the command was routed incorrectly\n return {\n success: false,\n error: 'NOT_IMPLEMENTED',\n output: 'worktree_setup must be handled by the daemon, not GitExecutor'\n }\n }\n\n /**\n * EP944: Clone a repository as a bare repository\n * Used for worktree-based development setup\n */\n private async executeCloneBare(\n command: { url: string; path: string },\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const fs = await import('fs').then(m => m.promises)\n const path = await import('path')\n\n // Check if path already exists\n try {\n await fs.access(command.path)\n return {\n success: false,\n error: 'BRANCH_ALREADY_EXISTS', // Reusing for path exists\n output: `Directory already exists at path: ${command.path}`\n }\n } catch {\n // Path doesn't exist, which is what we want\n }\n\n // Create parent directory if needed\n const parentDir = path.dirname(command.path)\n try {\n await fs.mkdir(parentDir, { recursive: true })\n } catch {\n // Directory might already exist\n }\n\n // Clone as bare repository\n const { stdout, stderr } = await execAsync(\n `git clone --bare \"${command.url}\" \"${command.path}\"`,\n { timeout: options?.timeout || 120000 } // 2 minutes for clone\n )\n\n return {\n success: true,\n output: `Cloned bare repository to ${command.path}`,\n details: {\n worktreePath: command.path\n }\n }\n } catch (error: any) {\n // Check for auth errors\n if (error.message?.includes('Authentication') || error.message?.includes('Permission denied')) {\n return {\n success: false,\n error: 'AUTH_FAILURE',\n output: 'Authentication failed. Please check your credentials.'\n }\n }\n\n // Check for network errors\n if (error.message?.includes('Could not resolve') || error.message?.includes('unable to access')) {\n return {\n success: false,\n error: 'NETWORK_ERROR',\n output: 'Network error. Please check your connection.'\n }\n }\n\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to clone repository'\n }\n }\n }\n\n /**\n * EP944: Get project info including worktree mode\n * Returns information about the project configuration\n */\n private async executeProjectInfo(\n cwd: string,\n options?: ExecutionOptions\n ): Promise<ExecutionResult> {\n try {\n const fs = await import('fs').then(m => m.promises)\n const path = await import('path')\n\n // EP971: All projects use worktree architecture\n // Walk up to find the project root with .bare and .episoda directories\n let currentPath = cwd\n let projectPath = cwd\n let bareRepoPath: string | undefined\n\n for (let i = 0; i < 10; i++) {\n const bareDir = path.join(currentPath, '.bare')\n const episodaDir = path.join(currentPath, '.episoda')\n\n try {\n await fs.access(bareDir)\n await fs.access(episodaDir)\n\n // Found project root\n projectPath = currentPath\n bareRepoPath = bareDir\n break\n } catch {\n // Not found at this level, check parent\n const parentPath = path.dirname(currentPath)\n if (parentPath === currentPath) {\n break // Reached filesystem root\n }\n currentPath = parentPath\n }\n }\n\n return {\n success: true,\n output: bareRepoPath ? 'Episoda project' : 'Git repository',\n details: {\n projectPath,\n bareRepoPath\n }\n }\n } catch (error: any) {\n return {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error.message || 'Failed to get project info'\n }\n }\n }\n\n /**\n * Run a git command and return structured result\n */\n private async runGitCommand(\n args: string[],\n cwd: string,\n options?: ExecutionOptions & { env?: NodeJS.ProcessEnv }\n ): Promise<ExecutionResult> {\n try {\n // Sanitize arguments\n const sanitizedArgs = sanitizeArgs(args)\n\n // Build command\n const command = ['git', ...sanitizedArgs].join(' ')\n\n // Execute with timeout\n const timeout = options?.timeout || 30000 // 30 second default\n const execOptions = {\n cwd,\n timeout,\n env: options?.env || process.env,\n maxBuffer: 1024 * 1024 * 10 // 10MB buffer\n }\n\n const { stdout, stderr } = await execAsync(command, execOptions)\n\n // Combine output\n const output = (stdout + stderr).trim()\n\n // Extract additional details\n const details: ExecutionResult['details'] = {}\n\n // Try to extract branch name\n const branchName = extractBranchName(output)\n if (branchName) {\n details.branchName = branchName\n }\n\n // Check for detached HEAD\n if (isDetachedHead(output)) {\n details.branchName = 'HEAD (detached)'\n }\n\n return {\n success: true,\n output,\n details: Object.keys(details).length > 0 ? details : undefined\n }\n } catch (error: any) {\n // Parse error\n const stderr = error.stderr || ''\n const stdout = error.stdout || ''\n const exitCode = error.code || 1\n\n // Determine error code\n const errorCode = parseGitError(stderr, stdout, exitCode)\n\n // Extract additional details based on error type\n const details: ExecutionResult['details'] = {\n exitCode\n }\n\n // Parse conflicts if merge conflict\n if (errorCode === 'MERGE_CONFLICT') {\n const conflicts = parseMergeConflicts(stdout + stderr)\n if (conflicts.length > 0) {\n details.conflictingFiles = conflicts\n }\n }\n\n // Parse status for uncommitted changes\n if (errorCode === 'UNCOMMITTED_CHANGES') {\n try {\n const statusResult = await this.executeStatus(cwd, options)\n if (statusResult.details?.uncommittedFiles) {\n details.uncommittedFiles = statusResult.details.uncommittedFiles\n }\n } catch {\n // Ignore errors when getting status\n }\n }\n\n return {\n success: false,\n error: errorCode,\n output: (stdout + stderr).trim(),\n details\n }\n }\n }\n\n /**\n * Validate that git is installed\n */\n private async validateGitInstalled(): Promise<boolean> {\n try {\n await execAsync('git --version', { timeout: 5000 })\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Check if directory is a git repository\n */\n private async isGitRepository(cwd: string): Promise<boolean> {\n try {\n await execAsync('git rev-parse --git-dir', { cwd, timeout: 5000 })\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Detect the git working directory (repository root)\n * @returns Path to the git repository root\n */\n private async detectWorkingDirectory(startPath?: string): Promise<string | null> {\n try {\n const { stdout } = await execAsync('git rev-parse --show-toplevel', {\n cwd: startPath || process.cwd(),\n timeout: 5000\n })\n return stdout.trim()\n } catch {\n return null\n }\n }\n}\n","/**\n * Version management for @episoda/core\n *\n * EP812: Updated to be bundle-safe. When bundled into CLI by tsup,\n * the package.json path lookup fails because __dirname changes.\n * Now tries package.json first, falls back to hardcoded version.\n *\n * IMPORTANT: Keep FALLBACK_VERSION in sync with package.json version!\n * This should be updated when running `npm version` in the core package.\n */\n\nimport { readFileSync, existsSync } from 'fs'\nimport { join } from 'path'\n\n// Fallback version for bundled usage (update with package.json!)\nconst FALLBACK_VERSION = '0.1.11'\n\nfunction getVersion(): string {\n try {\n // Try to read from package.json (works when running from source)\n const packageJsonPath = join(__dirname, '..', 'package.json')\n if (existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))\n return packageJson.version\n }\n } catch {\n // Bundled or path doesn't exist - use fallback\n }\n return FALLBACK_VERSION\n}\n\nexport const VERSION: string = getVersion()\n","/**\n * Episoda WebSocket Client\n *\n * EP589-9: Implemented with comprehensive error handling and reconnection logic\n *\n * Provides reliable WebSocket connection to episoda.dev CLI gateway with:\n * - Automatic reconnection with exponential backoff\n * - Heartbeat/ping-pong to keep connection alive\n * - Event-driven architecture for handling server commands\n * - Graceful error handling and recovery\n */\n\nimport WS from 'ws'\nimport https from 'https'\nimport { ServerMessage, ClientMessage, ConnectionStatus } from './command-protocol'\nimport { VERSION } from './version'\n\n// EP801: Create HTTPS agent that forces IPv4 to avoid IPv6 timeout issues\n// Many networks have IPv6 configured but not fully routable, causing connection failures\nconst ipv4Agent = new https.Agent({ family: 4 })\n\n// EP701: Client-side events emitted by EpisodaClient\nexport type DisconnectEvent = {\n type: 'disconnected'\n code: number\n reason: string\n willReconnect: boolean\n}\n\nexport type ClientEvent = ServerMessage | DisconnectEvent\n\nexport type EventHandler = (event: ClientEvent) => void | Promise<void>\n\n// EP605: Reconnection configuration for long-term stability\nconst INITIAL_RECONNECT_DELAY = 1000 // 1 second\nconst MAX_RECONNECT_DELAY = 60000 // 60 seconds standard max\nconst IDLE_RECONNECT_DELAY = 600000 // 10 minutes when idle (no commands for 1+ hour)\nconst MAX_RETRY_DURATION = 6 * 60 * 60 * 1000 // 6 hours maximum retry duration\nconst IDLE_THRESHOLD = 60 * 60 * 1000 // 1 hour - after this, use slower retry\n\n// EP648: Rate limit and rapid reconnect prevention\nconst RATE_LIMIT_BACKOFF = 60000 // 60 seconds when rate limited\nconst RAPID_CLOSE_THRESHOLD = 2000 // If connection closes within 2s, it's likely an error\nconst RAPID_CLOSE_BACKOFF = 30000 // 30 seconds backoff for rapid close scenarios\n\n// EP605: Client-side heartbeat to detect dead connections\n// EP846-7: Aligned to 20s (was 45s) - server is 15s, so intervals don't collide\nconst CLIENT_HEARTBEAT_INTERVAL = 20000 // 20 seconds (offset from server's 15s)\nconst CLIENT_HEARTBEAT_TIMEOUT = 15000 // 15 seconds to wait for pong\n\n// EP606: Connection timeout for initial WebSocket connection\nconst CONNECTION_TIMEOUT = 15000 // 15 seconds to establish connection\n\n/**\n * WebSocket client for connecting to episoda.dev/cli gateway\n *\n * DESIGN PRINCIPLES:\n * - Reverse WebSocket: Client connects TO server (bypasses NAT/firewall)\n * - Automatic reconnection with exponential backoff\n * - Server-initiated heartbeat via WebSocket ping/pong (15s interval)\n * - Event-driven architecture for handling server commands\n */\nexport class EpisodaClient {\n private ws?: WS\n private eventHandlers: Map<string, EventHandler[]> = new Map()\n private reconnectAttempts = 0\n private reconnectTimeout?: NodeJS.Timeout\n private url = ''\n private token = ''\n private machineId?: string\n private hostname?: string\n private osPlatform?: string\n private osArch?: string\n private daemonPid?: number\n private isConnected = false\n private isDisconnecting = false\n private isGracefulShutdown = false // Track if shutdown was graceful (server-initiated)\n // EP605: Client-side heartbeat for connection health monitoring\n private heartbeatTimer?: NodeJS.Timeout\n private heartbeatTimeoutTimer?: NodeJS.Timeout\n // EP605: Activity and retry duration tracking\n private lastCommandTime = Date.now() // Track last command for idle detection\n private firstDisconnectTime?: number // Track when reconnection attempts started\n private isIntentionalDisconnect = false // Prevent reconnection after intentional disconnect\n // EP648: Rate limit and rapid reconnect tracking\n private rateLimitBackoffUntil?: number // Timestamp until which we should wait (rate limited)\n private lastConnectAttemptTime = 0 // Track when we last attempted to connect\n private lastErrorCode?: string // Track the last error code received\n\n /**\n * Connect to episoda.dev WebSocket gateway\n * @param url - WebSocket URL (wss://episoda.dev/cli)\n * @param token - OAuth access token\n * @param machineId - Optional machine identifier for multi-machine support\n * @param deviceInfo - Optional device information (hostname, OS, daemonPid)\n */\n async connect(\n url: string,\n token: string,\n machineId?: string,\n deviceInfo?: { hostname?: string; osPlatform?: string; osArch?: string; daemonPid?: number }\n ): Promise<void> {\n this.url = url\n this.token = token\n this.machineId = machineId\n this.hostname = deviceInfo?.hostname\n this.osPlatform = deviceInfo?.osPlatform\n this.osArch = deviceInfo?.osArch\n this.daemonPid = deviceInfo?.daemonPid\n this.isDisconnecting = false\n this.isGracefulShutdown = false // Reset graceful shutdown flag on new connection\n this.isIntentionalDisconnect = false // Allow reconnection on fresh connect\n this.lastConnectAttemptTime = Date.now() // EP648: Track when this connection attempt started\n this.lastErrorCode = undefined // EP648: Clear last error code\n\n // EP816: Close any existing WebSocket before creating a new one\n // This prevents orphaned connections when connect() is called multiple times\n if (this.ws) {\n try {\n this.ws.removeAllListeners() // Prevent close handler from triggering reconnect\n this.ws.terminate()\n } catch {\n // Ignore errors during cleanup\n }\n this.ws = undefined\n }\n\n // Clear any pending reconnect timer to prevent race conditions\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout)\n this.reconnectTimeout = undefined\n }\n\n return new Promise((resolve, reject) => {\n // EP606: Connection timeout to prevent hanging indefinitely\n const connectionTimeout = setTimeout(() => {\n if (this.ws) {\n this.ws.terminate()\n }\n reject(new Error(`Connection timeout after ${CONNECTION_TIMEOUT / 1000}s - server may be unreachable`))\n }, CONNECTION_TIMEOUT)\n\n try {\n // EP801: Use IPv4 agent to avoid IPv6 timeout issues on networks with broken IPv6\n this.ws = new WS(url, { agent: ipv4Agent })\n\n // Connection opened\n this.ws.on('open', () => {\n clearTimeout(connectionTimeout) // EP606: Clear timeout on successful connection\n console.log('[EpisodaClient] WebSocket connected')\n this.isConnected = true\n this.reconnectAttempts = 0\n this.firstDisconnectTime = undefined // EP605: Reset retry duration tracking\n this.lastCommandTime = Date.now() // EP605: Reset activity timer\n\n // Send auth message (K722: includes machineId, EP596: includes device info, EP601: includes daemonPid)\n this.send({\n type: 'auth',\n token,\n version: VERSION,\n machineId,\n hostname: this.hostname,\n osPlatform: this.osPlatform,\n osArch: this.osArch,\n daemonPid: this.daemonPid\n })\n\n // EP605: Start client-side heartbeat\n this.startHeartbeat()\n\n resolve()\n })\n\n // EP605: Handle pong response from server (for client-initiated pings)\n this.ws.on('pong', () => {\n // Clear the timeout - connection is alive\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n this.heartbeatTimeoutTimer = undefined\n }\n })\n\n // Message received\n this.ws.on('message', (data: WS.Data) => {\n try {\n const message = JSON.parse(data.toString()) as ServerMessage\n this.handleMessage(message)\n } catch (error) {\n console.error('[EpisodaClient] Failed to parse message:', error)\n }\n })\n\n // EP603: Log ping events for debugging connection health\n this.ws.on('ping', () => {\n console.log('[EpisodaClient] Received ping from server')\n })\n\n // Connection closed\n this.ws.on('close', (code: number, reason: Buffer) => {\n console.log(`[EpisodaClient] WebSocket closed: ${code} ${reason.toString()}`)\n this.isConnected = false\n\n const willReconnect = !this.isDisconnecting\n\n // EP701: Emit 'disconnected' event so daemon can clean up connection\n this.emit({\n type: 'disconnected',\n code,\n reason: reason.toString(),\n willReconnect\n })\n\n // Attempt reconnection if not intentional disconnect\n if (willReconnect) {\n this.scheduleReconnect()\n }\n })\n\n // Error occurred\n this.ws.on('error', (error: Error) => {\n console.error('[EpisodaClient] WebSocket error:', error)\n\n if (!this.isConnected) {\n // Connection failed, reject the promise\n clearTimeout(connectionTimeout) // EP606: Clear timeout on error\n reject(error)\n }\n })\n\n } catch (error) {\n clearTimeout(connectionTimeout) // EP606: Clear timeout on exception\n reject(error)\n }\n })\n }\n\n /**\n * Disconnect from the server\n * @param intentional - If true, prevents automatic reconnection (user-initiated disconnect)\n */\n async disconnect(intentional = true): Promise<void> {\n this.isDisconnecting = true\n this.isIntentionalDisconnect = intentional // EP605: Prevent reconnection if user intentionally disconnected\n\n // EP605: Clear all timers\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout)\n this.reconnectTimeout = undefined\n }\n\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = undefined\n }\n\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n this.heartbeatTimeoutTimer = undefined\n }\n\n if (this.ws) {\n this.ws.close()\n this.ws = undefined\n }\n\n this.isConnected = false\n }\n\n /**\n * Register an event handler\n * @param event - Event type ('command', 'ping', 'error', 'auth_success')\n * @param handler - Handler function\n */\n on(event: string, handler: EventHandler): void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, [])\n }\n this.eventHandlers.get(event)!.push(handler)\n }\n\n /**\n * EP812: Register a one-time event handler (removes itself after first call)\n * @param event - Event type\n * @param handler - Handler function\n */\n once(event: string, handler: EventHandler): void {\n const onceHandler: EventHandler = (message) => {\n this.off(event, onceHandler)\n handler(message)\n }\n this.on(event, onceHandler)\n }\n\n /**\n * EP812: Remove an event handler\n * @param event - Event type\n * @param handler - Handler function to remove\n */\n off(event: string, handler: EventHandler): void {\n const handlers = this.eventHandlers.get(event)\n if (handlers) {\n const index = handlers.indexOf(handler)\n if (index !== -1) {\n handlers.splice(index, 1)\n }\n }\n }\n\n /**\n * Send a message to the server\n * @param message - Client message to send\n */\n async send(message: ClientMessage): Promise<void> {\n if (!this.ws || !this.isConnected) {\n throw new Error('WebSocket not connected')\n }\n\n return new Promise((resolve, reject) => {\n this.ws!.send(JSON.stringify(message), (error) => {\n if (error) {\n console.error('[EpisodaClient] Failed to send message:', error)\n reject(error)\n } else {\n resolve()\n }\n })\n })\n }\n\n /**\n * Get current connection status\n */\n getStatus(): ConnectionStatus {\n return {\n connected: this.isConnected\n }\n }\n\n /**\n * EP605: Update last command time to reset idle detection\n * Call this when a command is received/executed\n */\n updateActivity(): void {\n this.lastCommandTime = Date.now()\n }\n\n /**\n * EP701: Emit a client-side event to registered handlers\n * Used for events like 'disconnected' that originate from the client, not server\n */\n private emit(event: ClientEvent): void {\n const handlers = this.eventHandlers.get(event.type) || []\n handlers.forEach(handler => {\n try {\n handler(event)\n } catch (error) {\n console.error(`[EpisodaClient] Handler error for ${event.type}:`, error)\n }\n })\n }\n\n /**\n * Handle incoming message from server\n */\n private handleMessage(message: ServerMessage): void {\n // Special handling for graceful shutdown messages from server\n if (message.type === 'shutdown') {\n console.log('[EpisodaClient] Received graceful shutdown message from server')\n this.isGracefulShutdown = true\n }\n\n // EP648: Handle error messages, especially rate limiting and rapid reconnect\n if (message.type === 'error') {\n const errorMessage = message as ServerMessage & { code?: string; retryAfter?: number }\n this.lastErrorCode = errorMessage.code\n\n if (errorMessage.code === 'RATE_LIMITED' || errorMessage.code === 'TOO_SOON') {\n // Use server-provided retryAfter or default to 60 seconds for rate limit, 5 seconds for too soon\n const defaultRetry = errorMessage.code === 'RATE_LIMITED' ? 60 : 5\n const retryAfterMs = (errorMessage.retryAfter || defaultRetry) * 1000\n this.rateLimitBackoffUntil = Date.now() + retryAfterMs\n console.log(`[EpisodaClient] ${errorMessage.code}: will retry after ${retryAfterMs / 1000}s`)\n }\n }\n\n const handlers = this.eventHandlers.get(message.type) || []\n\n // Execute all handlers for this message type\n handlers.forEach(handler => {\n try {\n handler(message)\n } catch (error) {\n console.error(`[EpisodaClient] Handler error for ${message.type}:`, error)\n }\n })\n }\n\n /**\n * Schedule reconnection with simplified retry logic\n *\n * EP843: Simplified from complex exponential backoff to fast-fail approach\n *\n * Strategy:\n * - For graceful shutdown (server restart): Quick retry (500ms, 1s, 2s) up to 3 attempts\n * - For other disconnects: 1 retry after 1 second, then stop\n * - Always respect rate limits from server\n * - Surface errors quickly so user can take action\n *\n * This replaces the previous 6-hour retry with exponential backoff,\n * which masked problems and delayed error visibility.\n */\n private scheduleReconnect(): void {\n // Don't reconnect if user intentionally disconnected\n if (this.isIntentionalDisconnect) {\n console.log('[EpisodaClient] Intentional disconnect - not reconnecting')\n return\n }\n\n // Don't schedule if reconnection is already pending\n if (this.reconnectTimeout) {\n console.log('[EpisodaClient] Reconnection already scheduled, skipping duplicate')\n return\n }\n\n // Clear heartbeat timers before reconnection attempt\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = undefined\n }\n\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n this.heartbeatTimeoutTimer = undefined\n }\n\n // Check if we're still in rate limit backoff period\n if (this.rateLimitBackoffUntil && Date.now() < this.rateLimitBackoffUntil) {\n const waitTime = this.rateLimitBackoffUntil - Date.now()\n console.log(`[EpisodaClient] Rate limited, waiting ${Math.round(waitTime / 1000)}s before retry`)\n this.reconnectAttempts++\n this.reconnectTimeout = setTimeout(() => {\n this.rateLimitBackoffUntil = undefined\n this.scheduleReconnect()\n }, waitTime)\n return\n }\n\n // EP843: Simplified retry logic\n let delay: number\n let shouldRetry = true\n\n if (this.isGracefulShutdown) {\n // Graceful shutdown (server restart): Quick retry up to 7 attempts\n // EP843: Increased from 3 to 7 to cover longer server restart windows (deploys, migrations)\n // Retry schedule: 500ms, 1s, 2s, 4s, 5s, 5s, 5s = ~22.5s total coverage\n if (this.reconnectAttempts >= 7) {\n console.error('[EpisodaClient] Server restart reconnection failed after 7 attempts. Run \"episoda dev\" to reconnect.')\n shouldRetry = false\n } else {\n // Exponential backoff capped at 5s: 500ms, 1s, 2s, 4s, 5s, 5s, 5s\n delay = Math.min(500 * Math.pow(2, this.reconnectAttempts), 5000)\n console.log(`[EpisodaClient] Server restarting, reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}/7)`)\n }\n } else {\n // EP956: Non-graceful disconnects (code 1006 etc): 5 retries with exponential backoff\n // Previously only 1 retry, which was too aggressive at failing\n // Retry schedule: 1s, 2s, 4s, 8s, 16s = ~31s total coverage\n const MAX_NON_GRACEFUL_RETRIES = 5\n if (this.reconnectAttempts >= MAX_NON_GRACEFUL_RETRIES) {\n // EP843: Improved error message with more context\n console.error(`[EpisodaClient] Connection lost. Reconnection failed after ${MAX_NON_GRACEFUL_RETRIES} attempts. Check server status or restart with \"episoda dev\".`)\n shouldRetry = false\n } else {\n // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 16000)\n console.log(`[EpisodaClient] Connection lost, retrying in ${delay / 1000}s... (attempt ${this.reconnectAttempts + 1}/${MAX_NON_GRACEFUL_RETRIES})`)\n }\n }\n\n if (!shouldRetry) {\n // Emit disconnected event so daemon can handle it\n this.emit({\n type: 'disconnected',\n code: 1006,\n reason: 'Reconnection attempts exhausted',\n willReconnect: false\n })\n return\n }\n\n this.reconnectAttempts++\n\n this.reconnectTimeout = setTimeout(() => {\n console.log('[EpisodaClient] Attempting reconnection...')\n this.connect(this.url, this.token, this.machineId, {\n hostname: this.hostname,\n osPlatform: this.osPlatform,\n osArch: this.osArch,\n daemonPid: this.daemonPid\n }).then(() => {\n console.log('[EpisodaClient] Reconnection successful')\n this.reconnectAttempts = 0\n this.isGracefulShutdown = false\n this.firstDisconnectTime = undefined\n this.rateLimitBackoffUntil = undefined\n }).catch(error => {\n console.error('[EpisodaClient] Reconnection failed:', error.message)\n // scheduleReconnect will be called again from the 'close' event\n })\n }, delay!)\n }\n\n /**\n * EP605: Start client-side heartbeat to detect dead connections\n *\n * Sends ping every 45 seconds and expects pong within 15 seconds.\n * If no pong received, terminates connection to trigger reconnection.\n */\n private startHeartbeat(): void {\n // Clear any existing heartbeat\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n }\n\n this.heartbeatTimer = setInterval(() => {\n if (!this.ws || this.ws.readyState !== WS.OPEN) {\n return\n }\n\n // Send ping\n try {\n this.ws.ping()\n\n // Clear any existing timeout before setting new one (prevents timer leak)\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer)\n }\n\n // Set timeout for pong response\n this.heartbeatTimeoutTimer = setTimeout(() => {\n console.log('[EpisodaClient] Heartbeat timeout - no pong received, terminating connection')\n if (this.ws) {\n this.ws.terminate() // Force close to trigger reconnection\n }\n }, CLIENT_HEARTBEAT_TIMEOUT)\n } catch (error) {\n console.error('[EpisodaClient] Error sending heartbeat ping:', error)\n }\n }, CLIENT_HEARTBEAT_INTERVAL)\n }\n}\n","/**\n * Authentication utilities\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport { execSync } from 'child_process'\nimport { EpisodaConfig } from './command-protocol'\n\nconst DEFAULT_CONFIG_FILE = 'config.json'\n\n/**\n * Get the config directory path\n * Supports EPISODA_CONFIG_DIR env var for running multiple environments\n */\nexport function getConfigDir(): string {\n return process.env.EPISODA_CONFIG_DIR || path.join(os.homedir(), '.episoda')\n}\n\n/**\n * Get the full path to the config file\n */\nexport function getConfigPath(configPath?: string): string {\n if (configPath) {\n return configPath\n }\n return path.join(getConfigDir(), DEFAULT_CONFIG_FILE)\n}\n\n/**\n * Ensure the config directory exists\n *\n * EP798: Also excludes the directory from iCloud/Dropbox sync on macOS\n * to prevent machine-specific files (machine-id, daemon.sock) from syncing.\n */\nfunction ensureConfigDir(configPath: string): void {\n const dir = path.dirname(configPath)\n const isNew = !fs.existsSync(dir)\n\n if (isNew) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 })\n }\n\n // EP798: Exclude from cloud sync on macOS\n // Only do this once when directory is created (or if .nosync doesn't exist)\n if (process.platform === 'darwin') {\n const nosyncPath = path.join(dir, '.nosync')\n if (isNew || !fs.existsSync(nosyncPath)) {\n try {\n // Create .nosync file (respected by some cloud services)\n fs.writeFileSync(nosyncPath, '', { mode: 0o600 })\n // Set iCloud-specific exclusion attribute\n execSync(`xattr -w com.apple.fileprovider.ignore 1 \"${dir}\"`, {\n stdio: 'ignore',\n timeout: 5000\n })\n } catch {\n // Ignore errors - xattr may not be available or dir may already be excluded\n }\n }\n }\n}\n\n/**\n * Load configuration from .episoda/config.json\n */\nexport async function loadConfig(configPath?: string): Promise<EpisodaConfig | null> {\n const fullPath = getConfigPath(configPath)\n\n if (!fs.existsSync(fullPath)) {\n return null\n }\n\n try {\n const content = fs.readFileSync(fullPath, 'utf8')\n const config = JSON.parse(content) as EpisodaConfig\n return config\n } catch (error) {\n console.error('Error loading config:', error)\n return null\n }\n}\n\n/**\n * Save configuration to .episoda/config.json\n */\nexport async function saveConfig(config: EpisodaConfig, configPath?: string): Promise<void> {\n const fullPath = getConfigPath(configPath)\n ensureConfigDir(fullPath)\n\n try {\n const content = JSON.stringify(config, null, 2)\n fs.writeFileSync(fullPath, content, { mode: 0o600 })\n } catch (error) {\n throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\n/**\n * Validate an access token\n */\nexport async function validateToken(token: string): Promise<boolean> {\n // For now, just check if token exists and is non-empty\n // In the future, we can make an API call to validate\n return Boolean(token && token.length > 0)\n}\n","/**\n * Error handling utilities\n */\n\nimport { ErrorCode } from './command-protocol'\n\n/**\n * Custom error class for Episoda operations\n */\nexport class EpisodaError extends Error {\n constructor(\n public code: ErrorCode,\n message: string,\n public details?: Record<string, any>\n ) {\n super(message)\n this.name = 'EpisodaError'\n }\n}\n\n// Note: parseGitError is exported from git-parser.ts\n\n/**\n * Create a user-friendly error message from error code\n * (For CLI use - MCP will format differently)\n */\nexport function formatErrorMessage(code: ErrorCode, details?: Record<string, any>): string {\n const messages: Record<ErrorCode, string> = {\n 'GIT_NOT_INSTALLED': 'Git is not installed or not in PATH',\n 'NOT_GIT_REPO': 'Not a git repository',\n 'MERGE_CONFLICT': 'Merge conflict detected',\n 'REBASE_CONFLICT': 'Rebase conflict detected - resolve conflicts or abort rebase',\n 'UNCOMMITTED_CHANGES': 'Uncommitted changes would be overwritten',\n 'NETWORK_ERROR': 'Network error occurred',\n 'AUTH_FAILURE': 'Authentication failed',\n 'BRANCH_NOT_FOUND': 'Branch not found',\n 'BRANCH_ALREADY_EXISTS': 'Branch already exists',\n 'PUSH_REJECTED': 'Push rejected by remote',\n 'COMMAND_TIMEOUT': 'Command timed out',\n // EP944: Worktree error messages\n 'WORKTREE_EXISTS': 'Worktree already exists at this path',\n 'WORKTREE_NOT_FOUND': 'Worktree not found at this path',\n 'WORKTREE_LOCKED': 'Worktree is locked',\n 'BRANCH_IN_USE': 'Branch is already checked out in another worktree',\n // EP1002: Worktree setup error messages\n 'NOT_IMPLEMENTED': 'Command not implemented in this context',\n 'SETUP_FAILED': 'Worktree setup failed',\n 'UNKNOWN_ERROR': 'Unknown error occurred'\n }\n\n let message = messages[code] || `Error: ${code}`\n\n // Add details if provided\n if (details) {\n if (details.uncommittedFiles && details.uncommittedFiles.length > 0) {\n message += `\\nUncommitted files: ${details.uncommittedFiles.join(', ')}`\n }\n if (details.conflictingFiles && details.conflictingFiles.length > 0) {\n message += `\\nConflicting files: ${details.conflictingFiles.join(', ')}`\n }\n if (details.branchName) {\n message += `\\nBranch: ${details.branchName}`\n }\n }\n\n return message\n}\n","/**\n * @episoda/core\n *\n * Reusable business logic for Episoda local development orchestration.\n * Used by both episoda CLI (current) and @episoda/mcp-local-dev (future).\n *\n * DESIGN PRINCIPLES:\n * - Interface-agnostic: No CLI or MCP-specific code\n * - Structured data: Return objects, not formatted strings\n * - Error codes: Return codes, not messages\n * - Reusable: 80% code reuse target for MCP conversion\n */\n\n// Core types (LOCKED for Phase 2)\nexport * from './command-protocol'\n\n// Business logic classes\nexport { GitExecutor } from './git-executor'\nexport { EpisodaClient, type DisconnectEvent, type ClientEvent, type EventHandler } from './websocket-client'\n\n// Utilities\nexport * from './auth'\nexport * from './errors'\nexport * from './git-validator'\nexport * from './git-parser'\n\n// Package version - single source of truth from package.json\nexport { VERSION } from './version'\n","/**\n * Port availability checker\n */\n\nimport * as net from 'net'\n\n/**\n * Check if a port is in use\n * @param port Port number to check\n * @returns true if port is in use, false otherwise\n */\nexport async function isPortInUse(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer()\n\n server.once('error', (err: any) => {\n if (err.code === 'EADDRINUSE') {\n resolve(true)\n } else {\n resolve(false)\n }\n })\n\n server.once('listening', () => {\n server.close()\n resolve(false)\n })\n\n // Don't specify host - bind to all interfaces to detect any listener\n // This matches how Next.js and other dev servers bind\n server.listen(port)\n })\n}\n\n/**\n * Get the server port from config or default\n * @returns Port number\n */\nexport function getServerPort(): number {\n // Check environment variable\n if (process.env.PORT) {\n return parseInt(process.env.PORT, 10)\n }\n\n // Default to 3000 (Next.js default)\n return 3000\n}\n","{\n \"name\": \"episoda\",\n \"version\": \"0.2.36\",\n \"description\": \"CLI tool for Episoda local development workflow orchestration\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"bin\": {\n \"episoda\": \"dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"clean\": \"rm -rf dist\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"keywords\": [\n \"episoda\",\n \"cli\",\n \"git\",\n \"workflow\",\n \"development\"\n ],\n \"author\": \"Episoda\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"chalk\": \"^4.1.2\",\n \"commander\": \"^11.1.0\",\n \"ora\": \"^5.4.1\",\n \"semver\": \"7.7.3\",\n \"tar\": \"7.5.2\",\n \"ws\": \"^8.18.0\",\n \"zod\": \"^4.0.10\"\n },\n \"optionalDependencies\": {\n \"@anthropic-ai/claude-code\": \"^1.0.0\"\n },\n \"devDependencies\": {\n \"@episoda/core\": \"*\",\n \"@types/node\": \"^20.11.24\",\n \"@types/semver\": \"7.7.1\",\n \"@types/tar\": \"6.1.13\",\n \"@types/ws\": \"^8.5.10\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"^5.3.3\"\n },\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/v20x/episoda.git\",\n \"directory\": \"packages/episoda\"\n }\n}\n","/**\n * Machine ID generation and management\n *\n * EP812: Changed to use UUID format for consistency across the system.\n * The machine ID is now a proper UUID that can be used directly as the\n * database local_machine.id, eliminating the need for separate TEXT and UUID IDs.\n *\n * Format: Standard UUID v4 (e.g., \"550e8400-e29b-41d4-a716-446655440000\")\n *\n * EP731: Derives UUID deterministically from hardware UUID to prevent duplicate\n * device registrations when ~/.episoda syncs between devices via iCloud/Dropbox.\n *\n * Properties:\n * - Stable across daemon restarts\n * - Unique per PHYSICAL machine (derived from hardware UUID)\n * - Standard UUID format (works as database PK)\n * - Survives OS reboots\n * - Cannot sync between devices (hardware-based)\n *\n * Migration: Existing TEXT machine IDs (e.g., \"hostname-a3f2b1c4\") are\n * automatically migrated to UUID format on first read.\n */\n\nimport * as os from 'os'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as crypto from 'crypto'\nimport { execSync } from 'child_process'\nimport { getConfigDir } from '@episoda/core'\n\n/**\n * Check if a string is a valid UUID format\n */\nfunction isValidUUID(str: string): boolean {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n return uuidRegex.test(str)\n}\n\n/**\n * Get or generate machine ID\n *\n * EP812: Now returns a UUID format machine ID that can be used directly\n * as the database local_machine.id.\n *\n * Reads from config directory's machine-id if exists, otherwise generates new one\n * and saves it for future use. Migrates old TEXT format IDs to UUID.\n *\n * @returns Machine ID as UUID string\n */\nexport async function getMachineId(): Promise<string> {\n const machineIdPath = path.join(getConfigDir(), 'machine-id')\n\n // Try to read existing machine ID\n try {\n if (fs.existsSync(machineIdPath)) {\n const existingId = fs.readFileSync(machineIdPath, 'utf-8').trim()\n if (existingId) {\n // EP812: Check if already UUID format\n if (isValidUUID(existingId)) {\n return existingId\n }\n // EP812: Migrate old TEXT format to UUID\n // Generate new UUID based on hardware to maintain device uniqueness\n console.log('[MachineId] Migrating legacy machine ID to UUID format...')\n const newUUID = generateMachineId()\n fs.writeFileSync(machineIdPath, newUUID, 'utf-8')\n console.log(`[MachineId] Migrated: ${existingId} → ${newUUID}`)\n return newUUID\n }\n }\n } catch (error) {\n // File doesn't exist or can't be read, generate new one\n }\n\n // Generate new machine ID (UUID format)\n const machineId = generateMachineId()\n\n // Save to file\n try {\n const dir = path.dirname(machineIdPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(machineIdPath, machineId, 'utf-8')\n } catch (error) {\n console.error('Warning: Could not save machine ID to disk:', error)\n // Continue anyway - machine ID will be regenerated next time\n }\n\n return machineId\n}\n\n/**\n * EP731: Get hardware UUID for the current machine\n *\n * Uses platform-specific methods to get a hardware identifier that is:\n * - Unique per physical machine\n * - Stable across reboots\n * - Cannot sync between devices\n *\n * Falls back to random UUID if hardware UUID cannot be obtained.\n *\n * @returns Hardware UUID string\n */\nfunction getHardwareUUID(): string {\n try {\n if (process.platform === 'darwin') {\n // macOS: Get IOPlatformUUID from ioreg\n const output = execSync(\n 'ioreg -d2 -c IOPlatformExpertDevice | awk -F\\\\\" \\'/IOPlatformUUID/{print $(NF-1)}\\'',\n { encoding: 'utf-8', timeout: 5000 }\n ).trim()\n if (output && output.length > 0) {\n return output\n }\n } else if (process.platform === 'linux') {\n // Linux: Read /etc/machine-id\n if (fs.existsSync('/etc/machine-id')) {\n const machineId = fs.readFileSync('/etc/machine-id', 'utf-8').trim()\n if (machineId && machineId.length > 0) {\n return machineId\n }\n }\n // Fallback: Try /var/lib/dbus/machine-id\n if (fs.existsSync('/var/lib/dbus/machine-id')) {\n const dbusId = fs.readFileSync('/var/lib/dbus/machine-id', 'utf-8').trim()\n if (dbusId && dbusId.length > 0) {\n return dbusId\n }\n }\n } else if (process.platform === 'win32') {\n // Windows: Get UUID from wmic\n const output = execSync('wmic csproduct get uuid', {\n encoding: 'utf-8',\n timeout: 5000\n })\n const lines = output.trim().split('\\n')\n if (lines.length >= 2) {\n const uuid = lines[1].trim()\n if (uuid && uuid.length > 0 && uuid !== 'UUID') {\n return uuid\n }\n }\n }\n } catch (error) {\n // Hardware UUID retrieval failed, will fall back to random\n console.warn('Could not get hardware UUID, using random fallback:', error)\n }\n\n // Fallback: Generate random UUID\n return crypto.randomUUID()\n}\n\n/**\n * Generate a new machine ID as UUID\n *\n * EP812: Now generates a proper UUID that can be used as database PK.\n * EP731: Derives UUID deterministically from hardware UUID to ensure uniqueness\n * per physical machine, even if ~/.episoda syncs between devices.\n *\n * Format: Standard UUID v4\n * Example: \"550e8400-e29b-41d4-a716-446655440000\"\n *\n * @returns Generated machine ID as UUID\n */\nfunction generateMachineId(): string {\n const hwUUID = getHardwareUUID()\n\n // If hardware UUID is already a valid UUID, use it directly\n if (isValidUUID(hwUUID)) {\n return hwUUID.toLowerCase()\n }\n\n // Otherwise, derive a deterministic UUID from the hardware identifier\n // This ensures the same physical machine always gets the same UUID\n const hash = crypto.createHash('sha256').update(hwUUID).digest('hex')\n\n // Format as UUID v4 (but deterministic based on hardware)\n // Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\n // where y is 8, 9, a, or b\n const uuid = [\n hash.slice(0, 8),\n hash.slice(8, 12),\n '4' + hash.slice(13, 16), // Version 4\n ((parseInt(hash.slice(16, 17), 16) & 0x3) | 0x8).toString(16) + hash.slice(17, 20), // Variant\n hash.slice(20, 32)\n ].join('-')\n\n return uuid.toLowerCase()\n}\n\n/**\n * Reset machine ID (force regeneration)\n *\n * Deletes the stored machine ID file, forcing a new ID to be generated\n * on next getMachineId() call.\n *\n * Use case: Developer explicitly wants to change their machine identity\n */\nexport function resetMachineId(): void {\n const machineIdPath = path.join(getConfigDir(), 'machine-id')\n try {\n if (fs.existsSync(machineIdPath)) {\n fs.unlinkSync(machineIdPath)\n }\n } catch (error) {\n throw new Error(`Failed to reset machine ID: ${error}`)\n }\n}\n","/**\n * Project tracking for daemon\n *\n * Manages ~/.episoda/projects.json which tracks all projects\n * the daemon is monitoring.\n *\n * Format:\n * {\n * \"projects\": [\n * {\n * \"id\": \"proj_abc123\",\n * \"path\": \"/Users/alan/Dev/my-project\",\n * \"name\": \"my-project\",\n * \"added_at\": \"2025-01-18T10:30:00.000Z\",\n * \"last_active\": \"2025-01-18T12:45:00.000Z\"\n * }\n * ]\n * }\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { getConfigDir } from '@episoda/core'\n\nexport interface TrackedProject {\n id: string // Supabase project ID\n path: string // Absolute path to project directory\n name: string // Project name (from directory)\n added_at: string // ISO timestamp when first added\n last_active: string // ISO timestamp of last activity\n // EP971: All projects use worktree architecture\n bareRepoPath?: string // Path to .bare/ directory\n}\n\ninterface ProjectsData {\n projects: TrackedProject[]\n}\n\n/**\n * Get path to projects.json file\n */\nfunction getProjectsFilePath(): string {\n return path.join(getConfigDir(), 'projects.json')\n}\n\n/**\n * Read projects from file\n *\n * @returns Projects data, or empty list if file doesn't exist\n */\nfunction readProjects(): ProjectsData {\n const projectsPath = getProjectsFilePath()\n\n try {\n if (!fs.existsSync(projectsPath)) {\n return { projects: [] }\n }\n\n const content = fs.readFileSync(projectsPath, 'utf-8')\n const data = JSON.parse(content) as ProjectsData\n\n // Validate structure\n if (!data.projects || !Array.isArray(data.projects)) {\n console.warn('Invalid projects.json structure, resetting')\n return { projects: [] }\n }\n\n return data\n } catch (error) {\n console.error('Error reading projects.json:', error)\n return { projects: [] }\n }\n}\n\n/**\n * Write projects to file\n */\nfunction writeProjects(data: ProjectsData): void {\n const projectsPath = getProjectsFilePath()\n\n try {\n // Ensure directory exists\n const dir = path.dirname(projectsPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n fs.writeFileSync(projectsPath, JSON.stringify(data, null, 2), 'utf-8')\n } catch (error) {\n throw new Error(`Failed to write projects.json: ${error}`)\n }\n}\n\n/**\n * Options for adding a project\n * EP971: All projects use worktree architecture\n */\nexport interface AddProjectOptions {\n bareRepoPath?: string // Path to .bare/ directory\n}\n\n/**\n * Add or update a project\n *\n * EP593: Now enforces one entry per projectId to prevent duplicate connections.\n * When adding a project:\n * - If same path exists, update last_active\n * - If same projectId exists with different path, REPLACE the old entry\n * (user wants git operations in the new directory)\n * - Otherwise, add new project\n *\n * EP971: All projects use worktree architecture.\n *\n * @param projectId Supabase project ID\n * @param projectPath Absolute path to project\n * @param options Optional settings (bareRepoPath)\n * @returns The tracked project\n */\nexport function addProject(\n projectId: string,\n projectPath: string,\n options?: AddProjectOptions\n): TrackedProject {\n const data = readProjects()\n const now = new Date().toISOString()\n\n // Check if project already exists by path (exact match)\n const existingByPath = data.projects.find(p => p.path === projectPath)\n\n if (existingByPath) {\n // Update existing project\n existingByPath.id = projectId // Update ID in case it changed\n existingByPath.last_active = now\n // EP971: Update bare repo path if specified\n if (options?.bareRepoPath) {\n existingByPath.bareRepoPath = options.bareRepoPath\n }\n writeProjects(data)\n return existingByPath\n }\n\n // EP593: Check if project exists by ID (different path)\n // This prevents multiple entries for the same project from different directories\n const existingByIdIndex = data.projects.findIndex(p => p.id === projectId)\n\n if (existingByIdIndex !== -1) {\n const existingById = data.projects[existingByIdIndex]\n console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`)\n\n // Remove the old entry\n data.projects.splice(existingByIdIndex, 1)\n }\n\n // Add new project\n const projectName = path.basename(projectPath)\n const newProject: TrackedProject = {\n id: projectId,\n path: projectPath,\n name: projectName,\n added_at: now,\n last_active: now,\n // EP971: Bare repo path for worktree architecture\n bareRepoPath: options?.bareRepoPath,\n }\n\n data.projects.push(newProject)\n writeProjects(data)\n\n return newProject\n}\n\n/**\n * Remove a project\n *\n * @param projectPath Absolute path to project\n * @returns true if removed, false if not found\n */\nexport function removeProject(projectPath: string): boolean {\n const data = readProjects()\n const initialLength = data.projects.length\n\n data.projects = data.projects.filter(p => p.path !== projectPath)\n\n if (data.projects.length < initialLength) {\n writeProjects(data)\n return true\n }\n\n return false\n}\n\n/**\n * Get a project by path\n *\n * @param projectPath Absolute path to project\n * @returns Project if found, null otherwise\n */\nexport function getProject(projectPath: string): TrackedProject | null {\n const data = readProjects()\n return data.projects.find(p => p.path === projectPath) || null\n}\n\n/**\n * Get a project by ID\n *\n * @param projectId Supabase project ID\n * @returns Project if found, null otherwise\n */\nexport function getProjectById(projectId: string): TrackedProject | null {\n const data = readProjects()\n return data.projects.find(p => p.id === projectId) || null\n}\n\n/**\n * Get all tracked projects\n *\n * @returns Array of tracked projects\n */\nexport function getAllProjects(): TrackedProject[] {\n const data = readProjects()\n return data.projects\n}\n\n/**\n * Update last active timestamp for a project\n *\n * @param projectPath Absolute path to project\n */\nexport function touchProject(projectPath: string): void {\n const data = readProjects()\n const project = data.projects.find(p => p.path === projectPath)\n\n if (project) {\n project.last_active = new Date().toISOString()\n writeProjects(data)\n }\n}\n\n/**\n * Clean up stale projects\n *\n * Removes projects that:\n * - Haven't been active in N days\n * - Directory no longer exists\n *\n * @param maxAgeDays Maximum age in days (default: 30)\n * @returns Number of projects removed\n */\nexport function cleanupStaleProjects(maxAgeDays: number = 30): number {\n const data = readProjects()\n const now = Date.now()\n const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000\n\n const initialLength = data.projects.length\n\n data.projects = data.projects.filter(project => {\n // Check if directory still exists\n if (!fs.existsSync(project.path)) {\n return false\n }\n\n // Check if too old\n const lastActive = new Date(project.last_active).getTime()\n const age = now - lastActive\n\n return age < maxAgeMs\n })\n\n const removedCount = initialLength - data.projects.length\n\n if (removedCount > 0) {\n writeProjects(data)\n }\n\n return removedCount\n}\n\n/**\n * Clear all projects\n *\n * USE WITH CAUTION - removes all tracked projects\n */\nexport function clearAllProjects(): void {\n writeProjects({ projects: [] })\n}\n","/**\n * Daemon lifecycle management\n *\n * Manages the Episoda daemon process lifecycle:\n * - Spawning daemon in detached mode\n * - Checking daemon status\n * - Stopping daemon gracefully\n * - PID file management\n *\n * Ensures only one daemon runs per user.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { spawn, execSync } from 'child_process'\nimport { getConfigDir } from '@episoda/core'\n\n/**\n * EP734: Kill all stale Episoda processes\n *\n * Finds and kills any existing episoda-related processes to ensure\n * a clean slate before starting a new daemon. This prevents:\n * - Multiple daemon processes running simultaneously\n * - Stale `episoda dev` foreground processes from previous sessions\n * - Orphaned node processes running daemon-process.js\n *\n * @returns Number of processes killed\n */\nexport function killAllEpisodaProcesses(): number {\n const currentPid = process.pid\n let killedCount = 0\n\n // EP734: Windows not supported for process cleanup - skip with warning\n if (process.platform === 'win32') {\n console.warn('[Cleanup] Process cleanup not supported on Windows - skipping')\n return 0\n }\n\n try {\n // Find all episoda-related processes using ps (Unix/macOS only)\n // Patterns: 'episoda dev', 'daemon-process.js', 'episoda status', 'ws-server.js'\n // EP797: Added ws-server.js to catch orphaned WebSocket server processes\n const psOutput = execSync(\n 'ps aux | grep -E \"(episoda (dev|status|stop)|daemon-process\\\\.js|node ws-server\\\\.js)\" | grep -v grep || true',\n { encoding: 'utf-8', timeout: 5000 }\n )\n\n const lines = psOutput.trim().split('\\n').filter(line => line.length > 0)\n\n for (const line of lines) {\n // Parse PID from ps aux output (second column)\n const parts = line.trim().split(/\\s+/)\n if (parts.length < 2) continue\n\n const pid = parseInt(parts[1], 10)\n if (isNaN(pid) || pid === currentPid) continue\n\n try {\n process.kill(pid, 'SIGTERM')\n killedCount++\n console.log(`[Cleanup] Killed stale process PID ${pid}`)\n } catch (err) {\n // Process might have already exited, ignore\n }\n }\n\n // Give processes a moment to terminate\n if (killedCount > 0) {\n execSync('sleep 0.5', { timeout: 2000 })\n }\n\n // Clean up stale files\n const configDir = getConfigDir()\n const pidPath = path.join(configDir, 'daemon.pid')\n const sockPath = path.join(configDir, 'daemon.sock')\n\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n if (fs.existsSync(sockPath)) {\n fs.unlinkSync(sockPath)\n }\n\n } catch (error) {\n // Non-fatal - best effort cleanup\n console.warn('[Cleanup] Error during process cleanup:', error instanceof Error ? error.message : error)\n }\n\n return killedCount\n}\n\n/**\n * Get path to daemon PID file\n */\nexport function getPidFilePath(): string {\n return path.join(getConfigDir(), 'daemon.pid')\n}\n\n/**\n * Check if daemon is running\n *\n * Reads PID from file and checks if process exists.\n *\n * @returns PID if running, null if not\n */\nexport function isDaemonRunning(): number | null {\n const pidPath = getPidFilePath()\n\n try {\n if (!fs.existsSync(pidPath)) {\n return null\n }\n\n const pidStr = fs.readFileSync(pidPath, 'utf-8').trim()\n const pid = parseInt(pidStr, 10)\n\n if (isNaN(pid)) {\n // Invalid PID, clean up file\n fs.unlinkSync(pidPath)\n return null\n }\n\n // Check if process exists\n try {\n // Sending signal 0 checks existence without killing\n process.kill(pid, 0)\n return pid\n } catch (error) {\n // Process doesn't exist, clean up stale PID file\n fs.unlinkSync(pidPath)\n return null\n }\n } catch (error) {\n console.error('Error checking daemon status:', error)\n return null\n }\n}\n\n/**\n * Start the daemon process\n *\n * Spawns daemon in detached mode. Daemon survives terminal close.\n *\n * @throws Error if daemon already running or spawn fails\n */\nexport async function startDaemon(): Promise<number> {\n // Check if already running\n const existingPid = isDaemonRunning()\n if (existingPid) {\n throw new Error(`Daemon already running (PID: ${existingPid})`)\n }\n\n // Ensure config directory exists\n const configDir = getConfigDir()\n if (!fs.existsSync(configDir)) {\n fs.mkdirSync(configDir, { recursive: true })\n }\n\n // Path to daemon entry point\n // When built: dist/daemon/daemon-process.js (tsup preserves directory structure)\n // __dirname is dist/ when running from bundled index.js\n const daemonScript = path.join(__dirname, 'daemon', 'daemon-process.js')\n\n if (!fs.existsSync(daemonScript)) {\n throw new Error(`Daemon script not found: ${daemonScript}. Make sure CLI is built.`)\n }\n\n // EP813: Debug - log daemon output to file for troubleshooting\n const logPath = path.join(configDir, 'daemon.log')\n const logFd = fs.openSync(logPath, 'a')\n\n // Spawn daemon as detached process\n const child = spawn('node', [daemonScript], {\n detached: true, // Run independently of parent\n stdio: ['ignore', logFd, logFd], // EP813: Redirect stdout/stderr to log file\n env: {\n ...process.env,\n EPISODA_DAEMON_MODE: '1', // Signal to daemon it's running in daemon mode\n },\n })\n\n // Detach from parent process\n child.unref()\n\n const pid = child.pid!\n\n // Save PID to file\n const pidPath = getPidFilePath()\n fs.writeFileSync(pidPath, pid.toString(), 'utf-8')\n\n // Give daemon a moment to start\n await new Promise(resolve => setTimeout(resolve, 500))\n\n // Verify daemon actually started\n const runningPid = isDaemonRunning()\n if (!runningPid) {\n throw new Error('Daemon failed to start')\n }\n\n return pid\n}\n\n/**\n * Stop the daemon process\n *\n * Sends SIGTERM for graceful shutdown. If daemon doesn't stop\n * within timeout, sends SIGKILL.\n *\n * @param timeout Milliseconds to wait before SIGKILL (default: 5000)\n * @returns true if stopped, false if wasn't running\n */\nexport async function stopDaemon(timeout: number = 5000): Promise<boolean> {\n const pid = isDaemonRunning()\n if (!pid) {\n // Clean up PID file just in case\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n return false\n }\n\n try {\n // Send SIGTERM for graceful shutdown\n process.kill(pid, 'SIGTERM')\n\n // Wait for process to exit\n const startTime = Date.now()\n while (Date.now() - startTime < timeout) {\n try {\n process.kill(pid, 0) // Check if still alive\n await new Promise(resolve => setTimeout(resolve, 100))\n } catch (error) {\n // Process exited\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n return true\n }\n }\n\n // Timeout reached, force kill\n console.warn(`Daemon didn't stop gracefully, forcing shutdown (PID: ${pid})`)\n process.kill(pid, 'SIGKILL')\n\n // Clean up PID file\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n\n return true\n } catch (error) {\n console.error('Error stopping daemon:', error)\n return false\n }\n}\n\n/**\n * Restart the daemon\n *\n * Stops existing daemon and starts new one.\n *\n * @returns PID of new daemon\n */\nexport async function restartDaemon(): Promise<number> {\n await stopDaemon()\n return startDaemon()\n}\n\n/**\n * Get daemon status information\n *\n * @returns Status object with running state and PID\n */\nexport function getDaemonStatus(): { running: boolean; pid: number | null } {\n const pid = isDaemonRunning()\n return {\n running: pid !== null,\n pid,\n }\n}\n","/**\n * IPC Server - Runs in daemon process\n *\n * Listens for commands from CLI via Unix domain socket.\n * Handles command routing and response sending.\n *\n * Socket location: ~/.episoda/daemon.sock\n */\n\nimport * as net from 'net'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { getConfigDir } from '@episoda/core'\n\nconst getSocketPath = () => path.join(getConfigDir(), 'daemon.sock')\n\nexport interface IPCRequest {\n id: string // Request ID for matching responses\n command: string // Command name (e.g., 'add-project', 'status')\n params?: any // Command parameters\n}\n\nexport interface IPCResponse {\n id: string // Matches request ID\n success: boolean // Success/failure\n data?: any // Response data\n error?: string // Error message if failed\n}\n\nexport type CommandHandler = (params: any) => Promise<any>\n\n/**\n * IPC Server\n *\n * Listens on Unix socket for CLI commands.\n */\nexport class IPCServer {\n private server: net.Server | null = null\n private handlers = new Map<string, CommandHandler>()\n\n /**\n * Register a command handler\n *\n * @param command Command name\n * @param handler Async function to handle command\n */\n on(command: string, handler: CommandHandler): void {\n this.handlers.set(command, handler)\n }\n\n /**\n * Start the IPC server\n *\n * Creates Unix socket and listens for connections.\n */\n async start(): Promise<void> {\n const socketPath = getSocketPath()\n\n // Clean up existing socket if it exists\n if (fs.existsSync(socketPath)) {\n fs.unlinkSync(socketPath)\n }\n\n // Ensure config directory exists\n const dir = path.dirname(socketPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n this.server = net.createServer(socket => {\n this.handleConnection(socket)\n })\n\n return new Promise((resolve, reject) => {\n this.server!.listen(socketPath, () => {\n // Set socket permissions (owner only)\n fs.chmodSync(socketPath, 0o600)\n resolve()\n })\n\n this.server!.on('error', reject)\n })\n }\n\n /**\n * Stop the IPC server\n */\n async stop(): Promise<void> {\n if (!this.server) return\n\n const socketPath = getSocketPath()\n return new Promise((resolve) => {\n this.server!.close(() => {\n // Clean up socket file\n if (fs.existsSync(socketPath)) {\n fs.unlinkSync(socketPath)\n }\n resolve()\n })\n })\n }\n\n /**\n * Handle incoming client connection\n */\n private handleConnection(socket: net.Socket): void {\n let buffer = ''\n\n socket.on('data', async (chunk) => {\n buffer += chunk.toString()\n\n // Check if we have a complete message (delimited by newline)\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex === -1) return\n\n // Extract message\n const message = buffer.slice(0, newlineIndex)\n buffer = buffer.slice(newlineIndex + 1)\n\n try {\n const request = JSON.parse(message) as IPCRequest\n const response = await this.handleRequest(request)\n\n // Send response\n socket.write(JSON.stringify(response) + '\\n')\n } catch (error) {\n const errorResponse: IPCResponse = {\n id: 'unknown',\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n socket.write(JSON.stringify(errorResponse) + '\\n')\n }\n })\n\n socket.on('error', (error) => {\n console.error('[IPC Server] Socket error:', error)\n })\n }\n\n /**\n * Handle IPC request\n */\n private async handleRequest(request: IPCRequest): Promise<IPCResponse> {\n const handler = this.handlers.get(request.command)\n\n if (!handler) {\n return {\n id: request.id,\n success: false,\n error: `Unknown command: ${request.command}`,\n }\n }\n\n try {\n const data = await handler(request.params)\n return {\n id: request.id,\n success: true,\n data,\n }\n } catch (error) {\n return {\n id: request.id,\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n }\n }\n}\n","/**\n * Episoda Daemon Process\n *\n * Main entry point for the persistent daemon that:\n * - Maintains WebSocket connections to multiple projects\n * - Listens for IPC commands from CLI\n * - Handles graceful shutdown\n * - Survives terminal close\n *\n * This file is spawned by daemon-manager.ts in detached mode.\n */\n\nimport { getMachineId } from './machine-id'\nimport { getAllProjects, addProject as trackProject, removeProject as untrackProject, touchProject } from './project-tracker'\nimport { getPidFilePath } from './daemon-manager'\nimport { IPCServer } from '../ipc/ipc-server'\nimport { loadConfig, saveConfig, EpisodaClient, GitCommand, ExecutionResult, GitExecutor, DisconnectEvent, EpisodaConfig, RemoteCommand, TunnelCommand, TunnelCommandResult, AgentCommand, AgentResult, ReconciliationReport, ReconciliationModuleStatus, OrphanTunnelStatus, ModuleState } from '@episoda/core'\nimport { checkForUpdates, performBackgroundUpdate } from '../utils/update-checker'\n// EP812: Removed IdentityServer - browser identity now uses cookie-based pairing\nimport { handleFileRead, handleFileWrite, handleFileEdit, handleFileDelete, handleFileMkdir, handleFileList, handleFileSearch, handleFileGrep, handleExec } from './handlers'\nimport { cleanupStaleCommits } from './handlers/stale-commit-cleanup'\nimport { getTunnelManager, clearTunnelUrl } from '../tunnel'\nimport { getAgentManager } from '../agent'\nimport { ensureDevServer, stopDevServer, restartDevServer, isDevServerHealthy, killProcessOnPort, getDevServerStatus } from '../utils/dev-server'\nimport { detectDevPort } from '../utils/port-detect'\n// EP957: Worktree manager for git worktree operations\nimport { WorktreeManager, findProjectRoot } from './worktree-manager'\n// EP988: Shared env setup utility\nimport { writeEnvFile } from '../utils/env-setup'\n// EP956: Worktree path resolution and port allocation for multi-tunnel mode\n// EP959-8: Added getProjectRootPath for worktree creation\nimport { getWorktreeInfoForModule, getProjectRootPath } from '../utils/worktree'\nimport { allocatePort, releasePort, clearAllPorts } from '../utils/port-allocator'\n// EP986: Import getInstallCommand for auto-dependency installation\nimport { getInstallCommand } from '../framework-detector'\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport * as path from 'path'\n\n// EP783: Get current version for update checking\nconst packageJson = require('../../package.json')\n\n// EP812: Removed identity server - browser identity now uses cookie-based pairing\n\n/**\n * EP904: Token refresh utility\n *\n * Checks if the access token is expired or about to expire, and refreshes it\n * using the refresh_token. Returns the (possibly updated) config.\n *\n * @param config Current config\n * @param bufferMs Buffer time before expiry to trigger refresh (default 5 minutes)\n * @returns Updated config with fresh token, or original if refresh not needed/failed\n */\nasync function ensureValidToken(\n config: EpisodaConfig,\n bufferMs: number = 5 * 60 * 1000\n): Promise<EpisodaConfig> {\n // Check if token is expired or about to expire\n const now = Date.now()\n const expiresAt = config.expires_at || 0\n\n if (expiresAt > now + bufferMs) {\n // Token is still valid\n return config\n }\n\n // Token needs refresh\n if (!config.refresh_token) {\n console.warn('[Daemon] EP904: Token expired but no refresh_token available')\n return config\n }\n\n console.log('[Daemon] EP904: Access token expired or expiring soon, refreshing...')\n\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetch(`${apiUrl}/api/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n refresh_token: config.refresh_token,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as { error?: string }\n console.error(`[Daemon] EP904: Token refresh failed: ${response.status} ${errorData.error || response.statusText}`)\n return config\n }\n\n const tokenResponse = await response.json() as {\n access_token: string\n refresh_token?: string\n expires_in: number\n }\n\n // Update config with new token\n const updatedConfig: EpisodaConfig = {\n ...config,\n access_token: tokenResponse.access_token,\n refresh_token: tokenResponse.refresh_token || config.refresh_token,\n expires_at: now + (tokenResponse.expires_in * 1000),\n }\n\n // Save to disk\n await saveConfig(updatedConfig)\n console.log('[Daemon] EP904: Access token refreshed successfully')\n\n return updatedConfig\n } catch (error) {\n console.error('[Daemon] EP904: Token refresh error:', error instanceof Error ? error.message : error)\n return config\n }\n}\n\n/**\n * EP904: Make an authenticated API request with automatic token refresh\n *\n * Wraps fetch with token refresh and retry logic.\n */\nasync function fetchWithAuth(\n url: string,\n options: RequestInit = {},\n retryOnUnauthorized: boolean = true\n): Promise<Response> {\n let config = await loadConfig()\n if (!config?.access_token) {\n throw new Error('No access token configured')\n }\n\n // Ensure token is valid before making request\n config = await ensureValidToken(config)\n\n const headers = {\n ...options.headers,\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json',\n }\n\n let response = await fetch(url, { ...options, headers })\n\n // If unauthorized and we have a refresh token, try refreshing and retry once\n if (response.status === 401 && retryOnUnauthorized && config.refresh_token) {\n console.log('[Daemon] EP904: Received 401, attempting token refresh and retry...')\n\n // Force token refresh\n const refreshedConfig = await ensureValidToken({ ...config, expires_at: 0 })\n\n if (refreshedConfig.access_token !== config.access_token) {\n // Token was refreshed, retry the request\n const retryHeaders = {\n ...options.headers,\n 'Authorization': `Bearer ${refreshedConfig.access_token}`,\n 'Content-Type': 'application/json',\n }\n response = await fetch(url, { ...options, headers: retryHeaders })\n }\n }\n\n return response\n}\n\n/**\n * EP973: Fetch decrypted environment variables from the server\n *\n * Calls the /api/cli/env-vars endpoint to get environment variables\n * for injection into .env files during worktree setup.\n *\n * @returns Record of key-value pairs, or empty object on error\n */\nasync function fetchEnvVars(): Promise<Record<string, string>> {\n try {\n const config = await loadConfig()\n if (!config?.project_id) {\n console.warn('[Daemon] EP973: No project_id in config, cannot fetch env vars')\n return {}\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetchWithAuth(`${apiUrl}/api/cli/env-vars`)\n\n if (!response.ok) {\n console.warn(`[Daemon] EP973: Failed to fetch env vars: ${response.status}`)\n return {}\n }\n\n const data = await response.json() as { env_vars?: Record<string, string> }\n const envVars = data.env_vars || {}\n\n console.log(`[Daemon] EP973: Fetched ${Object.keys(envVars).length} env vars from server`)\n return envVars\n } catch (error) {\n console.warn('[Daemon] EP973: Error fetching env vars:', error instanceof Error ? error.message : error)\n return {}\n }\n}\n\n/**\n * Active WebSocket connection\n */\ninterface ActiveConnection {\n projectId: string\n projectPath: string\n client: EpisodaClient\n gitExecutor: GitExecutor\n reconnectTimer?: NodeJS.Timeout\n}\n\n/**\n * Daemon state\n */\nclass Daemon {\n private machineId: string = ''\n private deviceId: string | null = null // EP726: Cached device UUID from server\n private deviceName: string | null = null // EP661: Cached device name from server\n private flyMachineId: string | null = null // EP735: Fly.io machine ID for sticky session routing\n private ipcServer: IPCServer\n // EP812: Removed identityServer - browser identity now uses cookie-based pairing\n private connections = new Map<string, ActiveConnection>() // projectPath -> connection\n // EP701: Track which connections are currently live (WebSocket open)\n // Updated by 'auth_success' (add) and 'disconnected' (remove) events\n private liveConnections = new Set<string>() // projectPath\n // EP813: Track connections that are still authenticating (in progress)\n // Prevents race condition between restoreConnections() and add-project IPC\n private pendingConnections = new Set<string>() // projectPath\n private shuttingDown = false\n // EP822: Periodic tunnel polling interval\n private tunnelPollInterval: NodeJS.Timeout | null = null\n private static readonly TUNNEL_POLL_INTERVAL_MS = 15000 // 15 seconds\n // EP822: Prevent concurrent tunnel syncs (backpressure guard)\n private tunnelSyncInProgress = false\n // EP833: Track consecutive health check failures per tunnel\n private tunnelHealthFailures = new Map<string, number>() // moduleUid -> consecutive failures\n private static readonly HEALTH_CHECK_FAILURE_THRESHOLD = 2 // Restart after 2 consecutive failures\n private static readonly HEALTH_CHECK_TIMEOUT_MS = 3000 // 3 second timeout for health checks\n // EP911: Track last reported health status to avoid unnecessary DB writes\n private lastReportedHealthStatus = new Map<string, 'healthy' | 'unhealthy' | 'unknown'>() // moduleUid -> status\n // EP837: Prevent concurrent commit syncs (backpressure guard)\n private commitSyncInProgress = false\n // EP843: Per-module mutex for tunnel operations\n // EP1003: Prevents race conditions between server-orchestrated tunnel commands\n private tunnelOperationLocks = new Map<string, Promise<void>>() // moduleUid -> operation promise\n // EP929: Health check polling interval (restored from EP843 removal)\n // Health checks are orthogonal to push-based state sync - they detect dead tunnels\n private healthCheckInterval: NodeJS.Timeout | null = null\n private healthCheckInProgress = false\n private static readonly HEALTH_CHECK_INTERVAL_MS = 60000 // 60 seconds\n\n constructor() {\n this.ipcServer = new IPCServer()\n }\n\n /**\n * Start the daemon\n */\n async start(): Promise<void> {\n console.log('[Daemon] Starting Episoda daemon...')\n\n // Get machine ID\n this.machineId = await getMachineId()\n console.log(`[Daemon] Machine ID: ${this.machineId}`)\n\n // EP726: Load cached device ID from config if available\n const config = await loadConfig()\n if (config?.device_id) {\n this.deviceId = config.device_id\n console.log(`[Daemon] Loaded cached Device ID (UUID): ${this.deviceId}`)\n }\n\n // Start IPC server\n await this.ipcServer.start()\n console.log('[Daemon] IPC server started')\n\n // EP812: Removed identity server - browser identity now uses cookie-based pairing\n\n // Register IPC command handlers\n this.registerIPCHandlers()\n\n // Restore connections for tracked projects\n await this.restoreConnections()\n\n // EP822: Clean up orphaned tunnels from previous daemon runs\n await this.cleanupOrphanedTunnels()\n\n // EP957: Audit worktrees on startup to detect orphans from crashed sessions\n await this.auditWorktreesOnStartup()\n\n // EP843: Tunnel state sync polling removed - now using push-based state sync via module_state_changed events\n // this.startTunnelPolling()\n\n // EP929: Start health check polling (restored from EP843 removal)\n // Health checks are orthogonal to state sync - they detect when running tunnels die\n this.startHealthCheckPolling()\n\n // Setup graceful shutdown\n this.setupShutdownHandlers()\n\n console.log('[Daemon] Daemon started successfully')\n\n // EP783: Check for updates in background (non-blocking)\n this.checkAndNotifyUpdates()\n }\n\n /**\n * EP783: Check for CLI updates and auto-update in background\n * Non-blocking - runs after daemon starts, fails silently on errors\n */\n private async checkAndNotifyUpdates(): Promise<void> {\n try {\n const result = await checkForUpdates(packageJson.version)\n\n if (result.updateAvailable) {\n console.log(`\\n⬆️ Update available: ${result.currentVersion} → ${result.latestVersion}`)\n console.log(' Updating in background...\\n')\n performBackgroundUpdate()\n }\n } catch (error) {\n // Silently ignore - update check is non-critical\n }\n }\n\n // EP738: Removed startHttpServer - device info now flows through WebSocket broadcast + database\n\n /**\n * Register IPC command handlers\n */\n private registerIPCHandlers(): void {\n // Ping - health check\n this.ipcServer.on('ping', async () => {\n return { status: 'ok' }\n })\n\n // Status - get daemon status\n // EP726: Now includes deviceId (UUID) for unified device identification\n // EP738: Added hostname, platform, arch for status command (HTTP server removed)\n // EP776: Use liveConnections (actual WebSocket state) instead of connections Map\n this.ipcServer.on('status', async () => {\n const projects = getAllProjects().map(p => ({\n id: p.id,\n path: p.path,\n name: p.name,\n // EP843: Use actual WebSocket state instead of liveConnections Set\n // This is more reliable as it checks the real connection state\n connected: this.isWebSocketOpen(p.path),\n // Keep liveConnections for backwards compatibility and debugging\n liveConnectionsHas: this.liveConnections.has(p.path),\n }))\n\n return {\n running: true,\n machineId: this.machineId,\n deviceId: this.deviceId, // EP726: UUID for unified device identification\n hostname: os.hostname(),\n platform: os.platform(),\n arch: os.arch(),\n projects,\n }\n })\n\n // EP734: Add project - now blocking, waits for connection to complete\n // This eliminates the need for polling from the client side\n // EP813: Added retry with exponential backoff for reliability\n this.ipcServer.on('add-project', async (params: { projectId: string; projectPath: string }) => {\n const { projectId, projectPath } = params\n trackProject(projectId, projectPath)\n\n const MAX_RETRIES = 3\n const INITIAL_DELAY = 1000 // 1 second\n let lastError: string = ''\n\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n try {\n // Await connection - blocks until connected or fails\n await this.connectProject(projectId, projectPath)\n\n // EP805: Verify connection is actually healthy before returning success\n const isHealthy = this.isConnectionHealthy(projectPath)\n if (!isHealthy) {\n console.warn(`[Daemon] Connection completed but not healthy for ${projectPath}`)\n lastError = 'Connection established but not healthy'\n // Don't retry for this - it's a state issue, not a transient failure\n return { success: false, connected: false, error: lastError }\n }\n\n return { success: true, connected: true }\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] Connection attempt ${attempt}/${MAX_RETRIES} failed:`, lastError)\n\n if (attempt < MAX_RETRIES) {\n // EP813: Exponential backoff: 1s, 2s, 4s\n const delay = INITIAL_DELAY * Math.pow(2, attempt - 1)\n console.log(`[Daemon] Retrying in ${delay / 1000}s...`)\n await new Promise(resolve => setTimeout(resolve, delay))\n\n // Clean up before retry\n await this.disconnectProject(projectPath)\n }\n }\n }\n\n // All retries exhausted\n return { success: false, connected: false, error: `Failed after ${MAX_RETRIES} attempts: ${lastError}` }\n })\n\n // EP734: Removed connection-status handler - no longer needed with blocking add-project\n\n // Remove project\n this.ipcServer.on('remove-project', async (params: { projectPath: string }) => {\n const { projectPath } = params\n await this.disconnectProject(projectPath)\n untrackProject(projectPath)\n return { success: true }\n })\n\n // Connect project\n this.ipcServer.on('connect-project', async (params: { projectPath: string }) => {\n const { projectPath } = params\n const project = getAllProjects().find(p => p.path === projectPath)\n if (!project) {\n throw new Error('Project not tracked')\n }\n await this.connectProject(project.id, projectPath)\n return { success: true }\n })\n\n // Disconnect project\n this.ipcServer.on('disconnect-project', async (params: { projectPath: string }) => {\n const { projectPath } = params\n await this.disconnectProject(projectPath)\n return { success: true }\n })\n\n // Shutdown\n this.ipcServer.on('shutdown', async () => {\n console.log('[Daemon] Shutdown requested via IPC')\n await this.shutdown()\n return { success: true }\n })\n\n // EP805: Verify connection health - checks both Map and liveConnections Set\n // EP843: Also includes actual WebSocket state for more accurate diagnostics\n this.ipcServer.on('verify-health', async () => {\n const projects = getAllProjects().map(p => ({\n id: p.id,\n path: p.path,\n name: p.name,\n inConnectionsMap: this.connections.has(p.path),\n inLiveConnections: this.liveConnections.has(p.path),\n // EP843: Add actual WebSocket state\n wsOpen: this.isWebSocketOpen(p.path),\n isHealthy: this.isConnectionHealthy(p.path),\n }))\n\n const healthyCount = projects.filter(p => p.wsOpen).length // EP843: Use actual WS state\n const staleCount = projects.filter(p => p.inConnectionsMap && !p.wsOpen).length // EP843: Stale if in Map but WS not open\n\n return {\n totalProjects: projects.length,\n healthyConnections: healthyCount,\n staleConnections: staleCount,\n projects,\n }\n })\n\n // EP846-6: Verify connection with server - queries /api/cli/status\n // This is the authoritative check - local state can be stale\n this.ipcServer.on('verify-server-connection', async () => {\n const config = await loadConfig()\n if (!config?.access_token || !config?.api_url) {\n return {\n verified: false,\n error: 'No authentication configured',\n localConnected: false,\n serverConnected: false,\n }\n }\n\n // Check local state - EP843: Use actual WebSocket state\n const projects = getAllProjects()\n const localConnected = projects.some(p => this.isWebSocketOpen(p.path))\n\n // Query server for its view\n let serverConnected = false\n let serverMachineId: string | null = null\n let serverError: string | null = null\n\n try {\n const response = await fetch(`${config.api_url}/api/cli/status`, {\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (response.ok) {\n const data = await response.json() as { connected?: boolean; machine_id?: string }\n serverConnected = data.connected === true\n serverMachineId = data.machine_id || null\n } else {\n serverError = `Server returned ${response.status}`\n }\n } catch (err) {\n serverError = err instanceof Error ? err.message : 'Network error'\n }\n\n // Check if server sees THIS machine\n const machineMatch = serverMachineId === this.machineId\n\n return {\n verified: true,\n localConnected,\n serverConnected,\n machineMatch,\n machineId: this.machineId,\n serverMachineId,\n serverError,\n // Overall status: both local and server must agree\n actuallyConnected: localConnected && serverConnected && machineMatch,\n }\n })\n\n // EP823: Tunnel status - get all running tunnels\n // Returns tunnel info from in-memory TunnelManager\n this.ipcServer.on('tunnel-status', async () => {\n const tunnelManager = getTunnelManager()\n const tunnels = tunnelManager.getAllTunnels()\n return { tunnels }\n })\n\n // EP823: Tunnel stop - stop a specific tunnel by module UID\n // Stops the tunnel and clears the URL from the API\n this.ipcServer.on('tunnel-stop', async (params: { moduleUid: string }) => {\n const { moduleUid } = params\n\n // Validate input\n if (!moduleUid) {\n return { success: false, error: 'Module UID is required' }\n }\n\n const tunnelManager = getTunnelManager()\n\n // Check if tunnel exists\n if (!tunnelManager.hasTunnel(moduleUid)) {\n return { success: false, error: 'No tunnel found for this module' }\n }\n\n // Stop tunnel and dev server\n await tunnelManager.stopTunnel(moduleUid)\n await stopDevServer(moduleUid)\n\n // EP823: Clear tunnel URL from the API using shared utility\n await clearTunnelUrl(moduleUid)\n // EP911: Clear cached health status\n this.lastReportedHealthStatus.delete(moduleUid)\n this.tunnelHealthFailures.delete(moduleUid)\n console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`)\n\n return { success: true }\n })\n\n // EP932: Dev server restart - restart dev server for a module\n this.ipcServer.on('dev-server-restart', async (params: { moduleUid: string }) => {\n const { moduleUid } = params\n\n if (!moduleUid) {\n return { success: false, error: 'Module UID is required' }\n }\n\n console.log(`[Daemon] EP932: Dev server restart requested for ${moduleUid}`)\n\n const result = await restartDevServer(moduleUid)\n return result\n })\n\n // EP932: Dev server status - get status of all running dev servers\n this.ipcServer.on('dev-server-status', async () => {\n const status = getDevServerStatus()\n return { success: true, servers: status }\n })\n }\n\n /**\n * Restore WebSocket connections for tracked projects\n */\n private async restoreConnections(): Promise<void> {\n const projects = getAllProjects()\n\n for (const project of projects) {\n try {\n await this.connectProject(project.id, project.path)\n } catch (error) {\n console.error(`[Daemon] Failed to restore connection for ${project.name}:`, error)\n }\n }\n }\n\n /**\n * EP805: Check if a connection is healthy (exists AND is live)\n * A connection can exist in the Map but be dead if WebSocket disconnected\n */\n private isConnectionHealthy(projectPath: string): boolean {\n return this.connections.has(projectPath) && this.liveConnections.has(projectPath)\n }\n\n /**\n * EP843: Check if a connection's WebSocket is actually open\n *\n * This checks the actual WebSocket state, not just our tracking Sets.\n * More reliable than liveConnections Set which can become stale.\n *\n * @param projectPath - The project path to check\n * @returns true if WebSocket exists and is in OPEN state\n */\n private isWebSocketOpen(projectPath: string): boolean {\n const connection = this.connections.get(projectPath)\n if (!connection) return false\n\n // Check the actual WebSocket state via EpisodaClient.getStatus()\n return connection.client.getStatus().connected\n }\n\n /**\n * EP843: Acquire a per-module lock for tunnel operations\n *\n * Prevents race conditions between:\n * - Server-orchestrated tunnel_start commands (EP1003)\n * - module_state_changed event handler\n * - Multiple rapid state transitions\n *\n * @param moduleUid - The module UID to lock\n * @param operation - Async operation to run while holding the lock\n * @returns Result of the operation\n */\n private async withTunnelLock<T>(moduleUid: string, operation: () => Promise<T>): Promise<T | null> {\n // Check if there's already an operation in progress for this module\n const existingLock = this.tunnelOperationLocks.get(moduleUid)\n if (existingLock) {\n console.log(`[Daemon] EP843: Tunnel operation already in progress for ${moduleUid}, waiting...`)\n try {\n await existingLock\n } catch {\n // Previous operation failed, but we can proceed\n }\n }\n\n // Create a new lock for this operation\n let releaseLock: () => void\n const lockPromise = new Promise<void>(resolve => {\n releaseLock = resolve\n })\n this.tunnelOperationLocks.set(moduleUid, lockPromise)\n\n try {\n return await operation()\n } finally {\n releaseLock!()\n // Only delete if this is still our lock (prevents race with new lock)\n if (this.tunnelOperationLocks.get(moduleUid) === lockPromise) {\n this.tunnelOperationLocks.delete(moduleUid)\n }\n }\n }\n\n /**\n * Connect to a project's WebSocket\n */\n private async connectProject(projectId: string, projectPath: string): Promise<void> {\n // EP805: Check BOTH connections Map AND liveConnections Set\n // A stale connection (in Map but not in Set) means WebSocket died\n // EP813: Also check pendingConnections to avoid race condition with restoreConnections\n if (this.connections.has(projectPath)) {\n if (this.liveConnections.has(projectPath)) {\n console.log(`[Daemon] Already connected to ${projectPath}`)\n return\n }\n if (this.pendingConnections.has(projectPath)) {\n // EP813: Connection is still in progress (waiting for auth_success)\n // Wait for it to complete rather than killing it\n console.log(`[Daemon] Connection in progress for ${projectPath}, waiting...`)\n // Wait up to 35s for pending connection to complete (30s auth timeout + 5s buffer)\n const maxWait = 35000\n const startTime = Date.now()\n while (this.pendingConnections.has(projectPath) && Date.now() - startTime < maxWait) {\n await new Promise(resolve => setTimeout(resolve, 500))\n }\n // Check if it succeeded\n if (this.liveConnections.has(projectPath)) {\n console.log(`[Daemon] Pending connection succeeded for ${projectPath}`)\n return\n }\n // If still pending after timeout, treat as stale\n console.warn(`[Daemon] Pending connection timed out for ${projectPath}`)\n }\n // EP805: Stale connection detected - clean up and reconnect\n console.warn(`[Daemon] Stale connection detected for ${projectPath}, forcing reconnection`)\n await this.disconnectProject(projectPath)\n }\n\n // Load auth token from config\n const config = await loadConfig()\n if (!config || !config.access_token) {\n throw new Error('No access token found. Please run: episoda auth')\n }\n\n // EP734: Removed pre-flight health check - WebSocket will fail naturally if server is down\n // This saves ~200-500ms on every connection attempt\n\n // EP734: Determine server URL using cached settings (avoids network call)\n // Priority: 1. Cached local_server_url, 2. Global config api_url, 3. Default production\n let serverUrl = config.api_url || process.env.EPISODA_API_URL || 'https://episoda.dev'\n\n // Use cached local_server_url if available\n if (config.project_settings?.local_server_url) {\n serverUrl = config.project_settings.local_server_url\n console.log(`[Daemon] Using cached server URL: ${serverUrl}`)\n }\n\n // EP593: Connect to WebSocket server on port 3001 (standalone WebSocket server)\n const serverUrlObj = new URL(serverUrl)\n const wsProtocol = serverUrlObj.protocol === 'https:' ? 'wss:' : 'ws:'\n const wsPort = process.env.EPISODA_WS_PORT || '3001'\n const wsUrl = `${wsProtocol}//${serverUrlObj.hostname}:${wsPort}`\n console.log(`[Daemon] Connecting to ${wsUrl} for project ${projectId}...`)\n\n // Create EpisodaClient (handles auth, reconnection, heartbeat)\n const client = new EpisodaClient()\n\n // Create GitExecutor for this project\n const gitExecutor = new GitExecutor()\n\n // Store connection\n const connection: ActiveConnection = {\n projectId,\n projectPath,\n client,\n gitExecutor,\n }\n this.connections.set(projectPath, connection)\n // EP813: Mark as pending until auth_success is received\n this.pendingConnections.add(projectPath)\n\n // Register command handler\n client.on('command', async (message) => {\n if (message.type === 'command' && message.command) {\n console.log(`[Daemon] Received command for ${projectId}:`, message.command)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n try {\n // Execute git command with working directory\n // EP971: All projects use worktree architecture\n // - Commands with worktreePath run in that worktree\n // - Commands without worktreePath run in .bare/ (the git directory)\n const gitCmd = message.command as GitCommand\n const bareRepoPath = path.join(projectPath, '.bare')\n\n // Determine working directory: specific worktree or .bare/\n const cwd = gitCmd.worktreePath || bareRepoPath\n\n if (gitCmd.worktreePath) {\n console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`)\n } else {\n console.log(`[Daemon] Running git command in bare repo: ${bareRepoPath}`)\n }\n\n // EP1002: Handle worktree_setup specially - needs daemon-specific logic\n if (gitCmd.action === 'worktree_setup') {\n const wtManager = new WorktreeManager(projectPath)\n await wtManager.initialize()\n\n // EP1002: Add timeout to prevent setup from hanging indefinitely\n // npm install has its own 10min timeout, but other operations might hang\n const SETUP_TIMEOUT_MS = 15 * 60 * 1000 // 15 minutes total\n const setupPromise = this.handleWorktreeSetup(gitCmd, projectPath, wtManager)\n const timeoutPromise = new Promise<ExecutionResult>((_, reject) =>\n setTimeout(() => reject(new Error('Worktree setup timed out after 15 minutes')), SETUP_TIMEOUT_MS)\n )\n\n let setupResult: ExecutionResult\n try {\n setupResult = await Promise.race([setupPromise, timeoutPromise])\n } catch (timeoutError) {\n setupResult = {\n success: false,\n error: 'COMMAND_TIMEOUT',\n output: timeoutError instanceof Error ? timeoutError.message : 'Setup timed out'\n }\n }\n\n await client.send({\n type: 'result',\n commandId: message.id!,\n result: setupResult\n })\n console.log(`[Daemon] EP1002: Worktree setup completed for ${gitCmd.moduleUid}:`, setupResult.success ? 'success' : 'failed')\n return\n }\n\n const result = await gitExecutor.execute(gitCmd, {\n cwd\n })\n\n // Send result back\n await client.send({\n type: 'result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] Command completed for ${projectId}:`, result.success ? 'success' : 'failed')\n\n // EP950: Cleanup stale commits after successful push to main\n if (result.success && gitCmd.action === 'push' && gitCmd.branch === 'main') {\n // Run cleanup asynchronously (don't block the response)\n cleanupStaleCommits(projectPath).then(cleanupResult => {\n if (cleanupResult.deleted_count > 0) {\n console.log(`[Daemon] EP950: Cleaned up ${cleanupResult.deleted_count} stale commit(s) after push to main`)\n }\n }).catch(err => {\n console.warn('[Daemon] EP950: Cleanup after push failed:', err.message)\n })\n }\n } catch (error) {\n // Send error result\n await client.send({\n type: 'result',\n commandId: message.id!,\n result: {\n success: false,\n error: 'UNKNOWN_ERROR',\n output: error instanceof Error ? error.message : String(error)\n }\n })\n\n console.error(`[Daemon] Command execution error for ${projectId}:`, error)\n }\n }\n })\n\n // EP808: Register remote command handler for file/exec operations\n client.on('remote_command', async (message) => {\n if (message.type === 'remote_command' && message.command) {\n const cmd = message.command as RemoteCommand\n console.log(`[Daemon] Received remote command for ${projectId}:`, cmd.action)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n try {\n let result\n switch (cmd.action) {\n case 'file:read':\n result = await handleFileRead(cmd, projectPath)\n break\n case 'file:write':\n result = await handleFileWrite(cmd, projectPath)\n break\n case 'file:edit':\n result = await handleFileEdit(cmd, projectPath)\n break\n case 'file:delete':\n result = await handleFileDelete(cmd, projectPath)\n break\n case 'file:mkdir':\n result = await handleFileMkdir(cmd, projectPath)\n break\n case 'file:list':\n result = await handleFileList(cmd, projectPath)\n break\n case 'file:search':\n result = await handleFileSearch(cmd, projectPath)\n break\n case 'file:grep':\n result = await handleFileGrep(cmd, projectPath)\n break\n case 'exec':\n result = await handleExec(cmd, projectPath)\n break\n default:\n result = {\n success: false,\n error: `Unknown remote command action: ${(cmd as any).action}`\n }\n }\n\n // Send result back\n await client.send({\n type: 'remote_result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] Remote command ${cmd.action} completed for ${projectId}:`, result.success ? 'success' : 'failed')\n } catch (error) {\n // Send error result\n await client.send({\n type: 'remote_result',\n commandId: message.id!,\n result: {\n success: false,\n error: error instanceof Error ? error.message : String(error)\n }\n })\n\n console.error(`[Daemon] Remote command execution error for ${projectId}:`, error)\n }\n }\n })\n\n // EP672: Register tunnel command handler for local dev preview\n client.on('tunnel_command', async (message) => {\n if (message.type === 'tunnel_command' && message.command) {\n const cmd = message.command as TunnelCommand\n console.log(`[Daemon] Received tunnel command for ${projectId}:`, cmd.action)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n try {\n const tunnelManager = getTunnelManager()\n let result: TunnelCommandResult\n\n if (cmd.action === 'start') {\n // EP818: Respond immediately - tunnel starts asynchronously (like cloud preview)\n // This prevents blocking the workflow transition\n\n // EP973: Resolve worktree path for this module (fix: was using projectPath which is project root)\n const worktree = await getWorktreeInfoForModule(cmd.moduleUid)\n if (!worktree) {\n console.error(`[Daemon] EP973: Cannot resolve worktree path for ${cmd.moduleUid}`)\n // Send error result immediately\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result: { success: false, error: 'Cannot resolve worktree path - missing config slugs' }\n })\n return\n }\n\n if (!worktree.exists) {\n console.error(`[Daemon] EP973: Worktree not found at ${worktree.path}`)\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result: { success: false, error: `Worktree not found at ${worktree.path}` }\n })\n return\n }\n\n console.log(`[Daemon] EP973: Using worktree path ${worktree.path} for ${cmd.moduleUid}`)\n\n // EP973: Use worktree path for port detection (was projectPath)\n const port = cmd.port || detectDevPort(worktree.path)\n const previewUrl = `https://${cmd.moduleUid.toLowerCase()}-${cmd.projectUid.toLowerCase()}.episoda.site`\n\n // EP818: Helper to report tunnel status to the API\n const reportTunnelStatus = async (data: {\n tunnel_url?: string | null\n tunnel_error?: string | null\n tunnel_started_at?: string | null\n }) => {\n const config = await loadConfig()\n if (config?.access_token) {\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetch(`${apiUrl}/api/modules/${cmd.moduleUid}/tunnel`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(data)\n })\n\n if (response.ok) {\n console.log(`[Daemon] Tunnel status reported for ${cmd.moduleUid}`)\n } else {\n console.warn(`[Daemon] Failed to report tunnel status: ${response.statusText}`)\n }\n } catch (reportError) {\n console.warn(`[Daemon] Error reporting tunnel status:`, reportError)\n }\n }\n }\n\n // Fire off async work - dev server + tunnel startup with retry\n // Don't await - let it run in background\n ;(async () => {\n const MAX_RETRIES = 3\n // EP953: Increased retry delay for more reliable tunnel startup after cleanup\n const RETRY_DELAY_MS = 3000\n\n // EP818: Report that tunnel is starting (sets tunnel_started_at for UI timeout)\n await reportTunnelStatus({\n tunnel_started_at: new Date().toISOString(),\n tunnel_error: null // Clear any previous error\n })\n\n try {\n // Initialize tunnel manager if needed\n await tunnelManager.initialize()\n\n // EP973: Get custom dev server script from project settings\n const devConfig = await loadConfig()\n const devServerScript = devConfig?.project_settings?.worktree_dev_server_script\n\n // EP818: Ensure dev server is running before starting tunnel\n // EP973: Use worktree.path instead of projectPath\n console.log(`[Daemon] EP973: Ensuring dev server is running in ${worktree.path} on port ${port}...`)\n const devServerResult = await ensureDevServer(worktree.path, port, cmd.moduleUid, devServerScript)\n if (!devServerResult.success) {\n const errorMsg = `Dev server failed to start: ${devServerResult.error}`\n console.error(`[Daemon] ${errorMsg}`)\n await reportTunnelStatus({ tunnel_error: errorMsg })\n return\n }\n console.log(`[Daemon] Dev server ready on port ${port}`)\n\n // EP818: Start tunnel with retry logic\n let lastError: string | undefined\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n console.log(`[Daemon] Starting tunnel (attempt ${attempt}/${MAX_RETRIES})...`)\n\n const startResult = await tunnelManager.startTunnel({\n moduleUid: cmd.moduleUid,\n port,\n onUrl: async (url) => {\n // EP672-9: Report URL on both initial start and reconnections\n console.log(`[Daemon] Tunnel URL for ${cmd.moduleUid}: ${url}`)\n await reportTunnelStatus({\n tunnel_url: url,\n tunnel_error: null // Clear error on success\n })\n },\n onStatusChange: (status, error) => {\n if (status === 'error') {\n console.error(`[Daemon] Tunnel error for ${cmd.moduleUid}: ${error}`)\n // Report error asynchronously\n reportTunnelStatus({ tunnel_error: error || 'Tunnel connection error' })\n } else if (status === 'reconnecting') {\n console.log(`[Daemon] Tunnel reconnecting for ${cmd.moduleUid}...`)\n }\n }\n })\n\n if (startResult.success) {\n console.log(`[Daemon] Tunnel started successfully for ${cmd.moduleUid}`)\n return // Success - exit retry loop\n }\n\n lastError = startResult.error\n console.warn(`[Daemon] Tunnel start attempt ${attempt} failed: ${lastError}`)\n\n if (attempt < MAX_RETRIES) {\n console.log(`[Daemon] Retrying in ${RETRY_DELAY_MS}ms...`)\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS))\n }\n }\n\n // All retries failed\n const errorMsg = `Tunnel failed after ${MAX_RETRIES} attempts: ${lastError}`\n console.error(`[Daemon] ${errorMsg}`)\n await reportTunnelStatus({ tunnel_error: errorMsg })\n\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] Async tunnel startup error:`, error)\n await reportTunnelStatus({ tunnel_error: `Unexpected error: ${errorMsg}` })\n }\n })()\n\n // Respond immediately with \"starting\" status\n result = {\n success: true,\n previewUrl,\n // Note: actual tunnel URL will be reported via API when ready\n }\n } else if (cmd.action === 'stop') {\n await tunnelManager.stopTunnel(cmd.moduleUid)\n\n // EP818: Stop the dev server we started for this module\n await stopDevServer(cmd.moduleUid)\n\n // Clear tunnel URL from the server\n const config = await loadConfig()\n if (config?.access_token) {\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n await fetch(`${apiUrl}/api/modules/${cmd.moduleUid}/tunnel`, {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`\n }\n })\n console.log(`[Daemon] Tunnel URL cleared for ${cmd.moduleUid}`)\n } catch {\n // Ignore cleanup errors\n }\n }\n\n result = { success: true }\n } else {\n result = {\n success: false,\n error: `Unknown tunnel action: ${(cmd as any).action}`\n }\n }\n\n // Send result back\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] Tunnel command ${cmd.action} completed for ${cmd.moduleUid}:`, result.success ? 'success' : 'failed')\n } catch (error) {\n // Send error result\n await client.send({\n type: 'tunnel_result',\n commandId: message.id!,\n result: {\n success: false,\n error: error instanceof Error ? error.message : String(error)\n }\n })\n\n console.error(`[Daemon] Tunnel command execution error:`, error)\n }\n }\n })\n\n // EP912: Register agent command handler for local Claude Code execution\n client.on('agent_command', async (message) => {\n if (message.type === 'agent_command' && message.command) {\n const cmd = message.command as AgentCommand\n console.log(`[Daemon] EP912: Received agent command for ${projectId}:`, cmd.action)\n\n // EP605: Update activity timestamp to reset idle detection\n client.updateActivity()\n\n // Helper to create streaming callbacks - avoids duplication between start/message\n const createStreamingCallbacks = (sessionId: string, commandId: string) => ({\n onChunk: async (chunk: string) => {\n try {\n await client.send({\n type: 'agent_result',\n commandId,\n result: { success: true, status: 'chunk', sessionId, chunk }\n })\n } catch (sendError) {\n // EP912: Handle WebSocket disconnection mid-stream\n console.error(`[Daemon] EP912: Failed to send chunk (WebSocket may be disconnected):`, sendError)\n }\n },\n onComplete: async (claudeSessionId?: string) => {\n try {\n await client.send({\n type: 'agent_result',\n commandId,\n result: { success: true, status: 'complete', sessionId, claudeSessionId }\n })\n } catch (sendError) {\n console.error(`[Daemon] EP912: Failed to send complete (WebSocket may be disconnected):`, sendError)\n }\n },\n onError: async (error: string) => {\n try {\n await client.send({\n type: 'agent_result',\n commandId,\n result: { success: false, status: 'error', sessionId, error }\n })\n } catch (sendError) {\n console.error(`[Daemon] EP912: Failed to send error (WebSocket may be disconnected):`, sendError)\n }\n }\n })\n\n try {\n const agentManager = getAgentManager()\n await agentManager.initialize()\n\n let result: AgentResult\n\n if (cmd.action === 'start') {\n // Start a new agent session and send initial message\n const callbacks = createStreamingCallbacks(cmd.sessionId, message.id!)\n\n // EP959-10: Resolve worktree path - agent should run in module's worktree\n let agentWorkingDir = projectPath\n if (cmd.moduleUid) {\n const worktreeInfo = await getWorktreeInfoForModule(cmd.moduleUid)\n if (worktreeInfo?.exists) {\n agentWorkingDir = worktreeInfo.path\n console.log(`[Daemon] EP959: Agent for ${cmd.moduleUid} in worktree: ${agentWorkingDir}`)\n }\n }\n\n const startResult = await agentManager.startSession({\n sessionId: cmd.sessionId,\n moduleId: cmd.moduleId,\n moduleUid: cmd.moduleUid,\n projectPath: agentWorkingDir,\n message: cmd.message,\n credentials: cmd.credentials,\n systemPrompt: cmd.systemPrompt,\n ...callbacks\n })\n\n result = {\n success: startResult.success,\n status: startResult.success ? 'started' : 'error',\n sessionId: cmd.sessionId,\n error: startResult.error\n }\n\n } else if (cmd.action === 'message') {\n // Send message to existing session\n const callbacks = createStreamingCallbacks(cmd.sessionId, message.id!)\n const sendResult = await agentManager.sendMessage({\n sessionId: cmd.sessionId,\n message: cmd.message,\n isFirstMessage: false,\n claudeSessionId: cmd.claudeSessionId,\n ...callbacks\n })\n\n result = {\n success: sendResult.success,\n status: sendResult.success ? 'started' : 'error',\n sessionId: cmd.sessionId,\n error: sendResult.error\n }\n\n } else if (cmd.action === 'abort') {\n await agentManager.abortSession(cmd.sessionId)\n result = { success: true, status: 'aborted', sessionId: cmd.sessionId }\n\n } else if (cmd.action === 'stop') {\n await agentManager.stopSession(cmd.sessionId)\n result = { success: true, status: 'complete', sessionId: cmd.sessionId }\n\n } else {\n result = {\n success: false,\n status: 'error',\n sessionId: (cmd as any).sessionId || 'unknown',\n error: `Unknown agent action: ${(cmd as any).action}`\n }\n }\n\n // Send initial result back\n await client.send({\n type: 'agent_result',\n commandId: message.id!,\n result\n })\n\n console.log(`[Daemon] EP912: Agent command ${cmd.action} completed for session ${cmd.action === 'start' || cmd.action === 'message' ? cmd.sessionId : (cmd as any).sessionId}`)\n } catch (error) {\n // Send error result\n try {\n await client.send({\n type: 'agent_result',\n commandId: message.id!,\n result: {\n success: false,\n status: 'error',\n sessionId: (cmd as any).sessionId || 'unknown',\n error: error instanceof Error ? error.message : String(error)\n }\n })\n } catch (sendError) {\n console.error(`[Daemon] EP912: Failed to send error result (WebSocket may be disconnected):`, sendError)\n }\n\n console.error(`[Daemon] EP912: Agent command execution error:`, error)\n }\n }\n })\n\n // Register shutdown handler - allows server to request graceful shutdown\n // EP613: Only exit on user-requested shutdowns, reconnect for server restarts\n client.on('shutdown', async (message) => {\n const shutdownMessage = message as { message?: string; reason?: string }\n const reason = shutdownMessage.reason || 'unknown'\n\n console.log(`[Daemon] Received shutdown request from server for ${projectId}`)\n console.log(`[Daemon] Reason: ${reason} - ${shutdownMessage.message || 'No message'}`)\n\n if (reason === 'user_requested') {\n // User explicitly requested disconnect - exit cleanly\n console.log(`[Daemon] User requested disconnect, shutting down...`)\n await this.cleanupAndExit()\n } else {\n // Server restart or deployment - don't exit, let WebSocket reconnection handle it\n console.log(`[Daemon] Server shutdown (${reason}), will reconnect automatically...`)\n // The WebSocket close event will trigger reconnection via EpisodaClient\n // We don't call process.exit() here - just let the connection close naturally\n }\n })\n\n // Register auth success handler\n client.on('auth_success', async (message) => {\n console.log(`[Daemon] Authenticated for project ${projectId}`)\n touchProject(projectPath)\n\n // EP701: Mark connection as live (for /device-info endpoint)\n this.liveConnections.add(projectPath)\n // EP813: No longer pending - auth succeeded\n this.pendingConnections.delete(projectPath)\n\n // EP812: Removed identity server status update - browser uses cookie-based pairing\n\n // EP595: Configure git with userId and workspaceId for post-checkout hook\n // EP655: Also configure machineId for device isolation\n // EP661: Cache deviceName for browser identification\n // EP726: Cache deviceId (UUID) for unified device identification\n // EP735: Cache flyMachineId for sticky session routing\n const authMessage = message as { userId?: string; workspaceId?: string; deviceName?: string; deviceId?: string; flyMachineId?: string }\n if (authMessage.userId && authMessage.workspaceId) {\n // EP726: Pass deviceId to configureGitUser so it's stored in git config\n await this.configureGitUser(projectPath, authMessage.userId, authMessage.workspaceId, this.machineId, projectId, authMessage.deviceId)\n // EP610: Install git hooks after configuring git user\n await this.installGitHooks(projectPath)\n }\n\n // EP661: Store device name for logging and config\n if (authMessage.deviceName) {\n this.deviceName = authMessage.deviceName\n console.log(`[Daemon] Device name: ${this.deviceName}`)\n }\n\n // EP726: Cache device UUID for unified device identification\n // Persist to config for future use\n if (authMessage.deviceId) {\n this.deviceId = authMessage.deviceId\n console.log(`[Daemon] Device ID (UUID): ${this.deviceId}`)\n\n // Persist deviceId to config file so it's available on daemon restart\n await this.cacheDeviceId(authMessage.deviceId)\n }\n\n // EP735: Cache Fly.io machine ID for sticky session routing\n // Only set when server is running on Fly.io, null otherwise\n if (authMessage.flyMachineId) {\n this.flyMachineId = authMessage.flyMachineId\n console.log(`[Daemon] Fly Machine ID: ${this.flyMachineId}`)\n }\n\n // EP964: Sync project settings (including worktree_env_vars) from server\n // Run in background to avoid blocking auth\n this.syncProjectSettings(projectId).catch(err => {\n console.warn('[Daemon] EP964: Settings sync failed:', err.message)\n })\n\n // EP995: Sync project path to server (local_machine.project_paths)\n // Run in background to avoid blocking auth\n this.syncMachineProjectPath(projectId, projectPath).catch(err => {\n console.warn('[Daemon] EP995: Project path sync failed:', err.message)\n })\n\n // EP1003: autoStartTunnelsForProject removed - server now orchestrates via reconciliation\n // Recovery flow: daemon sends reconciliation_report → server sends tunnel_start commands\n\n // EP950: Cleanup stale commits on connect/reconnect\n // This catches pushes that happened outside the daemon (e.g., user ran git push directly)\n cleanupStaleCommits(projectPath).then(cleanupResult => {\n if (cleanupResult.deleted_count > 0) {\n console.log(`[Daemon] EP950: Cleaned up ${cleanupResult.deleted_count} stale commit(s) on connect`)\n }\n }).catch(err => {\n // Non-fatal - just log and continue\n console.warn('[Daemon] EP950: Cleanup on connect failed:', err.message)\n })\n\n // EP1003: Reconcile worktrees on connect/reconnect\n // Reports local state to server, which sends commands as needed\n this.reconcileWorktrees(projectId, projectPath, client).catch(err => {\n console.warn('[Daemon] EP1003: Reconciliation report failed:', err.message)\n })\n })\n\n // EP843: Register module state change handler for push-based tunnel management\n // Replaces polling-based tunnel sync with real-time event-driven approach\n client.on('module_state_changed', async (message) => {\n if (message.type === 'module_state_changed') {\n const { moduleUid, state, previousState, branchName, devMode, checkoutMachineId } = message\n\n console.log(`[Daemon] EP843: Module ${moduleUid} state changed: ${previousState} → ${state}`)\n\n // EP1003: Autonomous tunnel start/stop removed - server now orchestrates via tunnel_command\n // This handler now only logs state changes for visibility.\n // Tunnel lifecycle is controlled by:\n // - ReadyToDoingOrchestrator sends tunnel_start after successful transition\n // - ReviewToDoneOrchestrator sends tunnel_stop before worktree removal\n // - Reconciliation will report status and server will send commands as needed\n\n // Only log relevant events for local dev mode\n if (devMode !== 'local') {\n console.log(`[Daemon] EP1003: State change for non-local module ${moduleUid} (mode: ${devMode || 'unknown'})`)\n return\n }\n\n // Validate checkout_machine_id\n if (checkoutMachineId && checkoutMachineId !== this.deviceId) {\n console.log(`[Daemon] EP1003: State change for ${moduleUid} handled by different machine: ${checkoutMachineId}`)\n return\n }\n\n // Log the transition for observability\n if (previousState === 'ready' && state === 'doing') {\n console.log(`[Daemon] EP1003: Module ${moduleUid} entering doing - server will send tunnel_start`)\n } else if (state === 'done') {\n console.log(`[Daemon] EP1003: Module ${moduleUid} entering done - server will send tunnel_stop`)\n }\n }\n })\n\n // Register error handler\n client.on('error', (message) => {\n console.error(`[Daemon] Server error for ${projectId}:`, message)\n })\n\n // EP701: Register disconnected handler to track connection state\n // Removes from liveConnections immediately so /device-info returns accurate state\n // Keeps entry in connections map for potential reconnection (client handles reconnect internally)\n client.on('disconnected', (event) => {\n const disconnectEvent = event as DisconnectEvent\n console.log(`[Daemon] Connection closed for ${projectId}: code=${disconnectEvent.code}, willReconnect=${disconnectEvent.willReconnect}`)\n\n // Always remove from liveConnections - connection is dead until reconnected\n this.liveConnections.delete(projectPath)\n\n // EP812: Removed identity server status update - browser uses cookie-based pairing\n\n // Only remove from connections map if we won't reconnect (intentional disconnect)\n if (!disconnectEvent.willReconnect) {\n this.connections.delete(projectPath)\n console.log(`[Daemon] Removed connection for ${projectPath} from map`)\n }\n })\n\n try {\n // EP601: Read daemon PID from file\n let daemonPid: number | undefined\n try {\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n const pidStr = fs.readFileSync(pidPath, 'utf-8').trim()\n daemonPid = parseInt(pidStr, 10)\n }\n } catch (pidError) {\n console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError)\n }\n\n // EP812: Create promise that resolves when auth_success is received\n // This ensures liveConnections is populated before connectProject returns\n // EP813: Increased timeout from 10s to 30s for reliability during server load\n const authSuccessPromise = new Promise<void>((resolve, reject) => {\n const AUTH_TIMEOUT = 30000 // 30 second timeout for auth\n const timeout = setTimeout(() => {\n reject(new Error('Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds.'))\n }, AUTH_TIMEOUT)\n\n // One-time handler for auth_success\n const authHandler = () => {\n clearTimeout(timeout)\n resolve()\n }\n client.once('auth_success', authHandler)\n\n // Also handle auth errors\n const errorHandler = (message: unknown) => {\n clearTimeout(timeout)\n const errorMsg = message as { message?: string }\n reject(new Error(errorMsg.message || 'Authentication failed'))\n }\n client.once('auth_error', errorHandler)\n })\n\n // Connect with OAuth token, machine ID, and device info (EP596, EP601: includes daemonPid)\n await client.connect(wsUrl, config.access_token, this.machineId, {\n hostname: os.hostname(),\n osPlatform: os.platform(),\n osArch: os.arch(),\n daemonPid\n })\n console.log(`[Daemon] Successfully connected to project ${projectId}`)\n\n // EP812: Wait for auth_success before returning\n // This ensures liveConnections.add() has been called\n await authSuccessPromise\n console.log(`[Daemon] Authentication complete for project ${projectId}`)\n } catch (error) {\n console.error(`[Daemon] Failed to connect to ${projectId}:`, error)\n this.connections.delete(projectPath)\n // EP813: No longer pending - connection failed\n this.pendingConnections.delete(projectPath)\n throw error\n }\n }\n\n /**\n * Disconnect from a project's WebSocket\n */\n private async disconnectProject(projectPath: string): Promise<void> {\n const connection = this.connections.get(projectPath)\n if (!connection) {\n return\n }\n\n // Clear reconnect timer\n if (connection.reconnectTimer) {\n clearTimeout(connection.reconnectTimer)\n }\n\n // Disconnect client (handles WebSocket close gracefully)\n await connection.client.disconnect()\n this.connections.delete(projectPath)\n this.liveConnections.delete(projectPath) // EP701: Also clean up liveConnections\n this.pendingConnections.delete(projectPath) // EP813: Also clean up pendingConnections\n\n console.log(`[Daemon] Disconnected from ${projectPath}`)\n }\n\n /**\n * Setup graceful shutdown handlers\n * EP613: Now uses cleanupAndExit to ensure PID file is removed\n */\n private setupShutdownHandlers(): void {\n const shutdownHandler = async (signal: string) => {\n console.log(`[Daemon] Received ${signal}, shutting down...`)\n await this.cleanupAndExit()\n }\n\n process.on('SIGTERM', () => shutdownHandler('SIGTERM'))\n process.on('SIGINT', () => shutdownHandler('SIGINT'))\n }\n\n /**\n * EP595: Configure git with user and workspace ID for post-checkout hook\n * EP655: Added machineId for device isolation in multi-device environments\n * EP725: Added projectId for main branch badge tracking\n * EP726: Added deviceId (UUID) for unified device identification\n *\n * This stores the IDs in .git/config so the post-checkout hook can\n * update module.checkout_* fields when git operations happen from terminal.\n */\n private async configureGitUser(projectPath: string, userId: string, workspaceId: string, machineId: string, projectId: string, deviceId?: string | null): Promise<void> {\n try {\n const { execSync } = await import('child_process')\n\n // Set git config values in the project's .git/config\n execSync(`git config episoda.userId ${userId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n execSync(`git config episoda.workspaceId ${workspaceId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n // EP655: Set machineId for device isolation in post-checkout hook\n execSync(`git config episoda.machineId ${machineId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n // EP725: Set projectId for main branch badge tracking\n // This ensures main branch checkouts are recorded with project_id\n execSync(`git config episoda.projectId ${projectId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n\n // EP726: Set deviceId (UUID) for unified device identification\n // This allows the post-checkout hook to pass the UUID to the database\n if (deviceId) {\n execSync(`git config episoda.deviceId ${deviceId}`, {\n cwd: projectPath,\n encoding: 'utf8',\n stdio: 'pipe'\n })\n }\n\n console.log(`[Daemon] Configured git for project: episoda.userId=${userId}, machineId=${machineId}, projectId=${projectId}${deviceId ? `, deviceId=${deviceId}` : ''}`)\n } catch (error) {\n // Non-fatal error - git hook just won't work\n console.warn(`[Daemon] Failed to configure git user for ${projectPath}:`, error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP610: Install git hooks from bundled files\n *\n * Installs post-checkout and pre-commit hooks to enable:\n * - Branch tracking (post-checkout updates module.checkout_* fields)\n * - Main branch protection (pre-commit blocks direct commits to main)\n */\n private async installGitHooks(projectPath: string): Promise<void> {\n // EP837: Added post-commit hook for machine-aware commit tracking\n const hooks = ['post-checkout', 'pre-commit', 'post-commit']\n const hooksDir = path.join(projectPath, '.git', 'hooks')\n\n // Ensure hooks directory exists\n if (!fs.existsSync(hooksDir)) {\n console.warn(`[Daemon] Hooks directory not found: ${hooksDir}`)\n return\n }\n\n for (const hookName of hooks) {\n try {\n const hookPath = path.join(hooksDir, hookName)\n\n // Read bundled hook content\n // __dirname in compiled code points to dist/daemon/, hooks are in dist/hooks/\n const bundledHookPath = path.join(__dirname, '..', 'hooks', hookName)\n\n if (!fs.existsSync(bundledHookPath)) {\n console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`)\n continue\n }\n\n const hookContent = fs.readFileSync(bundledHookPath, 'utf-8')\n\n // Check if hook already exists with same content\n if (fs.existsSync(hookPath)) {\n const existingContent = fs.readFileSync(hookPath, 'utf-8')\n if (existingContent === hookContent) {\n // Hook is up to date\n continue\n }\n }\n\n // Write hook file\n fs.writeFileSync(hookPath, hookContent, { mode: 0o755 })\n console.log(`[Daemon] Installed git hook: ${hookName}`)\n } catch (error) {\n // Non-fatal - just log warning\n console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error)\n }\n }\n }\n\n /**\n * EP726: Cache device UUID to config file\n *\n * Persists the device_id (UUID) received from the server so it's available\n * on daemon restart without needing to re-register the device.\n */\n private async cacheDeviceId(deviceId: string): Promise<void> {\n try {\n const config = await loadConfig()\n if (!config) {\n console.warn('[Daemon] Cannot cache device ID - no config found')\n return\n }\n\n // Only update if device_id has changed\n if (config.device_id === deviceId) {\n return\n }\n\n // Update config with device_id and machine_id\n const updatedConfig: EpisodaConfig = {\n ...config,\n device_id: deviceId,\n machine_id: this.machineId\n }\n\n await saveConfig(updatedConfig)\n console.log(`[Daemon] Cached device ID to config: ${deviceId}`)\n } catch (error) {\n console.warn('[Daemon] Failed to cache device ID:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP964: Sync project settings from server on connect/reconnect\n *\n * Fetches worktree_env_vars and other settings from the server\n * and caches them locally for use during worktree checkout.\n */\n private async syncProjectSettings(projectId: string): Promise<void> {\n try {\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetchWithAuth(`${apiUrl}/api/projects/${projectId}/settings`)\n\n if (!response.ok) {\n console.warn(`[Daemon] EP964: Failed to sync settings: ${response.status}`)\n return\n }\n\n // EP973: Settings response now includes project_slug and workspace_slug\n const data = await response.json() as {\n settings?: Record<string, unknown>\n project_slug?: string\n workspace_slug?: string\n }\n const serverSettings = data.settings as Record<string, unknown> | undefined\n\n if (serverSettings) {\n // EP973: Use slugs from settings response if missing locally\n const projectSlug = data.project_slug || config.project_slug\n const workspaceSlug = data.workspace_slug || config.workspace_slug\n\n if (data.project_slug && !config.project_slug) {\n console.log(`[Daemon] EP973: Synced project_slug: ${data.project_slug}`)\n }\n if (data.workspace_slug && !config.workspace_slug) {\n console.log(`[Daemon] EP973: Synced workspace_slug: ${data.workspace_slug}`)\n }\n\n // EP973: Removed worktree_env_vars - now fetched from /api/cli/env-vars on demand\n const updatedConfig: EpisodaConfig = {\n ...config,\n // EP973: Include synced slugs\n project_slug: projectSlug,\n workspace_slug: workspaceSlug,\n project_settings: {\n ...config.project_settings,\n worktree_setup_script: serverSettings.worktree_setup_script as string | undefined,\n worktree_cleanup_script: serverSettings.worktree_cleanup_script as string | undefined,\n worktree_dev_server_script: serverSettings.worktree_dev_server_script as string | undefined,\n // Keep deprecated field for backward compatibility\n worktree_copy_files: serverSettings.worktree_copy_files as string[] | undefined,\n cached_at: Date.now(),\n }\n }\n\n await saveConfig(updatedConfig)\n console.log(`[Daemon] EP973: Project settings synced (slugs: ${projectSlug}/${workspaceSlug})`)\n }\n } catch (error) {\n // Non-fatal - just log and continue\n console.warn('[Daemon] EP964: Failed to sync project settings:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP995: Sync project path to server (local_machine.project_paths)\n *\n * Reports the local filesystem path for this project to the server,\n * enabling server-side visibility into where projects are checked out.\n * Uses atomic RPC to prevent race conditions.\n */\n private async syncMachineProjectPath(projectId: string, projectPath: string): Promise<void> {\n try {\n if (!this.deviceId) {\n console.warn('[Daemon] EP995: Cannot sync project path - deviceId not available')\n return\n }\n\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n const response = await fetchWithAuth(`${apiUrl}/api/account/machines/${this.deviceId}`, {\n method: 'PATCH',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n project_id: projectId,\n project_path: projectPath\n })\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n console.warn(`[Daemon] EP995: Failed to sync project path: ${response.status}`, errorData)\n return\n }\n\n console.log(`[Daemon] EP995: Synced project path to server: ${projectPath}`)\n } catch (error) {\n // Non-fatal - just log and continue\n console.warn('[Daemon] EP995: Failed to sync project path:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP995: Update module worktree status on server\n *\n * Reports worktree setup progress to the server, enabling:\n * - Server-side visibility into worktree state\n * - UI progress display (compatible with EP978)\n * - Reconciliation queries on daemon reconnect\n */\n private async updateModuleWorktreeStatus(\n moduleUid: string,\n status: 'pending' | 'setup' | 'ready' | 'error',\n worktreePath?: string,\n errorMessage?: string\n ): Promise<void> {\n try {\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n const body: Record<string, unknown> = {\n worktree_status: status\n }\n\n if (worktreePath) {\n body.worktree_path = worktreePath\n }\n\n if (status === 'error' && errorMessage) {\n body.worktree_error = errorMessage\n }\n\n // Clear error when transitioning to non-error state\n if (status !== 'error') {\n body.worktree_error = null\n }\n\n const response = await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}`, {\n method: 'PATCH',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(body)\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n console.warn(`[Daemon] EP995: Failed to update worktree status: ${response.status}`, errorData)\n return\n }\n\n console.log(`[Daemon] EP995: Updated module ${moduleUid} worktree_status=${status}`)\n } catch (error) {\n // Non-fatal - local worktree still works\n console.warn('[Daemon] EP995: Failed to update worktree status:', error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP995: Reconcile worktrees on daemon connect/reconnect\n *\n * Figma-style \"fresh snapshot on reconnect\" approach:\n * 1. Query server for modules that should have worktrees on this machine\n * 2. For modules missing local worktrees, create and setup\n * 3. Log orphaned worktrees (local exists but module not in doing/review)\n *\n * This self-healing mechanism catches modules that transitioned\n * while the daemon was disconnected.\n */\n /**\n * EP1003: Report-only reconciliation\n * Daemon reports local state to server, server decides what commands to send.\n * This replaces autonomous worktree creation and tunnel starting.\n */\n private async reconcileWorktrees(projectId: string, projectPath: string, client: EpisodaClient): Promise<void> {\n console.log(`[Daemon] EP1003: Starting reconciliation report for project ${projectId}`)\n\n try {\n if (!this.deviceId) {\n console.log('[Daemon] EP1003: Cannot reconcile - deviceId not available yet')\n return\n }\n\n const config = await loadConfig()\n if (!config) return\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n // Query server for modules that should have worktrees on this machine\n // Modules in doing/review with local dev mode and checked out on this machine\n // EP1003/P2a: Add timeout to prevent reconciliation from hanging\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 10000) // 10s timeout\n\n let modulesResponse: Response\n try {\n modulesResponse = await fetchWithAuth(\n `${apiUrl}/api/modules?state=doing,review&dev_mode=local&checkout_machine_id=${this.deviceId}&project_id=${projectId}`,\n { signal: controller.signal }\n )\n } finally {\n clearTimeout(timeoutId)\n }\n\n if (!modulesResponse.ok) {\n console.warn(`[Daemon] EP1003: Failed to fetch modules for reconciliation: ${modulesResponse.status}`)\n return\n }\n\n const modulesData = await modulesResponse.json() as { modules?: Array<{ id: string; uid: string; branch_name: string; worktree_status?: string; state: string }> }\n const modules = modulesData.modules || []\n\n console.log(`[Daemon] EP1003: Building reconciliation report for ${modules.length} module(s)`)\n\n // Build status report for each module\n const tunnelManager = getTunnelManager()\n await tunnelManager.initialize()\n\n const moduleStatuses: ReconciliationModuleStatus[] = []\n const expectedModuleUids = new Set(modules.map(m => m.uid))\n\n for (const module of modules) {\n const moduleUid = module.uid\n\n // Check local worktree state\n const worktree = await getWorktreeInfoForModule(moduleUid)\n const tunnelRunning = tunnelManager.hasTunnel(moduleUid)\n const tunnelInfo = tunnelManager.getTunnel(moduleUid)\n\n const status: ReconciliationModuleStatus = {\n moduleUid,\n moduleState: module.state as ModuleState,\n worktreeExists: worktree?.exists || false,\n worktreePath: worktree?.path,\n tunnelRunning,\n tunnelPort: tunnelInfo?.port\n }\n\n moduleStatuses.push(status)\n\n // Log status for debugging\n console.log(`[Daemon] EP1003: Module ${moduleUid}: worktree=${status.worktreeExists}, tunnel=${status.tunnelRunning}`)\n }\n\n // EP1003/P1a: Detect orphan tunnels (tunnels for modules not in expected list)\n const allTunnels = tunnelManager.getAllTunnels()\n const orphanTunnels: OrphanTunnelStatus[] = []\n for (const tunnel of allTunnels) {\n if (!expectedModuleUids.has(tunnel.moduleUid)) {\n console.log(`[Daemon] EP1003: Detected orphan tunnel for ${tunnel.moduleUid} (port ${tunnel.port})`)\n orphanTunnels.push({\n moduleUid: tunnel.moduleUid,\n port: tunnel.port\n })\n }\n }\n\n if (orphanTunnels.length > 0) {\n console.log(`[Daemon] EP1003: Reporting ${orphanTunnels.length} orphan tunnel(s) for server cleanup`)\n }\n\n // Build and send reconciliation report\n const report: ReconciliationReport = {\n projectId,\n machineId: this.deviceId,\n modules: moduleStatuses,\n orphanTunnels: orphanTunnels.length > 0 ? orphanTunnels : undefined\n }\n\n console.log(`[Daemon] EP1003: Sending reconciliation report with ${moduleStatuses.length} module(s)`)\n\n // Send report to server via WebSocket\n await client.send({\n type: 'reconciliation_report',\n report\n })\n\n console.log('[Daemon] EP1003: Reconciliation report sent - awaiting server commands')\n } catch (error) {\n console.error('[Daemon] EP1003: Reconciliation error:', error instanceof Error ? error.message : error)\n throw error\n }\n }\n\n /**\n * EP956: Cleanup module worktree when module moves to done\n *\n * Runs asynchronously to reduce burden on the state change handler.\n * Steps:\n * 1. Stop dev server for the module\n * 2. Log worktree path for potential removal (actual removal TBD)\n *\n * Note: Worktree removal requires git worktree remove command, which\n * needs careful handling to avoid data loss. For now, just stop the\n * dev server and log the path.\n */\n private async cleanupModuleWorktree(moduleUid: string): Promise<void> {\n console.log(`[Daemon] EP956: Starting async cleanup for ${moduleUid}`)\n\n try {\n // Stop dev server for this module\n await stopDevServer(moduleUid)\n console.log(`[Daemon] EP956: Dev server stopped for ${moduleUid}`)\n\n // EP994: Actually remove the worktree\n const worktree = await getWorktreeInfoForModule(moduleUid)\n if (worktree?.exists && worktree.path) {\n console.log(`[Daemon] EP994: Removing worktree for ${moduleUid} at ${worktree.path}`)\n\n // Find project root from worktree path\n const projectRoot = await findProjectRoot(worktree.path)\n if (projectRoot) {\n const manager = new WorktreeManager(projectRoot)\n if (await manager.initialize()) {\n // Use force=true since module has transitioned to done\n const result = await manager.removeWorktree(moduleUid, true)\n if (result.success) {\n console.log(`[Daemon] EP994: Successfully removed worktree for ${moduleUid}`)\n } else {\n // Non-fatal - worktree may already be gone or have other issues\n console.warn(`[Daemon] EP994: Could not remove worktree for ${moduleUid}: ${result.error}`)\n }\n } else {\n console.warn(`[Daemon] EP994: Could not initialize WorktreeManager for ${moduleUid}`)\n }\n } else {\n console.warn(`[Daemon] EP994: Could not find project root for ${moduleUid} worktree`)\n }\n } else {\n console.log(`[Daemon] EP994: No worktree to remove for ${moduleUid}`)\n }\n\n // EP995: Clear worktree status on server when module moves to done\n // This allows the module to be picked up fresh if reverted\n try {\n const cleanupConfig = await loadConfig()\n const cleanupApiUrl = cleanupConfig?.api_url || 'https://episoda.dev'\n await fetchWithAuth(`${cleanupApiUrl}/api/modules/${moduleUid}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n worktree_status: null,\n worktree_path: null,\n worktree_error: null\n })\n })\n console.log(`[Daemon] EP995: Cleared worktree status for ${moduleUid}`)\n } catch (clearError) {\n // Non-fatal\n console.warn(`[Daemon] EP995: Failed to clear worktree status for ${moduleUid}:`, clearError)\n }\n\n console.log(`[Daemon] EP956: Async cleanup complete for ${moduleUid}`)\n } catch (error) {\n console.error(`[Daemon] EP956: Cleanup error for ${moduleUid}:`, error instanceof Error ? error.message : error)\n throw error // Re-throw for caller to handle\n }\n }\n\n /**\n * EP1002: Handle worktree_setup command from server\n * This provides a unified setup flow for both local and cloud environments.\n * Server orchestrates, daemon executes.\n */\n private async handleWorktreeSetup(\n command: { path: string; moduleUid: string },\n projectPath: string,\n worktreeManager: WorktreeManager\n ): Promise<ExecutionResult> {\n const { path: worktreePath, moduleUid } = command\n console.log(`[Daemon] EP1002: Handling worktree_setup for ${moduleUid} at ${worktreePath}`)\n\n try {\n // Fetch env vars from server\n const envVars = await fetchEnvVars()\n console.log(`[Daemon] EP1002: Fetched ${Object.keys(envVars).length} env vars for ${moduleUid}`)\n\n // Get project settings for setup script\n const config = await loadConfig()\n const setupConfig = config?.project_settings\n\n // Run the setup synchronously (server is waiting for result)\n await this.runWorktreeSetupSync(\n moduleUid,\n worktreeManager,\n setupConfig?.worktree_copy_files || [],\n setupConfig?.worktree_setup_script,\n worktreePath,\n envVars\n )\n\n return {\n success: true,\n output: `Worktree setup completed for ${moduleUid}`,\n details: {\n moduleUid,\n worktreePath,\n envVarsCount: Object.keys(envVars).length\n }\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] EP1002: Worktree setup failed for ${moduleUid}:`, errorMessage)\n return {\n success: false,\n error: 'SETUP_FAILED',\n output: errorMessage\n }\n }\n }\n\n /**\n * EP1002: Synchronous worktree setup for command-driven flow\n * Similar to runWorktreeSetupAsync but runs synchronously for server orchestration\n */\n private async runWorktreeSetupSync(\n moduleUid: string,\n worktreeManager: WorktreeManager,\n copyFiles: string[],\n setupScript: string | undefined,\n worktreePath: string,\n envVars: Record<string, string> = {}\n ): Promise<void> {\n console.log(`[Daemon] EP1002: Running worktree setup for ${moduleUid}`)\n\n // Mark as running\n await worktreeManager.updateWorktreeStatus(moduleUid, 'running')\n await this.updateModuleWorktreeStatus(moduleUid, 'setup', worktreePath)\n\n // Write .env file\n if (Object.keys(envVars).length > 0) {\n console.log(`[Daemon] EP1002: Writing .env with ${Object.keys(envVars).length} variables`)\n writeEnvFile(worktreePath, envVars)\n }\n\n // Auto-install dependencies\n const installCmd = getInstallCommand(worktreePath)\n if (installCmd) {\n console.log(`[Daemon] EP1002: ${installCmd.description} (detected from ${installCmd.detectedFrom})`)\n console.log(`[Daemon] EP1002: Running: ${installCmd.command.join(' ')}`)\n\n try {\n const { execSync } = await import('child_process')\n execSync(installCmd.command.join(' '), {\n cwd: worktreePath,\n stdio: 'inherit',\n timeout: 10 * 60 * 1000, // 10 minute timeout\n env: { ...process.env, CI: 'true' }\n })\n console.log(`[Daemon] EP1002: Dependencies installed successfully`)\n } catch (installError) {\n const errorMsg = installError instanceof Error ? installError.message : String(installError)\n console.warn(`[Daemon] EP1002: Dependency installation failed (non-fatal): ${errorMsg}`)\n }\n } else {\n console.log(`[Daemon] EP1002: No package manager detected, skipping dependency installation`)\n }\n\n // Run setup script if configured\n if (setupScript) {\n console.log(`[Daemon] EP1002: Running setup script`)\n const scriptResult = await worktreeManager.runSetupScript(moduleUid, setupScript)\n if (!scriptResult.success) {\n throw new Error(`Setup script failed: ${scriptResult.error}`)\n }\n }\n\n // Mark as ready\n await worktreeManager.updateWorktreeStatus(moduleUid, 'ready')\n await this.updateModuleWorktreeStatus(moduleUid, 'ready', worktreePath)\n console.log(`[Daemon] EP1002: Worktree setup complete for ${moduleUid}`)\n }\n\n /**\n * EP959-11: Run worktree setup asynchronously\n * EP964: Added envVars parameter to inject .env file\n * Writes .env, copies files, and runs setup script after worktree creation\n */\n private async runWorktreeSetupAsync(\n moduleUid: string,\n worktreeManager: WorktreeManager,\n copyFiles: string[],\n setupScript: string | undefined,\n worktreePath: string,\n envVars: Record<string, string> = {}\n ): Promise<void> {\n console.log(`[Daemon] EP959: Running async worktree setup for ${moduleUid}`)\n\n try {\n // Mark as running (local + server)\n await worktreeManager.updateWorktreeStatus(moduleUid, 'running')\n // EP995: Report setup status to server\n await this.updateModuleWorktreeStatus(moduleUid, 'setup', worktreePath)\n\n // EP988: Write .env file using shared utility\n if (Object.keys(envVars).length > 0) {\n console.log(`[Daemon] EP988: Writing .env with ${Object.keys(envVars).length} variables to ${moduleUid}`)\n writeEnvFile(worktreePath, envVars)\n }\n\n // Copy files from main worktree (deprecated - EP964)\n if (copyFiles.length > 0) {\n console.log(`[Daemon] EP964: DEPRECATED - Copying ${copyFiles.length} files to ${moduleUid}`)\n console.warn(`[Daemon] EP964: worktree_copy_files is deprecated. Use worktree_env_vars or worktree_setup_script instead.`)\n const copyResult = await worktreeManager.copyFilesFromMain(moduleUid, copyFiles)\n if (!copyResult.success) {\n // Non-fatal for deprecated feature\n console.warn(`[Daemon] EP964: File copy failed (non-fatal): ${copyResult.error}`)\n }\n }\n\n // EP986: Auto-install dependencies\n const installCmd = getInstallCommand(worktreePath)\n if (installCmd) {\n console.log(`[Daemon] EP986: ${installCmd.description} (detected from ${installCmd.detectedFrom})`)\n console.log(`[Daemon] EP986: Running: ${installCmd.command.join(' ')}`)\n\n try {\n const { execSync } = await import('child_process')\n execSync(installCmd.command.join(' '), {\n cwd: worktreePath,\n stdio: 'inherit',\n timeout: 10 * 60 * 1000, // 10 minute timeout\n env: { ...process.env, CI: 'true' } // CI=true for cleaner output\n })\n console.log(`[Daemon] EP986: Dependencies installed successfully for ${moduleUid}`)\n } catch (installError) {\n // Non-fatal - log warning but continue\n const errorMsg = installError instanceof Error ? installError.message : String(installError)\n console.warn(`[Daemon] EP986: Dependency installation failed (non-fatal): ${errorMsg}`)\n console.warn(`[Daemon] EP986: You may need to run '${installCmd.command.join(' ')}' manually`)\n }\n } else {\n console.log(`[Daemon] EP986: No package manager detected for ${moduleUid}, skipping dependency installation`)\n }\n\n // Run setup script\n if (setupScript) {\n console.log(`[Daemon] EP959: Running setup script for ${moduleUid}`)\n const scriptResult = await worktreeManager.runSetupScript(moduleUid, setupScript)\n if (!scriptResult.success) {\n throw new Error(`Setup script failed: ${scriptResult.error}`)\n }\n }\n\n // Mark as ready (local + server)\n await worktreeManager.updateWorktreeStatus(moduleUid, 'ready')\n // EP995: Report ready status to server\n await this.updateModuleWorktreeStatus(moduleUid, 'ready', worktreePath)\n console.log(`[Daemon] EP959: Worktree setup complete for ${moduleUid}`)\n\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`[Daemon] EP959: Worktree setup failed for ${moduleUid}:`, errorMessage)\n await worktreeManager.updateWorktreeStatus(moduleUid, 'error', errorMessage)\n // EP995: Report error status to server\n await this.updateModuleWorktreeStatus(moduleUid, 'error', worktreePath, errorMessage)\n throw error\n }\n }\n\n // EP1003: startTunnelForModule removed - server now orchestrates via tunnel_start commands\n // EP1003: autoStartTunnelsForProject removed - server now orchestrates via reconciliation\n // Recovery flow: daemon sends reconciliation_report → server processes and sends commands\n // Orphan tunnel cleanup is now also handled server-side via reconciliation report\n\n // EP843: startTunnelPolling() removed - replaced by push-based state sync\n // See module_state_changed handler for the new implementation\n\n /**\n * EP822: Stop periodic tunnel polling\n * EP843: Kept for cleanup during shutdown, but interval is never started\n */\n private stopTunnelPolling(): void {\n if (this.tunnelPollInterval) {\n clearInterval(this.tunnelPollInterval)\n this.tunnelPollInterval = null\n console.log('[Daemon] EP822: Tunnel polling stopped')\n }\n }\n\n /**\n * EP929: Start health check polling\n *\n * Restored from EP843 removal. Health checks run every 60 seconds to:\n * - Verify running tunnels are still responsive\n * - Detect dead tunnels that haven't been cleaned up\n * - Auto-restart unhealthy tunnels after consecutive failures\n *\n * This is orthogonal to the push-based state sync (module_state_changed events).\n * State sync handles start/stop based on module transitions.\n * Health checks handle detecting and recovering from tunnel crashes.\n */\n private startHealthCheckPolling(): void {\n if (this.healthCheckInterval) {\n console.log('[Daemon] EP929: Health check polling already running')\n return\n }\n\n console.log(`[Daemon] EP929: Starting health check polling (every ${Daemon.HEALTH_CHECK_INTERVAL_MS / 1000}s)`)\n\n this.healthCheckInterval = setInterval(async () => {\n // Guard against overlapping health checks\n if (this.healthCheckInProgress) {\n console.log('[Daemon] EP929: Health check still in progress, skipping')\n return\n }\n\n this.healthCheckInProgress = true\n try {\n const config = await loadConfig()\n if (config?.access_token) {\n await this.performHealthChecks(config)\n }\n } catch (error) {\n console.error('[Daemon] EP929: Health check error:', error instanceof Error ? error.message : error)\n } finally {\n this.healthCheckInProgress = false\n }\n }, Daemon.HEALTH_CHECK_INTERVAL_MS)\n }\n\n /**\n * EP929: Stop health check polling\n */\n private stopHealthCheckPolling(): void {\n if (this.healthCheckInterval) {\n clearInterval(this.healthCheckInterval)\n this.healthCheckInterval = null\n console.log('[Daemon] EP929: Health check polling stopped')\n }\n }\n\n /**\n * EP822: Clean up orphaned tunnels from previous daemon runs\n * EP904: Enhanced to aggressively clean ALL cloudflared processes then restart\n * tunnels for qualifying modules (doing/review state, dev_mode=local)\n *\n * When the daemon crashes or is killed, tunnels may continue running.\n * This method:\n * 1. Kills ALL cloudflared processes (aggressive cleanup for clean slate)\n * 2. Queries API for modules that should have tunnels\n * 3. Restarts tunnels for qualifying modules\n */\n private async cleanupOrphanedTunnels(): Promise<void> {\n try {\n const tunnelManager = getTunnelManager()\n await tunnelManager.initialize()\n\n // EP904: Clean up any in-memory tracked tunnels first\n const runningTunnels = tunnelManager.getAllTunnels()\n if (runningTunnels.length > 0) {\n console.log(`[Daemon] EP904: Stopping ${runningTunnels.length} tracked tunnel(s)...`)\n for (const tunnel of runningTunnels) {\n try {\n await tunnelManager.stopTunnel(tunnel.moduleUid)\n await stopDevServer(tunnel.moduleUid)\n } catch (error) {\n console.error(`[Daemon] EP904: Failed to stop tunnel for ${tunnel.moduleUid}:`, error)\n }\n }\n }\n\n // EP904: Use TunnelManager's orphan cleanup to kill ALL cloudflared processes\n // This catches processes not tracked in memory (from previous daemon instances)\n const cleanup = await tunnelManager.cleanupOrphanedProcesses()\n if (cleanup.cleaned > 0) {\n console.log(`[Daemon] EP904: Killed ${cleanup.cleaned} orphaned cloudflared process(es)`)\n }\n\n console.log('[Daemon] EP904: Orphaned tunnel cleanup complete - clean slate ready')\n\n // EP1003: Tunnel restart for qualifying modules now happens via server reconciliation\n // Daemon sends reconciliation_report → server processes and sends tunnel_start commands\n } catch (error) {\n console.error('[Daemon] EP904: Failed to clean up orphaned tunnels:', error)\n }\n }\n\n /**\n * EP957: Audit worktrees on daemon startup to detect orphaned worktrees\n *\n * Compares local worktrees against active modules (doing/review state).\n * Logs orphaned worktrees for visibility but does NOT auto-cleanup - that's\n * the user's decision via `episoda release` or manual cleanup.\n *\n * This is a non-blocking diagnostic - failures are logged but don't affect startup.\n */\n private async auditWorktreesOnStartup(): Promise<void> {\n try {\n const projects = getAllProjects()\n\n for (const project of projects) {\n await this.auditProjectWorktrees(project.path)\n }\n } catch (error) {\n console.warn('[Daemon] EP957: Worktree audit failed (non-blocking):', error)\n }\n }\n\n /**\n * EP957: Audit worktrees for a single project\n */\n private async auditProjectWorktrees(projectPath: string): Promise<void> {\n try {\n // Find project root (may be in a worktree subdirectory)\n const projectRoot = await findProjectRoot(projectPath)\n if (!projectRoot) {\n return // Not a worktree project\n }\n\n const manager = new WorktreeManager(projectRoot)\n if (!await manager.initialize()) {\n return // Not a valid worktree project\n }\n\n const config = manager.getConfig()\n if (!config) {\n return\n }\n\n // Get list of worktrees\n const worktrees = manager.listWorktrees()\n if (worktrees.length === 0) {\n return // No worktrees to audit\n }\n\n // Fetch active modules from API to compare\n const activeModuleUids = await this.fetchActiveModuleUids(config.projectId)\n if (activeModuleUids === null) {\n return // Couldn't fetch, skip audit\n }\n\n // Audit worktrees\n const { orphaned } = manager.auditWorktrees(activeModuleUids)\n\n if (orphaned.length > 0) {\n console.log(`[Daemon] EP957: Found ${orphaned.length} orphaned worktree(s) in ${config.workspaceSlug}/${config.projectSlug}:`)\n for (const w of orphaned) {\n console.log(` - ${w.moduleUid} (branch: ${w.branchName})`)\n }\n console.log('[Daemon] EP957: Run \"episoda release <module>\" to clean up')\n }\n } catch (error) {\n // Non-blocking - just log\n console.warn(`[Daemon] EP957: Failed to audit ${projectPath}:`, error)\n }\n }\n\n /**\n * EP957: Fetch UIDs of active modules (doing/review state) for a project\n * Returns null if fetch fails (non-blocking)\n */\n private async fetchActiveModuleUids(projectId: string): Promise<string[] | null> {\n try {\n const config = await loadConfig()\n if (!config?.access_token || !config?.api_url) {\n return null\n }\n\n const url = `${config.api_url}/api/modules?project_id=${projectId}&state=doing,review`\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json'\n }\n })\n\n if (!response.ok) {\n return null\n }\n\n const data = await response.json() as { success?: boolean; modules?: Array<{ uid: string }> }\n if (!data.success || !data.modules) {\n return null\n }\n\n return data.modules.map(m => m.uid).filter((uid): uid is string => !!uid)\n } catch {\n return null\n }\n }\n\n // EP843: syncTunnelsWithActiveModules() removed - replaced by push-based state sync\n // See module_state_changed handler for the new implementation\n\n /**\n * EP833: Perform health checks on all running tunnels\n * Checks both tunnel URL and local dev server responsiveness\n */\n private async performHealthChecks(config: EpisodaConfig): Promise<void> {\n const tunnelManager = getTunnelManager()\n const runningTunnels = tunnelManager.getAllTunnels()\n\n if (runningTunnels.length === 0) {\n return\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n for (const tunnel of runningTunnels) {\n if (tunnel.status !== 'connected') {\n continue // Skip tunnels that aren't connected\n }\n\n const isHealthy = await this.checkTunnelHealth(tunnel)\n\n if (isHealthy) {\n // Reset failure counter on success\n this.tunnelHealthFailures.delete(tunnel.moduleUid)\n await this.reportTunnelHealth(tunnel.moduleUid, 'healthy', config)\n } else {\n // Increment failure counter\n const failures = (this.tunnelHealthFailures.get(tunnel.moduleUid) || 0) + 1\n this.tunnelHealthFailures.set(tunnel.moduleUid, failures)\n\n console.log(`[Daemon] EP833: Health check failed for ${tunnel.moduleUid} (${failures}/${Daemon.HEALTH_CHECK_FAILURE_THRESHOLD})`)\n\n if (failures >= Daemon.HEALTH_CHECK_FAILURE_THRESHOLD) {\n console.log(`[Daemon] EP833: Tunnel unhealthy for ${tunnel.moduleUid}, restarting...`)\n // EP929: Use mutex to prevent race condition with module_state_changed handler\n await this.withTunnelLock(tunnel.moduleUid, async () => {\n await this.restartTunnel(tunnel.moduleUid, tunnel.port)\n })\n this.tunnelHealthFailures.delete(tunnel.moduleUid)\n await this.reportTunnelHealth(tunnel.moduleUid, 'unhealthy', config)\n }\n }\n }\n }\n\n /**\n * EP833: Check if a tunnel is healthy\n * Verifies both the tunnel URL and local dev server respond\n */\n private async checkTunnelHealth(tunnel: { moduleUid: string; url: string; port: number }): Promise<boolean> {\n // Check 1: Tunnel URL responds\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), Daemon.HEALTH_CHECK_TIMEOUT_MS)\n\n const response = await fetch(tunnel.url, {\n method: 'HEAD',\n signal: controller.signal\n })\n clearTimeout(timeout)\n\n // 5xx errors indicate server problems\n if (response.status >= 500) {\n console.log(`[Daemon] EP833: Tunnel URL returned ${response.status} for ${tunnel.moduleUid}`)\n return false\n }\n } catch (error) {\n // Network error, tunnel is down\n console.log(`[Daemon] EP833: Tunnel URL unreachable for ${tunnel.moduleUid}:`, error instanceof Error ? error.message : error)\n return false\n }\n\n // Check 2: Dev server on localhost responds\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 2000) // 2s timeout for local\n\n const localResponse = await fetch(`http://localhost:${tunnel.port}`, {\n method: 'HEAD',\n signal: controller.signal\n })\n clearTimeout(timeout)\n\n if (localResponse.status >= 500) {\n console.log(`[Daemon] EP833: Local dev server returned ${localResponse.status} for ${tunnel.moduleUid}`)\n return false\n }\n } catch (error) {\n console.log(`[Daemon] EP833: Local dev server unreachable for ${tunnel.moduleUid}:`, error instanceof Error ? error.message : error)\n return false\n }\n\n return true\n }\n\n /**\n * EP833: Restart a failed tunnel\n * EP932: Now uses restartDevServer() for robust dev server restart with auto-restart\n */\n private async restartTunnel(moduleUid: string, port: number): Promise<void> {\n const tunnelManager = getTunnelManager()\n\n try {\n // Stop existing tunnel\n await tunnelManager.stopTunnel(moduleUid)\n\n // Load config for API access\n const config = await loadConfig()\n if (!config?.access_token) {\n console.error(`[Daemon] EP833: No access token for tunnel restart`)\n return\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n // EP932: Use restartDevServer() which handles:\n // - Stopping the current server\n // - Killing hung processes on the port\n // - Starting a fresh server with auto-restart enabled\n const devServerResult = await restartDevServer(moduleUid)\n\n if (!devServerResult.success) {\n // If no tracked server exists, we need to start fresh\n // EP833: Lookup module's project_id from API to find correct project path\n console.log(`[Daemon] EP932: No tracked server for ${moduleUid}, looking up project...`)\n\n let projectId: string | null = null\n try {\n const moduleResponse = await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}`)\n if (moduleResponse.ok) {\n const moduleData = await moduleResponse.json() as { moduleRecord?: { project_id?: string } }\n projectId = moduleData.moduleRecord?.project_id ?? null\n }\n } catch (e) {\n console.warn(`[Daemon] EP833: Failed to fetch module details for project lookup`)\n }\n\n // EP973: Resolve worktree path for this module (fix: was using project.path which is project root)\n const worktree = await getWorktreeInfoForModule(moduleUid)\n if (!worktree) {\n console.error(`[Daemon] EP973: Cannot resolve worktree path for ${moduleUid} - missing config slugs`)\n return\n }\n\n if (!worktree.exists) {\n console.error(`[Daemon] EP973: Worktree not found at ${worktree.path}`)\n return\n }\n\n // EP929: Check if port is in use by an untracked/hung process\n const { isPortInUse } = await import('../utils/port-check')\n if (await isPortInUse(port)) {\n console.log(`[Daemon] EP932: Port ${port} in use, checking health...`)\n const healthy = await isDevServerHealthy(port)\n if (!healthy) {\n console.log(`[Daemon] EP932: Dev server on port ${port} is not responding, killing process...`)\n await killProcessOnPort(port)\n }\n }\n\n // EP973: Get custom dev server script from project settings\n const devServerScript = config.project_settings?.worktree_dev_server_script\n\n // Start fresh dev server with auto-restart (EP973: use worktree.path, not project.path)\n const startResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript)\n if (!startResult.success) {\n console.error(`[Daemon] EP932: Failed to start dev server: ${startResult.error}`)\n return\n }\n }\n\n console.log(`[Daemon] EP932: Dev server ready, restarting tunnel for ${moduleUid}...`)\n\n // Restart tunnel\n // EP1003: Health-check restart is acceptable autonomous behavior - the tunnel was\n // originally started by server command, daemon is just maintaining it\n const startResult = await tunnelManager.startTunnel({\n moduleUid,\n port,\n onUrl: async (url) => {\n console.log(`[Daemon] EP833: Tunnel restarted for ${moduleUid}: ${url}`)\n try {\n // EP1003: Notify server of restart with reason for observability\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'POST',\n body: JSON.stringify({\n tunnel_url: url,\n tunnel_error: null,\n restart_reason: 'health_check_failure' // EP1003: Server can track restart causes\n })\n })\n } catch (e) {\n console.warn(`[Daemon] EP833: Failed to report restarted tunnel URL`)\n }\n }\n })\n\n if (startResult.success) {\n console.log(`[Daemon] EP833: Tunnel restart successful for ${moduleUid}`)\n } else {\n console.error(`[Daemon] EP833: Tunnel restart failed for ${moduleUid}: ${startResult.error}`)\n }\n } catch (error) {\n console.error(`[Daemon] EP833: Error restarting tunnel for ${moduleUid}:`, error)\n }\n }\n\n /**\n * EP833: Report tunnel health status to the API\n * EP904: Use fetchWithAuth for token refresh\n * EP911: Only report when status CHANGES to reduce DB writes\n */\n private async reportTunnelHealth(\n moduleUid: string,\n healthStatus: 'healthy' | 'unhealthy' | 'unknown',\n config: EpisodaConfig\n ): Promise<void> {\n if (!config.access_token) {\n return\n }\n\n // EP911: Skip update if status hasn't changed (reduces module table writes by ~90%)\n const lastStatus = this.lastReportedHealthStatus.get(moduleUid)\n if (lastStatus === healthStatus) {\n return // Status unchanged, no need to update DB\n }\n\n const apiUrl = config.api_url || 'https://episoda.dev'\n\n try {\n await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/health`, {\n method: 'PATCH',\n body: JSON.stringify({\n tunnel_health_status: healthStatus,\n tunnel_last_health_check: new Date().toISOString()\n })\n })\n // EP911: Track the successfully reported status\n this.lastReportedHealthStatus.set(moduleUid, healthStatus)\n } catch (error) {\n // Non-critical, just log\n console.warn(`[Daemon] EP833: Failed to report health for ${moduleUid}:`, error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * EP833: Kill processes matching a pattern\n * Used to clean up orphaned cloudflared processes\n */\n private async killProcessesByPattern(pattern: string): Promise<number> {\n const { exec } = await import('child_process')\n const { promisify } = await import('util')\n const execAsync = promisify(exec)\n\n try {\n // Find PIDs matching pattern using pgrep\n const { stdout } = await execAsync(`pgrep -f \"${pattern}\"`)\n const pids = stdout.trim().split('\\n').filter(Boolean)\n\n if (pids.length === 0) {\n return 0\n }\n\n // Kill each process\n let killed = 0\n for (const pid of pids) {\n try {\n await execAsync(`kill ${pid}`)\n killed++\n } catch {\n // Process may have already exited\n }\n }\n\n if (killed > 0) {\n console.log(`[Daemon] EP833: Killed ${killed} process(es) matching: ${pattern}`)\n }\n return killed\n } catch {\n // No matching processes, which is fine\n return 0\n }\n }\n\n /**\n * EP833: Kill process using a specific port\n * Used to clean up dev servers when stopping tunnels\n */\n private async killProcessOnPort(port: number): Promise<boolean> {\n const { exec } = await import('child_process')\n const { promisify } = await import('util')\n const execAsync = promisify(exec)\n\n try {\n // Find PIDs using the port\n const { stdout } = await execAsync(`lsof -ti :${port}`)\n const pids = stdout.trim().split('\\n').filter(Boolean)\n\n if (pids.length === 0) {\n return false\n }\n\n // Kill each process\n let killed = false\n for (const pid of pids) {\n try {\n await execAsync(`kill ${pid}`)\n killed = true\n } catch {\n // Process may have already exited\n }\n }\n\n if (killed) {\n console.log(`[Daemon] EP833: Killed process(es) on port ${port}`)\n }\n return killed\n } catch {\n // No process on port, which is fine\n return false\n }\n }\n\n /**\n * EP833: Find orphaned cloudflared processes\n * Returns process info for cloudflared processes not tracked by TunnelManager\n */\n private async findOrphanedCloudflaredProcesses(): Promise<Array<{ pid: string; moduleUid?: string }>> {\n const { exec } = await import('child_process')\n const { promisify } = await import('util')\n const execAsync = promisify(exec)\n\n try {\n // Find all cloudflared processes with their command lines\n const { stdout } = await execAsync('ps aux | grep cloudflared | grep -v grep')\n const lines = stdout.trim().split('\\n').filter(Boolean)\n\n const tunnelManager = getTunnelManager()\n const trackedModules = new Set(tunnelManager.getAllTunnels().map(t => t.moduleUid))\n const orphaned: Array<{ pid: string; moduleUid?: string }> = []\n\n for (const line of lines) {\n const parts = line.trim().split(/\\s+/)\n const pid = parts[1]\n\n // Try to extract module UID from process command line\n // Pattern: cloudflared tunnel --url http://localhost:PORT\n // We can't easily get module UID from the process, but we can identify orphans\n // by comparing the number of processes to the number of tracked tunnels\n\n if (pid) {\n orphaned.push({ pid })\n }\n }\n\n // If we have more cloudflared processes than tracked tunnels, some are orphaned\n if (orphaned.length > trackedModules.size) {\n console.log(`[Daemon] EP833: Found ${orphaned.length} cloudflared processes but only ${trackedModules.size} tracked tunnels`)\n }\n\n return orphaned.length > trackedModules.size ? orphaned : []\n } catch {\n // No cloudflared processes found\n return []\n }\n }\n\n /**\n * EP833: Clean up orphaned cloudflared processes\n * Called during sync to ensure no zombie tunnels are running\n */\n private async cleanupOrphanedCloudflaredProcesses(): Promise<void> {\n const orphaned = await this.findOrphanedCloudflaredProcesses()\n\n if (orphaned.length === 0) {\n return\n }\n\n console.log(`[Daemon] EP833: Cleaning up ${orphaned.length} potentially orphaned cloudflared process(es)`)\n\n // Kill orphaned processes\n const killed = await this.killProcessesByPattern('cloudflared tunnel')\n\n if (killed > 0) {\n console.log(`[Daemon] EP833: Cleaned up ${killed} orphaned cloudflared process(es)`)\n }\n }\n\n // EP843: syncLocalCommits() removed - replaced by GitHub webhook push handler\n // See /api/webhooks/github handlePushEvent() for the new implementation\n\n /**\n * Gracefully shutdown daemon\n */\n private async shutdown(): Promise<void> {\n if (this.shuttingDown) return\n this.shuttingDown = true\n\n console.log('[Daemon] Shutting down...')\n\n // EP822: Stop tunnel polling\n this.stopTunnelPolling()\n\n // EP929: Stop health check polling\n this.stopHealthCheckPolling()\n\n // Close all WebSocket connections\n for (const [projectPath, connection] of this.connections) {\n if (connection.reconnectTimer) {\n clearTimeout(connection.reconnectTimer)\n }\n await connection.client.disconnect()\n }\n this.connections.clear()\n\n // EP672: Stop all active tunnels\n try {\n const tunnelManager = getTunnelManager()\n await tunnelManager.stopAllTunnels()\n console.log('[Daemon] All tunnels stopped')\n } catch (error) {\n console.error('[Daemon] Failed to stop tunnels:', error)\n }\n\n // EP956: Clear all port allocations (in-memory, resets on daemon restart)\n clearAllPorts()\n\n // EP912: Stop all active agent sessions\n try {\n const agentManager = getAgentManager()\n await agentManager.stopAllSessions()\n console.log('[Daemon] All agent sessions stopped')\n } catch (error) {\n console.error('[Daemon] Failed to stop agent sessions:', error)\n }\n\n // EP812: Removed identity server shutdown - browser uses cookie-based pairing\n\n // Stop IPC server\n await this.ipcServer.stop()\n\n console.log('[Daemon] Shutdown complete')\n }\n\n /**\n * EP613: Clean up PID file and exit gracefully\n * Called when user explicitly requests disconnect\n */\n private async cleanupAndExit(): Promise<void> {\n await this.shutdown()\n\n // Clean up PID file\n try {\n const pidPath = getPidFilePath()\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n console.log('[Daemon] PID file cleaned up')\n }\n } catch (error) {\n console.error('[Daemon] Failed to clean up PID file:', error)\n }\n\n console.log('[Daemon] Exiting...')\n process.exit(0)\n }\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n // Only run if explicitly in daemon mode\n if (!process.env.EPISODA_DAEMON_MODE) {\n console.error('This script should only be run by daemon-manager')\n process.exit(1)\n }\n\n const daemon = new Daemon()\n await daemon.start()\n\n // Keep process alive\n await new Promise(() => {\n // Never resolves - daemon runs until killed\n })\n}\n\n// Run daemon\nmain().catch(error => {\n console.error('[Daemon] Fatal error:', error)\n process.exit(1)\n})\n","/**\n * CLI Auto-Update Checker\n * EP783: Check for updates on daemon startup and auto-update in background\n *\n * This module provides non-blocking update checking and background updates\n * to ensure users always have the latest CLI version with correct git hooks.\n */\n\nimport { spawn, execSync } from 'child_process'\nimport * as semver from 'semver'\n\nconst PACKAGE_NAME = 'episoda'\nconst NPM_REGISTRY = 'https://registry.npmjs.org'\n\nexport interface UpdateCheckResult {\n currentVersion: string\n latestVersion: string\n updateAvailable: boolean\n /** EP989: True if check failed due to network issues (offline, timeout, etc.) */\n offline?: boolean\n}\n\n/**\n * Check npm registry for latest version\n * Non-blocking - fails silently on network errors\n */\nexport async function checkForUpdates(currentVersion: string): Promise<UpdateCheckResult> {\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 5000) // 5s timeout\n\n const response = await fetch(`${NPM_REGISTRY}/${PACKAGE_NAME}/latest`, {\n signal: controller.signal\n })\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n return { currentVersion, latestVersion: currentVersion, updateAvailable: false }\n }\n\n const data = await response.json() as { version: string }\n const latestVersion = data.version\n\n return {\n currentVersion,\n latestVersion,\n updateAvailable: semver.gt(latestVersion, currentVersion)\n }\n } catch (error) {\n // EP989: Detect network errors and report offline status\n // This includes timeouts (AbortError), DNS failures, connection refused, etc.\n return { currentVersion, latestVersion: currentVersion, updateAvailable: false, offline: true }\n }\n}\n\n/**\n * Run npm update in background (detached process)\n * The update runs independently of the daemon process\n */\nexport function performBackgroundUpdate(): void {\n try {\n const child = spawn('npm', ['update', '-g', PACKAGE_NAME], {\n detached: true,\n stdio: 'ignore',\n // Use shell on Windows for proper npm execution\n shell: process.platform === 'win32'\n })\n child.unref() // Allow parent to exit independently\n } catch (error) {\n // Silently ignore spawn errors\n }\n}\n\n/**\n * EP989: Get the currently installed version of the CLI from npm\n * Used for version verification after update\n */\nexport function getInstalledVersion(): string | null {\n try {\n const output = execSync(`npm list -g ${PACKAGE_NAME} --json`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n timeout: 10000\n }).toString()\n\n const data = JSON.parse(output)\n // npm list output structure: { dependencies: { episoda: { version: \"x.x.x\" } } }\n return data?.dependencies?.[PACKAGE_NAME]?.version || null\n } catch {\n // Package might not be installed globally or npm command failed\n return null\n }\n}\n","/**\n * EP808: File operation handlers for remote access\n *\n * These handlers enable Claude Code on one machine to read/write files\n * on another machine via WebSocket.\n *\n * All operations use Node.js native fs APIs for cross-platform compatibility\n * (works on macOS, Linux, and Windows).\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as readline from 'readline'\nimport { RemoteCommand, RemoteCommandResult } from '@episoda/core'\n\n// Maximum file size for read operations (20MB)\nconst DEFAULT_MAX_FILE_SIZE = 20 * 1024 * 1024\n\n/**\n * Validates a path to prevent directory traversal attacks\n * Returns the resolved absolute path if valid, null if invalid\n *\n * SECURITY: All paths MUST resolve to within the project directory.\n * This prevents access to system files like /etc/passwd even with absolute paths.\n */\nfunction validatePath(filePath: string, projectPath: string): string | null {\n // Normalize the project path for consistent comparison\n const normalizedProjectPath = path.resolve(projectPath)\n\n // Resolve to absolute path relative to project\n const absolutePath = path.isAbsolute(filePath)\n ? path.resolve(filePath)\n : path.resolve(projectPath, filePath)\n\n // Normalize to handle ../ etc\n const normalizedPath = path.normalize(absolutePath)\n\n // CRITICAL: Always verify path is within project boundaries\n // This blocks both traversal attacks (../) AND absolute paths outside project\n if (!normalizedPath.startsWith(normalizedProjectPath + path.sep) &&\n normalizedPath !== normalizedProjectPath) {\n return null\n }\n\n return normalizedPath\n}\n\n/**\n * Handle file:read command\n */\nexport async function handleFileRead(\n command: Extract<RemoteCommand, { action: 'file:read' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, encoding = 'base64', maxSize = DEFAULT_MAX_FILE_SIZE } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if file exists\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'File not found'\n }\n }\n\n // Check file stats\n const stats = fs.statSync(validPath)\n\n if (stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is a directory, not a file'\n }\n }\n\n if (stats.size > maxSize) {\n return {\n success: false,\n error: `File too large: ${stats.size} bytes exceeds limit of ${maxSize} bytes`\n }\n }\n\n // Read file\n const buffer = fs.readFileSync(validPath)\n let content: string\n\n if (encoding === 'base64') {\n content = buffer.toString('base64')\n } else {\n content = buffer.toString('utf8')\n }\n\n return {\n success: true,\n content,\n encoding,\n size: stats.size\n }\n } catch (error) {\n // Don't expose internal error details that might contain paths\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to read file'\n }\n }\n}\n\n/**\n * Handle file:write command\n */\nexport async function handleFileWrite(\n command: Extract<RemoteCommand, { action: 'file:write' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, content, encoding = 'utf8', createDirs = true } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Create parent directories if needed\n if (createDirs) {\n const dirPath = path.dirname(validPath)\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true })\n }\n }\n\n // Decode content\n let buffer: Buffer\n if (encoding === 'base64') {\n buffer = Buffer.from(content, 'base64')\n } else {\n buffer = Buffer.from(content, 'utf8')\n }\n\n // Write to temp file first, then rename (atomic write)\n const tempPath = `${validPath}.tmp.${Date.now()}`\n fs.writeFileSync(tempPath, buffer)\n fs.renameSync(tempPath, validPath)\n\n return {\n success: true,\n bytesWritten: buffer.length\n }\n } catch (error) {\n // Don't expose internal error details that might contain paths\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n const isDiskError = errMsg.includes('ENOSPC') || errMsg.includes('no space')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : isDiskError ? 'Disk full' : 'Failed to write file'\n }\n }\n}\n\n/**\n * Handle file:list command\n * Lists directory contents using Node.js fs (cross-platform)\n */\nexport async function handleFileList(\n command: Extract<RemoteCommand, { action: 'file:list' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: dirPath, recursive = false, includeHidden = false } = command\n\n // Validate path\n const validPath = validatePath(dirPath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if path exists and is a directory\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Directory not found'\n }\n }\n\n const stats = fs.statSync(validPath)\n if (!stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is not a directory'\n }\n }\n\n const entries: Array<{ name: string; type: 'file' | 'directory'; size: number }> = []\n\n if (recursive) {\n // Recursive listing\n await listDirectoryRecursive(validPath, validPath, entries, includeHidden)\n } else {\n // Single directory listing\n const dirEntries = await fs.promises.readdir(validPath, { withFileTypes: true })\n for (const entry of dirEntries) {\n if (!includeHidden && entry.name.startsWith('.')) continue\n\n const entryPath = path.join(validPath, entry.name)\n const entryStats = await fs.promises.stat(entryPath)\n\n entries.push({\n name: entry.name,\n type: entry.isDirectory() ? 'directory' : 'file',\n size: entryStats.size\n })\n }\n }\n\n return {\n success: true,\n entries\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to list directory'\n }\n }\n}\n\n/**\n * Recursively list directory contents\n */\nasync function listDirectoryRecursive(\n basePath: string,\n currentPath: string,\n entries: Array<{ name: string; type: 'file' | 'directory'; size: number }>,\n includeHidden: boolean\n): Promise<void> {\n const dirEntries = await fs.promises.readdir(currentPath, { withFileTypes: true })\n\n for (const entry of dirEntries) {\n if (!includeHidden && entry.name.startsWith('.')) continue\n\n const entryPath = path.join(currentPath, entry.name)\n const relativePath = path.relative(basePath, entryPath)\n\n try {\n const entryStats = await fs.promises.stat(entryPath)\n\n entries.push({\n name: relativePath,\n type: entry.isDirectory() ? 'directory' : 'file',\n size: entryStats.size\n })\n\n if (entry.isDirectory()) {\n await listDirectoryRecursive(basePath, entryPath, entries, includeHidden)\n }\n } catch {\n // Skip entries we can't stat (permission denied, etc.)\n }\n }\n}\n\n/**\n * Handle file:search command\n * Searches for files by glob pattern using Node.js (cross-platform)\n */\nexport async function handleFileSearch(\n command: Extract<RemoteCommand, { action: 'file:search' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { pattern, basePath, maxResults = 100 } = command\n const effectiveMaxResults = Math.min(Math.max(1, maxResults), 1000)\n\n // Validate path\n const validPath = validatePath(basePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Directory not found'\n }\n }\n\n const files: string[] = []\n const globRegex = globToRegex(pattern)\n\n await searchFilesRecursive(validPath, validPath, globRegex, files, effectiveMaxResults)\n\n return {\n success: true,\n files\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to search files'\n }\n }\n}\n\n/**\n * Convert a glob pattern to a regex\n *\n * Supports:\n * - * (any chars except /)\n * - ** (any chars including /)\n * - ? (single char except /)\n * - {a,b,c} (brace expansion - matches a OR b OR c)\n * - [abc] (character class - matches a, b, or c)\n * - [a-z] (character range - matches a through z)\n * - [!abc] or [^abc] (negated class - matches anything except a, b, c)\n */\nfunction globToRegex(pattern: string): RegExp {\n let i = 0\n let regexStr = ''\n\n while (i < pattern.length) {\n const char = pattern[i]\n\n // Handle ** (globstar - matches anything including /)\n if (char === '*' && pattern[i + 1] === '*') {\n regexStr += '.*'\n i += 2\n // Skip trailing / after **\n if (pattern[i] === '/') i++\n continue\n }\n\n // Handle * (matches any chars except /)\n if (char === '*') {\n regexStr += '[^/]*'\n i++\n continue\n }\n\n // Handle ? (matches single char except /)\n if (char === '?') {\n regexStr += '[^/]'\n i++\n continue\n }\n\n // Handle {a,b,c} (brace expansion)\n if (char === '{') {\n const closeIdx = pattern.indexOf('}', i)\n if (closeIdx !== -1) {\n const options = pattern.slice(i + 1, closeIdx).split(',')\n const escaped = options.map(opt => escapeRegexChars(opt))\n regexStr += `(?:${escaped.join('|')})`\n i = closeIdx + 1\n continue\n }\n }\n\n // Handle [...] (character class)\n if (char === '[') {\n const closeIdx = findClosingBracket(pattern, i)\n if (closeIdx !== -1) {\n let classContent = pattern.slice(i + 1, closeIdx)\n // Convert [!...] to [^...] (glob negation to regex negation)\n if (classContent.startsWith('!')) {\n classContent = '^' + classContent.slice(1)\n }\n regexStr += `[${classContent}]`\n i = closeIdx + 1\n continue\n }\n }\n\n // Escape special regex characters\n if ('.+^${}()|[]\\\\'.includes(char)) {\n regexStr += '\\\\' + char\n } else {\n regexStr += char\n }\n i++\n }\n\n return new RegExp(`^${regexStr}$`)\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegexChars(str: string): string {\n return str.replace(/[.+^${}()|[\\]\\\\*?]/g, '\\\\$&')\n}\n\n/**\n * Find the closing bracket for a character class, handling escapes\n */\nfunction findClosingBracket(pattern: string, start: number): number {\n let i = start + 1\n // Handle []] or [!]] - ] right after [ or [! is literal\n if (pattern[i] === '!' || pattern[i] === '^') i++\n if (pattern[i] === ']') i++\n\n while (i < pattern.length) {\n if (pattern[i] === ']') return i\n if (pattern[i] === '\\\\' && i + 1 < pattern.length) i++ // Skip escaped char\n i++\n }\n return -1 // No closing bracket found\n}\n\n/**\n * Recursively search for files matching a pattern\n */\nasync function searchFilesRecursive(\n basePath: string,\n currentPath: string,\n pattern: RegExp,\n files: string[],\n maxResults: number\n): Promise<void> {\n if (files.length >= maxResults) return\n\n const entries = await fs.promises.readdir(currentPath, { withFileTypes: true })\n\n for (const entry of entries) {\n if (files.length >= maxResults) return\n if (entry.name.startsWith('.')) continue // Skip hidden files\n\n const entryPath = path.join(currentPath, entry.name)\n const relativePath = path.relative(basePath, entryPath)\n\n try {\n if (entry.isDirectory()) {\n await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults)\n } else if (pattern.test(relativePath) || pattern.test(entry.name)) {\n files.push(relativePath)\n }\n } catch {\n // Skip entries we can't access\n }\n }\n}\n\n/**\n * Handle file:grep command\n * Searches for content in files using Node.js (cross-platform)\n */\nexport async function handleFileGrep(\n command: Extract<RemoteCommand, { action: 'file:grep' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const {\n pattern,\n path: searchPath,\n filePattern = '*',\n caseSensitive = true,\n maxResults = 100\n } = command\n const effectiveMaxResults = Math.min(Math.max(1, maxResults), 1000)\n\n // Validate path\n const validPath = validatePath(searchPath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Path not found'\n }\n }\n\n const matches: Array<{ file: string; line: number; content: string }> = []\n const searchRegex = new RegExp(pattern, caseSensitive ? '' : 'i')\n const fileGlobRegex = globToRegex(filePattern)\n\n const stats = fs.statSync(validPath)\n if (stats.isFile()) {\n // Search single file\n await grepFile(validPath, validPath, searchRegex, matches, effectiveMaxResults)\n } else {\n // Search directory recursively\n await grepDirectoryRecursive(validPath, validPath, searchRegex, fileGlobRegex, matches, effectiveMaxResults)\n }\n\n return {\n success: true,\n matches\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to search content'\n }\n }\n}\n\n/**\n * Search for pattern in a single file\n */\nasync function grepFile(\n basePath: string,\n filePath: string,\n pattern: RegExp,\n matches: Array<{ file: string; line: number; content: string }>,\n maxResults: number\n): Promise<void> {\n if (matches.length >= maxResults) return\n\n const relativePath = path.relative(basePath, filePath)\n\n // Read file line by line to handle large files\n const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' })\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity\n })\n\n let lineNumber = 0\n for await (const line of rl) {\n lineNumber++\n if (matches.length >= maxResults) {\n rl.close()\n break\n }\n\n if (pattern.test(line)) {\n matches.push({\n file: relativePath || path.basename(filePath),\n line: lineNumber,\n content: line.slice(0, 500) // Truncate long lines\n })\n }\n }\n}\n\n/**\n * Recursively grep files in a directory\n */\nasync function grepDirectoryRecursive(\n basePath: string,\n currentPath: string,\n searchPattern: RegExp,\n filePattern: RegExp,\n matches: Array<{ file: string; line: number; content: string }>,\n maxResults: number\n): Promise<void> {\n if (matches.length >= maxResults) return\n\n const entries = await fs.promises.readdir(currentPath, { withFileTypes: true })\n\n for (const entry of entries) {\n if (matches.length >= maxResults) return\n if (entry.name.startsWith('.')) continue // Skip hidden files/dirs\n\n const entryPath = path.join(currentPath, entry.name)\n\n try {\n if (entry.isDirectory()) {\n await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults)\n } else if (filePattern.test(entry.name)) {\n await grepFile(basePath, entryPath, searchPattern, matches, maxResults)\n }\n } catch {\n // Skip entries we can't access\n }\n }\n}\n\n/**\n * EP851: Handle file:edit command\n * Performs precise string replacement in a file\n */\nexport async function handleFileEdit(\n command: Extract<RemoteCommand, { action: 'file:edit' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, oldString, newString, replaceAll = false } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if file exists\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'File not found'\n }\n }\n\n // Check it's a file\n const stats = fs.statSync(validPath)\n if (stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is a directory, not a file'\n }\n }\n\n // Check file size (max 10MB for edit operations)\n const MAX_EDIT_SIZE = 10 * 1024 * 1024 // 10MB\n if (stats.size > MAX_EDIT_SIZE) {\n return {\n success: false,\n error: `File too large for edit operation: ${stats.size} bytes exceeds limit of ${MAX_EDIT_SIZE} bytes (10MB). Use write-file for large files.`\n }\n }\n\n // Read file content\n const content = fs.readFileSync(validPath, 'utf8')\n\n // Count occurrences\n const occurrences = content.split(oldString).length - 1\n\n if (occurrences === 0) {\n return {\n success: false,\n error: 'old_string not found in file. Make sure it matches exactly, including whitespace.'\n }\n }\n\n if (occurrences > 1 && !replaceAll) {\n return {\n success: false,\n error: `old_string found ${occurrences} times. Use replaceAll=true to replace all, or provide more context.`\n }\n }\n\n // Perform replacement\n // Use split/join for replaceAll to support older Node.js versions\n const newContent = replaceAll\n ? content.split(oldString).join(newString)\n : content.replace(oldString, newString)\n\n const replacements = replaceAll ? occurrences : 1\n\n // Write to temp file first, then rename (atomic write)\n const tempPath = `${validPath}.tmp.${Date.now()}`\n fs.writeFileSync(tempPath, newContent, 'utf8')\n fs.renameSync(tempPath, validPath)\n\n const newSize = Buffer.byteLength(newContent, 'utf8')\n\n return {\n success: true,\n replacements,\n newSize\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to edit file'\n }\n }\n}\n\n/**\n * EP851: Handle file:delete command\n * Deletes a file or directory\n */\nexport async function handleFileDelete(\n command: Extract<RemoteCommand, { action: 'file:delete' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: filePath, recursive = false } = command\n\n // Validate path\n const validPath = validatePath(filePath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n // Additional safety: don't allow deleting the project root\n const normalizedProjectPath = path.resolve(projectPath)\n if (validPath === normalizedProjectPath) {\n return {\n success: false,\n error: 'Cannot delete project root directory'\n }\n }\n\n try {\n // Check if path exists\n if (!fs.existsSync(validPath)) {\n return {\n success: false,\n error: 'Path not found'\n }\n }\n\n const stats = fs.statSync(validPath)\n const isDirectory = stats.isDirectory()\n\n // Require recursive flag for directories\n if (isDirectory && !recursive) {\n return {\n success: false,\n error: 'Cannot delete directory without recursive=true'\n }\n }\n\n // Delete\n if (isDirectory) {\n fs.rmSync(validPath, { recursive: true, force: true })\n } else {\n fs.unlinkSync(validPath)\n }\n\n return {\n success: true,\n deleted: true,\n pathType: isDirectory ? 'directory' : 'file'\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to delete'\n }\n }\n}\n\n/**\n * EP851: Handle file:mkdir command\n * Creates a directory (with parent directories if needed)\n */\nexport async function handleFileMkdir(\n command: Extract<RemoteCommand, { action: 'file:mkdir' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const { path: dirPath, mode = '0755' } = command\n\n // Validate path\n const validPath = validatePath(dirPath, projectPath)\n if (!validPath) {\n return {\n success: false,\n error: 'Invalid path: directory traversal not allowed'\n }\n }\n\n try {\n // Check if already exists\n if (fs.existsSync(validPath)) {\n const stats = fs.statSync(validPath)\n if (stats.isDirectory()) {\n return {\n success: true,\n created: false // Already exists\n }\n } else {\n return {\n success: false,\n error: 'Path exists but is a file, not a directory'\n }\n }\n }\n\n // Create directory with parents\n const modeNum = parseInt(mode, 8)\n fs.mkdirSync(validPath, { recursive: true, mode: modeNum })\n\n return {\n success: true,\n created: true\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error)\n const isPermissionError = errMsg.includes('EACCES') || errMsg.includes('permission')\n return {\n success: false,\n error: isPermissionError ? 'Permission denied' : 'Failed to create directory'\n }\n }\n}\n","/**\n * EP808: Command execution handler for remote access\n *\n * Enables executing shell commands on the local machine via WebSocket.\n */\n\nimport { spawn } from 'child_process'\nimport { RemoteCommand, RemoteCommandResult } from '@episoda/core'\n\n// Default timeout: 30 seconds\nconst DEFAULT_TIMEOUT = 30000\n// Maximum timeout: 5 minutes (for long-running builds)\nconst MAX_TIMEOUT = 300000\n\n/**\n * Handle exec command\n */\nexport async function handleExec(\n command: Extract<RemoteCommand, { action: 'exec' }>,\n projectPath: string\n): Promise<RemoteCommandResult> {\n const {\n command: cmd,\n cwd = projectPath,\n timeout = DEFAULT_TIMEOUT,\n env = {}\n } = command\n\n // Validate timeout\n const effectiveTimeout = Math.min(Math.max(timeout, 1000), MAX_TIMEOUT)\n\n return new Promise((resolve) => {\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let resolved = false\n\n const done = (result: RemoteCommandResult) => {\n if (resolved) return\n resolved = true\n resolve(result)\n }\n\n try {\n // Spawn shell process\n const proc = spawn(cmd, {\n shell: true,\n cwd,\n env: { ...process.env, ...env },\n stdio: ['ignore', 'pipe', 'pipe']\n })\n\n // Setup timeout\n const timeoutId = setTimeout(() => {\n timedOut = true\n proc.kill('SIGTERM')\n // Give it 5 seconds to terminate gracefully, then SIGKILL\n setTimeout(() => {\n if (!resolved) {\n proc.kill('SIGKILL')\n }\n }, 5000)\n }, effectiveTimeout)\n\n // Collect stdout\n proc.stdout.on('data', (data: Buffer) => {\n stdout += data.toString()\n // Limit stdout to prevent memory issues (10MB)\n if (stdout.length > 10 * 1024 * 1024) {\n stdout = stdout.slice(-10 * 1024 * 1024)\n }\n })\n\n // Collect stderr\n proc.stderr.on('data', (data: Buffer) => {\n stderr += data.toString()\n // Limit stderr to prevent memory issues (10MB)\n if (stderr.length > 10 * 1024 * 1024) {\n stderr = stderr.slice(-10 * 1024 * 1024)\n }\n })\n\n // Handle process exit\n proc.on('close', (code, signal) => {\n clearTimeout(timeoutId)\n done({\n success: code === 0 && !timedOut,\n stdout,\n stderr,\n exitCode: code ?? -1,\n timedOut,\n error: timedOut\n ? `Command timed out after ${effectiveTimeout}ms`\n : code !== 0\n ? `Command exited with code ${code}`\n : undefined\n })\n })\n\n // Handle spawn errors\n proc.on('error', (error) => {\n clearTimeout(timeoutId)\n done({\n success: false,\n stdout,\n stderr,\n exitCode: -1,\n error: `Failed to execute command: ${error.message}`\n })\n })\n } catch (error) {\n done({\n success: false,\n stdout: '',\n stderr: '',\n exitCode: -1,\n error: `Failed to spawn process: ${error instanceof Error ? error.message : String(error)}`\n })\n }\n })\n}\n","/**\n * EP950: Stale Commit Cleanup Handler\n *\n * Cleans up local_commit records for commits that no longer exist in git history.\n * This happens when commits are rebased away - the old SHAs become orphaned in\n * the database but the new commits (with different SHAs) are what actually got pushed.\n *\n * Called after:\n * 1. Successful push to main branch\n * 2. CLI daemon connection (periodic cleanup)\n */\n\nimport { exec } from 'child_process'\nimport { promisify } from 'util'\nimport { loadConfig, EpisodaConfig } from '@episoda/core'\nimport { getMachineId } from '../machine-id'\n\nconst execAsync = promisify(exec)\n\n/**\n * Clean up stale commit records for the main branch.\n *\n * Compares local_commit records in the database with actual unpushed commits\n * from git, and removes any records for commits that no longer exist.\n *\n * @param projectPath - Path to the git repository\n * @returns Cleanup result with counts\n */\nexport async function cleanupStaleCommits(projectPath: string): Promise<{\n success: boolean\n deleted_count: number\n kept_count: number\n message: string\n}> {\n try {\n // Get machine ID\n const machineId = await getMachineId()\n\n // Load config for API access\n const config = await loadConfig()\n if (!config?.access_token) {\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: 'No access token available'\n }\n }\n\n // Fetch latest refs from origin\n try {\n await execAsync('git fetch origin', { cwd: projectPath, timeout: 30000 })\n } catch (fetchError) {\n // Non-fatal - continue with possibly stale refs\n console.warn('[EP950] Could not fetch origin:', fetchError)\n }\n\n // Get current branch\n let currentBranch = ''\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd: projectPath, timeout: 5000 })\n currentBranch = stdout.trim()\n } catch {\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: 'Could not determine current branch'\n }\n }\n\n // Only cleanup for main branch\n if (currentBranch !== 'main' && currentBranch !== 'master') {\n return {\n success: true,\n deleted_count: 0,\n kept_count: 0,\n message: `Not on main branch (on ${currentBranch}), skipping cleanup`\n }\n }\n\n // Get actual unpushed commits (these are valid, should be kept)\n let validShas: string[] = []\n try {\n const { stdout } = await execAsync(\n 'git log origin/main..HEAD --format=%H',\n { cwd: projectPath, timeout: 10000 }\n )\n validShas = stdout.trim().split('\\n').filter(Boolean)\n } catch {\n // No unpushed commits or origin/main doesn't exist\n validShas = []\n }\n\n // Call cleanup API\n const apiUrl = config.api_url || 'https://episoda.dev'\n const response = await fetch(`${apiUrl}/api/commits/local/batch`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n machine_id: machineId,\n valid_shas: validShas,\n branch: 'main'\n })\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as { error?: { message?: string } }\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: `API error: ${errorData.error?.message || response.statusText}`\n }\n }\n\n const result = await response.json() as {\n success: boolean\n deleted_count?: number\n kept_count?: number\n message?: string\n }\n\n if (result.deleted_count && result.deleted_count > 0) {\n console.log(`[EP950] Cleaned up ${result.deleted_count} stale commit record(s)`)\n }\n\n return {\n success: true,\n deleted_count: result.deleted_count || 0,\n kept_count: result.kept_count || 0,\n message: result.message || 'Cleanup completed'\n }\n\n } catch (error: any) {\n console.error('[EP950] Error during stale commit cleanup:', error)\n return {\n success: false,\n deleted_count: 0,\n kept_count: 0,\n message: error.message || 'Cleanup failed'\n }\n }\n}\n\n/**\n * Check if we should run cleanup (on main branch)\n */\nexport async function shouldRunCleanup(projectPath: string): Promise<boolean> {\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd: projectPath, timeout: 5000 })\n const branch = stdout.trim()\n return branch === 'main' || branch === 'master'\n } catch {\n return false\n }\n}\n","/**\n * Cloudflared Binary Manager\n *\n * EP672: Manages the cloudflared binary for local tunnel functionality.\n * Downloads and installs cloudflared if not available in PATH.\n *\n * @module tunnel/cloudflared-manager\n */\n\nimport { execSync, spawnSync } from 'child_process'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport * as https from 'https'\nimport * as tar from 'tar'\n\n/**\n * Download URLs for cloudflared releases\n * Using GitHub releases for the latest stable version\n */\nconst DOWNLOAD_URLS: Record<string, Record<string, string>> = {\n darwin: {\n arm64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-arm64.tgz',\n x64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz'\n },\n linux: {\n arm64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64',\n x64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64'\n },\n win32: {\n x64: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe',\n ia32: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-386.exe'\n }\n}\n\n/**\n * Get the path to the Episoda bin directory\n */\nfunction getEpisodaBinDir(): string {\n return path.join(os.homedir(), '.episoda', 'bin')\n}\n\n/**\n * Get the expected path for cloudflared in the Episoda bin directory\n */\nfunction getCloudflaredPath(): string {\n const binaryName = os.platform() === 'win32' ? 'cloudflared.exe' : 'cloudflared'\n return path.join(getEpisodaBinDir(), binaryName)\n}\n\n/**\n * Check if cloudflared is available in PATH\n */\nfunction isCloudflaredInPath(): string | null {\n try {\n // Use 'where' on Windows, 'which' on Unix\n const command = os.platform() === 'win32' ? 'where' : 'which'\n const binaryName = os.platform() === 'win32' ? 'cloudflared.exe' : 'cloudflared'\n const result = spawnSync(command, [binaryName], { encoding: 'utf-8' })\n if (result.status === 0 && result.stdout.trim()) {\n // 'where' on Windows may return multiple lines, take the first one\n return result.stdout.trim().split('\\n')[0].trim()\n }\n } catch {\n // Not found\n }\n return null\n}\n\n/**\n * Check if cloudflared is installed in Episoda bin directory\n */\nfunction isCloudflaredInstalled(): boolean {\n const cloudflaredPath = getCloudflaredPath()\n try {\n fs.accessSync(cloudflaredPath, fs.constants.X_OK)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Verify cloudflared binary works by checking version\n */\nfunction verifyCloudflared(binaryPath: string): boolean {\n try {\n const result = spawnSync(binaryPath, ['version'], { encoding: 'utf-8', timeout: 5000 })\n return result.status === 0 && result.stdout.includes('cloudflared')\n } catch {\n return false\n }\n}\n\n/**\n * Get the download URL for the current platform\n */\nfunction getDownloadUrl(): string | null {\n const platform = os.platform()\n const arch = os.arch()\n\n const platformUrls = DOWNLOAD_URLS[platform]\n if (!platformUrls) {\n return null\n }\n\n return platformUrls[arch] || null\n}\n\n/**\n * Download a file from URL with redirect support\n */\nasync function downloadFile(url: string, destPath: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const followRedirect = (currentUrl: string, redirectCount = 0) => {\n if (redirectCount > 5) {\n reject(new Error('Too many redirects'))\n return\n }\n\n const urlObj = new URL(currentUrl)\n const options = {\n hostname: urlObj.hostname,\n path: urlObj.pathname + urlObj.search,\n headers: {\n 'User-Agent': 'episoda-cli'\n }\n }\n\n https.get(options, (response) => {\n // Handle redirects\n if (response.statusCode === 301 || response.statusCode === 302) {\n const redirectUrl = response.headers.location\n if (redirectUrl) {\n followRedirect(redirectUrl, redirectCount + 1)\n return\n }\n }\n\n if (response.statusCode !== 200) {\n reject(new Error(`Failed to download: HTTP ${response.statusCode}`))\n return\n }\n\n const file = fs.createWriteStream(destPath)\n response.pipe(file)\n file.on('finish', () => {\n file.close()\n resolve()\n })\n file.on('error', (err) => {\n fs.unlinkSync(destPath)\n reject(err)\n })\n }).on('error', reject)\n }\n\n followRedirect(url)\n })\n}\n\n/**\n * Extract tgz archive\n */\nasync function extractTgz(archivePath: string, destDir: string): Promise<void> {\n return tar.x({\n file: archivePath,\n cwd: destDir\n })\n}\n\n/**\n * Download and install cloudflared\n */\nasync function downloadCloudflared(): Promise<string> {\n const url = getDownloadUrl()\n if (!url) {\n throw new Error(`Unsupported platform: ${os.platform()} ${os.arch()}`)\n }\n\n const binDir = getEpisodaBinDir()\n const cloudflaredPath = getCloudflaredPath()\n\n // Ensure bin directory exists\n fs.mkdirSync(binDir, { recursive: true })\n\n const isTgz = url.endsWith('.tgz')\n\n if (isTgz) {\n // Download to temp file and extract\n const tempFile = path.join(binDir, 'cloudflared.tgz')\n\n console.log(`[Tunnel] Downloading cloudflared from ${url}...`)\n await downloadFile(url, tempFile)\n\n console.log('[Tunnel] Extracting cloudflared...')\n await extractTgz(tempFile, binDir)\n\n // Clean up temp file\n fs.unlinkSync(tempFile)\n } else {\n // Direct binary download (Linux/Windows)\n console.log(`[Tunnel] Downloading cloudflared from ${url}...`)\n await downloadFile(url, cloudflaredPath)\n }\n\n // Make executable (not needed on Windows)\n if (os.platform() !== 'win32') {\n fs.chmodSync(cloudflaredPath, 0o755)\n }\n\n // Verify installation\n if (!verifyCloudflared(cloudflaredPath)) {\n throw new Error('Downloaded cloudflared binary failed verification')\n }\n\n console.log('[Tunnel] cloudflared installed successfully')\n return cloudflaredPath\n}\n\n/**\n * Ensure cloudflared is available\n * Returns the path to the cloudflared binary\n */\nexport async function ensureCloudflared(): Promise<string> {\n // Check PATH first\n const pathBinary = isCloudflaredInPath()\n if (pathBinary && verifyCloudflared(pathBinary)) {\n return pathBinary\n }\n\n // Check Episoda bin directory\n const episodaBinary = getCloudflaredPath()\n if (isCloudflaredInstalled() && verifyCloudflared(episodaBinary)) {\n return episodaBinary\n }\n\n // Download and install\n return downloadCloudflared()\n}\n\n/**\n * Get cloudflared version\n */\nexport function getCloudflaredVersion(binaryPath: string): string | null {\n try {\n const result = spawnSync(binaryPath, ['version'], { encoding: 'utf-8', timeout: 5000 })\n if (result.status === 0) {\n // Parse version from output like \"cloudflared version 2024.1.0 (built 2024-01-15T12:00:00Z)\"\n const match = result.stdout.match(/cloudflared version ([\\d.]+)/)\n return match ? match[1] : null\n }\n } catch {\n // Ignore errors\n }\n return null\n}\n\n/**\n * Check if cloudflared is available (without downloading)\n */\nexport function isCloudflaredAvailable(): boolean {\n const pathBinary = isCloudflaredInPath()\n if (pathBinary) return true\n\n return isCloudflaredInstalled()\n}\n","/**\n * Tunnel Manager\n *\n * EP672: Manages Cloudflare quick tunnels for local dev preview.\n * Handles starting, stopping, and monitoring tunnel processes.\n *\n * EP877: Added PID-based process tracking for reliable cleanup across restarts,\n * mutex locks to prevent concurrent tunnel starts, and orphan cleanup on init.\n *\n * @module tunnel/tunnel-manager\n */\n\nimport { spawn, ChildProcess, execSync } from 'child_process'\nimport { EventEmitter } from 'events'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport { ensureCloudflared } from './cloudflared-manager'\nimport type {\n TunnelInfo,\n TunnelStatus,\n StartTunnelOptions,\n StartTunnelResult,\n TunnelEvent\n} from './types'\n\n/**\n * EP877: Directory for storing tunnel PID files\n */\nconst TUNNEL_PID_DIR = path.join(os.homedir(), '.episoda', 'tunnels')\n\n/**\n * Regex to match tunnel URL from cloudflared output\n * Format: \"https://random-words-here.trycloudflare.com\"\n */\nconst TUNNEL_URL_REGEX = /https:\\/\\/[a-z0-9-]+\\.trycloudflare\\.com/i\n\n/**\n * EP672-9: Reconnection configuration\n */\ninterface ReconnectConfig {\n maxRetries: number\n initialDelayMs: number\n maxDelayMs: number\n backoffMultiplier: number\n}\n\nconst DEFAULT_RECONNECT_CONFIG: ReconnectConfig = {\n maxRetries: 5,\n initialDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2\n}\n\n/**\n * EP672-9: Internal tunnel state for reconnection tracking\n */\ninterface TunnelState {\n info: TunnelInfo & { process: ChildProcess }\n options: StartTunnelOptions\n intentionallyStopped: boolean\n retryCount: number\n retryTimeoutId: NodeJS.Timeout | null\n}\n\n/**\n * Manages Cloudflare quick tunnels for local development\n */\nexport class TunnelManager extends EventEmitter {\n private tunnelStates: Map<string, TunnelState> = new Map()\n private cloudflaredPath: string | null = null\n private reconnectConfig: ReconnectConfig\n\n /**\n * EP877: Mutex locks to prevent concurrent tunnel starts for the same module\n */\n private startLocks: Map<string, Promise<StartTunnelResult>> = new Map()\n\n constructor(config?: Partial<ReconnectConfig>) {\n super()\n this.reconnectConfig = { ...DEFAULT_RECONNECT_CONFIG, ...config }\n }\n\n /**\n * EP877: Ensure PID directory exists\n * EP904: Added proper error handling and logging\n */\n private ensurePidDir(): void {\n try {\n if (!fs.existsSync(TUNNEL_PID_DIR)) {\n console.log(`[Tunnel] EP904: Creating PID directory: ${TUNNEL_PID_DIR}`)\n fs.mkdirSync(TUNNEL_PID_DIR, { recursive: true })\n console.log(`[Tunnel] EP904: PID directory created successfully`)\n }\n } catch (error) {\n console.error(`[Tunnel] EP904: Failed to create PID directory ${TUNNEL_PID_DIR}:`, error)\n // Re-throw to make failures visible - don't swallow errors\n throw error\n }\n }\n\n /**\n * EP877: Get PID file path for a module\n */\n private getPidFilePath(moduleUid: string): string {\n return path.join(TUNNEL_PID_DIR, `${moduleUid}.pid`)\n }\n\n /**\n * EP877: Write PID to file for tracking across restarts\n * EP904: Enhanced logging and error visibility\n */\n private writePidFile(moduleUid: string, pid: number): void {\n try {\n this.ensurePidDir()\n const pidPath = this.getPidFilePath(moduleUid)\n fs.writeFileSync(pidPath, pid.toString(), 'utf8')\n console.log(`[Tunnel] EP904: Wrote PID ${pid} for ${moduleUid} to ${pidPath}`)\n } catch (error) {\n console.error(`[Tunnel] EP904: Failed to write PID file for ${moduleUid}:`, error)\n // Don't re-throw - PID file is for recovery, not critical path\n // But log prominently so we notice failures\n }\n }\n\n /**\n * EP877: Read PID from file\n */\n private readPidFile(moduleUid: string): number | null {\n try {\n const pidPath = this.getPidFilePath(moduleUid)\n if (!fs.existsSync(pidPath)) {\n return null\n }\n const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10)\n return isNaN(pid) ? null : pid\n } catch (error) {\n return null\n }\n }\n\n /**\n * EP877: Remove PID file\n */\n private removePidFile(moduleUid: string): void {\n try {\n const pidPath = this.getPidFilePath(moduleUid)\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n console.log(`[Tunnel] EP877: Removed PID file for ${moduleUid}`)\n }\n } catch (error) {\n console.error(`[Tunnel] EP877: Failed to remove PID file for ${moduleUid}:`, error)\n }\n }\n\n /**\n * EP877: Check if a process is running by PID\n */\n private isProcessRunning(pid: number): boolean {\n try {\n // Sending signal 0 checks if process exists without killing it\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n }\n\n /**\n * EP877: Kill a process by PID\n */\n private killByPid(pid: number, signal: NodeJS.Signals = 'SIGTERM'): boolean {\n try {\n process.kill(pid, signal)\n console.log(`[Tunnel] EP877: Sent ${signal} to PID ${pid}`)\n return true\n } catch (error) {\n // Process doesn't exist or we don't have permission\n return false\n }\n }\n\n /**\n * EP877: Find all cloudflared processes using pgrep\n */\n private findCloudflaredProcesses(): number[] {\n try {\n // Use pgrep to find cloudflared processes\n const output = execSync('pgrep -f cloudflared', { encoding: 'utf8' })\n return output.trim().split('\\n').map(pid => parseInt(pid, 10)).filter(pid => !isNaN(pid))\n } catch {\n // pgrep returns non-zero if no processes found\n return []\n }\n }\n\n /**\n * EP904: Get the port a cloudflared process is tunneling to\n * Extracts port from process command line arguments\n */\n private getProcessPort(pid: number): number | null {\n try {\n // Get the full command line for the process\n const output = execSync(`ps -p ${pid} -o args=`, { encoding: 'utf8' }).trim()\n\n // Match pattern: --url http://localhost:PORT\n const portMatch = output.match(/--url\\s+https?:\\/\\/localhost:(\\d+)/)\n if (portMatch) {\n return parseInt(portMatch[1], 10)\n }\n return null\n } catch {\n return null\n }\n }\n\n /**\n * EP904: Find cloudflared processes on a specific port\n */\n private findCloudflaredOnPort(port: number): number[] {\n const allProcesses = this.findCloudflaredProcesses()\n return allProcesses.filter(pid => this.getProcessPort(pid) === port)\n }\n\n /**\n * EP904: Kill all cloudflared processes on a specific port\n * Returns the PIDs that were killed\n */\n private async killCloudflaredOnPort(port: number): Promise<number[]> {\n const pidsOnPort = this.findCloudflaredOnPort(port)\n const killed: number[] = []\n\n for (const pid of pidsOnPort) {\n // Check if this PID is tracked by us (current TunnelManager instance)\n const isTracked = Array.from(this.tunnelStates.values()).some(s => s.info.pid === pid)\n\n console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`)\n\n this.killByPid(pid, 'SIGTERM')\n await new Promise(resolve => setTimeout(resolve, 500))\n\n if (this.isProcessRunning(pid)) {\n this.killByPid(pid, 'SIGKILL')\n await new Promise(resolve => setTimeout(resolve, 200))\n }\n\n killed.push(pid)\n }\n\n if (killed.length > 0) {\n console.log(`[Tunnel] EP904: Killed ${killed.length} cloudflared process(es) on port ${port}: ${killed.join(', ')}`)\n }\n\n return killed\n }\n\n /**\n * EP877: Cleanup orphaned cloudflared processes on startup\n * Kills any cloudflared processes that have PID files but aren't tracked in memory,\n * and any cloudflared processes that don't have corresponding PID files.\n */\n async cleanupOrphanedProcesses(): Promise<{ cleaned: number; pids: number[] }> {\n const cleaned: number[] = []\n\n try {\n this.ensurePidDir()\n\n // 1. Clean up based on PID files\n const pidFiles = fs.readdirSync(TUNNEL_PID_DIR).filter(f => f.endsWith('.pid'))\n\n for (const pidFile of pidFiles) {\n const moduleUid = pidFile.replace('.pid', '')\n const pid = this.readPidFile(moduleUid)\n\n if (pid && this.isProcessRunning(pid)) {\n // Process is running but not tracked - kill it\n if (!this.tunnelStates.has(moduleUid)) {\n console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`)\n this.killByPid(pid, 'SIGTERM')\n\n // Wait a bit then force kill if needed\n await new Promise(resolve => setTimeout(resolve, 1000))\n if (this.isProcessRunning(pid)) {\n this.killByPid(pid, 'SIGKILL')\n }\n\n cleaned.push(pid)\n }\n }\n\n // Remove stale PID file\n this.removePidFile(moduleUid)\n }\n\n // 2. Find any cloudflared processes not tracked by PID files\n const runningPids = this.findCloudflaredProcesses()\n const trackedPids = Array.from(this.tunnelStates.values())\n .map(s => s.info.pid)\n .filter((pid): pid is number => pid !== undefined)\n\n for (const pid of runningPids) {\n if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {\n console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`)\n this.killByPid(pid, 'SIGTERM')\n\n await new Promise(resolve => setTimeout(resolve, 500))\n if (this.isProcessRunning(pid)) {\n this.killByPid(pid, 'SIGKILL')\n }\n\n cleaned.push(pid)\n }\n }\n\n if (cleaned.length > 0) {\n console.log(`[Tunnel] EP877: Cleaned up ${cleaned.length} orphaned cloudflared processes: ${cleaned.join(', ')}`)\n }\n\n } catch (error) {\n console.error('[Tunnel] EP877: Error during orphan cleanup:', error)\n }\n\n return { cleaned: cleaned.length, pids: cleaned }\n }\n\n /**\n * Emit typed tunnel events\n */\n private emitEvent(event: TunnelEvent): void {\n this.emit('tunnel', event)\n }\n\n /**\n * Initialize the tunnel manager\n * Ensures cloudflared is available and cleans up orphaned processes\n *\n * EP877: Now includes orphaned process cleanup on startup\n */\n async initialize(): Promise<void> {\n this.cloudflaredPath = await ensureCloudflared()\n\n // EP877: Clean up any orphaned cloudflared processes from previous runs\n const cleanup = await this.cleanupOrphanedProcesses()\n if (cleanup.cleaned > 0) {\n console.log(`[Tunnel] EP877: Initialization cleaned up ${cleanup.cleaned} orphaned processes`)\n }\n }\n\n /**\n * EP672-9: Calculate delay for exponential backoff\n */\n private calculateBackoffDelay(retryCount: number): number {\n const delay = this.reconnectConfig.initialDelayMs *\n Math.pow(this.reconnectConfig.backoffMultiplier, retryCount)\n return Math.min(delay, this.reconnectConfig.maxDelayMs)\n }\n\n /**\n * EP672-9: Attempt to reconnect a crashed tunnel\n */\n private async attemptReconnect(moduleUid: string): Promise<void> {\n const state = this.tunnelStates.get(moduleUid)\n if (!state || state.intentionallyStopped) {\n return\n }\n\n if (state.retryCount >= this.reconnectConfig.maxRetries) {\n console.log(`[Tunnel] Max retries (${this.reconnectConfig.maxRetries}) reached for ${moduleUid}, giving up`)\n this.emitEvent({\n type: 'error',\n moduleUid,\n error: `Tunnel failed after ${this.reconnectConfig.maxRetries} reconnection attempts`\n })\n state.options.onStatusChange?.('error', 'Max reconnection attempts reached')\n this.tunnelStates.delete(moduleUid)\n return\n }\n\n const delay = this.calculateBackoffDelay(state.retryCount)\n console.log(`[Tunnel] Reconnecting ${moduleUid} in ${delay}ms (attempt ${state.retryCount + 1}/${this.reconnectConfig.maxRetries})`)\n\n this.emitEvent({ type: 'reconnecting', moduleUid })\n state.options.onStatusChange?.('reconnecting')\n\n state.retryTimeoutId = setTimeout(async () => {\n state.retryCount++\n\n // Start a new tunnel process (internal method that doesn't reset state)\n const result = await this.startTunnelProcess(state.options, state)\n if (result.success) {\n console.log(`[Tunnel] Reconnected ${moduleUid} successfully with new URL: ${result.url}`)\n state.retryCount = 0 // Reset on success\n }\n // If failed, the exit handler will trigger another reconnect attempt\n }, delay)\n }\n\n /**\n * EP672-9: Internal method to start the tunnel process\n * Separated from startTunnel to support reconnection\n */\n private async startTunnelProcess(\n options: StartTunnelOptions,\n existingState?: TunnelState\n ): Promise<StartTunnelResult> {\n const { moduleUid, port = 3000, onUrl, onStatusChange } = options\n\n // Ensure cloudflared is available\n if (!this.cloudflaredPath) {\n try {\n this.cloudflaredPath = await ensureCloudflared()\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return { success: false, error: `Failed to get cloudflared: ${errorMessage}` }\n }\n }\n\n return new Promise((resolve) => {\n const tunnelInfo: TunnelInfo & { process: ChildProcess } = {\n moduleUid,\n url: '',\n port,\n status: 'starting',\n startedAt: new Date(),\n process: null as any // Will be set below\n }\n\n // Start cloudflared quick tunnel\n // Command: cloudflared tunnel --url http://localhost:3000\n const process = spawn(this.cloudflaredPath!, [\n 'tunnel',\n '--url',\n `http://localhost:${port}`\n ], {\n stdio: ['ignore', 'pipe', 'pipe']\n })\n\n tunnelInfo.process = process\n tunnelInfo.pid = process.pid\n\n // EP877: Write PID file for tracking across restarts\n if (process.pid) {\n this.writePidFile(moduleUid, process.pid)\n }\n\n // Update or create state\n const state: TunnelState = existingState || {\n info: tunnelInfo,\n options,\n intentionallyStopped: false,\n retryCount: 0,\n retryTimeoutId: null\n }\n state.info = tunnelInfo\n this.tunnelStates.set(moduleUid, state)\n\n let urlFound = false\n let stdoutBuffer = ''\n let stderrBuffer = ''\n\n // Parse tunnel URL from stderr (cloudflared outputs info to stderr)\n const parseOutput = (data: string) => {\n if (urlFound) return\n\n const match = data.match(TUNNEL_URL_REGEX)\n if (match) {\n urlFound = true\n tunnelInfo.url = match[0]\n tunnelInfo.status = 'connected'\n\n onStatusChange?.('connected')\n onUrl?.(tunnelInfo.url)\n\n this.emitEvent({\n type: 'started',\n moduleUid,\n url: tunnelInfo.url\n })\n\n resolve({ success: true, url: tunnelInfo.url })\n }\n }\n\n process.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString()\n parseOutput(stdoutBuffer)\n })\n\n process.stderr?.on('data', (data: Buffer) => {\n stderrBuffer += data.toString()\n parseOutput(stderrBuffer)\n })\n\n // Handle process exit\n process.on('exit', (code, signal) => {\n const wasConnected = tunnelInfo.status === 'connected'\n tunnelInfo.status = 'disconnected'\n\n const currentState = this.tunnelStates.get(moduleUid)\n\n if (!urlFound) {\n // Process exited before we got a URL\n const errorMsg = `Tunnel process exited with code ${code}`\n tunnelInfo.status = 'error'\n tunnelInfo.error = errorMsg\n\n // EP672-9: Attempt reconnect if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('error', errorMsg)\n this.emitEvent({ type: 'error', moduleUid, error: errorMsg })\n }\n\n resolve({ success: false, error: errorMsg })\n } else if (wasConnected) {\n // Tunnel was running and then disconnected unexpectedly\n // EP672-9: Attempt reconnect if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n console.log(`[Tunnel] ${moduleUid} crashed unexpectedly, attempting reconnect...`)\n onStatusChange?.('reconnecting')\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('disconnected')\n this.emitEvent({ type: 'stopped', moduleUid })\n }\n }\n })\n\n process.on('error', (error) => {\n tunnelInfo.status = 'error'\n tunnelInfo.error = error.message\n\n const currentState = this.tunnelStates.get(moduleUid)\n\n // EP672-9: Attempt reconnect if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('error', error.message)\n this.emitEvent({ type: 'error', moduleUid, error: error.message })\n }\n\n if (!urlFound) {\n resolve({ success: false, error: error.message })\n }\n })\n\n // Timeout if tunnel doesn't start in 30 seconds\n setTimeout(() => {\n if (!urlFound) {\n process.kill()\n const errorMsg = 'Tunnel startup timed out after 30 seconds'\n\n tunnelInfo.status = 'error'\n tunnelInfo.error = errorMsg\n\n const currentState = this.tunnelStates.get(moduleUid)\n\n // EP672-9: Attempt reconnect on timeout if not intentionally stopped\n if (currentState && !currentState.intentionallyStopped) {\n this.attemptReconnect(moduleUid)\n } else {\n this.tunnelStates.delete(moduleUid)\n onStatusChange?.('error', errorMsg)\n this.emitEvent({ type: 'error', moduleUid, error: errorMsg })\n }\n\n resolve({ success: false, error: errorMsg })\n }\n }, 30000)\n })\n }\n\n /**\n * Start a tunnel for a module\n *\n * EP877: Uses mutex lock to prevent concurrent starts for the same module\n */\n async startTunnel(options: StartTunnelOptions): Promise<StartTunnelResult> {\n const { moduleUid } = options\n\n // EP877: Check if there's already a pending start for this module\n const existingLock = this.startLocks.get(moduleUid)\n if (existingLock) {\n console.log(`[Tunnel] EP877: Waiting for existing start operation for ${moduleUid}`)\n return existingLock\n }\n\n // EP877: Create a lock for this module's start operation\n const startPromise = this.startTunnelWithLock(options)\n this.startLocks.set(moduleUid, startPromise)\n\n try {\n return await startPromise\n } finally {\n // EP877: Release the lock when done\n this.startLocks.delete(moduleUid)\n }\n }\n\n /**\n * EP877: Internal start implementation with lock already held\n * EP901: Enhanced to clean up ALL orphaned cloudflared processes before starting\n * EP904: Added port-based deduplication to prevent multiple tunnels on same port\n */\n private async startTunnelWithLock(options: StartTunnelOptions): Promise<StartTunnelResult> {\n const { moduleUid, port = 3000 } = options\n\n // Check if tunnel already exists for this module\n const existingState = this.tunnelStates.get(moduleUid)\n if (existingState) {\n if (existingState.info.status === 'connected') {\n return { success: true, url: existingState.info.url }\n }\n // Stop existing tunnel before starting new one\n await this.stopTunnel(moduleUid)\n }\n\n // EP877: Also check for orphaned process by PID file before starting\n const orphanPid = this.readPidFile(moduleUid)\n if (orphanPid && this.isProcessRunning(orphanPid)) {\n console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`)\n this.killByPid(orphanPid, 'SIGTERM')\n await new Promise(resolve => setTimeout(resolve, 500))\n if (this.isProcessRunning(orphanPid)) {\n this.killByPid(orphanPid, 'SIGKILL')\n }\n this.removePidFile(moduleUid)\n }\n\n // EP904: Port-based deduplication - kill any cloudflared processes on target port\n // This ensures only one tunnel per port, preventing the duplicate process issue\n const killedOnPort = await this.killCloudflaredOnPort(port)\n if (killedOnPort.length > 0) {\n console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`)\n // EP953: Increased delay to ensure ports are fully released after killing orphans\n // 300ms was often insufficient, causing \"exited with code null\" on new tunnel start\n await new Promise(resolve => setTimeout(resolve, 1000))\n }\n\n // EP901: Clean up ALL orphaned cloudflared processes before starting a new tunnel\n // This catches orphans from other modules that might interfere with the new tunnel\n const cleanup = await this.cleanupOrphanedProcesses()\n if (cleanup.cleaned > 0) {\n console.log(`[Tunnel] EP901: Pre-start cleanup removed ${cleanup.cleaned} orphaned processes`)\n }\n\n return this.startTunnelProcess(options)\n }\n\n /**\n * Stop a tunnel for a module\n *\n * EP877: Enhanced to handle cleanup via PID file when in-memory state is missing\n */\n async stopTunnel(moduleUid: string): Promise<void> {\n const state = this.tunnelStates.get(moduleUid)\n\n // EP877: Even if no in-memory state, check for orphaned process via PID file\n if (!state) {\n const orphanPid = this.readPidFile(moduleUid)\n if (orphanPid && this.isProcessRunning(orphanPid)) {\n console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`)\n this.killByPid(orphanPid, 'SIGTERM')\n await new Promise(resolve => setTimeout(resolve, 1000))\n if (this.isProcessRunning(orphanPid)) {\n this.killByPid(orphanPid, 'SIGKILL')\n }\n }\n this.removePidFile(moduleUid)\n return\n }\n\n // EP672-9: Mark as intentionally stopped to prevent reconnection\n state.intentionallyStopped = true\n\n // EP672-9: Clear any pending reconnect timeout\n if (state.retryTimeoutId) {\n clearTimeout(state.retryTimeoutId)\n state.retryTimeoutId = null\n }\n\n const tunnel = state.info\n\n // Kill the cloudflared process\n if (tunnel.process && !tunnel.process.killed) {\n tunnel.process.kill('SIGTERM')\n\n // Give it a moment to exit gracefully\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n if (tunnel.process && !tunnel.process.killed) {\n tunnel.process.kill('SIGKILL')\n }\n resolve()\n }, 3000)\n\n tunnel.process.once('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n // EP877: Remove PID file\n this.removePidFile(moduleUid)\n\n this.tunnelStates.delete(moduleUid)\n this.emitEvent({ type: 'stopped', moduleUid })\n }\n\n /**\n * Stop all active tunnels\n */\n async stopAllTunnels(): Promise<void> {\n const moduleUids = Array.from(this.tunnelStates.keys())\n await Promise.all(moduleUids.map(uid => this.stopTunnel(uid)))\n }\n\n /**\n * Get information about an active tunnel\n */\n getTunnel(moduleUid: string): TunnelInfo | null {\n const state = this.tunnelStates.get(moduleUid)\n if (!state) return null\n\n // Return without the process reference\n const { process, ...info } = state.info\n return info\n }\n\n /**\n * Get all active tunnels\n */\n getAllTunnels(): TunnelInfo[] {\n return Array.from(this.tunnelStates.values()).map(state => {\n const { process, ...info } = state.info\n return info\n })\n }\n\n /**\n * Check if a tunnel is active for a module\n */\n hasTunnel(moduleUid: string): boolean {\n return this.tunnelStates.has(moduleUid)\n }\n\n /**\n * EP956: Get all module UIDs with active tunnels\n */\n getActiveModuleUids(): string[] {\n return Array.from(this.tunnelStates.keys())\n }\n\n /**\n * Get the URL for an active tunnel\n */\n getTunnelUrl(moduleUid: string): string | null {\n return this.tunnelStates.get(moduleUid)?.info.url || null\n }\n}\n\n// Singleton instance\nlet tunnelManagerInstance: TunnelManager | null = null\n\n/**\n * Get the singleton TunnelManager instance\n */\nexport function getTunnelManager(): TunnelManager {\n if (!tunnelManagerInstance) {\n tunnelManagerInstance = new TunnelManager()\n }\n return tunnelManagerInstance\n}\n","/**\n * Tunnel API Utilities\n *\n * EP823: Shared functions for tunnel API interactions.\n * Consolidates duplicated tunnel URL cleanup logic.\n *\n * @module tunnel/tunnel-api\n */\n\nimport { loadConfig } from '@episoda/core'\n\n/**\n * Clear tunnel URL from the Episoda API\n *\n * Sends a DELETE request to clear tunnel_url, tunnel_error, and tunnel_started_at\n * from the module record. Fails silently on errors to avoid blocking tunnel operations.\n *\n * @param moduleUid - The module UID (e.g., \"EP823\")\n * @returns Promise that resolves when cleanup is complete (or fails silently)\n */\nexport async function clearTunnelUrl(moduleUid: string): Promise<void> {\n // Skip for LOCAL tunnels (not associated with a module)\n if (!moduleUid || moduleUid === 'LOCAL') {\n return\n }\n\n const config = await loadConfig()\n if (!config?.access_token) {\n return\n }\n\n try {\n const apiUrl = config.api_url || 'https://episoda.dev'\n await fetch(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${config.access_token}`\n }\n })\n } catch {\n // Ignore cleanup errors - don't block tunnel operations\n }\n}\n","/**\n * EP912: Claude Code Binary Detection\n *\n * Detection-first approach:\n * 1. Check if claude is in PATH (user's global install)\n * 2. Check if bundled in node_modules/.bin\n * 3. Return path to working binary, or throw clear error\n *\n * This ensures zero user action is required - Claude Code is available\n * either from their global install or from our bundled dependency.\n */\n\nimport { execSync } from 'child_process'\nimport * as path from 'path'\nimport * as fs from 'fs'\n\n/**\n * Cached binary path (resolved once per daemon lifetime)\n */\nlet cachedBinaryPath: string | null = null\n\n/**\n * Check if a binary path is executable and returns valid version\n */\nfunction isValidClaudeBinary(binaryPath: string): boolean {\n try {\n // Check if file exists and is executable\n fs.accessSync(binaryPath, fs.constants.X_OK)\n\n // Try to get version to verify it's actually Claude Code\n const version = execSync(`\"${binaryPath}\" --version`, {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe']\n }).trim()\n\n // Version should contain a semver-like pattern\n if (version && /\\d+\\.\\d+/.test(version)) {\n console.log(`[AgentManager] Found Claude Code at ${binaryPath}: v${version}`)\n return true\n }\n\n return false\n } catch {\n return false\n }\n}\n\n/**\n * Find the Claude Code binary using detection-first approach\n *\n * @returns Path to Claude Code binary\n * @throws Error if Claude Code is not found anywhere\n */\nexport async function ensureClaudeBinary(): Promise<string> {\n // Return cached path if already resolved\n if (cachedBinaryPath) {\n return cachedBinaryPath\n }\n\n // 1. Check PATH for global installation\n try {\n const pathResult = execSync('which claude', {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe']\n }).trim()\n\n if (pathResult && isValidClaudeBinary(pathResult)) {\n cachedBinaryPath = pathResult\n return cachedBinaryPath\n }\n } catch {\n // Not in PATH, continue checking\n }\n\n // 2. Check bundled location in this package's node_modules/.bin\n const bundledPaths = [\n // In production: node_modules/.bin/claude\n path.join(__dirname, '..', '..', 'node_modules', '.bin', 'claude'),\n // In monorepo development: packages/episoda/node_modules/.bin/claude\n path.join(__dirname, '..', '..', '..', '..', 'node_modules', '.bin', 'claude'),\n // Root monorepo node_modules\n path.join(__dirname, '..', '..', '..', '..', '..', 'node_modules', '.bin', 'claude'),\n ]\n\n for (const bundledPath of bundledPaths) {\n if (fs.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {\n cachedBinaryPath = bundledPath\n return cachedBinaryPath\n }\n }\n\n // 3. Check npx availability as last resort\n try {\n const npxResult = execSync('npx --yes @anthropic-ai/claude-code --version', {\n encoding: 'utf-8',\n timeout: 30000, // npx might need to download\n stdio: ['pipe', 'pipe', 'pipe']\n }).trim()\n\n if (npxResult && /\\d+\\.\\d+/.test(npxResult)) {\n // Return a special marker that indicates npx should be used\n cachedBinaryPath = 'npx:@anthropic-ai/claude-code'\n console.log(`[AgentManager] Using npx to run Claude Code: v${npxResult}`)\n return cachedBinaryPath\n }\n } catch {\n // npx failed too\n }\n\n throw new Error(\n 'Claude Code not found. Please install it globally with: npm install -g @anthropic-ai/claude-code'\n )\n}\n\n/**\n * Get the cached binary path without performing detection\n * Returns null if not yet resolved\n */\nexport function getCachedClaudeBinary(): string | null {\n return cachedBinaryPath\n}\n\n/**\n * Clear the cached binary path (useful for testing)\n */\nexport function clearCachedClaudeBinary(): void {\n cachedBinaryPath = null\n}\n\n/**\n * Check if Claude Code binary is available without throwing\n */\nexport async function isClaudeCodeAvailable(): Promise<boolean> {\n try {\n await ensureClaudeBinary()\n return true\n } catch {\n return false\n }\n}\n","/**\n * EP912: Agent Manager\n *\n * Manages local Claude Code agent sessions for dev_mode: local modules.\n * Follows the TunnelManager pattern for consistency.\n *\n * Responsibilities:\n * - Track active agent sessions\n * - Spawn Claude Code processes with stream-json output\n * - Forward streaming output via callbacks\n * - Handle abort/stop with SIGINT\n * - Clean up orphaned processes on daemon restart\n */\n\nimport { ChildProcess, spawn } from 'child_process'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport { ensureClaudeBinary, getCachedClaudeBinary } from './claude-binary'\nimport type {\n AgentSession,\n AgentSessionStatus,\n StartAgentOptions,\n SendMessageOptions,\n StartAgentResult,\n SendMessageResult\n} from './types'\n\n/**\n * Extended session with credentials (stored in memory only, never persisted to disk)\n * EP936: Changed from apiKey to oauthToken - Claude Code uses OAuth credentials\n * written to ~/.claude/.credentials.json, not ANTHROPIC_API_KEY env var\n */\ninterface AgentSessionWithCredentials extends AgentSession {\n credentials: {\n oauthToken: string // OAuth access_token from claude.ai (NOT an API key)\n }\n systemPrompt?: string\n}\n\n/**\n * Singleton AgentManager instance\n */\nlet instance: AgentManager | null = null\n\n/**\n * Get the singleton AgentManager instance\n */\nexport function getAgentManager(): AgentManager {\n if (!instance) {\n instance = new AgentManager()\n }\n return instance\n}\n\n/**\n * AgentManager class - manages local Claude Code agent sessions\n */\nexport class AgentManager {\n private sessions = new Map<string, AgentSessionWithCredentials>()\n private processes = new Map<string, ChildProcess>()\n private initialized = false\n private pidDir: string\n\n constructor() {\n this.pidDir = path.join(os.homedir(), '.episoda', 'agent-pids')\n }\n\n /**\n * Initialize the agent manager\n * - Ensure Claude Code is available\n * - Clean up any orphaned processes from previous daemon runs\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n return\n }\n\n console.log('[AgentManager] Initializing...')\n\n // Ensure PID directory exists\n if (!fs.existsSync(this.pidDir)) {\n fs.mkdirSync(this.pidDir, { recursive: true })\n }\n\n // Clean up orphaned processes from previous daemon runs\n await this.cleanupOrphanedProcesses()\n\n // Pre-warm binary detection\n try {\n await ensureClaudeBinary()\n console.log('[AgentManager] Claude Code binary verified')\n } catch (error) {\n console.warn('[AgentManager] Claude Code not available:', error instanceof Error ? error.message : error)\n // Don't fail initialization - we'll handle this when start is called\n }\n\n this.initialized = true\n console.log('[AgentManager] Initialized')\n }\n\n /**\n * Start a new agent session\n *\n * Creates the session record but doesn't spawn the process yet.\n * The process is spawned on the first message.\n */\n async startSession(options: StartAgentOptions): Promise<StartAgentResult> {\n const { sessionId, moduleId, moduleUid, projectPath, message, credentials, systemPrompt, onChunk, onComplete, onError } = options\n\n // Check if session already exists\n if (this.sessions.has(sessionId)) {\n return { success: false, error: 'Session already exists' }\n }\n\n // Validate credentials - EP936: Accept both oauthToken (new) and apiKey (legacy) for backward compatibility\n // The token is actually an OAuth access_token from claude.ai, written to ~/.claude/.credentials.json\n const oauthToken = credentials?.oauthToken || (credentials as any)?.apiKey\n if (!oauthToken) {\n return { success: false, error: 'Missing OAuth token in credentials. Please connect your Claude account in Settings.' }\n }\n // Store the resolved token for use later\n (credentials as any).oauthToken = oauthToken\n\n // Verify Claude Code is available\n try {\n await ensureClaudeBinary()\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Claude Code not available'\n }\n }\n\n // Create session record with credentials (stored in memory only)\n const session: AgentSessionWithCredentials = {\n sessionId,\n moduleId,\n moduleUid,\n projectPath,\n credentials,\n systemPrompt,\n status: 'starting',\n startedAt: new Date(),\n lastActivityAt: new Date()\n }\n\n this.sessions.set(sessionId, session)\n console.log(`[AgentManager] Started session ${sessionId} for ${moduleUid}`)\n\n // Immediately send the first message (spawns Claude Code process)\n return this.sendMessage({\n sessionId,\n message,\n isFirstMessage: true,\n onChunk,\n onComplete,\n onError\n })\n }\n\n /**\n * Send a message to an agent session\n *\n * Spawns a new Claude Code process for each message.\n * Uses --print for non-interactive mode and --output-format stream-json for structured output.\n * Subsequent messages use --resume with the claudeSessionId for conversation continuity.\n */\n async sendMessage(options: SendMessageOptions): Promise<SendMessageResult> {\n const { sessionId, message, isFirstMessage, claudeSessionId, onChunk, onComplete, onError } = options\n\n const session = this.sessions.get(sessionId)\n if (!session) {\n return { success: false, error: 'Session not found' }\n }\n\n // Update activity timestamp\n session.lastActivityAt = new Date()\n session.status = 'running'\n\n try {\n // Get Claude binary path\n const binaryPath = await ensureClaudeBinary()\n\n // Build command arguments\n // Note: --verbose is required when using --print with --output-format stream-json\n const args: string[] = [\n '--print', // Non-interactive mode\n '--output-format', 'stream-json', // Structured streaming output\n '--verbose', // Required for stream-json with --print\n ]\n\n // Add system prompt if provided (only on first message)\n if (isFirstMessage && session.systemPrompt) {\n args.push('--system-prompt', session.systemPrompt)\n }\n\n // Add resume flag for conversation continuity\n if (claudeSessionId) {\n args.push('--resume', claudeSessionId)\n session.claudeSessionId = claudeSessionId\n }\n\n // Add the user message\n args.push('--', message)\n\n console.log(`[AgentManager] Spawning Claude Code for session ${sessionId}`)\n console.log(`[AgentManager] Command: ${binaryPath} ${args.join(' ').substring(0, 100)}...`)\n\n // Determine how to spawn (direct path or via npx)\n let spawnCmd: string\n let spawnArgs: string[]\n\n if (binaryPath.startsWith('npx:')) {\n // Use npx\n spawnCmd = 'npx'\n spawnArgs = ['--yes', binaryPath.replace('npx:', ''), ...args]\n } else {\n // Direct binary path\n spawnCmd = binaryPath\n spawnArgs = args\n }\n\n // EP936: Write OAuth credentials to ~/.claude/.credentials.json\n // Claude Code reads this file for authentication with claude.ai OAuth\n const claudeDir = path.join(os.homedir(), '.claude')\n const credentialsPath = path.join(claudeDir, '.credentials.json')\n\n // Ensure .claude directory exists\n if (!fs.existsSync(claudeDir)) {\n fs.mkdirSync(claudeDir, { recursive: true })\n }\n\n // Write OAuth credentials in the format Claude Code expects\n const credentialsContent = JSON.stringify({\n claudeAiOauth: {\n accessToken: session.credentials.oauthToken\n }\n }, null, 2)\n fs.writeFileSync(credentialsPath, credentialsContent, { mode: 0o600 })\n console.log('[AgentManager] EP936: Wrote OAuth credentials to ~/.claude/.credentials.json')\n\n // Spawn process - no ANTHROPIC_API_KEY needed, uses credentials file\n const childProcess = spawn(spawnCmd, spawnArgs, {\n cwd: session.projectPath,\n env: {\n ...process.env,\n // Disable color output for cleaner JSON parsing\n NO_COLOR: '1',\n FORCE_COLOR: '0',\n },\n stdio: ['pipe', 'pipe', 'pipe']\n })\n\n // Track the process\n this.processes.set(sessionId, childProcess)\n\n // EP942: Close stdin immediately - Claude Code in --print mode doesn't need stdin\n // and will hang waiting for EOF if we don't close it\n childProcess.stdin?.end()\n\n // Write PID file for orphan cleanup\n if (childProcess.pid) {\n session.pid = childProcess.pid\n this.writePidFile(sessionId, childProcess.pid)\n }\n\n // Log stderr for debugging\n childProcess.stderr?.on('data', (data: Buffer) => {\n console.error(`[AgentManager] stderr: ${data.toString()}`)\n })\n\n // Log spawn errors\n childProcess.on('error', (error) => {\n console.error(`[AgentManager] Process spawn error:`, error)\n })\n\n // Buffer for incomplete JSON lines\n let stdoutBuffer = ''\n let extractedSessionId: string | undefined\n\n // Handle stdout (stream-json output)\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString()\n\n // Process complete lines\n const lines = stdoutBuffer.split('\\n')\n stdoutBuffer = lines.pop() || '' // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.trim()) continue\n\n try {\n const parsed = JSON.parse(line)\n\n // Handle different message types from Claude Code stream-json format\n // Types: init, assistant, result, error, etc.\n switch (parsed.type) {\n case 'assistant':\n // Assistant message chunk\n if (parsed.message?.content) {\n // Content is an array of content blocks\n for (const block of parsed.message.content) {\n if (block.type === 'text' && block.text) {\n onChunk(block.text)\n }\n }\n }\n break\n\n case 'content_block_delta':\n // Streaming delta\n if (parsed.delta?.text) {\n onChunk(parsed.delta.text)\n }\n break\n\n case 'result':\n // Final result - extract session ID for resume\n if (parsed.session_id) {\n extractedSessionId = parsed.session_id\n session.claudeSessionId = extractedSessionId\n }\n // Also check for subagent format\n if (parsed.result?.session_id) {\n extractedSessionId = parsed.result.session_id\n session.claudeSessionId = extractedSessionId\n }\n break\n\n case 'system':\n // System messages (init, session info)\n if (parsed.session_id) {\n extractedSessionId = parsed.session_id\n session.claudeSessionId = extractedSessionId\n }\n break\n\n case 'error':\n // Error from Claude Code\n onError(parsed.error?.message || parsed.message || 'Unknown error from Claude Code')\n break\n\n default:\n // Log unknown types for debugging\n console.log(`[AgentManager] Unknown stream-json type: ${parsed.type}`)\n }\n } catch (parseError) {\n // Non-JSON line - might be plain text output, forward it\n if (line.trim()) {\n onChunk(line + '\\n')\n }\n }\n }\n })\n\n // Handle stderr\n let stderrBuffer = ''\n childProcess.stderr?.on('data', (data: Buffer) => {\n stderrBuffer += data.toString()\n })\n\n // Handle process exit\n childProcess.on('exit', (code, signal) => {\n console.log(`[AgentManager] Claude Code exited for session ${sessionId}: code=${code}, signal=${signal}`)\n\n // Clean up\n this.processes.delete(sessionId)\n this.removePidFile(sessionId)\n\n if (code === 0) {\n session.status = 'stopped'\n onComplete(extractedSessionId || session.claudeSessionId)\n } else if (signal === 'SIGINT') {\n session.status = 'stopped'\n // Aborted by user - still complete but without session ID\n onComplete(extractedSessionId || session.claudeSessionId)\n } else {\n session.status = 'error'\n const errorMsg = stderrBuffer.trim() || `Process exited with code ${code}`\n onError(errorMsg)\n }\n })\n\n // Handle process errors\n childProcess.on('error', (error) => {\n console.error(`[AgentManager] Process error for session ${sessionId}:`, error)\n session.status = 'error'\n this.processes.delete(sessionId)\n this.removePidFile(sessionId)\n onError(error.message)\n })\n\n return { success: true }\n } catch (error) {\n session.status = 'error'\n const errorMsg = error instanceof Error ? error.message : String(error)\n onError(errorMsg)\n return { success: false, error: errorMsg }\n }\n }\n\n /**\n * Abort an agent session (SIGINT)\n *\n * Sends SIGINT to the Claude Code process to abort the current operation.\n */\n async abortSession(sessionId: string): Promise<void> {\n const process = this.processes.get(sessionId)\n if (process && !process.killed) {\n console.log(`[AgentManager] Aborting session ${sessionId} with SIGINT`)\n process.kill('SIGINT')\n }\n\n const session = this.sessions.get(sessionId)\n if (session) {\n session.status = 'stopping'\n }\n }\n\n /**\n * Stop an agent session gracefully\n *\n * Sends SIGINT and waits for process to exit.\n * If it doesn't exit within 5 seconds, sends SIGTERM.\n */\n async stopSession(sessionId: string): Promise<void> {\n const process = this.processes.get(sessionId)\n const session = this.sessions.get(sessionId)\n\n if (session) {\n session.status = 'stopping'\n }\n\n if (process && !process.killed) {\n console.log(`[AgentManager] Stopping session ${sessionId}`)\n\n // Send SIGINT first\n process.kill('SIGINT')\n\n // Wait up to 5 seconds for graceful exit\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n if (!process.killed) {\n console.log(`[AgentManager] Force killing session ${sessionId}`)\n process.kill('SIGTERM')\n }\n resolve()\n }, 5000)\n\n process.once('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n // Clean up session\n this.sessions.delete(sessionId)\n this.processes.delete(sessionId)\n this.removePidFile(sessionId)\n\n console.log(`[AgentManager] Session ${sessionId} stopped`)\n }\n\n /**\n * Stop all active sessions\n */\n async stopAllSessions(): Promise<void> {\n const sessionIds = Array.from(this.sessions.keys())\n await Promise.all(sessionIds.map(id => this.stopSession(id)))\n }\n\n /**\n * Get session info\n */\n getSession(sessionId: string): AgentSession | undefined {\n return this.sessions.get(sessionId)\n }\n\n /**\n * Get all active sessions\n */\n getAllSessions(): AgentSession[] {\n return Array.from(this.sessions.values())\n }\n\n /**\n * Check if a session exists\n */\n hasSession(sessionId: string): boolean {\n return this.sessions.has(sessionId)\n }\n\n /**\n * Clean up orphaned processes from previous daemon runs\n *\n * Reads PID files from ~/.episoda/agent-pids/ and kills any\n * processes that are still running.\n */\n async cleanupOrphanedProcesses(): Promise<{ cleaned: number }> {\n let cleaned = 0\n\n if (!fs.existsSync(this.pidDir)) {\n return { cleaned }\n }\n\n const pidFiles = fs.readdirSync(this.pidDir).filter(f => f.endsWith('.pid'))\n\n for (const pidFile of pidFiles) {\n const pidPath = path.join(this.pidDir, pidFile)\n\n try {\n const pidStr = fs.readFileSync(pidPath, 'utf-8').trim()\n const pid = parseInt(pidStr, 10)\n\n if (!isNaN(pid)) {\n // Check if process is still running\n try {\n process.kill(pid, 0) // Signal 0 just checks if process exists\n // Process is running, kill it\n console.log(`[AgentManager] Killing orphaned process ${pid}`)\n process.kill(pid, 'SIGTERM')\n cleaned++\n } catch {\n // Process not running, just clean up the file\n }\n }\n\n // Remove PID file\n fs.unlinkSync(pidPath)\n } catch (error) {\n // Ignore errors reading/cleaning PID files\n console.warn(`[AgentManager] Error cleaning PID file ${pidFile}:`, error)\n }\n }\n\n if (cleaned > 0) {\n console.log(`[AgentManager] Cleaned up ${cleaned} orphaned process(es)`)\n }\n\n return { cleaned }\n }\n\n /**\n * Write PID file for session tracking\n */\n private writePidFile(sessionId: string, pid: number): void {\n const pidPath = path.join(this.pidDir, `${sessionId}.pid`)\n fs.writeFileSync(pidPath, pid.toString())\n }\n\n /**\n * Remove PID file for session\n */\n private removePidFile(sessionId: string): void {\n const pidPath = path.join(this.pidDir, `${sessionId}.pid`)\n try {\n if (fs.existsSync(pidPath)) {\n fs.unlinkSync(pidPath)\n }\n } catch {\n // Ignore errors\n }\n }\n}\n","/**\n * Dev Server Manager\n *\n * EP818: Manages local dev server for tunnel preview.\n * EP932: Enhanced with auto-restart, memory limits, and logging.\n */\n\nimport { spawn, ChildProcess, execSync } from 'child_process'\nimport { isPortInUse } from './port-check'\nimport { getConfigDir, loadConfig } from '@episoda/core'\nimport { fetchEnvVarsWithCache } from './env-cache'\nimport http from 'http'\nimport * as fs from 'fs'\nimport * as path from 'path'\n\n/**\n * EP932: Configuration constants\n */\nconst MAX_RESTART_ATTEMPTS = 5\nconst INITIAL_RESTART_DELAY_MS = 2000\nconst MAX_RESTART_DELAY_MS = 30000\nconst MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024 // 5MB\nconst NODE_MEMORY_LIMIT_MB = 2048\n\n/**\n * EP932: Extended server info with metadata\n * EP959-m2: Added customCommand for worktree_dev_server_script support\n * EP998: Added injectedEnvVars for runtime injection\n */\ninterface ServerInfo {\n process: ChildProcess\n moduleUid: string\n projectPath: string\n port: number\n startedAt: Date\n restartCount: number\n lastRestartAt: Date | null\n autoRestartEnabled: boolean\n logFile: string | null\n customCommand?: string // EP959-m2: Custom dev server command from project settings\n injectedEnvVars?: Record<string, string> // EP998: Env vars from database\n}\n\n/**\n * Dev server process tracking\n * EP932: Now stores extended ServerInfo instead of just ChildProcess\n */\nconst activeServers: Map<string, ServerInfo> = new Map()\n\n/**\n * EP932: Get the logs directory, creating if needed\n */\nfunction getLogsDir(): string {\n const logsDir = path.join(getConfigDir(), 'logs')\n if (!fs.existsSync(logsDir)) {\n fs.mkdirSync(logsDir, { recursive: true })\n }\n return logsDir\n}\n\n/**\n * EP932: Get log file path for a module\n */\nfunction getLogFilePath(moduleUid: string): string {\n return path.join(getLogsDir(), `dev-${moduleUid}.log`)\n}\n\n/**\n * EP932: Rotate log file if it exceeds max size\n */\nfunction rotateLogIfNeeded(logPath: string): void {\n try {\n if (fs.existsSync(logPath)) {\n const stats = fs.statSync(logPath)\n if (stats.size > MAX_LOG_SIZE_BYTES) {\n // Keep one backup\n const backupPath = `${logPath}.1`\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath)\n }\n fs.renameSync(logPath, backupPath)\n console.log(`[DevServer] EP932: Rotated log file for ${path.basename(logPath)}`)\n }\n }\n } catch (error) {\n console.warn(`[DevServer] EP932: Failed to rotate log:`, error)\n }\n}\n\n/**\n * EP932: Write to log file with timestamp\n */\nfunction writeToLog(logPath: string, line: string, isError: boolean = false): void {\n try {\n const timestamp = new Date().toISOString()\n const prefix = isError ? 'ERR' : 'OUT'\n const logLine = `[${timestamp}] [${prefix}] ${line}\\n`\n fs.appendFileSync(logPath, logLine)\n } catch {\n // Ignore log write errors\n }\n}\n\n/**\n * EP929: Check if dev server is healthy (actually responding to requests)\n * This is different from isPortInUse() which only checks if the port is bound.\n * A hung process can have the port bound but not respond to HTTP requests.\n */\nexport async function isDevServerHealthy(port: number, timeoutMs: number = 5000): Promise<boolean> {\n return new Promise((resolve) => {\n const req = http.request(\n {\n hostname: 'localhost',\n port,\n path: '/',\n method: 'HEAD',\n timeout: timeoutMs\n },\n (res) => {\n // Any response (even 404/500) means server is responding\n resolve(true)\n }\n )\n\n req.on('error', () => {\n resolve(false)\n })\n\n req.on('timeout', () => {\n req.destroy()\n resolve(false)\n })\n\n req.end()\n })\n}\n\n/**\n * EP929: Kill any process using the specified port\n * Used to clean up hung dev servers that aren't responding.\n */\nexport async function killProcessOnPort(port: number): Promise<boolean> {\n try {\n // Find PID using the port (macOS/Linux compatible)\n const result = execSync(`lsof -ti:${port} 2>/dev/null || true`, { encoding: 'utf8' }).trim()\n\n if (!result) {\n console.log(`[DevServer] EP929: No process found on port ${port}`)\n return true\n }\n\n const pids = result.split('\\n').filter(Boolean)\n console.log(`[DevServer] EP929: Found ${pids.length} process(es) on port ${port}: ${pids.join(', ')}`)\n\n for (const pid of pids) {\n try {\n // First try SIGTERM for graceful shutdown\n execSync(`kill -15 ${pid} 2>/dev/null || true`, { encoding: 'utf8' })\n console.log(`[DevServer] EP929: Sent SIGTERM to PID ${pid}`)\n } catch {\n // Ignore errors, process may have already exited\n }\n }\n\n // Wait briefly for graceful shutdown\n await new Promise(resolve => setTimeout(resolve, 1000))\n\n // Check if any processes still running and force kill\n for (const pid of pids) {\n try {\n // Check if process still exists\n execSync(`kill -0 ${pid} 2>/dev/null`, { encoding: 'utf8' })\n // Still running, force kill\n execSync(`kill -9 ${pid} 2>/dev/null || true`, { encoding: 'utf8' })\n console.log(`[DevServer] EP929: Force killed PID ${pid}`)\n } catch {\n // Process already exited, good\n }\n }\n\n // Wait for port to be released\n await new Promise(resolve => setTimeout(resolve, 500))\n\n const stillInUse = await isPortInUse(port)\n if (stillInUse) {\n console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`)\n return false\n }\n\n console.log(`[DevServer] EP929: Successfully freed port ${port}`)\n return true\n } catch (error) {\n console.error(`[DevServer] EP929: Error killing process on port ${port}:`, error)\n return false\n }\n}\n\n/**\n * Wait for a port to become available\n */\nasync function waitForPort(port: number, timeoutMs: number = 30000): Promise<boolean> {\n const startTime = Date.now()\n const checkInterval = 500\n\n while (Date.now() - startTime < timeoutMs) {\n if (await isPortInUse(port)) {\n return true\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval))\n }\n\n return false\n}\n\n/**\n * EP932: Calculate restart delay with exponential backoff\n */\nfunction calculateRestartDelay(restartCount: number): number {\n const delay = INITIAL_RESTART_DELAY_MS * Math.pow(2, restartCount)\n return Math.min(delay, MAX_RESTART_DELAY_MS)\n}\n\n/**\n * EP932: Internal function to spawn the dev server process\n * EP959-m2: Now accepts custom command from project settings\n * EP998: Now accepts injected env vars from database\n */\nfunction spawnDevServerProcess(\n projectPath: string,\n port: number,\n moduleUid: string,\n logPath: string,\n customCommand?: string,\n injectedEnvVars?: Record<string, string>\n): ChildProcess {\n // Rotate log if needed before starting\n rotateLogIfNeeded(logPath)\n\n // EP932: Add memory limit via NODE_OPTIONS\n const nodeOptions = process.env.NODE_OPTIONS || ''\n const memoryFlag = `--max-old-space-size=${NODE_MEMORY_LIMIT_MB}`\n const enhancedNodeOptions = nodeOptions.includes('max-old-space-size')\n ? nodeOptions\n : `${nodeOptions} ${memoryFlag}`.trim()\n\n // EP959-m2: Use custom command if provided, otherwise default to 'npm run dev'\n const command = customCommand || 'npm run dev'\n const [cmd, ...args] = command.split(' ')\n console.log(`[DevServer] EP959: Starting with command: ${command}`)\n\n // EP998: Merge process.env with injected env vars (injected vars take precedence)\n const mergedEnv = {\n ...process.env,\n ...injectedEnvVars,\n PORT: String(port),\n NODE_OPTIONS: enhancedNodeOptions\n }\n\n const injectedCount = injectedEnvVars ? Object.keys(injectedEnvVars).length : 0\n if (injectedCount > 0) {\n console.log(`[DevServer] EP998: Injecting ${injectedCount} env vars from database`)\n }\n\n const devProcess = spawn(cmd, args, {\n cwd: projectPath,\n env: mergedEnv,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n shell: true // EP959: Use shell to handle complex commands\n })\n\n // Log to file and console\n devProcess.stdout?.on('data', (data: Buffer) => {\n const line = data.toString().trim()\n if (line) {\n console.log(`[DevServer:${moduleUid}] ${line}`)\n writeToLog(logPath, line, false)\n }\n })\n\n devProcess.stderr?.on('data', (data: Buffer) => {\n const line = data.toString().trim()\n if (line) {\n console.error(`[DevServer:${moduleUid}] ${line}`)\n writeToLog(logPath, line, true)\n }\n })\n\n return devProcess\n}\n\n/**\n * EP932: Handle process exit with auto-restart logic\n */\nasync function handleProcessExit(\n moduleUid: string,\n code: number | null,\n signal: NodeJS.Signals | null\n): Promise<void> {\n const serverInfo = activeServers.get(moduleUid)\n if (!serverInfo) {\n return\n }\n\n const exitReason = signal ? `signal ${signal}` : `code ${code}`\n console.log(`[DevServer] EP932: Process for ${moduleUid} exited with ${exitReason}`)\n writeToLog(serverInfo.logFile || '', `Process exited with ${exitReason}`, true)\n\n // Check if auto-restart is enabled and we haven't exceeded max attempts\n if (!serverInfo.autoRestartEnabled) {\n console.log(`[DevServer] EP932: Auto-restart disabled for ${moduleUid}`)\n activeServers.delete(moduleUid)\n return\n }\n\n if (serverInfo.restartCount >= MAX_RESTART_ATTEMPTS) {\n console.error(`[DevServer] EP932: Max restart attempts (${MAX_RESTART_ATTEMPTS}) reached for ${moduleUid}`)\n writeToLog(serverInfo.logFile || '', `Max restart attempts reached, giving up`, true)\n activeServers.delete(moduleUid)\n return\n }\n\n // Calculate delay with exponential backoff\n const delay = calculateRestartDelay(serverInfo.restartCount)\n console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`)\n writeToLog(serverInfo.logFile || '', `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false)\n\n await new Promise(resolve => setTimeout(resolve, delay))\n\n // Double-check we still want to restart (module might have been stopped manually)\n if (!activeServers.has(moduleUid)) {\n console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`)\n return\n }\n\n // Spawn new process\n // EP959-m2: Pass custom command for restart\n // EP998: Pass injected env vars for restart\n const logPath = serverInfo.logFile || getLogFilePath(moduleUid)\n const newProcess = spawnDevServerProcess(serverInfo.projectPath, serverInfo.port, moduleUid, logPath, serverInfo.customCommand, serverInfo.injectedEnvVars)\n\n // Update server info\n const updatedInfo: ServerInfo = {\n ...serverInfo,\n process: newProcess,\n restartCount: serverInfo.restartCount + 1,\n lastRestartAt: new Date()\n }\n activeServers.set(moduleUid, updatedInfo)\n\n // Set up exit handler for new process\n newProcess.on('exit', (newCode, newSignal) => {\n handleProcessExit(moduleUid, newCode, newSignal)\n })\n\n newProcess.on('error', (error) => {\n console.error(`[DevServer] EP932: Process error for ${moduleUid}:`, error)\n writeToLog(logPath, `Process error: ${error.message}`, true)\n })\n\n // Wait for server to be ready\n const serverReady = await waitForPort(serverInfo.port, 60000)\n if (serverReady) {\n console.log(`[DevServer] EP932: Server ${moduleUid} restarted successfully`)\n writeToLog(logPath, `Server restarted successfully`, false)\n // Reset restart count on successful start\n updatedInfo.restartCount = 0\n } else {\n console.error(`[DevServer] EP932: Server ${moduleUid} failed to restart`)\n writeToLog(logPath, `Server failed to restart within timeout`, true)\n }\n}\n\n/**\n * Start the dev server for a project\n * EP932: Now with auto-restart, memory limits, and logging\n * EP959-m2: Added customCommand option for worktree_dev_server_script\n * EP998: Added runtime env var injection from database\n */\nexport async function startDevServer(\n projectPath: string,\n port: number = 3000,\n moduleUid: string = 'default',\n options: { autoRestart?: boolean; customCommand?: string } = {}\n): Promise<{ success: boolean; error?: string; alreadyRunning?: boolean }> {\n const autoRestart = options.autoRestart ?? true // Default to auto-restart enabled\n const customCommand = options.customCommand\n\n // Check if server is already running\n if (await isPortInUse(port)) {\n console.log(`[DevServer] Server already running on port ${port}`)\n return { success: true, alreadyRunning: true }\n }\n\n // Check if we already have a process for this module\n if (activeServers.has(moduleUid)) {\n const existing = activeServers.get(moduleUid)\n if (existing && !existing.process.killed) {\n console.log(`[DevServer] Process already exists for ${moduleUid}`)\n return { success: true, alreadyRunning: true }\n }\n }\n\n console.log(`[DevServer] EP932: Starting dev server for ${moduleUid} on port ${port} (auto-restart: ${autoRestart})...`)\n\n // EP998: Fetch env vars from database for runtime injection\n let injectedEnvVars: Record<string, string> = {}\n try {\n const config = await loadConfig()\n if (config?.access_token && config?.project_id) {\n const apiUrl = config.api_url || 'https://episoda.dev'\n const result = await fetchEnvVarsWithCache(apiUrl, config.access_token, {\n projectId: config.project_id,\n cacheTtl: 300 // 5 minute cache for daemon\n })\n injectedEnvVars = result.envVars\n console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? 'cache' : 'server'})`)\n } else {\n console.log(`[DevServer] EP998: No auth config, skipping env var injection`)\n }\n } catch (error) {\n console.warn(`[DevServer] EP998: Failed to fetch env vars:`, error instanceof Error ? error.message : error)\n // Continue without injected env vars - better to start than fail\n }\n\n try {\n const logPath = getLogFilePath(moduleUid)\n // EP959-m2: Pass custom command to spawnDevServerProcess\n // EP998: Pass injected env vars\n const devProcess = spawnDevServerProcess(projectPath, port, moduleUid, logPath, customCommand, injectedEnvVars)\n\n // Create server info\n const serverInfo: ServerInfo = {\n process: devProcess,\n moduleUid,\n projectPath,\n port,\n startedAt: new Date(),\n restartCount: 0,\n lastRestartAt: null,\n autoRestartEnabled: autoRestart,\n logFile: logPath,\n customCommand, // EP959-m2: Store for restarts\n injectedEnvVars // EP998: Store for restarts\n }\n activeServers.set(moduleUid, serverInfo)\n\n writeToLog(logPath, `Starting dev server on port ${port}`, false)\n\n // Set up exit handler for auto-restart\n devProcess.on('exit', (code, signal) => {\n handleProcessExit(moduleUid, code, signal)\n })\n\n devProcess.on('error', (error) => {\n console.error(`[DevServer] Process error for ${moduleUid}:`, error)\n writeToLog(logPath, `Process error: ${error.message}`, true)\n })\n\n // Wait for the server to start\n console.log(`[DevServer] Waiting for server to start on port ${port}...`)\n const serverReady = await waitForPort(port, 60000) // 60 second timeout for npm install + start\n\n if (!serverReady) {\n devProcess.kill()\n activeServers.delete(moduleUid)\n writeToLog(logPath, `Failed to start within timeout`, true)\n return { success: false, error: 'Dev server failed to start within timeout' }\n }\n\n console.log(`[DevServer] Server started successfully on port ${port}`)\n writeToLog(logPath, `Server started successfully`, false)\n return { success: true }\n\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n console.error(`[DevServer] Failed to start:`, error)\n return { success: false, error: errorMsg }\n }\n}\n\n/**\n * Stop the dev server for a module\n * EP932: Also disables auto-restart to prevent immediate restart\n */\nexport async function stopDevServer(moduleUid: string): Promise<void> {\n const serverInfo = activeServers.get(moduleUid)\n if (!serverInfo) {\n return\n }\n\n // Disable auto-restart before stopping\n serverInfo.autoRestartEnabled = false\n\n if (!serverInfo.process.killed) {\n console.log(`[DevServer] Stopping server for ${moduleUid}`)\n if (serverInfo.logFile) {\n writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false)\n }\n serverInfo.process.kill('SIGTERM')\n\n // Wait a moment for graceful shutdown\n await new Promise(resolve => setTimeout(resolve, 2000))\n\n if (!serverInfo.process.killed) {\n serverInfo.process.kill('SIGKILL')\n }\n }\n\n activeServers.delete(moduleUid)\n}\n\n/**\n * EP932: Restart the dev server for a module\n * Stops the current server and starts a new one with fresh restart count\n */\nexport async function restartDevServer(\n moduleUid: string\n): Promise<{ success: boolean; error?: string }> {\n const serverInfo = activeServers.get(moduleUid)\n if (!serverInfo) {\n return { success: false, error: `No dev server found for ${moduleUid}` }\n }\n\n const { projectPath, port, autoRestartEnabled, logFile } = serverInfo\n\n console.log(`[DevServer] EP932: Restarting server for ${moduleUid}...`)\n if (logFile) {\n writeToLog(logFile, `Manual restart requested`, false)\n }\n\n // Stop current server (disables auto-restart temporarily)\n await stopDevServer(moduleUid)\n\n // Wait for port to be released\n await new Promise(resolve => setTimeout(resolve, 1000))\n\n // Kill any lingering process on the port\n if (await isPortInUse(port)) {\n await killProcessOnPort(port)\n }\n\n // Start fresh\n return startDevServer(projectPath, port, moduleUid, { autoRestart: autoRestartEnabled })\n}\n\n/**\n * Stop all dev servers\n */\nexport async function stopAllDevServers(): Promise<void> {\n const moduleUids = Array.from(activeServers.keys())\n await Promise.all(moduleUids.map(uid => stopDevServer(uid)))\n}\n\n/**\n * Check if a dev server is running for a module\n */\nexport function isDevServerRunning(moduleUid: string): boolean {\n const serverInfo = activeServers.get(moduleUid)\n return !!serverInfo && !serverInfo.process.killed\n}\n\n/**\n * EP932: Get status of all running dev servers\n */\nexport function getDevServerStatus(): Array<{\n moduleUid: string\n port: number\n pid: number | undefined\n startedAt: Date\n uptime: number // seconds\n restartCount: number\n lastRestartAt: Date | null\n autoRestartEnabled: boolean\n logFile: string | null\n}> {\n const now = Date.now()\n return Array.from(activeServers.values()).map(info => ({\n moduleUid: info.moduleUid,\n port: info.port,\n pid: info.process.pid,\n startedAt: info.startedAt,\n uptime: Math.floor((now - info.startedAt.getTime()) / 1000),\n restartCount: info.restartCount,\n lastRestartAt: info.lastRestartAt,\n autoRestartEnabled: info.autoRestartEnabled,\n logFile: info.logFile\n }))\n}\n\n/**\n * EP932: Get status of a specific dev server\n */\nexport function getDevServerInfo(moduleUid: string): ServerInfo | undefined {\n return activeServers.get(moduleUid)\n}\n\n/**\n * Ensure dev server is running, starting if needed\n * EP932: Now passes autoRestart option\n * EP959-m2: Added customCommand for worktree_dev_server_script support\n */\nexport async function ensureDevServer(\n projectPath: string,\n port: number = 3000,\n moduleUid: string = 'default',\n customCommand?: string\n): Promise<{ success: boolean; error?: string }> {\n // First check if something is already on the port\n if (await isPortInUse(port)) {\n return { success: true }\n }\n\n // Start the dev server with auto-restart enabled\n // EP959-m2: Pass custom command if provided\n return startDevServer(projectPath, port, moduleUid, { autoRestart: true, customCommand })\n}\n","/**\n * EP998: Environment Variable Caching\n *\n * Provides caching for fetched environment variables to reduce API calls\n * and enable offline fallback.\n *\n * Cache location: ~/.episoda/cache/env-vars-{projectId}.json\n * Default TTL: 60 seconds\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport { fetchEnvVars } from './env-setup'\n\nexport interface EnvCacheOptions {\n noCache?: boolean // Force fresh fetch, ignore cache\n cacheTtl?: number // Cache TTL in seconds (default: 60)\n offline?: boolean // Use cache only, don't fetch\n projectId?: string // Project ID for cache file naming\n}\n\nexport interface EnvCacheResult {\n envVars: Record<string, string>\n fromCache: boolean\n cacheAge?: number // Age of cache in milliseconds (if from cache)\n}\n\ninterface CacheData {\n vars: Record<string, string>\n fetchedAt: number // Unix timestamp in milliseconds\n projectId: string\n}\n\nconst DEFAULT_CACHE_TTL = 60 // seconds\nconst CACHE_DIR = path.join(os.homedir(), '.episoda', 'cache')\n\n/**\n * Get the cache file path for a project\n */\nfunction getCacheFilePath(projectId: string): string {\n return path.join(CACHE_DIR, `env-vars-${projectId}.json`)\n}\n\n/**\n * Ensure cache directory exists\n */\nfunction ensureCacheDir(): void {\n if (!fs.existsSync(CACHE_DIR)) {\n fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 })\n }\n}\n\n/**\n * Read cached environment variables\n */\nfunction readCache(projectId: string): CacheData | null {\n try {\n const cacheFile = getCacheFilePath(projectId)\n if (!fs.existsSync(cacheFile)) {\n return null\n }\n\n const content = fs.readFileSync(cacheFile, 'utf-8')\n const data = JSON.parse(content) as CacheData\n\n // Validate cache data structure\n if (!data.vars || typeof data.vars !== 'object' || !data.fetchedAt) {\n return null\n }\n\n return data\n } catch {\n return null\n }\n}\n\n/**\n * Write environment variables to cache\n */\nfunction writeCache(projectId: string, vars: Record<string, string>): void {\n try {\n ensureCacheDir()\n const cacheFile = getCacheFilePath(projectId)\n const data: CacheData = {\n vars,\n fetchedAt: Date.now(),\n projectId\n }\n fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 0o600 })\n } catch (error) {\n // Silently fail on cache write errors\n console.warn('[env-cache] Failed to write cache:', error instanceof Error ? error.message : error)\n }\n}\n\n/**\n * Check if cache is still valid (within TTL)\n */\nfunction isCacheValid(cache: CacheData, ttlSeconds: number): boolean {\n const ageMs = Date.now() - cache.fetchedAt\n return ageMs < ttlSeconds * 1000\n}\n\n/**\n * Fetch environment variables with caching support\n *\n * @param apiUrl - Base API URL\n * @param accessToken - OAuth access token\n * @param options - Cache options\n * @returns Environment variables and cache status\n */\nexport async function fetchEnvVarsWithCache(\n apiUrl: string,\n accessToken: string,\n options: EnvCacheOptions = {}\n): Promise<EnvCacheResult> {\n const {\n noCache = false,\n cacheTtl = DEFAULT_CACHE_TTL,\n offline = false,\n projectId = 'default'\n } = options\n\n // Offline mode: only use cache\n if (offline) {\n const cache = readCache(projectId)\n if (cache && Object.keys(cache.vars).length > 0) {\n return {\n envVars: cache.vars,\n fromCache: true,\n cacheAge: Date.now() - cache.fetchedAt\n }\n }\n throw new Error(\n 'Offline mode requires cached env vars, but no cache found.\\n' +\n 'Run without --offline first to populate the cache.'\n )\n }\n\n // Check cache (unless noCache is set)\n if (!noCache) {\n const cache = readCache(projectId)\n if (cache && isCacheValid(cache, cacheTtl)) {\n return {\n envVars: cache.vars,\n fromCache: true,\n cacheAge: Date.now() - cache.fetchedAt\n }\n }\n }\n\n // Fetch from server\n try {\n const envVars = await fetchEnvVars(apiUrl, accessToken)\n\n // Update cache (even if fetch returned empty, to record the attempt)\n if (Object.keys(envVars).length > 0) {\n writeCache(projectId, envVars)\n }\n\n return {\n envVars,\n fromCache: false\n }\n } catch (error) {\n // On fetch error, try to use stale cache as fallback\n const cache = readCache(projectId)\n if (cache && Object.keys(cache.vars).length > 0) {\n const cacheAge = Date.now() - cache.fetchedAt\n console.warn(\n `[env-cache] Failed to fetch env vars, using stale cache (${Math.round(cacheAge / 1000)}s old)`\n )\n return {\n envVars: cache.vars,\n fromCache: true,\n cacheAge\n }\n }\n\n // No cache available, rethrow error\n throw new Error(\n `Failed to fetch environment variables: ${error instanceof Error ? error.message : error}\\n` +\n 'No cached values available as fallback.'\n )\n }\n}\n\n/**\n * Clear the env var cache for a project\n */\nexport function clearEnvCache(projectId: string): boolean {\n try {\n const cacheFile = getCacheFilePath(projectId)\n if (fs.existsSync(cacheFile)) {\n fs.unlinkSync(cacheFile)\n return true\n }\n return false\n } catch {\n return false\n }\n}\n\n/**\n * Get cache info for a project\n */\nexport function getCacheInfo(projectId: string): { exists: boolean; age?: number; varCount?: number } {\n const cache = readCache(projectId)\n if (!cache) {\n return { exists: false }\n }\n return {\n exists: true,\n age: Date.now() - cache.fetchedAt,\n varCount: Object.keys(cache.vars).length\n }\n}\n","/**\n * EP988: Environment Variable Setup Utility\n *\n * Shared utility for fetching and writing environment variables to worktrees.\n * Used by both CLI checkout and daemon checkout_module handler.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\n/**\n * Fetch environment variables from the server\n *\n * @param apiUrl - Base API URL (e.g., https://episoda.dev)\n * @param accessToken - OAuth access token\n * @returns Key-value map of env vars, or empty object on error\n */\nexport async function fetchEnvVars(\n apiUrl: string,\n accessToken: string\n): Promise<Record<string, string>> {\n try {\n const url = `${apiUrl}/api/cli/env-vars`\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${accessToken}`,\n 'Content-Type': 'application/json'\n }\n })\n\n if (!response.ok) {\n console.warn(`[env-setup] Failed to fetch env vars: ${response.status}`)\n return {}\n }\n\n const data = await response.json() as { env_vars?: Record<string, string> }\n const envVars = data.env_vars || {}\n\n console.log(`[env-setup] Fetched ${Object.keys(envVars).length} env vars from server`)\n return envVars\n } catch (error) {\n console.warn('[env-setup] Error fetching env vars:', error instanceof Error ? error.message : error)\n return {}\n }\n}\n\n/**\n * Write environment variables to .env file in a directory\n *\n * @param targetPath - Directory path to write .env file to\n * @param envVars - Key-value map of environment variables\n */\nexport function writeEnvFile(\n targetPath: string,\n envVars: Record<string, string>\n): void {\n if (Object.keys(envVars).length === 0) {\n return\n }\n\n const envContent = Object.entries(envVars)\n .map(([key, value]) => {\n // Quote values containing spaces, quotes, newlines, or shell special chars\n if (/[\\s'\"#$`\\\\]/.test(value) || value.includes('\\n')) {\n // Escape backslashes, quotes, and newlines for .env compatibility\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n return `${key}=\"${escaped}\"`\n }\n return `${key}=${value}`\n })\n .join('\\n') + '\\n'\n\n const envPath = path.join(targetPath, '.env')\n fs.writeFileSync(envPath, envContent, { mode: 0o600 })\n console.log(`[env-setup] Wrote ${Object.keys(envVars).length} env vars to ${envPath}`)\n}\n\n/**\n * Complete env setup for a worktree: fetch from server and write to .env\n *\n * @param worktreePath - Path to the worktree directory\n * @param apiUrl - Base API URL\n * @param accessToken - OAuth access token\n * @returns true if env vars were written, false otherwise\n */\nexport async function setupWorktreeEnv(\n worktreePath: string,\n apiUrl: string,\n accessToken: string\n): Promise<boolean> {\n const envVars = await fetchEnvVars(apiUrl, accessToken)\n\n if (Object.keys(envVars).length === 0) {\n return false\n }\n\n writeEnvFile(worktreePath, envVars)\n return true\n}\n","/**\n * Port Detection Utility\n *\n * EP818: Auto-detect dev server port from project configuration.\n * Checks .env for PORT variable, then package.json dev script for --port flag.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nconst DEFAULT_PORT = 3000\n\n/**\n * Detect the dev server port for a project\n */\nexport function detectDevPort(projectPath: string): number {\n // 1. Check .env file for PORT variable\n const envPort = getPortFromEnv(projectPath)\n if (envPort) {\n console.log(`[PortDetect] Found PORT=${envPort} in .env`)\n return envPort\n }\n\n // 2. Check package.json dev script for --port flag\n const scriptPort = getPortFromPackageJson(projectPath)\n if (scriptPort) {\n console.log(`[PortDetect] Found port ${scriptPort} in package.json dev script`)\n return scriptPort\n }\n\n // 3. Default to 3000\n console.log(`[PortDetect] Using default port ${DEFAULT_PORT}`)\n return DEFAULT_PORT\n}\n\n/**\n * Read PORT from .env file\n */\nfunction getPortFromEnv(projectPath: string): number | null {\n const envPaths = [\n path.join(projectPath, '.env'),\n path.join(projectPath, '.env.local'),\n path.join(projectPath, '.env.development'),\n path.join(projectPath, '.env.development.local')\n ]\n\n for (const envPath of envPaths) {\n try {\n if (!fs.existsSync(envPath)) continue\n\n const content = fs.readFileSync(envPath, 'utf-8')\n const lines = content.split('\\n')\n\n for (const line of lines) {\n // Match PORT=3000 or PORT = 3000 (with optional quotes)\n const match = line.match(/^\\s*PORT\\s*=\\s*[\"']?(\\d+)[\"']?\\s*(?:#.*)?$/)\n if (match) {\n const port = parseInt(match[1], 10)\n if (port > 0 && port < 65536) {\n return port\n }\n }\n }\n } catch {\n // Ignore read errors, try next file\n }\n }\n\n return null\n}\n\n/**\n * Read port from package.json dev script\n */\nfunction getPortFromPackageJson(projectPath: string): number | null {\n const packageJsonPath = path.join(projectPath, 'package.json')\n\n try {\n if (!fs.existsSync(packageJsonPath)) return null\n\n const content = fs.readFileSync(packageJsonPath, 'utf-8')\n const pkg = JSON.parse(content)\n\n // Check scripts.dev for port flags\n const devScript = pkg.scripts?.dev\n if (!devScript) return null\n\n // Match various port flag formats:\n // --port 3001, --port=3001, -p 3001, -p=3001\n const portMatch = devScript.match(/(?:--port[=\\s]|-p[=\\s])(\\d+)/)\n if (portMatch) {\n const port = parseInt(portMatch[1], 10)\n if (port > 0 && port < 65536) {\n return port\n }\n }\n\n // Also check for PORT= in the script itself (e.g., PORT=3001 npm run dev)\n const envMatch = devScript.match(/PORT[=\\s](\\d+)/)\n if (envMatch) {\n const port = parseInt(envMatch[1], 10)\n if (port > 0 && port < 65536) {\n return port\n }\n }\n } catch {\n // Ignore parse errors\n }\n\n return null\n}\n","/**\n * Worktree Manager\n *\n * EP944: Manages git worktrees for multi-module development.\n * Each module gets its own worktree, enabling parallel development.\n *\n * Directory Structure:\n * ~/episoda/{workspace}/{project}/\n * ├── .bare/ # Bare git clone\n * ├── .episoda/\n * │ └── config.json # Project worktree config\n * ├── EP100/ # Module worktree\n * └── EP101/ # Another module worktree\n *\n * EP995 EVALUATION: Local worktrees[] array vs Server-side state\n * ---------------------------------------------------------------\n * With EP995, the server now stores:\n * - module.worktree_path - filesystem path on checkout_machine_id\n * - module.worktree_status - pending/setup/ready/error\n * - module.worktree_error - error message if setup failed\n *\n * The daemon does reconciliation on connect (reconcileWorktrees), making\n * the local worktrees[] array mostly redundant.\n *\n * RECOMMENDATION: Keep local worktrees[] for now because:\n * 1. It provides instant local lookup without network call\n * 2. Acts as fallback if server unreachable during worktree operations\n * 3. Stores setupStatus for local progress tracking during setup\n * 4. No breaking change - gradual migration is safer\n *\n * FUTURE: Consider removing in a follow-up module when:\n * - EP978 progress infrastructure is complete (UI can show server-side status)\n * - Offline support requirements are clarified\n * - Migration path for existing installations is defined\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { GitExecutor, ExecutionResult } from '@episoda/core'\n\n/**\n * EP957: Validate module UID to prevent path traversal attacks\n *\n * UIDs are server-generated in EPxxx format, but this provides defense-in-depth\n * in case a malformed UID ever reaches the worktree system.\n *\n * @param moduleUid - The module UID to validate\n * @returns true if valid, false if potentially dangerous\n */\nexport function validateModuleUid(moduleUid: string): boolean {\n // Reject empty, null, or whitespace-only\n if (!moduleUid || typeof moduleUid !== 'string' || !moduleUid.trim()) {\n return false\n }\n\n // Reject path traversal attempts\n if (moduleUid.includes('/') || moduleUid.includes('\\\\') || moduleUid.includes('..')) {\n return false\n }\n\n // Reject null bytes (could truncate paths in some systems)\n if (moduleUid.includes('\\0')) {\n return false\n }\n\n // Reject if it would resolve to a hidden directory\n if (moduleUid.startsWith('.')) {\n return false\n }\n\n return true\n}\n\n/**\n * Information about an active worktree\n */\nexport interface WorktreeInfo {\n moduleUid: string // Module UID (e.g., EP100)\n branchName: string // Git branch name\n worktreePath: string // Absolute path to worktree\n createdAt: string // ISO timestamp\n lastAccessed: string // ISO timestamp of last access\n // EP959-11: Setup status tracking\n setupStatus?: 'pending' | 'running' | 'ready' | 'error'\n setupStartedAt?: string // ISO timestamp\n setupCompletedAt?: string // ISO timestamp\n setupError?: string // Error message if setup failed\n}\n\n/**\n * Worktree project configuration\n * Stored in {projectRoot}/.episoda/config.json\n * EP971: All projects use worktree architecture\n */\nexport interface WorktreeProjectConfig {\n projectId: string\n workspaceSlug: string\n projectSlug: string\n bareRepoPath: string // Path to .bare/ directory\n createdAt: string\n worktrees: WorktreeInfo[]\n}\n\n/**\n * Result of a worktree operation\n */\nexport interface WorktreeOperationResult {\n success: boolean\n error?: string\n worktreePath?: string\n worktreeInfo?: WorktreeInfo\n}\n\n/**\n * Manages worktrees for a single project\n */\nexport class WorktreeManager {\n private projectRoot: string\n private bareRepoPath: string\n private configPath: string\n private gitExecutor: GitExecutor\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot\n this.bareRepoPath = path.join(projectRoot, '.bare')\n this.configPath = path.join(projectRoot, '.episoda', 'config.json')\n this.gitExecutor = new GitExecutor()\n }\n\n /**\n * Initialize worktree manager from existing project root\n * EP971: All projects use worktree architecture\n * @returns true if valid project, false otherwise\n */\n async initialize(): Promise<boolean> {\n // Check if .bare directory exists\n if (!fs.existsSync(this.bareRepoPath)) {\n return false\n }\n\n // Check if config exists\n if (!fs.existsSync(this.configPath)) {\n return false\n }\n\n try {\n const config = this.readConfig()\n return config !== null\n } catch {\n return false\n }\n }\n\n /**\n * Create a new worktree project from scratch\n */\n static async createProject(\n projectRoot: string,\n repoUrl: string,\n projectId: string,\n workspaceSlug: string,\n projectSlug: string\n ): Promise<WorktreeManager> {\n const manager = new WorktreeManager(projectRoot)\n\n // Create project directory structure\n const episodaDir = path.join(projectRoot, '.episoda')\n fs.mkdirSync(episodaDir, { recursive: true })\n\n // Clone as bare repository\n const cloneResult = await manager.gitExecutor.execute({\n action: 'clone_bare',\n url: repoUrl,\n path: manager.bareRepoPath\n })\n\n if (!cloneResult.success) {\n throw new Error(`Failed to clone repository: ${cloneResult.output}`)\n }\n\n // Create initial config (EP971: all projects use worktree architecture)\n const config: WorktreeProjectConfig = {\n projectId,\n workspaceSlug,\n projectSlug,\n bareRepoPath: manager.bareRepoPath,\n createdAt: new Date().toISOString(),\n worktrees: []\n }\n\n manager.writeConfig(config)\n\n return manager\n }\n\n /**\n * Create a worktree for a module\n * The entire operation is locked to prevent race conditions\n */\n async createWorktree(\n moduleUid: string,\n branchName: string,\n createBranch: boolean = false\n ): Promise<WorktreeOperationResult> {\n // EP957: Validate module UID to prevent path traversal\n if (!validateModuleUid(moduleUid)) {\n return {\n success: false,\n error: `Invalid module UID: \"${moduleUid}\" - contains disallowed characters`\n }\n }\n\n const worktreePath = path.join(this.projectRoot, moduleUid)\n\n // Acquire lock for the entire check-and-create operation\n const lockAcquired = await this.acquireLock()\n if (!lockAcquired) {\n return {\n success: false,\n error: 'Could not acquire lock for worktree creation'\n }\n }\n\n try {\n // Check if worktree already exists (inside lock to prevent race)\n const existing = this.getWorktreeByModuleUid(moduleUid)\n if (existing) {\n return {\n success: true,\n worktreePath: existing.worktreePath,\n worktreeInfo: existing\n }\n }\n\n // EP945-11: Fetch latest refs from origin before creating worktree\n // This ensures new worktrees are based on current main, not stale local refs\n const fetchResult = await this.gitExecutor.execute({\n action: 'fetch',\n remote: 'origin'\n }, { cwd: this.bareRepoPath })\n\n // EP996: Fail if fetch fails when creating a new branch\n // Without a fresh fetch, the worktree would be based on stale refs\n if (!fetchResult.success && createBranch) {\n console.error('[worktree-manager] Failed to fetch from origin:', fetchResult.output)\n return {\n success: false,\n error: 'Failed to fetch latest refs from origin. Cannot create worktree with stale refs.'\n }\n }\n\n // Create worktree via git\n // EP996: Use origin/main as start point when creating a new branch\n // This ensures the branch is based on the latest remote state, not stale local HEAD\n const result = await this.gitExecutor.execute({\n action: 'worktree_add',\n path: worktreePath,\n branch: branchName,\n create: createBranch,\n startPoint: createBranch ? 'origin/main' : undefined\n }, { cwd: this.bareRepoPath })\n\n if (!result.success) {\n return {\n success: false,\n error: result.output || 'Failed to create worktree'\n }\n }\n\n // Record worktree in config\n const worktreeInfo: WorktreeInfo = {\n moduleUid,\n branchName,\n worktreePath,\n createdAt: new Date().toISOString(),\n lastAccessed: new Date().toISOString()\n }\n\n const config = this.readConfig()\n if (config) {\n config.worktrees.push(worktreeInfo)\n this.writeConfig(config)\n }\n\n return {\n success: true,\n worktreePath,\n worktreeInfo\n }\n } finally {\n this.releaseLock()\n }\n }\n\n /**\n * Remove a worktree for a module\n * P1-2: Wrapped entire operation in lock to prevent race with createWorktree\n */\n async removeWorktree(\n moduleUid: string,\n force: boolean = false\n ): Promise<WorktreeOperationResult> {\n // EP957: Validate module UID to prevent path traversal\n if (!validateModuleUid(moduleUid)) {\n return {\n success: false,\n error: `Invalid module UID: \"${moduleUid}\" - contains disallowed characters`\n }\n }\n\n // Acquire lock for the entire check-and-remove operation\n const lockAcquired = await this.acquireLock()\n if (!lockAcquired) {\n return {\n success: false,\n error: 'Could not acquire lock for worktree removal'\n }\n }\n\n try {\n const existing = this.getWorktreeByModuleUid(moduleUid)\n if (!existing) {\n return {\n success: false,\n error: `No worktree found for module ${moduleUid}`\n }\n }\n\n // Remove worktree via git\n const result = await this.gitExecutor.execute({\n action: 'worktree_remove',\n path: existing.worktreePath,\n force\n }, { cwd: this.bareRepoPath })\n\n if (!result.success) {\n return {\n success: false,\n error: result.output || 'Failed to remove worktree'\n }\n }\n\n // Remove from config (already inside lock, so just read-modify-write)\n const config = this.readConfig()\n if (config) {\n config.worktrees = config.worktrees.filter(w => w.moduleUid !== moduleUid)\n this.writeConfig(config)\n }\n\n return {\n success: true,\n worktreePath: existing.worktreePath\n }\n } finally {\n this.releaseLock()\n }\n }\n\n /**\n * Get worktree path for a module\n */\n getWorktreePath(moduleUid: string): string | null {\n // EP957: Validate module UID for defense-in-depth\n if (!validateModuleUid(moduleUid)) {\n return null\n }\n const worktree = this.getWorktreeByModuleUid(moduleUid)\n return worktree?.worktreePath || null\n }\n\n /**\n * Get worktree info by module UID\n */\n getWorktreeByModuleUid(moduleUid: string): WorktreeInfo | null {\n const config = this.readConfig()\n return config?.worktrees.find(w => w.moduleUid === moduleUid) || null\n }\n\n /**\n * Get worktree info by branch name\n */\n getWorktreeByBranch(branchName: string): WorktreeInfo | null {\n const config = this.readConfig()\n return config?.worktrees.find(w => w.branchName === branchName) || null\n }\n\n /**\n * List all active worktrees\n */\n listWorktrees(): WorktreeInfo[] {\n const config = this.readConfig()\n return config?.worktrees || []\n }\n\n /**\n * EP957: Audit worktrees against known active module UIDs\n *\n * Compares local worktrees against a list of module UIDs that should be active.\n * Used by daemon startup to detect orphaned worktrees from crashed sessions.\n *\n * @param activeModuleUids - UIDs of modules currently in doing/review state\n * @returns Object with orphaned and valid worktree lists\n */\n auditWorktrees(activeModuleUids: string[]): {\n orphaned: WorktreeInfo[]\n valid: WorktreeInfo[]\n } {\n const allWorktrees = this.listWorktrees()\n const activeSet = new Set(activeModuleUids)\n\n const orphaned = allWorktrees.filter(w => !activeSet.has(w.moduleUid))\n const valid = allWorktrees.filter(w => activeSet.has(w.moduleUid))\n\n return { orphaned, valid }\n }\n\n /**\n * Update last accessed timestamp for a worktree\n */\n async touchWorktree(moduleUid: string): Promise<void> {\n await this.updateConfigSafe(config => {\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (worktree) {\n worktree.lastAccessed = new Date().toISOString()\n }\n return config\n })\n }\n\n /**\n * Prune stale worktrees (directories that no longer exist)\n */\n async pruneStaleWorktrees(): Promise<number> {\n // First, run git worktree prune\n await this.gitExecutor.execute({\n action: 'worktree_prune'\n }, { cwd: this.bareRepoPath })\n\n // Then sync our config with reality (with locking)\n let prunedCount = 0\n await this.updateConfigSafe(config => {\n const initialCount = config.worktrees.length\n config.worktrees = config.worktrees.filter(w => fs.existsSync(w.worktreePath))\n prunedCount = initialCount - config.worktrees.length\n return config\n })\n\n return prunedCount\n }\n\n /**\n * Validate all worktrees and sync with git\n */\n async validateWorktrees(): Promise<{\n valid: WorktreeInfo[]\n stale: WorktreeInfo[]\n orphaned: string[]\n }> {\n const config = this.readConfig()\n const valid: WorktreeInfo[] = []\n const stale: WorktreeInfo[] = []\n const orphaned: string[] = []\n\n // Get actual worktrees from git\n const listResult = await this.gitExecutor.execute({\n action: 'worktree_list'\n }, { cwd: this.bareRepoPath })\n\n const actualWorktrees = new Set(\n listResult.details?.worktrees?.map(w => w.path) || []\n )\n\n // Check config worktrees\n for (const worktree of config?.worktrees || []) {\n if (actualWorktrees.has(worktree.worktreePath)) {\n valid.push(worktree)\n actualWorktrees.delete(worktree.worktreePath)\n } else {\n stale.push(worktree)\n }\n }\n\n // Any remaining are orphaned (exist in git but not in config)\n // Filter out the bare repo itself\n for (const wpath of actualWorktrees) {\n if (wpath !== this.bareRepoPath) {\n orphaned.push(wpath)\n }\n }\n\n return { valid, stale, orphaned }\n }\n\n /**\n * Get project configuration\n */\n getConfig(): WorktreeProjectConfig | null {\n return this.readConfig()\n }\n\n /**\n * Get the bare repo path\n */\n getBareRepoPath(): string {\n return this.bareRepoPath\n }\n\n /**\n * Get the project root path\n */\n getProjectRoot(): string {\n return this.projectRoot\n }\n\n // ============================================================\n // Private methods\n // ============================================================\n\n private lockPath: string = ''\n\n private getLockPath(): string {\n if (!this.lockPath) {\n this.lockPath = this.configPath + '.lock'\n }\n return this.lockPath\n }\n\n /**\n * Check if a process is still running\n */\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0) // Signal 0 = check if process exists without killing\n return true\n } catch {\n return false // Process doesn't exist or no permission\n }\n }\n\n /**\n * Acquire a file lock with timeout\n * Uses atomic file creation to ensure only one process holds the lock\n * P1-1: Added PID verification before removing stale locks to prevent race conditions\n */\n private async acquireLock(timeoutMs: number = 5000): Promise<boolean> {\n const lockPath = this.getLockPath()\n const startTime = Date.now()\n const retryInterval = 50\n\n while (Date.now() - startTime < timeoutMs) {\n try {\n // wx flag = create exclusive (fails if file exists)\n fs.writeFileSync(lockPath, String(process.pid), { flag: 'wx' })\n return true\n } catch (err: any) {\n if (err.code === 'EEXIST') {\n // Lock exists, check if it's stale (older than 30 seconds)\n try {\n const stats = fs.statSync(lockPath)\n const lockAge = Date.now() - stats.mtimeMs\n if (lockAge > 30000) {\n // P1-1: Verify the lock holder process is actually dead before removing\n // This prevents race conditions where multiple processes detect stale lock\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf-8').trim()\n const lockPid = parseInt(lockContent, 10)\n if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {\n // Process still running despite old lock - don't remove, just wait\n // The lock holder might be doing a long operation\n await new Promise(resolve => setTimeout(resolve, retryInterval))\n continue\n }\n } catch {\n // Can't read PID, proceed with removal attempt\n }\n // Stale lock from dead process, remove it\n try {\n fs.unlinkSync(lockPath)\n } catch {\n // Another process may have removed it - that's fine, retry\n }\n continue\n }\n } catch {\n // Lock file disappeared, retry\n continue\n }\n // Wait and retry\n await new Promise(resolve => setTimeout(resolve, retryInterval))\n continue\n }\n throw err\n }\n }\n return false\n }\n\n /**\n * Release the file lock\n */\n private releaseLock(): void {\n try {\n fs.unlinkSync(this.getLockPath())\n } catch {\n // Ignore errors (lock may not exist)\n }\n }\n\n private readConfig(): WorktreeProjectConfig | null {\n try {\n if (!fs.existsSync(this.configPath)) {\n return null\n }\n const content = fs.readFileSync(this.configPath, 'utf-8')\n return JSON.parse(content) as WorktreeProjectConfig\n } catch (error) {\n console.error('[WorktreeManager] Failed to read config:', error)\n return null\n }\n }\n\n private writeConfig(config: WorktreeProjectConfig): void {\n try {\n const dir = path.dirname(this.configPath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8')\n } catch (error) {\n console.error('[WorktreeManager] Failed to write config:', error)\n throw error\n }\n }\n\n /**\n * Read-modify-write with file locking for safe concurrent access\n */\n private async updateConfigSafe(\n updater: (config: WorktreeProjectConfig) => WorktreeProjectConfig\n ): Promise<boolean> {\n const lockAcquired = await this.acquireLock()\n if (!lockAcquired) {\n console.error('[WorktreeManager] Failed to acquire lock for config update')\n return false\n }\n\n try {\n const config = this.readConfig()\n if (!config) {\n return false\n }\n const updated = updater(config)\n this.writeConfig(updated)\n return true\n } finally {\n this.releaseLock()\n }\n }\n\n // EP959-11: Worktree setup methods\n\n /**\n * Update the setup status of a worktree\n */\n async updateWorktreeStatus(\n moduleUid: string,\n status: 'pending' | 'running' | 'ready' | 'error',\n error?: string\n ): Promise<boolean> {\n return this.updateConfigSafe((config) => {\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (worktree) {\n worktree.setupStatus = status\n if (status === 'running') {\n worktree.setupStartedAt = new Date().toISOString()\n } else if (status === 'ready' || status === 'error') {\n worktree.setupCompletedAt = new Date().toISOString()\n }\n if (error) {\n worktree.setupError = error\n }\n }\n return config\n })\n }\n\n /**\n * Get worktree info including setup status\n */\n getWorktreeStatus(moduleUid: string): WorktreeInfo | null {\n const config = this.readConfig()\n if (!config) return null\n return config.worktrees.find(w => w.moduleUid === moduleUid) || null\n }\n\n /**\n * Copy files from main worktree to module worktree\n *\n * @deprecated EP964: This function is deprecated. Use worktree_env_vars for\n * environment variables or worktree_setup_script for other file operations.\n * This function now returns success (no-op) when main/ worktree doesn't exist.\n */\n async copyFilesFromMain(moduleUid: string, files: string[]): Promise<{ success: boolean; error?: string }> {\n // EP964: Always log deprecation warning\n console.warn(`[WorktreeManager] EP964: copyFilesFromMain is DEPRECATED.`)\n console.warn(`[WorktreeManager] EP964: Use worktree_env_vars for .env or worktree_setup_script for other files.`)\n\n const config = this.readConfig()\n if (!config) {\n return { success: false, error: 'Config not found' }\n }\n\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (!worktree) {\n return { success: false, error: `Worktree not found for ${moduleUid}` }\n }\n\n // EP964: Return success (no-op) when main/ doesn't exist\n const mainWorktree = config.worktrees.find(w => w.moduleUid === 'main')\n if (!mainWorktree) {\n console.warn(`[WorktreeManager] EP964: No 'main' worktree - skipping file copy (this is expected).`)\n return { success: true } // No-op success instead of failure\n }\n\n // Backward compatibility: still copy if main/ exists\n try {\n for (const file of files) {\n const srcPath = path.join(mainWorktree.worktreePath, file)\n const destPath = path.join(worktree.worktreePath, file)\n\n if (fs.existsSync(srcPath)) {\n const destDir = path.dirname(destPath)\n if (!fs.existsSync(destDir)) {\n fs.mkdirSync(destDir, { recursive: true })\n }\n fs.copyFileSync(srcPath, destPath)\n console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`)\n } else {\n console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`)\n }\n }\n return { success: true }\n } catch (error) {\n return { success: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /**\n * Run worktree setup script\n * EP959-M1: Enhanced logging with working directory, timeout info, and script preview\n */\n async runSetupScript(moduleUid: string, script: string): Promise<{ success: boolean; error?: string }> {\n const config = this.readConfig()\n if (!config) {\n return { success: false, error: 'Config not found' }\n }\n\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (!worktree) {\n return { success: false, error: `Worktree not found for ${moduleUid}` }\n }\n\n // EP959-M1: Enhanced logging - show script preview (truncate if very long)\n const TIMEOUT_MINUTES = 10\n const scriptPreview = script.length > 200 ? script.slice(0, 200) + '...' : script\n console.log(`[WorktreeManager] EP959: Running setup script for ${moduleUid}`)\n console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`)\n console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`)\n console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`)\n\n try {\n const { execSync } = require('child_process')\n\n execSync(script, {\n cwd: worktree.worktreePath,\n stdio: 'inherit',\n timeout: TIMEOUT_MINUTES * 60 * 1000,\n env: { ...process.env, NODE_ENV: 'development' }\n })\n\n console.log(`[WorktreeManager] EP959: Setup script completed successfully for ${moduleUid}`)\n return { success: true }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`[WorktreeManager] EP959: Setup script failed for ${moduleUid}:`, errorMessage)\n return { success: false, error: errorMessage }\n }\n }\n\n /**\n * Run worktree cleanup script before removal\n * EP959-m3: Execute cleanup script when worktree is being released\n */\n async runCleanupScript(moduleUid: string, script: string): Promise<{ success: boolean; error?: string }> {\n const config = this.readConfig()\n if (!config) {\n return { success: false, error: 'Config not found' }\n }\n\n const worktree = config.worktrees.find(w => w.moduleUid === moduleUid)\n if (!worktree) {\n return { success: false, error: `Worktree not found for ${moduleUid}` }\n }\n\n const TIMEOUT_MINUTES = 5\n const scriptPreview = script.length > 200 ? script.slice(0, 200) + '...' : script\n console.log(`[WorktreeManager] EP959: Running cleanup script for ${moduleUid}`)\n console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`)\n console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`)\n console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`)\n\n try {\n const { execSync } = require('child_process')\n\n execSync(script, {\n cwd: worktree.worktreePath,\n stdio: 'inherit',\n timeout: TIMEOUT_MINUTES * 60 * 1000,\n env: { ...process.env, NODE_ENV: 'development' }\n })\n\n console.log(`[WorktreeManager] EP959: Cleanup script completed successfully for ${moduleUid}`)\n return { success: true }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n // Cleanup failures are logged but don't block worktree removal\n console.warn(`[WorktreeManager] EP959: Cleanup script failed for ${moduleUid} (non-blocking):`, errorMessage)\n return { success: false, error: errorMessage }\n }\n }\n}\n\n/**\n * Get the default episoda root directory\n * Can be overridden with EPISODA_ROOT environment variable\n */\nexport function getEpisodaRoot(): string {\n return process.env.EPISODA_ROOT || path.join(require('os').homedir(), 'episoda')\n}\n\n/**\n * Get the project path for a workspace/project combination\n */\nexport function getProjectPath(workspaceSlug: string, projectSlug: string): string {\n return path.join(getEpisodaRoot(), workspaceSlug, projectSlug)\n}\n\n/**\n * Check if a path is a valid worktree project\n */\nexport async function isWorktreeProject(projectRoot: string): Promise<boolean> {\n const manager = new WorktreeManager(projectRoot)\n return manager.initialize()\n}\n\n/**\n * Find the project root by looking for .bare directory\n * Searches current directory and walks up to 5 levels looking for a valid worktree project.\n *\n * @param startPath - Directory to start searching from\n * @returns Absolute path to project root, or null if not found\n */\nexport async function findProjectRoot(startPath: string): Promise<string | null> {\n let current = path.resolve(startPath)\n const episodaRoot = getEpisodaRoot()\n\n // Must be under ~/episoda to be a worktree project\n if (!current.startsWith(episodaRoot)) {\n return null\n }\n\n // Walk up to find .bare directory (support up to 10 levels deep)\n for (let i = 0; i < 10; i++) {\n const bareDir = path.join(current, '.bare')\n const episodaDir = path.join(current, '.episoda')\n\n if (fs.existsSync(bareDir) && fs.existsSync(episodaDir)) {\n // Verify it's a valid worktree project\n if (await isWorktreeProject(current)) {\n return current\n }\n }\n\n // Move up one directory\n const parent = path.dirname(current)\n if (parent === current) {\n // Reached filesystem root\n break\n }\n current = parent\n }\n\n return null\n}\n","/**\n * EP956: Worktree Path Resolution Utilities\n * EP960: Added server-synced EPISODA_ROOT support\n *\n * Provides functions to resolve worktree paths for modules.\n * Worktrees follow the structure: ~/episoda/{workspace_slug}/{project_slug}/{module_uid}/\n */\n\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport { loadConfig, type EpisodaConfig } from '@episoda/core'\nimport { fetchEpisodaRoot } from '../api/machine-settings'\n\n/**\n * Information about a module's worktree\n */\nexport interface WorktreeInfo {\n /** Full path to worktree directory (e.g., ~/episoda/acme/website/EP956) */\n path: string\n /** Whether the worktree directory exists on disk */\n exists: boolean\n /** Module UID this worktree is for */\n moduleUid: string\n}\n\n/**\n * Get the episoda root directory where all worktrees are stored.\n * Default: ~/episoda\n * Override: Set EPISODA_ROOT environment variable\n *\n * Note: This is the synchronous version for backward compatibility.\n * Use getEpisodaRootAsync() to also check server-synced settings.\n */\nexport function getEpisodaRoot(): string {\n return process.env.EPISODA_ROOT || path.join(os.homedir(), 'episoda')\n}\n\n/**\n * EP960: Get the episoda root directory with server-synced settings.\n *\n * Priority order:\n * 1. EPISODA_ROOT environment variable (highest - allows local override)\n * 2. Server-synced value from local_machine.episoda_root\n * 3. Default: 'episoda' (~/episoda)\n *\n * @param config - Optional EpisodaConfig, will load from file if not provided\n * @returns Absolute path to episoda root directory\n */\nexport async function getEpisodaRootAsync(config?: EpisodaConfig): Promise<string> {\n // 1. Environment variable override (highest priority)\n if (process.env.EPISODA_ROOT) {\n return process.env.EPISODA_ROOT\n }\n\n // 2. Try to get server-synced value\n const cfg = config || await loadConfig()\n if (cfg?.device_id && cfg?.access_token) {\n try {\n const serverRoot = await fetchEpisodaRoot(cfg)\n if (serverRoot && serverRoot !== 'episoda') {\n // Server has a custom value - use it (relative to home)\n return path.join(os.homedir(), serverRoot)\n }\n } catch {\n // Server fetch failed - fall through to default\n }\n }\n\n // 3. Default\n return path.join(os.homedir(), 'episoda')\n}\n\n/**\n * Get worktree info for a module given explicit slugs.\n * Synchronous version - uses local EPISODA_ROOT only.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @param workspaceSlug - Workspace slug (e.g., \"acme\")\n * @param projectSlug - Project slug (e.g., \"website\")\n * @returns WorktreeInfo with path and existence status\n */\nexport function getWorktreeInfo(\n moduleUid: string,\n workspaceSlug: string,\n projectSlug: string\n): WorktreeInfo {\n const root = getEpisodaRoot()\n const worktreePath = path.join(root, workspaceSlug, projectSlug, moduleUid)\n\n return {\n path: worktreePath,\n exists: fs.existsSync(worktreePath),\n moduleUid\n }\n}\n\n/**\n * EP960: Get worktree info with server-synced EPISODA_ROOT.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @param workspaceSlug - Workspace slug (e.g., \"acme\")\n * @param projectSlug - Project slug (e.g., \"website\")\n * @param config - Optional EpisodaConfig, will load from file if not provided\n * @returns WorktreeInfo with path and existence status\n */\nexport async function getWorktreeInfoAsync(\n moduleUid: string,\n workspaceSlug: string,\n projectSlug: string,\n config?: EpisodaConfig\n): Promise<WorktreeInfo> {\n const root = await getEpisodaRootAsync(config)\n const worktreePath = path.join(root, workspaceSlug, projectSlug, moduleUid)\n\n return {\n path: worktreePath,\n exists: fs.existsSync(worktreePath),\n moduleUid\n }\n}\n\n/**\n * Get worktree info for a module using slugs from the global config.\n * Loads workspace_slug and project_slug from ~/.episoda/config.json.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @returns WorktreeInfo or null if config is missing required slugs\n */\nexport async function getWorktreeInfoForModule(\n moduleUid: string\n): Promise<WorktreeInfo | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n console.warn('[Worktree] Missing workspace_slug or project_slug in config')\n return null\n }\n\n return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug)\n}\n\n/**\n * Get the bare repo path for the current project.\n * Structure: ~/episoda/{workspace_slug}/{project_slug}/.bare/\n * Synchronous version - uses local EPISODA_ROOT only.\n *\n * @returns Path to .bare directory or null if config missing\n */\nexport async function getBareRepoPath(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n return path.join(\n getEpisodaRoot(),\n config.workspace_slug,\n config.project_slug,\n '.bare'\n )\n}\n\n/**\n * EP960: Get the bare repo path with server-synced EPISODA_ROOT.\n * Structure: {episoda_root}/{workspace_slug}/{project_slug}/.bare/\n *\n * @returns Path to .bare directory or null if config missing\n */\nexport async function getBareRepoPathAsync(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n const root = await getEpisodaRootAsync(config)\n return path.join(\n root,\n config.workspace_slug,\n config.project_slug,\n '.bare'\n )\n}\n\n/**\n * Get the project root path (parent of .bare and all worktrees).\n * Structure: ~/episoda/{workspace_slug}/{project_slug}/\n * Synchronous version - uses local EPISODA_ROOT only.\n *\n * @returns Path to project root or null if config missing\n */\nexport async function getProjectRootPath(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n return path.join(\n getEpisodaRoot(),\n config.workspace_slug,\n config.project_slug\n )\n}\n\n/**\n * EP960: Get the project root path with server-synced EPISODA_ROOT.\n * Structure: {episoda_root}/{workspace_slug}/{project_slug}/\n *\n * @returns Path to project root or null if config missing\n */\nexport async function getProjectRootPathAsync(): Promise<string | null> {\n const config = await loadConfig()\n\n if (!config?.workspace_slug || !config?.project_slug) {\n return null\n }\n\n const root = await getEpisodaRootAsync(config)\n return path.join(\n root,\n config.workspace_slug,\n config.project_slug\n )\n}\n","/**\n * EP956: In-Memory Port Allocator\n *\n * Allocates unique ports to modules for dev servers.\n * Ports are stored in memory only - no database persistence.\n *\n * Port Range: 3100-3199 (100 concurrent modules max)\n * Port 3000 is reserved for legacy/main app usage.\n */\n\nconst PORT_RANGE_START = 3100\nconst PORT_RANGE_END = 3199\nconst PORT_WARNING_THRESHOLD = 80 // Warn when 80% of ports are used\n\n/** In-memory port assignments: moduleUid -> port */\nconst portAssignments = new Map<string, number>()\n\n/**\n * Allocate a port for a module.\n * Idempotent: returns existing assignment if already allocated.\n *\n * @param moduleUid - Module UID (e.g., \"EP956\")\n * @returns Allocated port number\n * @throws Error if all 100 ports are exhausted\n */\nexport function allocatePort(moduleUid: string): number {\n // Return existing assignment (idempotent)\n const existing = portAssignments.get(moduleUid)\n if (existing) {\n return existing\n }\n\n // Find used ports\n const usedPorts = new Set(portAssignments.values())\n\n // Warn if approaching limit\n if (usedPorts.size >= PORT_WARNING_THRESHOLD) {\n console.warn(\n `[PortAllocator] Warning: ${usedPorts.size}/${PORT_RANGE_END - PORT_RANGE_START + 1} ports allocated`\n )\n }\n\n // Find first available port\n for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {\n if (!usedPorts.has(port)) {\n portAssignments.set(moduleUid, port)\n console.log(`[PortAllocator] Assigned port ${port} to ${moduleUid}`)\n return port\n }\n }\n\n throw new Error(\n `No available ports in range ${PORT_RANGE_START}-${PORT_RANGE_END}. ` +\n `${portAssignments.size} modules are using all available ports.`\n )\n}\n\n/**\n * Release a port assignment when a module is done.\n * Frees the port for reuse by another module.\n *\n * @param moduleUid - Module UID to release\n */\nexport function releasePort(moduleUid: string): void {\n const port = portAssignments.get(moduleUid)\n if (port) {\n portAssignments.delete(moduleUid)\n console.log(`[PortAllocator] Released port ${port} from ${moduleUid}`)\n }\n}\n\n/**\n * Get the currently assigned port for a module.\n *\n * @param moduleUid - Module UID to check\n * @returns Assigned port or null if not allocated\n */\nexport function getAssignedPort(moduleUid: string): number | null {\n return portAssignments.get(moduleUid) || null\n}\n\n/**\n * Get the number of currently allocated ports.\n * Useful for monitoring and debugging.\n *\n * @returns Number of allocated ports\n */\nexport function getPortUsageCount(): number {\n return portAssignments.size\n}\n\n/**\n * Get all current port assignments.\n * Returns a copy to prevent external modification.\n *\n * @returns Map of moduleUid -> port\n */\nexport function getAllPortAssignments(): Map<string, number> {\n return new Map(portAssignments)\n}\n\n/**\n * Clear all port assignments.\n * Used for testing and cleanup.\n */\nexport function clearAllPorts(): void {\n const count = portAssignments.size\n portAssignments.clear()\n if (count > 0) {\n console.log(`[PortAllocator] Cleared ${count} port assignments`)\n }\n}\n","/**\n * Framework Detection\n *\n * Detects the framework/stack based on project files and suggests\n * appropriate dev server command.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nexport interface FrameworkDetection {\n framework: string\n command: string[]\n confidence: 'high' | 'medium' | 'low'\n detectedFrom: string\n}\n\n/**\n * EP986: Install command detection result\n */\nexport interface InstallCommand {\n command: string[]\n description: string\n detectedFrom: string\n}\n\n/**\n * Detect framework from project files\n * @param cwd - Working directory to search (defaults to process.cwd())\n * @returns Detection result or null if no framework detected\n */\nexport async function detectFramework(cwd: string = process.cwd()): Promise<FrameworkDetection | null> {\n // Check for package.json (Node.js projects)\n const packageJsonPath = path.join(cwd, 'package.json')\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))\n\n // Check scripts first\n const scripts = packageJson.scripts || {}\n\n // Next.js detection\n if (packageJson.dependencies?.next || packageJson.devDependencies?.next) {\n if (scripts.dev) {\n return {\n framework: 'Next.js',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (next dependency + dev script)'\n }\n }\n return {\n framework: 'Next.js',\n command: ['npx', 'next', 'dev'],\n confidence: 'medium',\n detectedFrom: 'package.json (next dependency)'\n }\n }\n\n // React (Create React App, Vite, etc.)\n if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {\n if (scripts.dev) {\n return {\n framework: 'React',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (react + dev script)'\n }\n }\n if (scripts.start) {\n return {\n framework: 'React',\n command: ['npm', 'start'],\n confidence: 'high',\n detectedFrom: 'package.json (react + start script)'\n }\n }\n }\n\n // Express\n if (packageJson.dependencies?.express) {\n if (scripts.dev) {\n return {\n framework: 'Express',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (express + dev script)'\n }\n }\n if (scripts.start) {\n return {\n framework: 'Express',\n command: ['npm', 'start'],\n confidence: 'medium',\n detectedFrom: 'package.json (express + start script)'\n }\n }\n }\n\n // Vue\n if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {\n if (scripts.dev) {\n return {\n framework: 'Vue',\n command: ['npm', 'run', 'dev'],\n confidence: 'high',\n detectedFrom: 'package.json (vue + dev script)'\n }\n }\n if (scripts.serve) {\n return {\n framework: 'Vue',\n command: ['npm', 'run', 'serve'],\n confidence: 'high',\n detectedFrom: 'package.json (vue + serve script)'\n }\n }\n }\n\n // Generic Node.js (fallback)\n if (scripts.dev) {\n return {\n framework: 'Node.js',\n command: ['npm', 'run', 'dev'],\n confidence: 'medium',\n detectedFrom: 'package.json (dev script)'\n }\n }\n if (scripts.start) {\n return {\n framework: 'Node.js',\n command: ['npm', 'start'],\n confidence: 'low',\n detectedFrom: 'package.json (start script)'\n }\n }\n }\n\n // Check for requirements.txt (Python projects)\n const requirementsPath = path.join(cwd, 'requirements.txt')\n if (fs.existsSync(requirementsPath)) {\n const requirements = fs.readFileSync(requirementsPath, 'utf-8')\n\n // Django\n if (requirements.includes('Django') || requirements.includes('django')) {\n const managePy = path.join(cwd, 'manage.py')\n if (fs.existsSync(managePy)) {\n return {\n framework: 'Django',\n command: ['python', 'manage.py', 'runserver'],\n confidence: 'high',\n detectedFrom: 'requirements.txt (Django) + manage.py'\n }\n }\n return {\n framework: 'Django',\n command: ['python', 'manage.py', 'runserver'],\n confidence: 'medium',\n detectedFrom: 'requirements.txt (Django)'\n }\n }\n\n // Flask\n if (requirements.includes('Flask') || requirements.includes('flask')) {\n // Look for common Flask app files\n const appFiles = ['app.py', 'application.py', 'wsgi.py']\n for (const file of appFiles) {\n if (fs.existsSync(path.join(cwd, file))) {\n return {\n framework: 'Flask',\n command: ['flask', 'run'],\n confidence: 'high',\n detectedFrom: `requirements.txt (Flask) + ${file}`\n }\n }\n }\n return {\n framework: 'Flask',\n command: ['flask', 'run'],\n confidence: 'medium',\n detectedFrom: 'requirements.txt (Flask)'\n }\n }\n\n // FastAPI / Uvicorn\n if (requirements.includes('fastapi') || requirements.includes('uvicorn')) {\n return {\n framework: 'FastAPI',\n command: ['uvicorn', 'main:app', '--reload'],\n confidence: 'medium',\n detectedFrom: 'requirements.txt (fastapi/uvicorn)'\n }\n }\n }\n\n // Check for Gemfile (Ruby projects)\n const gemfilePath = path.join(cwd, 'Gemfile')\n if (fs.existsSync(gemfilePath)) {\n const gemfile = fs.readFileSync(gemfilePath, 'utf-8')\n\n // Rails\n if (gemfile.includes('rails')) {\n return {\n framework: 'Rails',\n command: ['rails', 'server'],\n confidence: 'high',\n detectedFrom: 'Gemfile (rails)'\n }\n }\n\n // Sinatra\n if (gemfile.includes('sinatra')) {\n return {\n framework: 'Sinatra',\n command: ['ruby', 'app.rb'],\n confidence: 'medium',\n detectedFrom: 'Gemfile (sinatra)'\n }\n }\n }\n\n // Check for go.mod (Go projects)\n const goModPath = path.join(cwd, 'go.mod')\n if (fs.existsSync(goModPath)) {\n return {\n framework: 'Go',\n command: ['go', 'run', '.'],\n confidence: 'medium',\n detectedFrom: 'go.mod'\n }\n }\n\n // Check for Cargo.toml (Rust projects)\n const cargoTomlPath = path.join(cwd, 'Cargo.toml')\n if (fs.existsSync(cargoTomlPath)) {\n return {\n framework: 'Rust',\n command: ['cargo', 'run'],\n confidence: 'medium',\n detectedFrom: 'Cargo.toml'\n }\n }\n\n // No framework detected\n return null\n}\n\n/**\n * EP986: Detect and return the appropriate dependency install command\n *\n * Detection priority (lock files first for exact versions):\n * 1. bun.lockb → bun install\n * 2. pnpm-lock.yaml → pnpm install\n * 3. yarn.lock → yarn install\n * 4. package-lock.json → npm ci (exact versions)\n * 5. package.json → npm install (fallback)\n * 6. Pipfile.lock → pipenv install\n * 7. poetry.lock → poetry install\n * 8. requirements.txt → pip install -r requirements.txt\n * 9. Gemfile.lock / Gemfile → bundle install\n * 10. go.sum / go.mod → go mod download\n * 11. Cargo.lock / Cargo.toml → cargo build\n *\n * @param cwd - Working directory to search\n * @returns Install command info or null if no package manager detected\n */\nexport function getInstallCommand(cwd: string): InstallCommand | null {\n // Node.js - check lock files first for specific package manager\n // Bun first (fastest runtime)\n if (fs.existsSync(path.join(cwd, 'bun.lockb'))) {\n return {\n command: ['bun', 'install'],\n description: 'Installing dependencies with bun',\n detectedFrom: 'bun.lockb'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {\n return {\n command: ['pnpm', 'install'],\n description: 'Installing dependencies with pnpm',\n detectedFrom: 'pnpm-lock.yaml'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {\n return {\n command: ['yarn', 'install'],\n description: 'Installing dependencies with yarn',\n detectedFrom: 'yarn.lock'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'package-lock.json'))) {\n return {\n command: ['npm', 'ci'],\n description: 'Installing dependencies with npm ci',\n detectedFrom: 'package-lock.json'\n }\n }\n\n // Fallback to npm install if only package.json exists\n if (fs.existsSync(path.join(cwd, 'package.json'))) {\n return {\n command: ['npm', 'install'],\n description: 'Installing dependencies with npm',\n detectedFrom: 'package.json'\n }\n }\n\n // Python - check lock files first\n if (fs.existsSync(path.join(cwd, 'Pipfile.lock')) || fs.existsSync(path.join(cwd, 'Pipfile'))) {\n return {\n command: ['pipenv', 'install'],\n description: 'Installing dependencies with pipenv',\n detectedFrom: fs.existsSync(path.join(cwd, 'Pipfile.lock')) ? 'Pipfile.lock' : 'Pipfile'\n }\n }\n\n // Poetry detection: check lock file first, then pyproject.toml\n if (fs.existsSync(path.join(cwd, 'poetry.lock'))) {\n return {\n command: ['poetry', 'install'],\n description: 'Installing dependencies with poetry',\n detectedFrom: 'poetry.lock'\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'pyproject.toml'))) {\n const pyprojectPath = path.join(cwd, 'pyproject.toml')\n const content = fs.readFileSync(pyprojectPath, 'utf-8')\n if (content.includes('[tool.poetry]')) {\n return {\n command: ['poetry', 'install'],\n description: 'Installing dependencies with poetry',\n detectedFrom: 'pyproject.toml'\n }\n }\n }\n\n if (fs.existsSync(path.join(cwd, 'requirements.txt'))) {\n return {\n command: ['pip', 'install', '-r', 'requirements.txt'],\n description: 'Installing dependencies with pip',\n detectedFrom: 'requirements.txt'\n }\n }\n\n // Ruby\n if (fs.existsSync(path.join(cwd, 'Gemfile.lock')) || fs.existsSync(path.join(cwd, 'Gemfile'))) {\n return {\n command: ['bundle', 'install'],\n description: 'Installing dependencies with bundler',\n detectedFrom: fs.existsSync(path.join(cwd, 'Gemfile.lock')) ? 'Gemfile.lock' : 'Gemfile'\n }\n }\n\n // Go\n if (fs.existsSync(path.join(cwd, 'go.sum')) || fs.existsSync(path.join(cwd, 'go.mod'))) {\n return {\n command: ['go', 'mod', 'download'],\n description: 'Downloading Go modules',\n detectedFrom: fs.existsSync(path.join(cwd, 'go.sum')) ? 'go.sum' : 'go.mod'\n }\n }\n\n // Rust\n if (fs.existsSync(path.join(cwd, 'Cargo.lock')) || fs.existsSync(path.join(cwd, 'Cargo.toml'))) {\n return {\n command: ['cargo', 'build'],\n description: 'Building Rust project (downloads dependencies)',\n detectedFrom: fs.existsSync(path.join(cwd, 'Cargo.lock')) ? 'Cargo.lock' : 'Cargo.toml'\n }\n }\n\n // No package manager detected\n return null\n}\n\n/**\n * Suggest command based on user-provided command or auto-detection\n * @param providedCommand - Command provided by user (e.g., [\"npm\", \"run\", \"dev\"])\n * @param cwd - Working directory\n * @returns Final command to use\n */\nexport async function resolveDevCommand(\n providedCommand: string[] | null,\n cwd: string = process.cwd()\n): Promise<{ command: string[]; detection: FrameworkDetection | null }> {\n // If user provided command, use it as-is\n if (providedCommand && providedCommand.length > 0) {\n return { command: providedCommand, detection: null }\n }\n\n // Auto-detect framework\n const detection = await detectFramework(cwd)\n if (detection) {\n return { command: detection.command, detection }\n }\n\n // No detection - default to npm run dev\n return {\n command: ['npm', 'run', 'dev'],\n detection: {\n framework: 'Unknown',\n command: ['npm', 'run', 'dev'],\n confidence: 'low',\n detectedFrom: 'default fallback'\n }\n }\n}\n"],"mappings":"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","names":["exports","exports","execAsync","GitExecutor","fs","path","exports","packageJson","exports","EpisodaClient","resolve","exports","exports","getConfigDir","loadConfig","saveConfig","fs","path","os","exports","exports","resolve","net","exports","module","fs","path","import_core","path","import_core","fs","path","import_core","resolve","import_core","import_child_process","fs","path","import_child_process","resolve","import_child_process","import_core","import_child_process","fs","path","platform","arch","resolve","import_child_process","fs","path","os","resolve","process","import_core","import_child_process","path","fs","import_child_process","path","fs","os","process","resolve","import_child_process","import_core","fs","path","os","fs","path","fs","path","resolve","http","fs","path","fs","path","import_core","resolve","execSync","path","fs","os","import_core","getEpisodaRoot","getEpisodaRoot","fs","path","fs","os","path","fetchEnvVars","resolve","config","errorMsg","execSync","module","isPortInUse","startResult","exec","promisify","execAsync"]}
|