The bandura command runs the same execution engine as the desktop app, with no
Electron and no UI — built for terminals and CI. A flow that passes in the editor passes in
the pipeline, because it’s the same engine reading the same file.
Install
Available at launch. The commands below are the intended setup; the
@bandura/clipackage publishes to npm at the v1.0.0 launch. Join early access for the launch-day link.
bandura is distributed on npm as a small installer that pulls a native, self-contained
binary for your platform — there’s no JavaScript to load and, once installed, no Node.js
runtime is needed to run it.
# Global install — puts `bandura` on your PATH
npm install -g @bandura/cli
bandura --version
# Or run without installing (CI-friendly)
npx @bandura/cli run
The installer selects the right prebuilt binary automatically. Supported platforms:
| OS | Architectures |
|---|---|
| macOS | Apple Silicon (arm64), Intel (x64) |
| Linux | x64 and arm64, both glibc and musl (e.g. Alpine) |
| Windows | x64 |
Installing with
--ignore-scriptsskips the step that places the binary. If you must, run the CLI via the bundled fallback:node node_modules/@bandura/cli/cli-wrapper.cjs <args>.
In CI, prefer npx @bandura/cli@<version> (pin the version) or the official
bandura-io/run@v1 action, which wraps the same binary.
bandura run
bandura run # the manifest's flows globs, or **/*.aether
bandura run flows/smoke.aether # one flow
bandura run "flows/**/*.aether" # a glob
With no globs, run uses the flows field from bandura.json when there is one.
Options:
| Option | What it does |
|---|---|
-r, --reporter <names> | Output format: pretty (default, human), json, junit, github — comma-separate to emit several at once. |
-o, --output <file> | Write the machine report (json/junit) to a file. |
-e, --env <name> | Load a manifest environment’s non-secret values (default: the manifest’s defaultEnvironment; an unknown name exits 2). |
--var KEY=VALUE | Inject or override a variable (repeatable; highest precedence). |
--env-file <path> | The dotenv file with secret values (default: .env). |
--concurrency <n> | Run up to N flows in parallel (default 1; each flow stays sequential inside). Output is buffered into input order, so reports stay deterministic. |
--timeout <ms> | Whole-run deadline per flow — a flow that exceeds it fails with run timed out after <ms>ms. (Per-request ceilings are the node’s own timeout field, default 30 s.) |
--bail | Stop at the first failing flow. |
Examples:
# CI: JUnit for the test dashboard, plus GitHub annotations
bandura run "flows/**/*.aether" -r junit,github -o report.xml
# Point the suite at a different host without touching any file
bandura run smoke.aether --var baseUrl=https://staging.api.example.com
# The staging environment from bandura.json, four flows at a time
bandura run -e staging --concurrency 4
Flows with ai-action nodes run headlessly too: set
ANTHROPIC_API_KEY in the environment (or .env) and they execute with your key;
ANTHROPIC_MODEL optionally picks the model. Without a key, only the ai-action node
fails — the rest of the suite is unaffected.
Exit codes
Deterministic, so CI can tell “the tests failed” apart from “the tool broke”:
| Code | Meaning |
|---|---|
0 | All flows passed. |
1 | One or more assertions/requests failed — a test failure. |
2 | Usage or config error (bad flags, no flows matched, missing env). |
3 | Parse/validation error in an .aether file or bandura.json. |
4 | Internal error. |
bandura validate
Parses and schema-validates flows — and bandura.json — without executing anything:
bandura validate # everything the manifest resolves to
bandura validate "flows/**/*.aether" # or an explicit glob
One ✓/✗ line per file; exit 0 when everything is valid, 3 on any schema error, 2 when
nothing matches. Fast enough for a pre-commit hook or a PR gate that doesn’t need a network.
bandura list
Prints the flows the manifest resolves to — one path name (N nodes) line each, sorted by
path, headed by the project name. Handy for verifying what a glob will actually run.
bandura init
Scaffolds a project in the current directory:
bandura.json— withrequiredEnvinferred by scanning existing.aetherfiles for${{ env.* }}references..env.example— the inferred names, values blank, safe to commit..gitignore—.envand friends merged in (existing entries kept).
It never overwrites an existing manifest. This is the CLI twin of the desktop app’s
clone-and-run setup — run it once and a teammate’s git clone knows exactly which variables
to ask for.
bandura import
The same importers as the desktop app’s Import modal, headless:
bandura import ./collection.json --out flows/ # Postman
bandura import ./openapi.yaml --out flows/ # OpenAPI / Swagger
bandura import ./get-user.bru --out flows/ # a Bruno request file
bandura import ./request.curl # a saved curl command
--from openapi|postman|postman-env|bruno|curl forces the format when auto-detection guesses
wrong; --out picks the output directory (default: current directory).
A CI recipe
Any CI system works — it’s one command with an exit code:
# GitHub Actions
- run: npx bandura run "flows/**/*.aether" -r junit,github -o report.xml
env:
API_KEY: ${{ secrets.API_KEY }} # process env is visible to flows as env.API_KEY
Flows read env.* from the process environment plus the dotenv file (--env-file,
default .env) — never from inside the .aether files, which stay clean in the repo.