@@ -1413,6 +1413,7 @@ struct CMUXCLI {
14131413 // so help text is available even when cmux is not running.
14141414 if command != " __tmux-compat " ,
14151415 command != " claude-teams " ,
1416+ command != " omo " ,
14161417 ( commandArgs. contains ( " --help " ) || commandArgs. contains ( " -h " ) ) {
14171418 if dispatchSubcommandHelp ( command: command, commandArgs: commandArgs) {
14181419 return
@@ -1463,6 +1464,15 @@ struct CMUXCLI {
14631464 return
14641465 }
14651466
1467+ if command == " omo " {
1468+ try runOMO (
1469+ commandArgs: commandArgs,
1470+ socketPath: resolvedSocketPath,
1471+ explicitPassword: socketPasswordArg
1472+ )
1473+ return
1474+ }
1475+
14661476 let client = SocketClient ( path: resolvedSocketPath)
14671477 if resolvedSocketPath != socketPath {
14681478 cliTelemetry. breadcrumb (
@@ -6073,6 +6083,29 @@ struct CMUXCLI {
60736083 cmux claude-teams --continue
60746084 cmux claude-teams --model sonnet
60756085 """ )
6086+ case " omo " :
6087+ return String ( localized: " cli.omo.usage " , defaultValue: """
6088+ Usage: cmux omo [opencode-args...]
6089+
6090+ Launch OpenCode with oh-my-openagent in a cmux-aware environment.
6091+
6092+ oh-my-openagent orchestrates multiple AI models as specialized agents in
6093+ parallel. This command sets up a tmux shim so agent panes become native
6094+ cmux splits with sidebar metadata and notifications.
6095+
6096+ This command:
6097+ - sets a tmux-like environment so oh-my-openagent uses cmux splits
6098+ - prepends a private tmux shim to PATH
6099+ - forwards all remaining arguments to opencode
6100+
6101+ The tmux shim translates tmux window/pane commands into cmux workspace
6102+ and split operations in the current cmux session.
6103+
6104+ Examples:
6105+ cmux omo
6106+ cmux omo --continue
6107+ cmux omo --model claude-sonnet-4-6
6108+ """ )
60766109 case " identify " :
60776110 return """
60786111 Usage: cmux identify [--workspace <id|ref|index>] [--surface <id|ref|index>] [--no-caller]
@@ -9380,6 +9413,132 @@ struct CMUXCLI {
93809413 throw CLIError ( message: " Failed to launch claude: \( String ( cString: strerror ( code) ) ) " )
93819414 }
93829415
9416+ // MARK: - cmux omo (OpenCode + oh-my-openagent)
9417+
9418+ private func resolveOpenCodeExecutable( searchPath: String ? ) -> String ? {
9419+ let entries = searchPath? . split ( separator: " : " ) . map ( String . init) ?? [ ]
9420+ for entry in entries where !entry. isEmpty {
9421+ let candidate = URL ( fileURLWithPath: entry, isDirectory: true )
9422+ . appendingPathComponent ( " opencode " , isDirectory: false )
9423+ . path
9424+ guard FileManager . default. isExecutableFile ( atPath: candidate) else { continue }
9425+ return candidate
9426+ }
9427+ return nil
9428+ }
9429+
9430+ private func createOMOShimDirectory( ) throws -> URL {
9431+ let homePath = ProcessInfo . processInfo. environment [ " HOME " ] ?? NSHomeDirectory ( )
9432+ let root = URL ( fileURLWithPath: homePath, isDirectory: true )
9433+ . appendingPathComponent ( " .cmuxterm " , isDirectory: true )
9434+ . appendingPathComponent ( " omo-bin " , isDirectory: true )
9435+ try FileManager . default. createDirectory ( at: root, withIntermediateDirectories: true , attributes: nil )
9436+ let tmuxURL = root. appendingPathComponent ( " tmux " , isDirectory: false )
9437+ let script = """
9438+ #!/usr/bin/env bash
9439+ set -euo pipefail
9440+ exec " ${CMUX_OMO_CMUX_BIN:-cmux} " __tmux-compat " $@ "
9441+ """
9442+ let normalizedScript = script. trimmingCharacters ( in: . whitespacesAndNewlines)
9443+ let existingScript = try ? String ( contentsOf: tmuxURL, encoding: . utf8)
9444+ if existingScript? . trimmingCharacters ( in: . whitespacesAndNewlines) != normalizedScript {
9445+ try script. write ( to: tmuxURL, atomically: false , encoding: . utf8)
9446+ }
9447+ try FileManager . default. setAttributes ( [ . posixPermissions: 0o755 ] , ofItemAtPath: tmuxURL. path)
9448+ return root
9449+ }
9450+
9451+ private func configureOMOEnvironment(
9452+ processEnvironment: [ String : String ] ,
9453+ shimDirectory: URL ,
9454+ executablePath: String ,
9455+ socketPath: String ,
9456+ explicitPassword: String ? ,
9457+ focusedContext: ClaudeTeamsFocusedContext ?
9458+ ) {
9459+ let updatedPath = prependPathEntries (
9460+ [ shimDirectory. path] ,
9461+ to: processEnvironment [ " PATH " ]
9462+ )
9463+ let fakeTmuxValue : String = {
9464+ if let focusedContext {
9465+ let windowToken = focusedContext. windowId ?? focusedContext. workspaceId
9466+ return " /tmp/cmux-omo/ \( focusedContext. workspaceId) , \( windowToken) , \( focusedContext. paneHandle) "
9467+ }
9468+ return processEnvironment [ " TMUX " ] ?? " /tmp/cmux-omo/default,0,0 "
9469+ } ( )
9470+ let fakeTmuxPane = focusedContext. map { " % \( $0. paneHandle) " }
9471+ ?? processEnvironment [ " TMUX_PANE " ]
9472+ ?? " %1 "
9473+ let fakeTerm = processEnvironment [ " CMUX_OMO_TERM " ] ?? " screen-256color "
9474+
9475+ setenv ( " CMUX_OMO_CMUX_BIN " , executablePath, 1 )
9476+ setenv ( " PATH " , updatedPath, 1 )
9477+ setenv ( " TMUX " , fakeTmuxValue, 1 )
9478+ setenv ( " TMUX_PANE " , fakeTmuxPane, 1 )
9479+ setenv ( " TERM " , fakeTerm, 1 )
9480+ setenv ( " CMUX_SOCKET_PATH " , socketPath, 1 )
9481+ setenv ( " CMUX_SOCKET " , socketPath, 1 )
9482+ if let explicitPassword,
9483+ !explicitPassword. trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty {
9484+ setenv ( " CMUX_SOCKET_PASSWORD " , explicitPassword, 1 )
9485+ }
9486+ unsetenv ( " TERM_PROGRAM " )
9487+ if let focusedContext {
9488+ setenv ( " CMUX_WORKSPACE_ID " , focusedContext. workspaceId, 1 )
9489+ if let surfaceId = focusedContext. surfaceId, !surfaceId. isEmpty {
9490+ setenv ( " CMUX_SURFACE_ID " , surfaceId, 1 )
9491+ }
9492+ }
9493+ }
9494+
9495+ private func runOMO(
9496+ commandArgs: [ String ] ,
9497+ socketPath: String ,
9498+ explicitPassword: String ?
9499+ ) throws {
9500+ let processEnvironment = ProcessInfo . processInfo. environment
9501+ var launcherEnvironment = processEnvironment
9502+ launcherEnvironment [ " CMUX_SOCKET_PATH " ] = socketPath
9503+ launcherEnvironment [ " CMUX_SOCKET " ] = socketPath
9504+ if let explicitPassword,
9505+ !explicitPassword. trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty {
9506+ launcherEnvironment [ " CMUX_SOCKET_PASSWORD " ] = explicitPassword
9507+ }
9508+ let shimDirectory = try createOMOShimDirectory ( )
9509+ let executablePath = resolvedExecutableURL ( ) ? . path ?? ( args. first ?? " cmux " )
9510+ let focusedContext = claudeTeamsFocusedContext (
9511+ processEnvironment: launcherEnvironment,
9512+ explicitPassword: explicitPassword
9513+ )
9514+ let openCodeExecutablePath = resolveOpenCodeExecutable ( searchPath: launcherEnvironment [ " PATH " ] )
9515+ configureOMOEnvironment (
9516+ processEnvironment: launcherEnvironment,
9517+ shimDirectory: shimDirectory,
9518+ executablePath: executablePath,
9519+ socketPath: socketPath,
9520+ explicitPassword: explicitPassword,
9521+ focusedContext: focusedContext
9522+ )
9523+
9524+ let launchPath = openCodeExecutablePath ?? " opencode "
9525+ var argv = ( [ launchPath] + commandArgs) . map { strdup ( $0) }
9526+ defer {
9527+ for item in argv {
9528+ free ( item)
9529+ }
9530+ }
9531+ argv. append ( nil )
9532+
9533+ if openCodeExecutablePath != nil {
9534+ execv ( launchPath, & argv)
9535+ } else {
9536+ execvp ( " opencode " , & argv)
9537+ }
9538+ let code = errno
9539+ throw CLIError ( message: " Failed to launch opencode: \( String ( cString: strerror ( code) ) ) " )
9540+ }
9541+
93839542 private func runClaudeTeamsTmuxCompat(
93849543 commandArgs: [ String ] ,
93859544 client: SocketClient ,
@@ -11361,6 +11520,7 @@ struct CMUXCLI {
1136111520 feedback [--email <email> --body <text> [--image <path> ...]]
1136211521 themes [list|set|clear]
1136311522 claude-teams [claude-args...]
11523+ omo [opencode-args...]
1136411524 ping
1136511525 version
1136611526 capabilities
0 commit comments