Sign the CLI in

Goal: saggar authenticated as you, for a human at a laptop or for a script on CI.

Prerequisite: an account. Identity is delegated. saggar.dev stores no passwords; your Gitea or GitHub provider owns the login. Sign up once through the web app; the CLI reuses that account.

The browser flow

$ saggar login

sign in to saggar on https://saggar.dev

open this URL in a browser:

  https://saggar.dev/device

then enter the code:  KXMJ-4QRT

waiting for confirmation… (Ctrl-C cancels; the request expires in 600 s)

signed in as @val on https://saggar.dev
connection stored in /home/val/.config/saggar/cli.toml

The CLI brokers a device login through the service: a provider’s redirect URIs are pre-registered, so a web flow cannot end on a localhost port of the CLI’s own. The CLI asks the service for an 8-character code, opens (or prints) the verification page, and long-polls until a signed-in visitor types the code there. On the default instance the page is https://saggar.dev/device. The page accepts the code with or without the dash, in any case. When approved, the CLI stores the minted token (sgt-…) and prints who it now acts as. Run saggar whoami to confirm.

The headless flow

Mint a token first, in the web app or on any machine that is already signed in:

$ saggar token create "ci-runner" --expires-days 90
token id:   t-5b249e203f7076f7
expires:    2026-12-27…
secret:     sgt-… (shown once — store it now)

Then hand it to login, which validates it against the server before storing anything:

$ saggar login --token sgt-…

Another instance

Both flows accept --server; without it, the default instance (https://api.saggar.dev) is used:

$ saggar login --server https://build.example.com:7020

The server you logged in to is stored alongside the token, so every later command hits the same instance.

Where credentials live

~/.config/saggar/cli.toml ($XDG_CONFIG_HOME honored; $SAGGAR_CONFIG points at an explicit file, which must exist):

server = "https://build.example.com:7020"
token = "sgt-…"

How a connection resolves, per invocation; the first non-empty of these wins:

  1. the flags (--server, --token),

  2. the environment (SAGGAR_SERVER, SAGGAR_TOKEN),

  3. cli.toml (written by saggar login),

  4. the default instance (https://api.saggar.dev); no token.

This makes CI trivial without touching the stored file:

$ SAGGAR_TOKEN=sgt-… SAGGAR_SERVER=https://build.example.com saggar submit …

Token hygiene

  • One token per machine; saggar token ls lists them, saggar token revoke t-… kills a lost one immediately.

  • Credentials that only ever pull from the registries (a docker config, apt auth.conf.d) should be pull-only: POST /api/v1/auth/tokens with {"scope": "registry"} (see The registries).

Troubleshooting

saggar whoami answers 401: the stored token expired or was revoked. Log in again.