A local CLI with environment detection, secret management and
webhook forwarding. It talks to the same gateway API the SDK does; there is
no second send surface (there is deliberately no nervly send).
nervly doctor # environment detection diagnostics
nervly env # print the resolved environment (--json)
nervly secrets … # local, SOPS-compatible secret store
nervly forward webhooks … # relay delivery webhooks to a local receiver
nervly login / logout # console-session auth for management commands
nervly config show/set # inspect/override the active profile
npm install -g @nervly/cli # prebuilt platform binary
cargo install nervly-cli # from source
brew install igmrrf/taps/nervly # HomebrewPrebuilt binaries for aarch64/x86_64 × darwin/linux and x86_64 windows are published on GitHub Releases with signed checksums; shell
completions and a man page ship inside every artifact.
Before every command the CLI resolves which environment it points at. First hit wins, and conflicts are loud, never silent:
| Step | Environment |
|---|---|
| 1 | --env live|test|local |
| 2 | NERVLY_ENV |
| 3 | the API key's mode segment (nervly_sk_test_… ⇒ test) |
| 4 | the base-URL shape (localhost/127.0.0.1 ⇒ local) |
| 5 | the active profile, then an interactive prompt |
Base URL: --base-url → the profile override → the environment default
(local ⇒ http://localhost:8080, live ⇒ https://api.nervly.io,
test ⇒ https://sandbox-api.nervly.io). A test resolution never falls
back to the production base — if the sandbox base is unreachable the command
fails loudly.
Credentials: --api-key → NERVLY_API_KEY → a .env file that defines
NERVLY_API_KEY → none. A key whose mode disagrees with the resolved
environment fails the command unless --force is given.
nervly secrets manages your local secrets in .nervly/secrets.enc.yaml — a
bit-for-bit SOPS-compatible age-encrypted YAML file. Nervly never sees them.
The real sops binary decrypts what the CLI writes, and the CLI decrypts what
sops -e writes.
nervly secrets set DB_PASSWORD # value read from stdin, never argv
nervly secrets get DB_PASSWORD # redacted; --reveal to print
nervly secrets list
nervly secrets remove DB_PASSWORD
nervly secrets import secrets/prod.enc.yaml
nervly secrets encrypt plain.yaml # → plain.enc.yaml
nervly secrets decrypt secrets/prod.enc.yaml
nervly secrets run -- your-command # store decrypted into the child envValues are never printed unredacted, never logged, never emitted in --json,
and the store is 0600 after every write. The age identity is found the same
way sops finds it (SOPS_AGE_KEY, SOPS_AGE_KEY_FILE, or
~/.config/sops/age/keys.txt).
nervly forward webhooks --to http://localhost:3000/webhooks/nervlyThe CLI dials out to the Nervly relay (GET /v1/relay/tunnel), receives
real recorded delivery events down the SSE tunnel, re-signs each body locally
with HMAC-SHA256 (x-nervly-signature, verified by the SDK's
nervly.webhooks.verifySignature) using a secret drawn from the CLI store, and
posts it to your receiver. It never synthesizes a delivery: if the relay is
unreachable it delivers nothing and exits non-zero. The tunnel is
workspace-scoped and at-least-once.
nervly login runs the device-code flow, shows a code and a browser URL, and
stores the resulting session token in the OS keychain (never the profile
file, never the secret store). Management commands (e.g. nervly config show)
accept the session; data-plane calls never do — a console session cannot
authorize the relay tunnel or any /v1 call.
make dev # cargo build + macOS dev-sign + run (ARGS=... to pass flags)
make dev-sign # sign target/debug/nervly with the stable macOS identity
cargo test # unit + integration; requires `sops` and `age-keygen`
cargo clippy --all-targets -- -D warningsThe integration suites drive the built binary and a real sops/age
interop — no simulated crypto. The contract-drift test reads the sibling
nervly-docs OpenAPI spec and the nervly-gate key parser.
On macOS, cargo build ad-hoc signs the binary, so its code identity changes
on every rebuild and the OS re-prompts for the keychain session nervly login
stored. make dev / make dev-sign signs the debug binary with one persistent
self-signed nervly-dev identity instead (created on first use in its own
~/Library/Keychains/nervly-dev keychain, via scripts/dev-sign.sh; the first
run asks macOS once to trust the certificate), so rebuilds read the session
silently. The identity is local-only and is never used for release artifacts.