Builds you can refactor, not just run.
Zuke is a code-first, strongly-typed build system. Define targets as TypeScript class fields, wire dependencies with a fluent API, and let Zuke resolve and run them in topological order — with zero runtime dependencies.
$deno install -A -g -n zuke jsr:@zuke/cli
$zuke setup
$./zuke
import { Build, cicd, run, target } from "jsr:@zuke/core";
import { DenoTasks } from "jsr:@zuke/deno";
class MyBuild extends Build {
// declare a pipeline → generates .github/workflows
ci = cicd({ provider: "github" });
clean = target().executes(() => Deno.remove("dist", { recursive: true }));
restore = target().executes(() => DenoTasks.cache((s) => s.paths("mod.ts")));
compile = target()
.description("Type-check & bundle")
.dependsOn(this.clean, this.restore)
.executes(async () => {
await DenoTasks.check((s) => s.paths("mod.ts"));
});
}
await run(MyBuild); $ ./zuke build build ▸ astro build — 4 pages in 2.2s ✔ build succeeded in 2.2s Build Summary ──────────────────────────── Target Status Duration ──────────────────────────── clean Succeeded 0.1s install Succeeded 12.4s check Succeeded 3.1s build Succeeded 2.2s ──────────────────────────── Total 17.8s ✔ Build succeeded — 4/4 in 17.8s
Everything a build needs — typed end to end.
5the model
- Typed, refactor-safe dependencies Targets reference each other with this.target — not strings. Rename one and every reference updates.
- Just TypeScript Ordinary async functions with full types, editor support, and debugging. No YAML, no bespoke DSL.
- Automatic topological order Declare what each target dependsOn; Zuke resolves the graph and runs everything in order, exactly once.
- Zero runtime dependencies Ships on Deno with nothing else to install. The launcher installs Deno for you on first run.
- Extensible by design Extend ToolSettings to build your own typed wrapper for any tool — no fork required.
6run & orchestrate
- Long-lived service targets Declare a dev server or database with service(); Zuke starts it, waits until ready, and stops it in a finally.
- Ergonomic, safe shell The $ tagged template runs processes with sane defaults and escapes interpolations to prevent injection.
- Incremental, remote & parallel Fingerprint .inputs()/.outputs() to skip unchanged work, share a remote cache, and run targets concurrently.
- Hermetic, pinned toolchains Fetch the CLIs a build drives, pinned by SHA-256, verified and cached — a bare CI runner is reproducible.
- Drops into Node monorepos Resolves node_modules/.bin npx-style, provisions npm-registry tools with toolchain().npm(), and runs npm workspaces — no npx, no global installs.
- Secrets, sourced & redacted Pull a secret from 1Password, Vault, or gcloud with .from(execSecret(…)); Zuke redacts it from every log.
3ship & automate
- Code-first CI/CD Declare pipelines with cicd() and generate GitHub, GitLab, or Azure YAML — verified up to date on every run.
- A typed tool ecosystem Strongly-typed wrappers for 54+ tools — Deno, npm, Docker, Kubernetes, Vite and more, all on JSR.
- Announce to chat Post build status to Slack, Teams, and Discord from a target — typed messages with levels, fields, and links.
3ai in the loop
- AI in your pipeline Typed wrappers for Claude Code, Codex, and Gemini CLI run headless in CI, injection-free.
- AI code-review gate A model reads the diff, returns a structured risk score, and breaks the build when it crosses your threshold.
- Self-healing builds Attach a fixer with .recoverWith(); @zuke/ai diagnoses a failure and can apply, commit, and re-run to verify.
Declare dependencies.
Zuke figures out the order.
Each target lists what it dependsOn. Zuke builds the
dependency graph, runs independent targets as far as it can, and
executes everything exactly once in the correct order.
- References are real values —
this.build, never a string. - Per-target status, timing, and a final build summary.
- Rename a target and every dependant updates automatically.
- Incremental caching skips a target whose
.inputs()are unchanged — reportedcached, with dependents still running. zuke graph --output=htmlrenders an interactive graph you can pan, zoom, and click to trace dependencies.
↓ The graph that builds this website. See Zuke's own build graph rendered to HTML.
A build log you can actually scan.
Every target is framed with a ruled banner and closes with a plain
pass/fail line and its duration. The run ends with an aligned
Build Summary — status and timing per target, a total,
and a one-line verdict stamped with the time. It's all powered by
@zuke/console, and the same
primitives are yours to call in any target.
- Blocks are easy to scan; a failure prints its error inline.
- The summary table totals the wall-clock time across targets.
-
@zuke/consolegives you tag-based[bold]/[green]markup plus rules, boxes, and tables — a levelled logger for your own output. - GitHub Actions gets matching groups, annotations, and a job-summary table.
$ ./zuke build build ▸ astro build — 4 pages in 2.2s ✔ build succeeded in 2.2s Build Summary ──────────────────────────── Target Status Duration ──────────────────────────── clean Succeeded 0.1s install Succeeded 12.4s check Succeeded 3.1s build Succeeded 2.2s ──────────────────────────── Total 17.8s ✔ Build succeeded — 4/4 targets in 17.8s · 2026-06-23 07:19
Compose the same output in your own targets
Import @zuke/console and reach for rules, colored markup,
boxes, and tables directly — the exact primitives the runner uses for
its banners and summary. Read the Console output guide →
import { ConsoleTasks as Log } from "jsr:@zuke/console";
Log.rule("Release");
Log.info("pushing [bold]core@1.2.0[/]"); // markup: [tag]…[/]
Log.success("[green]✔[/] published 4 packages");
Log.warn("coverage [yellow]94.2%[/] — below gate");
Log.box(["core 1.2.0 ✔", "cli 1.2.0 ✔"], {
title: "Artifacts",
border: ["green"],
});
Log.table(
[{ header: "Package" }, { header: "Size", align: "right" }],
[["@zuke/core", "18 kB"], ["@zuke/cli", "12 kB"]],
); ────────────── Release ────────────── ℹ pushing core@1.2.0 ✔ published 4 packages ⚠ coverage 94.2% — below gate ┌ Artifacts ───────────────────┐ │ core 1.2.0 ✔ │ │ cli 1.2.0 ✔ │ └──────────────────────────────┘ Package Size ───────────────── @zuke/core 18 kB @zuke/cli 12 kB
// The $ tagged template runs processes safely — no shell injection.
const sha = await $`git rev-parse HEAD`.text(); // trimmed stdout
await $`deno test -A`; // throws on non-zero
const code = await $`flaky-cmd`.noThrow().code(); // never throws Run processes without the footguns.
The $ tagged template from @zuke/core/shell
escapes interpolations automatically, so untrusted values can't break
out of a command. Chain .text(), .code(), or
.noThrow() for exactly the behaviour you want.
Build it once. Reuse it everywhere.
Zuke fingerprints a target's .inputs() and skips it when
nothing changed. A remote cache takes that across
machines — a fresh checkout or a CI job restores a
target's built outputs from a shared store instead of rebuilding them —
and --affected narrows a run to just what a change can reach.
- Incremental, locally —
.inputs()/.outputs()are fingerprinted; an unchanged target is skipped and showncached. - Remote cache — on a local miss, restore a target's outputs from a shared store instead of rebuilding; upload after a green run so the next machine reuses them.
- Two backends — a mounted directory or any HTTP object store (S3/GCS/R2), via a typed
remoteCache()override orZUKE_REMOTE_CACHE_*in CI. --affectedruns only the targets a git diff can reach, andfanOutemits one parallel CI job per target — the monorepo combo.
import { Build, HttpCacheStore, cicd, target } from "jsr:@zuke/core";
import { DenoTasks } from "jsr:@zuke/deno";
class CI extends Build {
// Restore built outputs on a local miss; upload
// after a green run so the next job reuses them.
override remoteCache() {
return new HttpCacheStore({
url: this.cacheUrl.value,
token: this.token.value,
});
}
build = target()
.inputs("src", "deno.json") // fingerprint → cache key
.outputs("dist") // archived to the store
.executes(() => DenoTasks.run((s) => s.script("build.ts")));
// One CI job per target, wired by dependsOn —
// independent jobs run in parallel.
ci = cicd({ provider: "github", fanOut: true });
} dist/ — uploaded after a green run cache.example.com build @ a3f9c21 dist/ — cached in 0.0s, no rebuild Let a model gate the build.
@zuke/ai attaches an AI reviewer to a target as a
validation. It reads the diff, asks the model for a
structured risk assessment, and breaks the build when
the score crosses your threshold — a real verdict your pipeline can
branch on, not a blob of prose.
- Security, secrets, correctness, licensing, or code quality — built-in rubrics, tunable with
.criteria(). - Claude, OpenAI, or Gemini, with the JSON shape enforced server-side.
.comment()posts the verdict to the PR/MR — GitHub, GitLab, Azure & Bitbucket.aiReviewWorkflow()generates the CI workflow from your reviewers, for every host.
- HIGH Hard-coded API token in
src/config.ts:42 - MED Unvalidated redirect in
routes/login.ts - LOW Stack trace leaked in error response
via @zuke/ai · claude-opus-4-8 · tokens: 1234 in · 567 out · 1801 total
When a build breaks, Zuke fixes it.
Attach a fixer to any target with .recoverWith(aiFixer(…)).
When the target fails, @zuke/ai diagnoses it from the
exact command, its stderr, and the diff,
then proposes a fix — or applies one. The real build command is the
verifier: a fix only counts when the command actually goes
green.
- Diagnose-only by default — posts a committable, Copilot-style suggestion to the PR, anchored to the exact
file:line. .autoApply()heals it — writes the fix, commits, and re-runs the real command; a bad edit fails the build instead of landing.- Safe by default — no files written until you opt in; edits gated behind a path allowlist, a file cap, and local-only defaults.
agentFixerhands open-ended failures to a coding agent you inject — Claude Code, Codex, or Gemini CLI — that edits files itself.
- ✕ test failed
TypeError: name is undefined— src/format.ts:18 - ⌕ aiFixer diagnosing command + stderr + diff → model
- + fix: guard the null user in
formatName()applied · committeda3f9c21 - ↻ re-running
deno testto verify the real command is the gate - ✓ healed — 12/12 passing in 1.9s a fix only counts when the build goes green
src/format.ts · line 18 - return user.name.toUpperCase(); + return (user?.name ?? "anon").toUpperCase();
import { Build, parameter, run, target } from "jsr:@zuke/core";
import { DenoTasks } from "jsr:@zuke/deno";
import { aiFixer } from "jsr:@zuke/ai";
class CI extends Build {
key = parameter("OpenAI API key").secret().required();
test = target()
.executes(() => DenoTasks.test((s) => s.allowAll()))
// On failure: diagnose from the error + diff, post a committable
// suggestion — or apply, commit, and re-run to verify it goes green.
.recoverWith(
aiFixer((f) =>
f.provider("openai").apiKey(this.key)
.autoApply() // write the fix to the working tree
.allowPaths("src/**") // blast-radius allowlist
.maxEdits(5) // …and a file cap
.commitFixes() // stage, commit & push to the PR branch
),
)
.recoverAttempts(2); // up to two fix-then-rerun cycles
}
await run(CI);
One primitive powers it: a Remediation runs only after a
target body fails. Attach a fixer per target, or
override recoverWith() on the build to heal
every target at once.
Hand your build to an agent.
zuke mcp runs a
Model Context Protocol
server over your build, so an AI client discovers typed
tools — list the targets, inspect the graph, run one with the
right parameters — and operates the pipeline through real calls instead
of guessing shell commands. Read-only by default;
--allow-run opts into execution.
- Read tools, always on —
list_targets,describe_build, andgraphexpose the whole build surface: the live counterpart ofllms.txt. run:<target>with--allow-run— one run tool per target, its input schema derived from your declared parameters (required,enum, number) plus adryRunflag.- Safe by default — execution is off until you opt in; run tools carry MCP's
destructiveHintso clients prompt, and.secret()values stay redacted in what the agent sees. - Dependency-free — newline-delimited JSON-RPC 2.0 over stdio, implemented by hand; no MCP SDK, no network endpoint.
# read-only: an agent inspects the build, never runs it
zuke mcp
# also expose run:<target> tools that execute targets
zuke mcp --allow-run
# register with an MCP client (Claude Code shown)
claude mcp add zuke -- deno run -A zuke.ts mcp - list_targets targets, descriptions & deps read
- describe_build the full build surface read
- graph target → deps read
- run:test executes the target ·
--allow-run⚡ run
{ "name": "run:test", "arguments": { "environment": "dev" } } A cross-run layer for real deployments.
Durable state, external-event waits, cross-run locks, batch fan-out, cancellation, and governed agent access — the orchestration Zuke layers on top of the typed DAG. All backed by a pluggable state store, with zero overhead until you reach for it.
Pipelines that outlive the process.
A target can suspend on an external event — deploy,
then wait days for manual approval. The run parks in the state store and
the process exits 0; a later zuke resume
continues it in a fresh process, exactly once. State
persists across the gap, so promoteToProd reads what
deployToSit left behind.
.waitsFor(externalSignal(…))suspends;zuke resume <id> --signalcontinues it.- A racing resumer gets a clean
AlreadyResumedError— safe to drive from a retrying cron. zuke runs list/show <id>reconstruct a run's full status from the store alone.
awaitTesting = target()
.dependsOn(this.deployToSit)
.waitsFor((s) =>
s.on(externalSignal("testing-approved"))
.timeout("72h")
.onTimeout(() => this.rollback));
promoteToProd = target()
.dependsOn(this.awaitTesting)
.executes((ctx) => {
// resumes in a fresh process, days later —
// the approval payload survived the wait
promote(ctx.signals.get("testing-approved")?.data);
}); $ ./zuke promoteToProd --repo api ✔ deployToSit succeeded ⏸ awaitTesting waiting — signal: testing-approved run 6f1c9a suspended · exit 0 $ zuke resume 6f1c9a --signal testing-approved ✔ promoteToProd succeeded — shipped to prod
promoteToProd = target()
// one deploy of a repo at a time — across runs, across machines
.lock((s) =>
s.lockKey("deploy", this.repo.value).withTtl("4h")
.onConflict((h) => `held by ${h.actor}, run ${h.runId}`))
.executes((ctx) => applyToProd(this.repo.value, ctx))
.onCancel(() => this.rollback); // undone in reverse order on cancel $ ./zuke promoteToProd --repo api ✘ promoteToProd lock conflict held by alice, run 6f1c9a since 09:14 wait, or: zuke cancel 6f1c9a $ zuke cancel 6f1c9a ↩ rollback compensated — sit-7 torn down ✔ run cancelled · lock released
Serialize deploys. Undo cleanly.
A .lock() claims a resource across runs, processes, and
machines — one deploy of a repo at a time. The loser fails fast with the
holder's identity and guidance, not a corrupt double-deploy. And
.onCancel() registers the inverse of a target, so
zuke cancel (or Ctrl-C, or a timed-out wait) unwinds
succeeded work in reverse order — reading the exact
state each step recorded.
- TTL + auto-renew: a live holder keeps its lock; a
kill -9frees it after the TTL. - Locks release in a
finally— success, failure, and cancellation all let go. - Compensations run maximally: one that throws is recorded, the rest still run.
One list. N deploys. Honest per-item status.
.forEach() runs the same ordered pipeline over a runtime
list — items concurrent, stages sequential per item, with a bounded
concurrency. One repo failing doesn't stop the others; the
batch fails at the end if any item did. Every item is first-class:
deployBatch[api].deploy gets its own summary row and run-record
entry, so zuke runs show and MCP report a verdict per repo.
- Per-item isolation with
continueOnItemFailure— no more all-or-nothing batches. - The list is an
.array()parameter, validated element-by-element before anything runs. - The end of JSON-array-in-a-text-field: typed, checked, reportable inputs.
deployBatch = target().forEach(
() => this.repos.value, // a validated runtime list
(repo) => ({ // an ordered pipeline per repo
checks: target().executes(() => check(repo)),
deploy: target().executes((c) => applyToSit(repo, c)),
}),
{ concurrency: 3, continueOnItemFailure: true },
); $ ./zuke deployBatch --repos api,web,worker ✔ deployBatch[api] succeeded ✘ deployBatch[web] failed — checks ✔ deployBatch[worker] succeeded ──────────────────────────── ✘ deployBatch — 1/3 items failed
# governed, auditable agent access over authenticated HTTP
zuke mcp --http 0.0.0.0:7777 \
--allow-run --protect promoteToProd --confirm-destructive
# one server over the whole catalog —
# pipelines registered later appear with no restart
zuke mcp --registry --allow-run tool actor outcome ─────────────────────────────────── run:deployBatch alice ok signal_run bob ok run:promoteToProd carol denied reason: operator token required
Hand CD to an agent — with a paper trail.
The MCP server grows a streamable-HTTP transport and three authorization tiers: an allow-list of runnable targets, an operator token for protected ones, and a confirm step for destructive calls. Every mutating call is attributed in an audit log. A build registry makes it fleet-wide — one server over a catalog of pipelines that picks up newly-registered builds with no restart.
--allow-run=deploy,checks*,--protect promoteToProd,--confirm-destructive.- Loopback by default; a non-loopback bind requires a bearer token.
zuke register+zuke mcp --registry— new pipelines become callable tools live.
Typed wrappers for the tools you already use.
Every wrapper is a fluent, strongly-typed API over a real CLI — published to JSR, tree-shakeable, and refactor-safe. And if one's missing, you can write your own.
Runtimes & package managers
7Install, run, and publish across every major JS toolchain.
@zuke/deno run, test, check, fmt, lint, coverage Node @zuke/node run scripts, npx, version pinning npm @zuke/npm install, ci, run scripts, publish Bun @zuke/bun install, add, run, x, bun test pnpm @zuke/pnpm frozen installs, dlx, --filter Yarn @zuke/yarn Classic & Berry: install, add, dlx npx @zuke/npx download & run a package binary in one step Bundlers & monorepo
5Bundle apps and orchestrate monorepos from a typed pipeline.
TypeScript runners & compilers
3Execute, type-check, and compile TypeScript with or without a build step.
Frameworks & code generation
4Scaffold app frameworks and generate typed clients from a schema.
AI coding, review & self-healing
5Fold the major AI coding CLIs into a build, gate it on a model-assessed review, or let a fixer heal failures.
@zuke/claude headless run, model & tool limits, MCP OpenAI Codex @zuke/codex codex exec headless, MCP config Gemini CLI @zuke/gemini headless prompt, MCP & extensions AI review @zuke/ai code-review gate with a structured risk score Self-healing @zuke/ai diagnose, suggest & auto-fix failed builds, then re-run to verify Lint, format & quality
7Keep the tree clean with linters, formatters, and dead-code checks.
@zuke/oxlint ultra-fast Rust linter ESLint @zuke/eslint configs, --fix, caching Biome @zuke/biome check, format, lint, ci dprint @zuke/dprint fmt, check cspell @zuke/cspell spell-check your sources Knip @zuke/knip find unused files & exports dpdm @zuke/dpdm detect circular deps & dependency trees Test, coverage & browsers
5Run unit and end-to-end suites, then upload coverage to your dashboard.
Containers & orchestration
5Build images and ship to clusters from a typed pipeline.
Cloud & infrastructure
3Provision and deploy with infra-as-code, typed end to end.
Version control, registry & CI
5Script Git, GitHub, and publishing straight from your build.
Supply-chain security
1Scan workflows, secrets, and dependencies as part of the gate.
Engine & plugins
5The first-party packages that aren't CLI wrappers — the build engine, the global CLI, the process layer, the console, and the OpenTelemetry plugin.
@zuke/core the Build base class, target() graph, $ shell, and cicd() @zuke/cli the global zuke command: setup, run targets, --list, graph, generate-ci @zuke/cmd the typed process layer the wrappers are built on @zuke/console markup, rules, boxes & tables — the levelled logger behind Zuke's output @zuke/otel OpenTelemetry export plugin — run & target spans over OTLP, joined across resume Ship your first typed build in minutes.
Install the CLI, scaffold a zuke.ts, and run your first target.
$deno install -A -g -n zuke jsr:@zuke/cli
$zuke setup
$./zuke --list