The saggar CLI

saggar (the saggar-cli crate) is the client: it drives saggar.dev over its REST API. One page, every command.

$ saggar <command> [flags]

Connection settings

Every subcommand takes the same two connection flags:

--server <url>

Base URL of the instance to talk to. Resolution order: this flag, then SAGGAR_SERVER, then server in the client config file, then the default instance https://api.saggar.dev.

--token <secret>

API token. Resolution order: this flag, then SAGGAR_TOKEN, then token in the client config file, then none; a command that needs auth then fails with 401.

The client config file is ~/.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-…"

Unknown keys in the file are refused: a typo must not silently run against the wrong server. saggar login (see Sign the CLI in) writes this file for you.

Exit behavior: 0 on success, 1 on any error, including a failed build followed with submit, and any failed job of a group.

saggar submit

$ saggar submit <source> [flags]

Submit a source and follow the build.

<source>

A git URL (https://, http://, ssh://, git:// or the user@host:path spelling; remote URLs only), a local directory, or a path to a tarball. Local trees travel as uploads; the service never reads your filesystem.

Flags:

--format <format>

Explicit format (deb, docker, native, …); auto-detected from the tree by default.

--name <name>

Project name override (derived from the tree when absent).

--project <slug>|<owner/slug>

Which project to submit into: a slug you own (created on first submit) or owner/slug for someone else’s (builder standing required). Default: your personal project.

--arch <arch>

Target architecture (amd64, arm64, …). Without it the job names no arch; the queue prefers a runner whose arch matches.

--arches <list>

Arch fan-out (comma list, e.g. amd64,riscv64): with a formats selector the group runs one job per format × arch. Mutually exclusive with --arch.

--env <ref>

Build environment: an OCI image ref native/node builds run in, or a fingerprint (debian:12@amd64/glibc-2.36) the build machine resolves to an image.

--ephemeral

Ephemeral build: the artifact is excluded from the registries and collected after the instance’s ephemeral TTL (24 h by default). For check-builds (agents, quick verification) that must not pollute the store.

--test

Run the tree’s own test suite instead of building (see Run a project’s tests).

--test-cmd "<command>"

Explicit test command; implies --test and overrides the autodetected suite.

--formats <auto|list>

Formats selector (see Fan out: formats and architectures): auto fans out to every claimant, or a comma list (deb,docker) pins the set. The response carries a group id; the whole group is followed and the command fails if any job does.

--no-reuse-build

Disable the build-once hand-off: every job of the group gets its own fresh tree again.

--cache <mode>

Cross-job reuse: incremental keeps the build tree between submissions (only what changed rebuilds); artifact reuses the stored artifact of an identical previous build (instant green). Default: fresh. A memo lookup never hits on runner-backed builds; incremental is the one that pays there.

--incremental

Shorthand for --cache incremental.

--cached

Shorthand for --cache artifact.

--no-wait

Do not follow the build; exit right after submission.

--quiet

Print only the job (or group) id, machine-readable; implies no waiting.

While following, the CLI streams the log and prints the verdict. On success it names the artifact route (saggar download, the registries); on failure it prints the outcome and the failure class (infra-retryable, infra-permanent).

saggar download

$ saggar download <reference> [flags]

Download the artifacts of a job (j-…) or artifact (a-…).

<reference>

A job id (downloads the job’s artifact) or an artifact id (downloads every file of that artifact).

-o, --out <dir>

Output directory. Default: the current directory.

--extract

Extract .tar.gz artifacts into <out> instead of saving them, preserving executable bits (entries that would escape <out> are refused).

--arch <arch>

Target arch to fetch. Defaults to this machine’s arch: job downloads give you what runs here. Arch-independent (all) artifacts always come along. Explicit artifact ids are never filtered.

--group <group-id>

Fetch every job of a fan-out group instead of one reference (mutually exclusive with a reference). A group member that produced no artifact (build-check success) is reported and skipped; any real failure exits 1.

A job that has not succeeded is an error naming its state and outcome.

saggar logs

$ saggar logs <job-id>

Print a job’s full log as plain text. For live streams, the SSE endpoint GET /api/v1/jobs/:id/logs is the interface (see The management API); submit’s follow loop already shows the log as the build runs.

saggar cancel

$ saggar cancel <job-id>

Cancel a job (queued or running). A queued job is cancelled outright; a running build stops at the next command boundary, and host-executed children are killed immediately. The job ends cancelled: terminal, log kept, no failure metadata.

saggar login

$ saggar login [--server <url>] [--token <sgt-…>]

Sign the CLI in and store the credentials (see Sign the CLI in). Two modes:

  • Browser (default): starts a device login. The CLI prints and opens the instance’s verification page (https://saggar.dev/device on the default instance) where the code is typed, then long-polls until the code is confirmed and stores the minted token. The typed code is normalized server-side: case, dashes and spaces do not matter.

  • Headless (--token): validates the token against the server (GET /auth/me must answer) and stores it in ~/.config/saggar/cli.toml. A rejected token writes nothing.

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

saggar whoami

$ saggar whoami

Show who the current token authenticates as: handle, name, email, provider, storage usage against the account’s artifact quota, and the projects the token can see with their roles. The first command to run after wiring a fresh token into the config.

saggar token

Manage your API tokens, the long-lived secrets the CLI and CI hold. Mint one per machine; revoke it when the machine dies.

$ saggar token create <name> [--expires-days <days>]
$ saggar token ls
$ saggar token revoke <id>
token create

Mint a new token; the secret (sgt-…) is shown exactly once. --expires-days sets an expiry; omit it for a non-expiring token. Minting needs an existing credential: bootstrap happens through saggar login or the web app, never through the CLI alone.

token ls

List your tokens; secrets are never shown again.

token revoke <id>

Revoke the token with the listing’s id (t-…): it stops authenticating immediately.

saggar projects

Manage your projects and their git remotes.

$ saggar projects ls
$ saggar projects create <slug>
$ saggar projects remotes <project>
$ saggar projects remote add <project> <url>
$ saggar projects remote remove <project> <url>
ls

The projects you own or are a member of, with your role.

create <slug>

Create a project you own. The slug is lowercase letters, digits and dashes.

remotes <project>

List the project’s remotes: its git mirrors, the URLs git submissions derive their project from.

remote add <project> <url>

Register one remote (owner; idempotent). The same URL on two forges is one project with two remotes. Registering a watch adds its URL as a remote too; a remote in use by a watch cannot be removed.

remote remove <project> <url>

Forget one remote (owner; refused while a watch of the project polls that URL).

<project> is owner/slug, or a bare slug of your own.

saggar watch

Register a repo once and every push builds itself (see Set up CI: watched repos, webhooks, statuses).

$ saggar watch add <url> [flags] [--disabled]
$ saggar watch ls
$ saggar watch show <id>
$ saggar watch edit <id> [flags]
$ saggar watch rm <id>
$ saggar watch trigger <id>
add <url> [flags]

Register a repo: every push to a matching branch or tag builds with the given defaults (and runs the tree’s tests, by default). One watch per repo. --disabled registers dormant: nothing triggers until it is enabled.

ls

The watches you can see: id, URL, project, state.

show <id>

One watch in full: patterns, settings, interval, enabled, last poll.

edit <id> [flags]

Edit a watch: only the flags you pass change.

rm <id>

Remove a watch. Its build history stays; re-registering the repo starts from a clean slate and re-triggers the current head.

trigger <id>

Poll right now, whatever the interval says; also how you test a fresh registration. Prints what triggered (nothing new → exit 0 with a “no changes” line).

The flags shared by add and edit:

--branch <glob>

Branch glob pattern, repeatable: main, release/*, or a full refs/heads/... form. Default: every branch.

--tag <glob>

Tag glob pattern, repeatable: v*. Default: no tags.

--formats <auto|list>

Formats selector for triggered builds. Default: the single-winner build.

--arch <list>

Comma list of target arches (amd64,arm64): one build per arch. Default: no arch named; the queue prefers a runner whose arch matches.

--env <ref>

Build environment for triggered builds.

--cache <mode>

Cross-job reuse for triggered builds: incremental keeps the build tree between pushes. Default: fresh. (artifact is refused: memoization does not compose with groups.)

--ephemeral / --no-ephemeral

Triggered artifacts are ephemeral / kept permanent.

--test / --no-test

Tests only (pushes run the suite, no artifact builds) / builds only (undoes the build+test default).

--test-cmd "<command>"

Run this command as the test suite instead of the autodetected one (implies the test member on pushes).

--interval <secs>

Poll interval override; five minutes otherwise.

--project <slug>|<owner/slug>

Which project the watch belongs to and its builds are attributed to. Default: your personal project.

On add, an absent flag means the server default; on edit, absent means unchanged. There is no credential flag: polling authenticates through the host’s git/ssh configuration; the forge credential for commit-status reporting is set through the API (see The management API).

saggar node

Manage the runners of your account: the roster, and retirement.

$ saggar node ls
$ saggar node retire <node-id>
ls

The roster: the runners you own or are shared with, each row carrying node id, arch, engine version, state, declared conduct profile (network, docker capability) and owner.

retire <node-id>

Retire a runner you own: its token stops authenticating immediately. The row stays listed retired for the record. Not idempotent: an unknown or already-retired node is an error, not a silent no-op. Non-owners are refused.

Sharing a runner with another account is a management-API call (PUT /api/v1/nodes/:id/shares, see The runner protocol) or a flip in the web app; the roster shows the sharees’ runners beside your own.