Skip to content

CLI reference

The pz command line, built on System.CommandLine + Spectre.Console. Every command runs the same eight phases (load → restore-check → compile → validate → plan → execute → finalize → report); pz compile and pz plan stop early. See The execution model.

Verb Does
pz init <name> [--template <id>] Scaffold a new project from a built-in template. The default minimal writes project.yml + connections.yml (plus a README and .gitignore), ready to author against; --template picks one of five instead. pz init --list-templates prints the catalog and scaffolds nothing
pz restore Resolve declared non-builtin connector packages, fetch them, write pz.lock.json
pz validate [--connect] Validate config/SQL (tiers 1–4); with --connect, also probe live connectivity and schema drift (tier 5). Sink output options are not schema-validated in v0 — the connectors themselves validate them at plan/probe time
pz compile Render pipelines, build the DAG, write .pz/target artifacts (no execution)
pz plan [names...] [--select ...] [--all] Compile + print the per-node execution strategy and static memory budget, without running; names/--select/--all filter the printed table only
pz run [names...] [--all] [--select ...] [--vars '{...}'] [--full-refresh] [--fail-fast] Execute the DAG: pz run <name> runs one flow (the node plus all ancestors and descendants); bare pz run errors with PZ0215 when the project has 2+ independent flows — pass --all to run everything
pz test [--select ...] Execute only data-quality checks and their required ancestors
pz ls [--select ...] List every project node in topological order: kind, name, tags
pz retry Re-execute only the nodes that didn’t succeed in the most recent run (plus their dependents); reuses the failed run’s staged source data when eligible (see Delivery guarantees); refuses interrupted or fatal runs
pz connectors List every registered connector (builtin + restored) with its package, version, tiers, and capabilities — one row per connector, including src:native-only/snk:native-only markers
pz clean [--keep-last N] [--older-than DUR] [--purge] [--dry-run] Reclaim space from .pz/runs (or, under a SQL state backend, from the configured store): deletes staging.duckdb from every run but the newest, keeping every run_results.json. Never touches .pz/state, .pz/target, or .pz/packages. pz run also sweeps automatically at the end of every run — see retention: in project structure. pz clean remains the on-demand verb, and the only way to purge whole run directories or select by age. Like pz state, it reads only project.yml’s state: key (plus connections.yml when state.connection names an entry) — a config that no longer parses never blocks a cleanup. Under a SQL-backed state.artifacts, a swept run is always deleted whole regardless of --purge — the run’s rows and its local .pz/runs/<id>/ directory, which under that backend holds nothing but staging.duckdb; see Move state off the local disk
pz state show [<key>] Report stored watermark and sync state; with a key, add that dataset’s run-by-run history and any manual changes. Under backend: local (the default) this is free: only project.yml’s own state: key is read — no pipeline validation, no connections.yml, no connectors, no network. Under a SQL state backend it additionally reads connections.yml (only when state.connection names an entry) and does real network I/O, and can fail with PZ0518/PZ0519 — reads never mutate anything, on either backend
pz state rollback <key> --to-run <id> Roll a watermark back to the value a named prior run advanced it to (backward only). Same backend exposure as pz state show above: free under backend: local, network I/O and PZ0518/PZ0519 under a SQL state backend
pz state set <key> --value <v> Set a watermark’s value directly, either direction; existing entries only. Same backend exposure as pz state show above
pz state clear <key> Remove a watermark entry, so the next run extracts that dataset in full. Same backend exposure as pz state show above
pz cdc status Report server-side change-capture state for every sync: {mode: cdc} dataset in the project — whether the source-side capture object still exists and whether pz’s stored position is still readable from it. Exits 0 when every reported dataset is healthy, 1 when any is not; see Capture changes with CDC
pz cdc drop <source>.<dataset> Drop the server-side change-capture state for one cdc dataset and clear pz’s sync-state entry for it (in whichever store state: resolved to), so the next run re-snapshots that dataset in full. Exactly one target — there is no bulk drop
pz schema accept [<connection>.<entity> ...] Accept the latest run’s observed schema for one or more contract-less source datasets as the new on_source_drift baseline (default: every dataset the latest run recorded a differing observed schema for). Never opens a connector — reads only the latest run’s artifacts and the current baseline; see Detect schema drift at run time
pz mcp [--allow-run] Serve the current project to AI agents over the Model Context Protocol (stdio only). Registers the introspect/verify/author/docs tools; --allow-run additionally exposes pz_run, pz_retry, and pz_run_results, which move real data. stdout belongs to the protocol — everything the CLI would print goes to the client as a log. See Use pz with an AI agent and the MCP contract
pz mcp init [<client>...] [--all] [--allow-run] [--skill-locations <list>] Write MCP client config for vscode, claude-code, copilot-cli, and/or opencode (merge-preserving — an existing config keeps its other servers) and install the pz-pipelines skill. --all wires all four
Option On Does
--template <id>, -t init Which built-in template to scaffold (default minimal): minimal, sample, incremental, http, sqlserver — see the template catalog. Every non-minimal template’s pipelines compile and run, so they are opt-in: a bare pz init never leaves you demo files to delete before authoring your own. Unknown id is PZ0131
--list-templates init Print every built-in template — id, one-line summary, and what it needs to run — then exit without scaffolding. Passing a project name alongside it is PZ0132
--select <selector> plan, run, test Limit to matching nodes; selection syntax is dbt’s: orders_enriched+ (node and descendants), +node, tag:daily, source:crm.*, unions/intersections
[names...] (positional) plan, run Flow names: each selects that node plus every ancestor and descendant (the whole flow through it); exact node names only (wildcards/tags are --select’s job)
--all plan, run Select the whole project explicitly; required for run when the project has 2+ independent flows. Names, --select, and --all are mutually exclusive (PZ0216)
--vars '{...}' run Override project.yml vars for this invocation
--full-refresh run Ignore stored watermarks this invocation; windowed datasets restart from initial
--fail-fast run Cancel everything on the first node failure (default: fail the node, skip its descendants, continue independent branches)
--connect validate Add tier-5 online checks: connectivity, schema drift, sink permission probes
--project <dir> all Run against a project directory other than the current one
--log-format json executing verbs Swap the human renderer for NDJSON events — same event stream, two renderers; contract in Run events
--keep-last <N> clean Keep the newest N runs (default: 1). 0 selects every run, including the newest — after which pz retry has no target. Mutually exclusive with --older-than (PZ0511)
--older-than <dur> clean Select runs older than 30m/12h/7d. The newest run is kept regardless; only --keep-last 0 gives it up
--purge clean Delete whole run directories instead of only staging.duckdb
--dry-run clean Print what would be deleted, and delete nothing
--to-run <id> state rollback The run whose recorded watermark becomes the new value; pick one from pz state show <key> (PZ0514 when it recorded none)
--value <v> state set The new cursor value, canonicalized against the stored cursor type (PZ0515 when it will not parse)
--reason "..." state writes Free text recorded in .pz/state/audit.jsonl alongside the replaced value
--dry-run state writes Print what would change, and change nothing
--yes state writes Skip the confirmation prompt; required when stdout is not a TTY (PZ0516)
--no-lock-check executing verbs Skip the lock-file drift check (emergencies only; loud)
--otel-endpoint <url> executing verbs Wire the OTLP exporter for spans/meters (also settable via env); zero cost when off
--quiet, -v, -vv all Adjust console verbosity

Console output during run is a live tree on a TTY (per-node status, rows moved, throughput, elapsed) and plain sequential lines when piped. CI is auto-detected: no ANSI, no live rendering.

pz init scaffolds from a fixed set of built-in templates. Each one is a real, in-place-runnable pz project — the same directories the pz repository keeps under templates/, embedded into the tool, so an installed pz scaffolds them with no source tree and no network. pz init --list-templates prints this table without scaffolding anything.

Id What it scaffolds Runnability
minimal (default) project.yml + connections.yml, commented and ready to author against nothing to run yet — it loads and compiles, but declares no pipelines
sample runnable four-pipeline demo over local CSVs: staging, a checked join, an aggregate; two independent flows runs offline
incremental watermark-bounded reads over local CSVs: run it twice, see the second run land nothing runs offline
http GitHub REST API to a parquet delta log: pagination, a crawl guard, a typed contract needs internet, no credentials
sqlserver SQL Server to SQL Server: incremental merge, six kinds of check, optional remote state needs a reachable SQL Server (ERP_DB_*/MART_DB_* env vars)

minimal is the default deliberately: every other template’s pipelines compile, so until you delete them pz run --all moves data you did not ask for. After scaffolding, pz init prints the next command for the template you picked.

Code Meaning
0 Every node succeeded
1 Run completed with node failures
2 Configuration or validation error
3 Fatal engine error