Fix --help flag executing commands instead of showing help (#657)

* Fix --help flag executing commands instead of showing help (#650)

The --help check fell through to command dispatch when subcommandUsage
returned nil for a command. Now --help always returns before dispatch,
printing a generic fallback when no specific help text exists.

Also adds missing subcommandUsage entries for: new-window, list-panes,
list-pane-surfaces, surface-health, trigger-flash, list-panels,
focus-panel, set-app-focus, ping, capabilities, identify, list-windows,
current-window, refresh-surfaces, current-workspace, list-notifications,
clear-notifications.

Closes #650

* Document all flags and options in CLI --help output

Every subcommand's help text now shows all accepted flags, options,
environment variable fallbacks, and positional arguments matching the
actual dispatch code. Also adds help entries for commands that were
returning the generic fallback (list-workspaces, list-panes,
list-pane-surfaces, surface-health, trigger-flash, list-panels,
focus-panel, set-app-focus, etc.)

* Show 'Unknown command' for invalid commands, add legacy alias help

Unknown commands now show "Unknown command 'X'. Run 'cmux help' to see
available commands." instead of the misleading "No detailed help
available." Also adds help entries for legacy browser aliases
(open-browser, navigate, etc.) pointing to 'cmux browser --help'.

* Audit all 89 CLI commands for complete help coverage

- Add missing `help` subcommandUsage entry
- Expand id|ref → id|ref|index for move-workspace-to-window,
  close-workspace, select-workspace, rename-workspace
- Document CMUX_WORKSPACE_ID/CMUX_TAB_ID/CMUX_SURFACE_ID env var
  defaults in tab-action, rename-workspace
- Expand browser help: get (--selector, --attr, --property),
  find (--name, --exact, --index), network route (--abort, --body),
  open/open-split/new env var defaults
- Remove duplicate rename-window help case (now handled by
  rename-workspace combined case)
- Upgrade regression test to auto-extract dispatch+subcommandUsage
  switches and flag any commands missing help entries
This commit is contained in:
Lawrence Chen 2026-02-27 17:38:33 -08:00 committed by GitHub
parent c5f749640a
commit 181574586e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 603 additions and 48 deletions

View file

@ -696,6 +696,8 @@ struct CMUXCLI {
if dispatchSubcommandHelp(command: command, commandArgs: commandArgs) {
return
}
print("Unknown command '\(command)'. Run 'cmux help' to see available commands.")
return
}
let client = SocketClient(path: socketPath)
@ -3394,10 +3396,59 @@ struct CMUXCLI {
throw CLIError(message: "Unable to resolve surface ID")
}
/// Return the help/usage text for a subcommand, or nil if the command has no
/// dedicated help (e.g. simple no-arg commands like `ping`).
/// Return the help/usage text for a subcommand, or nil if the command is unknown.
private func subcommandUsage(_ command: String) -> String? {
switch command {
case "ping":
return """
Usage: cmux ping
Check connectivity to the cmux socket server.
"""
case "capabilities":
return """
Usage: cmux capabilities
Print server capabilities as JSON.
"""
case "help":
return """
Usage: cmux help
Show top-level CLI usage and command list.
"""
case "identify":
return """
Usage: cmux identify [--workspace <id|ref|index>] [--surface <id|ref|index>] [--no-caller]
Print server identity and caller context details.
Flags:
--workspace <id|ref|index> Caller workspace context (default: $CMUX_WORKSPACE_ID)
--surface <id|ref|index> Caller surface context (default: $CMUX_SURFACE_ID)
--no-caller Omit caller context from the request
"""
case "list-windows":
return """
Usage: cmux list-windows
List open windows.
"""
case "current-window":
return """
Usage: cmux current-window
Print the currently selected window ID.
"""
case "new-window":
return """
Usage: cmux new-window
Create a new window.
Example:
cmux new-window
"""
case "focus-window":
return """
Usage: cmux focus-window --window <id|ref|index>
@ -3426,47 +3477,56 @@ struct CMUXCLI {
"""
case "move-workspace-to-window":
return """
Usage: cmux move-workspace-to-window --workspace <id|ref> --window <id|ref>
Usage: cmux move-workspace-to-window --workspace <id|ref|index> --window <id|ref|index>
Move a workspace to a different window.
Flags:
--workspace <id|ref> Workspace to move (required)
--window <id|ref> Target window (required)
--workspace <id|ref|index> Workspace to move (required)
--window <id|ref|index> Target window (required)
Example:
cmux move-workspace-to-window --workspace workspace:2 --window window:1
"""
case "move-surface":
return """
Usage: cmux move-surface --surface <id|ref|index> [flags]
Usage: cmux move-surface [--surface <id|ref|index> | <id|ref|index>] [flags]
Move a surface to a different pane, workspace, or window.
Flags:
--surface <id|ref|index> Surface to move (required)
--surface <id|ref|index> Surface to move (required unless passed positionally)
--pane <id|ref|index> Target pane
--workspace <id|ref|index> Target workspace
--window <id|ref|index> Target window
--before <id|ref|index> Place before this surface
--before-surface <id|ref|index>
Alias for --before
--after <id|ref|index> Place after this surface
--after-surface <id|ref|index>
Alias for --after
--index <n> Place at this index
--focus <true|false> Focus the surface after moving
Example:
cmux move-surface --surface surface:1 --workspace workspace:2
cmux move-surface --surface 0 --pane pane:2 --index 0
cmux move-surface surface:1 --pane pane:2 --index 0
"""
case "reorder-surface":
return """
Usage: cmux reorder-surface --surface <id|ref|index> [flags]
Usage: cmux reorder-surface [--surface <id|ref|index> | <id|ref|index>] [flags]
Reorder a surface within its pane.
Flags:
--surface <id|ref|index> Surface to reorder (required)
--surface <id|ref|index> Surface to reorder (required unless passed positionally)
--workspace <id|ref|index> Workspace context
--before <id|ref|index> Place before this surface
--before-surface <id|ref|index>
Alias for --before
--after <id|ref|index> Place after this surface
--after-surface <id|ref|index>
Alias for --after
--index <n> Place at this index
Example:
@ -3475,15 +3535,19 @@ struct CMUXCLI {
"""
case "reorder-workspace":
return """
Usage: cmux reorder-workspace --workspace <id|ref|index> [flags]
Usage: cmux reorder-workspace [--workspace <id|ref|index> | <id|ref|index>] [flags]
Reorder a workspace within its window.
Flags:
--workspace <id|ref|index> Workspace to reorder (required)
--workspace <id|ref|index> Workspace to reorder (required unless passed positionally)
--index <n> Place at this index
--before <id|ref|index> Place before this workspace
--before-workspace <id|ref|index>
Alias for --before
--after <id|ref|index> Place after this workspace
--after-workspace <id|ref|index>
Alias for --after
--window <id|ref|index> Window context
Example:
@ -3506,7 +3570,7 @@ struct CMUXCLI {
Flags:
--action <name> Action name (required if not positional)
--workspace <id|ref|index> Target workspace (default: current/$CMUX_WORKSPACE_ID)
--title <text> Title for rename
--title <text> Title for rename (or pass trailing title text)
Example:
cmux workspace-action --workspace workspace:2 --action pin
@ -3529,10 +3593,10 @@ struct CMUXCLI {
Flags:
--action <name> Action name (required if not positional)
--tab <id|ref|index> Target tab (accepts tab:<n> or surface:<n>; alias: --surface)
--tab <id|ref|index> Target tab (accepts tab:<n> or surface:<n>; default: $CMUX_TAB_ID, then $CMUX_SURFACE_ID, then focused tab)
--surface <id|ref|index> Alias for --tab (backward compatibility)
--workspace <id|ref|index> Workspace context (default: current/$CMUX_WORKSPACE_ID)
--title <text> Title for rename
--title <text> Title for rename (or pass trailing title text)
--url <url> Optional URL for new-browser-right
Example:
@ -3562,12 +3626,25 @@ struct CMUXCLI {
"""
case "new-workspace":
return """
Usage: cmux new-workspace
Usage: cmux new-workspace [--command <text>]
Create a new workspace in the current window.
Flags:
--command <text> Send text+Enter to the new workspace after creation
Example:
cmux new-workspace
cmux new-workspace --command "npm test"
"""
case "list-workspaces":
return """
Usage: cmux list-workspaces
List workspaces in the current window.
Example:
cmux list-workspaces
"""
case "new-split":
return """
@ -3584,6 +3661,33 @@ struct CMUXCLI {
cmux new-split right
cmux new-split down --workspace workspace:1
"""
case "list-panes":
return """
Usage: cmux list-panes [--workspace <id|ref>]
List panes in a workspace.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
Example:
cmux list-panes
cmux list-panes --workspace workspace:2
"""
case "list-pane-surfaces":
return """
Usage: cmux list-pane-surfaces [--workspace <id|ref>] [--pane <id|ref>]
List surfaces in a pane.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--pane <id|ref> Restrict to a specific pane (default: focused pane)
Example:
cmux list-pane-surfaces
cmux list-pane-surfaces --workspace workspace:2 --pane pane:1
"""
case "tree":
return """
Usage: cmux tree [flags]
@ -3612,16 +3716,17 @@ struct CMUXCLI {
"""
case "focus-pane":
return """
Usage: cmux focus-pane --pane <id|ref> [flags]
Usage: cmux focus-pane [--pane <id|ref> | <id|ref>] [flags]
Focus the specified pane.
Flags:
--pane <id|ref> Pane to focus (required)
--pane <id|ref> Pane to focus (required unless passed positionally)
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
Example:
cmux focus-pane --pane pane:2
cmux focus-pane pane:1
cmux focus-pane --pane pane:1 --workspace workspace:2
"""
case "new-pane":
@ -3685,26 +3790,87 @@ struct CMUXCLI {
cmux drag-surface-to-split --surface surface:1 right
cmux drag-surface-to-split --panel surface:2 down
"""
case "refresh-surfaces":
return """
Usage: cmux refresh-surfaces
Refresh surface snapshots for the focused workspace.
"""
case "surface-health":
return """
Usage: cmux surface-health [--workspace <id|ref>]
List health details for surfaces in a workspace.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
Example:
cmux surface-health
cmux surface-health --workspace workspace:2
"""
case "trigger-flash":
return """
Usage: cmux trigger-flash [--workspace <id|ref>] [--surface <id|ref>] [--panel <id|ref>]
Trigger the unread flash indicator for a surface.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--surface <id|ref> Target surface (default: $CMUX_SURFACE_ID)
--panel <id|ref> Alias for --surface
Example:
cmux trigger-flash
cmux trigger-flash --workspace workspace:2 --surface surface:3
"""
case "list-panels":
return """
Usage: cmux list-panels [--workspace <id|ref>]
List surfaces (panels) in a workspace.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
Example:
cmux list-panels
cmux list-panels --workspace workspace:2
"""
case "focus-panel":
return """
Usage: cmux focus-panel --panel <id|ref> [--workspace <id|ref>]
Focus a specific panel (surface).
Flags:
--panel <id|ref> Panel/surface to focus (required)
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
Example:
cmux focus-panel --panel surface:2
cmux focus-panel --panel surface:5 --workspace workspace:2
"""
case "close-workspace":
return """
Usage: cmux close-workspace --workspace <id|ref>
Usage: cmux close-workspace --workspace <id|ref|index>
Close the specified workspace.
Flags:
--workspace <id|ref> Workspace to close (required)
--workspace <id|ref|index> Workspace to close (required)
Example:
cmux close-workspace --workspace workspace:2
"""
case "select-workspace":
return """
Usage: cmux select-workspace --workspace <id|ref>
Usage: cmux select-workspace --workspace <id|ref|index>
Select (switch to) the specified workspace.
Flags:
--workspace <id|ref> Workspace to select (required)
--workspace <id|ref|index> Workspace to select (required)
Example:
cmux select-workspace --workspace workspace:2
@ -3712,51 +3878,210 @@ struct CMUXCLI {
"""
case "rename-workspace", "rename-window":
return """
Usage: cmux rename-workspace [--workspace <id|ref>] [--] <title>
Usage: cmux rename-workspace [--workspace <id|ref|index>] [--] <title>
Rename a workspace. Defaults to the current workspace.
tmux-compatible alias: rename-window
Flags:
--workspace <id|ref> Workspace to rename (default: current workspace)
--workspace <id|ref|index> Workspace to rename (default: current/$CMUX_WORKSPACE_ID)
Example:
cmux rename-workspace "backend logs"
cmux rename-window --workspace workspace:2 "agent run"
"""
case "current-workspace":
return """
Usage: cmux current-workspace
Print the currently selected workspace ID.
"""
case "capture-pane":
return """
Usage: cmux capture-pane [--workspace <id|ref>] [--surface <id|ref>] [--scrollback] [--lines <n>]
tmux-compatible alias for reading terminal text from a pane.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--surface <id|ref> Surface context (default: $CMUX_SURFACE_ID)
--scrollback Include scrollback
--lines <n> Return only the last N lines (implies --scrollback)
Example:
cmux capture-pane --workspace workspace:2 --surface surface:1 --scrollback --lines 200
"""
case "resize-pane":
return """
Usage: cmux resize-pane --pane <id|ref> [--workspace <id|ref>] (-L|-R|-U|-D) [--amount <n>]
Usage: cmux resize-pane [--pane <id|ref>] [--workspace <id|ref>] [-L|-R|-U|-D] [--amount <n>]
tmux-compatible pane resize command.
Note: currently returns not_supported until programmable divider resize is implemented.
Flags:
--pane <id|ref> Pane to resize (default: focused pane)
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
-L|-R|-U|-D Direction (default: -R)
--amount <n> Resize amount (default: 1)
"""
case "pipe-pane":
return """
Usage: cmux pipe-pane --command <shell-command> [--workspace <id|ref>] [--surface <id|ref>]
Usage: cmux pipe-pane [--workspace <id|ref>] [--surface <id|ref>] [--command <shell-command> | <shell-command>]
Capture pane text and pipe it to a shell command via stdin.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--surface <id|ref> Surface context (default: focused surface)
--command <command> Shell command to run (or pass as trailing text)
"""
case "wait-for":
return """
Usage: cmux wait-for [-S|--signal] <name> [--timeout <seconds>]
Wait for or signal a named synchronization token.
"""
case "swap-pane", "break-pane", "join-pane", "next-window", "previous-window", "last-window", "last-pane", "find-window", "clear-history", "set-hook", "popup", "bind-key", "unbind-key", "copy-mode", "set-buffer", "paste-buffer", "list-buffers", "respawn-pane", "display-message":
return """
Usage: cmux \(command) --help
tmux compatibility command. See `cmux --help` for exact syntax.
Flags:
-S, --signal Signal the token instead of waiting
--timeout <seconds> Wait timeout (default: 30)
"""
case "swap-pane":
return """
Usage: cmux swap-pane --pane <id|ref> --target-pane <id|ref> [--workspace <id|ref>]
Swap two panes.
Flags:
--pane <id|ref> Source pane (required)
--target-pane <id|ref> Target pane (required)
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
"""
case "break-pane":
return """
Usage: cmux break-pane [--workspace <id|ref>] [--pane <id|ref>] [--surface <id|ref>] [--no-focus]
Move a pane/surface out into its own pane context.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--pane <id|ref> Source pane
--surface <id|ref> Source surface
--no-focus Do not focus the result
"""
case "join-pane":
return """
Usage: cmux join-pane --target-pane <id|ref> [--workspace <id|ref>] [--pane <id|ref>] [--surface <id|ref>] [--no-focus]
Join a pane/surface into another pane.
Flags:
--target-pane <id|ref> Target pane (required)
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--pane <id|ref> Source pane
--surface <id|ref> Source surface
--no-focus Do not focus the result
"""
case "next-window", "previous-window", "last-window":
return """
Usage: cmux \(command)
Switch workspace selection (next/previous/last) in the current window.
"""
case "last-pane":
return """
Usage: cmux last-pane [--workspace <id|ref>]
Focus the previously focused pane in a workspace.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
"""
case "find-window":
return """
Usage: cmux find-window [--content] [--select] [query]
Find workspaces by title (and optionally terminal content).
Flags:
--content Search terminal content in addition to workspace titles
--select Select the first match
"""
case "clear-history":
return """
Usage: cmux clear-history [--workspace <id|ref>] [--surface <id|ref>]
Clear terminal scrollback history.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--surface <id|ref> Surface context (default: focused surface)
"""
case "set-hook":
return """
Usage: cmux set-hook [--list] [--unset <event>] | <event> <command>
Manage tmux-compat hook definitions.
Flags:
--list List configured hooks
--unset <event> Remove a hook by event name
"""
case "popup":
return """
Usage: cmux popup
tmux compatibility placeholder. This command is currently not supported.
"""
case "bind-key", "unbind-key", "copy-mode":
return """
Usage: cmux \(command)
tmux compatibility placeholder. This command is currently not supported.
"""
case "set-buffer":
return """
Usage: cmux set-buffer [--name <name>] [--] <text>
Save text into a named tmux-compat buffer.
Flags:
--name <name> Buffer name (default: default)
"""
case "paste-buffer":
return """
Usage: cmux paste-buffer [--name <name>] [--workspace <id|ref>] [--surface <id|ref>]
Paste a named tmux-compat buffer into a surface.
Flags:
--name <name> Buffer name (default: default)
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--surface <id|ref> Surface context (default: focused surface)
"""
case "list-buffers":
return """
Usage: cmux list-buffers
List tmux-compat buffers.
"""
case "respawn-pane":
return """
Usage: cmux respawn-pane [--workspace <id|ref>] [--surface <id|ref>] [--command <cmd> | <cmd>]
Send a command (or default shell restart command) to a surface.
Flags:
--workspace <id|ref> Workspace context (default: $CMUX_WORKSPACE_ID)
--surface <id|ref> Surface context (default: focused surface)
--command <cmd> Command text (or pass trailing command text)
"""
case "display-message":
return """
Usage: cmux display-message [-p|--print] <text>
Print text (or show it via notification bridge in parity mode).
Flags:
-p, --print Print to stdout only
"""
case "read-screen":
return """
@ -3846,6 +4171,18 @@ struct CMUXCLI {
cmux notify --title "Build done" --body "All tests passed"
cmux notify --title "Error" --subtitle "test.swift" --body "Line 42: syntax error"
"""
case "list-notifications":
return """
Usage: cmux list-notifications
List queued notifications.
"""
case "clear-notifications":
return """
Usage: cmux clear-notifications
Clear all queued notifications.
"""
case "set-status":
return """
Usage: cmux set-status <key> <value> [flags]
@ -3970,16 +4307,35 @@ struct CMUXCLI {
cmux sidebar-state
cmux sidebar-state --workspace workspace:2
"""
case "set-app-focus":
return """
Usage: cmux set-app-focus <active|inactive|clear>
Override app focus state for notification routing tests.
Example:
cmux set-app-focus inactive
cmux set-app-focus clear
"""
case "simulate-app-active":
return """
Usage: cmux simulate-app-active
Trigger the app-active handler used by notification focus tests.
"""
case "claude-hook":
return """
Usage: cmux claude-hook <session-start|stop|notification> [flags]
Usage: cmux claude-hook <session-start|active|stop|idle|notification|notify> [flags]
Hook for Claude Code integration. Reads JSON from stdin.
Subcommands:
session-start Signal that a Claude session has started
active Alias for session-start
stop Signal that a Claude session has stopped
idle Alias for stop
notification Forward a Claude notification
notify Alias for notification
Flags:
--workspace <id|ref> Target workspace (default: $CMUX_WORKSPACE_ID)
@ -3994,29 +4350,81 @@ struct CMUXCLI {
Usage: cmux browser [--surface <id|ref|index> | <surface>] <subcommand> [args]
Browser automation commands. Most subcommands require a surface handle.
A surface can be passed as `--surface <handle>` or as the first positional token.
`open`/`open-split`/`new`/`identify` can run without an explicit surface.
Subcommands:
open [url] Create browser split (or navigate if surface given)
open-split [url] Create browser in a new split
goto|navigate <url> Navigate to URL [--snapshot-after]
back|forward|reload History navigation [--snapshot-after]
url|get-url Get current URL
snapshot Get DOM snapshot [--interactive|-i] [--cursor] [--compact] [--max-depth <n>] [--selector <css>]
eval <script> Evaluate JavaScript
wait Wait for condition [--selector] [--text] [--url-contains] [--timeout-ms]
click|dblclick|hover <sel> Mouse actions [--snapshot-after]
type <selector> <text> Type text [--snapshot-after]
fill <selector> [text] Fill input [--snapshot-after]
press|keydown|keyup <key> Keyboard actions [--snapshot-after]
get <property> [selector] Get page properties (url|title|text|html|value|attr|count|box|styles)
find <strategy> <query> Find elements (role|text|label|placeholder|testid|first|last|nth)
identify Identify browser surface
open|open-split|new [url] [--workspace <id|ref|index>] [--window <id|ref|index>]
open/open-split/new default to $CMUX_WORKSPACE_ID when --workspace is omitted and --window is not set
goto|navigate <url> [--snapshot-after]
back|forward|reload [--snapshot-after]
url|get-url
focus-webview | is-webview-focused
snapshot [--interactive|-i] [--cursor] [--compact] [--max-depth <n>] [--selector <css>]
eval [--script <js> | <js>]
wait [--selector <css>] [--text <text>] [--url-contains <text>|--url <text>] [--load-state <interactive|complete>] [--function <js>] [--timeout-ms <ms>|--timeout <seconds>]
click|dblclick|hover|focus|check|uncheck|scroll-into-view [--selector <css> | <css>] [--snapshot-after]
type|fill [--selector <css> | <css>] [--text <text> | <text>] [--snapshot-after]
press|key|keydown|keyup [--key <key> | <key>] [--snapshot-after]
select [--selector <css> | <css>] [--value <value> | <value>] [--snapshot-after]
scroll [--selector <css>] [--dx <n>] [--dy <n>] [--snapshot-after]
screenshot [--out <path>]
get <url|title|text|html|value|attr|count|box|styles> [...]
text|html|value|count|box|styles|attr: [--selector <css> | <css>]
attr: [--attr <name> | <name>]
styles: [--property <name>]
is <visible|enabled|checked> [--selector <css> | <css>]
find <role|text|label|placeholder|alt|title|testid|first|last|nth> [...]
role: [--name <text>] [--exact] <role>
text|label|placeholder|alt|title|testid: [--exact] <text>
first|last: [--selector <css> | <css>]
nth: [--index <n> | <n>] [--selector <css> | <css>]
frame <main|selector> [--selector <css>]
dialog <accept|dismiss> [text]
download [wait] [--path <path>] [--timeout-ms <ms>|--timeout <seconds>]
cookies <get|set|clear> [--name <name>] [--value <value>] [--url <url>] [--domain <domain>] [--path <path>] [--expires <unix>] [--secure] [--all]
storage <local|session> <get|set|clear> [...]
tab <new|list|switch|close|<index>> [...]
console <list|clear>
errors <list|clear>
highlight [--selector <css> | <css>]
state <save|load> <path>
addinitscript|addscript [--script <js> | <js>]
addstyle [--css <css> | <css>]
viewport <width> <height>
geolocation|geo <latitude> <longitude>
offline <true|false>
trace <start|stop> [path]
network <route|unroute|requests> ...
route <pattern> [--abort] [--body <text>]
unroute <pattern>
screencast <start|stop>
input <mouse|keyboard|touch> [args...]
input_mouse | input_keyboard | input_touch
identify [--surface <id|ref|index>]
Example:
cmux browser open https://example.com
cmux browser surface:1 navigate https://google.com
cmux browser --surface surface:1 snapshot --interactive
"""
// Legacy browser aliases point users to `cmux browser --help`
case "open-browser":
return "Legacy alias for 'cmux browser open'. Run 'cmux browser --help' for details."
case "navigate":
return "Legacy alias for 'cmux browser navigate'. Run 'cmux browser --help' for details."
case "browser-back":
return "Legacy alias for 'cmux browser back'. Run 'cmux browser --help' for details."
case "browser-forward":
return "Legacy alias for 'cmux browser forward'. Run 'cmux browser --help' for details."
case "browser-reload":
return "Legacy alias for 'cmux browser reload'. Run 'cmux browser --help' for details."
case "get-url":
return "Legacy alias for 'cmux browser get-url'. Run 'cmux browser --help' for details."
case "focus-webview":
return "Legacy alias for 'cmux browser focus-webview'. Run 'cmux browser --help' for details."
case "is-webview-focused":
return "Legacy alias for 'cmux browser is-webview-focused'. Run 'cmux browser --help' for details."
default:
return nil
}

View file

@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""Regression tests for CLI subcommand help coverage and accuracy."""
from __future__ import annotations
import re
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def extract_switch_commands(content: str, start_index: int = 0) -> tuple[set[str], int]:
marker = "switch command {"
marker_index = content.find(marker, start_index)
if marker_index == -1:
return set(), -1
open_brace = content.find("{", marker_index)
if open_brace == -1:
return set(), -1
depth = 1
cursor = open_brace + 1
while cursor < len(content) and depth > 0:
char = content[cursor]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
cursor += 1
block = content[open_brace + 1:cursor - 1]
commands: set[str] = set()
collecting_case = False
case_lines: list[str] = []
for line in block.splitlines():
stripped = line.strip()
if stripped.startswith("case "):
collecting_case = True
case_lines = [line]
elif collecting_case:
case_lines.append(line)
if collecting_case and ":" in line:
case_text = "\n".join(case_lines)
commands.update(re.findall(r'"([^"]+)"', case_text))
collecting_case = False
case_lines = []
return commands, cursor
def main() -> int:
repo_root = get_repo_root()
cli_path = repo_root / "CLI" / "cmux.swift"
if not cli_path.exists():
print(f"FAIL: missing expected file: {cli_path}")
return 1
content = cli_path.read_text(encoding="utf-8")
failures: list[str] = []
require(
content,
'if commandArgs.contains("--help") || commandArgs.contains("-h") {',
"Subcommand help pre-dispatch gate is missing",
failures,
)
require(
content,
'if dispatchSubcommandHelp(command: command, commandArgs: commandArgs) {',
"Subcommand help dispatch call is missing",
failures,
)
require(
content,
"print(\"Unknown command '\\(command)'. Run 'cmux help' to see available commands.\")",
"Subcommand help fallback unknown-command line is missing",
failures,
)
require(
content,
"print(\"Unknown command '\\(command)'. Run 'cmux help' to see available commands.\")\n return",
"Subcommand help fallback must return before command execution",
failures,
)
dispatch_commands, next_index = extract_switch_commands(content, 0)
subcommand_usage_commands, _ = extract_switch_commands(content, next_index if next_index != -1 else 0)
if not dispatch_commands:
failures.append("Failed to parse main dispatch switch command list")
if not subcommand_usage_commands:
failures.append("Failed to parse subcommandUsage switch command list")
missing_help_entries = sorted(dispatch_commands - subcommand_usage_commands)
if missing_help_entries:
failures.append(
"Missing subcommandUsage entries for dispatch command(s): "
+ ", ".join(missing_help_entries)
)
# Regression checks for concrete help text that previously drifted from dispatch logic.
for needle, message in [
('case "help":', "Missing subcommandUsage entry for help"),
("Usage: cmux help", "help subcommand usage text is missing"),
("Usage: cmux move-workspace-to-window --workspace <id|ref|index> --window <id|ref|index>", "move-workspace-to-window help must document index handles"),
("--tab <id|ref|index> Target tab (accepts tab:<n> or surface:<n>; default: $CMUX_TAB_ID, then $CMUX_SURFACE_ID, then focused tab)", "tab-action help must document CMUX_TAB_ID/CMUX_SURFACE_ID fallback"),
("--workspace <id|ref|index> Workspace to rename (default: current/$CMUX_WORKSPACE_ID)", "rename-workspace help must document CMUX_WORKSPACE_ID fallback"),
("text|html|value|count|box|styles|attr: [--selector <css> | <css>]", "browser get help must document --selector"),
("attr: [--attr <name> | <name>]", "browser get attr help must document --attr"),
("styles: [--property <name>]", "browser get styles help must document --property"),
("role: [--name <text>] [--exact] <role>", "browser find role help must document --name/--exact"),
("text|label|placeholder|alt|title|testid: [--exact] <text>", "browser find text-like help must document --exact"),
("nth: [--index <n> | <n>] [--selector <css> | <css>]", "browser find nth help must document --index/--selector"),
("route <pattern> [--abort] [--body <text>]", "browser network route help must document --abort/--body"),
]:
require(content, needle, message, failures)
if failures:
print("FAIL: CLI subcommand help regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: CLI subcommand help coverage and flag/env documentation are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())