Getting started
This is the guided, command-line path through magus: install the binary, bootstrap a workspace, write your first magusfile, run a target, bind a spell, and compose a ci pipeline you can run against only the projects a change touched. Follow it top to bottom. Every command here is real; run each one as you read.
If you would rather try magus without installing anything, the interactive playground runs the same engine in your browser.
1. Install magus
magus ships as a single self-contained binary. Follow the Download guide for your platform, PATH setup, and signature verification, then confirm the binary is on your PATH:
magus version
The Download guide also covers magus self update and shell completion. This page assumes magus resolves on your PATH from here on.
2. Bootstrap a workspace with magus init
From the root of your repository:
magus init
magus init bootstraps a magus workspace in the current directory. It:
- Writes
magus.yaml, the workspace config. By default this goes to the global user config location ($XDG_CONFIG_HOME/magus/); pass--localto write it into the repo instead, which is what you want for checked-in, team-shared config. - Stubs a starter
magusfile.buzzin the repo root: a working file with every canonical stage already declared as a no-op, ready for you to fill in. - Wires the VCS merge driver so
magus.yamland the magusfile merge cleanly. The VCS is taken from--vcs(gitorhg), or chosen interactively when stdin is a terminal.
For a non-interactive run (for example in CI), pick the VCS explicitly and write the config into the repo:
magus init --local --vcs git
Other flags: --force overwrites an existing config file, and --global writes only the global config and skips the per-repo magusfile stub and merge driver.
3. Read your first magusfile
Open the magusfile.buzz that magus init created. It looks roughly like this: every exported function is a runnable target, and the current directory is already registered as a project on defaults.
import "magus";
import "os";
// Each exported function is a runnable target. Leave a stage as a no-op
// until you wire it.
export fun preflight(ctx: magus\Context, args: [str]) > void {}
export fun generate(ctx: magus\Context, args: [str]) > void { ctx.needs(preflight); }
export fun format(ctx: magus\Context, args: [str]) > void { ctx.needs(generate); }
export fun lint(ctx: magus\Context, args: [str]) > void { ctx.needs(format); }
export fun build(ctx: magus\Context, args: [str]) > void { ctx.needs(format); os\exec("echo", ["Hello from magus"]); }
export fun test(ctx: magus\Context, args: [str]) > void { ctx.needs(format); }
// 'ci' is the conventional anchor that `magus affected ci` keys off.
export fun ci(ctx: magus\Context, args: [str]) > void {
ctx.needs(lint, build, test);
}
Three ideas carry the whole model:
- Targets are exported functions. There is no registration call for a target: export a
fun, and its name becomes a runnable target. See targets for the full model and the CLI grammar. magus\needsdeclares prerequisites.ctx.needs(format)says "runformatfirst" - you pass the target function itself, so a typo is an undefined variable caught at load, not a run-time miss. magus builds a DAG from these edges, runs shared prerequisites once, and parallelizes independent branches.ciis the anchor. It is an ordinary target you compose withmagus\needs. magus does not hardcode its steps, but it is the targetmagus affectedkeys off, and it always runs read-only.
List what magus discovered, then run the starter build:
magus ls # list every discovered project, its files, outputs, and deps
magus run build # run the build target for the project under the cwd
magus ls prints every project it found; magus run build runs your build target (the starter one echoes a greeting after format). With no project argument, magus run selects the project containing the current directory.
4. Bind your first spell
The starter build shells out with os\exec. That is fine for a one-off, but real toolchains belong in a spell: a library of tool-native operations plus the cache metadata that toolchain needs. A spell runs nothing on its own; it contributes operations (go-build, go-test, go-vet, ...) that your targets compose, and it tells the cache which files are inputs and outputs. See spells, and Spells vs Targets for where the line falls.
Bind the built-in go spell by importing it and listing it in magus\project, then compose its ops into your targets. Op keys are the CLI command in kebab-case, so they are reached by subscript (go["go-build"]), and every op call takes the target's ctx as its first argument:
import "magus";
import "magus/spell/go";
magus\project({ "spells": [go] });
// Each exported function is a runnable target; its body calls the spell's ops.
export fun build(ctx: magus\Context, args: [str]) > void {
ctx.needs(format);
go["go-build"](ctx);
}
export fun format(ctx: magus\Context, args: [str]) > void { go["go-fmt"](ctx); }
// go-vet, golangci-lint, and govulncheck are all static analysis, so they
// compose into the canonical `lint` target, not bespoke targets.
export fun lint(ctx: magus\Context, args: [str]) > void {
go["go-vet"](ctx);
go["golangci-lint"](ctx);
go["govulncheck"](ctx);
}
export fun test(ctx: magus\Context, args: [str]) > void {
ctx.needs(format);
go["go-test"](ctx);
}
Now the canonical targets run the real toolchain:
magus run format # gofmt -l . (check only)
magus run lint # go vet, golangci-lint, govulncheck
magus run build # go build
magus run test # go test ./...
A note on read vs write: targets are read-only by default, so magus run format checks formatting rather than rewriting it. To mutate in place, attach the built-in rw charm:
magus run format:rw # gofmt -w . (rewrite files)
magus run lint:rw # golangci-lint --fix (apply autofixes)
Charms are shared, composable execution modifiers attached after :; see charms. If you want autofix as the local default so you do not type :rw each time, set default_charms: [rw] in magus.yaml (charms.md covers the safeguards that keep CI read-only regardless).
5. Compose a ci target and run only what changed
ci is where the pieces come together. Compose it from your other targets with magus\needs; magus fans them out in parallel where the DAG allows and runs shared prerequisites once:
export fun ci(ctx: magus\Context, args: [str]) > void {
ctx.needs(lint, build, test);
}
Run the whole pipeline locally:
magus run ci # lint, build, test (read-only; rw is stripped from ci)
The payoff arrives with magus affected. Instead of running ci for every project, it runs ci only for the projects a version-control change touched, plus everything transitively downstream of them in the dependency graph:
magus affected ci # run ci only for projects your changes touched
magus affected ci --base main # compare against a specific base ref
If you ever wonder why a project is in the affected set, ask:
magus affected ci --explain ./path/to/project
magus affected ci is the command your CI runs on every pull request. It keys off the ci anchor, computes the affected set from the VCS diff, and does the minimum work. For CI fan-out across runners, magus affected --plan emits a provider-neutral shard plan; see magus affected for --plan, --stdin, and bisect.
Recap
You now have the full loop:
magus initbootstrappedmagus.yaml, a starter magusfile, and the merge driver.- Exported functions in
magusfile.buzzbecame your targets. magus lsandmagus run buildlisted and ran them.- Binding the
gospell let your targets compose real ops (go-build,go-test,go-fmt, ...). - A
citarget composed withmagus\needsruns the pipeline, andmagus affected ciruns it only for what changed.
Next steps
The documentation index is the map. From here, the core concepts:
- targets - targets, the CLI grammar, and name resolution.
- spells - spells, their ops, and the built-in spell catalog.
- charms -
rwand other execution modifiers attached with:. - workspace - how magus discovers projects, and multi-project (monorepo) layout.
- cache - the content-addressed cache that makes re-runs fast.
- config - every
magus.yamlkey, itsMAGUS_*env var, and CLI flag. - sandbox - how spell subprocesses are confined to the workspace.
And the reference:
magus init,magus run,magus ls,magus affected- the commands in this guide.- playground.html - try any example live in the browser.