CLI Getting Started
The snug CLI provides a command for every API endpoint. It is built from
the same crate as the server, so the enabled features determine which
services are available. Run snug help to list the commands in your build.
cargo run --bin snug --features full -- help
# or build once and put it on PATH
cargo build --release --bin snug --features fullUse help to list services, commands, and options:
snug help # all services
snug kv help # commands for one service
snug kv set --help # flags and curated examplesAuthentication
# Sign up (or log in) - stores credentials in ~/.snug/credentials.json
# and refreshes the access token automatically
snug auth signup --email you@example.com --password '...'
# Or mint a token locally against your own server's keypair; PUBLIC_API_URL supplies
# the issuer, the same way it does for the server
export PUBLIC_API_URL=http://localhost:4000
export SNUG_API_TOKEN=$(snug jwt generate user -u dev -t <tenant> --format compact)
# Verify connectivity
snug health apiToken resolution order: --token flag, then SNUG_API_TOKEN, then stored
credentials. health, metrics, the jwt subcommands, and the public
auth flows work without a token; everything else requires one. See
Authentication for tenants and scopes.
Global flags
| Flag | Default | Description |
|---|---|---|
-u, --api-url | $SNUG_API_URL or http://localhost:4000 | API base URL |
-t, --token | $SNUG_API_TOKEN | JWT or snug_key_... API key |
--output | table | table, json, or compact |
-q, --quiet | off | Suppress non-error output |
-v, --verbose | off | Request/response details, on stderr |
--timeout | 30 | Request timeout in seconds |
Global flags go on snug, not the subcommand:
snug --output json kv get mykey - putting --output after the subcommand
fails with "unexpected argument". (Inside jwt generate, -u/-t mean
subject/tenant; the global meanings apply everywhere else.)
The output contract
All service commands use the following output and exit-code conventions.
Streams. Structured payloads go to stdout; progress, warnings, errors,
and --verbose traces go to stderr, so jq pipelines stay clean. When
stdout is piped, the CLI also suppresses its cosmetic blank-line padding -
captured output is exactly the payload.
Exit codes. 0 on success, non-zero on any failure, independent of
output format. Branch on the exit code, never by grepping output.
JSON mode prints the bare payload - the server's envelope is unwrapped,
so address fields directly (jq '.key', not jq '.data.key'):
snug --output json kv get docs/hello{
"key": "docs/hello",
"value": { "greeting": "hello world" },
"content_type": "application/json",
"updated_at": "2026-08-28T03:41:02.485104Z"
}Failures still print parseable JSON on stdout (plus a human message on stderr, plus a non-zero exit):
snug --output json kv get docs/missing; echo "exit: $?"{
"error": {
"status": 404,
"body": {
"status": 404,
"msg": "Key not found: docs/missing",
"error": "key_not_found"
}
}
}exit: 1A failure before any request is made emits
{"error": {"message": "..."}} instead.
Destructive operations need --force in scripts
Deletes prompt for confirmation on a TTY. In non-interactive mode
(--output json or --quiet) they refuse to prompt and exit non-zero:
{
"error": "confirmation_required",
"resource": "key",
"name": "docs/hello",
"hint": "pass --force to confirm destructive delete in non-interactive mode"
}Always pass --force explicitly in scripts. Note that REST deletes are
idempotent - deleting something already gone succeeds - so gate on a
head/get first when "must have existed" matters.
Scripting patterns
# Existence-gated work: head exits non-zero on a missing key
if snug kv head cache/token/xyz >/dev/null 2>&1; then
snug kv ttl cache/token/xyz --set 3600
fi
# JSON pipeline - no .data wrapper
snug --output json kv list --filter 'tags[eq]=ui' | jq -r '.keys[].key'
# Multi-identity testing: --token overrides for one command only
TOKEN_B=$(snug jwt generate user -u bob -t <tenant> --format compact)
snug --token "$TOKEN_B" kv set bob/data '{"x":2}'When a command takes an optional --id, it defaults to the token’s sub
claim. Set --id explicitly if you switch tokens but want to keep using the
same subject.
