From 8bc7bddbfdffdfba2f6243dadceca81aa2420c72 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Mon, 25 May 2026 18:54:45 -0700 Subject: [PATCH 01/13] Add remote surface listing commands --- CLI/cmux.swift | 279 ++++++++++++++++++ Resources/Localizable.xcstrings | 8 + ...ifyProcessIntegrationRegressionTests.swift | 191 ++++++++++++ docs/cli-contract.md | 4 + 4 files changed, 482 insertions(+) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index fa9808d38191..d96b96bf33d0 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -3697,6 +3697,26 @@ struct CMUXCLI { } } + case "list-surfaces": + try runListSurfacesCommand( + commandName: command, + commandArgs: commandArgs, + client: client, + jsonOutput: jsonOutput, + idFormat: idFormat, + terminalOnly: false + ) + + case "list-terminals": + try runListSurfacesCommand( + commandName: command, + commandArgs: commandArgs, + client: client, + jsonOutput: jsonOutput, + idFormat: idFormat, + terminalOnly: true + ) + case "tree": try runTreeCommand(commandArgs: commandArgs, client: client, jsonOutput: jsonOutput, idFormat: idFormat) @@ -12319,6 +12339,40 @@ struct CMUXCLI { cmux list-pane-surfaces cmux list-pane-surfaces --workspace workspace:2 --pane pane:1 """ + case "list-surfaces": + return String(localized: "cli.help.listSurfaces", defaultValue: """ + Usage: cmux list-surfaces [--workspace ] [--window ] [--all] + + List cmux surfaces across windows, workspaces, and panes. + By default, lists every window so this works from an SSH shell outside cmux. + + Flags: + --workspace Restrict to one workspace + --window Restrict to one window + --all List all windows (default when no window/workspace is specified) + --json Structured JSON output + + Example: + cmux list-surfaces + cmux list-surfaces --workspace workspace:2 + """) + case "list-terminals": + return String(localized: "cli.help.listTerminals", defaultValue: """ + Usage: cmux list-terminals [--workspace ] [--window ] [--all] + + List terminal surfaces across windows, workspaces, and panes. + By default, lists every window so this works from an SSH shell outside cmux. + + Flags: + --workspace Restrict to one workspace + --window Restrict to one window + --all List all windows (default when no window/workspace is specified) + --json Structured JSON output + + Example: + cmux list-terminals + cmux list-terminals --workspace workspace:2 + """) case "tree": return """ Usage: cmux tree [flags] @@ -13805,6 +13859,14 @@ struct CMUXCLI { let jsonOutput: Bool } + private struct ListSurfacesCommandOptions { + let includeAllWindows: Bool + let workspaceHandle: String? + let windowHandle: String? + let jsonOutput: Bool + let terminalOnly: Bool + } + private struct TopCommandOptions { let includeAllWindows: Bool let workspaceHandle: String? @@ -13824,6 +13886,221 @@ struct CMUXCLI { let surfaceHandle: String? } + private func runListSurfacesCommand( + commandName: String, + commandArgs: [String], + client: SocketClient, + jsonOutput: Bool, + idFormat: CLIIDFormat, + terminalOnly: Bool + ) throws { + let options = try parseListSurfacesCommandOptions( + commandArgs, + commandName: commandName, + terminalOnly: terminalOnly + ) + let payload = try buildListSurfacesPayload( + options: options, + client: client + ) + if jsonOutput || options.jsonOutput { + print(jsonString(formatIDs(payload, mode: idFormat))) + } else { + let surfaces = payload["surfaces"] as? [[String: Any]] ?? [] + print(renderListSurfacesText( + surfaces: surfaces, + idFormat: idFormat, + terminalOnly: terminalOnly + )) + } + } + + private func parseListSurfacesCommandOptions( + _ args: [String], + commandName: String, + terminalOnly: Bool + ) throws -> ListSurfacesCommandOptions { + let (workspaceOpt, rem0) = parseOption(args, name: "--workspace") + if rem0.contains("--workspace") { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.missingHandleOption", + defaultValue: "%@ requires %@ " + ), commandName, "--workspace")) + } + let (windowOpt, rem1) = parseOption(rem0, name: "--window") + if rem1.contains("--window") { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.missingHandleOption", + defaultValue: "%@ requires %@ " + ), commandName, "--window")) + } + + var includeAll = false + var jsonOutput = false + var remaining: [String] = [] + for arg in rem1 { + if arg == "--all" { + includeAll = true + continue + } + if arg == "--json" { + jsonOutput = true + continue + } + remaining.append(arg) + } + + if let unknown = remaining.first(where: { $0.hasPrefix("--") }) { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.unknownFlag", + defaultValue: "%@: unknown flag '%@'. Known flags: --all --workspace --window --json" + ), commandName, unknown)) + } + if let extra = remaining.first { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.unexpectedArgument", + defaultValue: "%@: unexpected argument '%@'" + ), commandName, extra)) + } + if includeAll, windowOpt != nil { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.windowWithAll", + defaultValue: "%@: --window cannot be combined with --all" + ), commandName)) + } + + let includeAllByDefault = windowOpt == nil && workspaceOpt == nil + return ListSurfacesCommandOptions( + includeAllWindows: includeAll || includeAllByDefault, + workspaceHandle: workspaceOpt, + windowHandle: windowOpt, + jsonOutput: jsonOutput, + terminalOnly: terminalOnly + ) + } + + private func buildListSurfacesPayload( + options: ListSurfacesCommandOptions, + client: SocketClient + ) throws -> [String: Any] { + let treePayload = try buildTreePayload( + options: TreeCommandOptions( + includeAllWindows: options.includeAllWindows, + workspaceHandle: options.workspaceHandle, + windowHandle: options.windowHandle, + jsonOutput: options.jsonOutput + ), + client: client + ) + let surfaces = flattenTreeSurfaces( + payload: treePayload, + terminalOnly: options.terminalOnly + ) + return [ + "count": surfaces.count, + "terminal_only": options.terminalOnly, + "surfaces": surfaces + ] + } + + private func flattenTreeSurfaces( + payload: [String: Any], + terminalOnly: Bool + ) -> [[String: Any]] { + let windows = payload["windows"] as? [[String: Any]] ?? [] + var result: [[String: Any]] = [] + + for window in windows { + let workspaces = window["workspaces"] as? [[String: Any]] ?? [] + for workspace in workspaces { + let panes = workspace["panes"] as? [[String: Any]] ?? [] + for pane in panes { + let surfaces = pane["surfaces"] as? [[String: Any]] ?? [] + for surface in surfaces { + let type = ((surface["type"] as? String) ?? "") + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + if terminalOnly, type != "terminal" { + continue + } + + var row = surface + copyTreeListContext(into: &row, kind: "surface", from: surface) + copyTreeListContext(into: &row, kind: "pane", from: pane) + copyTreeListContext(into: &row, kind: "workspace", from: workspace) + copyTreeListContext(into: &row, kind: "window", from: window) + copyTreeListValue(into: &row, key: "surface_index", from: surface["index"] ?? surface["index_in_pane"]) + copyTreeListValue(into: &row, key: "pane_index", from: pane["index"]) + copyTreeListValue(into: &row, key: "workspace_index", from: workspace["index"]) + copyTreeListValue(into: &row, key: "workspace_title", from: workspace["title"]) + copyTreeListValue(into: &row, key: "window_index", from: window["index"]) + copyTreeListValue(into: &row, key: "window_key", from: window["key"]) + copyTreeListValue(into: &row, key: "pane_focused", from: pane["focused"]) + result.append(row) + } + } + } + } + + return result + } + + private func copyTreeListContext( + into row: inout [String: Any], + kind: String, + from source: [String: Any] + ) { + copyTreeListValue(into: &row, key: "\(kind)_id", from: source["id"]) + copyTreeListValue(into: &row, key: "\(kind)_ref", from: source["ref"]) + } + + private func copyTreeListValue( + into row: inout [String: Any], + key: String, + from value: Any? + ) { + guard row[key] == nil, let value else { return } + row[key] = value + } + + private func renderListSurfacesText( + surfaces: [[String: Any]], + idFormat: CLIIDFormat, + terminalOnly: Bool + ) -> String { + guard !surfaces.isEmpty else { + return terminalOnly + ? String(localized: "cli.listTerminals.empty", defaultValue: "No terminal surfaces") + : String(localized: "cli.listSurfaces.empty", defaultValue: "No surfaces") + } + + return surfaces.map { row in + var parts: [String] = [] + let prefix = (row["active"] as? Bool) == true ? "* " : " " + let surface = formatHandle(row, kind: "surface", idFormat: idFormat) ?? textHandle(row, idFormat: idFormat) + let rawType = ((row["type"] as? String) ?? "").trimmingCharacters(in: .whitespacesAndNewlines) + let type = rawType.isEmpty ? "unknown" : rawType + parts.append("\(prefix)\(surface)") + parts.append(type) + parts.append("window=\(formatHandle(row, kind: "window", idFormat: idFormat) ?? "?")") + parts.append("workspace=\(formatHandle(row, kind: "workspace", idFormat: idFormat) ?? "?")") + parts.append("pane=\(formatHandle(row, kind: "pane", idFormat: idFormat) ?? "?")") + if let title = debugString(row["title"]), !title.isEmpty { + parts.append("\"\(title.replacingOccurrences(of: "\"", with: "\\\""))\"") + } + if let tty = debugString(row["tty"]), !tty.isEmpty { + parts.append("tty=\(tty)") + } + if type.lowercased() == "browser", + let url = debugString(row["url"]), + !url.isEmpty { + parts.append(url) + } + return parts.joined(separator: " ") + } + .joined(separator: "\n") + } + private func runTreeCommand( commandArgs: [String], client: SocketClient, @@ -29521,6 +29798,8 @@ export default function cmuxPiSessionExtension(pi: ExtensionAPI) { new-split [--workspace ] [--surface ] [--panel ] [--window ] [--focus ] list-panes [--workspace ] [--window ] list-pane-surfaces [--workspace ] [--pane ] [--window ] + list-surfaces [--workspace ] [--window ] [--all] + list-terminals [--workspace ] [--window ] [--all] tree [--all] [--workspace ] [--window ] top [--all] [--workspace ] [--window ] [--processes] [--sort ] [--flat] [--format ] memory [--all] [--workspace ] [--groups ] diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 12e580643703..83e8136f0e7b 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -117756,6 +117756,14 @@ "textbox.removeAttachment.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"bs":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"da":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"de":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"en":{"stringUnit":{"state":"translated","value":"Remove Attachment"}},"es":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"fr":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"it":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ja":{"stringUnit":{"state":"translated","value":"添付を削除"}},"km":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ko":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"nb":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"pl":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ru":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"th":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"tr":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"uk":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}}}}, "textbox.openInPreview.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"bs":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"da":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"de":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"en":{"stringUnit":{"state":"translated","value":"Open in Preview"}},"es":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"fr":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"it":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ja":{"stringUnit":{"state":"translated","value":"プレビューで開く"}},"km":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ko":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"nb":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"pl":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ru":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"th":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"tr":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"uk":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}}}}, "textbox.openWithPreview.button": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"bs":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"da":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"de":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"en":{"stringUnit":{"state":"translated","value":"Open with Preview"}},"es":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"fr":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"it":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ja":{"stringUnit":{"state":"translated","value":"プレビューで開く"}},"km":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ko":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"nb":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"pl":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ru":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"th":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"tr":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"uk":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}}}}, + "cli.help.listSurfaces": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断して cmux サーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}}}}, + "cli.help.listTerminals": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断してターミナルサーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}}}}, + "cli.listSurfaces.empty": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"No surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"サーフェスはありません"}}}}, + "cli.listTerminals.empty": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"No terminal surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"ターミナルサーフェスはありません"}}}}, + "cli.listSurfaces.error.missingHandleOption": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@ requires %@ "}},"ja":{"stringUnit":{"state":"translated","value":"%@ には %@ が必要です"}}}}, + "cli.listSurfaces.error.unknownFlag": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"ja":{"stringUnit":{"state":"translated","value":"%@: 不明なフラグ '%@' です。既知のフラグ: --all --workspace --window --json"}}}}, + "cli.listSurfaces.error.unexpectedArgument": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@: unexpected argument '%@'"}},"ja":{"stringUnit":{"state":"translated","value":"%@: 予期しない引数 '%@'"}}}}, + "cli.listSurfaces.error.windowWithAll": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@: --window cannot be combined with --all"}},"ja":{"stringUnit":{"state":"translated","value":"%@: --window は --all と同時に指定できません"}}}}, "textbox.send.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Send"}},"bs":{"stringUnit":{"state":"needs_review","value":"Send"}},"da":{"stringUnit":{"state":"needs_review","value":"Send"}},"de":{"stringUnit":{"state":"needs_review","value":"Send"}},"en":{"stringUnit":{"state":"translated","value":"Send"}},"es":{"stringUnit":{"state":"needs_review","value":"Send"}},"fr":{"stringUnit":{"state":"needs_review","value":"Send"}},"it":{"stringUnit":{"state":"needs_review","value":"Send"}},"ja":{"stringUnit":{"state":"translated","value":"送信"}},"km":{"stringUnit":{"state":"needs_review","value":"Send"}},"ko":{"stringUnit":{"state":"needs_review","value":"Send"}},"nb":{"stringUnit":{"state":"needs_review","value":"Send"}},"pl":{"stringUnit":{"state":"needs_review","value":"Send"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Send"}},"ru":{"stringUnit":{"state":"needs_review","value":"Send"}},"th":{"stringUnit":{"state":"needs_review","value":"Send"}},"tr":{"stringUnit":{"state":"needs_review","value":"Send"}},"uk":{"stringUnit":{"state":"needs_review","value":"Send"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Send"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Send"}}}} } } diff --git a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift index 200be4ad0f9e..c11002429766 100644 --- a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +++ b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift @@ -3250,6 +3250,197 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { ]) } + func testListTerminalsFlattensAllWindowsAndFiltersNonTerminals() throws { + let cliPath = try bundledCLIPath() + let socketPath = makeSocketPath("listterm") + let listenerFD = try bindUnixSocket(at: socketPath) + let state = MockSocketServerState() + + defer { + Darwin.close(listenerFD) + unlink(socketPath) + } + + let serverHandled = startMockServer(listenerFD: listenerFD, state: state) { line in + guard let payload = self.jsonObject(line), + let id = payload["id"] as? String, + let method = payload["method"] as? String else { + return self.malformedRequestResponse(raw: line) + } + XCTAssertEqual(method, "system.tree") + let params = payload["params"] as? [String: Any] ?? [:] + XCTAssertEqual(params["all_windows"] as? Bool, true) + XCTAssertNil(params["workspace_id"]) + XCTAssertNil(params["window_id"]) + return self.v2Response( + id: id, + ok: true, + result: [ + "windows": [ + [ + "id": "11111111-1111-1111-1111-111111111111", + "ref": "window:1", + "index": 0, + "workspaces": [ + [ + "id": "22222222-2222-2222-2222-222222222222", + "ref": "workspace:2", + "index": 1, + "title": "Ops", + "panes": [ + [ + "id": "33333333-3333-3333-3333-333333333333", + "ref": "pane:3", + "index": 0, + "surfaces": [ + [ + "id": "44444444-4444-4444-4444-444444444444", + "ref": "surface:4", + "index": 0, + "type": "terminal", + "title": "agent shell", + "tty": "/dev/ttys123", + ], + [ + "id": "55555555-5555-5555-5555-555555555555", + "ref": "surface:5", + "index": 1, + "type": "browser", + "title": "Docs", + "url": "https://example.test", + ], + ], + ], + ], + ], + ], + ], + ], + ] + ) + } + + var environment = ProcessInfo.processInfo.environment + for key in Array(environment.keys) where key.hasPrefix("CMUX_") { + environment.removeValue(forKey: key) + } + environment["CMUX_SOCKET_PATH"] = socketPath + environment["CMUX_CLI_SENTRY_DISABLED"] = "1" + + let result = runProcess( + executablePath: cliPath, + arguments: ["list-terminals"], + environment: environment, + timeout: 5 + ) + + wait(for: [serverHandled], timeout: 5) + XCTAssertFalse(result.timedOut, result.stderr) + XCTAssertEqual(result.status, 0, result.stderr) + XCTAssertTrue(result.stderr.isEmpty, result.stderr) + XCTAssertTrue(result.stdout.contains("surface:4"), result.stdout) + XCTAssertTrue(result.stdout.contains("terminal"), result.stdout) + XCTAssertTrue(result.stdout.contains("window=window:1"), result.stdout) + XCTAssertTrue(result.stdout.contains("workspace=workspace:2"), result.stdout) + XCTAssertTrue(result.stdout.contains("pane=pane:3"), result.stdout) + XCTAssertTrue(result.stdout.contains(#""agent shell""#), result.stdout) + XCTAssertTrue(result.stdout.contains("tty=/dev/ttys123"), result.stdout) + XCTAssertFalse(result.stdout.contains("surface:5"), result.stdout) + XCTAssertFalse(result.stdout.contains("Docs"), result.stdout) + XCTAssertEqual( + state.snapshot().compactMap { self.jsonObject($0)?["method"] as? String }, + ["system.tree"] + ) + } + + func testListSurfacesUsesWorkspaceScopeAndIncludesBrowsers() throws { + let cliPath = try bundledCLIPath() + let socketPath = makeSocketPath("listsurf") + let listenerFD = try bindUnixSocket(at: socketPath) + let state = MockSocketServerState() + + defer { + Darwin.close(listenerFD) + unlink(socketPath) + } + + let serverHandled = startMockServer(listenerFD: listenerFD, state: state) { line in + guard let payload = self.jsonObject(line), + let id = payload["id"] as? String, + let method = payload["method"] as? String else { + return self.malformedRequestResponse(raw: line) + } + XCTAssertEqual(method, "system.tree") + let params = payload["params"] as? [String: Any] ?? [:] + XCTAssertEqual(params["all_windows"] as? Bool, false) + XCTAssertEqual(params["workspace_id"] as? String, "workspace:2") + XCTAssertNil(params["window_id"]) + return self.v2Response( + id: id, + ok: true, + result: [ + "windows": [ + [ + "id": "11111111-1111-1111-1111-111111111111", + "ref": "window:1", + "index": 0, + "workspaces": [ + [ + "id": "22222222-2222-2222-2222-222222222222", + "ref": "workspace:2", + "index": 1, + "title": "Docs", + "panes": [ + [ + "id": "33333333-3333-3333-3333-333333333333", + "ref": "pane:3", + "index": 0, + "surfaces": [ + [ + "id": "55555555-5555-5555-5555-555555555555", + "ref": "surface:5", + "index": 1, + "type": "browser", + "title": "Docs", + "url": "https://example.test", + ], + ], + ], + ], + ], + ], + ], + ], + ] + ) + } + + var environment = ProcessInfo.processInfo.environment + for key in Array(environment.keys) where key.hasPrefix("CMUX_") { + environment.removeValue(forKey: key) + } + environment["CMUX_SOCKET_PATH"] = socketPath + environment["CMUX_CLI_SENTRY_DISABLED"] = "1" + + let result = runProcess( + executablePath: cliPath, + arguments: ["list-surfaces", "--workspace", "workspace:2"], + environment: environment, + timeout: 5 + ) + + wait(for: [serverHandled], timeout: 5) + XCTAssertFalse(result.timedOut, result.stderr) + XCTAssertEqual(result.status, 0, result.stderr) + XCTAssertTrue(result.stderr.isEmpty, result.stderr) + XCTAssertTrue(result.stdout.contains("surface:5"), result.stdout) + XCTAssertTrue(result.stdout.contains("browser"), result.stdout) + XCTAssertTrue(result.stdout.contains("window=window:1"), result.stdout) + XCTAssertTrue(result.stdout.contains("workspace=workspace:2"), result.stdout) + XCTAssertTrue(result.stdout.contains("pane=pane:3"), result.stdout) + XCTAssertTrue(result.stdout.contains("https://example.test"), result.stdout) + } + func testSSHSessionListAllWorkspacesReportsQueryErrors() throws { let cliPath = try bundledCLIPath() let socketPath = makeSocketPath("sshlist") diff --git a/docs/cli-contract.md b/docs/cli-contract.md index 6d2c8bf3f3c9..4d9337298b48 100644 --- a/docs/cli-contract.md +++ b/docs/cli-contract.md @@ -102,6 +102,8 @@ Environment: | `new-split` | Split from a surface in a direction. | | `list-panes` | List panes in a workspace. | | `list-pane-surfaces` | List surfaces in a pane. | +| `list-surfaces` | List surfaces across windows, workspaces, and panes. Defaults to all windows for SSH-friendly inspection. | +| `list-terminals` | List terminal surfaces across windows, workspaces, and panes. Defaults to all windows for SSH-friendly inspection. | | `tree` | Print a window, workspace, pane, and surface tree. | | `top` | Print process/resource usage for cmux windows, workspaces, panes, and surfaces. | | `focus-pane` | Focus a pane. | @@ -426,6 +428,8 @@ the expected text without connecting to a cmux socket. - `cmux new-split --help` -> `Usage: cmux new-split` - `cmux list-panes --help` -> `Usage: cmux list-panes` - `cmux list-pane-surfaces --help` -> `Usage: cmux list-pane-surfaces` +- `cmux list-surfaces --help` -> `Usage: cmux list-surfaces` +- `cmux list-terminals --help` -> `Usage: cmux list-terminals` - `cmux tree --help` -> `Usage: cmux tree` - `cmux top --help` -> `Usage: cmux top` - `cmux focus-pane --help` -> `Usage: cmux focus-pane` From ef7efe8cf3336635f9f8ffe597c66a7268a6a34f Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Tue, 26 May 2026 00:39:41 -0700 Subject: [PATCH 02/13] Address remote list review feedback --- CLI/cmux.swift | 6 ++--- Resources/Localizable.xcstrings | 16 ++++++------- ...ifyProcessIntegrationRegressionTests.swift | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 41e400fb69fa..af6a87343f93 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -14008,11 +14008,11 @@ struct CMUXCLI { defaultValue: "%@: unexpected argument '%@'" ), commandName, extra)) } - if includeAll, windowOpt != nil { + if includeAll, let scopedOption = windowOpt != nil ? "--window" : (workspaceOpt != nil ? "--workspace" : nil) { throw CLIError(message: String(format: String( localized: "cli.listSurfaces.error.windowWithAll", - defaultValue: "%@: --window cannot be combined with --all" - ), commandName)) + defaultValue: "%@: %@ cannot be combined with --all" + ), commandName, scopedOption)) } let includeAllByDefault = windowOpt == nil && workspaceOpt == nil diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index d17839b68a34..b2a186ef3efd 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -117915,14 +117915,14 @@ "textbox.removeAttachment.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"bs":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"da":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"de":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"en":{"stringUnit":{"state":"translated","value":"Remove Attachment"}},"es":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"fr":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"it":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ja":{"stringUnit":{"state":"translated","value":"添付を削除"}},"km":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ko":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"nb":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"pl":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ru":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"th":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"tr":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"uk":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}}}}, "textbox.openInPreview.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"bs":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"da":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"de":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"en":{"stringUnit":{"state":"translated","value":"Open in Preview"}},"es":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"fr":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"it":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ja":{"stringUnit":{"state":"translated","value":"プレビューで開く"}},"km":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ko":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"nb":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"pl":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ru":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"th":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"tr":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"uk":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}}}}, "textbox.openWithPreview.button": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"bs":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"da":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"de":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"en":{"stringUnit":{"state":"translated","value":"Open with Preview"}},"es":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"fr":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"it":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ja":{"stringUnit":{"state":"translated","value":"プレビューで開く"}},"km":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ko":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"nb":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"pl":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ru":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"th":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"tr":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"uk":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}}}}, - "cli.help.listSurfaces": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断して cmux サーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}}}}, - "cli.help.listTerminals": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断してターミナルサーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}}}}, - "cli.listSurfaces.empty": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"No surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"サーフェスはありません"}}}}, - "cli.listTerminals.empty": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"No terminal surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"ターミナルサーフェスはありません"}}}}, - "cli.listSurfaces.error.missingHandleOption": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@ requires %@ "}},"ja":{"stringUnit":{"state":"translated","value":"%@ には %@ が必要です"}}}}, - "cli.listSurfaces.error.unknownFlag": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"ja":{"stringUnit":{"state":"translated","value":"%@: 不明なフラグ '%@' です。既知のフラグ: --all --workspace --window --json"}}}}, - "cli.listSurfaces.error.unexpectedArgument": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@: unexpected argument '%@'"}},"ja":{"stringUnit":{"state":"translated","value":"%@: 予期しない引数 '%@'"}}}}, - "cli.listSurfaces.error.windowWithAll": {"extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"%@: --window cannot be combined with --all"}},"ja":{"stringUnit":{"state":"translated","value":"%@: --window は --all と同時に指定できません"}}}}, + "cli.help.listSurfaces": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"bs":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"da":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"de":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"es":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"fr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"it":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断して cmux サーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"km":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ko":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"nb":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"pl":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ru":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"th":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"tr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"uk":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}}}}, + "cli.help.listTerminals": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"bs":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"da":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"de":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"es":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"fr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"it":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断してターミナルサーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"km":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ko":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"nb":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"pl":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ru":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"th":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"tr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"uk":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}}}}, + "cli.listSurfaces.empty": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"bs":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"da":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"de":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"en":{"stringUnit":{"state":"translated","value":"No surfaces"}},"es":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"fr":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"it":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"サーフェスはありません"}},"km":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"ko":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"nb":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"pl":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"ru":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"th":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"tr":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"uk":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"No surfaces"}}}}, + "cli.listTerminals.empty": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"bs":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"da":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"de":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"en":{"stringUnit":{"state":"translated","value":"No terminal surfaces"}},"es":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"fr":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"it":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"ターミナルサーフェスはありません"}},"km":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"ko":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"nb":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"pl":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"ru":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"th":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"tr":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"uk":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}}}}, + "cli.listSurfaces.error.missingHandleOption": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"bs":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"da":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"de":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"en":{"stringUnit":{"state":"translated","value":"%@ requires %@ "}},"es":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"fr":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"it":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"ja":{"stringUnit":{"state":"translated","value":"%@ には %@ が必要です"}},"km":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"ko":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"nb":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"pl":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"ru":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"th":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"tr":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"uk":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}}}}, + "cli.listSurfaces.error.unknownFlag": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"bs":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"da":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"de":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"en":{"stringUnit":{"state":"translated","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"es":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"fr":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"it":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"ja":{"stringUnit":{"state":"translated","value":"%@: 不明なフラグ '%@' です。既知のフラグ: --all --workspace --window --json"}},"km":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"ko":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"nb":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"pl":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"ru":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"th":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"tr":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"uk":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"%@: unknown flag '%@'. Known flags: --all --workspace --window --json"}}}}, + "cli.listSurfaces.error.unexpectedArgument": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"bs":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"da":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"de":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"en":{"stringUnit":{"state":"translated","value":"%@: unexpected argument '%@'"}},"es":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"fr":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"it":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"ja":{"stringUnit":{"state":"translated","value":"%@: 予期しない引数 '%@'"}},"km":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"ko":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"nb":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"pl":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"ru":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"th":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"tr":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"uk":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"%@: unexpected argument '%@'"}}}}, + "cli.listSurfaces.error.windowWithAll": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"bs":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"da":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"de":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"en":{"stringUnit":{"state":"translated","value":"%@: %@ cannot be combined with --all"}},"es":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"fr":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"it":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"ja":{"stringUnit":{"state":"translated","value":"%@: %@ は --all と同時に指定できません"}},"km":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"ko":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"nb":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"pl":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"ru":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"th":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"tr":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"uk":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"%@: %@ cannot be combined with --all"}}}}, "textbox.send.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Send"}},"bs":{"stringUnit":{"state":"needs_review","value":"Send"}},"da":{"stringUnit":{"state":"needs_review","value":"Send"}},"de":{"stringUnit":{"state":"needs_review","value":"Send"}},"en":{"stringUnit":{"state":"translated","value":"Send"}},"es":{"stringUnit":{"state":"needs_review","value":"Send"}},"fr":{"stringUnit":{"state":"needs_review","value":"Send"}},"it":{"stringUnit":{"state":"needs_review","value":"Send"}},"ja":{"stringUnit":{"state":"translated","value":"送信"}},"km":{"stringUnit":{"state":"needs_review","value":"Send"}},"ko":{"stringUnit":{"state":"needs_review","value":"Send"}},"nb":{"stringUnit":{"state":"needs_review","value":"Send"}},"pl":{"stringUnit":{"state":"needs_review","value":"Send"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Send"}},"ru":{"stringUnit":{"state":"needs_review","value":"Send"}},"th":{"stringUnit":{"state":"needs_review","value":"Send"}},"tr":{"stringUnit":{"state":"needs_review","value":"Send"}},"uk":{"stringUnit":{"state":"needs_review","value":"Send"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Send"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Send"}}}} } } diff --git a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift index aa399322aca4..430a355dff05 100644 --- a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +++ b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift @@ -3618,6 +3618,30 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { XCTAssertTrue(result.stdout.contains("https://example.test"), result.stdout) } + func testListSurfacesRejectsWorkspaceWithAll() throws { + let cliPath = try bundledCLIPath() + var environment = ProcessInfo.processInfo.environment + for key in Array(environment.keys) where key.hasPrefix("CMUX_") { + environment.removeValue(forKey: key) + } + environment["CMUX_SOCKET_PATH"] = makeSocketPath("nosocket") + environment["CMUX_CLI_SENTRY_DISABLED"] = "1" + + let result = runProcess( + executablePath: cliPath, + arguments: ["list-surfaces", "--workspace", "workspace:2", "--all"], + environment: environment, + timeout: 5 + ) + + XCTAssertFalse(result.timedOut, result.stderr) + XCTAssertEqual(result.status, 1) + XCTAssertTrue( + result.stderr.contains("list-surfaces: --workspace cannot be combined with --all"), + result.stderr + ) + } + func testSSHSessionListAllWorkspacesReportsQueryErrors() throws { let cliPath = try bundledCLIPath() let socketPath = makeSocketPath("sshlist") From f085a0988b43db0a1db4bf3e28950d36f534173b Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Tue, 26 May 2026 02:44:13 -0700 Subject: [PATCH 03/13] Address list surfaces review feedback --- CLI/cmux.swift | 13 +++++++++---- Resources/Localizable.xcstrings | 4 ++-- ...CLINotifyProcessIntegrationRegressionTests.swift | 2 ++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index af6a87343f93..273df164655e 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -12387,7 +12387,7 @@ struct CMUXCLI { """ case "list-surfaces": return String(localized: "cli.help.listSurfaces", defaultValue: """ - Usage: cmux list-surfaces [--workspace ] [--window ] [--all] + Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json] List cmux surfaces across windows, workspaces, and panes. By default, lists every window so this works from an SSH shell outside cmux. @@ -12404,7 +12404,7 @@ struct CMUXCLI { """) case "list-terminals": return String(localized: "cli.help.listTerminals", defaultValue: """ - Usage: cmux list-terminals [--workspace ] [--window ] [--all] + Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json] List terminal surfaces across windows, workspaces, and panes. By default, lists every window so this works from an SSH shell outside cmux. @@ -14082,6 +14082,11 @@ struct CMUXCLI { copyTreeListValue(into: &row, key: "window_index", from: window["index"]) copyTreeListValue(into: &row, key: "window_key", from: window["key"]) copyTreeListValue(into: &row, key: "pane_focused", from: pane["focused"]) + copyTreeListValue( + into: &row, + key: "active", + from: surface["selected"] ?? surface["focused"] ?? pane["focused"] + ) result.append(row) } } @@ -30078,8 +30083,8 @@ export default function cmuxPiSessionExtension(pi: ExtensionAPI) { new-split [--workspace ] [--surface ] [--panel ] [--window ] [--focus ] list-panes [--workspace ] [--window ] list-pane-surfaces [--workspace ] [--pane ] [--window ] - list-surfaces [--workspace ] [--window ] [--all] - list-terminals [--workspace ] [--window ] [--all] + list-surfaces [--workspace ] [--window ] [--all] [--json] + list-terminals [--workspace ] [--window ] [--all] [--json] tree [--all] [--workspace ] [--window ] top [--all] [--workspace ] [--window ] [--processes] [--sort ] [--flat] [--format ] memory [--all] [--workspace ] [--groups ] diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index e870a09ff46c..0215b7bbe735 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -117918,8 +117918,8 @@ "textbox.removeAttachment.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"bs":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"da":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"de":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"en":{"stringUnit":{"state":"translated","value":"Remove Attachment"}},"es":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"fr":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"it":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ja":{"stringUnit":{"state":"translated","value":"添付を削除"}},"km":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ko":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"nb":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"pl":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"ru":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"th":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"tr":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"uk":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Remove Attachment"}}}}, "textbox.openInPreview.tooltip": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"bs":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"da":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"de":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"en":{"stringUnit":{"state":"translated","value":"Open in Preview"}},"es":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"fr":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"it":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ja":{"stringUnit":{"state":"translated","value":"プレビューで開く"}},"km":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ko":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"nb":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"pl":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"ru":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"th":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"tr":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"uk":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Open in Preview"}}}}, "textbox.openWithPreview.button": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"bs":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"da":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"de":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"en":{"stringUnit":{"state":"translated","value":"Open with Preview"}},"es":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"fr":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"it":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ja":{"stringUnit":{"state":"translated","value":"プレビューで開く"}},"km":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ko":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"nb":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"pl":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"ru":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"th":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"tr":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"uk":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Open with Preview"}}}}, - "cli.help.listSurfaces": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"bs":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"da":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"de":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"es":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"fr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"it":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断して cmux サーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"km":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ko":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"nb":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"pl":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ru":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"th":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"tr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"uk":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}}}}, - "cli.help.listTerminals": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"bs":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"da":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"de":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"es":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"fr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"it":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nウィンドウ、ワークスペース、ペインを横断してターミナルサーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"km":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ko":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"nb":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"pl":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ru":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"th":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"tr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"uk":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}}}}, + "cli.help.listSurfaces": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"bs":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"da":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"de":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"es":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"fr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"it":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nウィンドウ、ワークスペース、ペインを横断して cmux サーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"km":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ko":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"nb":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"pl":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"ru":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"th":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"tr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"uk":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-surfaces [--workspace ] [--window ] [--all] [--json]\n\nList cmux surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-surfaces\n cmux list-surfaces --workspace workspace:2"}}}}, + "cli.help.listTerminals": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"bs":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"da":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"de":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"en":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"es":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"fr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"it":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ja":{"stringUnit":{"state":"translated","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nウィンドウ、ワークスペース、ペインを横断してターミナルサーフェスを一覧表示します。\n既定ではすべてのウィンドウを一覧表示するため、cmux 外の SSH シェルから使えます。\n\nFlags:\n --workspace 1 つのワークスペースに限定\n --window 1 つのウィンドウに限定\n --all すべてのウィンドウを一覧表示(window/workspace 未指定時の既定)\n --json 構造化 JSON 出力\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"km":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ko":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"nb":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"pl":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"ru":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"th":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"tr":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"uk":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"Usage: cmux list-terminals [--workspace ] [--window ] [--all] [--json]\n\nList terminal surfaces across windows, workspaces, and panes.\nBy default, lists every window so this works from an SSH shell outside cmux.\n\nFlags:\n --workspace Restrict to one workspace\n --window Restrict to one window\n --all List all windows (default when no window/workspace is specified)\n --json Structured JSON output\n\nExample:\n cmux list-terminals\n cmux list-terminals --workspace workspace:2"}}}}, "cli.listSurfaces.empty": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"bs":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"da":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"de":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"en":{"stringUnit":{"state":"translated","value":"No surfaces"}},"es":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"fr":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"it":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"サーフェスはありません"}},"km":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"ko":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"nb":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"pl":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"ru":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"th":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"tr":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"uk":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"No surfaces"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"No surfaces"}}}}, "cli.listTerminals.empty": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"bs":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"da":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"de":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"en":{"stringUnit":{"state":"translated","value":"No terminal surfaces"}},"es":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"fr":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"it":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"ja":{"stringUnit":{"state":"translated","value":"ターミナルサーフェスはありません"}},"km":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"ko":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"nb":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"pl":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"ru":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"th":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"tr":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"uk":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"No terminal surfaces"}}}}, "cli.listSurfaces.error.missingHandleOption": {"extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"bs":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"da":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"de":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"en":{"stringUnit":{"state":"translated","value":"%@ requires %@ "}},"es":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"fr":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"it":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"ja":{"stringUnit":{"state":"translated","value":"%@ には %@ が必要です"}},"km":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"ko":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"nb":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"pl":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"ru":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"th":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"tr":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"uk":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"%@ requires %@ "}}}}, diff --git a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift index 430a355dff05..73d43915ad70 100644 --- a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +++ b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift @@ -3475,6 +3475,7 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { "ref": "surface:4", "index": 0, "type": "terminal", + "selected": true, "title": "agent shell", "tty": "/dev/ttys123", ], @@ -3515,6 +3516,7 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { XCTAssertFalse(result.timedOut, result.stderr) XCTAssertEqual(result.status, 0, result.stderr) XCTAssertTrue(result.stderr.isEmpty, result.stderr) + XCTAssertTrue(result.stdout.contains("* surface:4"), result.stdout) XCTAssertTrue(result.stdout.contains("surface:4"), result.stdout) XCTAssertTrue(result.stdout.contains("terminal"), result.stdout) XCTAssertTrue(result.stdout.contains("window=window:1"), result.stdout) From d4573c9cdc06a89a6ef7d72ea8adb7ae9d008e12 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Tue, 26 May 2026 02:49:26 -0700 Subject: [PATCH 04/13] Extract list surfaces CLI implementation --- CLI/CMUXCLI+ListSurfaces.swift | 262 +++++++++++++++++++++++++++++++++ CLI/cmux.swift | 232 +---------------------------- cmux.xcodeproj/project.pbxproj | 4 + 3 files changed, 268 insertions(+), 230 deletions(-) create mode 100644 CLI/CMUXCLI+ListSurfaces.swift diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift new file mode 100644 index 000000000000..afc541e80640 --- /dev/null +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -0,0 +1,262 @@ +import Foundation + +extension CMUXCLI { + private struct ListSurfacesCommandOptions { + let includeAllWindows: Bool + let workspaceHandle: String? + let windowHandle: String? + let jsonOutput: Bool + let terminalOnly: Bool + } + + func runListSurfacesCommand( + commandName: String, + commandArgs: [String], + client: SocketClient, + jsonOutput: Bool, + idFormat: CLIIDFormat, + terminalOnly: Bool + ) throws { + let options = try parseListSurfacesCommandOptions( + commandArgs, + commandName: commandName, + terminalOnly: terminalOnly + ) + let payload = try buildListSurfacesPayload( + options: options, + client: client + ) + if jsonOutput || options.jsonOutput { + print(jsonString(formatIDs(payload, mode: idFormat))) + } else { + let surfaces = payload["surfaces"] as? [[String: Any]] ?? [] + print(renderListSurfacesText( + surfaces: surfaces, + idFormat: idFormat, + terminalOnly: terminalOnly + )) + } + } + + private func parseListSurfacesCommandOptions( + _ args: [String], + commandName: String, + terminalOnly: Bool + ) throws -> ListSurfacesCommandOptions { + let (workspaceOpt, rem0) = parseOption(args, name: "--workspace") + if rem0.contains("--workspace") { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.missingHandleOption", + defaultValue: "%@ requires %@ " + ), commandName, "--workspace")) + } + let (windowOpt, rem1) = parseOption(rem0, name: "--window") + if rem1.contains("--window") { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.missingHandleOption", + defaultValue: "%@ requires %@ " + ), commandName, "--window")) + } + + var includeAll = false + var jsonOutput = false + var remaining: [String] = [] + for arg in rem1 { + if arg == "--all" { + includeAll = true + continue + } + if arg == "--json" { + jsonOutput = true + continue + } + remaining.append(arg) + } + + if let unknown = remaining.first(where: { $0.hasPrefix("--") }) { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.unknownFlag", + defaultValue: "%@: unknown flag '%@'. Known flags: --all --workspace --window --json" + ), commandName, unknown)) + } + if let extra = remaining.first { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.unexpectedArgument", + defaultValue: "%@: unexpected argument '%@'" + ), commandName, extra)) + } + if includeAll, let scopedOption = windowOpt != nil ? "--window" : (workspaceOpt != nil ? "--workspace" : nil) { + throw CLIError(message: String(format: String( + localized: "cli.listSurfaces.error.windowWithAll", + defaultValue: "%@: %@ cannot be combined with --all" + ), commandName, scopedOption)) + } + + let includeAllByDefault = windowOpt == nil && workspaceOpt == nil + return ListSurfacesCommandOptions( + includeAllWindows: includeAll || includeAllByDefault, + workspaceHandle: workspaceOpt, + windowHandle: windowOpt, + jsonOutput: jsonOutput, + terminalOnly: terminalOnly + ) + } + + private func buildListSurfacesPayload( + options: ListSurfacesCommandOptions, + client: SocketClient + ) throws -> [String: Any] { + let treePayload = try buildTreePayload( + options: TreeCommandOptions( + includeAllWindows: options.includeAllWindows, + workspaceHandle: options.workspaceHandle, + windowHandle: options.windowHandle, + jsonOutput: options.jsonOutput + ), + client: client + ) + let surfaces = flattenTreeSurfaces( + payload: treePayload, + terminalOnly: options.terminalOnly + ) + return [ + "count": surfaces.count, + "terminal_only": options.terminalOnly, + "surfaces": surfaces + ] + } + + private func flattenTreeSurfaces( + payload: [String: Any], + terminalOnly: Bool + ) -> [[String: Any]] { + let windows = payload["windows"] as? [[String: Any]] ?? [] + var result: [[String: Any]] = [] + + for window in windows { + let workspaces = window["workspaces"] as? [[String: Any]] ?? [] + for workspace in workspaces { + let panes = workspace["panes"] as? [[String: Any]] ?? [] + for pane in panes { + let surfaces = pane["surfaces"] as? [[String: Any]] ?? [] + for surface in surfaces { + let type = ((surface["type"] as? String) ?? "") + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + if terminalOnly, type != "terminal" { + continue + } + + var row = surface + copyTreeListContext(into: &row, kind: "surface", from: surface) + copyTreeListContext(into: &row, kind: "pane", from: pane) + copyTreeListContext(into: &row, kind: "workspace", from: workspace) + copyTreeListContext(into: &row, kind: "window", from: window) + copyTreeListValue(into: &row, key: "surface_index", from: surface["index"] ?? surface["index_in_pane"]) + copyTreeListValue(into: &row, key: "pane_index", from: pane["index"]) + copyTreeListValue(into: &row, key: "workspace_index", from: workspace["index"]) + copyTreeListValue(into: &row, key: "workspace_title", from: workspace["title"]) + copyTreeListValue(into: &row, key: "window_index", from: window["index"]) + copyTreeListValue(into: &row, key: "window_key", from: window["key"]) + copyTreeListValue(into: &row, key: "pane_focused", from: pane["focused"]) + copyTreeListValue( + into: &row, + key: "active", + from: surface["selected"] ?? surface["focused"] ?? pane["focused"] + ) + result.append(row) + } + } + } + } + + return result + } + + private func copyTreeListContext( + into row: inout [String: Any], + kind: String, + from source: [String: Any] + ) { + copyTreeListValue(into: &row, key: "\(kind)_id", from: source["id"]) + copyTreeListValue(into: &row, key: "\(kind)_ref", from: source["ref"]) + } + + private func copyTreeListValue( + into row: inout [String: Any], + key: String, + from value: Any? + ) { + guard row[key] == nil, let value else { return } + row[key] = value + } + + private func renderListSurfacesText( + surfaces: [[String: Any]], + idFormat: CLIIDFormat, + terminalOnly: Bool + ) -> String { + guard !surfaces.isEmpty else { + return terminalOnly + ? String(localized: "cli.listTerminals.empty", defaultValue: "No terminal surfaces") + : String(localized: "cli.listSurfaces.empty", defaultValue: "No surfaces") + } + + return surfaces.map { row in + var parts: [String] = [] + let prefix = (row["active"] as? Bool) == true ? "* " : " " + let surface = formatHandle(row, kind: "surface", idFormat: idFormat) + ?? listSurfaceFallbackHandle(row, idFormat: idFormat) + let rawType = ((row["type"] as? String) ?? "").trimmingCharacters(in: .whitespacesAndNewlines) + let type = rawType.isEmpty ? "unknown" : rawType + parts.append("\(prefix)\(surface)") + parts.append(type) + parts.append("window=\(formatHandle(row, kind: "window", idFormat: idFormat) ?? "?")") + parts.append("workspace=\(formatHandle(row, kind: "workspace", idFormat: idFormat) ?? "?")") + parts.append("pane=\(formatHandle(row, kind: "pane", idFormat: idFormat) ?? "?")") + if let title = listSurfaceDebugString(row["title"]), !title.isEmpty { + parts.append("\"\(title.replacingOccurrences(of: "\"", with: "\\\""))\"") + } + if let tty = listSurfaceDebugString(row["tty"]), !tty.isEmpty { + parts.append("tty=\(tty)") + } + if type.lowercased() == "browser", + let url = listSurfaceDebugString(row["url"]), + !url.isEmpty { + parts.append(url) + } + return parts.joined(separator: " ") + } + .joined(separator: "\n") + } + + private func listSurfaceFallbackHandle( + _ item: [String: Any], + idFormat: CLIIDFormat + ) -> String { + let ref = item["ref"] as? String + let id = item["id"] as? String + switch idFormat { + case .refs: + return ref ?? id ?? "?" + case .uuids: + return id ?? ref ?? "?" + case .both: + if let ref, let id { + return "\(ref) \(id)" + } + return ref ?? id ?? "?" + } + } + + private func listSurfaceDebugString(_ value: Any?) -> String? { + guard let value, !(value is NSNull) else { return nil } + if let string = value as? String { + return string + } + if let number = value as? NSNumber { + return number.stringValue + } + return String(describing: value) + } +} diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 273df164655e..9056596ba932 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -13898,21 +13898,13 @@ struct CMUXCLI { return parts.joined(separator: " ") } - private struct TreeCommandOptions { + struct TreeCommandOptions { let includeAllWindows: Bool let workspaceHandle: String? let windowHandle: String? let jsonOutput: Bool } - private struct ListSurfacesCommandOptions { - let includeAllWindows: Bool - let workspaceHandle: String? - let windowHandle: String? - let jsonOutput: Bool - let terminalOnly: Bool - } - private struct TopCommandOptions { let includeAllWindows: Bool let workspaceHandle: String? @@ -13932,226 +13924,6 @@ struct CMUXCLI { let surfaceHandle: String? } - private func runListSurfacesCommand( - commandName: String, - commandArgs: [String], - client: SocketClient, - jsonOutput: Bool, - idFormat: CLIIDFormat, - terminalOnly: Bool - ) throws { - let options = try parseListSurfacesCommandOptions( - commandArgs, - commandName: commandName, - terminalOnly: terminalOnly - ) - let payload = try buildListSurfacesPayload( - options: options, - client: client - ) - if jsonOutput || options.jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) - } else { - let surfaces = payload["surfaces"] as? [[String: Any]] ?? [] - print(renderListSurfacesText( - surfaces: surfaces, - idFormat: idFormat, - terminalOnly: terminalOnly - )) - } - } - - private func parseListSurfacesCommandOptions( - _ args: [String], - commandName: String, - terminalOnly: Bool - ) throws -> ListSurfacesCommandOptions { - let (workspaceOpt, rem0) = parseOption(args, name: "--workspace") - if rem0.contains("--workspace") { - throw CLIError(message: String(format: String( - localized: "cli.listSurfaces.error.missingHandleOption", - defaultValue: "%@ requires %@ " - ), commandName, "--workspace")) - } - let (windowOpt, rem1) = parseOption(rem0, name: "--window") - if rem1.contains("--window") { - throw CLIError(message: String(format: String( - localized: "cli.listSurfaces.error.missingHandleOption", - defaultValue: "%@ requires %@ " - ), commandName, "--window")) - } - - var includeAll = false - var jsonOutput = false - var remaining: [String] = [] - for arg in rem1 { - if arg == "--all" { - includeAll = true - continue - } - if arg == "--json" { - jsonOutput = true - continue - } - remaining.append(arg) - } - - if let unknown = remaining.first(where: { $0.hasPrefix("--") }) { - throw CLIError(message: String(format: String( - localized: "cli.listSurfaces.error.unknownFlag", - defaultValue: "%@: unknown flag '%@'. Known flags: --all --workspace --window --json" - ), commandName, unknown)) - } - if let extra = remaining.first { - throw CLIError(message: String(format: String( - localized: "cli.listSurfaces.error.unexpectedArgument", - defaultValue: "%@: unexpected argument '%@'" - ), commandName, extra)) - } - if includeAll, let scopedOption = windowOpt != nil ? "--window" : (workspaceOpt != nil ? "--workspace" : nil) { - throw CLIError(message: String(format: String( - localized: "cli.listSurfaces.error.windowWithAll", - defaultValue: "%@: %@ cannot be combined with --all" - ), commandName, scopedOption)) - } - - let includeAllByDefault = windowOpt == nil && workspaceOpt == nil - return ListSurfacesCommandOptions( - includeAllWindows: includeAll || includeAllByDefault, - workspaceHandle: workspaceOpt, - windowHandle: windowOpt, - jsonOutput: jsonOutput, - terminalOnly: terminalOnly - ) - } - - private func buildListSurfacesPayload( - options: ListSurfacesCommandOptions, - client: SocketClient - ) throws -> [String: Any] { - let treePayload = try buildTreePayload( - options: TreeCommandOptions( - includeAllWindows: options.includeAllWindows, - workspaceHandle: options.workspaceHandle, - windowHandle: options.windowHandle, - jsonOutput: options.jsonOutput - ), - client: client - ) - let surfaces = flattenTreeSurfaces( - payload: treePayload, - terminalOnly: options.terminalOnly - ) - return [ - "count": surfaces.count, - "terminal_only": options.terminalOnly, - "surfaces": surfaces - ] - } - - private func flattenTreeSurfaces( - payload: [String: Any], - terminalOnly: Bool - ) -> [[String: Any]] { - let windows = payload["windows"] as? [[String: Any]] ?? [] - var result: [[String: Any]] = [] - - for window in windows { - let workspaces = window["workspaces"] as? [[String: Any]] ?? [] - for workspace in workspaces { - let panes = workspace["panes"] as? [[String: Any]] ?? [] - for pane in panes { - let surfaces = pane["surfaces"] as? [[String: Any]] ?? [] - for surface in surfaces { - let type = ((surface["type"] as? String) ?? "") - .trimmingCharacters(in: .whitespacesAndNewlines) - .lowercased() - if terminalOnly, type != "terminal" { - continue - } - - var row = surface - copyTreeListContext(into: &row, kind: "surface", from: surface) - copyTreeListContext(into: &row, kind: "pane", from: pane) - copyTreeListContext(into: &row, kind: "workspace", from: workspace) - copyTreeListContext(into: &row, kind: "window", from: window) - copyTreeListValue(into: &row, key: "surface_index", from: surface["index"] ?? surface["index_in_pane"]) - copyTreeListValue(into: &row, key: "pane_index", from: pane["index"]) - copyTreeListValue(into: &row, key: "workspace_index", from: workspace["index"]) - copyTreeListValue(into: &row, key: "workspace_title", from: workspace["title"]) - copyTreeListValue(into: &row, key: "window_index", from: window["index"]) - copyTreeListValue(into: &row, key: "window_key", from: window["key"]) - copyTreeListValue(into: &row, key: "pane_focused", from: pane["focused"]) - copyTreeListValue( - into: &row, - key: "active", - from: surface["selected"] ?? surface["focused"] ?? pane["focused"] - ) - result.append(row) - } - } - } - } - - return result - } - - private func copyTreeListContext( - into row: inout [String: Any], - kind: String, - from source: [String: Any] - ) { - copyTreeListValue(into: &row, key: "\(kind)_id", from: source["id"]) - copyTreeListValue(into: &row, key: "\(kind)_ref", from: source["ref"]) - } - - private func copyTreeListValue( - into row: inout [String: Any], - key: String, - from value: Any? - ) { - guard row[key] == nil, let value else { return } - row[key] = value - } - - private func renderListSurfacesText( - surfaces: [[String: Any]], - idFormat: CLIIDFormat, - terminalOnly: Bool - ) -> String { - guard !surfaces.isEmpty else { - return terminalOnly - ? String(localized: "cli.listTerminals.empty", defaultValue: "No terminal surfaces") - : String(localized: "cli.listSurfaces.empty", defaultValue: "No surfaces") - } - - return surfaces.map { row in - var parts: [String] = [] - let prefix = (row["active"] as? Bool) == true ? "* " : " " - let surface = formatHandle(row, kind: "surface", idFormat: idFormat) ?? textHandle(row, idFormat: idFormat) - let rawType = ((row["type"] as? String) ?? "").trimmingCharacters(in: .whitespacesAndNewlines) - let type = rawType.isEmpty ? "unknown" : rawType - parts.append("\(prefix)\(surface)") - parts.append(type) - parts.append("window=\(formatHandle(row, kind: "window", idFormat: idFormat) ?? "?")") - parts.append("workspace=\(formatHandle(row, kind: "workspace", idFormat: idFormat) ?? "?")") - parts.append("pane=\(formatHandle(row, kind: "pane", idFormat: idFormat) ?? "?")") - if let title = debugString(row["title"]), !title.isEmpty { - parts.append("\"\(title.replacingOccurrences(of: "\"", with: "\\\""))\"") - } - if let tty = debugString(row["tty"]), !tty.isEmpty { - parts.append("tty=\(tty)") - } - if type.lowercased() == "browser", - let url = debugString(row["url"]), - !url.isEmpty { - parts.append(url) - } - return parts.joined(separator: " ") - } - .joined(separator: "\n") - } - private func runTreeCommand( commandArgs: [String], client: SocketClient, @@ -14371,7 +14143,7 @@ struct CMUXCLI { } } - private func buildTreePayload( + func buildTreePayload( options: TreeCommandOptions, client: SocketClient ) throws -> [String: Any] { diff --git a/cmux.xcodeproj/project.pbxproj b/cmux.xcodeproj/project.pbxproj index abd489cf1b1e..776ad5a94575 100644 --- a/cmux.xcodeproj/project.pbxproj +++ b/cmux.xcodeproj/project.pbxproj @@ -422,6 +422,7 @@ B9000063A1B2C3D4E5F60719 /* CMUXCLI+AgentHookDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000062A1B2C3D4E5F60719 /* CMUXCLI+AgentHookDefinitions.swift */; }; B9000068A1B2C3D4E5F60719 /* CMUXCLI+AmpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000069A1B2C3D4E5F60719 /* CMUXCLI+AmpExtension.swift */; }; B9000071A1B2C3D4E5F60719 /* CMUXCLI+Memory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000070A1B2C3D4E5F60719 /* CMUXCLI+Memory.swift */; }; + B9000073A1B2C3D4E5F60719 /* CMUXCLI+ListSurfaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000072A1B2C3D4E5F60719 /* CMUXCLI+ListSurfaces.swift */; }; B900000BA1B2C3D4E5F60719 /* cmux in Copy CLI */ = {isa = PBXBuildFile; fileRef = B9000004A1B2C3D4E5F60719 /* cmux */; }; B900002EA1B2C3D4E5F60719 /* CMUXCLI+Themes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B900002CA1B2C3D4E5F60719 /* CMUXCLI+Themes.swift */; }; B900002FA1B2C3D4E5F60719 /* CMUXCLI+ThemeSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = B900002DA1B2C3D4E5F60719 /* CMUXCLI+ThemeSupport.swift */; }; @@ -997,6 +998,7 @@ B9000062A1B2C3D4E5F60719 /* CMUXCLI+AgentHookDefinitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMUXCLI+AgentHookDefinitions.swift"; sourceTree = ""; }; B9000069A1B2C3D4E5F60719 /* CMUXCLI+AmpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMUXCLI+AmpExtension.swift"; sourceTree = ""; }; B9000070A1B2C3D4E5F60719 /* CMUXCLI+Memory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMUXCLI+Memory.swift"; sourceTree = ""; }; + B9000072A1B2C3D4E5F60719 /* CMUXCLI+ListSurfaces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMUXCLI+ListSurfaces.swift"; sourceTree = ""; }; B9000004A1B2C3D4E5F60719 /* cmux */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cmux; sourceTree = BUILT_PRODUCTS_DIR; }; B900002CA1B2C3D4E5F60719 /* CMUXCLI+Themes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMUXCLI+Themes.swift"; sourceTree = ""; }; B900002DA1B2C3D4E5F60719 /* CMUXCLI+ThemeSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMUXCLI+ThemeSupport.swift"; sourceTree = ""; }; @@ -1602,6 +1604,7 @@ B9000062A1B2C3D4E5F60719 /* CMUXCLI+AgentHookDefinitions.swift */, B9000069A1B2C3D4E5F60719 /* CMUXCLI+AmpExtension.swift */, B9000070A1B2C3D4E5F60719 /* CMUXCLI+Memory.swift */, + B9000072A1B2C3D4E5F60719 /* CMUXCLI+ListSurfaces.swift */, B9000031A1B2C3D4E5F60719 /* CMUXCLI+DocsSettings.swift */, B900002DA1B2C3D4E5F60719 /* CMUXCLI+ThemeSupport.swift */, B900002CA1B2C3D4E5F60719 /* CMUXCLI+Themes.swift */, @@ -2374,6 +2377,7 @@ B9000063A1B2C3D4E5F60719 /* CMUXCLI+AgentHookDefinitions.swift in Sources */, B9000068A1B2C3D4E5F60719 /* CMUXCLI+AmpExtension.swift in Sources */, B9000071A1B2C3D4E5F60719 /* CMUXCLI+Memory.swift in Sources */, + B9000073A1B2C3D4E5F60719 /* CMUXCLI+ListSurfaces.swift in Sources */, B9000035A1B2C3D4E5F60719 /* CMUXCLI+DocsSettings.swift in Sources */, B900002FA1B2C3D4E5F60719 /* CMUXCLI+ThemeSupport.swift in Sources */, B900002EA1B2C3D4E5F60719 /* CMUXCLI+Themes.swift in Sources */, From e44e609bc43a66f5bb7cdd69c6f842df31713008 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Tue, 26 May 2026 02:50:41 -0700 Subject: [PATCH 05/13] Avoid pane-wide active surface markers --- CLI/CMUXCLI+ListSurfaces.swift | 2 +- ...CLINotifyProcessIntegrationRegressionTests.swift | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift index afc541e80640..c8357e0c0b77 100644 --- a/CLI/CMUXCLI+ListSurfaces.swift +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -162,7 +162,7 @@ extension CMUXCLI { copyTreeListValue( into: &row, key: "active", - from: surface["selected"] ?? surface["focused"] ?? pane["focused"] + from: surface["selected"] ?? surface["focused"] ) result.append(row) } diff --git a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift index 73d43915ad70..ca479ce4147f 100644 --- a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +++ b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift @@ -3469,6 +3469,7 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { "id": "33333333-3333-3333-3333-333333333333", "ref": "pane:3", "index": 0, + "focused": true, "surfaces": [ [ "id": "44444444-4444-4444-4444-444444444444", @@ -3487,6 +3488,14 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { "title": "Docs", "url": "https://example.test", ], + [ + "id": "66666666-6666-6666-6666-666666666666", + "ref": "surface:6", + "index": 2, + "type": "terminal", + "title": "other shell", + "tty": "/dev/ttys456", + ], ], ], ], @@ -3517,13 +3526,17 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { XCTAssertEqual(result.status, 0, result.stderr) XCTAssertTrue(result.stderr.isEmpty, result.stderr) XCTAssertTrue(result.stdout.contains("* surface:4"), result.stdout) + XCTAssertFalse(result.stdout.contains("* surface:6"), result.stdout) XCTAssertTrue(result.stdout.contains("surface:4"), result.stdout) + XCTAssertTrue(result.stdout.contains("surface:6"), result.stdout) XCTAssertTrue(result.stdout.contains("terminal"), result.stdout) XCTAssertTrue(result.stdout.contains("window=window:1"), result.stdout) XCTAssertTrue(result.stdout.contains("workspace=workspace:2"), result.stdout) XCTAssertTrue(result.stdout.contains("pane=pane:3"), result.stdout) XCTAssertTrue(result.stdout.contains(#""agent shell""#), result.stdout) XCTAssertTrue(result.stdout.contains("tty=/dev/ttys123"), result.stdout) + XCTAssertTrue(result.stdout.contains(#""other shell""#), result.stdout) + XCTAssertTrue(result.stdout.contains("tty=/dev/ttys456"), result.stdout) XCTAssertFalse(result.stdout.contains("surface:5"), result.stdout) XCTAssertFalse(result.stdout.contains("Docs"), result.stdout) XCTAssertEqual( From c1954ef4e3e668a7ba1f8ca273223a8842f11eb1 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Tue, 26 May 2026 02:59:06 -0700 Subject: [PATCH 06/13] Fix list surfaces active fallback --- CLI/CMUXCLI+ListSurfaces.swift | 68 +++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift index c8357e0c0b77..2c1f5ba8f1b4 100644 --- a/CLI/CMUXCLI+ListSurfaces.swift +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -159,11 +159,7 @@ extension CMUXCLI { copyTreeListValue(into: &row, key: "window_index", from: window["index"]) copyTreeListValue(into: &row, key: "window_key", from: window["key"]) copyTreeListValue(into: &row, key: "pane_focused", from: pane["focused"]) - copyTreeListValue( - into: &row, - key: "active", - from: surface["selected"] ?? surface["focused"] - ) + row["active"] = listSurfaceIsActive(surface: surface, pane: pane) result.append(row) } } @@ -173,6 +169,68 @@ extension CMUXCLI { return result } + private func listSurfaceIsActive(surface: [String: Any], pane: [String: Any]) -> Bool { + if listSurfaceBool(surface["active"]) || listSurfaceBool(surface["focused"]) { + return true + } + + guard listSurfaceBool(pane["focused"]) else { + return false + } + + if listSurfaceBool(surface["selected"]) { + return true + } + + guard let selectedSurfaceHandle = listSurfaceRelatedHandle( + pane, + refKey: "selected_surface_ref", + idKey: "selected_surface_id" + ) else { + return false + } + return listSurfaceMatchesHandle(surface, handle: selectedSurfaceHandle) + } + + private func listSurfaceBool(_ value: Any?) -> Bool { + guard let value, !(value is NSNull) else { return false } + if let bool = value as? Bool { + return bool + } + if let number = value as? NSNumber { + return number.boolValue + } + if let string = value as? String { + switch string.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { + case "1", "true", "yes": + return true + default: + return false + } + } + return false + } + + private func listSurfaceRelatedHandle( + _ item: [String: Any], + refKey: String, + idKey: String + ) -> String? { + if let ref = item[refKey] as? String, !ref.isEmpty { + return ref + } + if let id = item[idKey] as? String, !id.isEmpty { + return id + } + return nil + } + + private func listSurfaceMatchesHandle(_ item: [String: Any], handle: String) -> Bool { + let trimmed = handle.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return false } + return (item["id"] as? String) == trimmed || (item["ref"] as? String) == trimmed + } + private func copyTreeListContext( into row: inout [String: Any], kind: String, From 46a42935744dcdc8e877944d9787fc23035b80c0 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sat, 6 Jun 2026 02:20:21 -0700 Subject: [PATCH 07/13] Fix list surfaces active marker scoping --- CLI/CMUXCLI+ListSurfaces.swift | 45 +----------------- ...ifyProcessIntegrationRegressionTests.swift | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift index 2c1f5ba8f1b4..3ff657c94e27 100644 --- a/CLI/CMUXCLI+ListSurfaces.swift +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -159,7 +159,7 @@ extension CMUXCLI { copyTreeListValue(into: &row, key: "window_index", from: window["index"]) copyTreeListValue(into: &row, key: "window_key", from: window["key"]) copyTreeListValue(into: &row, key: "pane_focused", from: pane["focused"]) - row["active"] = listSurfaceIsActive(surface: surface, pane: pane) + row["active"] = listSurfaceBool(surface["active"]) result.append(row) } } @@ -169,29 +169,6 @@ extension CMUXCLI { return result } - private func listSurfaceIsActive(surface: [String: Any], pane: [String: Any]) -> Bool { - if listSurfaceBool(surface["active"]) || listSurfaceBool(surface["focused"]) { - return true - } - - guard listSurfaceBool(pane["focused"]) else { - return false - } - - if listSurfaceBool(surface["selected"]) { - return true - } - - guard let selectedSurfaceHandle = listSurfaceRelatedHandle( - pane, - refKey: "selected_surface_ref", - idKey: "selected_surface_id" - ) else { - return false - } - return listSurfaceMatchesHandle(surface, handle: selectedSurfaceHandle) - } - private func listSurfaceBool(_ value: Any?) -> Bool { guard let value, !(value is NSNull) else { return false } if let bool = value as? Bool { @@ -211,26 +188,6 @@ extension CMUXCLI { return false } - private func listSurfaceRelatedHandle( - _ item: [String: Any], - refKey: String, - idKey: String - ) -> String? { - if let ref = item[refKey] as? String, !ref.isEmpty { - return ref - } - if let id = item[idKey] as? String, !id.isEmpty { - return id - } - return nil - } - - private func listSurfaceMatchesHandle(_ item: [String: Any], handle: String) -> Bool { - let trimmed = handle.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { return false } - return (item["id"] as? String) == trimmed || (item["ref"] as? String) == trimmed - } - private func copyTreeListContext( into row: inout [String: Any], kind: String, diff --git a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift index 8161185f997d..2e0a8e2ecc19 100644 --- a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +++ b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift @@ -3870,6 +3870,13 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { id: id, ok: true, result: [ + "active": [ + "window_ref": "window:1", + "workspace_ref": "workspace:2", + "pane_ref": "pane:3", + "surface_ref": "surface:4", + ], + "caller": NSNull(), "windows": [ [ "id": "11111111-1111-1111-1111-111111111111", @@ -3894,6 +3901,7 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { "index": 0, "type": "terminal", "selected": true, + "focused": true, "title": "agent shell", "tty": "/dev/ttys123", ], @@ -3919,6 +3927,39 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { ], ], ], + [ + "id": "77777777-7777-7777-7777-777777777777", + "ref": "window:7", + "index": 1, + "workspaces": [ + [ + "id": "88888888-8888-8888-8888-888888888888", + "ref": "workspace:8", + "index": 0, + "title": "Background", + "panes": [ + [ + "id": "99999999-9999-9999-9999-999999999999", + "ref": "pane:9", + "index": 0, + "focused": true, + "surfaces": [ + [ + "id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "ref": "surface:10", + "index": 0, + "type": "terminal", + "selected": true, + "focused": true, + "title": "background shell", + "tty": "/dev/ttys789", + ], + ], + ], + ], + ], + ], + ], ], ] ) @@ -3944,8 +3985,12 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { XCTAssertTrue(result.stderr.isEmpty, result.stderr) XCTAssertTrue(result.stdout.contains("* surface:4"), result.stdout) XCTAssertFalse(result.stdout.contains("* surface:6"), result.stdout) + XCTAssertFalse(result.stdout.contains("* surface:10"), result.stdout) + let starredRows = result.stdout.split(separator: "\n").filter { $0.hasPrefix("* ") } + XCTAssertEqual(starredRows.count, 1, result.stdout) XCTAssertTrue(result.stdout.contains("surface:4"), result.stdout) XCTAssertTrue(result.stdout.contains("surface:6"), result.stdout) + XCTAssertTrue(result.stdout.contains("surface:10"), result.stdout) XCTAssertTrue(result.stdout.contains("terminal"), result.stdout) XCTAssertTrue(result.stdout.contains("window=window:1"), result.stdout) XCTAssertTrue(result.stdout.contains("workspace=workspace:2"), result.stdout) @@ -3954,6 +3999,8 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { XCTAssertTrue(result.stdout.contains("tty=/dev/ttys123"), result.stdout) XCTAssertTrue(result.stdout.contains(#""other shell""#), result.stdout) XCTAssertTrue(result.stdout.contains("tty=/dev/ttys456"), result.stdout) + XCTAssertTrue(result.stdout.contains(#""background shell""#), result.stdout) + XCTAssertTrue(result.stdout.contains("tty=/dev/ttys789"), result.stdout) XCTAssertFalse(result.stdout.contains("surface:5"), result.stdout) XCTAssertFalse(result.stdout.contains("Docs"), result.stdout) XCTAssertEqual( From 19b6a93f667707f7456dc1d6e23a4c056db68f2d Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sat, 6 Jun 2026 02:27:44 -0700 Subject: [PATCH 08/13] Validate list surfaces scoped handles --- CLI/CMUXCLI+ListSurfaces.swift | 27 ++++++++++++- ...ifyProcessIntegrationRegressionTests.swift | 38 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift index 3ff657c94e27..cfc445ff07f0 100644 --- a/CLI/CMUXCLI+ListSurfaces.swift +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -43,15 +43,17 @@ extension CMUXCLI { commandName: String, terminalOnly: Bool ) throws -> ListSurfacesCommandOptions { + let workspaceWasProvided = listSurfaceOptionWasProvided(args, name: "--workspace") let (workspaceOpt, rem0) = parseOption(args, name: "--workspace") - if rem0.contains("--workspace") { + if listSurfaceOptionRequiresHandle(workspaceOpt, wasProvided: workspaceWasProvided) { throw CLIError(message: String(format: String( localized: "cli.listSurfaces.error.missingHandleOption", defaultValue: "%@ requires %@ " ), commandName, "--workspace")) } + let windowWasProvided = listSurfaceOptionWasProvided(rem0, name: "--window") let (windowOpt, rem1) = parseOption(rem0, name: "--window") - if rem1.contains("--window") { + if listSurfaceOptionRequiresHandle(windowOpt, wasProvided: windowWasProvided) { throw CLIError(message: String(format: String( localized: "cli.listSurfaces.error.missingHandleOption", defaultValue: "%@ requires %@ " @@ -102,6 +104,27 @@ extension CMUXCLI { ) } + private func listSurfaceOptionWasProvided(_ args: [String], name: String) -> Bool { + var pastTerminator = false + for arg in args { + if arg == "--" { + pastTerminator = true + continue + } + if !pastTerminator, arg == name || arg.hasPrefix("\(name)=") { + return true + } + } + return false + } + + private func listSurfaceOptionRequiresHandle(_ value: String?, wasProvided: Bool) -> Bool { + guard wasProvided else { return false } + guard let value else { return true } + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty || trimmed.hasPrefix("-") + } + private func buildListSurfacesPayload( options: ListSurfacesCommandOptions, client: SocketClient diff --git a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift index 8c84ce2b31d6..94f1bfd8c941 100644 --- a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +++ b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift @@ -4121,6 +4121,44 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { ) } + func testListSurfacesRejectsMissingScopedHandlesBeforeSocket() throws { + let cliPath = try bundledCLIPath() + var environment = ProcessInfo.processInfo.environment + for key in Array(environment.keys) where key.hasPrefix("CMUX_") { + environment.removeValue(forKey: key) + } + environment["CMUX_SOCKET_PATH"] = makeSocketPath("nosocket") + environment["CMUX_CLI_SENTRY_DISABLED"] = "1" + + let workspaceResult = runProcess( + executablePath: cliPath, + arguments: ["list-surfaces", "--workspace", "--json"], + environment: environment, + timeout: 5 + ) + + XCTAssertFalse(workspaceResult.timedOut, workspaceResult.stderr) + XCTAssertEqual(workspaceResult.status, 1) + XCTAssertTrue( + workspaceResult.stderr.contains("list-surfaces requires --workspace "), + workspaceResult.stderr + ) + + let windowResult = runProcess( + executablePath: cliPath, + arguments: ["list-terminals", "--window="], + environment: environment, + timeout: 5 + ) + + XCTAssertFalse(windowResult.timedOut, windowResult.stderr) + XCTAssertEqual(windowResult.status, 1) + XCTAssertTrue( + windowResult.stderr.contains("list-terminals requires --window "), + windowResult.stderr + ) + } + func testSSHSessionListAllWorkspacesReportsQueryErrors() throws { let cliPath = try bundledCLIPath() let socketPath = makeSocketPath("sshlist") From 617da6e1880929af660e743491994031406f5cb7 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sun, 14 Jun 2026 12:51:04 -0700 Subject: [PATCH 09/13] chore: refresh Swift file length budget --- .github/swift-file-length-budget.tsv | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/swift-file-length-budget.tsv b/.github/swift-file-length-budget.tsv index ea13c6d70701..92d5a798d4af 100644 --- a/.github/swift-file-length-budget.tsv +++ b/.github/swift-file-length-budget.tsv @@ -1,7 +1,7 @@ # cmux-owned Swift file length budget. # Format: max_linesrelative path # Reduce counts as files shrink. CI fails if tracked files exceed this budget. -33857 CLI/cmux.swift +33913 CLI/cmux.swift 17914 Sources/AppDelegate.swift 16740 Sources/ContentView.swift 14612 Sources/TerminalController.swift @@ -9,7 +9,7 @@ 12088 Sources/GhosttyTerminalView.swift 12046 cmuxTests/AppDelegateShortcutRoutingTests.swift 11992 Sources/Workspace.swift -9331 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +9646 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift 7911 Sources/Panels/BrowserPanelView.swift 7350 cmuxTests/WorkspaceUnitTests.swift 6944 cmuxTests/WorkspaceRemoteConnectionTests.swift @@ -20,8 +20,8 @@ 6074 Sources/TextBoxInput.swift 5925 cmuxTests/TerminalAndGhosttyTests.swift 5522 cmuxTests/BrowserConfigTests.swift -4921 Sources/cmuxApp.swift 5113 Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift +4921 Sources/cmuxApp.swift 4460 Sources/Panels/FilePreviewPanel.swift 4400 cmuxTests/BrowserPanelTests.swift 4227 Sources/BrowserWindowPortal.swift @@ -32,7 +32,7 @@ 3664 Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift 3397 Sources/CmuxConfig.swift 3331 cmuxTests/TabManagerSessionSnapshotTests.swift -3204 Sources/Update/UpdateTitlebarAccessory.swift +3200 Sources/Update/UpdateTitlebarAccessory.swift 2878 Sources/SessionIndexView.swift 2871 cmuxTests/CMUXOpenCommandTests.swift 2573 Sources/KeyboardShortcutSettings.swift @@ -59,11 +59,11 @@ 1652 cmuxTests/CMUXCLIErrorOutputRegressionTests.swift 1574 cmuxTests/MarkdownPanelTests.swift 1560 cmuxTests/TextBoxMentionCompletionTests.swift -1498 cmuxTests/OmnibarAndToolsTests.swift +1497 cmuxTests/OmnibarAndToolsTests.swift 1496 cmuxUITests/MultiWindowNotificationsUITests.swift 1446 Sources/FileExplorerStore.swift -1380 cmuxUITests/MenuKeyEquivalentRoutingUITests.swift 1382 cmuxTests/KeyboardShortcutSettingsFileStoreStartupTests.swift +1380 cmuxUITests/MenuKeyEquivalentRoutingUITests.swift 1373 cmuxTests/AppDelegateIssue2907RoutingTests.swift 1366 Sources/Feed/FeedButtonStyleDebugWindowController.swift 1362 Sources/CMUXInstalledExtensionSidebarHostView.swift @@ -92,9 +92,9 @@ 937 Sources/TextBoxMentionIndexStore.swift 934 Sources/App/ShortcutRoutingSupport.swift 926 Sources/DockPanelView.swift +920 Sources/CommandPalette/CommandPaletteSettingsToggle.swift 919 Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeLifecycle.swift 918 cmuxTests/WorkspaceGroupTests.swift -920 Sources/CommandPalette/CommandPaletteSettingsToggle.swift 905 Sources/CmuxSSHURLRequest.swift 901 Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift 893 Sources/WorkspaceContentView.swift @@ -117,13 +117,14 @@ 752 cmuxUITests/CloseWorkspaceCmdDUITests.swift 746 Sources/App/MenuBarExtraController.swift 738 Packages/CMUXProjectModel/Sources/CMUXProjectModel/XcodeProjectAdapter.swift -738 Packages/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator.swift +736 Packages/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator.swift +726 cmuxTests/CLICodexHookTimeoutRegressionTests.swift 716 Sources/TaskManagerSnapshot.swift 715 Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+Input.swift 715 Sources/AppleScriptSupport.swift 710 Sources/TerminalSSHSessionDetector.swift -706 CLI/CMUXCLI+Config.swift 707 CLI/CMUXCLI+AgentHookDefinitions.swift +706 CLI/CMUXCLI+Config.swift 699 Sources/RightSidebarPanelView.swift 699 cmuxTests/TerminalNotificationClearAllTests.swift 698 cmuxTests/RestorableAgentHookProviderResumeTests.swift @@ -187,7 +188,6 @@ 528 cmuxTests/CLINotifyProcessTestSupport.swift 528 cmuxUITests/AutomationSocketUITests.swift 527 CLI/CLISocketPathResolver.swift -726 cmuxTests/CLICodexHookTimeoutRegressionTests.swift 523 Packages/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+PortScan.swift 520 CLI/CMUXCLI+AmpExtension.swift 520 cmuxTests/MainWindowVisibilityControllerTests.swift From 240eb8021bfb505509535d2ed9002431dcb3769d Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sun, 14 Jun 2026 12:54:31 -0700 Subject: [PATCH 10/13] chore: refresh Swift file length budget after merge --- .github/swift-file-length-budget.tsv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/swift-file-length-budget.tsv b/.github/swift-file-length-budget.tsv index 92d5a798d4af..7f3444e80821 100644 --- a/.github/swift-file-length-budget.tsv +++ b/.github/swift-file-length-budget.tsv @@ -3,7 +3,7 @@ # Reduce counts as files shrink. CI fails if tracked files exceed this budget. 33913 CLI/cmux.swift 17914 Sources/AppDelegate.swift -16740 Sources/ContentView.swift +16709 Sources/ContentView.swift 14612 Sources/TerminalController.swift 13595 Sources/Panels/BrowserPanel.swift 12088 Sources/GhosttyTerminalView.swift @@ -13,8 +13,8 @@ 7911 Sources/Panels/BrowserPanelView.swift 7350 cmuxTests/WorkspaceUnitTests.swift 6944 cmuxTests/WorkspaceRemoteConnectionTests.swift +6363 cmuxTests/GhosttyConfigTests.swift 6317 cmuxTests/SessionPersistenceTests.swift -6299 cmuxTests/GhosttyConfigTests.swift 6153 CLI/cmux_open.swift 6074 Sources/TabManager.swift 6074 Sources/TextBoxInput.swift From e96bcce359e37ca8d3d2dcf4ef59babe70174e07 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sun, 14 Jun 2026 13:04:26 -0700 Subject: [PATCH 11/13] fix: mark list surfaces from active tree pointer --- CLI/CMUXCLI+ListSurfaces.swift | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift index cfc445ff07f0..37f80a6f1a21 100644 --- a/CLI/CMUXCLI+ListSurfaces.swift +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -154,6 +154,7 @@ extension CMUXCLI { terminalOnly: Bool ) -> [[String: Any]] { let windows = payload["windows"] as? [[String: Any]] ?? [] + let activeSurfaceHandle = listSurfaceActiveHandle(payload) var result: [[String: Any]] = [] for window in windows { @@ -182,7 +183,8 @@ extension CMUXCLI { copyTreeListValue(into: &row, key: "window_index", from: window["index"]) copyTreeListValue(into: &row, key: "window_key", from: window["key"]) copyTreeListValue(into: &row, key: "pane_focused", from: pane["focused"]) - row["active"] = listSurfaceBool(surface["active"]) + row["active"] = listSurfaceMatchesHandle(surface, handle: activeSurfaceHandle) + || listSurfaceBool(surface["active"]) result.append(row) } } @@ -192,6 +194,35 @@ extension CMUXCLI { return result } + private func listSurfaceActiveHandle(_ payload: [String: Any]) -> String? { + guard let active = payload["active"] as? [String: Any] else { return nil } + return listSurfaceHandle(active, refKey: "surface_ref", idKey: "surface_id") + } + + private func listSurfaceMatchesHandle(_ surface: [String: Any], handle: String?) -> Bool { + guard let handle else { return false } + return ["ref", "id", "surface_ref", "surface_id"].contains { key in + listSurfaceHandleString(surface[key]) == handle + } + } + + private func listSurfaceHandle( + _ item: [String: Any], + refKey: String, + idKey: String + ) -> String? { + if let ref = listSurfaceHandleString(item[refKey]) { + return ref + } + return listSurfaceHandleString(item[idKey]) + } + + private func listSurfaceHandleString(_ value: Any?) -> String? { + guard let string = listSurfaceDebugString(value)?.trimmingCharacters(in: .whitespacesAndNewlines), + !string.isEmpty else { return nil } + return string + } + private func listSurfaceBool(_ value: Any?) -> Bool { guard let value, !(value is NSNull) else { return false } if let bool = value as? Bool { From 3df43fb58dcd70cd668e188be52f3dcb7b9cf7a6 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sun, 14 Jun 2026 13:33:10 -0700 Subject: [PATCH 12/13] fix: route list surface commands through CLI command paths --- .github/swift-file-length-budget.tsv | 6 +- CLI/CMUXCLI+ListSurfaces.swift | 16 ++- CLI/cmux.swift | 8 +- ...ifyProcessIntegrationRegressionTests.swift | 117 ++++++++++++++++++ cmuxTests/CLINotifyProcessTestSupport.swift | 2 + 5 files changed, 138 insertions(+), 11 deletions(-) diff --git a/.github/swift-file-length-budget.tsv b/.github/swift-file-length-budget.tsv index 7f3444e80821..4f7844c31699 100644 --- a/.github/swift-file-length-budget.tsv +++ b/.github/swift-file-length-budget.tsv @@ -1,7 +1,7 @@ # cmux-owned Swift file length budget. # Format: max_linesrelative path # Reduce counts as files shrink. CI fails if tracked files exceed this budget. -33913 CLI/cmux.swift +33917 CLI/cmux.swift 17914 Sources/AppDelegate.swift 16709 Sources/ContentView.swift 14612 Sources/TerminalController.swift @@ -9,7 +9,7 @@ 12088 Sources/GhosttyTerminalView.swift 12046 cmuxTests/AppDelegateShortcutRoutingTests.swift 11992 Sources/Workspace.swift -9646 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +9763 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift 7911 Sources/Panels/BrowserPanelView.swift 7350 cmuxTests/WorkspaceUnitTests.swift 6944 cmuxTests/WorkspaceRemoteConnectionTests.swift @@ -185,7 +185,7 @@ 531 Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift 531 Sources/App/WorkspaceRuntimeSettings.swift 530 Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttyRuntime.swift -528 cmuxTests/CLINotifyProcessTestSupport.swift +530 cmuxTests/CLINotifyProcessTestSupport.swift 528 cmuxUITests/AutomationSocketUITests.swift 527 CLI/CLISocketPathResolver.swift 523 Packages/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+PortScan.swift diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift index 37f80a6f1a21..d478acef2c39 100644 --- a/CLI/CMUXCLI+ListSurfaces.swift +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -15,12 +15,14 @@ extension CMUXCLI { client: SocketClient, jsonOutput: Bool, idFormat: CLIIDFormat, - terminalOnly: Bool + terminalOnly: Bool, + windowOverride: String? = nil ) throws { let options = try parseListSurfacesCommandOptions( commandArgs, commandName: commandName, - terminalOnly: terminalOnly + terminalOnly: terminalOnly, + windowOverride: windowOverride ) let payload = try buildListSurfacesPayload( options: options, @@ -41,7 +43,8 @@ extension CMUXCLI { private func parseListSurfacesCommandOptions( _ args: [String], commandName: String, - terminalOnly: Bool + terminalOnly: Bool, + windowOverride: String? ) throws -> ListSurfacesCommandOptions { let workspaceWasProvided = listSurfaceOptionWasProvided(args, name: "--workspace") let (workspaceOpt, rem0) = parseOption(args, name: "--workspace") @@ -87,18 +90,19 @@ extension CMUXCLI { defaultValue: "%@: unexpected argument '%@'" ), commandName, extra)) } - if includeAll, let scopedOption = windowOpt != nil ? "--window" : (workspaceOpt != nil ? "--workspace" : nil) { + let effectiveWindowOpt = windowOpt ?? windowOverride + if includeAll, let scopedOption = effectiveWindowOpt != nil ? "--window" : (workspaceOpt != nil ? "--workspace" : nil) { throw CLIError(message: String(format: String( localized: "cli.listSurfaces.error.windowWithAll", defaultValue: "%@: %@ cannot be combined with --all" ), commandName, scopedOption)) } - let includeAllByDefault = windowOpt == nil && workspaceOpt == nil + let includeAllByDefault = effectiveWindowOpt == nil && workspaceOpt == nil return ListSurfacesCommandOptions( includeAllWindows: includeAll || includeAllByDefault, workspaceHandle: workspaceOpt, - windowHandle: windowOpt, + windowHandle: effectiveWindowOpt, jsonOutput: jsonOutput, terminalOnly: terminalOnly ) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 601a7e90a6b6..61d4a8a6e3fd 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -4244,7 +4244,8 @@ struct CMUXCLI { client: client, jsonOutput: jsonOutput, idFormat: idFormat, - terminalOnly: false + terminalOnly: false, + windowOverride: windowId ) case "list-terminals": @@ -4254,7 +4255,8 @@ struct CMUXCLI { client: client, jsonOutput: jsonOutput, idFormat: idFormat, - terminalOnly: true + terminalOnly: true, + windowOverride: windowId ) case "tree": @@ -5377,6 +5379,8 @@ struct CMUXCLI { "list-panels", "list-panes", "list-status", + "list-surfaces", + "list-terminals", "list-windows", "list-workspaces", "log", diff --git a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift index 9574ad485304..92322aeb99cb 100644 --- a/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift +++ b/cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift @@ -4934,6 +4934,123 @@ final class CLINotifyProcessIntegrationRegressionTests: XCTestCase { XCTAssertTrue(result.stdout.contains("https://example.test"), result.stdout) } + func testListSurfacesHonorsGlobalWindowOverride() throws { + let cliPath = try bundledCLIPath() + let socketPath = makeSocketPath("listsurf-window") + let listenerFD = try bindUnixSocket(at: socketPath) + let state = MockSocketServerState() + let windowId = "11111111-1111-1111-1111-111111111111" + + defer { + Darwin.close(listenerFD) + unlink(socketPath) + } + + let serverHandled = startMockServer(listenerFD: listenerFD, state: state) { line in + guard let payload = self.jsonObject(line), + let id = payload["id"] as? String, + let method = payload["method"] as? String else { + return self.malformedRequestResponse(raw: line) + } + XCTAssertEqual(method, "system.tree") + let params = payload["params"] as? [String: Any] ?? [:] + XCTAssertEqual(params["all_windows"] as? Bool, false) + XCTAssertEqual(params["window_id"] as? String, windowId) + XCTAssertNil(params["workspace_id"]) + return self.v2Response( + id: id, + ok: true, + result: [ + "active": NSNull(), + "caller": NSNull(), + "windows": [], + ] + ) + } + + var environment = ProcessInfo.processInfo.environment + for key in Array(environment.keys) where key.hasPrefix("CMUX_") { + environment.removeValue(forKey: key) + } + environment["CMUX_SOCKET_PATH"] = socketPath + environment["CMUX_CLI_SENTRY_DISABLED"] = "1" + + let result = runProcess( + executablePath: cliPath, + arguments: ["--window", windowId, "list-surfaces", "--json"], + environment: environment, + timeout: 5 + ) + + wait(for: [serverHandled], timeout: 5) + XCTAssertFalse(result.timedOut, result.stderr) + XCTAssertEqual(result.status, 0, result.stderr) + XCTAssertTrue(result.stderr.isEmpty, result.stderr) + let payload = try jsonPayload(from: result.stdout) + XCTAssertEqual(payload["count"] as? Int, 0) + } + + func testListSurfacesCommandWinsOverSameNamedPath() throws { + let cliPath = try bundledCLIPath() + let socketPath = makeSocketPath("listsurf-path") + let listenerFD = try bindUnixSocket(at: socketPath) + let state = MockSocketServerState() + let root = try makeTemporaryDirectory(prefix: "cmux-list-surfaces-path") + try FileManager.default.createDirectory( + at: root.appendingPathComponent("list-surfaces", isDirectory: true), + withIntermediateDirectories: true + ) + + defer { + Darwin.close(listenerFD) + unlink(socketPath) + } + + let serverHandled = startMockServer(listenerFD: listenerFD, state: state) { line in + guard let payload = self.jsonObject(line), + let id = payload["id"] as? String, + let method = payload["method"] as? String else { + return self.malformedRequestResponse(raw: line) + } + XCTAssertEqual(method, "system.tree") + let params = payload["params"] as? [String: Any] ?? [:] + XCTAssertEqual(params["all_windows"] as? Bool, true) + XCTAssertNil(params["window_id"]) + XCTAssertNil(params["workspace_id"]) + return self.v2Response( + id: id, + ok: true, + result: [ + "active": NSNull(), + "caller": NSNull(), + "windows": [], + ] + ) + } + + var environment = ProcessInfo.processInfo.environment + for key in Array(environment.keys) where key.hasPrefix("CMUX_") { + environment.removeValue(forKey: key) + } + environment["CMUX_SOCKET_PATH"] = socketPath + environment["CMUX_CLI_SENTRY_DISABLED"] = "1" + + let result = runProcess( + executablePath: cliPath, + arguments: ["list-surfaces", "--json"], + environment: environment, + currentDirectoryURL: root, + timeout: 5 + ) + + wait(for: [serverHandled], timeout: 5) + XCTAssertFalse(result.timedOut, result.stderr) + XCTAssertEqual(result.status, 0, result.stderr) + XCTAssertTrue(result.stderr.isEmpty, result.stderr) + let payload = try jsonPayload(from: result.stdout) + XCTAssertEqual(payload["count"] as? Int, 0) + } + func testListSurfacesRejectsWorkspaceWithAll() throws { let cliPath = try bundledCLIPath() var environment = ProcessInfo.processInfo.environment diff --git a/cmuxTests/CLINotifyProcessTestSupport.swift b/cmuxTests/CLINotifyProcessTestSupport.swift index a0ae0ffac4c3..9c25122afe81 100644 --- a/cmuxTests/CLINotifyProcessTestSupport.swift +++ b/cmuxTests/CLINotifyProcessTestSupport.swift @@ -477,6 +477,7 @@ extension CLINotifyProcessIntegrationRegressionTests { executablePath: String, arguments: [String], environment: [String: String], + currentDirectoryURL: URL? = nil, standardInput: String? = nil, timeout: TimeInterval ) -> ProcessRunResult { @@ -487,6 +488,7 @@ extension CLINotifyProcessIntegrationRegressionTests { process.executableURL = URL(fileURLWithPath: executablePath) process.arguments = arguments process.environment = environment + process.currentDirectoryURL = currentDirectoryURL process.standardInput = stdinPipe ?? FileHandle.nullDevice process.standardOutput = stdoutPipe process.standardError = stderrPipe From 14e2d524872a4ae338f0287de5b88499df2df94f Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sun, 14 Jun 2026 14:06:00 -0700 Subject: [PATCH 13/13] fix: keep list surface routing socketless and focus-safe --- .github/swift-file-length-budget.tsv | 2 +- CLI/CMUXCLI+ListSurfaces.swift | 14 ++++++++++++++ CLI/cmux.swift | 12 ++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/swift-file-length-budget.tsv b/.github/swift-file-length-budget.tsv index 4f7844c31699..67f441155bae 100644 --- a/.github/swift-file-length-budget.tsv +++ b/.github/swift-file-length-budget.tsv @@ -1,7 +1,7 @@ # cmux-owned Swift file length budget. # Format: max_linesrelative path # Reduce counts as files shrink. CI fails if tracked files exceed this budget. -33917 CLI/cmux.swift +33929 CLI/cmux.swift 17914 Sources/AppDelegate.swift 16709 Sources/ContentView.swift 14612 Sources/TerminalController.swift diff --git a/CLI/CMUXCLI+ListSurfaces.swift b/CLI/CMUXCLI+ListSurfaces.swift index d478acef2c39..64535094fd37 100644 --- a/CLI/CMUXCLI+ListSurfaces.swift +++ b/CLI/CMUXCLI+ListSurfaces.swift @@ -40,6 +40,20 @@ extension CMUXCLI { } } + func validateListSurfacesCommandSyntax( + commandName: String, + commandArgs: [String], + terminalOnly: Bool, + windowOverride: String? = nil + ) throws { + _ = try parseListSurfacesCommandOptions( + commandArgs, + commandName: commandName, + terminalOnly: terminalOnly, + windowOverride: windowOverride + ) + } + private func parseListSurfacesCommandOptions( _ args: [String], commandName: String, diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 61d4a8a6e3fd..8a8e55018d25 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -3162,6 +3162,9 @@ struct CMUXCLI { if normalizedCommand == "surface", commandArgs.first?.lowercased() == "resume" { return false } + if normalizedCommand == "list-surfaces" || normalizedCommand == "list-terminals" { + return false + } return true } @@ -3304,6 +3307,15 @@ struct CMUXCLI { return } + if command == "list-surfaces" || command == "list-terminals" { + try validateListSurfacesCommandSyntax( + commandName: command, + commandArgs: commandArgs, + terminalOnly: command == "list-terminals", + windowOverride: windowId + ) + } + // If the argument is a path (not a known command), open a workspace there. if shouldOpenAsPathArgument(command), explicitSocketPath == nil { try openPath(command)