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 with deno install) is the pre-build tool: it scaffolds a project (setup, import), documents packages (doc), and reports its own --version. You run it as zuke before a build file exists.
  • The build launcher — the ./zuke script setup writes, which is just deno 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 own doc).

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 setupEffect
--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, -foverwrite existing files
--yes, -yaccept 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 importEffect
--from <source>package.json or makefile; auto-detected when omitted
--dir, --name, --force, --yesas 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 (corejsr:@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-actionEffect
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
FlagBehaviour
--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-cacheignore the incremental cache and re-run every target
--no-remote-cacheuse the local cache only; skip the remote cache store (neither restore nor upload). --no-cache disables both
--dry-runresolve the plan and print every target that would run (honouring --skip and each onlyWhen) without executing any body or touching the cache
--statepersist 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, -llist all targets with descriptions and dependencies
--jsonemit 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
FlagBehaviour
--output=htmlrender the interactive HTML page instead of terminal text (--output text is the default)
--no-openwith --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
FlagBehaviour
--checkverify 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 runsBehaviour
listone 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 --countsprint 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-runreport what would be deleted without deleting; at least one --keep/--keep-last rule is required
--jsonon 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
FlagBehaviour
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-graphcontinue 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
FlagBehaviour
--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-destructivemake a destructive run tool return its plan until called with confirm:true (a .readOnly() target is exempt)
--registryserve 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
VariableEffect
ZUKE_MCP_TOKENbearer token required by a non-loopback mcp --http bind
ZUKE_OPERATOR_TOKENoperator token a protected (--protect) MCP run tool must carry
ZUKE_STATE_URL / ZUKE_STATE_TOKENselect an HttpStateStore for durable run state (see store precedence)
ZUKE_STATE_DIRselect a filesystem state store at this directory
ZUKE_REGISTRY_URL / ZUKE_REGISTRY_TOKENselect an HttpBuildRegistry for register and registry-backed MCP
ZUKE_REGISTRY_DIRselect a filesystem build registry at this directory
ZUKE_REMOTE_CACHE_URL / ZUKE_REMOTE_CACHE_TOKENselect an HttpCacheStore for the remote cache
ZUKE_REMOTE_CACHE_DIRselect a FileSystemCacheStore at this directory
ZUKE_ACTORdefault actor for a run when --actor is absent (before the CI actor and "anonymous")
ZUKE_TOOL_RESOLUTIONambient 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.