feat(cli): shell completion for zsh, bash, and fish

AM
1d ago
+14366-3183

Summary

Ships shell autocompletion for the cmux CLI in zsh, bash, and fish. Completion covers commands, subcommands, and flag names, plus live cmux entities: workspace, surface, window, pane, and tab refs, theme names, and VM ids.

The foundation is the ArgumentParser migration already prescribed by docs/cli-contract.md, taken only as far as step 3: a parse-only facade that declares the full command tree, with every declared command's run() delegating to the existing CMUXCLI runner. Completion quality depends on the declared tree, not on whether an implementation was rewritten, so no command implementation moves in this PR.

Routing is unchanged from main for every command the facade does not implement. The router hands ArgumentParser only completion, the hidden __complete-candidates and __dump-command-tree, and the ---completion callback the generated scripts use. Every other invocation, declared or not, goes straight to the legacy parser. A declaration therefore shapes completion and typo suggestions but never runtime behavior: a declaration mistake can at worst complete the wrong thing, never reject an invocation the legacy runner accepts. A command family joins the routed set when step 4 moves its runner.

Design: docs/superpowers/specs/2026-08-21-cmux-shell-completion-design.md
Plan: docs/superpowers/plans/2026-08-21-cmux-shell-completion.md

What's in

  • Parse-only ArgumentParser facade under CLI/Commands/, declaring every command family (auth, meta, VM/remote, window, workspace, pane/surface, browser, tmux compat, hooks and agent launchers, notification, system), including every vm verb and sub-verb family, the --skill spellings of cmux guide and cmux vm guide, and the commands main added while this branch was open.
  • cmux completion <shell> plus dynamic completion handlers for live refs, themes, and VM ids, with a safety contract on candidate output.
  • Typo suggestions derived from the declared tree instead of a hand-maintained list.
  • Generated, committed CLI surface snapshot at docs/cli-command-tree.txt, guarded by tests/test_cli_command_tree_snapshot.py.
  • Parity tests wired into CI, so a dispatched or documented command added on main without a declaration fails CI instead of silently dropping out of completion:
    • tests/test_cli_dispatch_parity.py: every legacy top-level dispatch name, every command cmux help lists, every vm verb the legacy runner dispatches or cmux vm --help lists, the --flag spellings those two help sections document, and alias help equality.
    • tests/test_cli_facade_behavior_parity.py: facade and legacy exit codes and stderr match for 55 invocations, guarding that delegated commands keep reaching the legacy runner untouched.
    • tests/test_cli_completion_scripts.py: generated script syntax for all three shells.
  • Localized command abstracts and option help in Resources/Localizable.xcstrings.
  • Homebrew cask installs completions automatically, via generate_completions_from_executable in both cask generators (.github/workflows/update-homebrew.yml and scripts/build-sign-upload.sh). See below.
  • README install instructions, docs/cli-contract.md updates, and a cmux docs topic.
  • All 20 translated READMEs resynced. They were frozen at e1825d40d5 (2026-07-09) and were missing both the shell completion section and the cmux ssh --command flag from 1328104466. Structure now matches English exactly across all 21 files. The commercial-license paragraph narrowed by e897f771e1 is deliberately left untranslated here; it is a legal correction with a different reviewer and needs its own PR.

What's out

  • Moving command implementations out of CMUXCLI, and routing any delegated command through ArgumentParser.
  • Deleting the hand-rolled parser, deferred one release per the design's rollout section.
  • Any change to CmuxCLIPathInstaller or the command palette.

Install

Homebrew users get completions with no setup as of the first release containing this PR. brew install --cask cmux runs the CLI at install time and writes:

shellpath
bash#{HOMEBREW_PREFIX}/etc/bash_completion.d/cmux
zsh#{HOMEBREW_PREFIX}/share/zsh/site-functions/_cmux
fish#{HOMEBREW_PREFIX}/share/fish/vendor_completions.d/cmux.fish

brew uninstall removes them. Any other install method, add the matching line to your shell startup file:

# zsh
eval "$(cmux completion zsh)"
# bash
eval "$(cmux completion bash)"
# fish
cmux completion fish | source

Notes on the cask stanza

The executable is referenced as #{HOMEBREW_PREFIX}/bin/cmux, not the staged-relative path the Cask Cookbook example uses. Artifact::App sorts before Artifact::GeneratedCompletion in ORDINARY_ARTIFACT_CLASSES, and Moved#move leaves no symlink behind, so cmux.app is already out of the Caskroom by the time completions are generated. Artifact::Binary sorts earlier, so the prefix symlink does exist. This matters because write_completion only opoos on failure, so the relative form would have silently shipped no completions.

shell_parameter_format is deliberately unset: Homebrew then appends the bare shell name, producing exactly cmux completion bash|zsh|fish, and defaults the shell list to the three the CLI supports. :cobra builds the same argv but also requests pwsh, which the CLI rejects with exit 2.

The tap's Casks/cmux.rb is regenerated wholesale from a heredoc on every release, so no change to the tap repo is needed or would survive. Both generators had silently drifted apart (homepage and the macOS floor differed, so a local release would have flipped the published cask); they now emit byte-identical casks, and the two brew style offenses are fixed. depends_on macos: :sonoma is not a behavior change, since Cask::DSL::DependsOn#macos= parses with comparator: ">=".

Testing

CLI, on this HEAD, with only the cmux-cli target built. The full app build, a tagged reload, and dogfood are still pending.

  • Pass: test_cli_dispatch_parity, test_cli_facade_behavior_parity (55 cases), test_cli_command_tree_snapshot, test_cli_completion_scripts, test_cli_ssh_pty_attach_retry_suppression, test_cli_vm_resize, test_cli_vm_transfer_progress.
  • Fails, same as on main: test_cli_contract_help, on cmux vm guide and cmux vm --skill. The test expects google-chrome-stable --remote-debugging-port=9222, while main's cloud guide prints google-chrome-stable --no-first-run --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222. This branch touches neither file.
  • Facade and legacy output and exit codes are identical across 56 vm invocations covering every newly declared verb, alias, and sub-verb, -- payloads, = values, repeated options, and unknown sub-verbs.

Verified for the packaging change:

  • brew style and brew audit --cask on the cask, in a scratch tap. Clean apart from one pre-existing Cask/Desc offense ("Description shouldn't contain the platform"), left alone since rewording the product description is a separate call.
  • Both generators executed and their output diffed: identical modulo version/sha256. brew style on the rendered release cask is clean apart from that same desc offense.
  • Homebrew's install-time invocation simulated against the built CLI with every CMUX_* variable unset and no socket: exit 0, zero bytes on stderr, correct headers for all three shells. This is the risk that matters, because Homebrew writes captured stdout straight into the completion file, so any stray stdout would corrupt it.
  • Generated scripts parse: bash -n, zsh -n, and fish --no-execute (fish 4.8.1).

🤖 Generated with Claude Code on behalf of Adriano Machado (@ammachado)

This was generated by an AI agent and may contain inaccuracies. Please verify before relying on it.

Changelog

Added: Adds shell completion for zsh, bash, and fish.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Adds shell completion for zsh, bash, and fish to the cmux CLI, built on a parse-only swift-argument-parser facade. cmux completion <shell> prints scripts driven by a declared command tree that mirrors the legacy parser; only completion, __complete-candidates, __dump-command-tree, and the generated scripts' ---completion callback run through the facade, so a declaration can complete the wrong thing but never reject an invocation the legacy parser accepts.

  • Every other command still runs through the legacy CMUXCLI runner, including every top-level command main added mid-branch (automation, vpn, vault, read-selection, fork, local-tmux, agent, sudo, guide, sessions, paste, resize-window, current, glaeda, socket-status). tmux is restricted to attach, surface is its ls/open/new-terminal/resume namespace, all vm verbs and the --skill spellings are covered, and docs/settings/config/hooks candidates come from the same tables their runners resolve against.
  • Dynamic handlers complete workspace/surface/window/pane/tab refs, theme names, and VM ids; they use id ?? ref, time out at 0.5s, degrade silently, and drop names with control characters. --tab uses the caller's own surface or workspace from the environment cmux exports.
  • Typo suggestions derive from the declared tree and never suggest hidden commands; ssh-pty-attach retry noise suppression stays with the legacy runner, pinned by a regression test. cr stays a passthrough alias, gated separately from help aliases.
  • wait-for gains -L/-U with atomic O_CREAT|O_EXCL lock acquisition, lock names hashed from the raw channel name, and a failed -U unlock now errors. Global --name=value options route correctly, and the macOS app build on main is unbroken.
  • Adds cmux __dump-command-tree with a committed snapshot at docs/cli-command-tree.txt; CI gates command/flag/completion changes, script syntax, dispatch/alias/help parity, facade vs legacy exit-code and stderr parity, and the tmux alias and attach-retry regressions, with hermetic subprocess isolation.
  • Homebrew cask installs completions via generate_completions_from_executable in both cask generators; docs add a completion topic, CLI help is localized across all 20 locales, and translated READMEs now cover shell completion and SSH --command.

Rollout

  • Users: add eval "$(cmux completion zsh)" (zsh), eval "$(cmux completion bash)" (bash), or cmux completion fish | source (fish) to the shell startup file; Homebrew users get this automatically.
  • Maintainers: refresh docs/cli-command-tree.txt when commands or flags change; CI fails if the snapshot or parity checks go stale.

Summary by CodeRabbit

  • New Features

    • Added shell completion generation for bash, zsh, and fish.
    • Added dynamic suggestions for workspaces, windows, panes, surfaces, browser tabs, themes, and virtual machines.
    • Expanded CLI support across authentication, browser controls, notifications, workspace and window management, system operations, virtual machines, and more.
    • Added tmux-compatible commands and preserved legacy aliases.
    • Added command-tree inspection and improved command validation.
  • Documentation

    • Added shell completion setup instructions, command-tree reference documentation, and localized CLI help.
  • Bug Fixes

    • Improved compatibility between modern and legacy CLI command behavior.

[!NOTE]
Medium Risk
Large CLI surface area declared for completion only—parity tests mitigate drift, but wrong declarations could mislead users without changing runtime behavior; Homebrew install-time completion generation must stay stderr-clean.

Overview
Adds shell completion for zsh, bash, and fish via cmux completion <shell>, backed by a parse-only Swift ArgumentParser facade (CmuxCommand and CLI/Commands/*) that declares the full command tree while delegating execution to the existing CMUXCLI runner. Only facade-native entrypoints (completion, hidden __complete-candidates / __dump-command-tree) run through ArgumentParser; routing for everything else stays on the legacy path.

Completion behavior: static lists are wired from the same catalogs as runners (hooks, docs, settings, config); dynamic candidates query the app over the socket (workspaces, surfaces, windows, panes, tabs, themes, VMs) with tight timeouts and safe fallbacks. Typo suggestions now use CmuxCommand.declaredCommandNames instead of a hand-maintained set. Docs gain a completion topic; help text documents wait-for -L|-U.

Packaging/docs: Homebrew cask generation adds generate_completions_from_executable and normalizes homepage trailing slash; README (incl. Japanese) documents completion setup and cmux ssh --command.

Reviewed by Cursor Bugbot for commit 402e57519b9eb6a79ad9fd83dcb8cd3311092652. Bugbot is set up for automated code reviews on this repo. Configure here.