Skip to content

feat(cli): generate shell completions#94

Merged
ryanlewis merged 2 commits into
mainfrom
shell-completions
May 30, 2026
Merged

feat(cli): generate shell completions#94
ryanlewis merged 2 commits into
mainfrom
shell-completions

Conversation

@ryanlewis
Copy link
Copy Markdown
Owner

PR 1 of #88 — the CLI half of shell completions. PR 2 (GoReleaser cask wiring) follows.

What

things now ships bash/zsh/fish completions that stay in sync with the CLI surface automatically. Two halves:

  • Runtimemain() calls kongplete.Complete(parser) before parser.Parse. When the shell invokes things with COMP_LINE set, it computes candidates from the kong command tree and exits; it's a no-op for normal runs. This gives subcommand, flag-name, and enum-value completion (e.g. --colorauto|always|never) with no custom predictors — i.e. flag-name-only, per the issue's v1 scope.
  • Static — a new things completions <bash|zsh|fish> command prints the per-shell stub that delegates back to the binary. It emits the bare command name (complete -C things things) rather than kongplete's absolute path, so the script survives brew upgrade (which moves the versioned Cellar path). This is exactly what PR 2's generate_completions_from_executable (args: [completions], shell_parameter_format: arg) will capture.

Acceptance (PR 1)

  • things completions bash|zsh|fish each print a valid script for the named shell
  • Eval'ing the script gives TAB-completion for subcommands and flags (verified end-to-end via COMP_LINE)
  • internal/skill/SKILL.md lists the new subcommand
  • make test (-race) and make lint pass

Tests

  • Static stub emission per shell + bashcompinit preamble + unsupported-shell rejection
  • renderCompletion name substitution proven with a non-things name (100% covered)
  • Runtime COMP_LINE query driven through kongplete in-process (WithExitFunc + kong.Writers), asserting subcommands, flag names, and --color enum values — locks down the load-bearing half against silent regressions from future kong/kongplete bumps

Notes

  • Manual sourcing is documented in the README for non-Homebrew paths (source <(things completions zsh), things completions fish | source), with the PATH requirement and zsh-after-compinit caveat called out.
  • kongplete (willabides/kongplete) is added as a direct dependency; pure-Go, no cgo.

🤖 Reviewed by two adversarial multi-agent passes before commit; all confirmed findings folded in.

ryanlewis added 2 commits May 30, 2026 15:22
Add bash/zsh/fish shell completions for `things` (PR1 of #88). Two halves:

- Runtime: wire kongplete.Complete(parser) into main() so the binary
  answers COMP_LINE queries from the kong command tree — subcommands,
  flag names, and enum values (e.g. --color). No-op for normal runs.
- Static: a `things completions <bash|zsh|fish>` command emits the
  per-shell stub that delegates back to the binary. It uses the bare
  command name so the script survives `brew upgrade`, and is what
  GoReleaser's generate_completions_from_executable will capture (PR2).

Tests cover both halves: static stub emission per shell, name
substitution, the unsupported-shell guard, and a real COMP_LINE query
driven through kongplete in-process. Documented in internal/skill/SKILL.md
and README.md.

Part of #88
The cask (binary install) has landed; the generate_completions_from_executable
block that emits completion files is a separate follow-up. Reword README and
SKILL.md so the present state isn't read as 'the whole cask is pending'.
@ryanlewis ryanlewis merged commit be072f4 into main May 30, 2026
1 check passed
@ryanlewis ryanlewis deleted the shell-completions branch May 30, 2026 14:39
ryanlewis added a commit that referenced this pull request May 30, 2026
PR 2 of #88 — the release plumbing. Builds on PR 1 (#94), which added
the `things completions <shell>` command.

## What

Wire `generate_completions_from_executable` into the `homebrew_casks`
block so `brew install` runs `things completions bash|zsh|fish` at
install time and drops the scripts into the Homebrew prefix. No custom
`executable:` — it defaults to the first binary (`things`), which is
correct for our root-level archive layout.

```ruby
binary "things"
generate_completions_from_executable "things", "completions",
  base_name: "things",
  shell_parameter_format: :arg,
  shells: [:bash, :zsh, :fish]
```

## The non-obvious part: quarantine ordering

Completion generation **runs the binary during artifact install**, which
is *before* `postflight`. Our binary is unsigned, so it's
Gatekeeper-quarantined on download — that's exactly why the cask already
removed the quarantine xattr. But that removal lived in
**`postflight`**, i.e. *after* the completion run. Left as-is,
`generate_completions_from_executable` would invoke a still-quarantined
binary, Gatekeeper would block it, and **zero completion files would be
written** — silently.

Fix: move the `xattr -dr com.apple.quarantine` hook from `postflight` →
`preflight` (`hooks.post.install` → `hooks.pre.install`). Homebrew runs
`preflight` before artifact install, so the binary is de-quarantined in
time. It's strictly safe for runtime too — same removal on the same
staged binary, just earlier.

## Docs

Flipped the README + `internal/skill/SKILL.md` wording from "doesn't
generate completions yet" to "Homebrew generates these on install." (PR
1 had set the honest interim phrasing.)

## Verification

- [x] `goreleaser check` passes
- [x] `goreleaser release --snapshot --clean --skip=publish` emits
`Casks/things.rb` containing the `generate_completions_from_executable`
block **and** the `preflight` de-quarantine
- [x] `go test ./internal/skill/` passes (SKILL.md is embedded)
- [ ] **Post-tag (needs a real release):** `brew install
ryanlewis/tap/things` (or `brew reinstall`) writes `bash`/`zsh`/`fish`
completion files into the prefix
- [ ] **Post-tag:** open a fresh shell, `things <TAB>` shows subcommands

The last two require a published tag, so they're for after merge +
`/release`. The quarantine-ordering fix is the thing to watch in that
test — if completions still don't appear, the next lever is
`no_quarantine` or confirming the sandbox can exec the de-quarantined
binary.
ryanlewis added a commit that referenced this pull request May 30, 2026
Release notes header for **v0.4.0**, required at the tagged commit by
`.goreleaser.yaml` (`readFile ".github/releases/<tag>.md"`). Merge this,
then the `v0.4.0` tag gets cut.

- Headlines bash/zsh/fish **shell completions** (#94, #95).
- Notes the lipgloss v2 migration (#93) and routine dep updates.
- Folds in the skill-doc changes (#89).

Also removes the orphaned `v0.3.2.md`: v0.3.2 was never tagged and its
only change (#89) is now covered here, so we go straight from v0.3.1 →
v0.4.0.
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.

1 participant