CLI & CI
The point of storing collections as files is that something other than a GUI can run them. The CLI shares the same runner as the desktop app, so a request cannot behave one way on your machine and another way in CI.
Install
curl -fsSL https://mandalo.dev/install.sh | sh
That works on macOS and Linux, on both Intel and ARM. It picks the archive for your machine off the latest GitHub release, checks it against the release's SHA256SUMS, refuses to go on if the checksum does not match, and installs into ~/.local/bin — no sudo, and it tells you where the binary went. Set MANDALO_INSTALL_DIR to put it somewhere else, or MANDALO_VERSION to pin a tag.
There is also a Homebrew tap — brew install De-Rus/tap/mandalo — and on Windows a .zip on the release page. Every platform archive is listed there by target triple, alongside SHA256SUMS if you would rather verify and unpack it yourself.
cargo install mandalo-cli does not work: nothing is published to crates.io yet. Building from a checkout is cargo build --profile release-cli -p mandalo-cli.
The command surface
mandalo run <collection> [--folder PATH] [--env NAME] [--reporter pretty|json|junit] [--fail-fast]
mandalo send <collection> <request-path> [--env NAME]
mandalo ls
mandalo env list | get <name> | set <name> <key> <value>
mandalo import <file>
mandalo export <file>
mandalo scan [--staged]
runexecutes every request in a collection, in order, applying the same assertions, captures and scripts the app applies. It exits non-zero if any request fails.sendruns a single saved request —mandalo send payments-api auth/login.http --env staging— and prints the response.lsprints the collection tree, which is the fastest way to learn the request pathssendwants.envreads environments and writes non-secret variables into them.importandexportare the Postman importer and the bundle exporter from the app.scanlooks for credential-shaped literals in workspace files and exits non-zero on a hit — with--stagedit looks only at what you are about to commit, which makes it a usable pre-commit hook.
Two global flags matter: --workspace selects the workspace directory, and --strict-network refuses requests to loopback, private, link-local and cloud metadata addresses — the flag you want on a shared runner.
Secrets
Secrets are not meant to live in the workspace files. Any variable the environment does not define is read from the process environment as MANDALO_SECRET__<ENV>__<KEY>: the environment name and variable name upper-cased, with - replaced by _. Resolved secret values are scrubbed from the runner's own output, and under GitHub Actions they are additionally registered as masked values. The resolution rules are the same ones described in Environments & variables.
A GitHub Actions workflow
Copy this into .github/workflows/ and it runs.
name: api-checks
on:
pull_request:
schedule:
- cron: "0 6 * * *"
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mandalo
run: |
curl -fsSL https://mandalo.dev/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Run the payments suite
env:
MANDALO_SECRET__STAGING__TOKEN: ${{ secrets.STAGING_API_TOKEN }}
run: |
mandalo run payments-api \
--workspace ./api \
--env staging \
--strict-network \
--reporter junit > results.xml
- name: Publish results
if: always()
uses: actions/upload-artifact@v4
with:
name: api-results
path: results.xml
The shape is the point: the collection under review is the collection that runs, the environment file in the repo supplies the non-secret variables, and the one secret comes from the Actions secret store and never touches a file.
Pre-commit hygiene
#!/bin/sh
# .git/hooks/pre-commit
mandalo scan --staged || exit 1
Committing a token by accident is the failure mode that actually happens with file-based collections. A staged scan is the cheap guard; the habits in Collections & git are the real fix.
Following along
Watch the repository for the release that ships the binary, and the changelog for the announcement. Until then, the desktop app is the supported way to run requests.