Groundwork

Commands

The modern command line: the names you know, far better tools underneath.

The classic Unix commands have faster, friendlier rewrites. This setup installs them and points the familiar names straight at them, because these are the same commands agents run, quote, and debug with you. Learn the words once and you can supervise both your own shell and an agent's shell.

When you'll want this You're retyping long commands, hunting through history, or trying to explain a terminal problem to an agent.
Quick reference — the keys worth memorizing
CtrlR search history
CtrlT insert a file path
AltC pick a folder and cd
CtrlE accept suggestion
CtrlA / CtrlE line start / end
CtrlW delete word behind
CtrlU clear the line
CtrlL clear the screen
Tab complete (case-insensitive)
z name jump to a folder
mkdir -p folder add a folder
pwd / open . show / open here
rg text search file contents
copy-context copy code context

#Already wired up

Two of the commands you'll type most are remapped the moment your shell starts. You don't do anything — ls and cat already run the better tools:

lsruns eza — colored, sorted, folders first
lleza in long form with permissions, sizes, and a git status column
lteza as a two-deep tree, the quick shape-of-a-repo view (ltt goes three deep)
catruns bat — syntax highlighting and line numbers
The originals are still there

Need the plain version? Put a backslash in front: \ls or \cat runs the untouched original. You never lose the classic behavior.

#Moving on the command line

Before you reach for the mouse or the arrow keys, these editing shortcuts move and erase text far faster. They work on every command you type, in any shell.

CtrlA / CtrlEJump to the start / end of the line
AltB / AltFMove back / forward one word
CtrlWDelete the word behind the cursor
CtrlU / CtrlKDelete to the start / end of the line
CtrlYPaste back whatever you just deleted
CtrlRSearch your history — start typing, pick, Enter
CtrlP / CtrlNPrevious / next command — the home-row /, so your hands stay put

Two more that save real typing: as you type, the shell suggests the rest of a past command in grey — press CtrlE to accept it without leaving the home row. (It's the same "jump to end of line" key; since the line ends with the suggestion, that fills it in. The arrow works too, but reaching for it is the habit worth dropping.) And !! repeats your last command (great as sudo !!), while !$ reuses its last argument.

Quality-of-life the shell is set up for

Type a folder's name with no cd in front to jump straight into it. Run cd - then Tab to bounce between recent folders. And start any command with a space to keep it out of your history — handy for anything with a password or token in it.

Inside tmux, shell keys stay shell keys

The commands on this page are identical whether or not you're in tmux. Pane and Neovim-split navigation uses Alth/j/k/l, leaving the classic shell controls alone: CtrlL clears, CtrlK deletes to the end of the line, and CtrlR searches history.

#Folders and files

These are the folder moves beginners ask for first, and the commands experienced developers still type all day. . means "this folder"; .. means "the parent folder."

pwdPrint the current folder path
open .Open the current folder in Finder on macOS. In WSL, use explorer.exe .
cd ..Move up one folder
cd -Jump back to the previous folder
mkdir -p notes/ideasAdd a folder, including parent folders if needed
touch todo.mdAdd an empty file or update its timestamp
mv old newRename or move a file/folder
cp -R src destCopy a folder recursively
rm -i fileRemove a file, asking before it does it
Respect rm

rm is permanent from the shell. Beginners should use rm -i until they understand exactly what will disappear, and avoid copied commands like rm -rf unless they can explain every character.

#The daily drivers

Why the names stick

Command names are easier to remember when they connect to the older Unix word they replace. Some names have official explanations; others are best treated as mnemonics, not gospel.

ezaThink "exa, continued." The official project describes it as a modern ls replacement and lists features beyond exa, but does not publish a clean expansion for the name.
batA cat clone "with wings": same job as cat, upgraded with highlighting, Git markers, and paging.
rg / ripgrep"Rip" as in rip through a project fast; rg is the two-letter command you type instead of long grep forms.
fdA short, friendly find. Remember it as "find, but direct."
zoxideThe z command is the memory hook: jump to frequently used folders by typing a few letters.
fzfFuzzy finder. It finds the thing even when you only remember a fuzzy part of the name.
yaziA visual file browser (the y command). Think "the folder landscape, with previews" — a TUI over the same fd/rg/fzf tools.
atuinA shell-history vault. Use the practical memory hook: "a tuned-in history."
deltaDelta means change; this makes Git changes easier to read.
dustA friendlier du that shows what is taking space. Think "disk usage, sorted tree."
dufDisk usage/free utility: the readable replacement for df.
btopA "better top": live CPU, memory, and process view of the whole machine.
tokeiJapanese for clock/time; here it quickly counts code by language.
hyperfineA benchmarking tool for fine-grained timing comparisons.
starshipThe prompt: the control panel you see before every command.
direnvDirectory environment: load project-specific env vars when you enter a folder.
miseShort for "mise-en-place": put project tools and versions in their right place.

eza — listing files

Everything ls did, plus git status, icons, and a real tree view. The name is best remembered as the maintained, modern successor to exa.

eza
$ ll
$ lt
# a two-deep tree of the current folder (eza --tree --level=2); ltt goes three deep

bat — reading files

A cat that highlights code, numbers lines, and marks git changes in the gutter. Piped into something else it quietly behaves like plain cat, so it never breaks a pipeline.

bat
$ bat README.md
$ bat -p script.sh
# -p = plain, no line numbers or borders

ripgrep — searching inside files

Search every file in a project for text, fast. Groundwork configures rg with smart case: lowercase searches match any casing, while a capital letter makes the search case-sensitive. By default it respects .gitignore and skips hidden files; use rga when you deliberately want hidden and ignored files too.

ripgrep
$ rg test              # smart-case: finds test, Test, TEST
$ rg Test              # capital letter makes it case-sensitive
$ rg -t md heading     # only markdown files
$ rga api_key          # include hidden + ignored files, still skip .git

For fuzzy content search, use rgf. It runs ripgrep, opens the matches in fzf, previews with bat, and opens the selected line in your editor.

rg + fzf
$ rgf button
$ rgf                 # live search as you type

fd — finding files by name

A simpler, quicker find. Type part of a name; it searches from here down, ignoring junk folders.

fd
$ fd config
$ fd -e png            # files ending in .png

zoxide — jumping to folders

It remembers the folders you visit. After you've been somewhere once, z plus a few letters teleports you back from anywhere — no long paths.

zoxide
$ z groundwork
$ z know               # jumps to ~/code/my-knowledge

fzf — fuzzy-finding anything

A fuzzy picker wired into the shell. Groundwork points its file picker at rg --files, so fuzzy file selection respects the same ignore rules as ripgrep. Three keystrokes are worth memorizing:

CtrlRSearch your command history — start typing, pick, Enter
CtrlTInsert a file path into the line you're typing, with a bat preview; opens as a floating popup inside tmux
AltCFuzzy-pick a folder and cd into it

yazi — browsing the folder landscape

When you want to see a directory rather than list it — skim a tree, preview files as you arrow through them, do a quick bulk rename or move — y opens Yazi, a visual file browser. It is a choice, not a replacement for cd, fd, and eza: those composable commands stay the fundamentals and the shared language with agents. Yazi is the visual layer on top for the moments you would otherwise leave the terminal for a GUI file manager. Quitting is a choice between two keys: q lands the shell in whatever folder you were browsing, so a visual detour ends where you looked; Q quits without changing directory, for when the browsing was just a peek and you want to stay where you started.

yazi
$ y
# arrow keys or hjkl to move, Enter to open in the editor, O to pick an opener
# q quits into the folder you were browsing; Q quits back where you started

Groundwork manages ~/.config/yazi/yazi.toml so the preview pane gets most of the width — the panes are for navigating, the preview is for reading. The preview is still a glance, not a reader: press Enter to open the file in the editor, or O and choose Read with bat for a highlighted, scrollable view that drops you back into Yazi when you quit it.

Image, PDF, and video previews render inline in Ghostty, which speaks the Kitty graphics protocol. Yazi already uses the fd, rg, fzf, and bat that Groundwork installs; for richer previews of more formats you can optionally add poppler (PDF) and ffmpeg (video thumbnails).

largest: find space, then clean by owner

largest wraps dust in a command that is easier to remember. It shows the biggest entries first, stays on one filesystem, and follows the scan with a cleanup guide. Start in the narrowest useful folder: scanning a project is cheaper and easier to reason about than scanning the whole drive.

largest
$ largest                 # current folder, top 50
$ largest ~               # home folder
$ largest --files /       # files on the startup filesystem
$ largest --folders -n 25 /Users
$ largest --help

On macOS, a root scan can only see protected locations when the terminal app has Full Disk Access in System Settings. On Linux, rerun with sudo only when the permission errors hide a location you actually need to inspect. Neither changes what is safe to delete.

AppsUse the vendor uninstaller when one exists. For a Homebrew app use brew uninstall --cask NAME; use AppCleaner for a plain dragged-in app. Treat --zap as an explicit deeper cleanup because it can remove shared preferences and caches.
Project outputFolders such as node_modules, .venv, target, dist, build, and coverage are often reproducible. Confirm with the repo before removing them, then let its package/build tool recreate them.
Package cachesUse the owner, such as brew cleanup or pnpm store prune. Shared caches are not corruption; removing them trades space now for downloads later.
ContainersRun groundwork-doctor (or docker system df) first for a read-only picture of usage and leftovers. Clean stopped containers, images, build cache, and volumes deliberately — a repo's own clean commands first, then Docker Desktop or the matching Docker prune command.
XcodeRemove runtimes in Xcode Settings, delete unavailable simulators with xcrun simctl delete unavailable, and remember that DerivedData can be rebuilt.
Archives and mediaReview old .dmg, .pkg, .zip, video, and audio files. Trash replaceable downloads; move irreplaceable originals to deliberate archive storage.
Photos and cloud filesDelete through Photos or the sync provider so its database, cloud state, and recently-deleted recovery stay consistent.
Logs, caches, and system dataPrefer the owning app's retention controls or macOS System Settings → General → Storage. Do not bulk-delete ~/Library, /var, APFS snapshots, or protected OS data just because it is large.
Size is a lead, not permission

A large file may be the only copy of something important. Inspect it, identify the owner, check backups, and use Trash when practical so recovery remains possible.

copy-context — sharing exact files with another AI tool

When a chat app or outside reviewer cannot see your repo, copy a small, explicit bundle instead of pasting random snippets. copy-context takes files or folders, expands folders through rg --files when available, and puts path-labeled contents on the clipboard. It works the same way inside tmux because it is just a shell command.

copy-context
$ copy-context AGENTS.md src tests
$ copy-context --stdout docs/workflow.html
$ copy-context --paths-only src

Default folder expansion respects ignore rules, so dependency and build folders stay out of the prompt. Use --stdout before pasting when the files are sensitive or large, and never copy secrets into a chat tool.

#Also installed

You won't reach for these every hour, but they're there when you need them.

atuinRecords every command with its context, fully searchable — a much smarter history behind CtrlR
deltaMakes git diff readable — syntax-highlighted, line-by-line
dustShows what's eating disk space, as a tree (a friendlier du)
dufFree disk space in clean tables (a friendlier df)
btopLive CPU, memory, and process monitor (a friendlier top) — your first stop when the machine feels slow
tokeiCounts lines of code in a project, by language
hyperfineBenchmarks a command properly, over many runs
starshipThe prompt itself — your OS and current folder on every line, so copied terminal text carries its context
direnvLoads per-folder settings from a .envrc when you enter (after you allow it)
miseGives each project the right tool versions (Node, Python…) automatically

#Practice

1

See git status while you list

Inside any Git repo, run ll and look at the status column on the left. Change a file, run it again, watch the marker appear. Then map the layout with lt.

2

Find, then search, then read

Locate a file by name with fd readme. Search the project's contents with rg "install". Open one of the hits with bat <file> and notice the highlighting. That find → search → read loop is most of a workday.

3

Teleport between folders

Visit two or three deep folders normally with cd. Now, from anywhere, jump straight back with z and a few letters of one. The more you use it, the better its guesses get.

4

Stop retyping commands

Press CtrlR and type a few letters of something you ran earlier. Pick it, Enter. Then try CtrlT mid-command to drop in a file path without typing it.

5

Edit a line without arrow keys

Type a long command but don't run it. Jump to the front with CtrlA, to the end with CtrlE, rub out the last word with CtrlW, then wipe the whole line with CtrlU. A week of this and your hands stop leaving the home row.

Learn one at a time

Don't try to absorb the whole table today. Pick rg and z this week — they pay off immediately — and let the rest arrive as you need them. Every tool answers --help.

#Under the hood: the zsh setup

The shell you're typing into is zsh, and a few small pieces make it feel modern. You don't configure any of this by hand — it's all in ~/.zshrc and managed by the setup — but here's what's running and why.

Plugins, loaded by antidote

antidote is the plugin manager. It reads a plain list of plugins from ~/.zsh_plugins.txt and loads them fast (it pre-compiles them so your shell still starts instantly). Three plugins are on that list:

zsh-autosuggestionsThe grey "ghost" text that predicts the rest of a command from your history. Press CtrlE to accept the whole thing (home row — no reaching for the arrows), or AltF to take just the next word.
fast-syntax-highlightingColors your command line as you type — valid commands go one color, unknown ones another, quotes and paths are highlighted. A typo in a command name is visible before you hit Enter.
ez-compinitPlumbing: it makes zsh's tab-completion system initialize correctly and quickly. You never interact with it directly — it just keeps Tab completion fast and unbroken.
Adding a plugin

Want another? Add its user/repo to ~/.zsh_plugins.txt (via chezmoi edit so it sticks), then exec zsh to reload. antidote clones and compiles it on the next start.

The init lines — what each tool hooks into your shell

Several tools need a line in ~/.zshrc to wire themselves in. This is where the prompt, directory-jumping, history, and version-switching come from:

starshipDraws the prompt — your OS badge and current folder, plus how long the last command took. The folder repeats on every line so pasted terminal output tells an AI tool where commands ran; git status answers branch questions and mise current answers which tool versions are active
zoxideThe z command. Type zi instead for an interactive fuzzy picker of your visited folders
atuinThe smarter CtrlR history (searchable by folder, exit code, time). It leaves your arrow alone for plain recall, and Groundwork keeps Atuin AI's bare ? binding off until you opt in
fzfThe fuzzy finder behind CtrlR / CtrlT / AltC
miseSwitches tool versions (Node, Python, Go…) automatically per project — see below
direnvLoads a folder's .envrc when you cd in (after you approve it once)

History that's actually useful

zsh is tuned to keep a long, shared, de-duplicated history: 100,000 lines, written across all your open tabs at once, with exact duplicates collapsed. Two habits this unlocks: start a command with a space and it's kept out of history entirely (for anything with a secret in it), and CtrlR (atuin) searches the whole thing instantly.

Atuin AI is opt-in

Recent Atuin versions include an English-to-shell assistant. Groundwork disables the bare ? binding by default so an empty prompt does not open a setup screen by surprise. To try it deliberately, run atuin setup, create the local marker with mkdir -p ~/.config/groundwork && touch ~/.config/groundwork/enable-atuin-ai, then restart the shell with exec zsh. You can still run the assistant directly with atuin ai inline.

Per-project tool versions — mise

The experienced-developer payoff. Instead of juggling nvm, pyenv, and friends, mise reads a mise.toml (or .tool-versions) in a project and gives you exactly the runtimes it asks for — the moment you cd in.

mise — set a project's tools
$ cd ~/code/my-app
$ mise use node@22 python@3.13   # pins them in mise.toml + installs
$ node --version                 # the pinned version, here only
$ mise install                   # on a fresh clone: grab everything the toml asks for

Walk into a different project and the versions change to match it automatically. Globally, this setup pins Node LTS, pnpm, and Python (in ~/.config/mise/config.toml), so every machine agrees on the baseline. mise install installs missing declared versions; mise upgrade deliberately advances floating baselines such as Node LTS to the newest matching patch. Upgrades are live at your next prompt, even in terminals that are already open, because mise refreshes the shell's PATH before each prompt. Python projects should still use uv for dependencies and virtual environments; mise supplies the interpreter and will source an existing uv .venv when you enter the project.

Resource defaults — open files and parallel builds

Two lines in ~/.zshrc make sure commands actually use a modern machine. Both are derived from the machine they run on, so the same setup behaves correctly on a laptop, a Linux box, or inside a container.

Open-file limitmacOS still caps each process at 256 open files by default — a limit from another era. File watchers, bundlers, language servers, and agent sessions blow through it and fail with a confusing too many open files. The shell raises its soft limit to 10,240 (never above the hard limit the system allows), and every command you or an agent launches from it inherits the room.
make parallelismMost modern build tools detect your CPU cores and parallelize on their own. Plain make does not — it builds one file at a time unless told otherwise. MAKEFLAGS is set to -j<cores>, counted on this machine at shell start, so compiling dependencies uses the hardware you paid for.

What's deliberately not configured: hard CPU or memory caps. macOS can't enforce per-process memory limits anyway, and a cap that silently kills a build is a worse beginner experience than a slow one. When you genuinely need hard limits — a runaway workload, or bounding an agent's sandbox — run it in a container, which is where real limits live: docker run --cpus 2 --memory 2g …. To see what's using the machine right now, run btop.

#Going deeper