Skip to content

Add zsh, bash and fish tab completion to the privacycommand CLI - #29

Merged
AdamXweb merged 1 commit into
mainfrom
feat/cli-shell-completions
Oct 9, 2026
Merged

AdamXweb merged 1 commit into
mainfrom
feat/cli-shell-completions

Conversation

@adamXbot

@adamXbot adamXbot commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Why

The privacycommand CLI (#28) had no tab completion. This adds it for zsh (macOS's default shell), bash and fish. Homebrew installs set it up automatically, and everyone else adds one line to their shell's startup file.

What changes

privacycommand completion <zsh|bash|fish> prints a completion script. Its --help lists the line to add for each shell. For zsh, in ~/.zshrc after compinit:

eval "$(privacycommand completion zsh)"

The scripts are generated from command tables in privacycommandCLIKit/ShellCompletion.swift, so all three complete the same things:

  • subcommands (with descriptions in zsh and fish) and the top-level options.
  • installed app names: the same top-level *.app names in /Applications, ~/Applications and /System/Applications that privacycommand <name> matches. Names with spaces are escaped. Directories complete too.
  • audit options after audit <app> or a bare <app>.
  • preview options, --min-tier levels, --apps-dir directories, and the outdated cask tokens from brew outdated --cask.
  • the shell names after completion.

The zsh script works both autoloaded from fpath and eval'd from ~/.zshrc. The bash script works with macOS's bash 3.2: it avoids compopt and relies on complete -o filenames for escaping.

Bundled for Homebrew. The Embed command-line tool phase writes the three scripts to Contents/Resources/completions. The cask links them with bash_completion, zsh_completion and fish_completion. Those stanzas need Homebrew 4.4.24 (March 2025) or later. I avoided the newer generate_completions_from_executable, which older Homebrew can't parse. brew style reports no new offences; the two it flags were already there.

A macOS quirk this hit. The first run of a binary from inside a freshly built .app silently loses whatever it writes into that same bundle. Swift's print ignores the failed writes, so it still exits 0. Generating with the embedded copy left _privacycommand empty on every fresh build. The phase now generates with the identical SwiftPM build output and copies the files in, and it fails the build if any script comes out empty.

This doesn't affect users: their output goes to the terminal or to files outside the app. I reproduced it outside Xcode and confirmed the same binary run from outside the bundle writes fine.

Docs.

  • The Install Command Line Tool… success alert, README, in-app manual, CONTRIBUTING and the source-tree README describe completion setup.
  • CONTRIBUTING tells contributors to add new subcommands and flags to the completion tables. This matters for the in-flight upgrade/--greedy work.
  • While there, README and CONTRIBUTING stop claiming preview "never runs brew" and "always exits 0". It runs read-only brew queries and exits 2 on usage errors or missing Homebrew.
  • CONTRIBUTING's --min-tier warn example, which exits 2, now uses high.

Verification

  • swift test: 290 Core tests (4 skipped, as before) and 52 CLI-kit tests (5 new), 0 failures. ShellCompletionTests checks that:
    • every table entry reaches every shell.
    • zsh -n and bash -n accept the scripts (fish -n too, when fish is installed).
    • bash completes subcommands, options, tiers and shells correctly for fixed inputs.
  • zsh, driven interactively through a pty, both from fpath and via eval after compinit:
    • lists subcommands, app names and directories.
    • prev→preview, preview --f→--fetch, --min-tier → the four tiers.
    • completion b→bash, Calc→Calculator, Calculator --s→--short.
  • bash 3.2 (/bin/bash): subcommands plus app names with spaces (App S→App Store), preview options, tiers, --apps-dir directories, and audit options after a bare app name.
  • Fresh xcodebuild bundles, signed (ad-hoc) and unsigned, universal: all three files are present and byte-identical to privacycommand completion <shell>, and the signed bundle passes codesign --verify --deep --strict.
  • just lint passes.

Not verified

  • fish isn't installed on this Mac. Its script uses long-standing helpers (__fish_use_subcommand, __fish_seen_subcommand_from, __fish_complete_directories), and the content test covers it, but it hasn't been run.
  • A real brew install --cask with the new stanzas needs a release.

🤖 Generated with Claude Code

- `privacycommand completion <zsh|bash|fish>` prints a completion script;
  `--help` gives the one line to add for each shell. Scripts are
  generated from command tables in privacycommandCLIKit/ShellCompletion,
  so subcommands, options, risk tiers, installed app names, directories
  and outdated casks complete the same way in every shell.
- The app build writes the three scripts to
  Contents/Resources/completions, and the cask links them with
  bash_completion / zsh_completion / fish_completion (Homebrew 4.4.24+).
  They're generated with the SwiftPM build output rather than the
  embedded copy: macOS drops what a binary writes into its own freshly
  built .app on its first run, which left _privacycommand empty. The
  phase now fails if any script comes out empty.
- ShellCompletionTests checks every table entry reaches every shell, that
  zsh and bash parse the scripts, and what bash completes for fixed
  inputs. zsh was also checked interactively through a pty, both from
  fpath and via eval; fish isn't installed here.
- The Install Command Line Tool… alert, README, in-app manual,
  CONTRIBUTING and the source-tree README describe completion setup.
  README and CONTRIBUTING also stop saying `preview` never runs brew or
  always exits 0, and CONTRIBUTING's `--min-tier warn` example (which
  exits 2) now uses `high`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adamXbot added a commit to privacykey/docs-privacycommand that referenced this pull request Oct 8, 2026
Documents `privacycommand completion <zsh|bash|fish>`
(privacykey/privacycommand#29): what completes, that Homebrew sets it up,
the one line per shell otherwise, and a zsh troubleshooting entry
(compinit order, Homebrew's fpath, a stale ~/.zcompdump). llms files
regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AdamXweb
AdamXweb merged commit b9dc877 into main Oct 9, 2026
7 checks passed
@AdamXweb
AdamXweb deleted the feat/cli-shell-completions branch October 9, 2026 01:35
adamXbot added a commit to privacykey/docs-privacycommand that referenced this pull request Oct 9, 2026
* docs: document the privacycommand CLI and how to install it

The CLI was renamed from auditctl to privacycommand and now ships inside
the app (privacykey/privacycommand#28). The docs still said it had to be
built from source.

- auditctl.mdx becomes cli.mdx ("Command line"), with a redirect from
  /auditctl. It covers the Homebrew install, the app menu's Install
  Command Line Tool… (and its sudo fallback and safety rules), building
  from source for 0.1.5 and earlier, usage with the new name, and the
  audit exit codes.
- Every page that ran `swift run -c release auditctl …` or linked
  /auditctl now uses `privacycommand …` and /cli.
- Installation and Updating mention the tool, Troubleshooting gains
  "command not found" and "asks you to move the app" entries, and the
  Develop pages use the new target names and the embed phase.
- llms.txt and llms-full.txt regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: add tab completion setup for the privacycommand CLI

Documents `privacycommand completion <zsh|bash|fish>`
(privacykey/privacycommand#29): what completes, that Homebrew sets it up,
the one line per shell otherwise, and a zsh troubleshooting entry
(compinit order, Homebrew's fpath, a stale ~/.zcompdump). llms files
regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: cover privacycommand upgrade, --max-risk, --greedy and .pkg

The CLI on main gained `upgrade`, a `--max-risk` gate, `--greedy` and
.pkg artifact support, so cli.mdx's "`.pkg` is skipped" and "will not
upgrade anything" were no longer the whole story.

- cli.mdx: preview's new flags, the casks and formulae it lists but
  can't analyse, the brew calls it makes now, exit code 3, .pkg handling,
  and a new "Upgrading within a risk limit" section (cleared/held,
  --dry-run, --no-input, exit codes 0/1/2/3, the daily alias).
- Security lists `brew upgrade` as the sixth outbound call; the
  Introduction and FAQ follow.
- llms files regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants