Berserk CLI
Command-line tool for interacting with Berserk
The Berserk CLI provides command-line access for querying data, managing clusters, and automation. It pairs especially well with AI coding agents like Claude Code for investigating production issues.
Installation
curl -fsSL https://go.bzrk.dev | bashThis installs the bzrk binary to ~/.local/bin. You can override the install directory with BZRK_INSTALL_DIR:
BZRK_INSTALL_DIR=/usr/local/bin curl -fsSL https://go.bzrk.dev | bashTo install a specific version:
curl -fsSL https://go.bzrk.dev | bash -s v1.0.15Platform support
The CLI is available for Linux (x86_64 and aarch64) and macOS (Apple Silicon).
Getting Started
The quickest path from a fresh install to your first query:
# 1. Verify the binary
bzrk --help
# 2. Create a profile pointing at your gateway, and make it active.
# --database picks the default database for queries; "default" is
# the one every cluster ships with (optional — this is the default).
bzrk profile add dev \
--endpoint https://berserk.dev.example.com \
--database default
bzrk profile use dev # so the commands below need no -P flag
# 3. Sign in (device-code flow)
bzrk login
# 4. See what's there
bzrk database list # databases (admin-only in v1)
bzrk table list # tables in the active database
# 5. Run a query. Always pass --since — it defaults to "1h ago".
bzrk search "default | count" --since "24h ago"A few notes on the sequence:
- Profile before login.
bzrk loginsigns in an existing profile, so it has to come after step 2 — there's no zero-config login. With no profile it stops and tells you to create one. profile usesets the active profile once, so the rest of the commands need no-P. Pass-P <name>for a one-off against a different profile — see Resolution order.defaultis the catch-all table every cluster ships with; swap in any name frombzrk table list.
The sections below cover profiles — file layout and resolution — and authentication in detail.
Profiles
Profiles let you store named connection configurations so you can switch between environments (local, dev, production) without remembering endpoint URLs. The storage splits two concerns:
- Endpoints — named URLs. Not secret; safe to share.
- Sessions — credentials (bearer + refresh token + expiry) referencing an endpoint by name.
Configuration file
User configuration lives at ~/.config/bzrk/config.toml (following XDG Base Directory). The file mode is enforced to 0600 — the CLI refuses to read it otherwise and tells you the exact chmod 600 <path> to run. bzrk profile add and bzrk login set the mode automatically.
default_session = "dev" # the active profile, used when no -P is passed
# Endpoint glossary — URLs by name, no secrets.
[endpoints.dev]
url = "https://berserk.dev.example.com"
[endpoints.gw-local]
url = "http://localhost:9500"
# A session pairs credentials with an endpoint, by name.
[sessions.dev]
endpoint = "dev"
bearer_token = "abcdef01…"
refresh_token = "0123456789…"
access_token_expires_at = "2026-05-26T14:00:00Z"
[sessions.gw-local]
endpoint = "gw-local"
bearer_token = "…"
refresh_token = "…"Each session has the following fields:
| Field | Required | Description |
|---|---|---|
endpoint | Yes | The name of an [endpoints.X] entry the session points at. |
bearer_token | No | Access token issued by bzrk login. Sent as Authorization: Bearer … on every request. Auto-refreshed before expiry. |
refresh_token | No | Long-lived token used to mint new access tokens. bzrk logout revokes it server-side and clears it locally. |
access_token_expires_at | No | RFC 3339 timestamp written by bzrk login. The CLI refreshes ~30s before this fires. |
username, database | No | Optional metadata. database sets the default database for queries on this profile (overridable per-invocation with --database). |
Client certificates and custom headers
When a gateway sits behind a mutual-TLS (mTLS) edge — one that requires every client to present an X.509 certificate — attach the certificate and any routing headers to the endpoint. These describe how to reach that URL, so every session pointing at the endpoint inherits them.
[endpoints.secure]
url = "https://gateway.internal.example:443"
# Client identity presented for mTLS (PEM). Both are required together.
client_cert_path = "~/.cache/certs/client.pem" # leaf + signing chain
client_key_path = "~/.cache/certs/client-key.pem"
# Verify the server against this CA instead of the system trust store (PEM, optional).
ca_cert_path = "~/.cache/certs/ca.pem"
# TLS server name (SNI / authority) to verify against, when it differs from the
# endpoint host — e.g. an upstream service identity behind a routing proxy (optional).
tls_server_name = "upstream.internal.example"
# Headers added to every gRPC request to this endpoint — e.g. an edge routing tag.
[endpoints.secure.headers]
"x-routing-tag" = "region/prod"| Endpoint field | Description |
|---|---|
client_cert_path | PEM client certificate (leaf + chain) presented for mTLS. Requires client_key_path and an https:// URL. |
client_key_path | PEM private key for client_cert_path. |
ca_cert_path | PEM CA bundle used to verify the server, instead of the system roots. |
tls_server_name | TLS server name (SNI / authority) to verify against when it differs from the endpoint host. |
headers | Table of Name = "Value" pairs attached to every gRPC request sent to this endpoint. |
Paths may start with ~ or $HOME; the CLI expands them to your home directory. The mTLS fields require an https:// endpoint — the CLI refuses to start otherwise. The certificate is re-read on every invocation, so an externally-rotated certificate is picked up by the next command.
bzrk profile add accepts the same flags and persists them into the new [endpoints.X] entry, so you don't have to hand-edit config.toml:
bzrk profile add secure \
--endpoint https://gateway.internal.example:443 \
--client-cert ~/.cache/certs/client.pem \
--client-key ~/.cache/certs/client-key.pem \
--ca-cert ~/.cache/certs/ca.pem \
--tls-server-name upstream.internal.example \
--header "x-routing-tag: region/prod"Every setting can also be supplied (or overridden) per-invocation with a flag — handy for CI or while testing:
bzrk -P secure search "default | take 10" --since "1h ago" \
--client-cert ~/.cache/certs/client.pem \
--client-key ~/.cache/certs/client-key.pem \
--ca-cert ~/.cache/certs/ca.pem \
--tls-server-name upstream.internal.example \
--header "x-routing-tag: region/prod"Flags win over the endpoint's stored values; --header is repeatable and merges over the endpoint's headers (the flag wins on a name clash).
Managing profiles
# List profiles
bzrk profile list
# Add a profile
bzrk profile add dev --endpoint https://berserk.dev.example.com
# Set the active profile (used when no -P is passed)
bzrk profile use gw-local
# Rename
bzrk profile rename dev-gw dev
# Remove
bzrk profile remove old-envAfter adding a profile, run bzrk login to authenticate against it (or set BZRK_BEARER_TOKEN for unattended use). See Authenticating below.
Resolution order
When you run bzrk, the CLI picks a profile by walking these in order:
- Explicit
--endpoint/--bearer-tokenflags override the resolved profile's URL / token. -P <name>— use that profile.BZRK_SESSIONenv var — same.- Otherwise the active profile (
default_session, set bybzrk profile use). - Default endpoint (
http://localhost:9500) if nothing matches.
Migration
If you previously had ~/.config/bzrk/profiles.toml, the first invocation auto-migrates it to config.toml and renames the original to profiles.toml.bak-<ISO>. The migration is idempotent — once config.toml exists, the legacy reader doesn't fire again.
Authenticating
The CLI talks to Berserk through the gateway service — the auth edge that owns OIDC, sessions, and bearer-token issuance. Every CLI invocation (except bzrk login itself) carries a bearer token; without one you'll see Run 'bzrk login' to authenticate. and exit.
bzrk login — RFC 8628 device-code flow
bzrk login signs in the active profile, so create one first. With no resolvable profile it stops with an error telling you to pass --profile <name> or run bzrk profile use <name> first; there is no zero-config login.
Sign in via the device-code flow:
bzrk login # signs in the active profile
bzrk -P dev login # or target a specific profileThe CLI does three things:
- POSTs to
/api/oauth/deviceon your gateway, getting back a short-liveddevice_code+user_code+verification_uri. - Prints the verification URL + user code on stderr so stdout stays clean for piping, and (on a TTY) tries to open the browser as a soft best-effort.
- Polls the gateway every few seconds until you approve in the browser (or until the device code expires — ~10 minutes).
$ bzrk -P dev login
To authenticate, open https://berserk.example.com/oauth/device and enter:
user_code: ABCD-WXYZ
Waiting for approval…
✓ Signed in as alice@example.com (profile: dev)On success the CLI persists (access_token, refresh_token, expires_at) into the active profile in ~/.config/bzrk/config.toml. Access tokens refresh automatically on every command; you only re-run bzrk login when the refresh token expires or you're switching identities.
Where the verification URL points
verification_uri is derived from the gateway's GATEWAY_PUBLIC_BASE_URL. If
you're running locally with port-forward, set that to http://127.0.0.1:9500
so the URL is reachable from your browser.
bzrk auth status — who am I
Prints the resolved identity from the gateway's GET /api/me:
bzrk auth status
# alice@example.com (user_id 019e41d1-…, session expires 2026-05-26T14:00:00Z)bzrk logout — revoke the token
Revokes the refresh token server-side and clears the access / refresh / expiry fields from the active profile. Idempotent.
bzrk logoutScripted / CI use
For non-interactive use (CI, scheduled jobs), set BZRK_BEARER_TOKEN directly — it overrides whatever's in the active profile:
BZRK_BEARER_TOKEN="…" bzrk search "default | take 10"Provision the bearer token via your secret manager. Service-account-style tokens (issued by a logged-in admin, scoped to a single workload) are tracked as a follow-up.