Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# cmux-owned Swift file length budget.
# Format: max_lines<TAB>relative path
# Reduce counts as files shrink. CI fails if tracked files exceed this budget.
32655 CLI/cmux.swift
32733 CLI/cmux.swift
22098 Sources/TerminalController.swift
19820 Sources/Workspace.swift
19209 Sources/ContentView.swift
19955 Sources/Workspace.swift
19251 Sources/ContentView.swift
18044 Sources/AppDelegate.swift
16537 Sources/GhosttyTerminalView.swift
13589 Sources/Panels/BrowserPanel.swift
Expand Down
86 changes: 82 additions & 4 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7213,7 +7213,10 @@ struct CMUXCLI {
windowOverride: String?
) throws {
guard let sub = commandArgs.first?.lowercased() else {
throw CLIError(message: "workspace requires a subcommand. Try: list, create, close, rename, select, group")
throw CLIError(message: String(
localized: "cli.error.workspaceSubcommandRequired",
defaultValue: "workspace requires a subcommand. Try: list, create, close, rename, select, reconnect, disconnect, group"
))
}
let rest = Array(commandArgs.dropFirst())
switch sub {
Expand Down Expand Up @@ -7273,11 +7276,76 @@ struct CMUXCLI {
windowOverride: windowOverride,
requireWorkspaceFlag: false
)
case "reconnect":
try runWorkspaceRemoteConnectionCommand(
commandName: "workspace reconnect",
method: "workspace.remote.reconnect",
commandArgs: rest,
client: client,
jsonOutput: jsonOutput,
idFormat: idFormat,
windowOverride: windowOverride
)
case "disconnect":
try runWorkspaceRemoteConnectionCommand(
commandName: "workspace disconnect",
method: "workspace.remote.disconnect",
commandArgs: rest,
client: client,
jsonOutput: jsonOutput,
idFormat: idFormat,
windowOverride: windowOverride
)
default:
throw CLIError(message: "Unknown workspace subcommand: \(sub). Try: list, create, close, rename, select, group")
throw CLIError(message: String(
format: String(
localized: "cli.error.workspaceSubcommandUnknown",
defaultValue: "Unknown workspace subcommand: %@. Try: list, create, close, rename, select, reconnect, disconnect, group"
),
locale: .current,
sub
))
}
}

/// `cmux workspace reconnect|disconnect` — manual control over a remote
/// (SSH) workspace's connection. Targets the positional/`--workspace`
/// handle, then the caller's workspace, then the selected workspace.
private func runWorkspaceRemoteConnectionCommand(
commandName: String,
method: String,
commandArgs: [String],
client: SocketClient,
jsonOutput: Bool,
idFormat: CLIIDFormat,
windowOverride: String?
) throws {
let (workspaceArg, rem0) = parseOption(commandArgs, name: "--workspace")
let (_, rem1) = parseOption(rem0, name: "--window")
let positional = rem1.first(where: { !$0.hasPrefix("--") })
let windowRaw = windowFromArgsOrOverride(commandArgs, windowOverride: windowOverride)
// With an explicit --window and no workspace argument, target that
// window's selected workspace on the server instead of the caller's
// CMUX_WORKSPACE_ID, which may live in a different window.
let target = workspaceArg
?? positional
?? (windowRaw == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil)

var params: [String: Any] = [:]
let winId = try normalizeWindowHandle(windowRaw, client: client)
if let winId { params["window_id"] = winId }
if let wsId = try normalizeWorkspaceHandle(target, client: client, windowHandle: winId) {
params["workspace_id"] = wsId
}
let payload = try client.sendV2(method: method, params: params)
printV2Payload(
payload,
jsonOutput: jsonOutput,
idFormat: idFormat,
fallbackText: v2OKSummary(payload, idFormat: idFormat, kinds: ["workspace", "window"])
)
}

/// Emit a `cmux workspace-group` mutation response: JSON when --json,
/// otherwise a compact `OK`. Centralized so every mutating subcommand
/// honors --json the same way the list/create paths do.
Expand Down Expand Up @@ -13593,7 +13661,7 @@ struct CMUXCLI {
cmux list-workspaces
"""
case "workspace":
return """
return String(localized: "cli.workspace.usage", defaultValue: """
Usage: cmux workspace <subcommand> [flags]

Canonical noun for workspace operations. Legacy verbs
Expand All @@ -13607,13 +13675,23 @@ struct CMUXCLI {
close <workspace> Close a workspace
rename <workspace> --title <new>
select <workspace> Make a workspace active
reconnect [workspace] Reconnect a remote (SSH) workspace, including one
whose automatic reconnect paused because the host
was unreachable
disconnect [workspace] Stop a remote (SSH) workspace's connection
group <subcommand> Workspace group operations (see cmux workspace-group --help)

reconnect/disconnect accept a positional handle or --workspace
<id|ref|index>, defaulting to the caller's workspace, then the
selected one (of --window's window when given).

Examples:
cmux workspace list --json
cmux workspace create --name Build --cwd ~/projects/myapp
cmux workspace close workspace:3
"""
cmux workspace reconnect
cmux workspace disconnect --workspace workspace:3
""")
case "workspace-group":
return """
Usage: cmux workspace-group <subcommand> [flags]
Expand Down
Loading
Loading