Skip to content

Functions

loadout ships 14 shell functions. Unlike aliases, these take arguments, branch, and loop — p resolves a path under your project root, msh parses flags, venv finds and activates an environment.

Every one is documented individually in the function catalog, generated from the shipped metadata.

Two of them have no choice. p changes the shell’s directory and activate changes its environment, and a child process cannot do either to its parent. A compiled subcommand could print a cd for you to evaluate, but then it is not a command, it is a string you have to remember to eval.

The other twelve could be subcommands of the binary. They are not, because the generator already exists: once the tool emits shell for p, emitting shell for msh too costs nothing and keeps every capability in one place, discoverable the same way.

The trade is that function bodies are shell, and shell has to work in both dialects loadout supports. That constraint is real and has bitten — see Shell portability.

Every function declares its requirements once:

pw:
requires:
- binary: code
- config: workspaces

That single declaration drives three things, which is why they cannot disagree:

  • loadout doctor reports it when unmet.
  • loadout describe pw prints the setup instructions.
  • The web interface shows a green, amber or red indicator.

Three kinds of requirement exist:

FormMeans
codeThe binary must be on PATH
path:{{project_root}}The path must exist, after your configuration is substituted
config:workspacesThat configuration section must be non-empty

You get a real error naming what is missing, not a silent failure:

pw: no workspaces configured
add one under `workspaces:` in ~/.config/loadout/config.yaml
run `loadout describe pw` for the full requirements

This is the payoff for generating an entry rather than dropping it. An alias that vanishes because a tool is missing gives you “command not found” and no explanation.

p changes directory to a project under your project root.

Terminal window
p # go to the projects root
p acme/api # go to <root>/acme/api

activate sources a Python virtual environment, searching your venv_root and the current directory.

Both run in your shell rather than as a subprocess. That is not a design preference, it is the only thing that works.

Functions that take flags use getopts, and every one of them resets OPTIND first:

Terminal window
msh -m api
msh -k mykey -u ubuntu -p 2222
msh -h # full flag reference

The reset matters more than it looks. zsh localises OPTIND per function; bash does not. Without an explicit local OPTIND=1, the second call to a function in a bash session silently ignores its flags — no error, just the defaults. Every shipped function that parses flags has it.

Terminal window
loadout catalog # includes functions
loadout describe msh # summary, usage, requirements, config it reads
loadout describe # list everything describable

describe resolves requirements against the machine you run it on, so it tells you what is actually missing here rather than what could theoretically be missing.