Skip to content

Architecture

An alias cannot be a binary. gpush and .. are shell state, not programs. A compiled tool can only ever hand your shell some text and ask it to evaluate it.

So the tool must emit shell no matter what language it is written in. Once it is a generator, keeping the functions as generated shell too is the consistent choice, and it keeps the binary out of the hot path entirely.

loadout CLI

web UI

127.0.0.1

config.yaml

loadout apply

init.zsh

sourced by ~/.zshrc

The CLI and the web interface are two front-ends over one configuration file. Neither runs when you open a shell.

DecisionChoiceWhy
Product scopeCurated defaults, user-personalisableThe taste is the value. Personal values are data.
LanguageGoSingle static binary, cross-compiles to both platforms, go:embed ships the web UI inside it
Web UIOn-demand localhost serverNo daemon, no background surface
Shell integrationGenerated file, sourcedZero process spawn per shell start, and p/activate stay real functions
Shellszsh and bash, pluggable backendsThe interface existed from day one, so bash was a new file rather than a rewrite
SecretsOS keychainSecrets never land in a file
SyncGit-backed config repositoryConfig is text. Credentials stay per-machine because a keychain is deliberately non-exportable
cmd/loadout/ cobra commands, one file per command group
internal/
config/ schema, local.yaml overlay, validation
catalog/ the shipped set, embedded with go:embed
data/aliases.yaml 136 aliases with category, pack, platform, requires
data/functions.yaml per-function metadata: summary, usage, requires
data/functions/ text/template bodies, one per function
data/scripts/ shipped executables
generate/ rendering
backend/ Backend interface; zsh and bash
binaries/ fetch, verify, extract, install third-party executables
credentials/ keychain: security, secret-tool, pass
requires/ capability checks driving doctor, describe and the UI
server/ localhost web UI and its access control
web/ embedded page, no build step
sync/ git-backed config repo, secret scanning
selfupdate/ install-method detection, self-replacement
uninstall/ inventory and removal
testdata/
fixture/config.yaml exercises every schema section
golden/ pinned output: 2 shells x 2 platforms

A clean split, and the reason emails can sit in plaintext configuration:

  • The repository ships the product: Go source, the curated templates, the web UI.
  • ~/.config/loadout/ holds the user’s data.
  • The OS keychain holds credential values, and nothing else.

Nothing personal is committed to the repository. The test fixture and every example use illustrative values.

The generator has a Backend interface with an implementation per shell dialect. Adding bash was one new file.

Every alias line the two backends emit is byte-identical. Only the preamble differs, because bash expands aliases in interactive shells only and needs shopt -s expand_aliases where zsh needs nothing.

The abstraction was never the risk. The risk was in the function bodies, and adding a second shell found four constructs that had been wrong all along — see Shell portability.

One declaration per entry, driving three outputs:

pw:
requires:
- binary: code
- config: workspaces
  • loadout doctor reports everything unmet.
  • Calling an unconfigured command gives a real error instead of a silent failure.
  • The web interface shows a green, amber or red indicator per entry.

They cannot drift apart because there is only one declaration.

This is where a review finding became a product feature. On the machine this was designed against, thirteen aliased tools were not installed, ls shadowed a real binary, dl pointed at a dead youtube-dl, and roughly twenty-eight aliases were Linux-only. A flat alias file cannot surface any of that.

The tool writes a file sourced into every shell you open. Showing exactly what changes is what makes that trustworthy, and it is nearly free since text is being generated anyway.

A test asserts the applied file is byte-identical to what the diff previewed. A preview that can differ from the result is worse than no preview.

Not a build flag. Real behavioural differences in the catalog, roughly twenty-eight aliases:

LinuxmacOS
apt-get, dpkg -ibrew
systemctl (7 aliases)launchctl
netstat -tulanplsof -i -P
wg-quick, openvpn3different tooling

Generating for darwin yields fewer aliases than Linux: some drop out entirely and several resolve differently.

Tool names were the easy half. The subtler risk is a command that exists on both and behaves differently — which is what sshp hit with find -regex, silently skipping every ed25519 key.