From 66b02604425f815147c73df54f3b725b5ee6d5f0 Mon Sep 17 00:00:00 2001 From: Usman Ehtesham Gul Date: Thu, 26 Mar 2026 18:18:20 -0400 Subject: [PATCH] Add --name flag to new-workspace CLI command (#2160) Allow naming a workspace at creation time instead of requiring a separate rename-workspace call afterward. Threads a title parameter through: - CLI: --name flag parsed and sent as "title" in v2 params - V2 handler: extracts title, passes to TabManager.addWorkspace() - TabManager: uses provided title instead of auto-generated "Terminal N" and calls setCustomTitle() to persist it - V1 handler: accepts optional name argument Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> --- CLI/cmux.swift | 14 ++++++++++---- Sources/TabManager.swift | 6 +++++- Sources/TerminalController.swift | 13 ++++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 9726d9ed..3ca612f3 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -1684,15 +1684,19 @@ struct CMUXCLI { case "new-workspace": let (commandOpt, rem0) = parseOption(commandArgs, name: "--command") - let (cwdOpt, remaining) = parseOption(rem0, name: "--cwd") + let (cwdOpt, rem1) = parseOption(rem0, name: "--cwd") + let (nameOpt, remaining) = parseOption(rem1, name: "--name") if let unknown = remaining.first(where: { $0.hasPrefix("--") }) { - throw CLIError(message: "new-workspace: unknown flag '\(unknown)'. Known flags: --command <text>, --cwd <path>") + throw CLIError(message: "new-workspace: unknown flag '\(unknown)'. Known flags: --name <title>, --command <text>, --cwd <path>") } var params: [String: Any] = [:] if let cwdOpt { let resolved = resolvePath(cwdOpt) params["cwd"] = resolved } + if let nameOpt { + params["title"] = nameOpt + } let response = try client.sendV2(method: "workspace.create", params: params) let wsId = (response["workspace_ref"] as? String) ?? (response["workspace_id"] as? String) ?? "" print("OK \(wsId)") @@ -6338,16 +6342,18 @@ struct CMUXCLI { """ case "new-workspace": return """ - Usage: cmux new-workspace [--cwd <path>] [--command <text>] + Usage: cmux new-workspace [--name <title>] [--cwd <path>] [--command <text>] Create a new workspace in the current window. Flags: + --name <title> Set a custom name for the new workspace --cwd <path> Set the working directory for the new workspace --command <text> Send text+Enter to the new workspace after creation Example: cmux new-workspace + cmux new-workspace --name "Build Server" cmux new-workspace --cwd ~/projects/myapp cmux new-workspace --cwd . --command "npm test" """ @@ -12087,7 +12093,7 @@ struct CMUXCLI { reorder-workspace --workspace <id|ref|index> (--index <n> | --before <id|ref|index> | --after <id|ref|index>) [--window <id|ref|index>] workspace-action --action <name> [--workspace <id|ref|index>] [--title <text>] [--color <name|#hex>] list-workspaces - new-workspace [--cwd <path>] [--command <text>] + new-workspace [--name <title>] [--cwd <path>] [--command <text>] ssh <destination> [--name <title>] [--port <n>] [--identity <path>] [--ssh-option <opt>] [-- <remote-command-args>] remote-daemon-status [--os <darwin|linux>] [--arch <arm64|amd64>] new-split <left|right|up|down> [--workspace <id|ref>] [--surface <id|ref>] [--panel <id|ref>] diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index e221c1bb..e1be8e39 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -1199,6 +1199,7 @@ class TabManager: ObservableObject { @discardableResult func addWorkspace( + title: String? = nil, workingDirectory overrideWorkingDirectory: String? = nil, initialTerminalCommand: String? = nil, initialTerminalEnvironment: [String: String] = [:], @@ -1243,7 +1244,7 @@ class TabManager: ObservableObject { let ordinal = Self.nextPortOrdinal Self.nextPortOrdinal += 1 let newWorkspace = makeWorkspaceForCreation( - title: "Terminal \(nextTabCount)", + title: title ?? "Terminal \(nextTabCount)", workingDirectory: workingDirectory, portOrdinal: ordinal, configTemplate: inheritedConfig, @@ -1251,6 +1252,9 @@ class TabManager: ObservableObject { initialTerminalEnvironment: initialTerminalEnvironment ) newWorkspace.owningTabManager = self + if title != nil { + newWorkspace.setCustomTitle(title) + } wireClosedBrowserTracking(for: newWorkspace) if eagerLoadTerminal && !select { requestBackgroundWorkspaceLoad(for: newWorkspace.id) diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index f96588d9..d418d883 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -1673,7 +1673,7 @@ class TerminalController { return listWorkspaces() case "new_workspace": - return newWorkspace() + return newWorkspace(args) case "new_split": return newSplit(args) @@ -3336,10 +3336,14 @@ class TerminalController { cwd = nil } + let requestedTitle = v2RawString(params, "title")?.trimmingCharacters(in: .whitespacesAndNewlines) + let title = (requestedTitle?.isEmpty == false) ? requestedTitle : nil + var newId: UUID? let shouldFocus = v2FocusAllowed() v2MainSync { let ws = tabManager.addWorkspace( + title: title, workingDirectory: cwd, initialTerminalCommand: initialCommand, initialTerminalEnvironment: initialEnv, @@ -12037,13 +12041,16 @@ class TerminalController { return result.isEmpty ? "No workspaces" : result } - private func newWorkspace() -> String { + private func newWorkspace(_ args: String = "") -> String { guard let tabManager = tabManager else { return "ERROR: TabManager not available" } + let trimmed = args.trimmingCharacters(in: .whitespacesAndNewlines) + let title: String? = trimmed.isEmpty ? nil : trimmed + var newTabId: UUID? let focus = socketCommandAllowsInAppFocusMutations() DispatchQueue.main.sync { - let workspace = tabManager.addTab(select: focus, eagerLoadTerminal: !focus) + let workspace = tabManager.addWorkspace(title: title, select: focus, eagerLoadTerminal: !focus) newTabId = workspace.id } return "OK \(newTabId?.uuidString ?? "unknown")"