CLI Getting Started
The snug command mirrors the HTTP API service for service. It is built
from the same crate as the server, so a build only exposes the services its
features enable - snug help shows exactly what your binary includes.
cargo run --bin snug --features full -- help
# or build once and put it on PATH
cargo build --release --bin snug --features fullDiscover commands instead of memorizing them:
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
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
Learn this once and every service scripts the same way. Each shape below was captured from a real run.
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 --extend 3600
fi
# JSON pipeline - no .data wrapper
snug --output json kv list --tags 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}'One cross-cutting gotcha: when a command takes an optional --id, it
defaults to the token's sub claim - convenient, but surprising when
switching tokens mid-script.