zc-workstationDocumentation PDF GitHub

Commands

Every zc-workstation command, flag and exit code.

.md
Command What it does

plan

Read live state and list what would change. The default.

apply [FILE]

Make the changes, then plan again to verify. With a saved plan, exactly those.

status

What is in place per target, without diffing. Targets for another system show as skipped.

list

Every package, app, dotfile and runtime the config declares, one line each: its state, installed version, the app it configures, and why it is there. Then what is installed but declared nowhere.

add ITEM

Declare a package or runtime in one target’s manifest, with why it is there. Installs nothing; plan and apply do.

capture

Print the manifest one target would declare to match this machine. Needs -target.

validate

Check the config offline.

doctor

Machine health: binary, system, config, the programs targets need, and each target. It also warns about weak security settings, keys without a passphrase, unsigned commits, secrets in the shell history and risky AI agent configs, each with its fix. Every check.

upgrade

Save what is outdated to the state folder, then apply -upgrade. The review skill reads the snapshot to say what moved from what to what.

history

What was applied, newest first: when, which target, from the terminal, the dashboard or an agent, and whether it worked. Drift changes show here too.

drift

Plan, and save how many changes are outstanding to ~/.local/state/zc-workstation/drift.json, which ZCBar and SketchyBar show. -last prints the saved one; -plist prints a launchd agent that runs it every morning.

ui

The dashboard.

mcp

The MCP server on standard input and output.

version

The version.

Flags

-config FILE

Config file, or - for standard input.

-target NAME

Limit to this target; repeat for several.

-out FILE

plan: save the plan for apply FILE. capture: write the manifest to FILE instead of standard output.

-force

capture -out: overwrite FILE if it exists.

-state LIST

list: only these states, comma-separated, such as outdated,not declared.

-kind LIST

list: only these kinds, comma-separated, such as cask,mas.

-adoc

list: the declared packages and runtimes as an AsciiDoc page, one table per section.

-why TEXT

add: one line on why the item is there.

-section NAME

add: the manifest section to add it under; a new name starts a new section.

-json

plan, status, list and doctor: machine-readable.

-exit-code

plan: exit 2 for changes, 3 for manual-only.

-upgrade

plan, apply: also upgrade outdated packages and runtimes, as if every packages and tools target said upgrade: true.

-only RESOURCE

plan, apply: only the change to this resource, such as brew:tmux; repeat for several. A saved plan remembers -upgrade and -only.

-yes

apply: skip the confirmation prompt for deletions.

Inventory

$ zc-workstation list
TARGET    KIND     NAME                STATE     VERSION   APP       ABOUT
brew      brew     starship            in place      1.26.0       -         Cross-shell prompt for astronauts
brew      brew     tmux                outdated      3.7c -> 3.8  -         Terminal multiplexer
brew      cask     ghostty             in place      1.3.1        Ghostty   Terminal emulator that uses platform UI and GPU
brew      cask     google-chrome       missing       -            -         -
brew      brew     jq                  not declared  1.8.1        -         Lightweight and flexible command-line JSON processor
dotfiles  dotfile  .config/ghostty/config  in place  -            ghostty   -
runtimes  runtime  node                in place      24.21.0      -         requested lts

States:

  • in place: installed, and nothing newer is known.

  • outdated: installed, and the manager has a newer version. A pinned formula never shows as outdated.

  • missing: declared, not installed.

  • differs: a dotfile that does not match its source, or a script still to run.

  • manual: an entry zc-workstation does not install.

  • not declared: installed on purpose, but no target declares it. For brew these are formulae installed on request, casks, taps and App Store apps; a dependency never shows.

ABOUT is the item’s why, from its manifest line, else the package manager’s description; JSON has both, as why and desc, with the section. Versions and descriptions come from the package manager; see Providers. A target for another system is listed as skipped, one that cannot be read with its error. The reads run side by side, so a full list takes about two seconds on a Mac with a few hundred packages.

$ zc-workstation list -state outdated
$ zc-workstation list -state "not declared" -kind cask,mas

Why each item is there

A manifest says what is installed; its comments say why. A comment at the end of an entry’s line is that entry’s why. A comment line that opens a block, after a blank line or at the top of the file, is the heading of the section below it.

# Git
brew "git-delta" # Side-by-side, syntax-highlighted git diffs
brew "lazygit"   # Git in a terminal UI, opened from tmux with prefix g

The same works in apt and dnf package lists and in a mise config’s [tools] table: go = "1.26" # Builds zc-workstation.

list, the dashboard and the MCP list tool show the why. list -adoc turns it into a page that names every tool, links its homepage, and says why you have it; keep that page in your config repository and generate it again after a change:

$ zc-workstation list -adoc > docs/software.adoc

Add

add writes one entry, with its why, into a target’s manifest. It installs nothing: plan and apply do, so you review the change like any other edit.

$ zc-workstation add -target packages -section Git -why "Fixup commits that find their target" brew:git-absorb
added brew:git-absorb to /Users/me/config/Brewfile; review and install it with: zc-workstation plan -target packages
$ zc-workstation add -target runtimes -why "Builds zc-workstation" runtime:go@1.26
  • The item is kind:name: brew, cask or tap for a Brewfile, the manager’s name for apt and dnf, runtime for mise. A bare name is a formula, a package or a runtime.

  • A formula or cask from a third-party tap, such as brew:cirruslabs/cli/tart, brings its tap line too.

  • A runtime is name@version; without a version it asks for latest.

  • In a manifest with sections, -section is required, and the error lists them. An item the manifest already declares is refused.

  • App Store apps need their id: write the mas line yourself. winget files are not edited.

Remove

remove uninstalls one item now and drops its line from the manifest, so the next plan does not bring it back. Apply only ever adds; remove is how something leaves.

$ zc-workstation remove -target packages brew:htop
removed brew:htop and dropped it from /Users/me/config/Brewfile
$ zc-workstation remove -target packages mas:409201541
  • The item is kind:name as list shows it. An item the manifest does not declare is still uninstalled, so leftovers go through the tool too.

  • An App Store app that is not declared is named by its id.

Capture

capture writes the manifest that would declare what the machine has: a Brewfile for brew, one package per line for apt and dnf, and a [tools] table for mise. Use it to start a config from a machine you already set up, or to see what a manifest misses.

$ zc-workstation capture -target brew > Brewfile.new
$ zc-workstation capture -target brew -out Brewfile.new
captured brew to Brewfile.new

It writes standard output unless you pass -out, and it refuses to replace a file without -force. Diff the result against your manifest and copy what you want; a capture lists everything installed on request, including what you only tried once. winget has its own: winget export -o FILE.

Installs ask in the terminal

Apply runs installers attached to your terminal, so sudo, a license prompt or an App Store sign-in can ask you. Their output goes to standard error, so -json output stays parseable.

With no terminal, as under an AI agent or launchd, sudo asks for your password in a macOS dialog instead, through SUDO_ASKPASS; Touch ID still comes first when sudo is set up for it. A SUDO_ASKPASS you set yourself is left alone.

Deletions ask first

A plan with a DELETE stops and asks you to type yes. Without a terminal, and without -yes, apply is cancelled. Agents never get -yes.

Drift watch

drift -plist prints a launchd agent that runs drift at 9:00 (-hour moves it). Keep it in your dotfiles and loaded by a services target, like any other agent:

$ zc-workstation drift -plist > Library/LaunchAgents/dev.zoolcoder.zc-workstation.drift.plist

The agent carries the PATH of the shell that printed it, since launchd starts agents with almost none. It only plans; nothing is applied. A change in the count is written to history, and SketchyBar’s drift item shows the count while it is above zero.

Exit codes

0

Nothing to do, or the apply converged and was verified.

1

Something failed.

2

With -exit-code: changes are outstanding.

3

With -exit-code: nothing left but MANUAL items.

JSON

$ zc-workstation plan -json
{
  "changes": 1,
  "manual": 1,
  "actions": [
    {
      "op": "CREATE",
      "target": "brew",
      "resource": "cask:ghostty",
      "detail": "install with brew",
      "manual": false
    },
    {
      "op": "MANUAL",
      "target": "runtimes",
      "resource": "trust",
      "detail": "mise does not trust /Users/me/dotfiles/mise.toml; review it, then run mise trust /Users/me/dotfiles/mise.toml",
      "manual": true
    }
  ]
}

Reference

The usage text, as zc-workstation help prints it:

zc-workstation sets up this machine from a YAML file: packages, dotfiles
(chezmoi) and runtimes (mise), on macOS, Linux and Windows.

Usage:
  zc-workstation plan     [flags]        show what would change (default)
  zc-workstation apply    [flags] [file] make the changes; with a saved plan, exactly those
  zc-workstation status   [flags]        what is in place right now, per target
  zc-workstation list     [flags]        every package, dotfile and runtime, with versions
  zc-workstation capture  -target t      write t's manifest from what this machine has
  zc-workstation validate [flags]        check the config offline
  zc-workstation doctor   [-json]        health: system, config, programs, targets
  zc-workstation upgrade  [flags]        save what is outdated, then upgrade it
  zc-workstation history  [-n 20] [-json] what was applied, newest first
  zc-workstation drift    [-last] [-json] count what differs from the config, and save it;
                                 -plist prints a launchd agent that runs it daily
  zc-workstation add      -target t -why w [-section s] kind:name
                                declare an item in t's manifest; plan and apply install it
  zc-workstation mcp                     stdio MCP server for agents
  zc-workstation remove   -target t kind:name
                                uninstall an item and drop it from t's manifest; mas:ID for an undeclared app
  zc-workstation ui       [-addr a]      the admin page, on loopback only
  zc-workstation version

Exit codes:
  0  nothing to do, or the apply converged and was verified
  1  something failed
  2  with -exit-code: changes are outstanding
  3  with -exit-code: nothing left but MANUAL items

Flags:
  -config string   config file, or "-" for standard input; default: $ZC_WORKSTATION_CONFIG,
                   then ./zc-workstation.yaml, then ~/.config/zc-workstation/zc-workstation.yaml
  -target value    limit to this target; repeat for several
  -yes             skip the confirmation prompt for deletions
  -exit-code       plan: exit 2 for changes, 3 for manual-only
  -upgrade         plan and apply: also upgrade outdated packages and runtimes
  -only value      plan and apply: only the change to this resource, such as
                   brew:tmux; repeat for several
  -json            plan, status and list: machine-readable
  -out string      plan: save the plan, to apply exactly what you reviewed;
                   capture: write the manifest to this file, not stdout
  -force           capture: overwrite the -out file
  -state list      list: only these states, comma-separated (missing,outdated,...)
  -kind list       list: only these kinds, comma-separated (brew,cask,dotfile,...)
  -adoc            list: the declared packages and runtimes, with why each is
                   there, as an AsciiDoc page

Edit this page Last updated

Keyboard shortcuts

Ctrl/⌘ K or /
Search the docs
↑ ↓ Enter
Move through the search results
← →
Previous or next page
f
Filter the pages in the sidebar
g h
Home
t
Cycle the theme
?
This list
Esc
Close a menu or the search results