Groundwork

Reference

Command catalog: the repo's aliases, helper commands, and daily moves.

This is the "what command was that again?" page. The same catalog is available in the terminal with groundwork-help, so the help follows you even when the docs are not open.

Terminal help
groundwork-help show everything
groundwork-help update filter commands
groundwork-help git show Git helpers
groundwork-help project show scaffolders
groundwork-help context show AI context helpers

#Why not package.json?

A package.json would be valid, and this setup does install Node and pnpm for JavaScript projects. But Groundwork itself is not a JavaScript package; it is a macOS/chezmoi project whose first-run path has to work before Node exists. The most useful commands should also work from any folder after setup, so the primary interface is a normal shell command installed to ~/.local/bin: groundwork-help.

Repo-maintenance commands live as shell scripts under scripts/; for example, CI and humans both run scripts/validate-groundwork. That validator includes ShellCheck linting for checked-in and rendered shell scripts, so agent edits get the same shell review locally and in GitHub Actions. If a future workflow genuinely needs Node libraries, adding a tiny package.json with pnpm scripts as wrappers would be fine. It just should not be the only way a beginner discovers or runs Groundwork commands.

The ownership rule is simple: a command that performs work in child processes lives in ~/.local/bin; a shell function is reserved for work that must change the current shell, such as cd, an export, or activation. Self-updating commands split in two: update-all is a stable launcher that synchronizes Groundwork and then hands the same invocation to the freshly applied runner, groundwork-update-run, which holds the actual update stages. A fix delivered by the sync therefore governs the rest of that same invocation, and the launcher itself never needs to change.

A separate hand-written man page would duplicate the same command contract in a third place. Groundwork follows the modern CLI layers instead: every substantial helper owns complete -h/--help output, groundwork-help provides searchable offline discovery across helpers, and this site teaches the larger workflow. If Groundwork later grows into a stable multi-command suite, its man pages should be generated from that command definition rather than maintained separately.

#Daily Groundwork commands

groundwork-helpShow the command catalog. Add a query to filter it.
groundwork-help --pickFuzzy-pick a command from the catalog with fzf; prints it and copies it to the clipboard when possible.
update-allRun a visible batched refresh: synchronize Groundwork, resume in the freshly applied groundwork-update-run runner, attempt brew update up to three times with short backoff, then run brew upgrade --require-sha/cleanup against that refreshed metadata, mise upgrade, and latest-track AI updates. It stops before later stages when a required stage fails and records success only after the complete batch. With the beta channel on, it also prints the xcodes workflow without downloading a beta.
groundwork-configureReview and selectively change this machine's setup answers (profile, work machine, password manager, code directory, identity, and so on). It shows current values, changes only what you pick, previews the dotfile diff, and applies only after you confirm — promoting the config atomically under a lock with a timestamped backup. Use it to change a setting; plain chezmoi init only regenerates from your existing answers.
chezmoi diffPreview managed-file changes before applying them.
chezmoi applyApply this repo to your home directory.
chezmoi update --initPull the Groundwork repo and apply it. --init also refreshes the setup answers when new questions were added — answers you already gave are kept, only new questions are asked. This syncs config; it does not upgrade already-installed tools to latest.
chezmoi edit ~/.zshrcEdit the source template for a managed file.
chezmoi cdJump into the Groundwork source repo.

#AI context sharing

Use these when you want a chat app, reviewer, or second agent to see the exact files you chose, with each path attached to its contents. Keep the bundle small: copy the files that explain the question, not the whole repo.

copy-context AGENTS.md src testsCopy selected files or folders to the clipboard with path headers and contents.
copy-context --stdout src/foo.tsPrint the payload first so you can inspect what you are about to paste.
copy-context --paths-only docsCopy only the selected file list when you want to discuss scope before sharing contents.

#Repository navigation

groundwork-reposTable of every Git repository discovered under your configured roots (~/.config/groundwork/repos.conf): branch, dirty/clean as symbol + word, ahead/behind, worktree marker, path. Discovery is dynamic — a fresh clone appears within the cache TTL (30 s), or immediately with --refresh, with no config edit. See Repository navigation.
groundwork-repos pickFuzzy-pick a discovered repository (fzf) with a live git-status preview, then open or reuse its lazygit tmux window. Inside tmux it becomes a window (also on Ctrla then ShiftR); outside, it creates or attaches the repos session.
groundwork-repos changedOpen one lazygit tmux window per repository with uncommitted work — the fastest way to see everything your agents left unreviewed (also on Ctrla then ShiftG).

#Project scaffolders

new-project my-appCreate a starter repo with AGENTS.md, shared agent folders, editor settings, and ignored local/secrets files.
groundwork-docker-build-scratch review .Build a disposable Docker image under the ephemeral-build contract. The wrapper owns the syntax nobody should hand-type: it applies dev.roost.ephemeral=true, generates the RFC 3339 dev.roost.built timestamp itself, tags <project>/scratch:<purpose> (project derived from the context, or --project), validates the purpose as a legal tag component, and prints the resulting image ID. --rm-after removes the image right after a successful build (prove-and-delete); allowlisted docker build arguments go after -- (--file/-f, --target, --platform, --build-arg, --secret, --ssh, --no-cache, --pull, --progress; anything else is refused by name — --output/--push are unsupported because the contract requires a local, inspectable image). See Platforms.
new-wiki ~/code/llm-wikiCreate a private knowledge wiki where sources live in raw/, an agent maintains wiki/, and Obsidian can browse the vault.
obsidian-plugins ~/code/llm-wikiInstall the Groundwork-managed Obsidian plugin set into a vault.

#GitHub auth

gh auth loginSign in to GitHub for gh and HTTPS Git operations.
gh auth setup-gitInstall gh as Git's HTTPS credential helper.

#Dev runtimes

mise use node@24Pin a runtime/tool version in the current project.
mise upgradeUpgrade globally managed mise tools, including floating baselines such as Node LTS. Takes effect at your next prompt, even in already-open terminals.
pnpmPackage manager installed through mise for JavaScript projects.
pythonPython installed and versioned by mise. Avoid relying on macOS system Python.
uvFast Python package, project, script, tool, and virtualenv manager.

#Git commands

git statusShow changed, staged, and untracked files.
git diffInspect unstaged changes before asking an agent to continue.
git add -pStage changes in reviewable chunks.
git commit -m "message"Commit the staged changes with a short message.
git show --statReview the latest commit summary.

#Groundwork safety

chezmoi diffPreview what Groundwork would change before applying.
chezmoi applyApply the source repo to the home directory.
groundwork-restoreRestore the first-run backup of managed files and macOS defaults. Does not uninstall apps or undo Homebrew upgrades.
groundwork-doctorRead-only machine health report. It covers the shell runtime (managed zsh installed, registered, selected as your login shell, and actually running here) and Docker: daemon reachability, log rotation, image store, disk usage, and leftover containers, images, and volumes, each with owner-scoped cleanup guidance. It never deletes or changes anything.
groundwork-doctor --shellRun only the shell runtime section: managed zsh installed, registered, selected as login shell, and running here.
groundwork-doctor --runtimesFlag any mise-managed runtime that was adopted inside the five-day release floor. This closes a real gap: a cooldown only filters what you acquire — it never retires a version already on disk, so anything installed before the floor existed stays until something says so. It reads the release date from the tool's own backend rather than from mise's upgrade warning, which only appears when mise's version cache happens to be cold.
groundwork-doctor --dockerRun only the Docker section of the health report.
groundwork-docker-tidyDry-run Docker tidy, by enumeration: every image labeled dev.roost.ephemeral=true is inspected individually and reported as eligible (its strict-parsed dev.roost.built label is past the 72-hour grace, every tag sits in the scratch namespace */scratch:*, and no container — running or stopped — references it), retained (inside the grace window), protected (a non-scratch tag or a container reference refuses deletion — a scratch image retagged groundwork:latest keeps its label, so the tag check is the backstop), or unverified (missing jq, or a missing or malformed build label — retained and explained, never "probably old"); exited containers carrying the label are judged by the same grace window against their CreatedAt. The label is the consent, so your real groundwork image, base images, and other projects' images are structurally out of reach — and dangling images and builder cache are daemon-wide state this command never touches. See Platforms for the ephemeral-build contract.
groundwork-docker-tidy --yesRemove the eligible set: exited containers carrying the label whose CreatedAt is past the same 72-hour grace (container state under an ephemeral image is disposable only after the grace, and the status is re-checked immediately before removal), then each eligible image — deduplicated by image ID, re-checked immediately before deletion, and deleted by explicit image ID, never by a broad prune. A multi-tag image that only partly cleans up is reported as partially cleaned, naming the removed tags and why the image stayed. Weekly, or before a large build, is plenty; update-all prints a one-line nudge when tidy-eligible leftovers have piled up.
groundwork-docker-cache-tidyDry-run preview of the daemon-wide cleanup: stopped containers, dangling images, and builder cache from every project on this machine, not just Groundwork. That is exactly why it is a separate command — it says what it touches, and update-all and scheduled agent paths never invoke it.
groundwork-docker-cache-tidy --yesApply the daemon-wide cleanup: prune stopped containers and dangling images, and prune builder cache toward a 5 GB retention target. --keep-storage asks Docker to prune eligible cache for the selected builder while retaining a target — it does not guarantee total cache lands at or below the number. Never volumes, never tagged images.

#The shell runtime

Provisional beyond Apple Silicon

Shell adoption and Ctrla then ShiftY are covered by automated tests and verified on Apple Silicon macOS. Intel macOS, native Linux, and WSL2 receipts are still being collected — and Groundwork's rule is that support follows verification, not intent — so treat those platforms as provisional for these two commands until the receipts land. Recovery (--revert) is designed to work regardless, including with Homebrew broken or missing.

Groundwork owns the interactive shell itself, not only its configuration. The zsh that runs your prompt, plugins, completions, and tmux integration is the one Homebrew installs, so it behaves the same on macOS, Linux, and WSL2 and update-all keeps it current along with everything else. Your operating system's own shell is never modified or removed — it stays as the recovery path.

Adopting it is a deliberate step, never something an apply does behind your back, because it changes your account's login-shell record and needs sudo to register the shell in /etc/shells. It also never replaces the shell process you are sitting in: new terminals pick it up, and the command prints an exec line if you want to switch the current one immediately.

Recovery is designed for the bad day. Adoption records the exact login shell you had first, so --revert restores that shell rather than guessing a platform default — and both --status and --revert work with Homebrew missing or broken, since that is precisely when you need them. Groundwork never modifies or removes your operating system's shell.

groundwork-shell-adoptMake Groundwork's Homebrew zsh your login shell: registers it in /etc/shells (idempotent, needs sudo), runs chsh, then verifies the account record actually changed.
groundwork-shell-adopt --statusReport the shell runtime state — managed zsh and version, the account's login shell, the $SHELL session value, the recorded previous shell, and /etc/shells registration — and change nothing.
groundwork-shell-adopt --revertRestore the login shell you had before adopting — the exact one, recorded at adoption, not a guessed default (on Linux that is often bash, not zsh). It deliberately works with Homebrew missing or broken, because that is the incident it exists for.
groundwork-shell-adopt --revert-to <shell>Restore a specific login shell, for the case where no previous one was recorded.

#Shell aliases and search

llDetailed file listing with hidden files, Git status, and directories first.
ltTree view of the current folder, two levels deep; the quick shape-of-a-repo view.
lttTree view, three levels deep, when lt is not enough.
lgOpen lazygit.
vim / viOpen Neovim.
catUse bat for syntax-highlighted file viewing.
z nameJump to a recent/frequent folder with zoxide.
CtrlRSearch shell history with Atuin.
atuin ai inlineRun Atuin's opt-in English-to-shell assistant after setup.
AltCFuzzy-pick a folder, preview it as a tree, and cd into it.
CtrlTFuzzy-pick files with a bat preview and insert the chosen paths into the current command.
mkdir -p folderCreate a folder, including parent folders if needed.
pwd / open .Print the current folder / open it in Finder.
rg text / fd nameSearch file contents / find files by name.
rga textSearch hidden and gitignored files too, while still skipping .git internals.
rgf [text]Fuzzy-pick ripgrep results, preview with bat, and open the selected line in the editor.
yOpen Yazi, a visual file browser with inline preview; quit to land the shell in whatever folder you were browsing.
largest [path]Show the 50 largest files and folders under a path, then print a cleanup guide that routes each kind of result to its owning tool.
largest --files /Show only files on the startup filesystem. A complete macOS scan requires Full Disk Access for the terminal.
largest --folders ~Show only folders under your home folder. Add -n N to change the result count.

#tmux commands

tmux new -s nameStart a named tmux session.
tmux attach -t nameAttach to an existing session.
tmuxp load .tmuxp.yamlStart a reusable project layout from a checked-in config file.
tmuxp freeze > .tmuxp.yamlCapture the current session as a starter layout config.
tmux-freshStart tmux without restoring the previous resurrect snapshot.
~/.config/tmux/plugins/tpm/bin/update_plugins allUpdate tmux plugins from a normal shell when TPM's tmux pane is hard to read.
Ctrla then ShiftYCopy the last completed command and its output into a tmux buffer (and the clipboard when supported), selected by semantic prompt marks; a platform + working-directory line is prepended so pasted output tells an agent where it ran. Review for secrets before pasting into an AI tool.
Ctrla then ShiftRRepo picker popup: fuzzy-search every repository discovered under your configured roots, preview its git status, and jump to (or open) its lazygit window. See tmux repo windows.
Ctrla then ShiftGOpen a lazygit window for every repository with uncommitted changes (groundwork-repos changed).
Ctrla then zZoom the active pane to fill the screen; press again to restore. The status line shows Z while zoomed — this is what happened if a pane suddenly took over the screen.
Ctrla then xClose the active pane. It asks first, because x sits next to z (zoom).
Ctrla then CtrlsSave a tmux-resurrect snapshot now.
Ctrla then CtrlrRestore the last tmux-resurrect snapshot.
Ctrla then ShiftUUpdate tmux plugins with TPM. Press Esc or Enter if the update pane does not repaint.
Ctrla then ShiftIInstall missing tmux plugins with TPM.
Ctrla then rReload the tmux config.
Ctrla then PRename the active pane.
Ctrla then ,Rename the active window.
Ctrla then &Close the current window and all panes inside it.
tmux kill-session -t nameEnd a named tmux session.
tmux kill-serverEnd every live tmux session; snapshots are separate.
Alth/j/k/lMove between tmux panes and Neovim splits with the same keys.

#Mac and display commands

defaultbrowserList or set the default browser. After setup, run defaultbrowser zen.
browser-extensionsList or open the recommended extension links for Zen, Chrome, and Dia.
raycast-extensionsList or open the recommended Raycast Store entries for browser bookmarks, tabs, Anybox, GitHub, Homebrew, project openers, and optional AI.
betterdisplaycliBetterDisplay's CLI for display automation.
open -a ZenOpen a macOS app from the shell. The zen alias does this for Zen.

#Practice

1

Ask for help first

Run groundwork-help, then groundwork-help update, then groundwork-help project. Notice which commands are aliases, functions, keys, or real executables.

2

Trace one command

Run which groundwork-help, which new-project, and type update-all. That teaches where commands come from: installed scripts, shell functions, and aliases.