Tour
A guided walk through magusfiles and Buzz, step by step. Open any example in the Playground to run and edit it.
-
1
Magusfiles: write your first target
A magusfile is a build definition; each exported function is a runnable target. Targets
import "magus"; export fun greet(ctx: magus\Context, args: [str]) > void { magus\info("hello from magus"); } -
2
Targets: wire the dependencies
Wire a pipeline with magus.needs; ci is the conventional anchor it all keys off. Targets
import "magus"; export fun build(ctx: magus\Context, args: [str]) > void { magus\info("compiling"); } export fun test(ctx: magus\Context, args: [str]) > void { -
3
Spells: bind a real toolchain
Bind a real toolchain (Go, TypeScript, Docker) as cached, kebab-case ops. Spells
import "magus"; import "magus/spell/go"; // the Go toolchain: go-build, go-test, go-vet, ... magus\project({ "spells": [go] }); export fun build(ctx: magus\Context, args: [str]) > void { go["go-build"](ctx); -
4
Buzz: branch, match, recurse
Functions, recursion, closures and match - evaluated live in your browser. Engines
import "magus"; // Recursion, the classic. `eval fibo(30)` computes 832040 in a real bytecode VM. fun fibo(n: int) > int { if (n < 2) { return n; } return fibo(n - 1) + fibo(n - 2); } -
5
Slots: taming a greedy target
A target starts with one slot; reserve more so a parallel tool cannot starve the rest. Operations
import "magus"; import "magus/spell/go"; import "magus/spell/typescript"; // Size the heavy job once, then feed the SAME number to both levers: the slots // magus reserves for the target, and the --concurrency eslint runs with. Reserve // without using them and the slots sit idle; use more than you reserved and you -
6
Cache: replay, and catch drift
Targets replay from cache and run once per plan; skip_cache opts a check out. Caching
import "magus"; import "magus/spell/go"; magus\project({ "spells": [go], "outputs": ["bin/**", "gen/**"], "targets": { -
7
Globs: gather a target family, minus one
Name targets by a shared suffix, gather them with one glob, and subtract the odd one out with a negation. Dependencies
import "magus"; import "magus/spell/go"; import "magus/spell/buf"; magus\project({ "spells": [go, buf] }); // One target per generator, all sharing the -generate suffix. -
8
Charms: check, then apply
A charm rides down the plan; the built-in rw flips read-only checks to rewrites. Charms
import "magus"; import "magus/spell/go"; magus\project({ "spells": [go] }); // Read the active charm set with has_charm, then branch. (Most real spell ops do // this flip internally, so you usually get rw for free; here it is spelled out so -
9
Services: share one across runs
Author a spell whose op returns a Service; magus supervises one, shared across runs. Services
import "magus/spell"; // A spell announces its name... export fun mgs_getName() > str { return "postgres"; } // serve returns a Service, so magus treats it as a long-running, shared op: // command - the process magus forks in the foreground and supervises -
10
Wards: catch the contradiction
A detached service trips MGS5002 at resolution, before anything forks. Wards
import "magus/spell"; export fun mgs_getName() > str { return "postgres"; } fun serve(t: Target) > Service { return Service{ // The -d is the bug: a service that detaches contradicts its own kind. -
11
Workspaces: run only what changed
Projects wire cross-project order with depends_on; affected runs only what changed. CI and affected
import "magus"; import "magus/spell/go"; // The shared library project. Nothing depends on the app, so a lib change is what // ripples outward through the graph. magus\project("lib", { "spells": [go], -
12
Pipelines: assemble the whole build
The full multi-toolchain pipeline: generate, format, lint, build, test, release. CI
import "magus"; // project registration, targets, dependency edges import "magus/spell/buf"; // protobuf toolchain: buf-generate, buf-lint, buf-format import "magus/spell/go"; // Go toolchain: go-build, go-test, go-vet, go-fmt, ... import "magus/spell/typescript"; // TypeScript toolchain: tsc, eslint, prettier, vitest import "magus/spell/docker"; // container image: docker-build, hadolint final VERSION: str = "1.4.0"; -
13
Custom targets: name your own phases
The canonical names are conventions, not a closed set; export a release or deploy target and drive one from another. Targets
import "magus"; import "magus/spell/go"; magus\project({ "spells": [go] }); export fun build(ctx: magus\Context, args: [str]) > void { go["go-build"](ctx); -
14
Standard library: batteries included
Buzz ships host modules - string casing, semver math, JSON, hashing - you import and call straight from a target. Buzz modules
import "magus"; import "semver"; import "strings"; // A target assembling build config from the stdlib the way a real one would: // derive an artifact slug and compare two versions, then print them. `run stamp` // computes each value for real. -
15
Custom charms: patch the argv
Declare your own charm as a JSON Patch over a spell op's argv; run op:charm to watch the flag splice in. Charms
import "magus/spell"; import "magus/charm"; export fun mgs_getName() > str { return "linter"; } // One op, one base command, two charms that reshape it. `after` splices values in // just past an anchor argument; `append` adds to the end. Both resolve to an index -
16
Guardrails: try to break it
A deliberate cycle, caught at resolution before anything forks - plus the three other refusals worth knowing. Wards
import "magus"; magus\project({}); export fun build(ctx: magus\Context, args: [str]) > void { // The contradiction: build needs bundle, and bundle needs build. ctx.needs(bundle); -
17
Output refs: what they are, and are not
A failure mints a handle to that run's exact bytes - not a cache key, not deterministic, and not portable off your machine. Output references
import "magus"; import "os"; magus\project({}); export fun build(ctx: magus\Context, args: [str]) > void { os\exec("sh", ["-c", "echo 'compiling widget...'; echo 'error: undefined symbol' >&2; exit 1"], ".", {}); -
18
Typed boundaries: keep the data inside
A target returns void, so structured data belongs in the magusfile - declare the table once, export a verb per action, and let CI supply only the secrets. Tips and tricks
import "magus"; import "os"; // The shape, declared once. Note what user_ref holds: a REFERENCE to a credential - // under the built-in provider, the name of an environment variable - never the value. // That is the whole trick to keeping secrets out of a magusfile: this file describes // where to find a credential, something else supplies it, and neither has to know about -
19
Errors: raise, catch, and branch on a code
A host call that cannot answer raises rather than returning a blank; catch binds a map with message, and a code and url when the failure carries one, so a target branches on identity instead of matching prose. Diagnostics
import "magus"; import "os"; import "vcs"; magus\project({ "name": "tour-errors", "targets": { -
20
Footprints: what a target reads and writes
Two places to put a glob and the difference is not obvious from the names. Declaring the wrong one is the mistake people make most often. Caching
import "magus"; import "fs"; magus\project({ "name": "tour-footprints", // Project-wide because EVERY target here reads it. A target-specific input would not // belong up here - see report/stamp below. -
21
Cache identity: what counts as the same work
A hit means magus decided this run is identical to an earlier one, so every surprise comes from something that is part of that identity without looking like an input. Tool identity
import "magus"; magus\project({ "name": "tour-cache-identity", "sources": ["magusfile.buzz", "src/**"], "targets": { // These exist to PRINT, and a replay skips the print - which would hide the very -
22
Services: when two services are the same service
Whether your test suite gets one Postgres or four comes down to what magus considers the same config. Services
import "magus/spell"; export fun mgs_getName() > str { return "pg"; } // The baseline. Everything in this command that the fingerprint covers - image, tag, port, // env - is part of what makes it THIS service rather than another one. fun db(target: Target) > Service { -
23
Charm axes: one question each, and a charm that checks itself
The shape a real publish target reaches: two charms on separate axes, a guard for the pair that answers one question twice, and magus.raise so a refusal carries a code instead of a sentence. Recommendations
import "magus"; import "semver"; import "std"; magus\project({}); // The version this build would carry. Real magusfiles read vcs\describe(); a literal keeps