From f54d212176637082d2f761309dc3f9f48843635a Mon Sep 17 00:00:00 2001 From: Nico Ritschel Date: Wed, 4 Mar 2026 20:47:49 -0800 Subject: [PATCH] Add opencode wrapper script for PATH resolution in sidebar launches --- .../AgentStatus/AgentHookInstaller.swift | 45 ++++++++++++++++++- .../AgentStatus/AgentStatusPaths.swift | 4 ++ .../Worktrunk/WorktrunkPreferences.swift | 3 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/macos/Sources/Features/Worktrunk/AgentStatus/AgentHookInstaller.swift b/macos/Sources/Features/Worktrunk/AgentStatus/AgentHookInstaller.swift index be8eadde12..a075bb172c 100644 --- a/macos/Sources/Features/Worktrunk/AgentStatus/AgentHookInstaller.swift +++ b/macos/Sources/Features/Worktrunk/AgentStatus/AgentHookInstaller.swift @@ -58,6 +58,13 @@ enum AgentHookInstaller { content: buildCopilotCliWrapper() ) + ensureFile( + url: AgentStatusPaths.opencodeWrapperPath, + mode: 0o755, + marker: wrapperMarker, + content: buildOpenCodeWrapper() + ) + ensureFile( url: AgentStatusPaths.opencodeGlobalPluginPath, mode: 0o644, @@ -209,7 +216,7 @@ enum AgentHookInstaller { if [ -x /usr/libexec/path_helper ]; then eval "$(/usr/libexec/path_helper -s)" 2>/dev/null fi - for _d in "$HOME/.local/bin" "$HOME/.bun/bin" "/opt/homebrew/bin" "/opt/homebrew/sbin" "/usr/local/bin" "$HOME/.cargo/bin"; do + for _d in "$HOME/.local/bin" "$HOME/.bun/bin" "$HOME/.opencode/bin" "/opt/homebrew/bin" "/opt/homebrew/sbin" "/usr/local/bin" "$HOME/.cargo/bin"; do if [ -d "$_d" ]; then case ":$PATH:" in *":$_d:"*) ;; @@ -356,6 +363,42 @@ enum AgentHookInstaller { """ } + private static func buildOpenCodeWrapper() -> String { + let binDir = AgentStatusPaths.binDir.path + return """ + #!/bin/bash + \(wrapperMarker) + # Wrapper for OpenCode: ensures PATH includes common install locations. + + \(pathAugmentSnippet()) + + find_real_binary() { + local name="$1" + local IFS=: + for dir in $PATH; do + [ -z "$dir" ] && continue + dir="${dir%/}" + if [ "$dir" = "\(binDir)" ]; then + continue + fi + if [ -x "$dir/$name" ] && [ ! -d "$dir/$name" ]; then + printf "%s\\n" "$dir/$name" + return 0 + fi + done + return 1 + } + + REAL_BIN="$(find_real_binary "opencode")" + if [ -z "$REAL_BIN" ]; then + echo "Ghostree: opencode not found in PATH. Install it and ensure it is on PATH, then retry." >&2 + exit 127 + fi + + exec "$REAL_BIN" "$@" + """ + } + private static func buildOpenCodePlugin() -> String { let marker = AgentStatusPaths.opencodePluginMarker return """ diff --git a/macos/Sources/Features/Worktrunk/AgentStatus/AgentStatusPaths.swift b/macos/Sources/Features/Worktrunk/AgentStatus/AgentStatusPaths.swift index f011407360..7ddcc738fa 100644 --- a/macos/Sources/Features/Worktrunk/AgentStatus/AgentStatusPaths.swift +++ b/macos/Sources/Features/Worktrunk/AgentStatus/AgentStatusPaths.swift @@ -50,6 +50,10 @@ enum AgentStatusPaths { binDir.appendingPathComponent("copilot") } + static var opencodeWrapperPath: URL { + binDir.appendingPathComponent("opencode") + } + static var opencodePluginMarker: String { "// Ghostree opencode plugin v5" } /** @see https://opencode.ai/docs/plugins */ diff --git a/macos/Sources/Features/Worktrunk/WorktrunkPreferences.swift b/macos/Sources/Features/Worktrunk/WorktrunkPreferences.swift index 5e65534f62..4de9ec1c52 100644 --- a/macos/Sources/Features/Worktrunk/WorktrunkPreferences.swift +++ b/macos/Sources/Features/Worktrunk/WorktrunkPreferences.swift @@ -56,7 +56,8 @@ enum WorktrunkAgent: String, CaseIterable, Identifiable { private static func searchPaths(excluding excludedPath: String) -> [String] { var paths: [String] = [] - let prefix = ["/opt/homebrew/bin", "/usr/local/bin"] + let home = FileManager.default.homeDirectoryForCurrentUser.path + let prefix = ["/opt/homebrew/bin", "/usr/local/bin", "\(home)/.opencode/bin"] let existingPath = ProcessInfo.processInfo.environment["PATH"] ?? "" let existingComponents = existingPath.split(separator: ":").map(String.init)