CLI reference
The complete command and flag reference. Every entry here is grounded in the
argument parser (cli_spec.ts, cli.ts) and the
launcher CLI (@zuke/cli), not the help text alone — so what you
read is what the parser actually accepts.
# The global CLI (project scaffolding), installed once:
deno install -A -g -n zuke jsr:@zuke/cli
zuke setup # writes zuke.ts and a ./zuke launcher
# Everything after setup runs through the scaffolded launcher:
./zuke build # deno run -A zuke.ts build Two commands named zuke
There are two distinct programs, and the flags below belong to one or the other:
- The global CLI (
@zuke/cli, installed withdeno install) is the pre-build tool: it scaffolds a project (setup,import), documents packages (doc), and reports its own--version. You run it aszukebefore a build file exists. - The build launcher — the
./zukescriptsetupwrites, which is justdeno run -A zuke.ts— drives your build: it runs targets and hosts every build-aware command (graph,generate-ci,completions,runs,resume,cancel,mcp,register, and its owndoc).
Below, zuke denotes the global CLI and ./zuke the
build launcher. Both accept --help/-h.
The reserved command words (graph, generate-ci,
completions, mcp, resume,
runs, cancel, register,
doc) are not runnable as target names — a target called
graph can't be launched by name. The parser, --help,
and shell completion all read this list from a single source of truth, so
they never drift out of sync.
Project: setup, import, doc, version
zuke setup scaffolds a starter zuke.ts and a
./zuke launcher into a directory. It prompts for the build class
name and whether to overwrite unless --yes is given or stdin
isn't a terminal.
zuke setup # scaffold into the current directory
zuke setup --dir services/api # scaffold into a subdirectory
zuke setup --name ApiBuild --yes # name the Build class, skip prompts
zuke setup --launcher-name make # write ./make when a zuke/ dir is in the way zuke setup | Effect |
|---|---|
--dir <path> | directory to scaffold into (default .) |
--name <Class> | the Build subclass name for zuke.ts (default MyBuild) |
--launcher-name <name> | base name for the launcher scripts — used when a zuke/ directory already occupies the name. Must be a single path segment |
--force, -f | overwrite existing files |
--yes, -y | accept defaults without prompting |
zuke import takes the same flags plus --from, and
generates a zuke.ts from an existing project's build scripts —
a package.json's scripts or a Makefile.
Without --from it auto-detects the source.
zuke import # auto-detect package.json scripts or a Makefile
zuke import --from package.json # force the source
zuke import --from makefile --yes zuke import | Effect |
|---|---|
--from <source> | package.json or makefile; auto-detected when omitted |
--dir, --name, --force, --yes | as for setup |
zuke doc <package> prints a package's API by running
deno doc from an isolated empty directory. A
bare name resolves to a Zuke package (core →
jsr:@zuke/core); a scoped name (@scope/pkg) becomes
jsr:@scope/pkg; and any explicit
jsr:/npm:/https:/file:/path
specifier passes through. The isolation matters inside a Node repo: run
plainly, deno doc resolves the repo's
node_modules/@types/* and buries the API under
type-resolution warnings; the empty working directory has nothing to
resolve. A relative path is pinned to your real directory first.
zuke doc core # → deno doc jsr:@zuke/core
zuke doc @scope/pkg # a scoped package
zuke doc jsr:@zuke/deno # a jsr:/npm:/https:/file: specifier, passed through
# The build launcher has its own 'doc' command, but it only passes through
# explicit specifiers/paths (jsr:/npm:/https:/file:/relative) — no bare-name
# 'core' → jsr:@zuke/core mapping, so give the full specifier:
./zuke doc jsr:@zuke/deno zuke --version (or -V) prints the CLI version.
./zuke completions
A build-launcher command: it emits a shell-completion script for
bash, zsh, or fish that completes your
build's target names, the reserved commands, the built-in flags, and any
declared parameters as --flag candidates. Unlisted targets stay
hidden, exactly as in --list. It takes an explicit sub-action
first — print or install — then the shell.
# Print a script and source it in the current shell:
source <(./zuke completions print bash)
source <(./zuke completions print zsh)
./zuke completions print fish | source
# Or wire it into your shell's startup once:
./zuke completions install zsh | Sub-action | Effect |
|---|---|
print <shell> | write the script to stdout (source it yourself) |
install <shell> | write the script under your config directory (honouring $XDG_CONFIG_HOME) and wire it into the shell's startup. Idempotent — an already-sourced rc file is left untouched |
The script is a static snapshot of the build it was generated from, so
regenerate and re-source it after you add, rename, or remove targets — the
same model as deno completions.
Running targets
./zuke <target> runs the target and all its transitive
dependencies in order. With no positional argument, the launcher runs a
target literally named default if one exists, otherwise it
prints --list. Each target prints a start banner and a
✔/✘ line with its duration; a failure aborts the
rest and exits 1.
./zuke build # run 'build' and all its dependencies
./zuke # run the 'default' target, else print --list
./zuke test --skip lint # run 'test' but skip the 'lint' dependency
./zuke ci --parallel # run independent targets concurrently
./zuke ci --parallel=4 # cap the number in flight
./zuke ci --affected=origin/main # only targets reachable from changed files
./zuke deploy --dry-run # print the plan without executing any body
./zuke build --no-cache # ignore the incremental cache; re-run all | Flag | Behaviour |
|---|---|
--skip <dep> | run the target but skip the named dependency (repeatable) |
--parallel[=N] | run independent targets concurrently, still completing every dependency before its dependents. N caps the number in flight (default: the host CPU count). See execution model |
--affected[=<base>] | run only the targets a git diff since <base> can reach (default base HEAD); a target with no declared .inputs() can't be proven unaffected, so it always runs. See affected targets |
--no-cache | ignore the incremental cache and re-run every target |
--no-remote-cache | use the local cache only; skip the remote cache store (neither restore nor upload). --no-cache disables both |
--dry-run | resolve the plan and print every target that would run (honouring --skip and each onlyWhen) without executing any body or touching the cache |
--state | persist a durable run record under .zuke/runs when no store is otherwise configured |
--actor <name> | attribute the run to <name> in its state record (else ZUKE_ACTOR, the CI actor, or "anonymous") |
--list, -l | list all targets with descriptions and dependencies |
--json | emit the whole build surface (commands, flags, targets, parameters) as JSON — for tools and agents |
./zuke --list # targets with descriptions and dependencies
./zuke --list --json # the whole build surface (commands, flags, targets) as JSON Graph & CI
./zuke graph shows the dependency graph. By default it prints the
terminal target → deps adjacency listing; --output=html
renders an interactive Cytoscape page to
<repo root>/.zuke/graph.html and opens it (Cytoscape loads
from a pinned CDN, so the first view needs internet access).
./zuke graph # print the 'target → deps' adjacency listing
./zuke graph --output=html # render an interactive page to .zuke/graph.html and open it
./zuke graph --output=html --no-open | Flag | Behaviour |
|---|---|
--output=html | render the interactive HTML page instead of terminal text (--output text is the default) |
--no-open | with --output=html, write the file without opening a browser |
./zuke generate-ci writes the CI configuration files the build
declares via cicd(). Running any target regenerates them too, so
the command is mainly for CI verification.
./zuke generate-ci # write the CI files declared via cicd()
./zuke generate-ci --check # verify they are current instead (fail if stale) — use on CI | Flag | Behaviour |
|---|---|
--check | verify the declared files are current instead of writing them, failing if any has drifted (use on CI, so an ephemeral checkout is never left dirty) |
State & orchestration
These commands read and steer persisted run records.
They resolve the state store the same way a run does — ZUKE_STATE_URL
/ ZUKE_STATE_DIR, the build's stateStore() override,
or the default .zuke/runs — and report a friendly error when no
store is configured.
./zuke ci --state # persist a run record under .zuke/runs
./zuke runs list # one row per run, newest first
./zuke runs list --status failed --limit 20
./zuke runs list --target deploy --since 2026-07-01T00:00:00Z
./zuke runs list --counts --json # aggregate { total, byStatus }
./zuke runs show <run-id> --json # one run's full per-target status
./zuke runs prune --keep 90d --keep-last 50 --dry-run ./zuke runs takes a sub-action: list,
show <id>, or prune.
./zuke runs | Behaviour |
|---|---|
list | one row per run — id, status, root target, actor, creation time — newest first |
list --status <s> | keep only runs with this status: running, suspended, cancelling, succeeded, failed, or cancelled |
list --target <t> | keep only runs whose graph contains that target |
list --since <iso> | keep only runs created at/after an ISO-8601 timestamp |
list --limit <n> | return at most the newest N (must be a positive integer) |
list --counts | print aggregate counts (a total and one line per status) instead of rows; with --json emits { total, byStatus } |
show <id> | reconstruct one run in full: header, resolved non-secret parameters, per-target status/duration/error, and any signals received |
prune --keep <age> | keep runs newer than an age (e.g. 90d); older terminal runs become eligible |
prune --keep-last <n> | always keep the newest N terminal runs |
prune --dry-run | report what would be deleted without deleting; at least one --keep/--keep-last rule is required |
--json | on list/show, emit machine-readable output (the summary array or the whole record) |
prune never touches non-terminal runs
(suspended, running, cancelling) — a
run is deleted only when it is terminal and matches neither keep rule. See
retention for who owns pruning on each
backend.
./zuke resume <id> continues a run parked at a
.waitsFor() gate; ./zuke cancel <id> stops a
run and unwinds its .onCancel(...) compensations in reverse
order. Resumption is exactly-once — concurrent resumers race
a compare-and-swap and all but one get AlreadyResumedError. See
suspend & resume and
cancellation.
./zuke resume <run-id> # continue a suspended run
./zuke resume <run-id> --signal approved --data '{"by":"alice"}'
./zuke resume --check # re-check predicate waits & timeouts
./zuke cancel <run-id> --actor ci-bot # cancel and run compensations | Flag | Behaviour |
|---|---|
resume --signal <name> | deliver a named external signal to the run |
resume --data <json> | with --signal, the signal's JSON payload (default {}; capped at 64 KiB) |
resume --check [<id>] | re-check predicate waits and enforce timeouts across suspended runs — the cron/webhook entry point |
resume --force-graph | continue even if the build graph changed since the run was suspended |
resume --actor <name>, cancel --actor <name> | attribute the resume/cancellation in the audit trail |
Ctrl-C (or SIGTERM) during a run triggers the same
graceful cancellation in-process; a second signal forces an immediate exit.
MCP & registry
./zuke mcp runs a Model Context Protocol
server over the build so an AI agent operates the pipeline through typed tool
calls. It is read-only by default (inspect targets,
parameters, graph, and runs); --allow-run adds one
run:<target> tool per target. Every mutating or denied call
is written to an audit trail (./zuke runs show mcp-audit).
./zuke mcp # read-only MCP server on stdio
./zuke mcp --allow-run # also expose run:<target> tools
./zuke mcp --allow-run='build,test:*' --protect='deploy,release'
./zuke mcp --allow-run --confirm-destructive # destructive runs return a plan first
./zuke mcp --http 127.0.0.1:8787 # streamable-HTTP transport
./zuke mcp --registry --max-concurrent-runs 8 # serve every registered build
./zuke register # record this build in the registry | Flag | Behaviour |
|---|---|
--allow-run[=<globs>] | let agents execute targets, not just inspect them; an optional comma-separated glob allow-list exposes only matching targets as run tools (others stay invisible) |
--protect <globs> | require an operator token (ZUKE_OPERATOR_TOKEN) as a tool-call argument to run the matching targets |
--confirm-destructive | make a destructive run tool return its plan until called with confirm:true (a .readOnly() target is exempt) |
--registry | serve the build registry instead of this one build — expose every registered pipeline's targets, re-read live so a newly-registered build appears with no restart |
--max-concurrent-runs <n> | with --registry, cap concurrent run-tool spawns (default 4); a call past the cap gets an immediate busy error, read tools are never counted |
--http <host:port> | serve the streamable-HTTP transport instead of stdio. A bare <port> binds 127.0.0.1; a non-loopback host requires a bearer token (ZUKE_MCP_TOKEN) |
--allowed-origin <origin> | with --http, permit an extra browser Origin (repeatable). By default a loopback bind accepts only loopback origins — the drive-by / DNS-rebinding guard; a request with no Origin (a CLI client) is always allowed |
There is no --token flag. The HTTP bearer
token is read only from the ZUKE_MCP_TOKEN environment
variable, and the operator token from ZUKE_OPERATOR_TOKEN —
never from an argument that would land in a process listing or shell
history.
./zuke register records this build in the build registry (its
targets, parameters, and launch location, excluding secrets) so a
registry-backed MCP server can discover it. It is idempotent, accepts
--actor <name> and --json, and writes to
.zuke/builds unless ZUKE_REGISTRY_URL/_DIR
or the build's registry() configures a store. See
the registry.
Environment variables
These configure state, the registry, the remote cache, MCP authorization, and
tool resolution without a build-file change — the usual way to point the same
build at production infrastructure from CI. For each pluggable backend, the
_URL variable (with an optional _TOKEN) selects the
HTTP backend; the _DIR variable selects the filesystem backend.
# Never a flag — the loopback-only guard reads it from the environment:
ZUKE_MCP_TOKEN=… ./zuke mcp --http 0.0.0.0:8787
# Configure a remote cache with no build-file change:
ZUKE_REMOTE_CACHE_URL=https://cache.example.com ZUKE_REMOTE_CACHE_TOKEN=… ./zuke ci
ZUKE_REMOTE_CACHE_DIR=/mnt/zuke-cache ./zuke ci
# Configure durable state / the registry the same way:
ZUKE_STATE_URL=https://state.example.com ZUKE_STATE_TOKEN=… ./zuke ci --state
ZUKE_STATE_DIR=.zuke/runs ./zuke runs list | Variable | Effect |
|---|---|
ZUKE_MCP_TOKEN | bearer token required by a non-loopback mcp --http bind |
ZUKE_OPERATOR_TOKEN | operator token a protected (--protect) MCP run tool must carry |
ZUKE_STATE_URL / ZUKE_STATE_TOKEN | select an HttpStateStore for durable run state (see store precedence) |
ZUKE_STATE_DIR | select a filesystem state store at this directory |
ZUKE_REGISTRY_URL / ZUKE_REGISTRY_TOKEN | select an HttpBuildRegistry for register and registry-backed MCP |
ZUKE_REGISTRY_DIR | select a filesystem build registry at this directory |
ZUKE_REMOTE_CACHE_URL / ZUKE_REMOTE_CACHE_TOKEN | select an HttpCacheStore for the remote cache |
ZUKE_REMOTE_CACHE_DIR | select a FileSystemCacheStore at this directory |
ZUKE_ACTOR | default actor for a run when --actor is absent (before the CI actor and "anonymous") |
ZUKE_TOOL_RESOLUTION | ambient override for how tool wrappers locate their binary: path (bare name on $PATH) or node_modules (npx-style node_modules/.bin walk). See installing tools |
Store precedence is always: an explicit execute(...) option,
then the build's typed override (stateStore(),
registry(), remoteCache()), then these
environment variables. An unreadable ZUKE_TOOL_RESOLUTION (no
--allow-env) or an unrecognised value is treated as unset.