Skip to content

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 full

Use 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 examples

Authentication

# 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 api

Token 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

FlagDefaultDescription
-u, --api-url$SNUG_API_URL or http://localhost:4000API base URL
-t, --token$SNUG_API_TOKENJWT or snug_key_... API key
--outputtabletable, json, or compact
-q, --quietoffSuppress non-error output
-v, --verboseoffRequest/response details, on stderr
--timeout30Request 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: 1

A 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.