Overview
sxcli.dev/completion adds bash and zsh completion to any binary built
on sxcli.dev/fw. It is an ordinary ecosystem module — it
consumes the framework's public introspection API from the outside, with
no privileged access, and nothing about it is or needs to be part of the
core.
The whole integration#
One blank import per shell, exactly like the framework's log sinks — a binary links only what it wants to support:
import ( _ "sxcli.dev/completion/bash" _ "sxcli.dev/completion/zsh" )
That is all. There is nothing to declare beyond what the framework
already knows: completions are computed from the same config structs,
usage: tags and registration metadata that
drive your binary's arguments, environment variables and config files.
Declare once, get everything.
Installing the completions#
Each import registers a System applet
(completionbash, completionzsh) — machinery of the binary, never
listed in usage, invoked by the generated scripts rather than by humans.
Being System is what makes the module safe to add: it is excluded from
single-applet counting, so importing it can never flip your binary's
dispatch mode.
The script for a command name is generated by the binary itself:
eval "$(mybin completionbash --script)" # bash eval "$(mybin completionzsh --script)" # zsh, after compinit
In a busybox-style installation, generate through each symlink you actually created — each name gets its own registration with the target applet baked in:
eval "$(cat completionbash --script)" # ./cat -> mybin eval "$(ls completionbash --script)"
Put the eval line in your shell rc file, or write the generated script
into the shell's completion directory — it is ordinary shell text.
What completes#
- Applet names — the first word of a multi-applet binary, public applets only. Hidden and System applets are never offered: a completion must not suggest what a human should not type.
- Argument names — long forms of the dispatched applet's whole closure, core arguments included. Already-used scalars drop out; repeatable slice arguments stay.
- Declared value domains — a field with
Allowedmetadata completes exactly its legal values: the same declaration the framework already enforces at startup. - Files and directories — fields declaring
HintFile/HintDirectoryhand over to the shell's native file completion (the core's own--configdoes this). - Service ids — fields declaring
HintServiceIDcomplete from the binary's actual registry (--disable,--enable). --name=value— bools completetrue/falseafter the=.
Zsh additionally renders each candidate's description from the usage:
text and Doc metadata.
Everything is computed per keystroke against the real configuration:
an --enable already typed on the line changes the closure — and the
completions with it.
How it works#
The framework core exposes a read-only Introspector; this module consumes it like any other consumer could. The generated scripts are deliberately dumb transports: they forward the shell's raw completion state, and every decision happens in Go, where it is tested — unit tests against a fake introspector, integration tests that re-exec a real framework binary, and the generated scripts executed under the real shells.
Other shells#
Bash and zsh are what this module ships — and the machinery they are built on is public API. A third-party adapter (fish, PowerShell, elvish, …) is two small pieces over two packages:
engine—Complete(src, query)answers what completes: applet names, argument names, declared domains, file/directory directives. The adapter only decodes its shell's transport into aQueryand encodes theCandidates back.script— the--scriptgeneration policy (which target to bake for the name the binary was invoked as), so dispatch semantics are never reimplemented per shell.
The bash and zsh
packages are the reference implementations — each is one template plus
~60 lines of Go.
Status#
v0 — the API is settling and may still move. Module path
sxcli.dev/completion, requires sxcli.dev/fw v0.2.0+, licensed under
Apache-2.0.