zuke-setup
Set up Zuke — a code-first, strongly-typed build automation system for Deno/TypeScript — in a project. Use when the user wants to add Zuke to a repo, scaffold a zuke.ts build file, install the Zuke CLI, or bootstrap the ./zuke launcher. After scaffolding, switch to the zuke-write
Install
npx skills add https://github.com/zuke-build/zuke/tree/master/plugins/zuke/skills/zuke-setup
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install zuke-build-zuke@llmmart
git clone https://github.com/zuke-build/zuke.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole zuke-build/zuke collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Set up Zuke in a project
Zuke defines builds as a TypeScript class run on Deno. Each target is a
class field; targets reference each other by this.<field> (never strings).
Packages are imported from JSR (jsr:@zuke/...), not npm.
The fast path: zuke setup
The @zuke/cli tool scaffolds everything. Install it once, then run setup in
the target project:
deno install -A -g -n zuke jsr:@zuke/cli # once, globally
zuke setup # in the project root
./zuke # run the build
No global install? The same wizard runs directly:
deno run -A jsr:@zuke/cli setup
setup flags: --dir <path>, --name <ClassName>, --force (overwrite
existing files), --yes (non-interactive), --bootstrap-deno /
--no-bootstrap-deno (which launchers to write — see below; the wizard asks
when interactive, and --yes takes the default, bootstrap), --mcp (also write
.mcp.json registering the build's MCP server, so this agent — and any other
stdio MCP client — can list and run the targets through typed calls;
--allow-run registers it with execution enabled and implies --mcp),
--launcher-name <name> (write the launcher under a different name when a
zuke/ directory already occupies it — a directory collision now fails with an
actionable error instead of silently skipping the launcher). With --mcp, an
MCP client lists and runs the targets through typed calls; without it, the build
is still discoverable through ./zuke --list --json.
Running as an agent, always pass --yes: it skips every interactive question,
including the closing "star the Zuke repository?" prompt — that question is for
a human at a terminal, and an agent must never answer it (or star anything) on
the user's behalf.
To read a @zuke/* package's API without a Node repo's @types/node noise, run
zuke doc <package> (e.g. zuke doc core) — it runs deno doc in an isolated
directory.
Migrating an existing project: zuke import
If the project already has package.json scripts or a Makefile, prefer
zuke import over setup — it reads them and generates a zuke.ts with a
target per task, a working starting point instead of a blank build:
zuke import # auto-detects package.json, then a Makefile
zuke import --from makefile # or pin the source (package.json | makefile)
Each script/target becomes a target(); a command maps to CmdTasks.exec(...)
— a placeholder, not the destination: before accepting it, check the package
catalogue (llms.txt's ## Packages list, or the table in
zuke-write-build's cheatsheet)
for a @zuke/<tool> wrapper matching that command and replace the placeholder
with it — leaving CmdTasks.exec in place for a tool that has a typed wrapper
is a bug, not a shortcut. An && chain becomes sequential steps, a
run/prerequisite delegation becomes .dependsOn(...), and anything too
shell-specific to translate (pipes, redirects, env assignments) is preserved
behind a // TODO so the file still compiles. It scaffolds the launchers and
deno.json exactly like setup, and takes the same --dir, --name,
--force, --yes, --bootstrap-deno / --no-bootstrap-deno, --mcp and
--allow-run flags. Afterwards, use the zuke-write-build skill to finish
replacing any remaining generated CmdTasks.exec calls with typed *Tasks
wrappers.
What zuke setup writes
zuke.ts— a starter build class with a sample target and adefault../zuke+./zuke.ps1— launchers that locate the project and runzuke.ts. By default (--bootstrap-deno) they use the Deno onPATHand, when there is none, download the pinned release Zuke itself runs on, verify it against a per-platform SHA-256, and install it under~/.deno— never an install script, never an unverified binary — so a clone needs nothing installed first. With--no-bootstrap-denothey require Deno onPATHand exit with the install docs URL when it is missing, for a project that must never download a tool from its build entry point. Both pass--frozenonce adeno.lockexists, so the first run writes the lockfile and every run after verifies it.deno.json— merged to add animportsentry for@zuke/core(zuke importadds@zuke/cmdtoo when it generates one), azuketask, andfmt/lint/testif absent. The scaffolded build imports by bare specifier (from "@zuke/core"), not an inlinejsr:@zuke/core@^1— Deno's default lint set rejects an inline specifier underno-import-prefix, so an inlined one would fail the project's owndeno task linton its only source file. The caret major is pinned in the import map instead. Merging is additive per key: a task or import the file already declares is kept exactly as it is (so a deliberate version pin survives), only what is missing is added. A file that has thezuketask but not the imports is still completed — skipping it would leave thezuke.tswritten in the same run with nothing to resolve. Two shapes cannot be completed automatically, and setup reports them as steps for you and exits 1 rather than printingNext: ./zukeover a build that cannot start: adeno.jsonthat is JSONC (Deno accepts//comments and trailing commas that a JSON parser does not, and rewriting the file would discard them), and one that delegates viaimportMapto a separate file (Deno ignores that field the momentimportsappears beside it, so the entry belongs in the other file — setup reads that file and stays quiet once it declares the entry, so a correctly configured project still exits 0). Both are left byte-for-byte untouched. A mapping that already points somewhere other than the JSR package is kept — that is how a pin or a local checkout survives — with a note saying so.--mcpthat cannot register into an unparseable.mcp.jsonis reported the same way, as a note.zuke.json—{ "name": "..." }, which marks the repo root..gitignore— created or appended so.zuke/is ignored (the cache and durable run state live there); untouched if it already covers it..mcp.json(with--mcp) —mcpServers.zukelaunchingdeno run -A zuke.ts mcp(plus--allow-runwhen asked). Merged around any other servers already in the file; an existingzukeentry is kept unless--forceis set.
Running the build
./zuke # run the default target (Windows: .\zuke.ps1)
./zuke <target> # run a specific target
./zuke --list # list every target
./zuke --list --json # the whole build surface (commands, flags, targets) as JSON
./zuke <target> --dry-run # print the plan without executing
./zuke <target> --no-banner # no opening banner (also ZUKE_NO_BANNER)
Every run opens with a banner: the Zuke wordmark, then
zuke <version> · deno <version> · <platform> and run <id> · <cwd>. On CI the
wordmark is dropped — the identifying lines are what a runner log wants, six
lines of ASCII are not — and the detected host is appended to the first line.
--no-banner or ZUKE_NO_BANNER turns it off entirely.
With the global CLI installed, the bare zuke works from anywhere inside the
project: zuke <target>, zuke --list, zuke graph, zuke mcp, bare zuke
for the default target — every command that is not the CLI's own (setup,
import, doc) is forwarded to the nearest zuke.ts. --help and --version
show both surfaces, the CLI's and then the build's, each labelled;
zuke -- --help is the build's own usage alone, and zuke -- <target> reaches
a target that shares one of those names. It walks up to the zuke.json that
marks the repository root and runs deno run -A zuke.ts <args> from there, with
--frozen once a deno.lock exists — the launcher's exact behaviour, minus the
Deno bootstrap. Outside a project, zuke <target> reports the unknown command
and the missing zuke.json, and a bare zuke prints the usage. The forwarding
refuses a project whose root directory is owned by another user or is
world-writable, and one where an ancestor deno.json, deno.jsonc or
package.json Deno would read is owned by another user (the safe.directory
rule git applies, since discovery runs code the caller never named); the error
names what was refused and the fix — run that project's own ./zuke there, or
fix the ownership. On Windows the gate is inert.
The CLI is self-describing: ./zuke --help prints the usage grammar plus the
build's live targets and parameters, so an agent discovers the real command
surface instead of guessing. For an AI client to operate the build through typed
calls, zuke mcp runs a Model Context Protocol server over it (register with
claude mcp add zuke -- deno run -A zuke.ts mcp; add --allow-run to let the
agent execute targets, not just inspect them).
If Deno is already installed you can also use deno task zuke <target> or
deno run -A zuke.ts <target>. The -A flag grants permissions, since targets
typically run processes and touch files. These are not quite equivalent to the
launcher: the scaffolded zuke task deliberately omits --frozen, so it may
heal a stale lockfile where ./zuke would fail on it.
Manual setup (no CLI)
Declare the packages the build imports in deno.json — bare specifier to jsr:
dependency, caret major — so the build can import them by bare specifier. An
inline jsr:@zuke/core@^1 in the import statement fails deno lint under its
default no-import-prefix rule:
{
"imports": {
"@zuke/core": "jsr:@zuke/core@^1",
"@zuke/deno": "jsr:@zuke/deno@^1"
}
}
Then create zuke.ts in the project root, extend Build, declare targets with
target(), and call await run(MyBuild) at the bottom:
import { Build, run, target } from "@zuke/core";
import { DenoTasks } from "@zuke/deno";
class CI extends Build {
lint = target().executes(() => DenoTasks.lint());
test = target().dependsOn(this.lint)
.executes(() => DenoTasks.test((s) => s.allowAll()));
default = target().dependsOn(this.test).executes(() => {});
}
await run(CI);
Run with deno run -A zuke.ts test. (For the ./zuke launcher experience,
prefer zuke setup, which drops the launcher scripts in for you.)
Finding the exact API — never guess
Every external tool has a typed *Tasks wrapper; do not fall back to
Deno.Command or hand-rolled shell. First confirm a wrapper exists at all —
llms.txt's ## Packages catalogue or the table in
zuke-write-build's cheatsheet
is the only way to answer that; a per-package deno doc needs a name to target,
so it cannot reveal that one exists. Once you know the package name, get its
exact signatures:
- A single package on the command line:
deno doc jsr:@zuke/<package>. Prefer this in a consumer repo — it resolves the version the project actually has installed, so it cannot describe an API that version lacks. - The whole typed surface of every package is in
llms-full.txt(indexed byllms.txt) — at the repo root in the Zuke repo itself, or from a consumer repo https://raw.githubusercontent.com/zuke-build/zuke/master/llms-full.txt (index: https://raw.githubusercontent.com/zuke-build/zuke/master/llms.txt). Both trackmaster, so they may document symbols that are merged but not yet released; use them to find what exists, then confirm the signature withdeno doc.
Once the project is scaffolded, use the zuke-write-build skill to add and edit targets.
Files (zuke)
-
SKILL.md 11.9 KB
--- name: zuke-setup description: Set up Zuke — a code-first, strongly-typed build automation system for Deno/TypeScript — in a project. Use when the user wants to add Zuke to a repo, scaffold a zuke.ts build file, install the Zuke CLI, or bootstrap the ./zuke launcher. After scaffolding, switch to the zuke-write-build skill to author targets. --- # Set up Zuke in a project Zuke defines builds as a TypeScript class run on **Deno**. Each target is a class field; targets reference each other by `this.<field>` (never strings). Packages are imported from **JSR** (`jsr:@zuke/...`), not npm. ## The fast path: `zuke setup` The `@zuke/cli` tool scaffolds everything. Install it once, then run `setup` in the target project: ```sh deno install -A -g -n zuke jsr:@zuke/cli # once, globally zuke setup # in the project root ./zuke # run the build ``` No global install? The same wizard runs directly: ```sh deno run -A jsr:@zuke/cli setup ``` `setup` flags: `--dir <path>`, `--name <ClassName>`, `--force` (overwrite existing files), `--yes` (non-interactive), `--bootstrap-deno` / `--no-bootstrap-deno` (which launchers to write — see below; the wizard asks when interactive, and `--yes` takes the default, bootstrap), `--mcp` (also write `.mcp.json` registering the build's MCP server, so this agent — and any other stdio MCP client — can list and run the targets through typed calls; `--allow-run` registers it with execution enabled and implies `--mcp`), `--launcher-name <name>` (write the launcher under a different name when a `zuke/` directory already occupies it — a directory collision now fails with an actionable error instead of silently skipping the launcher). With `--mcp`, an MCP client lists and runs the targets through typed calls; without it, the build is still discoverable through `./zuke --list --json`. Running as an agent, always pass `--yes`: it skips every interactive question, including the closing "star the Zuke repository?" prompt — that question is for a human at a terminal, and an agent must never answer it (or star anything) on the user's behalf. To read a `@zuke/*` package's API without a Node repo's `@types/node` noise, run `zuke doc <package>` (e.g. `zuke doc core`) — it runs `deno doc` in an isolated directory. ### Migrating an existing project: `zuke import` If the project already has `package.json` scripts or a `Makefile`, prefer `zuke import` over `setup` — it reads them and generates a `zuke.ts` with a target per task, a working starting point instead of a blank build: ```sh zuke import # auto-detects package.json, then a Makefile zuke import --from makefile # or pin the source (package.json | makefile) ``` Each script/target becomes a `target()`; a command maps to `CmdTasks.exec(...)` — a **placeholder**, not the destination: before accepting it, check the package catalogue (`llms.txt`'s `## Packages` list, or the table in [`zuke-write-build`'s cheatsheet](../zuke-write-build/references/cheatsheet.md)) for a `@zuke/<tool>` wrapper matching that command and replace the placeholder with it — leaving `CmdTasks.exec` in place for a tool that has a typed wrapper is a bug, not a shortcut. An `&&` chain becomes sequential steps, a `run`/prerequisite delegation becomes `.dependsOn(...)`, and anything too shell-specific to translate (pipes, redirects, env assignments) is preserved behind a `// TODO` so the file still compiles. It scaffolds the launchers and `deno.json` exactly like `setup`, and takes the same `--dir`, `--name`, `--force`, `--yes`, `--bootstrap-deno` / `--no-bootstrap-deno`, `--mcp` and `--allow-run` flags. Afterwards, use the **zuke-write-build** skill to finish replacing any remaining generated `CmdTasks.exec` calls with typed `*Tasks` wrappers. ### What `zuke setup` writes - **`zuke.ts`** — a starter build class with a sample target and a `default`. - **`./zuke`** + **`./zuke.ps1`** — launchers that locate the project and run `zuke.ts`. By default (`--bootstrap-deno`) they use the Deno on `PATH` and, when there is none, download the pinned release Zuke itself runs on, verify it against a per-platform SHA-256, and install it under `~/.deno` — never an install script, never an unverified binary — so a clone needs nothing installed first. With `--no-bootstrap-deno` they require Deno on `PATH` and exit with the install docs URL when it is missing, for a project that must never download a tool from its build entry point. Both pass `--frozen` once a `deno.lock` exists, so the first run writes the lockfile and every run after verifies it. - **`deno.json`** — merged to add an `imports` entry for `@zuke/core` (`zuke import` adds `@zuke/cmd` too when it generates one), a `zuke` task, and `fmt`/`lint`/`test` if absent. The scaffolded build imports by **bare specifier** (`from "@zuke/core"`), not an inline `jsr:@zuke/core@^1` — Deno's default lint set rejects an inline specifier under `no-import-prefix`, so an inlined one would fail the project's own `deno task lint` on its only source file. The caret major is pinned in the import map instead. Merging is additive per key: a task or import the file already declares is kept exactly as it is (so a deliberate version pin survives), only what is missing is added. A file that has the `zuke` task but not the imports is still completed — skipping it would leave the `zuke.ts` written in the same run with nothing to resolve. Two shapes cannot be completed automatically, and setup reports them as steps for you and **exits 1** rather than printing `Next: ./zuke` over a build that cannot start: a `deno.json` that is JSONC (Deno accepts `//` comments and trailing commas that a JSON parser does not, and rewriting the file would discard them), and one that delegates via `importMap` to a separate file (Deno ignores that field the moment `imports` appears beside it, so the entry belongs in the other file — setup reads that file and stays quiet once it declares the entry, so a correctly configured project still exits 0). Both are left byte-for-byte untouched. A mapping that already points somewhere other than the JSR package is kept — that is how a pin or a local checkout survives — with a note saying so. `--mcp` that cannot register into an unparseable `.mcp.json` is reported the same way, as a note. - **`zuke.json`** — `{ "name": "..." }`, which marks the repo root. - **`.gitignore`** — created or appended so `.zuke/` is ignored (the cache and durable run state live there); untouched if it already covers it. - **`.mcp.json`** (with `--mcp`) — `mcpServers.zuke` launching `deno run -A zuke.ts mcp` (plus `--allow-run` when asked). Merged around any other servers already in the file; an existing `zuke` entry is kept unless `--force` is set. ## Running the build ```sh ./zuke # run the default target (Windows: .\zuke.ps1) ./zuke <target> # run a specific target ./zuke --list # list every target ./zuke --list --json # the whole build surface (commands, flags, targets) as JSON ./zuke <target> --dry-run # print the plan without executing ./zuke <target> --no-banner # no opening banner (also ZUKE_NO_BANNER) ``` Every run opens with a banner: the Zuke wordmark, then `zuke <version> · deno <version> · <platform>` and `run <id> · <cwd>`. On CI the wordmark is dropped — the identifying lines are what a runner log wants, six lines of ASCII are not — and the detected host is appended to the first line. `--no-banner` or `ZUKE_NO_BANNER` turns it off entirely. With the global CLI installed, the bare `zuke` works from anywhere inside the project: `zuke <target>`, `zuke --list`, `zuke graph`, `zuke mcp`, bare `zuke` for the default target — every command that is not the CLI's own (`setup`, `import`, `doc`) is forwarded to the nearest `zuke.ts`. `--help` and `--version` show both surfaces, the CLI's and then the build's, each labelled; `zuke -- --help` is the build's own usage alone, and `zuke -- <target>` reaches a target that shares one of those names. It walks up to the `zuke.json` that marks the repository root and runs `deno run -A zuke.ts <args>` from there, with `--frozen` once a `deno.lock` exists — the launcher's exact behaviour, minus the Deno bootstrap. Outside a project, `zuke <target>` reports the unknown command and the missing `zuke.json`, and a bare `zuke` prints the usage. The forwarding refuses a project whose root directory is owned by another user or is world-writable, and one where an ancestor `deno.json`, `deno.jsonc` or `package.json` Deno would read is owned by another user (the `safe.directory` rule git applies, since discovery runs code the caller never named); the error names what was refused and the fix — run that project's own `./zuke` there, or fix the ownership. On Windows the gate is inert. The CLI is self-describing: `./zuke --help` prints the usage grammar plus the build's live targets and parameters, so an agent discovers the real command surface instead of guessing. For an AI client to operate the build through typed calls, `zuke mcp` runs a Model Context Protocol server over it (register with `claude mcp add zuke -- deno run -A zuke.ts mcp`; add `--allow-run` to let the agent execute targets, not just inspect them). If Deno is already installed you can also use `deno task zuke <target>` or `deno run -A zuke.ts <target>`. The `-A` flag grants permissions, since targets typically run processes and touch files. These are not quite equivalent to the launcher: the scaffolded `zuke` task deliberately omits `--frozen`, so it may heal a stale lockfile where `./zuke` would fail on it. ## Manual setup (no CLI) Declare the packages the build imports in `deno.json` — bare specifier to `jsr:` dependency, caret major — so the build can import them by bare specifier. An inline `jsr:@zuke/core@^1` in the import statement fails `deno lint` under its default `no-import-prefix` rule: ```json { "imports": { "@zuke/core": "jsr:@zuke/core@^1", "@zuke/deno": "jsr:@zuke/deno@^1" } } ``` Then create `zuke.ts` in the project root, extend `Build`, declare targets with `target()`, and call `await run(MyBuild)` at the bottom: <!-- check --> ```ts import { Build, run, target } from "@zuke/core"; import { DenoTasks } from "@zuke/deno"; class CI extends Build { lint = target().executes(() => DenoTasks.lint()); test = target().dependsOn(this.lint) .executes(() => DenoTasks.test((s) => s.allowAll())); default = target().dependsOn(this.test).executes(() => {}); } await run(CI); ``` Run with `deno run -A zuke.ts test`. (For the `./zuke` launcher experience, prefer `zuke setup`, which drops the launcher scripts in for you.) ## Finding the exact API — never guess Every external tool has a typed `*Tasks` wrapper; **do not fall back to `Deno.Command` or hand-rolled shell.** First confirm a wrapper exists at all — `llms.txt`'s `## Packages` catalogue or the table in [`zuke-write-build`'s cheatsheet](../zuke-write-build/references/cheatsheet.md) is the only way to answer that; a per-package `deno doc` needs a name to target, so it cannot reveal that one exists. Once you know the package name, get its exact signatures: - A single package on the command line: `deno doc jsr:@zuke/<package>`. Prefer this in a consumer repo — it resolves the version the project actually has installed, so it cannot describe an API that version lacks. - The whole typed surface of every package is in **`llms-full.txt`** (indexed by `llms.txt`) — at the repo root in the Zuke repo itself, or from a consumer repo <https://raw.githubusercontent.com/zuke-build/zuke/master/llms-full.txt> (index: <https://raw.githubusercontent.com/zuke-build/zuke/master/llms.txt>). Both track `master`, so they may document symbols that are merged but not yet released; use them to find what exists, then confirm the signature with `deno doc`. Once the project is scaffolded, use the **zuke-write-build** skill to add and edit targets.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.