episoda 0.2.12 → 0.2.13
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 +14 -4
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +13 -3
- 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","../../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"],"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 // 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 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 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 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 * 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 * Single source of truth: reads version from package.json\n * This ensures version is always in sync and only needs to be\n * updated in one place (package.json) during releases.\n */\n\nimport { readFileSync } from 'fs'\nimport { join } from 'path'\n\n// Read version from package.json at module load time\n// Using readFileSync to avoid require() caching issues during development\nconst packageJsonPath = join(__dirname, '..', 'package.json')\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))\n\nexport const VERSION: string = packageJson.version\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\nconst CLIENT_HEARTBEAT_INTERVAL = 45000 // 45 seconds (offset from server's 30s to avoid collision)\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 (30s 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 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 * 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 exponential backoff and randomization\n *\n * EP605: Conservative reconnection with multiple safeguards:\n * - 6-hour maximum retry duration to prevent indefinite retrying\n * - Activity-based backoff (10 min delay if idle for 1+ hour)\n * - No reconnection after intentional disconnect\n * - Randomization to prevent thundering herd\n *\n * EP648: Additional protections against reconnection loops:\n * - Rate limit awareness: respect server's RATE_LIMITED response\n * - Rapid close detection: if connection closes within 2s, apply longer backoff\n */\n private scheduleReconnect(): void {\n // EP605: Don't reconnect if user intentionally disconnected\n if (this.isIntentionalDisconnect) {\n console.log('[EpisodaClient] Intentional disconnect - not reconnecting')\n return\n }\n\n // EP605: 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 // EP605: Track when reconnection attempts started\n if (!this.firstDisconnectTime) {\n this.firstDisconnectTime = Date.now()\n }\n\n // EP605: Check 6-hour maximum retry duration\n const retryDuration = Date.now() - this.firstDisconnectTime\n if (retryDuration >= MAX_RETRY_DURATION) {\n console.error(`[EpisodaClient] Maximum retry duration (6 hours) exceeded, giving up. Please restart the CLI.`)\n return\n }\n\n // EP605: Log periodically to show we're still trying\n if (this.reconnectAttempts > 0 && this.reconnectAttempts % 10 === 0) {\n const hoursRemaining = ((MAX_RETRY_DURATION - retryDuration) / (60 * 60 * 1000)).toFixed(1)\n console.log(`[EpisodaClient] Still attempting to reconnect (attempt ${this.reconnectAttempts}, ${hoursRemaining}h remaining)...`)\n }\n\n // EP648: 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 // Clear rate limit after waiting\n this.scheduleReconnect()\n }, waitTime)\n return\n }\n\n // EP648: Detect rapid close (connection closed within 2 seconds of opening)\n // This indicates an error condition like rate limiting, auth failure, etc.\n const timeSinceConnect = Date.now() - this.lastConnectAttemptTime\n const wasRapidClose = timeSinceConnect < RAPID_CLOSE_THRESHOLD && this.lastConnectAttemptTime > 0\n\n // EP605: Check if connection is idle (no commands for 1+ hour)\n const timeSinceLastCommand = Date.now() - this.lastCommandTime\n const isIdle = timeSinceLastCommand >= IDLE_THRESHOLD\n\n // Calculate base delay based on shutdown type and conditions\n let baseDelay: number\n if (this.isGracefulShutdown && this.reconnectAttempts < 3) {\n // Graceful shutdown: try reconnecting quickly (500ms, 1s, 2s)\n baseDelay = 500 * Math.pow(2, this.reconnectAttempts)\n } else if (wasRapidClose) {\n // EP648: Connection failed almost immediately - likely rate limited or auth error\n // Apply longer backoff to prevent spam\n baseDelay = Math.max(RAPID_CLOSE_BACKOFF, INITIAL_RECONNECT_DELAY * Math.pow(2, Math.min(this.reconnectAttempts, 6)))\n console.log(`[EpisodaClient] Rapid close detected (${timeSinceConnect}ms), applying ${baseDelay / 1000}s backoff`)\n } else if (isIdle) {\n // EP605: Use slower retry for idle connections (10 minutes)\n baseDelay = IDLE_RECONNECT_DELAY\n } else {\n // EP605: Use capped exponential backoff\n // After 6 attempts, we hit max delay and stay there\n const cappedAttempts = Math.min(this.reconnectAttempts, 6)\n baseDelay = INITIAL_RECONNECT_DELAY * Math.pow(2, cappedAttempts)\n }\n\n // Add randomization (±25%) to prevent thundering herd\n // When server restarts, all clients shouldn't hit it at the exact same time\n const jitter = baseDelay * 0.25 * (Math.random() * 2 - 1) // Random between -25% and +25%\n const maxDelay = isIdle ? IDLE_RECONNECT_DELAY : MAX_RECONNECT_DELAY\n const delay = Math.min(baseDelay + jitter, maxDelay)\n\n this.reconnectAttempts++\n const shutdownType = this.isGracefulShutdown ? 'graceful' : 'ungraceful'\n const idleStatus = isIdle ? ', idle' : ''\n const rapidStatus = wasRapidClose ? ', rapid-close' : ''\n\n // EP605: Only log first few attempts and then periodically\n if (this.reconnectAttempts <= 5 || this.reconnectAttempts % 10 === 0) {\n console.log(`[EpisodaClient] Reconnecting in ${Math.round(delay / 1000)}s (attempt ${this.reconnectAttempts}, ${shutdownType}${idleStatus}${rapidStatus})`)\n }\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 // EP605: Reset counters after successful reconnection\n console.log('[EpisodaClient] Reconnection successful, resetting retry counter')\n this.reconnectAttempts = 0\n this.isGracefulShutdown = false\n this.firstDisconnectTime = undefined\n this.rateLimitBackoffUntil = undefined // EP648: Clear rate limit on success\n }).catch(error => {\n console.error('[EpisodaClient] Reconnection failed:', error)\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 '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 \"name\": \"episoda\",\n \"version\": \"0.2.11\",\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 \"ws\": \"^8.18.0\",\n \"zod\": \"^4.0.10\"\n },\n \"devDependencies\": {\n \"@episoda/core\": \"*\",\n \"@types/node\": \"^20.11.24\",\n \"@types/semver\": \"7.7.1\",\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}\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 * 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 * @param projectId Supabase project ID\n * @param projectPath Absolute path to project\n * @returns The tracked project\n */\nexport function addProject(projectId: string, projectPath: string): 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 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 }\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 // Spawn daemon as detached process\n const child = spawn('node', [daemonScript], {\n detached: true, // Run independently of parent\n stdio: 'ignore', // Don't inherit stdio (prevents hanging)\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 } from '@episoda/core'\nimport { checkForUpdates, performBackgroundUpdate } from '../utils/update-checker'\n// EP812: Removed IdentityServer - browser identity now uses cookie-based pairing\nimport { handleFileRead, handleFileWrite, handleFileList, handleFileSearch, handleFileGrep, handleExec } from './handlers'\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 * 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 private shuttingDown = false\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 // 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 connected: 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 this.ipcServer.on('add-project', async (params: { projectId: string; projectPath: string }) => {\n const { projectId, projectPath } = params\n trackProject(projectId, projectPath)\n\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 return { success: false, connected: false, error: 'Connection established but not healthy' }\n }\n\n return { success: true, connected: true }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return { success: false, connected: false, error: errorMessage }\n }\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 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 isHealthy: this.isConnectionHealthy(p.path),\n }))\n\n const healthyCount = projects.filter(p => p.isHealthy).length\n const staleCount = projects.filter(p => p.inConnectionsMap && !p.inLiveConnections).length\n\n return {\n totalProjects: projects.length,\n healthyConnections: healthyCount,\n staleConnections: staleCount,\n projects,\n }\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 * 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 if (this.connections.has(projectPath)) {\n if (this.liveConnections.has(projectPath)) {\n console.log(`[Daemon] Already connected to ${projectPath}`)\n return\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\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 const result = await gitExecutor.execute(message.command as GitCommand, {\n cwd: projectPath\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 } 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: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 // 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\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\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 // 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 } catch (error) {\n console.error(`[Daemon] Failed to connect to ${projectId}:`, error)\n this.connections.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\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 const hooks = ['post-checkout', 'pre-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 * 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 // 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 // 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 } 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}\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 // Non-blocking - fail silently on network errors or timeouts\n return { currentVersion, latestVersion: currentVersion, updateAvailable: false }\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 * 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 * 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"],"mappings":"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","names":["exports","exports","GitExecutor","fs","exports","packageJson","exports","EpisodaClient","resolve","exports","exports","getConfigDir","loadConfig","saveConfig","fs","path","os","exports","exports","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","fs","path","execSync"]}
|
|
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","../../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"],"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 // 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 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 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 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 * 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\nconst CLIENT_HEARTBEAT_INTERVAL = 45000 // 45 seconds (offset from server's 30s to avoid collision)\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 (30s 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 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 * 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 exponential backoff and randomization\n *\n * EP605: Conservative reconnection with multiple safeguards:\n * - 6-hour maximum retry duration to prevent indefinite retrying\n * - Activity-based backoff (10 min delay if idle for 1+ hour)\n * - No reconnection after intentional disconnect\n * - Randomization to prevent thundering herd\n *\n * EP648: Additional protections against reconnection loops:\n * - Rate limit awareness: respect server's RATE_LIMITED response\n * - Rapid close detection: if connection closes within 2s, apply longer backoff\n */\n private scheduleReconnect(): void {\n // EP605: Don't reconnect if user intentionally disconnected\n if (this.isIntentionalDisconnect) {\n console.log('[EpisodaClient] Intentional disconnect - not reconnecting')\n return\n }\n\n // EP605: 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 // EP605: Track when reconnection attempts started\n if (!this.firstDisconnectTime) {\n this.firstDisconnectTime = Date.now()\n }\n\n // EP605: Check 6-hour maximum retry duration\n const retryDuration = Date.now() - this.firstDisconnectTime\n if (retryDuration >= MAX_RETRY_DURATION) {\n console.error(`[EpisodaClient] Maximum retry duration (6 hours) exceeded, giving up. Please restart the CLI.`)\n return\n }\n\n // EP605: Log periodically to show we're still trying\n if (this.reconnectAttempts > 0 && this.reconnectAttempts % 10 === 0) {\n const hoursRemaining = ((MAX_RETRY_DURATION - retryDuration) / (60 * 60 * 1000)).toFixed(1)\n console.log(`[EpisodaClient] Still attempting to reconnect (attempt ${this.reconnectAttempts}, ${hoursRemaining}h remaining)...`)\n }\n\n // EP648: 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 // Clear rate limit after waiting\n this.scheduleReconnect()\n }, waitTime)\n return\n }\n\n // EP648: Detect rapid close (connection closed within 2 seconds of opening)\n // This indicates an error condition like rate limiting, auth failure, etc.\n const timeSinceConnect = Date.now() - this.lastConnectAttemptTime\n const wasRapidClose = timeSinceConnect < RAPID_CLOSE_THRESHOLD && this.lastConnectAttemptTime > 0\n\n // EP605: Check if connection is idle (no commands for 1+ hour)\n const timeSinceLastCommand = Date.now() - this.lastCommandTime\n const isIdle = timeSinceLastCommand >= IDLE_THRESHOLD\n\n // Calculate base delay based on shutdown type and conditions\n let baseDelay: number\n if (this.isGracefulShutdown && this.reconnectAttempts < 3) {\n // Graceful shutdown: try reconnecting quickly (500ms, 1s, 2s)\n baseDelay = 500 * Math.pow(2, this.reconnectAttempts)\n } else if (wasRapidClose) {\n // EP648: Connection failed almost immediately - likely rate limited or auth error\n // Apply longer backoff to prevent spam\n baseDelay = Math.max(RAPID_CLOSE_BACKOFF, INITIAL_RECONNECT_DELAY * Math.pow(2, Math.min(this.reconnectAttempts, 6)))\n console.log(`[EpisodaClient] Rapid close detected (${timeSinceConnect}ms), applying ${baseDelay / 1000}s backoff`)\n } else if (isIdle) {\n // EP605: Use slower retry for idle connections (10 minutes)\n baseDelay = IDLE_RECONNECT_DELAY\n } else {\n // EP605: Use capped exponential backoff\n // After 6 attempts, we hit max delay and stay there\n const cappedAttempts = Math.min(this.reconnectAttempts, 6)\n baseDelay = INITIAL_RECONNECT_DELAY * Math.pow(2, cappedAttempts)\n }\n\n // Add randomization (±25%) to prevent thundering herd\n // When server restarts, all clients shouldn't hit it at the exact same time\n const jitter = baseDelay * 0.25 * (Math.random() * 2 - 1) // Random between -25% and +25%\n const maxDelay = isIdle ? IDLE_RECONNECT_DELAY : MAX_RECONNECT_DELAY\n const delay = Math.min(baseDelay + jitter, maxDelay)\n\n this.reconnectAttempts++\n const shutdownType = this.isGracefulShutdown ? 'graceful' : 'ungraceful'\n const idleStatus = isIdle ? ', idle' : ''\n const rapidStatus = wasRapidClose ? ', rapid-close' : ''\n\n // EP605: Only log first few attempts and then periodically\n if (this.reconnectAttempts <= 5 || this.reconnectAttempts % 10 === 0) {\n console.log(`[EpisodaClient] Reconnecting in ${Math.round(delay / 1000)}s (attempt ${this.reconnectAttempts}, ${shutdownType}${idleStatus}${rapidStatus})`)\n }\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 // EP605: Reset counters after successful reconnection\n console.log('[EpisodaClient] Reconnection successful, resetting retry counter')\n this.reconnectAttempts = 0\n this.isGracefulShutdown = false\n this.firstDisconnectTime = undefined\n this.rateLimitBackoffUntil = undefined // EP648: Clear rate limit on success\n }).catch(error => {\n console.error('[EpisodaClient] Reconnection failed:', error)\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 '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 \"name\": \"episoda\",\n \"version\": \"0.2.12\",\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 \"ws\": \"^8.18.0\",\n \"zod\": \"^4.0.10\"\n },\n \"devDependencies\": {\n \"@episoda/core\": \"*\",\n \"@types/node\": \"^20.11.24\",\n \"@types/semver\": \"7.7.1\",\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}\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 * 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 * @param projectId Supabase project ID\n * @param projectPath Absolute path to project\n * @returns The tracked project\n */\nexport function addProject(projectId: string, projectPath: string): 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 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 }\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 // Spawn daemon as detached process\n const child = spawn('node', [daemonScript], {\n detached: true, // Run independently of parent\n stdio: 'ignore', // Don't inherit stdio (prevents hanging)\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 } from '@episoda/core'\nimport { checkForUpdates, performBackgroundUpdate } from '../utils/update-checker'\n// EP812: Removed IdentityServer - browser identity now uses cookie-based pairing\nimport { handleFileRead, handleFileWrite, handleFileList, handleFileSearch, handleFileGrep, handleExec } from './handlers'\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 * 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 private shuttingDown = false\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 // 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 connected: 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 this.ipcServer.on('add-project', async (params: { projectId: string; projectPath: string }) => {\n const { projectId, projectPath } = params\n trackProject(projectId, projectPath)\n\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 return { success: false, connected: false, error: 'Connection established but not healthy' }\n }\n\n return { success: true, connected: true }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return { success: false, connected: false, error: errorMessage }\n }\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 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 isHealthy: this.isConnectionHealthy(p.path),\n }))\n\n const healthyCount = projects.filter(p => p.isHealthy).length\n const staleCount = projects.filter(p => p.inConnectionsMap && !p.inLiveConnections).length\n\n return {\n totalProjects: projects.length,\n healthyConnections: healthyCount,\n staleConnections: staleCount,\n projects,\n }\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 * 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 if (this.connections.has(projectPath)) {\n if (this.liveConnections.has(projectPath)) {\n console.log(`[Daemon] Already connected to ${projectPath}`)\n return\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\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 const result = await gitExecutor.execute(message.command as GitCommand, {\n cwd: projectPath\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 } 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: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 // 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\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\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 // 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 } catch (error) {\n console.error(`[Daemon] Failed to connect to ${projectId}:`, error)\n this.connections.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\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 const hooks = ['post-checkout', 'pre-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 * 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 // 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 // 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 } 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}\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 // Non-blocking - fail silently on network errors or timeouts\n return { currentVersion, latestVersion: currentVersion, updateAvailable: false }\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 * 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 * 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"],"mappings":"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","names":["exports","exports","GitExecutor","fs","exports","packageJson","exports","EpisodaClient","resolve","exports","exports","getConfigDir","loadConfig","saveConfig","fs","path","os","exports","exports","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","fs","path","execSync"]}
|