|
| 1 | +import { execSync } from "node:child_process"; |
1 | 2 | import { randomUUID } from "node:crypto"; |
| 3 | +import { existsSync } from "node:fs"; |
| 4 | +import { homedir } from "node:os"; |
| 5 | +import { join } from "node:path"; |
2 | 6 | import { Agent, PermissionMode } from "@posthog/agent"; |
3 | 7 | import { type BrowserWindow, type IpcMainInvokeEvent, ipcMain } from "electron"; |
4 | 8 |
|
@@ -36,6 +40,50 @@ function resolvePermissionMode( |
36 | 40 | return (match as PermissionMode | undefined) ?? PermissionMode.ACCEPT_EDITS; |
37 | 41 | } |
38 | 42 |
|
| 43 | +function findClaudeExecutable(): string | undefined { |
| 44 | + // Common installation locations based on Claude Code docs |
| 45 | + const commonPaths = [ |
| 46 | + join(homedir(), ".local", "bin", "claude"), // Native installer location |
| 47 | + join(homedir(), ".claude", "local", "claude"), // Migrated local installation |
| 48 | + join(homedir(), ".volta", "bin", "claude"), // Volta (Node version manager) |
| 49 | + join(homedir(), ".nvm", "current", "bin", "claude"), // nvm |
| 50 | + "/opt/homebrew/bin/claude", // Homebrew on Apple Silicon |
| 51 | + "/usr/local/bin/claude", // Homebrew on Intel Mac / apt on Linux |
| 52 | + "/usr/bin/claude", // System installation |
| 53 | + ]; |
| 54 | + |
| 55 | + // Add npm global installation paths |
| 56 | + try { |
| 57 | + const npmPrefix = execSync("npm config get prefix", { |
| 58 | + encoding: "utf-8", |
| 59 | + }).trim(); |
| 60 | + if (npmPrefix) { |
| 61 | + commonPaths.push(join(npmPrefix, "bin", "claude")); |
| 62 | + } |
| 63 | + } catch { |
| 64 | + // npm not available or failed, continue |
| 65 | + } |
| 66 | + |
| 67 | + // Check common paths first |
| 68 | + for (const path of commonPaths) { |
| 69 | + if (existsSync(path)) { |
| 70 | + return path; |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + // Fall back to using 'which' if available |
| 75 | + try { |
| 76 | + const path = execSync("which claude", { encoding: "utf-8" }).trim(); |
| 77 | + if (path && existsSync(path)) { |
| 78 | + return path; |
| 79 | + } |
| 80 | + } catch { |
| 81 | + // which command failed, continue |
| 82 | + } |
| 83 | + |
| 84 | + return undefined; |
| 85 | +} |
| 86 | + |
39 | 87 | export function registerAgentIpc( |
40 | 88 | taskControllers: Map<string, TaskController>, |
41 | 89 | getMainWindow: () => BrowserWindow | null, |
@@ -171,13 +219,21 @@ export function registerAgentIpc( |
171 | 219 |
|
172 | 220 | const mcpOverrides = {}; |
173 | 221 |
|
| 222 | + const claudePath = findClaudeExecutable(); |
| 223 | + if (!claudePath) { |
| 224 | + throw new Error( |
| 225 | + "Claude Code executable not found in PATH. Please install Claude Code CLI.", |
| 226 | + ); |
| 227 | + } |
| 228 | + |
174 | 229 | await agent.runWorkflow(posthogTaskId, workflowId, { |
175 | 230 | repositoryPath: repoPath, |
176 | 231 | permissionMode: resolvedPermission, |
177 | 232 | autoProgress: autoProgress ?? true, |
178 | 233 | queryOverrides: { |
179 | 234 | abortController, |
180 | 235 | ...(model ? { model } : {}), |
| 236 | + pathToClaudeCodeExecutable: claudePath, |
181 | 237 | stderr: forwardClaudeStderr, |
182 | 238 | env: envOverrides, |
183 | 239 | mcpServers: mcpOverrides, |
|
0 commit comments