diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 6585353d..72cc04de 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -3379,14 +3379,18 @@ struct CMUXCLI { let workspaceArg = workspaceOpt ?? (windowOverride == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil) let workspaceId = try normalizeWorkspaceHandle(workspaceArg, client: client, allowCurrent: true) - let inferredTitle = positional.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines) - let title = (titleOpt ?? (inferredTitle.isEmpty ? nil : inferredTitle))?.trimmingCharacters(in: .whitespacesAndNewlines) + let inferredPositional = positional.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines) + let title = (titleOpt ?? (action == "rename" && !inferredPositional.isEmpty ? inferredPositional : nil))?.trimmingCharacters(in: .whitespacesAndNewlines) if action == "rename", (title?.isEmpty ?? true) { throw CLIError(message: "workspace-action rename requires --title (or a trailing title)") } - if action == "set_color", (colorOpt?.isEmpty ?? true) { - throw CLIError(message: "workspace-action set-color requires --color <#hex|name>") + + let color = ( + colorOpt ?? (action == "set_color" ? (inferredPositional.isEmpty ? nil : inferredPositional) : nil) + )?.trimmingCharacters(in: .whitespacesAndNewlines) + if action == "set_color", (color?.isEmpty ?? true) { + throw CLIError(message: "workspace-action set-color requires --color (or a trailing color)") } var params: [String: Any] = ["action": action] @@ -3396,7 +3400,7 @@ struct CMUXCLI { if let title, !title.isEmpty { params["title"] = title } - if let color = colorOpt, !color.isEmpty { + if let color, !color.isEmpty { params["color"] = color } @@ -3414,6 +3418,9 @@ struct CMUXCLI { if let index = payload["index"] { summaryParts.append("index=\(index)") } + if let color = payload["color"] as? String { + summaryParts.append("color=\(color)") + } printV2Payload(payload, jsonOutput: jsonOutput, idFormat: idFormat, fallbackText: summaryParts.joined(separator: " ")) } @@ -6260,15 +6267,21 @@ struct CMUXCLI { Flags: --action Action name (required if not positional) --workspace Target workspace (default: current/$CMUX_WORKSPACE_ID) - --title Title for rename (or pass trailing title text) - --color <#hex|name> Color for set-color (e.g. '#C0392B' or 'Red') + --title Title for rename + --color Color for set-color (name or #RRGGBB hex) + + Named colors: + Red, Crimson, Orange, Amber, Olive, Green, Teal, Aqua, + Blue, Navy, Indigo, Purple, Magenta, Rose, Brown, Charcoal Example: cmux workspace-action --workspace workspace:2 --action pin cmux workspace-action --action rename --title "infra" cmux workspace-action close-others - cmux workspace-action --action set-color --workspace workspace:1 --color '#C0392B' - cmux workspace-action --action clear-color --workspace workspace:1 + cmux workspace-action --action set-color --color blue + cmux workspace-action --action set-color --color "#C0392B" + cmux workspace-action set-color Amber + cmux workspace-action clear-color """ case "tab-action": return """ @@ -11981,7 +11994,7 @@ struct CMUXCLI { close-window --window move-workspace-to-window --workspace --window reorder-workspace --workspace (--index | --before | --after ) [--window ] - workspace-action --action [--workspace ] [--title ] [--color <#hex|name>] + workspace-action --action [--workspace ] [--title ] [--color ] list-workspaces new-workspace [--cwd ] [--command ] ssh [--name ] [--port <n>] [--identity <path>] [--ssh-option <opt>] [-- <remote-command-args>] diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index ce54dfe3..848adc5e 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -4085,29 +4085,30 @@ class TerminalController { finish() case "set_color": - guard let colorRaw = v2String(params, "color"), !colorRaw.isEmpty else { - result = .err(code: "invalid_params", message: "set-color requires --color", data: nil) + guard let colorRaw = v2String(params, "color"), + !colorRaw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + result = .err(code: "invalid_params", message: "Missing or invalid color", data: nil) return } - // Resolve named color to hex via palette lookup - let resolved: String - if colorRaw.hasPrefix("#") { - guard let normalized = WorkspaceTabColorSettings.normalizedHex(colorRaw) else { - result = .err(code: "invalid_params", message: "Invalid hex color '\(colorRaw)'. Expected #RRGGBB", data: nil) - return - } - resolved = normalized - } else if let entry = WorkspaceTabColorSettings.defaultPalette.first(where: { - $0.name.lowercased() == colorRaw.lowercased() + let colorInput = colorRaw.trimmingCharacters(in: .whitespacesAndNewlines) + // Resolve named colors from effective palette (includes user overrides, excludes custom entries) + let effectivePalette = WorkspaceTabColorSettings.defaultPaletteWithOverrides() + let hex: String + if let entry = effectivePalette.first(where: { + $0.name.caseInsensitiveCompare(colorInput) == .orderedSame }) { - resolved = entry.hex + hex = entry.hex + } else if let normalized = WorkspaceTabColorSettings.normalizedHex(colorInput) { + hex = normalized } else { - let names = WorkspaceTabColorSettings.defaultPalette.map(\.name).joined(separator: ", ") - result = .err(code: "invalid_params", message: "Unknown color '\(colorRaw)'. Use #RRGGBB or: \(names)", data: nil) + let colorNames = effectivePalette.map(\.name) + result = .err(code: "invalid_params", message: "Invalid color. Use a hex value (#RRGGBB) or a named color.", data: [ + "named_colors": colorNames + ]) return } - tabManager.setTabColor(tabId: workspace.id, color: resolved) - finish(["color": resolved]) + tabManager.setTabColor(tabId: workspace.id, color: hex) + finish(["color": hex]) case "clear_color": tabManager.setTabColor(tabId: workspace.id, color: nil)