Five subcommands and one top-level alias, covering the session the CLI holds and the two GitLab tokens your site checkout depends on. This page is flags and outputs; for what a session actually is, where it is stored and how it stays alive, read CLI authentication first.
leed auth login
leed auth login
leed auth login --env staging
leed auth login --email you@example.com| Flag | Type | Default | Required | Hidden from --help? | What it does |
|---|---|---|---|---|---|
-e, --env <name> | string | the nearest leed.config.json, else production | No | Yes | Which environment to sign in to: production, staging or dev |
--browser | boolean | true | No | No | Use the browser device-authorization flow. This is the default |
--email [email] | string | — | No | No | Switch to magic-link sign-in. Omit the address to be asked for it |
The default flow is the browser device authorization. The CLI prints a verification code, opens your browser at the approval screen, and polls until you confirm:
Environment: staging
Your verification code is: AB12-CD34
Opening browser for authorization...
Waiting for authorization...
Authenticated as Ada Lovelace (you@example.com) on staging
Credentials saved to /home/you/.config/leed/credentials.jsonEverything down to “Waiting for authorization…” is narration on stderr; the two closing lines are the command’s result on stdout. Credentials saved to … appears only for a non-production environment, where the path is worth knowing. If the platform’s browser opener is unavailable — a remote shell, a container — the URL is printed for you to open anywhere you are already signed in.
The browser screen you approve on is documented from the CMS side in authorizing devices and clients.
The magic-link mode does not sign you in
--email switches to a magic-link send, and that is all it does:
Check your email and click the sign-in link.
After clicking the link, run: leed auth login --browser --env productionNo session is stored. The email gets you signed in to the CMS in a browser; the CLI still needs the device flow afterwards to obtain its own session. This is the single most common misunderstanding of this command — plan on running leed auth login twice.
--email takes an optional value, so a bare --email prompts for the address rather than failing. With nobody at the terminal it refuses instead of blocking on a stdin that will never answer.
leed login
leed loginThe top-level alias for leed auth login. Identical flags, identical behavior, identical output. The only difference is the name it reports in its --json document: login rather than auth.login, so a consumer can match the command line it actually ran.
leed auth logout
leed auth logout
leed auth logout --env staging| Flag | Type | Default | Required | Hidden from --help? | What it does |
|---|---|---|---|---|---|
-e, --env <name> | string | the nearest leed.config.json, else production | No | Yes | Which environment to sign out of |
It asks the CMS to revoke the session, then deletes the stored credentials for that environment. The revoke is best effort and the deletion is not: a laptop that cannot reach the CMS still has to be able to drop its cached token, so a refusal or a network failure is reported as a warning and the credentials go anyway. When that happens the session may remain live on the server until it expires, and re-running the command from a connected machine will still revoke it.
With nothing stored it prints Not logged in to <env>. and reports success — the goal, no session on this machine, already held. Otherwise it prints Logged out of <env>.
It ends only that environment’s session. A machine signed in to both production and staging stays signed in to the other one.
leed auth status
leed auth statusNo flags of its own. What it reports depends on where you run it.
- Anywhere else
- Inside a site directory
Credentials: /home/you/.config/leed/credentials.json
✗ dev: you@example.com (expired Aug 3, 2026)
✓ staging: you@example.com (expires Sep 9, 2026)Every environment this machine holds a session for, one row each. The Credentials: path line appears only when a non-production environment is in play — that is exactly when knowing which file to look in matters.
With nothing stored at all, the whole output is:
Not authenticated. Run: leed auth login Credentials: /home/you/.config/leed/credentials.json
✓ staging: you@example.com (expires Sep 9, 2026)
GitLab tokens for example.com (staging):
✓ package registry: expires Nov 30, 2026
⚠ raw-content: expires in 4 days (Sep 6, 2026)The two extra rows are read live from the CMS, with a five-second timeout, and only appear here — the command has to be inside a site checkout to know which company to ask about. They are the tokens that let Bun install the CLI and let git talk to your raw-content repository, and they are the reason “logged in” and “can git fetch” are different states.
Symbols mean the same thing in both places, except that session rows never show a warning — a session is either live or it is not.
| Symbol | Meaning | Threshold |
|---|---|---|
✓ | Valid | More than 7 days remaining |
⚠ | Expiring soon — rotate | 7 days or fewer remaining (GitLab token rows only) |
✗ | Expired and already failing | Past its expiry |
? | Unknown | No expiry recorded, or one this CLI cannot parse |
The token lookup can end four other ways, each replacing both token rows with a single line:
| Line | What happened | What to run |
|---|---|---|
Session expired. Run: leed auth login --env <env> | The CMS rejected the stored session (401) | leed auth login --env <env> |
No developer access for this organization | The account is signed in but not permitted (403) | Ask an administrator — see billing and developer access |
Could not reach <cms url> (<detail>) | The CMS did not answer | Check connectivity, then retry |
(no tokens stored yet — run 'leed auth rotate' or rerun the installer) | Neither token has ever been issued | leed auth rotate |
A separate line, GitLab tokens for <domain> (<env>): not authenticated for this env, means the site targets an environment you hold no session for — sign in to that environment before the tokens can even be asked about.
leed auth rotate
leed auth rotate
leed auth rotate --yes| Flag | Type | Default | Required | Hidden from --help? | What it does |
|---|---|---|---|---|---|
-y, --yes | boolean | false | No | No | Confirm without being asked. Required for any unattended run — under --json, or with no interactive terminal |
Reissues this site’s two GitLab tokens and writes them where they are used: the @leed registry entry in ~/.bunfig.toml, and the origin URL of the raw-content checkout. Your CMS login session is not touched — you stay signed in, and nothing else changes.
It must be run from inside a site directory. Anywhere else it stops with “This command must be run from inside a leed-managed site directory.” and the hint to cd into the folder containing leed.config.json.
Because the previous tokens stop working the moment the new pair is issued, it asks first:
Rotate this site's GitLab tokens? The current tokens stop working, and ~/.bunfig.toml
and the raw-content .git/config are rewritten.A successful run prints a three-line summary:
✓ GitLab credentials rotated
Bunfig: /home/you/.bunfig.toml
Git repo: /home/you/leed/example.com/raw-content
Expires: 11/30/2026
Your CMS login session was not touched. No re-authentication needed.The bunfig write has three outcomes, and the summary line tells you which one you got. written — the fresh token was stored, and the path is printed plain. unchanged — the file already held exactly this token. skipped-env-var — your @leed entry points at an environment variable you manage yourself, so it was deliberately left alone; the path is annotated (skipped — uses environment variable) and followed by:
Your bunfig references an env var, so we left it alone. The rotated
registry token is stored on the backend; if your env var points at a
personal access token you manage yourself, no action needed.Two failures are worth recognizing. A 401 is “Session expired or invalid. Run: leed auth login --envleed site init, which is the supported repair — re-running leed site init re-points the origin URL with a fresh token.
Rotation is scoped to one site. If you develop several, run it in each.
leed auth reset
leed auth reset
leed auth reset --all-sessions| Flag | Type | Default | Required | Hidden from --help? | What it does |
|---|---|---|---|---|---|
--all-sessions | boolean | off | No | No | Also revoke your sessions on other devices, rather than this one alone |
-y, --yes | boolean | false | No | No | Confirm without being asked. Required for any unattended run |
The order of operations is fixed, and it explains the failure modes:
- Confirm — before the site context, the credentials or the network, so a refusal leaves everything exactly as it found it.
- Rotate the GitLab tokens, while the session is still valid. Revoking first would make this step fail with a 401.
- Write the results — the bunfig entry, then the raw-content
originURL. - Revoke and sign out — the other devices’ sessions when
--all-sessionswas given, then this one — and delete the local credentials regardless of whether the server was reachable.
A remote that could not be rewritten in step 3 does not abort step 4. You asked for your credentials to stop working, and that has to happen even when a local file could not be updated. The run still succeeds, with a warning naming the .git/config that kept the stale token and telling you to re-run leed site init or fix the origin URL by hand.
✓ Credentials reset
Bunfig: /home/you/.bunfig.toml
Git repo: /home/you/leed/example.com/raw-content
CMS login: revoked (all devices)
You will need to re-authenticate. Run: leed auth login --env stagingRevoking other sessions is the same operation the CMS offers on sessions, connected accounts and API tokens.
Choosing between rotate and reset
leed auth rotate | leed auth reset | |
|---|---|---|
GitLab registry token (~/.bunfig.toml) | Reissued | Reissued |
GitLab raw-content token (.git/config) | Reissued | Reissued |
| CMS login session | Untouched | Revoked |
| Sessions on other devices | Untouched | Revoked with --all-sessions |
| Local credentials file | Untouched | Deleted for that environment |
| Re-login required afterwards | No | Yes |
Rotate for hygiene, for a token leed auth status shows expiring, and when git fetch or a CLI update starts failing with an authorization error. Reset for a lost or compromised machine, or a credential you believe has leaked.
JSON output
The five commands report as auth.login, auth.logout, auth.status, auth.rotate and auth.reset; the alias reports as login. Envelope, warnings and the full code list are on machine-readable output.
{
"environment": "staging",
"customer": "example-com",
"rawContentRepo": "/home/you/leed/example.com/raw-content",
"bunfig": { "path": "/home/you/.bunfig.toml", "status": "written" },
"tokensExpireAt": "2026-11-30T00:00:00.000Z",
"tokensExpireAtLabel": "11/30/2026",
"cmsSessionPreserved": true
}These commands can produce not_authenticated, auth_expired, permission_denied, not_a_site_repo, network_error and external_command_failed. login refuses under --json with interactive_input_required; rotate and reset refuse with confirmation_required unless --yes is given. auth status never fails for a state it can describe — a rejected session, a missing developer override and an unreachable CMS are all successful status reports carrying the reason.
Notes worth carrying
The credentials file holds a refreshToken field that is always empty and reserved. It is not a working refresh token; the CLI refreshes by round-tripping its access token instead.
The raw-content origin URL that rotate, reset and init write embeds the token in plain text — https://access-token:<token>@gitlab.com/…. Copying a site folder to a shared machine copies a live credential with it.
leed auth is one of four command groups; the whole tree with its global options is at CLI command reference. Both files these commands rewrite are cataloged on leed.config.json, files and environment, and a token that has quietly expired shows up first as an authentication failure in the git workflow.