The LeanSignal command-line client.
leanctl authenticates with a personal access token and acts with your own
identity and role — what you can do in the web app is what you can do here, and
nothing more.
$ leanctl auth login --tenant acme
Resolving tenant "acme"…
Personal access token: ****
Logged in to https://acme-api.eu11.leansignal.io as you@example.com (admin)
$ leanctl demand list
NAME DESCRIPTION CREATED BY AGE
host-metrics node health and disk you@example.com 12d
kubernetes cluster + workloads you@example.com 5d
$ leanctl demand export host-metrics > demands/host-metrics.jsoncurl -fsSL https://raw.githubusercontent.com/LeanSignal/leansignal-cli/main/scripts/install.sh | shDetects your OS and architecture, downloads the matching release, verifies its
checksum, and installs to /usr/local/bin — or ~/.local/bin when that is not
writable, so it never demands sudo. Options: --version vX.Y.Z,
--bin-dir DIR, --no-verify. Review the script before piping it to a shell.
Releases carry leanctl_<version>_<os>_<arch>.tar.gz for darwin and linux,
amd64 and arm64.
# macOS (Apple Silicon: arm64; Intel: amd64) — check with `uname -m`
curl -fsSLO https://github.com/LeanSignal/leansignal-cli/releases/download/v0.8.1/leanctl_0.8.1_darwin_arm64.tar.gz
# Linux
curl -fsSLO https://github.com/LeanSignal/leansignal-cli/releases/download/v0.8.1/leanctl_0.8.1_linux_amd64.tar.gz
tar -xzf leanctl_*.tar.gz
sudo install -m 0755 leanctl /usr/local/bin/leanctl
leanctl versionWithout sudo, put it anywhere on your PATH instead:
install -m 0755 leanctl ~/.local/bin/leanctl.
macOS: the binaries are not notarized, so a build fetched through a browser
is quarantined and Gatekeeper will refuse it. curl does not set that
flag; if you did download it another way, clear it with
xattr -d com.apple.quarantine leanctl.
Verifying the download (optional). Checksums are signed with cosign keylessly, so no key is needed:
base=https://github.com/LeanSignal/leansignal-cli/releases/download/v0.8.1
curl -fsSLO $base/checksums.txt -O $base/checksums.txt.sig -O $base/checksums.txt.pem
shasum -a 256 -c checksums.txt --ignore-missing # `sha256sum -c` on Linux
cosign verify-blob checksums.txt \
--signature checksums.txt.sig --certificate checksums.txt.pem \
--certificate-identity-regexp 'https://github.com/LeanSignal/leansignal-cli/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comNeeds Go 1.25+.
git clone https://github.com/LeanSignal/leansignal-cli.git
cd leansignal-cli
make install # -> $GOBIN, or $(go env GOPATH)/bin
leanctl versionmake install does not touch your PATH. If leanctl: command not found
follows, add Go's bin directory:
echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.zshrc # ~/.bashrc on Linuxmake build instead drops the binary in ./bin/leanctl without installing.
# zsh (macOS default)
leanctl completion zsh > "${fpath[1]}/_leanctl"
# bash — Linux
leanctl completion bash | sudo tee /etc/bash_completion.d/leanctl >/dev/null
# bash — macOS with Homebrew's bash-completion@2
leanctl completion bash > "$(brew --prefix)/etc/bash_completion.d/leanctl"
# fish
leanctl completion fish > ~/.config/fish/completions/leanctl.fishStart a new shell afterwards.
Repeat the install; it overwrites in place. To remove, delete the binary, the
completion file, and ~/.config/leanctl/ (which holds your token — revoke it
first with leanctl auth logout --revoke).
- In the web app, go to Preferences → Access tokens and mint a token.
Scopes are
read(always granted),write(create and update), andwrite:delete(delete; implieswrite). Write scopes need the editor or admin role. leanctl auth login --tenant <your-tenant>and paste it.
You name the tenant; the regional endpoint is looked up for you. Tenant APIs
live in regions (acme-api.eu11.leansignal.io) and tenants can move between
them, so leanctl treats the endpoint as a cache, never a fact: login resolves
it through LeanSignal's region-less front door, and if the tenant later moves,
the first command that finds the old region gone re-resolves, retries, and
rewrites the cache — no re-login. (Automatic retry is limited to what is
provably safe: any request whose connection was refused, and idempotent reads
that timed out. A leanctl profile refresh forces it by hand.)
--api-url pins an endpoint instead — for local development or air-gapped
setups — and opts that profile out of resolution.
The token is verified against /auth/me before anything is written to disk, and
stored at ~/.config/leanctl/config.yaml with mode 0600. The resolve lookup
is the only control-center contact leanctl ever makes (a test enforces
it); everything else talks to your tenant's API alone.
In CI, skip login entirely — a pinned endpoint keeps CI free of the resolver:
export LEANCTL_TOKEN=lsp_…
export LEANCTL_API_URL=https://<tenant>-api.eu11.leansignal.io
leanctl demand import --file demands/host-metrics.json --dry-runHaving several tenants is normal. Each login saves its own profile (the
command also answers to context, kubectl-style — same thing):
leanctl auth login --tenant acme # saves profile "acme"
leanctl auth login --tenant globex # and "globex"
leanctl profile list
leanctl profile use globex # switch the default
leanctl demand list --profile acme # or override per command
leanctl profile refresh acme # re-resolve its endpoint by handLEANCTL_PROFILE selects one from the environment. Each profile carries its own
token and its own cached endpoint; a region move rewrites only the profile it
belongs to. Pass --name at login to call a profile something other than the
tenant slug.
LeanSignal stores only demanded telemetry. Every query command therefore has two sides:
| default | --available |
|
|---|---|---|
| Reads | the central store | the connected agent's local store |
| Holds | only demanded data | everything the agent collects |
| Retention | 30 days | ~1d metrics, ~1h logs and traces |
| Answers | "what are we keeping?" | "what could we demand?" |
An empty result from the central store is usually a demand gap, not an outage,
so leanctl says so and hands you the next command:
$ leanctl logs query '{job="nginx"}'
No stored logs matched.
The central store holds only demanded telemetry, so this may be a demand gap
rather than an absence of data. Check both sides:
leanctl filter list --type log
leanctl logs query '{job="nginx"}' --available| Command | Does |
|---|---|
auth |
login, logout, status, tokens list|create|revoke |
context |
list, use, delete, current |
demand |
list, get, create, update, delete, export, import |
dashboard |
list, get, apply, delete, versions |
agent |
list, get, create, update, delete, diagnose, config get|apply |
alert |
list, get, create, update, delete, pause, resume, mute, unmute, test |
channel |
list, get, create, update, delete, test |
synthetic |
list, get, create, update, delete, pause, resume, test, results |
rule |
custom rules: list, get, create, update, delete, enable, disable |
filter |
demand set: list, purged, sync, sweep |
metrics |
names, query, query-range, series, labels, label-values |
logs |
query, labels, label-values, stats |
traces |
search, get, tags |
status |
what this tenant is storing |
audit |
list (admin) |
settings |
get, set (admin) |
search |
find anything by name |
User management, invitations, and support cases are not here, by design.
Those live in control-center and need a real browser session, which a token does
not carry — so leanctl has no commands for them rather than commands that
cannot work. Use the web app.
Run leanctl <command> --help for flags and examples.
leanctl demand list # aligned plain text, headers on
leanctl demand list -o json | jq . # the server's own JSON, unreshaped
leanctl demand list -o yaml
leanctl demand list -o wide # extra columns, including ids
leanctl demand list -o name | xargs # bare ids, for scripting
leanctl demand list --no-headersTables are space-padded text — never box-drawing — so awk and cut keep
working. Empty results write their explanation to stderr, leaving stdout
empty for pipelines. Colour turns itself off when stdout is not a terminal or
NO_COLOR is set.
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | generic failure |
| 2 | usage error (bad flags or arguments) |
| 3 | authentication or authorization (401/403, or no credential configured) |
| 4 | not found (404) |
| 5 | validation (422) |
| 6 | server error (5xx) |
| 7 | the API could not be reached |
agent config reaches the collector configuration on the agent host itself,
over the agent's control stream — admin role, connected agent:
leanctl agent config get my-agent # what sources exist
leanctl agent config get my-agent --path /etc/leansignal-agent/config.yaml > c.yaml
$EDITOR c.yaml
leanctl agent config apply my-agent --file c.yamlThe agent validates the write — YAML parse plus a full collector dry-run of
the merged config — and applies it only if it passes: previous contents kept as
<path>.bak, atomic write, then reload. A rejected config changes nothing on the
host and exits 5 with the validator's own complaint. Values stay unresolved, so
${env:...} references are never expanded into the output.
Export and import round-trip a whole demand — dashboards and alert rules included, UUIDs stripped, channels referenced by name — which makes a demand a reviewable artifact:
leanctl demand export host-metrics > demands/host-metrics.json # commit it
leanctl demand import --file demands/host-metrics.json --dry-run # PR check
leanctl demand import --file demands/host-metrics.json # on merge--dry-run validates and reports without writing. Missing notification channels
are reported as warnings, not errors, and any rule left without a channel is
imported paused.
Precedence: flags → environment → config file.
| Variable | Effect |
|---|---|
LEANCTL_TOKEN |
personal access token |
LEANCTL_API_URL |
tenant API origin (pins; skips resolution) |
LEANCTL_CONTEXT / LEANCTL_PROFILE |
profile to use |
LEANCTL_CC_URL |
resolve front door (default https://cc.leansignal.io) |
LEANCTL_RESOLVE_TOKEN |
public resolve token override |
LEANCTL_OUTPUT |
default output format |
LEANCTL_CONFIG |
config file path |
NO_COLOR |
disable colour |
- The token travels in an
Authorizationheader, never in a URL. - There is no
--tokenflag. A command line is visible to every process on the host and lands in shell history; use the login prompt,--token-stdin, orLEANCTL_TOKEN. - The config file is written
0600inside a0700directory, atomically.leanctlrefuses to read a config file others can read. - Sending a token over plain HTTP to a non-loopback host is refused outright.
- TLS 1.2 is the floor. There is no flag to skip certificate verification.
leanctl auth logout --revokerevokes the token server-side as well as removing it locally.- Two hosts, each with one job: your tenant's API for everything, and the region-less front door for exactly one endpoint — tenant resolution at login and after a region move. No telemetry, no update check.
- Destructive commands prompt, and refuse to run unattended without
--yes.
make build # ./bin/leanctl
make test # go test ./...
make check # fmt + vet + lint
make ci # everything CI runs
make snapshot # build release artifacts locallyReleases are cut by pushing a v* tag; goreleaser builds the matrix and cosign
signs the checksums.