Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,7 @@ jobs:
for binary in \
"$APP/Contents/MacOS/cmux" \
"$APP/Contents/Resources/bin/cmux" \
"$APP/Contents/Resources/bin/cmux-cloud" \
"$APP/Contents/Resources/bin/ghostty" \
"$APP/Contents/Resources/bin/cmux-tui" \
"$APP/Contents/Resources/bin/cmux-cua" \
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -387,18 +387,22 @@ jobs:
./scripts/normalize-system-extension-bundle.sh "$APP" "com.cmuxterm.app.tunnel"
APP_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/MacOS/cmux"
CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
CLOUD_CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux-cloud"
HELPER_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/ghostty"
DIFF_SIDECAR="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux-diff-sidecar"
APP_ARCHS="$(lipo -archs "$APP_BINARY")"
CLI_ARCHS="$(lipo -archs "$CLI_BINARY")"
CLOUD_CLI_ARCHS="$(lipo -archs "$CLOUD_CLI_BINARY")"
HELPER_ARCHS="$(lipo -archs "$HELPER_BINARY")"
SDK_VERSION="$(otool -l "$APP_BINARY" | awk '/LC_BUILD_VERSION/ { in_version=1; next } in_version && /sdk / { print $2; exit }')"
echo "App binary architectures: $APP_ARCHS"
echo "CLI binary architectures: $CLI_ARCHS"
echo "Cloud CLI binary architectures: $CLOUD_CLI_ARCHS"
echo "Ghostty helper architectures: $HELPER_ARCHS"
echo "App SDK version: $SDK_VERSION"
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
[[ "$CLOUD_CLI_ARCHS" == *arm64* && "$CLOUD_CLI_ARCHS" == *x86_64* ]]
[[ "$HELPER_ARCHS" == *arm64* && "$HELPER_ARCHS" == *x86_64* ]]
TUNNEL_BINARY="$APP/Contents/Library/SystemExtensions/com.cmuxterm.app.tunnel.systemextension/Contents/MacOS/cmuxTunnel"
[ -x "$TUNNEL_BINARY" ] || { echo "Cloud tunnel extension binary not found at $TUNNEL_BINARY" >&2; exit 1; }
Expand Down
102 changes: 102 additions & 0 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3043,6 +3043,10 @@ final class SocketClient {
relayEndpoint != nil
}

static func isRelaySocketPath(_ raw: String) -> Bool {
parseRelayEndpoint(raw) != nil
}

func connectionAppearsOpen() -> Bool {
if relayEndpoint != nil, socketFD < 0 {
do {
Expand Down Expand Up @@ -4699,6 +4703,92 @@ struct CMUXCLI {
}
}

private static func cloudCLIExecutablePath() -> String? {
let fileManager = FileManager.default
var candidates: [URL] = []
if let override = normalizedEnvValue(ProcessInfo.processInfo.environment["CMUX_CLOUD_CLI_PATH"]) {
candidates.append(URL(fileURLWithPath: override))
}
if let executableURL = CLIExecutableLocator.currentExecutableURL() {
candidates.append(executableURL.deletingLastPathComponent().appendingPathComponent("cmux-cloud"))
}
if let appBundle = CLIExecutableLocator.enclosingAppBundle() {
candidates.append(
appBundle.bundleURL
.appendingPathComponent("Contents", isDirectory: true)
.appendingPathComponent("Resources", isDirectory: true)
.appendingPathComponent("bin", isDirectory: true)
.appendingPathComponent("cmux-cloud", isDirectory: false)
)
}
for candidate in candidates {
let path = candidate.path
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: path, isDirectory: &isDirectory),
!isDirectory.boolValue,
fileManager.isExecutableFile(atPath: path)
{
return path
}
}
return nil
}

private func runCloudRustCommand(
commandArgs: [String],
socketPath: String,
explicitPassword: String?,
jsonOutput: Bool,
idFormatArg: String?,
windowId: String?
) throws -> Never {
guard let helperPath = Self.cloudCLIExecutablePath() else {
throw CLIError(message: String(
localized: "cli.cloud.helper.missing",
defaultValue: "cmux cloud helper is missing. Reload cmux and retry."
))
}

let resolvedPassword = SocketPasswordResolver.resolve(
explicit: explicitPassword,
socketPath: socketPath
)
setenv("CMUX_CLOUD_SOCKET_PATH", socketPath, 1)
setenv("CMUX_SOCKET_PATH", socketPath, 1)
setenv("CMUX_CLOUD_JSON", jsonOutput ? "1" : "0", 1)
if let resolvedPassword {
setenv("CMUX_CLOUD_SOCKET_PASSWORD", resolvedPassword, 1)
} else {
unsetenv("CMUX_CLOUD_SOCKET_PASSWORD")
}
if let idFormatArg {
setenv("CMUX_CLOUD_ID_FORMAT", idFormatArg, 1)
} else {
unsetenv("CMUX_CLOUD_ID_FORMAT")
}
if let windowId {
setenv("CMUX_CLOUD_WINDOW", windowId, 1)
} else {
unsetenv("CMUX_CLOUD_WINDOW")
}
if let currentExecutablePath = CLIExecutableLocator.currentExecutableURL()?.path {
setenv("CMUX_CLOUD_PARENT_CLI", currentExecutablePath, 1)
}

var argv = ([helperPath] + commandArgs).map { strdup($0) }
defer {
for item in argv {
free(item)
}
}
argv.append(nil)
execv(helperPath, &argv)
throw CLIError(message: String(
localized: "cli.cloud.helper.launchFailed",
defaultValue: "Could not start the cmux cloud helper. Reload cmux and retry."
))
}

private static func shouldFocusWindowBeforeDispatch(command: String, commandArgs: [String]) -> Bool {
let normalizedCommand = command.lowercased()
// `window` repositions a window (e.g. `window display`); it must not
Expand Down Expand Up @@ -5327,6 +5417,18 @@ struct CMUXCLI {
)

let idFormat = try resolvedIDFormat(jsonOutput: jsonOutput, raw: idFormatArg)
if command == "cloud", !SocketClient.isRelaySocketPath(resolvedSocketPath) {
client.close()
try runCloudRustCommand(
commandArgs: commandArgs,
socketPath: resolvedSocketPath,
explicitPassword: socketPasswordArg,
jsonOutput: jsonOutput,
idFormatArg: idFormatArg,
windowId: windowId
)
}

// Workspace inspection JSON is a scripting boundary: keep stable UUIDs
// beside renumberable refs unless the caller explicitly chooses a format.
let preserveStableWorkspaceIDs = jsonOutput && idFormatArg == nil
Expand Down
1 change: 1 addition & 0 deletions Native/CloudCLI/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target/
107 changes: 107 additions & 0 deletions Native/CloudCLI/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions Native/CloudCLI/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "cmux-cloud-cli"
version = "0.1.0"
edition = "2021"
publish = false

[[bin]]
name = "cmux-cloud"
path = "src/main.rs"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
Loading
Loading