CLICLItypeA command you run, rather than a dependency you import.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

@saptools/bruno

A command line that runs Bruno collections against XSUAA-protected apps on Cloud Foundry, fetching the app's token and writing it into the selected environment file before each run.

Bruno does not mint OAuth2 tokens, so running a collection with bru run against a service behind XSUAA means pasting a fresh token into its environment file each time the last one expires. bruno run gets the token through @saptools/cf-xsuaa, writes it as accessToken into the chosen .bru environment file and passes it to bru with --env-var, using the bru on the PATH or else the bundled @usebruno/cli. bruno setup-app scaffolds a region__<key>/org__<org>/space__<space>/<app> folder from the landscape cached by @saptools/cf-sync, with __cf_* variables in each environment file that tell the runner which app's token to fetch, and bruno use pins a default target. The requests and assertions stay ordinary Bruno files; what it adds is the token and the folder layout.

Facts

Latestv0.4.1ReleasedLast pushLicenceMIT

Where it sits

Lives in
Provides
TestingBTPTestingcapabilityProving the service still does what it did — against the model and the handlers, without a deployed system and without a database somebody has to keep clean.
Runs
Cloud Foundry
Requires
@saptools/cf-xsuaa@saptools/cf-xsuaaartifactFetching or reusing the cached XSUAA token of the target app before every run.@saptools/cf-sync@saptools/cf-syncartifactThe cached Cloud Foundry landscape that setup-app reads to offer regions, orgs, spaces and apps; it has to be synced first.Node.jsNode.jsoutside the atlasIt is a Node command line and library, and the README names the Node version it needs.

Who is behind it

Đông TrầnĐông TrầnPersonMaintains this
In the atlas: maintains 18, contributes to 18
@dongitran
maintainer

What it declares

Every artifact

Type
CLI
Origin
Independently maintained
Runs on
Cloud Foundry

As a CLI

Distributed through
npm

Connections4

Install

npm add @saptools/bruno

Derived from the package name. No recipe recorded; the README has the configuration.

Where @saptools/bruno runsBy its type: a CLI is run from a terminalcallsdocumentedrequiresdocumentedrequiresdocumentedrequiresYour terminal, actorYour terminalNode.js, systemNode.jssystem@saptools/bruno, module@saptools/bruno@saptools/cf-xsuaa, module@saptools/cf-xsuaa@saptools/cf-sync, module@saptools/cf-sync
SAPnot SAP's, or the project's ownstated in the documentdocumented only

README

quoted from dongitran/saptools under MIT, read
dongitran/saptools/packages/bruno/README.mdOn GitHub

🎯 @saptools/bruno

A smart runner for Bruno collections on SAP BTP Cloud Foundry.

Scaffold a CF-aware collection. Resolve requests by region/org/space/app shorthand. Let every bru run start with a fresh XSUAA token already injected and written back to the selected env file — no more pasting Authorization headers into env files, no more manual token refresh dances.

npm version downloads license node types build

Install · Quick Start · CLI · FAQ · Roadmap


⚡ At a glance

$ bruno use ap10/demo-prod/api/orders-srv
✔ Default context set to ap10/demo-prod/api/orders-srv

$ bruno
# chọn Run .bru file (folder tree)
# duyệt cây thư mục rồi chọn file .bru để chạy
▶ bru run --env dev --env-var accessToken=eyJhbGciOi…  (cwd=…/orders-srv)
Running Folder Recursively
✓ GET /orders              204 OK   54ms
✓ POST /orders             201 Created 120ms
✓ GET /orders/:id          200 OK   48ms
All assertions passed ✓

You just ran Bruno against a production-grade XSUAA-protected service without ever touching a token. That's the entire pitch.


✨ Features

  • 🏗️ Interactive setup-app — pick a region → org → space, then search apps as you type before choosing exactly the environments you want (or typing a custom name like qa-eu). Every env file is seeded with __cf_* metadata so the runner knows where to fetch a token.
  • 🧭 Shorthand paths — region/org/space/app[/folder/file.bru] expands to the right filesystem path. No more cd-ing through nested folders.
  • 🔐 Automatic XSUAA tokens — every run fetches (or reuses) a cached token via @saptools/cf-xsuaa, writes it into the selected env file as accessToken, and still injects it for bru at execution time.
  • 📦 Bundled Bruno CLI fallback — if bru is already on your PATH, bruno uses it. If not, it falls back to the bundled @usebruno/cli.
  • 🎯 Default context — bruno use <shorthand> pins a target so subsequent run calls need zero arguments. Feels like cf target for Bruno.
  • 🧩 CLI & typed API — every command has a zero-config Node.js equivalent. Full TypeScript definitions shipped. Bring your own prompts for headless/CI use.
  • 🧪 Fully tested — unit tests plus offline e2e coverage (stub bru binary + fixture CF snapshot). No network required in CI.
  • 🪶 Small + boring — a small runtime surface, no background daemons, no plugin system, no magic.

😩 Before → 😎 After

Without @saptools/bruno With @saptools/bruno
# 1. Find the service creds on Cockpit
# 2. cf target -o demo-prod -s api
# 3. cf create-service-key orders-srv bruno-key
# 4. cf service-key orders-srv bruno-key
# 5. Copy clientid / clientsecret / url
# 6. curl -X POST $URL/oauth/token \
#    -u $CLIENT_ID:$CLIENT_SECRET \
#    -d grant_type=client_credentials
# 7. Copy access_token
# 8. Paste into environments/dev.bru
# 9. bru run --env dev
# 10. Token expires → goto 6
bruno use ap10/demo-prod/api/orders-srv
bruno run --env dev

That's it. Token is cached, refreshed on expiry, written back to the env file, and injected automatically.


📦 Install

# Global CLIs
npm install -g @saptools/cf-sync @saptools/bruno

# Or as a project dependency
npm install @saptools/bruno
# pnpm add @saptools/bruno
# yarn add @saptools/bruno

Note

Requires Node.js ≥ 20 and a cached CF landscape from @saptools/cf-sync. @saptools/bruno now bundles @usebruno/cli automatically, but still prefers an existing bru on PATH if you already have one installed.

npm does install @saptools/cf-sync as a dependency of @saptools/bruno, but a global install of @saptools/bruno does not expose the transitive cf-sync binary on your PATH. For global CLI usage, install @saptools/cf-sync explicitly. For project-local usage, invoke the local bin through your package manager, for example npx --no-install cf-sync sync or pnpm exec cf-sync sync.


🚀 Quick Start

# 1. Sync your CF landscape once
cf-sync sync

# 2. Scaffold an app folder with seeded __cf_* metadata
bruno setup-app

# 3. Pin a default CF context so future runs need zero args
bruno use ap10/my-org/dev/my-srv

# 4. Run — XSUAA token is fetched, written to the env file, and injected automatically
bruno run --env dev

After setup-app, your workspace looks like this:

.
└── region__ap10/
    └── org__my-org/
        └── space__dev/
            └── my-srv/
                ├── bruno.json
                └── environments/
                    ├── dev.bru
                    └── prod.bru

Each env file starts with the CF coordinates needed for token lookup:

vars {
  __cf_region: ap10
  __cf_org:    my-org
  __cf_space:  dev
  __cf_app:    my-srv
  environment: dev
  baseUrl:
}

Your .bru requests reference {{accessToken}} like any other Bruno variable — the runner refreshes it into the selected env file before spawning Bruno.


🧰 CLI

🗂️ bruno (interactive tree mode)

Chạy bruno không kèm subcommand sẽ mở menu tương tác:

  • Run .bru file
  • Set default context
  • Setup app folder
  • Set Bruno root folder

Ở chế độ chạy file, danh sách browse chỉ hiển thị cấp thư mục hiện tại (tên gần nhất). Bạn có thể duyệt vào thư mục con/back, hoặc chọn Search .bru file để tìm toàn cây và kết quả sẽ hiển thị theo đường dẫn tree tương đối.

📌 bruno set-root [dir]

Lưu Bruno root folder mặc định vào ~/.saptools/bruno/cli-state.json.

bruno set-root /path/to/your/collection

Sau khi set, lần gọi bruno tiếp theo sẽ dùng root này nếu bạn không truyền --collection và không set env var.

🏗️ bruno setup-app

Interactively scaffold a Bruno app folder inside the current Bruno collection directory. Walks you through region → org → space → app, with the app step using a searchable picker for large spaces, then lets you pick which environments to create and add custom names without leaving the environment picker.

bruno setup-app
bruno --collection ./collections setup-app

Tip

--collection only applies to the current command. If you omit it, bruno falls back to $SAPTOOLS_BRUNO_COLLECTION, then to your current working directory.

Important

setup-app reads the cached CF landscape prepared by cf-sync. If the cache is missing or stale, run cf-sync sync first.

What you get

  • An app-level bruno.json inside region__<key>/org__<org>/space__<space>/<app>/
  • Folder tree: region__<key>/org__<org>/space__<space>/<app>/environments/
  • One .bru env file per selection, each seeded with __cf_region, __cf_org, __cf_space, __cf_app, environment, and an empty baseUrl
  • Existing env files are preserved; only missing __cf_* vars are patched back in

Tip

The env prompt shows the common names (local, dev, staging, prod) plus any envs already on disk. Pre-existing envs are pre-checked; common ones are not — so you only create what you actually need. The menu also includes Add custom environment, and once you enter a value like qa-eu or uat.us, it appears back in the same checklist already selected so you can review the full set before finishing.

▶️ bruno run

Run a Bruno request or folder, refreshing accessToken in the chosen env file and auto-injecting the same token for the current execution.

# Use the default context
bruno run --env dev

# Explicit shorthand
bruno run ap10/my-org/dev/my-srv --env dev

# Drill into a subfolder or a single file
bruno run ap10/my-org/dev/my-srv/users/get-all.bru --env dev

# Or pass a real filesystem path (absolute or relative)
bruno run ./region__ap10/org__my-org/space__dev/my-srv --env dev
FlagDescription
-e, --env <name>Environment name (default: current context or first discovered env)
--collection <dir>Bruno collection directory (default: $SAPTOOLS_BRUNO_COLLECTION or cwd)

Under the hood this:

  • fetches or reuses a token via @saptools/cf-xsuaa
  • writes accessToken: <token> into the selected .bru env file
  • spawns bru run <target> --env <name> --env-var accessToken=<token>
🎯 bruno use

Pin a default CF context so run can be called without arguments.

bruno use ap10/my-org/dev/my-srv
bruno use ap10/my-org/dev/my-srv --no-verify
FlagDescription
--no-verifySkip verifying the shorthand against the cached CF structure

The context lives at ~/.saptools/bruno-context.json.


📁 Folder Layout

All state lives under your home directory or your collection root:

~/.saptools/bruno-context.json              # pinned region/org/space/app + updatedAt

<root>/
├── bruno.json
└── region__<key>/
    └── org__<org>/
        └── space__<space>/
            └── <app>/
                ├── environments/
                │   ├── dev.bru              # vars { __cf_region, __cf_org, ... }
                │   └── prod.bru
                └── <your .bru requests>
🔬 Shape of an env file after setup-app
vars {
  __cf_region: ap10
  __cf_org:    my-org
  __cf_space:  dev
  __cf_app:    my-srv
  environment: dev
  baseUrl:
}

The __cf_* vars drive XSUAA lookup. run adds accessToken on the fly via bru --env-var, so your requests can simply reference {{accessToken}}.

Important

Prefer the CLI or the exported APIs over hand-editing these files — the on-disk format is parsed and rewritten by setup-app, and re-setup will patch missing __cf_* vars back in.


🌱 Environment Variables

VariablePurpose
SAPTOOLS_BRUNO_COLLECTIONDefault Bruno collection directory when --collection isn't passed
SAPTOOLS_ACCESS_TOKENExported to the spawned bru process (alongside --env-var accessToken=…)
SAP_EMAIL / SAP_PASSWORDConsumed by @saptools/cf-xsuaa when the token cache is cold

🧭 How it compares

ApproachXSUAA handlingShorthand pathsCF-aware scaffoldingCache/refreshWorks in CI
Hand-edit environments/*.bru❌ manual❌❌❌❌
Bruno GUI OAuth2✅❌❌partial❌ (GUI)
bru run alone❌❌❌❌✅
bruno✅ automatic✅✅✅✅

🧪 Quality

  • 74 unit tests via Vitest (strict TS · ESLint · 80%+ branch coverage on core flows)
  • 4 end-to-end tests via Playwright's test runner — stubbed bru binary, fixture CF snapshot, zero network
  • Type-checked under strict + exactOptionalPropertyTypes + noUncheckedIndexedAccess — the strictest realistic TS profile
  • CI on every push (lint · typecheck · build · unit · e2e · npm pack --dry-run)
  • npm publishes with provenance via GitHub OIDC trusted publishing

❓ FAQ

Why does Bruno need a wrapper — can't I just call bru run?

You can, but every CF service behind XSUAA needs a fresh OAuth2 token, and Bruno doesn't mint them. bruno run fetches the token (cached when possible), injects it as accessToken, and gets out of the way. Your .bru requests stay portable.

Do I have to re-run setup-app when CF changes?

Only when you add a new app folder. setup-app on an existing app is idempotent — it pre-checks existing envs, preserves their contents, and patches missing __cf_* vars back in.

How do I add an env that isn't local / dev / staging / prod?

Choose Add custom environment inside the checkbox list. After you type any [A-Za-z0-9._-]+ name (for example qa-eu or uat.us), the prompt returns to the same checklist with that new environment already selected.

Where does the token come from?

@saptools/cf-xsuaa. runBruno calls getTokenCached({ region, org, space, app }) and reuses the local cache until it expires. You can inject your own fetcher via the getTokenCached option when using the API.

Can I use this without the CF-structured folder layout?

run accepts both shorthand (region/org/space/app/...) and real filesystem paths. However, __cf_region/__cf_org/__cf_space/__cf_app must be present in the env file — those are what drive the XSUAA lookup. Run setup-app once to bootstrap them.

How do I use this in CI?

Use the programmatic API with your own prompt stubs (every field just returns the value you want), or drive the CLI after injecting SAP_EMAIL / SAP_PASSWORD so the token cache can be populated on first run. The e2e suite of this repo is itself a CI-safe example.


🗺️ Roadmap

  • setup-app with selectable environments and custom-name input
  • Shorthand path resolution (region/org/space/app[/file])
  • Default CF context via use
  • Offline e2e via stubbed bru
  • bruno doctor — diagnose missing __cf_* vars, stale tokens, missing bru
  • bruno migrate — move collections from a flat layout into the CF-aware layout
  • First-class --reporter json support for piping test results into dashboards

Have an idea? Open an issue — the roadmap is driven by real use.


🛠️ Development

From the monorepo root:

pnpm install
pnpm --filter @saptools/bruno build
pnpm --filter @saptools/bruno typecheck
pnpm --filter @saptools/bruno test:unit
pnpm --filter @saptools/bruno test:e2e

The e2e suite uses a stub bru binary and fixture CF snapshots, so it runs fully offline. Contributions, bug reports, and feature requests are all welcome — see the issues tab.



🤝 Contributors

Contributors

👨‍💻 Author

dongtran ✨

📄 License

MIT


Made with ❤️ to make your work life easier!

Updates

Every command first checks npm for a newer @saptools/bruno (at most once an hour, one small request with a 2-second timeout) and, when one exists, installs that exact version with the package manager that owns the running binary and re-runs the command you typed on the new version. Both steps are announced on stderr; nothing is printed when the install is already current:

saptools-bruno: updating 0.4.0 -> 0.5.0 ...
saptools-bruno: updated to 0.5.0; re-running the command

If the install cannot complete, one stderr line gives the manual command and the command runs on the installed version; that version is not retried for a day. saptools-bruno self-update forces the check and install now; saptools-bruno self-update --check only reports.

ControlEffect
SAPTOOLS_AUTO_UPDATE=on|notify|offon (default) installs and re-runs; notify prints the manual command once per version; off never checks. Applies to every @saptools CLI.
BRUNO_AUTO_UPDATEsame values, this CLI only; wins over the global variable
SAPTOOLS_UPDATE_INTERVAL_MINUTESminutes between checks (default 60; 0 checks on every run)
SAPTOOLS_NPM_REGISTRYregistry to check and install from (default: npm's configured registry, then npmjs)
SAPTOOLS_UPDATE_DEBUG=1explain on stderr why nothing happened

The updater switches itself off in CI (CI set), under NODE_ENV=test or NO_UPDATE_NOTIFIER, when the binary runs from a source checkout, an npm link or an npx cache, and inside the re-run itself. It never writes to stdout, never asks for input, never uses sudo, and never moves onto a prerelease. Its state lives in ~/.saptools/updates/.

MIT, read

History

Latest

v0.4.1

  1. v0.4.0

  2. v0.3.6

  3. v0.3.5

  4. v0.3.4

  5. v0.3.3

  6. v0.3.2

  7. v0.3.1

  8. v0.3.0

  9. v0.2.13

  10. v0.2.12

  11. v0.2.11

  12. v0.2.10

  13. v0.2.9

  14. v0.2.8

  15. v0.2.7

  16. v0.2.6

  17. v0.2.5

  18. v0.2.4

  19. v0.2.3

  20. v0.2.2

  21. v0.2.1

  22. v0.2.0

  23. v0.1.0

On npm
1downloads per week, npm
Weekly downloads for @saptools/bruno26 complete calendar weeks from 2026-04-27 to 2026-10-25; the latest total is 1.27 Apr – 3 May: 3774 May – 10 May: 9111 May – 17 May: 8018 May – 24 May: 18625 May – 31 May: 511 Jun – 7 Jun: 1108 Jun – 14 Jun: 8215 Jun – 21 Jun: 2922 Jun – 28 Jun: 3029 Jun – 5 Jul: 3386 Jul – 12 Jul: 3513 Jul – 19 Jul: 4020 Jul – 26 Jul: 4027 Jul – 2 Aug: 73 Aug – 9 Aug: 5910 Aug – 16 Aug: 7917 Aug – 23 Aug: 624 Aug – 30 Aug: 1331 Aug – 6 Sept: 4107 Sept – 13 Sept: 4614 Sept – 20 Sept: 2921 Sept – 27 Sept: 3028 Sept – 4 Oct: 135 Oct – 11 Oct: 1712 Oct – 18 Oct: 819 Oct – 25 Oct: 1
Testing
  1. com.sap.cds:cds-services-bomMaven bill of materials pinning CAP Java module versionsfrom CAP
  2. @cap-js/cds-testTest support for CAP Node.js: server start, HTTP client, assertionsfrom CAP
  3. cds-cucumberGherkin scenarios driving a running CAP servicefrom CAP
  4. sapbrunoTerminal UI running Bruno collections against a CAP servicefrom CAP
  5. cds-launchpad-pluginSandbox launchpad for all SAPUI5 apps in a CAP workspacefrom CAP
  6. enforce-export-coverageStatic check that each exported TypeScript function has a testfrom General
  7. @sap-ux/fe-mockserver-plugin-cdsFiori Elements mock server plugin taking a CDS file as metadatafrom CAP

Details

Sources4
Added by handdiscovered herechecked
npm registry, @saptools/brunoversion, releases, installschecked
github.com/git+https://github.com/dongitran/saptools.gittree, pushes, contributors, CIchecked
packages/bruno/README.mdquoted, and what the entry declareschecked

Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.

Compatibility and provenance
deploymentCloud Foundry
latestv0.4.1
first release
releases and cadence24, ~3d median
discovered viaAdded by hand
first seen in the atlas
ecosystem and domainbtp, developer-experience
History in the atlas1
  1. First seen.
For your agent
Ref
artifact:saptools-bruno
JSON
/artifacts/saptools-bruno.json
MCP
Connecting an agent