Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1733,12 +1733,13 @@ struct CMUXCLI {
case "set-status":
let (icon, r1) = parseOption(commandArgs, name: "--icon")
let (color, r2) = parseOption(r1, name: "--color")
let (wsFlag, r3) = parseOption(r2, name: "--workspace")
guard r3.count >= 2 else {
let (pid, r3) = parseOption(r2, name: "--pid")
let (wsFlag, r4) = parseOption(r3, name: "--workspace")
guard r4.count >= 2 else {
throw CLIError(message: "set-status requires <key> and <value>")
}
let key = r3[0]
let value = r3.dropFirst().joined(separator: " ")
let key = r4[0]
let value = r4.dropFirst().joined(separator: " ")
guard !value.isEmpty else {
throw CLIError(message: "set-status requires a non-empty value")
}
Expand All @@ -1747,6 +1748,7 @@ struct CMUXCLI {
var socketCmd = "set_status \(key) \(socketQuote(value))"
if let icon { socketCmd += " --icon=\(socketQuote(icon))" }
if let color { socketCmd += " --color=\(socketQuote(color))" }
if let pid { socketCmd += " --pid=\(socketQuote(pid))" }
socketCmd += " --tab=\(wsId)"
let response = try sendV1Command(socketCmd, client: client)
print(response)
Expand Down Expand Up @@ -5252,6 +5254,7 @@ struct CMUXCLI {
Flags:
--icon <name> Icon name (e.g. "sparkle", "hammer")
--color <#hex> Pill color (e.g. "#ff9500")
--pid <pid> Track a process for stale-pill cleanup
--workspace <id|ref> Target workspace (default: $CMUX_WORKSPACE_ID)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Example:
Expand Down
8 changes: 8 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
B900000BA1B2C3D4E5F60719 /* cmux in Copy CLI */ = {isa = PBXBuildFile; fileRef = B9000004A1B2C3D4E5F60719 /* cmux */; };
C1ADE00002A1B2C3D4E5F719 /* claude in Copy CLI */ = {isa = PBXBuildFile; fileRef = C1ADE00001A1B2C3D4E5F719 /* claude */; };
D1BEF00002A1B2C3D4E5F719 /* open in Copy CLI */ = {isa = PBXBuildFile; fileRef = D1BEF00001A1B2C3D4E5F719 /* open */; };
E1A0D30002A1B2C3D4E5F719 /* opencode in Copy CLI */ = {isa = PBXBuildFile; fileRef = E1A0D30001A1B2C3D4E5F719 /* opencode */; };
E1A0D30004A1B2C3D4E5F719 /* opencode-cmux-plugin.js in Copy CLI */ = {isa = PBXBuildFile; fileRef = E1A0D30003A1B2C3D4E5F719 /* opencode-cmux-plugin.js */; };
84E00D47E4584162AE53BC8D /* xterm-ghostty in Resources */ = {isa = PBXBuildFile; fileRef = B2E7294509CC42FE9191870E /* xterm-ghostty */; };
A5002000 /* THIRD_PARTY_LICENSES.md in Resources */ = {isa = PBXBuildFile; fileRef = A5002001 /* THIRD_PARTY_LICENSES.md */; };
B9000012A1B2C3D4E5F60719 /* AutomationSocketUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000011A1B2C3D4E5F60719 /* AutomationSocketUITests.swift */; };
Expand Down Expand Up @@ -121,6 +123,8 @@
B900000BA1B2C3D4E5F60719 /* cmux in Copy CLI */,
C1ADE00002A1B2C3D4E5F719 /* claude in Copy CLI */,
D1BEF00002A1B2C3D4E5F719 /* open in Copy CLI */,
E1A0D30002A1B2C3D4E5F719 /* opencode in Copy CLI */,
E1A0D30004A1B2C3D4E5F719 /* opencode-cmux-plugin.js in Copy CLI */,
);
name = "Copy CLI";
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -218,6 +222,8 @@
B2E7294509CC42FE9191870E /* xterm-ghostty */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty/terminfo/78/xterm-ghostty"; sourceTree = "<group>"; };
C1ADE00001A1B2C3D4E5F719 /* claude */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "Resources/bin/claude"; sourceTree = SOURCE_ROOT; };
D1BEF00001A1B2C3D4E5F719 /* open */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "Resources/bin/open"; sourceTree = SOURCE_ROOT; };
E1A0D30001A1B2C3D4E5F719 /* opencode */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "Resources/bin/opencode"; sourceTree = SOURCE_ROOT; };
E1A0D30003A1B2C3D4E5F719 /* opencode-cmux-plugin.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "Resources/bin/opencode-cmux-plugin.js"; sourceTree = SOURCE_ROOT; };
A5002001 /* THIRD_PARTY_LICENSES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = THIRD_PARTY_LICENSES.md; sourceTree = SOURCE_ROOT; };
B9000001A1B2C3D4E5F60719 /* cmux.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cmux.swift; sourceTree = "<group>"; };
B9000004A1B2C3D4E5F60719 /* cmux */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cmux; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -425,6 +431,8 @@
B2E7294509CC42FE9191870E /* xterm-ghostty */,
A5002001 /* THIRD_PARTY_LICENSES.md */,
C1ADE00001A1B2C3D4E5F719 /* claude */,
E1A0D30001A1B2C3D4E5F719 /* opencode */,
E1A0D30003A1B2C3D4E5F719 /* opencode-cmux-plugin.js */,
DA7A10CA710E000000000001 /* Localizable.xcstrings */,
DA7A10CA710E000000000002 /* InfoPlist.xcstrings */,
A5001622 /* cmux.sdef */,
Expand Down
110 changes: 110 additions & 0 deletions Resources/bin/opencode
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# cmux opencode wrapper - injects sidebar status + notifications

find_real_opencode() {
local self_dir
self_dir="$(cd "$(dirname "$0")" && pwd)"
local IFS=:
local dir
for dir in $PATH; do
[[ "$dir" == "$self_dir" ]] && continue
[[ -x "$dir/opencode" ]] && printf '%s' "$dir/opencode" && return 0
done
return 1
}

CMUX_BIN=""

cmux_socket_available() {
local socket="${CMUX_SOCKET_PATH:-${CMUX_SOCKET:-}}"
[[ -n "$socket" && -S "$socket" ]] || return 1

local self_dir
self_dir="$(cd "$(dirname "$0")" && pwd)"
CMUX_BIN="$self_dir/cmux"
[[ -x "$CMUX_BIN" ]] || CMUX_BIN="$(command -v cmux || true)"
[[ -n "$CMUX_BIN" ]] || return 1

CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC=0.75 \
"$CMUX_BIN" --socket "$socket" ping >/dev/null 2>&1
}

run_cmux() {
[[ -n "$CMUX_BIN" ]] || return 1
"$CMUX_BIN" "$@" >/dev/null 2>&1
}

link_children() {
local source_dir="$1"
local destination_dir="$2"
[[ -d "$source_dir" ]] || return 0

mkdir -p "$destination_dir"
local item
local name
shopt -s dotglob nullglob
for item in "$source_dir"/*; do
name="$(basename "$item")"
[[ "$name" == "." || "$name" == ".." ]] && continue
[[ -e "$destination_dir/$name" ]] && continue
ln -s "$item" "$destination_dir/$name"
done
shopt -u dotglob nullglob
}

IN_CMUX=0
if [[ -n "${CMUX_SURFACE_ID:-}" ]]; then
IN_CMUX=1
fi

if [[ "$IN_CMUX" == "0" || "${CMUX_OPENCODE_INTEGRATION_DISABLED:-}" == "1" ]] || ! cmux_socket_available; then
REAL_OPENCODE="$(find_real_opencode)" || { echo "Error: opencode not found in PATH" >&2; exit 127; }
exec "$REAL_OPENCODE" "$@"
fi

REAL_OPENCODE="$(find_real_opencode)" || { echo "Error: opencode not found in PATH" >&2; exit 127; }

SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
PLUGIN_SOURCE="$SELF_DIR/opencode-cmux-plugin.js"
if [[ ! -r "$PLUGIN_SOURCE" ]]; then
exec "$REAL_OPENCODE" "$@"
fi

CMUX_OPENCODE_CONFIG_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-opencode-config.XXXXXX")"
cleanup() {
rm -rf "$CMUX_OPENCODE_CONFIG_DIR"
}
trap cleanup EXIT HUP INT TERM

EXISTING_CONFIG_DIR="${OPENCODE_CONFIG_DIR:-}"
if [[ -n "$EXISTING_CONFIG_DIR" && -d "$EXISTING_CONFIG_DIR" ]]; then
local_item=""
local_name=""
shopt -s dotglob nullglob
for local_item in "$EXISTING_CONFIG_DIR"/*; do
local_name="$(basename "$local_item")"
[[ "$local_name" == "." || "$local_name" == ".." ]] && continue
[[ "$local_name" == "plugin" || "$local_name" == "plugins" ]] && continue
[[ -e "$CMUX_OPENCODE_CONFIG_DIR/$local_name" ]] && continue
ln -s "$local_item" "$CMUX_OPENCODE_CONFIG_DIR/$local_name"
done
shopt -u dotglob nullglob

link_children "$EXISTING_CONFIG_DIR/plugin" "$CMUX_OPENCODE_CONFIG_DIR/plugin"
link_children "$EXISTING_CONFIG_DIR/plugins" "$CMUX_OPENCODE_CONFIG_DIR/plugins"
fi

mkdir -p "$CMUX_OPENCODE_CONFIG_DIR/plugins"
cp "$PLUGIN_SOURCE" "$CMUX_OPENCODE_CONFIG_DIR/plugins/cmux-integration.js"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

export OPENCODE_CONFIG_DIR="$CMUX_OPENCODE_CONFIG_DIR"

run_cmux clear-status opencode || true

"$REAL_OPENCODE" "$@"
STATUS=$?

run_cmux clear-status opencode || true
run_cmux clear-notifications || true

exit "$STATUS"
Loading