Repository navigation
Add self-maintaining shell completions for the cmux CLI (bash/zsh/fish) - #6814
austinywang wants to merge 14 commits into
Conversation
The cmux CLI has ~150 top-level commands but no shell completions, making its large surface area hard to navigate. Add bash/zsh/fish completions that are *generated, never hand-maintained*, so they can't rot: - Command names come from the authoritative `topLevelCommandNames` registry in `CLI/cmux.swift`. - Per-command flags, first-level subcommands, and enum flag values are parsed best-effort from the `usage()` help heredoc in the same file (no built binary required, so it works in CI). - `tests/test_cli_completions_contract.py` regenerates from source and fails on drift, so adding a command without regenerating turns CI red. This is what keeps the scripts in sync without anyone having to remember. What completes: `cmux <Tab>` (all commands), `cmux send --<Tab>` (flags), `cmux auth <Tab>` -> `login logout status`, `cmux diff --source <Tab>` -> `unstaged staged branch last-turn`. Touches no Swift. The generator's input can later be swapped to ArgumentParser's `--generate-completion-script` once #3254 reaches the subcommand tree, after which the generator retires. Implements the reference design contributed in #6616. Fixes #6616 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 16 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds generated bash, zsh, and fish completion scripts for Changescmux shell completions
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Workflow as workflow-guard-tests job
participant ContractTest as tests/test_cli_completions_contract.py
participant Generator as scripts/generate-cli-completions.py
participant SwiftSource as CLI/cmux.swift
participant BashOut as completions/cmux.bash
participant ZshOut as completions/cmux.zsh
participant FishOut as completions/cmux.fish
Workflow->>ContractTest: run contract test
ContractTest->>Generator: regenerate bash, zsh, fish
Generator->>SwiftSource: read topLevelCommandNames and usage()
ContractTest->>BashOut: compare regenerated output
ContractTest->>ZshOut: compare regenerated output
ContractTest->>FishOut: compare regenerated output
Possibly related PRs
🚥 Pre-merge checks | ✅ 24 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (24 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR adds self-maintaining bash/zsh/fish tab-completions for the
Confidence Score: 4/5Safe to merge with a straightforward follow-up to install zsh and fish in the CI step so syntax validation actually runs for those shells. The generator logic, all three emitters, the drift check, the registry-coverage guard, and the Swift registry additions are all correct. The gap is that the CI contract step does not install zsh or fish, so shell-syntax checks for those two completion scripts silently no-op on ubuntu-latest. A bug introduced into the zsh or fish emitter after a --write regeneration would pass CI undetected. tests/test_cli_completions_contract.py and .github/workflows/ci.yml — the CI step needs to install zsh and fish so syntax checks run rather than silently skip. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["CLI/CMUXCLI+CommandSuggestions.swift\ntopLevelCommandNames registry"] -->|parse_registry| G
B["CLI/cmux.swift\nusage() heredoc"] -->|extract_usage_heredoc| G
G["generate-cli-completions.py\nbuild()"]
G -->|emit_bash| BA["completions/cmux.bash"]
G -->|emit_zsh| ZS["completions/cmux.zsh"]
G -->|emit_fish| FI["completions/cmux.fish"]
CI["CI: workflow-guard-tests"] -->|python3| CT
CT["test_cli_completions_contract.py"]
CT -->|regenerate + byte-diff| BA
CT -->|regenerate + byte-diff| ZS
CT -->|regenerate + byte-diff| FI
CT -->|"--check: registry coverage"| B
CT -->|"bash -n always runs"| BA
CT -->|"zsh -n skipped if absent"| ZS
CT -->|"fish -n skipped if absent"| FI
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["CLI/CMUXCLI+CommandSuggestions.swift\ntopLevelCommandNames registry"] -->|parse_registry| G
B["CLI/cmux.swift\nusage() heredoc"] -->|extract_usage_heredoc| G
G["generate-cli-completions.py\nbuild()"]
G -->|emit_bash| BA["completions/cmux.bash"]
G -->|emit_zsh| ZS["completions/cmux.zsh"]
G -->|emit_fish| FI["completions/cmux.fish"]
CI["CI: workflow-guard-tests"] -->|python3| CT
CT["test_cli_completions_contract.py"]
CT -->|regenerate + byte-diff| BA
CT -->|regenerate + byte-diff| ZS
CT -->|regenerate + byte-diff| FI
CT -->|"--check: registry coverage"| B
CT -->|"bash -n always runs"| BA
CT -->|"zsh -n skipped if absent"| ZS
CT -->|"fish -n skipped if absent"| FI
Reviews (14): Last reviewed commit: "Preserve top-level path completion" | Re-trigger Greptile |
| lines.append(" local context state line") | ||
| lines.append(" local -a commands") |
There was a problem hiding this comment.
The
local context state line declaration is left over from _arguments-style zsh completion that was never wired up. These three variables are never read or assigned after declaration, producing dead code in every generated cmux.zsh.
| lines.append(" local context state line") | |
| lines.append(" local -a commands") | |
| lines.append(" local -a commands") |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 3ecdc40 — removed the dead local context state line declaration from the zsh emitter.
— Claude Code
…aining-shell-completions-for-the
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@completions/cmux.bash`:
- Around line 7-12: Remove the unused shell variables from the cmux.bash
completion generator: in the logic around _init_completion and the fallback
assignment, drop words and cword from the local declaration unless they are
actually referenced later. Keep the generator output consistent with the
variables it uses so the generated completion script only declares cur and prev,
avoiding shellcheck SC2034.
- Line 23: The completion generator in cmux.bash is emitting an unsafe unquoted
array assignment for COMPREPLY, which triggers shellcheck SC2207 and can break
with future completions containing spaces. Update the generator logic around the
compgen/COMPREPLY emission to use the safer mapfile -t pattern with process
substitution instead of COMPREPLY=( $(...) ), preserving the existing behavior
while avoiding word-splitting.
In `@scripts/generate-cli-completions.py`:
- Around line 310-336: The zsh completion generator in emit_zsh is missing the
flag-value enum completions that emit_bash and emit_fish already provide. Update
the _cmux case handling to also consult spec.flag_values for each command and,
when the user is completing a value for a value-bearing flag, offer those enum
values instead of only subcommands and flag names. Use the existing emit_zsh,
CommandSpec, and specs structures to keep behavior aligned with the other shell
emitters.
- Around line 230-235: The subcommand detection in generate-cli-completions.py
is scanning the full help line and can mistake the first word of a description
for a real subcommand. Update the subcommand logic in the same parsing flow that
handles the command portion, and reuse the double-space boundary used by the
alias parsing branch so only the actual command text is tokenized. Make this
change in the subcommand detection path near the tokens/sub branch, keeping the
existing METAVARS and regex checks but applying them only to the trimmed command
segment.
- Around line 191-202: The `help_commands_region` helper uses an ambiguous loop
variable name (`l`) in the `next(...)` lookup, which triggers Ruff E741. Rename
that variable to something clear like `line` so the `help_commands_region` logic
remains unchanged while satisfying linting in CI.
- Around line 164-179: Make the registry parsing in parse_registry more robust
by updating REGISTRY_RE to accept an optional trailing comma so the final
command name is still captured when the Swift array literal omits it. Also
rename the ambiguous loop variable in parse_registry from l to line for
readability and to avoid E741. If you touch the completion generators, keep
emit_zsh consistent with the Bash/Fish behavior by adding flag_values support
when the help output includes --flag <value>.
In `@tests/test_cli_completions_contract.py`:
- Around line 86-95: The completion contract check currently only scans the
regenerated bash script for token matches, so it can miss commands that are
absent from the actual top-level completion table and it ignores zsh/fish
entirely. Update the test logic around the regenerated shell outputs to validate
each shipped shell’s top-level command list directly, using the existing
regeneration structure in test_cli_completions_contract.py instead of a
full-script substring search, and make sure the visible command coverage
assertion runs against bash, zsh, and fish.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: fcb2624e-fdac-4dcf-a53f-846122210716
📒 Files selected for processing (7)
.github/workflows/ci.ymlcompletions/README.mdcompletions/cmux.bashcompletions/cmux.fishcompletions/cmux.zshscripts/generate-cli-completions.pytests/test_cli_completions_contract.py
Fixes raised by Codex/Greptile on the completions PR: - zsh emitter now wires up flag-value enum completions (e.g. `diff --source <Tab>` -> unstaged/staged/branch/last-turn) to match bash/fish, and drops the dead `local context state line` left over from `_arguments`-style completion. - parse_help now captures unbracketed `a|b|c` subcommand groups, so first-level subcommands like `feed tui|clear`, `browser goto|navigate`, and `browser url|get-url` are completed. - help_commands_region now stops at the next section header regardless of indentation, so the source-heredoc and `cmux help` (binary) input modes agree on the Commands region (previously the heredoc path ran into Environment:). - Added `remote`, `remotes`, and `simulate-sidebar-drag` to topLevelCommandNames — they are dispatched (`case "remotes", "remote"`, `case "simulate-sidebar-drag"`) and documented in usage() but were missing from the registry, so completions (and shouldOpenAsPathArgument) did not know them. - Added a registry-coverage drift guard: `generate-cli-completions.py --check` fails if usage() documents a top-level command absent from topLevelCommandNames, and the contract test runs it. This makes the "self-maintaining" promise cover the command registry itself. Budget refreshed via scripts/swift_file_length_budget.py --write-budget for the 3-line registry addition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address two edge-case defects raised in review: - All three emitters now locate the command word by scanning past cmux's value-bearing global options (`--socket`, `--id-format`, `--window`, `--password`) and their values, instead of taking the first non-option word (bash/fish) or a fixed `words[2]` position (zsh). So `cmux --socket <path> send --<Tab>` now completes `send`'s flags rather than treating the socket path as the command. Verified at runtime for bash. - The fish emitter now resolves the active top-level command by position via a `__cmux_command` helper and matches it with `__cmux_command_is`, replacing `__fish_seen_subcommand_from` which matched a word anywhere on the line. So `cmux docs browser <Tab>` no longer also fires the top-level `browser` automation completions. The helpers use a quoted local var so an empty command substitution can't malform `test`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`set-hook` is a documented, dispatched tmux-compat command, but it was listed in INTERNAL_COMMANDS and therefore filtered out of every generated completion script. Remove it so `cmux set-hook <Tab>` works. This also exposed that the registry-coverage guard only checked the raw registry, so it could not catch a documented command that was filtered as internal. Tighten `registry_coverage_gaps` to require every documented top-level command to be in `visible_commands()` (i.e. actually completable), catching both "absent from topLevelCommandNames" and "wrongly filtered as internal". Verified the guard flags set-hook if it is re-filtered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`vm ... (alias: cloud)` and `remotes ... (alias: remote)` listed the alias at `cmux <Tab>` (it is in the registry) but `cmux cloud <Tab>` / `cmux remote <Tab>` completed nothing, because parse_help only attached the parsed spec to the line's leading command. Parse the `(alias: name)` form and copy the canonical command's flags/subcommands/enum values to the alias, so the alias completes identically. Verified at runtime: `cmux cloud <Tab>` now offers vm's subcommands and `cmux remote <Tab>` offers remotes' subcommands + flags. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aining-shell-completions-for-the # Conflicts: # .github/swift-file-length-budget.tsv
`is_real_enum` rejected a whole `<a|b|c>` group if any token matched a metavar name, so `config <doctor|check|validate|path|paths|docs|documentation|reload>` produced no completions at all because `path` is a metavar name -- even though `path` is a real `config` subcommand here. Same for `hooks <agent> <install|uninstall|event>` and `browser find|get|frame`. A token is only a placeholder when the *entire* group is type metavariables (`<id|ref|index>`); a group with at least one literal is a real choice list and is kept whole (`path`/`docs`/`text`/`title` are real subcommands in their context). Pure-placeholder groups like `<id|ref|index>` are still dropped, so `cmux send --workspace <Tab>` does not offer `id ref index`. Verified at runtime: `cmux config <Tab>` now offers all eight subcommands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
…aining-shell-completions-for-the # Conflicts: # .github/swift-file-length-budget.tsv
…aining-shell-completions-for-the # Conflicts: # .github/swift-file-length-budget.tsv # CLI/cmux.swift
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_cli_completions_contract.py`:
- Around line 179-194: The first-level subcommand regression guard in
test_cli_completions_contract is hardcoded and can drift from the CLI spec.
Replace the literal expected_subcommands mapping with values derived from the
generator’s parsed command specs, using the same source of truth as the
top-level command check (for example via parse_help/build from
generate-cli-completions.py and the resulting spec subcommands). Keep the
existing command_completion_words loop, but compute each command’s expected
first-level subcommands dynamically so new subcommands are picked up
automatically.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 943fd3f8-21f0-48ed-908c-a066187ad550
📒 Files selected for processing (5)
completions/cmux.bashcompletions/cmux.fishcompletions/cmux.zshscripts/generate-cli-completions.pytests/test_cli_completions_contract.py
| # Regression guard: first-level subcommands can be written as spaced pipe | ||
| # alternatives, optional single values, or nested optional choice groups. | ||
| expected_subcommands = { | ||
| "browser": {"disable", "enable", "status"}, | ||
| "markdown": {"open"}, | ||
| "settings": {"docs", "open", "path"}, | ||
| } | ||
| for shell, text in regenerated.items(): | ||
| for command, expected_words in expected_subcommands.items(): | ||
| actual = command_completion_words(shell, text, command) | ||
| missing = sorted(expected_words - actual) | ||
| if missing: | ||
| failures.append( | ||
| f"{shell}: {command} completions missing first-level " | ||
| f"subcommand(s): {', '.join(missing)}" | ||
| ) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Hardcoded expected_subcommands can silently go stale.
This regression guard nicely fixes the earlier per-shell coverage gap, but the subcommand list itself is hand-maintained — the opposite of the "self-maintaining" goal for these completions. If a new subcommand is added to browser, markdown, settings, or a new multi-subcommand command is introduced, this dict won't be updated automatically and the guard silently stops covering it, unlike the top-level command check above which dynamically derives expected from --list-commands.
Consider deriving expected first-level subcommands from the generator's own parsed specs (e.g. importing parse_help/build from generate-cli-completions.py and inspecting the resulting spec's subcommands) instead of a hardcoded literal, so this guard stays in sync the same way the rest of the contract test does.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_cli_completions_contract.py` around lines 179 - 194, The
first-level subcommand regression guard in test_cli_completions_contract is
hardcoded and can drift from the CLI spec. Replace the literal
expected_subcommands mapping with values derived from the generator’s parsed
command specs, using the same source of truth as the top-level command check
(for example via parse_help/build from generate-cli-completions.py and the
resulting spec subcommands). Keep the existing command_completion_words loop,
but compute each command’s expected first-level subcommands dynamically so new
subcommands are picked up automatically.
Fixes #6616
What
The
cmuxCLI has ~150 top-level commands but no shell completions, which makes its large surface area hard to navigate. This adds bash/zsh/fish completions that are generated, never hand-maintained, so they can't rot.How it stays in sync
topLevelCommandNamesregistry inCLI/cmux.swift.a|b|cgroups), enum flag values, and(alias: x)aliases are parsed best-effort from theusage()help heredoc in the same file. The generator reads straight from source, so no built binary is required — it runs in CI.tests/test_cli_completions_contract.py, sibling totests/test_cli_contract_help.py) regenerates from source and fails on drift. Adding a command without regenerating turns CI red. It also runs a--checkguard that fails ifusage()documents a top-level command that isn't completable (absent from the registry or filtered as internal). That makes the "self-maintaining" promise cover the command registry itself.What completes
cmux <Tab>cmux send --<Tab>--surface --window --workspacecmux auth <Tab>login logout statuscmux diff --source <Tab>unstaged staged branch last-turn(bash, zsh, fish)cmux --socket /x send --<Tab>send's flags (global options are skipped)cmux cloud <Tab>vm's subcommands (alias inherits the spec)Files
scripts/generate-cli-completions.py— source-only generator (--write,--shell <bash\|zsh\|fish>,--list-commands,--check,--cmux-binto probe a built binary instead).completions/cmux.{bash,zsh,fish}+completions/README.md— generated scripts (marked auto-generated) and install/regenerate docs.tests/test_cli_completions_contract.py— drift + coverage + registry-coverage + shell-syntax guard..github/workflows/ci.yml— runs the contract test in the always-onworkflow-guard-testsjob.CLI/cmux.swift— addsremote,remotes, andsimulate-sidebar-dragtotopLevelCommandNames. These are dispatched (case \"remotes\", \"remote\",case \"simulate-sidebar-drag\") and documented inusage()but were missing from the registry, so completions (andshouldOpenAsPathArgument) did not know them..github/swift-file-length-budget.tsvrefreshed via the official script for the 3-line addition.Review notes
Implements the reference design contributed by @bdmorin in #6616, regenerated against current
CLI/cmux.swiftand hardened through review: zsh enum parity with bash/fish,a|b|csubcommand groups, leading global-option handling in all three shells, fish command matched by position (not__fish_seen_subcommand_fromanywhere), documented-alias spec inheritance, and the registry-coverage drift guard.Built so the generator's input can later be swapped to ArgumentParser's
--generate-completion-scriptonce #3254 reaches the subcommand tree, after which the generator retires.Localization: no app/web UI strings introduced — completion scripts contain literal CLI command tokens, the README is contributor docs, and the generator/test/CI are tooling; none are subject to the
Localizable.xcstrings/web/messagesrequirement. Thecmux.swiftchange adds command-name identifiers, not UI strings.🤖 Generated with Claude Code
Summary by CodeRabbit