diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 069cb955a..f2817dbff 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -10414,7 +10414,7 @@ struct CMUXCLI { } } - private static let omoPluginName = "oh-my-opencode" + private static let omoPluginName = "oh-my-openagent" private func resolveExecutableInPath(_ name: String) -> String? { let entries = ProcessInfo.processInfo.environment["PATH"]?.split(separator: ":").map(String.init) ?? [] @@ -10456,7 +10456,7 @@ struct CMUXCLI { // Keep the shadow package isolated from stale/yanked pins in the user's // opencode package.json. bun will update this manifest with the resolved - // oh-my-opencode version when installation succeeds. + // oh-my-openagent version when installation succeeds. let packageManifest: [String: Any] = [ "dependencies": [ Self.omoPluginName: "latest" @@ -10600,7 +10600,7 @@ struct CMUXCLI { return "4096" } - /// Creates a shadow config directory that layers oh-my-opencode on top of the user's + /// Creates a shadow config directory that layers oh-my-openagent on top of the user's /// existing opencode config without modifying the original. Sets OPENCODE_CONFIG_DIR /// to point at the shadow directory. private func omoEnsurePlugin() throws { @@ -10625,6 +10625,8 @@ struct CMUXCLI { } var plugins = (config["plugin"] as? [String]) ?? [] + // Remove legacy oh-my-opencode entries to avoid duplicate plugin registration + plugins.removeAll { $0 == "oh-my-opencode" || $0.hasPrefix("oh-my-opencode@") } let alreadyPresent = plugins.contains { $0 == Self.omoPluginName || $0.hasPrefix("\(Self.omoPluginName)@") } @@ -10650,12 +10652,18 @@ struct CMUXCLI { try? fm.removeItem(at: shadowBunLockURL) } - // Copy oh-my-opencode plugin config (jsonc) if the user has one - for filename in ["oh-my-opencode.json", "oh-my-opencode.jsonc"] { - let userFile = userDir.appendingPathComponent(filename) - let shadowFile = shadowDir.appendingPathComponent(filename) - if fm.fileExists(atPath: userFile.path) && !fm.fileExists(atPath: shadowFile.path) { + // Copy oh-my-openagent plugin config if the user has one (fall back to legacy oh-my-opencode name) + for ext in ["json", "jsonc"] { + let newName = "oh-my-openagent.\(ext)" + let legacyName = "oh-my-opencode.\(ext)" + let shadowFile = shadowDir.appendingPathComponent(newName) + guard !fm.fileExists(atPath: shadowFile.path) else { continue } + let userFile = userDir.appendingPathComponent(newName) + let legacyFile = userDir.appendingPathComponent(legacyName) + if fm.fileExists(atPath: userFile.path) { try fm.createSymbolicLink(at: shadowFile, withDestinationURL: userFile) + } else if fm.fileExists(atPath: legacyFile.path) { + try fm.createSymbolicLink(at: shadowFile, withDestinationURL: legacyFile) } } @@ -10664,7 +10672,7 @@ struct CMUXCLI { if !fm.fileExists(atPath: pluginPackageDir.path) { let installDir = shadowDir if let bunPath = resolveExecutableInPath("bun") { - FileHandle.standardError.write("Installing oh-my-opencode plugin (this may take a minute on first run)...\n".data(using: .utf8)!) + FileHandle.standardError.write("Installing oh-my-openagent plugin (this may take a minute on first run)...\n".data(using: .utf8)!) let installArguments = ["add", Self.omoPluginName] let firstAttemptStatus = try omoRunPackageInstall( executablePath: bunPath, @@ -10672,7 +10680,7 @@ struct CMUXCLI { currentDirectoryURL: installDir ) if firstAttemptStatus != 0 { - FileHandle.standardError.write("Retrying oh-my-opencode install with a clean shadow package state...\n".data(using: .utf8)!) + FileHandle.standardError.write("Retrying oh-my-openagent install with a clean shadow package state...\n".data(using: .utf8)!) try? fm.removeItem(at: shadowBunLockURL) try? fm.removeItem(at: shadowNodeModules) try omoEnsureShadowNodeModulesSymlink(shadowNodeModules: shadowNodeModules, userNodeModules: userNodeModules) @@ -10682,37 +10690,39 @@ struct CMUXCLI { currentDirectoryURL: installDir ) if retryStatus != 0 { - throw CLIError(message: "Failed to install oh-my-opencode. Try manually: npm install -g oh-my-opencode") + throw CLIError(message: "Failed to install oh-my-openagent. Try manually: npm install -g oh-my-openagent") } } } else if let npmPath = resolveExecutableInPath("npm") { - FileHandle.standardError.write("Installing oh-my-opencode plugin (this may take a minute on first run)...\n".data(using: .utf8)!) + FileHandle.standardError.write("Installing oh-my-openagent plugin (this may take a minute on first run)...\n".data(using: .utf8)!) let status = try omoRunPackageInstall( executablePath: npmPath, arguments: ["install", Self.omoPluginName], currentDirectoryURL: installDir ) if status != 0 { - throw CLIError(message: "Failed to install oh-my-opencode. Try manually: npm install -g oh-my-opencode") + throw CLIError(message: "Failed to install oh-my-openagent. Try manually: npm install -g oh-my-openagent") } } else { - throw CLIError(message: "Neither bun nor npm found in PATH. Install oh-my-opencode manually: bunx oh-my-opencode install") + throw CLIError(message: "Neither bun nor npm found in PATH. Install oh-my-openagent manually: bunx oh-my-openagent install") } - FileHandle.standardError.write("oh-my-opencode plugin installed\n".data(using: .utf8)!) + FileHandle.standardError.write("oh-my-openagent plugin installed\n".data(using: .utf8)!) } - // Ensure tmux mode is enabled in oh-my-opencode config. + // Ensure tmux mode is enabled in oh-my-openagent config. // Without this, the TmuxSessionManager won't spawn visual panes even though // $TMUX is set (tmux.enabled defaults to false). - let omoConfigURL = shadowDir.appendingPathComponent("oh-my-opencode.json") + let omoConfigURL = shadowDir.appendingPathComponent("oh-my-openagent.json") var omoConfig: [String: Any] if let data = try? Data(contentsOf: omoConfigURL), let existing = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { omoConfig = existing } else { - // Check if user has a config we symlinked, read from source - let userOmoConfig = userDir.appendingPathComponent("oh-my-opencode.json") - if let data = try? Data(contentsOf: userOmoConfig), + // Check if user has a config we symlinked, read from source (new name first, fall back to legacy) + let userOmoConfig = userDir.appendingPathComponent("oh-my-openagent.json") + let legacyOmoConfig = userDir.appendingPathComponent("oh-my-opencode.json") + let userConfigData = (try? Data(contentsOf: userOmoConfig)) ?? (try? Data(contentsOf: legacyOmoConfig)) + if let data = userConfigData, let existing = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { omoConfig = existing // Remove the symlink so we can write our own copy @@ -10809,7 +10819,7 @@ struct CMUXCLI { } } - // Ensure oh-my-opencode plugin is registered and installed + // Ensure oh-my-openagent plugin is registered and installed try omoEnsurePlugin() let shimDirectory = try createOMOShimDirectory() diff --git a/daemon/remote/cmd/cmuxd-remote/agent_launch.go b/daemon/remote/cmd/cmuxd-remote/agent_launch.go index 2499d59b5..d75537a49 100644 --- a/daemon/remote/cmd/cmuxd-remote/agent_launch.go +++ b/daemon/remote/cmd/cmuxd-remote/agent_launch.go @@ -86,7 +86,7 @@ func runOMORelay(socketPath string, args []string, refreshAddr func() string) in return 1 } - // Ensure oh-my-opencode plugin is set up + // Ensure oh-my-openagent plugin is set up if err := omoEnsurePlugin(originalPath); err != nil { fmt.Fprintf(os.Stderr, "cmux omo: plugin setup: %v\n", err) return 1 @@ -407,9 +407,9 @@ func configureAgentEnvironment(cfg agentConfig) { } } -// --- oh-my-opencode plugin setup --- +// --- oh-my-openagent plugin setup --- -const omoPluginName = "oh-my-opencode" +const omoPluginName = "oh-my-openagent" func omoUserConfigDir() string { home, _ := os.UserHomeDir() @@ -422,7 +422,7 @@ func omoShadowConfigDir() string { } // omoEnsurePlugin creates a shadow config directory that layers the -// oh-my-opencode plugin on top of the user's opencode config, installs +// oh-my-openagent plugin on top of the user's opencode config, installs // the plugin if needed, and sets OPENCODE_CONFIG_DIR. func omoEnsurePlugin(searchPath string) error { userDir := omoUserConfigDir() @@ -445,7 +445,7 @@ func omoEnsurePlugin(searchPath string) error { config = map[string]any{} } - // Add oh-my-opencode to the plugins list + // Add oh-my-openagent to the plugins list var plugins []string if raw, ok := config["plugin"].([]any); ok { for _, p := range raw { @@ -454,6 +454,14 @@ func omoEnsurePlugin(searchPath string) error { } } } + // Remove legacy oh-my-opencode entries to avoid duplicate plugin registration + var cleaned []string + for _, p := range plugins { + if p != "oh-my-opencode" && !strings.HasPrefix(p, "oh-my-opencode@") { + cleaned = append(cleaned, p) + } + } + plugins = cleaned alreadyPresent := false for _, p := range plugins { if p == omoPluginName || strings.HasPrefix(p, omoPluginName+"@") { @@ -494,12 +502,20 @@ func omoEnsurePlugin(searchPath string) error { } } - // Symlink oh-my-opencode config files - for _, filename := range []string{"oh-my-opencode.json", "oh-my-opencode.jsonc"} { - userFile := filepath.Join(userDir, filename) - shadowFile := filepath.Join(shadowDir, filename) - if fileExists(userFile) && !fileExists(shadowFile) { + // Symlink oh-my-openagent config files (fall back to legacy oh-my-opencode name) + for _, ext := range []string{"json", "jsonc"} { + newName := "oh-my-openagent." + ext + legacyName := "oh-my-opencode." + ext + shadowFile := filepath.Join(shadowDir, newName) + if fileExists(shadowFile) { + continue + } + userFile := filepath.Join(userDir, newName) + legacyFile := filepath.Join(userDir, legacyName) + if fileExists(userFile) { os.Symlink(userFile, shadowFile) + } else if fileExists(legacyFile) { + os.Symlink(legacyFile, shadowFile) } } @@ -516,10 +532,10 @@ func omoEnsurePlugin(searchPath string) error { bunPath := findExecutableInPath("bun", searchPath, "") npmPath := findExecutableInPath("npm", searchPath, "") if bunPath == "" && npmPath == "" { - return fmt.Errorf("neither bun nor npm found in PATH. Install oh-my-opencode manually: bunx oh-my-opencode install") + return fmt.Errorf("neither bun nor npm found in PATH. Install oh-my-openagent manually: bunx oh-my-openagent install") } - fmt.Fprintf(os.Stderr, "Installing oh-my-opencode plugin...\n") + fmt.Fprintf(os.Stderr, "Installing oh-my-openagent plugin...\n") var cmd *exec.Cmd if bunPath != "" { cmd = exec.Command(bunPath, "add", omoPluginName) @@ -530,9 +546,9 @@ func omoEnsurePlugin(searchPath string) error { cmd.Stdout = os.Stderr cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to install oh-my-opencode: %v\nTry manually: npm install -g oh-my-opencode", err) + return fmt.Errorf("failed to install oh-my-openagent: %v\nTry manually: npm install -g oh-my-openagent", err) } - fmt.Fprintf(os.Stderr, "oh-my-opencode plugin installed\n") + fmt.Fprintf(os.Stderr, "oh-my-openagent plugin installed\n") // Re-create symlink if we installed into user dir if installDir == userDir && !fileExists(shadowNodeModules) { @@ -540,18 +556,22 @@ func omoEnsurePlugin(searchPath string) error { } } - // Configure oh-my-opencode.json with tmux settings - omoConfigPath := filepath.Join(shadowDir, "oh-my-opencode.json") + // Configure oh-my-openagent.json with tmux settings + omoConfigPath := filepath.Join(shadowDir, "oh-my-openagent.json") var omoConfig map[string]any if data, err := os.ReadFile(omoConfigPath); err == nil { json.Unmarshal(data, &omoConfig) } if omoConfig == nil { - // Check if user had one we symlinked - userOmoConfig := filepath.Join(userDir, "oh-my-opencode.json") + // Check if user had one we symlinked (new name first, fall back to legacy) + userOmoConfig := filepath.Join(userDir, "oh-my-openagent.json") + legacyOmoConfig := filepath.Join(userDir, "oh-my-opencode.json") if data, err := os.ReadFile(userOmoConfig); err == nil { json.Unmarshal(data, &omoConfig) - os.Remove(omoConfigPath) // Remove symlink so we can write our own copy + os.Remove(omoConfigPath) + } else if data, err := os.ReadFile(legacyOmoConfig); err == nil { + json.Unmarshal(data, &omoConfig) + os.Remove(omoConfigPath) } } if omoConfig == nil {