CLI reference¶
Run loadout --help or loadout COMMAND --help for the installed version's help.
Commands default to the current directory. Run them from the source or repository root, or
supply --root PATH.
Render and check¶
loadout sync
loadout check
loadout sync --root /path/to/repository
loadout check --root /path/to/repository
loadout sync --global
loadout check --global
loadout sync --global --profile focused
loadout check --global --profile focused
| Option | Commands | Meaning |
|---|---|---|
--root PATH |
sync, check |
Directory containing the source/project configuration; a loadout.toml there renders only when the directory is this machine's registered global source |
--global |
sync, check |
Resolve the source and default profile from this machine's configuration |
--profile NAME |
sync, check |
Select a global manifest profile; without it, the registered global source uses the machine's profile and any other root uses default |
--force |
sync |
Overwrite generated files protected as modified outside Loadout |
--json |
check |
Emit status, exit_code, each out-of-sync path and any notes as JSON; not with --staged |
--root and --global are mutually exclusive. The explicit profile wins over the machine
configuration's selection; otherwise the default is default.
sync writes generated outputs. It rewrites only files whose bytes change, so an unchanged file
keeps its modification time, prints wrote <path> for each one it changed, and ends with
N written, M unchanged, or Up to date. when nothing changed. It also retires what it wrote
before and no longer renders, printing retired <path> and adding , N retired to the summary:
an unchanged file is deleted with its .gitignore entry, and a shared file loses only the keys
loadout recorded writing. A retired file edited since sync wrote it is refused like any other
hand edit. check compares them with the current source and reports
drift without rewriting the generated files, so check is the preview of sync; there is no
sync --dry-run. Each drifted file carries a kind, judged by the same comparison sync uses to
refuse an overwrite: source-changed (the source moved on; run sync), hand-edited (it differs
from what sync last wrote, including bytes that are not UTF-8, which are shown with � in
their place; port the edit into the source with /loadout, or sync --force to discard it), owned-key (a key loadout writes in a file it shares with the harness, such as a
server in ~/.claude.json, was edited there; port it with /loadout, or sync --force to discard
it), owned-key-removed (such a key was removed there; remove it from the source too, or
sync --force to write it back), unknown (no record and no committed baseline to judge by;
review the diff, then port or discard the same way), missing,
orphaned (sync wrote it, the source no longer produces it, and the next sync retires it), or
unmanaged (loadout did not write it, but it sits in a folder the harness reads whole, such as a
skills folder or Codex's rules folder; port it with /loadout, then delete it — sync warns
about it and carries on), unmanaged-link (a link loadout did not create in such a folder; it has
nothing to port, so check where it points before deleting it), or unparsable (a file loadout shares with a harness, such as
~/.claude.json, no longer parses, including one that is not even UTF-8, or parses but cannot hold loadout's keys, such as a JSON
array or a TOML scalar where a table goes: fix or delete it and sync, or sync --force replaces it
outright; plain sync refuses with exit 3 and names the file. One the source no longer renders
has nothing to replace it, so sync refuses it with or without --force until it is fixed or
deleted).
check --json reports the same kind per path and exits with the same code as check. The registered global
source may also hold a project configuration, and then renders both scopes. Any other root holding
a loadout.toml is refused with exit 3 before anything is read or written, its project
configuration included. The refusal names its cause: this machine has no registered source (run
loadout init global <that folder>), or another one is registered (add --registration replace).
A machine config that does not load is reported as its own error instead. integrate git-hooks install and the template commands refuse such a root too, rather than installing hooks that would refuse every commit; check --staged refuses one in the index the same way, under any case spelling
of its name, since a case-insensitive disk reads Loadout.toml as the manifest; a commit that
removes one is not refused for HEAD still holding it. Registering is the
trust decision: loadout init global <path> registers a folder, and there is no other way to
render one.
JSON output (init --json, check --json) is not covered by a stability promise in 1.0.
Use --force only after reviewing the reported output edits and deciding to discard them.
It does not resolve invalid sources or merge a modified vendored template.
Initialise¶
loadout init project --harness claude --harness codex
loadout init project --harness opencode --root /path/to/repository
loadout init global ~/agent-config
| Option | Commands | Meaning |
|---|---|---|
--harness NAME |
init project, init global |
Select a harness: claude, codex, droid, opencode or pi; repeat for several |
--root PATH |
init project |
Project directory to initialise |
DIR (positional) |
init global |
Existing global source, or directory in which to scaffold a new one |
Scope is a subcommand: init project for a repository, init global for machine-wide
configuration. init global [DIR] takes the directory as an optional argument, like
git init [dir], and defaults to the current directory, so a bare init global adopts the cwd. --root exists only on init project; --registration keep|replace only on init global. At the registered global source that also holds a project, init project previews, compares and writes the project's outputs alone; the global ones stay for sync. Keeping another registration while init sets up a new source exits 3 before writing anything: the first sync renders global scope, which only the registered source may.
Init either converts or puts everything back. It backs up every path it will touch first and
deletes that backup when it succeeds. If applying or the first render fails, Ctrl-C included,
it puts every path back from the backup, deletes it and exits 1. After such a failure, and when
the plan cannot complete (exit 2), it recommends doing the conversion with the bundled /loadout
skill. At a terminal it asks whether to copy the skill into the user skills folder of each
harness being converted; without one, or under --json, it prints the command instead. A refusal
that only needs --harness, a scope or --registration names that option and not the skill.
An init killed part way leaves its backup, and the next init puts it back before anything else:
at a terminal it asks first, and without one it exits 2 with "status": "interrupted".
loadout integrate skill copy --harness NAME (repeatable) copies the bundled skill as it is into
~/.claude/skills/loadout for Claude, ~/.factory/skills/loadout for Droid and
~/.agents/skills/loadout for Codex, OpenCode and Pi, following CLAUDE_CONFIG_DIR and
FACTORY_HOME_OVERRIDE. It is a plain copy: no source holds it and sync never touches it. An
identical copy already there is left as it is; one that differs is refused before anything is
copied, exit 3. It then says how to start the skill in each harness.
Under --json, init prints one JSON object on stdout, whatever happens. It always has
status and exit_code, the code the command exits with:
status |
exit_code |
When |
|---|---|---|
preview |
0 | --dry-run: the plan, with every planned write, secrets, and outputs, each with its change: identical, normalised (only loadout's banner or formatting differs), update or create |
incomplete |
2 | the plan needs a choice or a fix; issues names each, and unless every issue is a choice an option makes, recommendation and install_command name the skill and the command that copies it |
complete |
0 | applied; written (the source), rendered (the generated files), untracked, already_initialized, delinked, unadopted and secrets |
restored |
1 | init failed and put every path back; error says why, restored names what was put back, and recommendation and install_command are as for incomplete |
interrupted |
2, or 1 | an earlier init was killed part way (2), or this one failed and could not put everything back (1); backup names the backup and error says what to do |
error |
2, 3 or 4 | a usage error (an unknown option or --harness name included), a source error, or an unexpected one; error holds the message |
Fresh project initialisation uses explicit --harness options, then the machine configuration's
harnesses default, then discovered project configuration. If none establishes membership, it
requires a selection. Global initialisation accepts --harness and automatically records its
resolved harness list as the project default.
Re-running project initialisation on an initialised project reports it as already initialised
and changes nothing; loadout sync keeps .gitignore covering the generated paths.
Add a project harness¶
Add the name to harnesses in loadout/config.toml, then run loadout sync:
The accepted names are claude, codex, droid, opencode, and pi. sync writes the new
harness's outputs and extends .gitignore to cover every generated path. Removing a harness from
the list retires its outputs on the next sync and drops their .gitignore entries.
Templates¶
loadout template add python --tracked
loadout template add python --vendored
loadout template sync python
loadout template list
All four accept --root PATH and work on a project. Outside one they exit 3: templates are
applied to projects and authored as templates/<name>/ in your global source.
| Command | Result |
|---|---|
add NAME --tracked |
Declare the template; it resolves from a source on every render |
add NAME --vendored |
Copy the source into the project and record its content hash |
add (bare) |
List every resolvable template and prompt for a name and a mode; without a terminal, a missing name or mode is a usage error (exit 2) |
sync NAME |
Update an unchanged vendored copy from the available upstream source |
list |
Show declared templates, each one's mode, and where it resolves |
Adding a template that is already declared changes its mode in either direction.
When vendoring or syncing a catalog template would change parts it shares with other declared
templates, the command lists the affected templates and asks before writing. --yes (on add
and sync) applies the change without asking; without a terminal and without --yes it exits 2
and changes nothing. Declining exits 0 with "declined; no changes made".
Run the ordinary loadout sync to render the resulting project configuration.
See templates for the complete lifecycle.
Integrations¶
Commands that wire loadout into another system so it runs automatically.
Bundled skill¶
loadout integrate skill install
loadout integrate skill install --yes
loadout integrate skill status
loadout integrate skill uninstall
loadout integrate skill copy --harness claude --harness codex
install, status and uninstall accept --profile NAME and --source NAME. --source names a declared
[[source]] from the manifest; it is not a directory.
install and uninstall accept --yes to apply the displayed change without a prompt.
They operate on the global source and synchronise the selected profile.
Installation and removal protect edited skill copies. An unchanged installed copy can be upgraded or removed; a conflicting local edit is reported for review.
copy needs no source: it copies the skill into each named harness's user skills folder for a
conversion init could not do, as Initialise describes.
Git hooks¶
loadout integrate git-hooks install --root .
loadout integrate git-hooks install --root . --regenerate
loadout integrate git-hooks status --root .
loadout integrate git-hooks uninstall --root . --yes
All three accept --root; none takes --profile, since each hook resolves the profile when it
runs. install also accepts --dry-run, and
--regenerate to add the post-checkout and post-merge hooks alongside pre-commit.
uninstall removes only hooks loadout wrote, identified by the marker on the second line of
the generated script rather than by the command they invoke. A hook without that marker is
never touched, whoever wrote it. A hook that loadout wrote for a different source in the same
repository is left alone too. --yes skips the confirmation prompt.
Shell completions¶
loadout integrate completions install
loadout integrate completions status
loadout integrate completions uninstall --shell zsh
Completes subcommands and fixed-choice options for bash, zsh and fish. --shell defaults to the
shell running loadout, then to $SHELL; when neither is one of the three, the command exits 2 and
asks for --shell. The script is generated once, at install, so a TAB costs no Python start-up.
| Shell | Installed at | Loaded by |
|---|---|---|
| bash | ${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-~/.local/share}/bash-completion}/completions/loadout |
bash-completion 2, on demand; otherwise source it from ~/.bashrc |
| zsh | ${XDG_DATA_HOME:-~/.local/share}/zsh/site-functions/_loadout |
compinit, once that directory is on fpath |
| fish | ${XDG_CONFIG_HOME:-~/.config}/fish/completions/loadout.fish |
fish, automatically |
Loadout never edits a shell startup file; install prints the one line to add where one is
needed. status reports whether the script is installed and current, since an upgrade can add
commands. uninstall removes only a script carrying loadout's marker, and installing over a file
without it is refused.
Exit codes¶
| Code | Meaning |
|---|---|
0 |
Success: nothing to do, or a check that found no differences |
1 |
Drift, a refused safe update, an init that failed and restored itself, or a restore that kept paths |
2 |
Invalid command-line usage, unresolved init choices, missing noninteractive approval, or an interrupted init found by the next one |
3 |
Source, configuration, or resolution error: missing or invalid input, an unsafe destination, or an ownership collision |
4 |
Unexpected internal error, with a traceback |
A modified vendored template is a source change. check can report it without failing solely
for that reason. A refused template sync exits 1.