diff --git a/CLI/cmux.swift b/CLI/cmux.swift index a68ac6fd..d0c69360 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -9779,30 +9779,14 @@ struct CMUXCLI { } else { throw CLIError(message: "Neither bun nor npm found in PATH. Install oh-my-opencode manually: bunx oh-my-opencode install") } - let stdoutPipe = Pipe() - let stderrPipe = Pipe() - process.standardOutput = stdoutPipe - process.standardError = stderrPipe - FileHandle.standardError.write("Installing oh-my-opencode plugin...\n".data(using: .utf8)!) + // Show install output directly so the user sees progress (npm can take 30s+) + process.standardOutput = FileHandle.standardError + process.standardError = FileHandle.standardError + FileHandle.standardError.write("Installing oh-my-opencode plugin (this may take a minute on first run)...\n".data(using: .utf8)!) try process.run() - // Drain pipes concurrently to prevent deadlock from full buffers - var stderrData = Data() - let drainGroup = DispatchGroup() - drainGroup.enter() - DispatchQueue.global().async { - stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() - drainGroup.leave() - } - drainGroup.enter() - DispatchQueue.global().async { - _ = stdoutPipe.fileHandleForReading.readDataToEndOfFile() - drainGroup.leave() - } - drainGroup.wait() process.waitUntilExit() if process.terminationStatus != 0 { - let errText = String(data: stderrData, encoding: .utf8) ?? "" - throw CLIError(message: "Failed to install oh-my-opencode: \(errText)") + throw CLIError(message: "Failed to install oh-my-opencode. Try manually: npm install -g oh-my-opencode") } FileHandle.standardError.write("oh-my-opencode plugin installed\n".data(using: .utf8)!) // Re-create symlink if we installed into user dir @@ -9899,9 +9883,6 @@ struct CMUXCLI { socketPath: String, explicitPassword: String? ) throws { - // Ensure oh-my-opencode plugin is registered and installed - try omoEnsurePlugin() - let processEnvironment = ProcessInfo.processInfo.environment var launcherEnvironment = processEnvironment launcherEnvironment["CMUX_SOCKET_PATH"] = socketPath @@ -9910,13 +9891,31 @@ struct CMUXCLI { !explicitPassword.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { launcherEnvironment["CMUX_SOCKET_PASSWORD"] = explicitPassword } + + // Check for opencode before doing expensive plugin setup + let openCodeExecutablePath = resolveOpenCodeExecutable(searchPath: launcherEnvironment["PATH"]) + if openCodeExecutablePath == nil { + let checkProcess = Process() + checkProcess.executableURL = URL(fileURLWithPath: "/usr/bin/which") + checkProcess.arguments = ["opencode"] + checkProcess.standardOutput = Pipe() + checkProcess.standardError = Pipe() + try? checkProcess.run() + checkProcess.waitUntilExit() + if checkProcess.terminationStatus != 0 { + throw CLIError(message: "opencode is not installed. Install it first:\n npm install -g opencode-ai\n # or\n bun install -g opencode-ai\n\nThen run: cmux omo") + } + } + + // Ensure oh-my-opencode plugin is registered and installed + try omoEnsurePlugin() + let shimDirectory = try createOMOShimDirectory() let executablePath = resolvedExecutableURL()?.path ?? (args.first ?? "cmux") let focusedContext = tmuxCompatFocusedContext( processEnvironment: launcherEnvironment, explicitPassword: explicitPassword ) - let openCodeExecutablePath = resolveOpenCodeExecutable(searchPath: launcherEnvironment["PATH"]) configureOMOEnvironment( processEnvironment: launcherEnvironment, shimDirectory: shimDirectory, @@ -9949,7 +9948,7 @@ struct CMUXCLI { execvp("opencode", &argv) } let code = errno - throw CLIError(message: "Failed to launch opencode: \(String(cString: strerror(code)))") + throw CLIError(message: "Failed to launch opencode: \(String(cString: strerror(code)))\n\nIs opencode installed? Install with:\n npm install -g opencode-ai") } private func runClaudeTeamsTmuxCompat(