The bandura CLI

Run flows headlessly in a terminal or CI pipeline — reporters, exit codes, variable injection, plus validate, list, init, and importing from the command line.

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/cli package 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:

OSArchitectures
macOSApple Silicon (arm64), Intel (x64)
Linuxx64 and arm64, both glibc and musl (e.g. Alpine)
Windowsx64

Installing with --ignore-scripts skips 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:

OptionWhat 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=VALUEInject 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.)
--bailStop 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”:

CodeMeaning
0All flows passed.
1One or more assertions/requests failed — a test failure.
2Usage or config error (bad flags, no flows matched, missing env).
3Parse/validation error in an .aether file or bandura.json.
4Internal error.

bandura validate

Parses and schema-validates flows — and bandura.jsonwithout 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 — with requiredEnv inferred by scanning existing .aether files for ${{ env.* }} references.
  • .env.example — the inferred names, values blank, safe to commit.
  • .gitignore.env and 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.