From 2c1fd1f8019e90de6bab47f4fb07ea1b43582f65 Mon Sep 17 00:00:00 2001 From: sugakoji Date: Sun, 22 Feb 2026 22:54:52 +0900 Subject: [PATCH] Add open wrapper to route URLs to embedded browser When running `open https://...` inside a cmux terminal, the URL now opens in the built-in browser panel instead of the system default browser. Non-URL arguments and explicit flags pass through to /usr/bin/open unchanged. Closes #306 Co-Authored-By: Claude Opus 4.6 (cherry picked from commit 174623776eb0baef04f5a9ab49b926427c149acd) --- GhosttyTabs.xcodeproj/project.pbxproj | 3 ++ Resources/bin/open | 66 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100755 Resources/bin/open diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index 65cc12e6..58641e08 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ B9000002A1B2C3D4E5F60719 /* cmux.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000001A1B2C3D4E5F60719 /* cmux.swift */; }; 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 */; }; 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 */; }; @@ -96,6 +97,7 @@ files = ( B900000BA1B2C3D4E5F60719 /* cmux in Copy CLI */, C1ADE00002A1B2C3D4E5F719 /* claude in Copy CLI */, + D1BEF00002A1B2C3D4E5F719 /* open in Copy CLI */, ); name = "Copy CLI"; runOnlyForDeploymentPostprocessing = 0; @@ -182,6 +184,7 @@ A5001101 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B2E7294509CC42FE9191870E /* xterm-ghostty */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty/terminfo/78/xterm-ghostty"; sourceTree = ""; }; 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; }; 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 = ""; }; B9000004A1B2C3D4E5F60719 /* cmux */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cmux; sourceTree = BUILT_PRODUCTS_DIR; }; diff --git a/Resources/bin/open b/Resources/bin/open new file mode 100755 index 00000000..4000fed6 --- /dev/null +++ b/Resources/bin/open @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# cmux open wrapper - routes HTTP(S) URLs to cmux's in-app browser +# +# When running inside a cmux terminal (CMUX_SOCKET_PATH is set), this wrapper +# intercepts `open https://...` invocations and opens them in cmux's built-in +# browser within the same workspace. All other arguments pass through to +# /usr/bin/open unchanged. + +# Pass through immediately if not in a cmux terminal. +if [[ -z "$CMUX_SOCKET_PATH" ]]; then + exec /usr/bin/open "$@" +fi + +# No arguments → pass through. +if [[ $# -eq 0 ]]; then + exec /usr/bin/open "$@" +fi + +# Scan for flags that indicate explicit user intent → pass through. +# Also collect non-flag arguments (potential URLs/files). +passthrough=false +urls=() +for arg in "$@"; do + case "$arg" in + -a|-b|-R|-e|-t|-f|-W|-g|-n|-h|-s|-j|-u|--env|--stdin|--stdout|--stderr) + passthrough=true + break + ;; + -*) + # Unknown flag → be conservative, pass through + passthrough=true + break + ;; + http://*|https://*) + urls+=("$arg") + ;; + *) + # Non-URL, non-flag argument (file path, etc.) → pass through all + passthrough=true + break + ;; + esac +done + +if [[ "$passthrough" == true ]] || [[ ${#urls[@]} -eq 0 ]]; then + exec /usr/bin/open "$@" +fi + +# Find cmux CLI (same directory as this script). +SELF_DIR="$(cd "$(dirname "$0")" && pwd)" +CMUX_CLI="$SELF_DIR/cmux" + +if [[ ! -x "$CMUX_CLI" ]]; then + exec /usr/bin/open "$@" +fi + +# Open each URL in cmux's in-app browser. +failed=false +for url in "${urls[@]}"; do + "$CMUX_CLI" browser open "$url" 2>/dev/null || failed=true +done + +# If any failed, fall back to system open for all URLs. +if [[ "$failed" == true ]]; then + exec /usr/bin/open "$@" +fi