> For the complete documentation index, see [llms.txt](/llms.txt)

# CLI reference

The Glotto CLI is invoked as `glotto <command>`. Run `glotto` with no arguments to print the
command list. Every command exits `0` on success, `1` on a run failure, and `2` on a usage or load
error (an unknown flag, a missing `glotto.yml`).

**This page documents the CLI you can install, and only that.** Every command below is in the
published `@glotto/cli`; there are no others, and nothing here requires a plan, a flag, or an
invitation to unlock. Glotto also delivers most of these capabilities **without** your running
anything — keeping your committed SDKs verified, detecting drift, publishing to registries — on
your pull request and your dashboard; those routes are described by what they produce and where you
collect it, under [What Glotto also runs for you](#what-glotto-also-runs-for-you) below.

Most commands discover the config automatically: with no `--config`, Glotto looks for
`.glotto/workspace.json`, then `glotto.yml`, then `glotto.yaml` — first in the current directory,
then in each parent directory up to the filesystem root. So commands work from anywhere inside your
project, not only from the directory holding the config. See [Getting started](/docs/getting-started)
for the end-to-end workflow and the [glotto.yml reference](/docs/glotto-yml) for the configuration
these commands consume.

## The workspace file

A `.glotto/workspace.json` declares which project a directory belongs to. You only need one when a
single repository holds **more than one** `glotto.yml`, or when you want an SDK written somewhere
other than `sdks/<language>/`. A repository with one config at its root needs no workspace file —
the upward walk already finds that config from any subdirectory, which is why `glotto init` does not
write one.

```json
{
  "config": "api/glotto.yml",
  "targets": {
    "typescript": "clients/typescript",
    "python": "clients/python"
  }
}
```

- **`config`** (required) — path to the `glotto.yml` this workspace binds to.
- **`targets`** (optional) — where each language's SDK is written. A language you leave out is
  written to `sdks/<language>/` as usual. Drift detection reads the same map, so it compares each
  language against the directory declared here — there is no second root to keep in step with the
  layout.

Both are relative to the workspace root — the directory containing `.glotto/` — and must stay inside
it. Absolute paths and `..` escapes are rejected. [`glotto workspace`](#glotto-workspace) writes,
checks, and explains the file: `glotto workspace show` prints the binding for the directory you are
standing in, `glotto workspace validate` checks the file on its own, and `glotto workspace init`
writes one with the `config` path filled in for you.

Two keys you might expect are deliberately **not** accepted, because `glotto.yml` already owns those
values and a second copy would drift out of step: `project` (that is `hosted.project`) and
`openapi_spec` (that is `openapi.source`, resolved relative to the config). Setting either one
reports [`GLOTTO_WORKSPACE_KEY_OWNED_ELSEWHERE`](/docs/diagnostics-fatal#the-workspace-file) naming where
the value belongs.

**Nearest declaration wins.** In a monorepo, give each project its own workspace file (or just its
own `glotto.yml`): a command run inside `services/billing/` binds to the nearest declaration above
it, not to the repository root. Where output lands follows the same rule — with a workspace file in
effect, a relative output path is resolved against the **workspace root** rather than wherever you
happened to run the command, so `glotto generate` writes to the same place from every directory.
An explicit `--out` is always resolved against the current directory.

## How you get each command

The published `@glotto/cli` on npm is a **thin client**: code generation runs on Glotto's
infrastructure, so the codegen engines never ship in the installed binary. Sixteen commands are in
it — `login`, `logout`, `init`, `migrate`, `schema`, `workspace`, `generate`, `mcp`,
`breaking-changes`, `verify`, `verify-compile`, `verify-contract`, `verify-upload`,
`verify-attestation`, `publish`, and `code-owners`.

That list is complete. `glotto <anything-else>` exits `2` with `unknown command`, and every section
below carries the same label because every section below is a command you can run:

- **Available in the published CLI** — `npm i -g @glotto/cli` gives you this command.

**Thin client does not mean everything is remote.** Which side a command runs on follows from what
it needs, not from a policy. `verify-compile` and `verify-contract` invoke your committed SDKs' own
toolchains and test suites, and `publish` pushes with your own registry credentials — all three run
entirely on your machine, and nothing about them reaches Glotto. `generate`, `verify` and
`breaking-changes` need the code-generation and spec-normalisation engines, which stay
server-side, so those three need `glotto login` or a `GLOTTO_API_TOKEN`. Each section below says
which it is.

What you will not find here is a reference section for a command you cannot obtain. Documenting one
would mean printing a flag table and a shell sample under a note saying you cannot run it, which is
not a caveat so much as an instruction that fails — so the one capability with no command left
(`glotto migrate fern`, which needs the spec pipeline) is described by its outcome instead. See
[What Glotto also runs for you](#what-glotto-also-runs-for-you).

## glotto init

> **Available in the published CLI** — `npm i -g @glotto/cli`

Scaffold a new `glotto.yml` and `spec/openapi.yaml` in the current directory, then print the next
steps. Without `--force` it refuses to overwrite an existing `glotto.yml` or `spec/openapi.yaml`.

- `--force` — overwrite existing files without prompting.

```sh
glotto init           # scaffold glotto.yml + spec/openapi.yaml
glotto init --force   # overwrite an existing scaffold
```

## glotto migrate

> **Available in the published CLI** — `npm i -g @glotto/cli`

Convert a competitor's config into a `glotto.yml`, and print a **migration report** saying what
happened to every key — what was carried, what was normalized, what was dropped and why, and which
values are placeholders you have to fill in. Nothing already on disk is overwritten without
`--force`, and your OpenAPI document is never modified in place.

- `--in <path>` — input file (default: `stainless.yml`).
- `--out <path>` — output file (default: `glotto.yml`).
- `--force` — overwrite an existing `--out`.
- `--report <path>` — also write the migration report to a file.
- `--openapi <path>` — read `x-stainless-naming` / `x-stainless-param` into `naming`, and resolve
  every `transforms` target against your real document so the translation is exact rather than
  inferred. See [Migrate from Stainless](/docs/migrate-from-stainless).
- `--openapi-out <path>` — also write that document back out with the Terraform attribute-shaping
  extensions translated to their `x-glotto-*` equivalents.

```sh
glotto migrate stainless                                  # stainless.yml -> glotto.yml
glotto migrate stainless --openapi openapi.yaml           # exact transform + naming translation
glotto migrate stainless --report migration-report.txt    # keep the report alongside the config
```

**Fern.** Glotto also converts a `fern/generators.yml` (and `fern/docs.yml`), but that conversion
reads your API surface through the spec pipeline, which stays server-side — so it is **not**
something the published CLI runs. `glotto migrate fern` exits `2` and says so. What it converts is
documented in [Migrate from Fern](/docs/migrate-from-fern); to have us run it, email
[hello@glotto.dev](mailto:hello@glotto.dev).

## glotto workspace

> **Available in the published CLI** — `npm i -g @glotto/cli`

Inspect and author the [workspace file](#the-workspace-file). Every subcommand answers a question
about **which project the current directory belongs to**, so none of them takes `--config` — that
flag is what bypasses discovery, and these commands exist to report what discovery does.

### glotto workspace show

Print the resolved binding for the current directory: the workspace root, the config it binds to,
which file declared it and how far up the tree that was, the root relative output is anchored to,
and where each enabled target's SDK is written. Exits `2` when no config is found anywhere above the
current directory, or when the config it found cannot be parsed (the binding is still printed).

- `--json` — emit the same facts as a JSON object instead of prose.

```sh
glotto workspace show          # "which project am I in, and where does my output go?"
glotto workspace show --json   # the same, for a script
```

```
workspace root: /repo
config:         /repo/api/glotto.yml
declared by:    .glotto/workspace.json (in this directory)
output root:    /repo

targets:
  python      /repo/clients/python  (declared)
  typescript  /repo/sdks/typescript
```

The directories it reports are the directories `glotto generate` writes to — both commands resolve
them through the same code, so the report cannot drift from the writer. `(declared)` marks a
directory that came from the workspace file's `targets` map rather than the `sdks/<language>/`
default.

### glotto workspace validate

Validate the nearest `.glotto/workspace.json` at or above the current directory, without generating
anything and without requiring `glotto.yml` to be valid. Prints nothing and exits `0` when the file
is clean, so it drops into a pre-commit hook or CI step. Exits `1` with the
[diagnostic code](/docs/diagnostics-fatal#the-workspace-file) when the file is wrong, and `2` when there is
no workspace file to check (an ambient `glotto.yml` is not a declaration and is never reported as
one).

- `--json` — emit the outcome as a JSON object. The exit code is unchanged.

```sh
glotto workspace validate          # silent + exit 0 when the file is clean
glotto workspace validate --json   # {"ok": false, "code": "GLOTTO_WORKSPACE_CONFIG_MISSING", …}
```

### glotto workspace init

Write a `.glotto/workspace.json` in the current directory, which becomes the workspace root. The
`config` path is derived rather than typed — from `--config` when you pass one, otherwise from the
config found by walking up — and written relative to the root with `/` separators, so the committed
file is portable. A config that does not exist, or that sits outside the workspace root, is refused
rather than written for a later command to reject.

`targets` is never written: it declares an output layout only you can choose. Add it by hand when
you want one, and `glotto workspace validate` will check it.

- `--config <path>` — bind to this config (relative to the current directory) instead of the
  discovered one.
- `--force` — overwrite an existing `.glotto/workspace.json`.

```sh
glotto workspace init --config api/glotto.yml   # writes {"config": "api/glotto.yml"}
glotto workspace init --force                   # overwrite an existing declaration
```

Note that `glotto init` deliberately does *not* write a workspace file — a fresh single-config
project does not need one. Reach for `glotto workspace init` when a repository grows a second
project, or when you want SDKs written somewhere other than `sdks/<language>/`.

## glotto schema

> **Available in the published CLI** — `npm i -g @glotto/cli`

Emit the JSON Schema for `glotto.yml` (the same schema core-config exports as
`glottoConfigJsonSchema()`). With no flags it prints to stdout; reference it from your config with
a `# yaml-language-server: $schema=…` header for editor completion and validation.

- `--out <file>` — write the schema to a file (relative to the current directory) instead of stdout.

```sh
glotto schema                            # print the schema to stdout
glotto schema --out glotto.schema.json   # write it, then reference it from glotto.yml:
                                         #   # yaml-language-server: $schema=./glotto.schema.json
```

## glotto generate

> **Available in the published CLI** — `npm i -g @glotto/cli`

Validate the config, then generate SDKs from the spec across enabled targets. Writes all files
atomically: nothing is written if the run *fails*. A target an engine *refuses* is different — it is
dropped, the remaining targets are written normally, and the command exits non-zero naming the
target it could not build (see [Reserved model names](/docs/diagnostics-sdk-generation#reserved-model-names)). Each SDK's customer-owned `lib/` extension
files are written only if absent, and managed files with local edits are preserved (or, with
`--merge`, three-way-merged) unless `--force` is passed — see [Custom code](/docs/custom-code).

- `--config <path>` — use this config instead of auto-discovery.
- `--out <dir>` — output directory, relative to the current directory (default `sdks`, resolved
  against the [workspace root](#the-workspace-file) when a workspace file is in effect). Overrides
  any `targets` path a workspace file declares.
- `--target <target>` — restrict to a target; repeatable. Omit to generate all configured targets.
- `--version <semver>` — stamp the generated SDKs with this version (`X.Y.Z` or `X.Y.Z-pre`).
- `--dry-run` — print the files that would be generated without writing.
- `--force` — overwrite managed files that have local edits.
- `--merge` — three-way-merge managed files that have local edits with the regenerated output
  (conflicts are written with git conflict markers), keeping a pristine baseline under
  `.glotto/baseline/` (gitignore it). Without the flag, edited files are preserved untouched.
- `--format` — run each language's native formatter over the generated source (`gofmt`, `rustfmt`,
  `dart format`, `swift-format`, `mix format`, `rubocop`, `php-cs-fixer`, `google-java-format`,
  `ktfmt`). A formatter that isn't installed degrades with a warning — that language is emitted
  unformatted; point `GLOTTO_<TOOL>` (e.g. `GLOTTO_RUSTFMT`) at a binary to override resolution.

```sh
glotto generate                              # generate every configured target into ./sdks
glotto generate --target typescript          # one target only (repeat --target for more)
glotto generate --out build/sdks --version 1.4.0
glotto generate --dry-run                    # preview the file list, write nothing
```

## glotto mcp

> **Available in the published CLI** — `npm i -g @glotto/cli`

Work with the [MCP server](/docs/mcp-server) artifact. Three subcommands:

- `glotto mcp generate` — emit **only** the MCP server from `glotto.yml`, without the SDKs or the
  docs site. Runs server-side, like `generate`, so it needs `glotto login`.
  - `--out <dir>` — write the server here instead of the configured output directory.
  - `--config <path>` — use this `glotto.yml` instead of auto-discovery.
  - `--dry-run` — print the file list, write nothing.
  - `--force` — overwrite managed files that have local edits.
- `glotto mcp serve` — run an emitted MCP server over stdio, so an MCP client can launch it
  directly. Local only: it spawns the artifact already on disk and generates nothing.
  - `--dir <path>` — the emitted server to run (default `sdks/mcp`).
  - Any remaining flags are forwarded to the server unchanged.
- `glotto mcp annotate` — author tool descriptions into `glotto.yml`'s `mcp.operations` block.
  Runs server-side and needs `glotto login`.
  - `--tool <name>` — annotate one tool; repeatable.
  - `--config <path>`, `--dry-run`, `--force` — as above.

```sh
glotto mcp generate                    # emit just the MCP server (runs server-side)
glotto mcp serve                       # run the emitted server over stdio
glotto mcp serve --dir build/mcp       # run one from a non-default directory
glotto mcp annotate --tool listPets    # author one tool's description into glotto.yml
```

## glotto breaking-changes

> **Available in the published CLI** — `npm i -g @glotto/cli`

Classify what changed between your current spec and a baseline: which changes break a caller, which
do not, and what each does to the generated SDK surface. With `--gate` it fails the run when a
breaking change lands without a major version bump, which is what the CI workflow Glotto emits for
your repository does on every pull request.

The comparison builds two IRs through Glotto's spec pipeline and diffs them, so **it runs on
Glotto's infrastructure** and needs `glotto login` or a `GLOTTO_API_TOKEN`. Both spec documents are
read from your machine and sent with any configured companion spec documents. The published CLI
accepts local file sources. URL, git, command and introspect sources are refused: fetch, check out
or export those documents first, then point the config at local files.

- `--against <path>` — the baseline spec document to compare against. A local file.
- `--provider <github|gitlab|bitbucket|azure>` with `--repo <ref>` and `--branch <branch>` — fetch
  the baseline from a repository instead. `--spec-path <path>` overrides which file is fetched.
- `--gate` — exit `1` when breaking changes were introduced without a major version bump. Also
  enabled by `settings.detect_breaking_changes` in your `glotto.yml`.
- `--format json` — emit the classified report as JSON instead of text.
- `--emit-workflow <github|gitlab|bitbucket|azure>` — print the CI workflow that runs this gate.

`--against-ref <ref>` is available in the first-party build only: it names a git object, which needs
the spec loader that clones repositories. Use `--against` with a checked-out file, or `--provider`.

The emitted workflows require a `GLOTTO_API_TOKEN`: configure an Actions secret on GitHub, a masked
CI/CD variable on GitLab, a secured repository variable on Bitbucket, or a secret pipeline variable
on Azure. The GitHub and Azure templates map that secret into the gate's environment. For a
self-hosted control plane, follow the template's `GLOTTO_API_URL` instructions; the default is
`https://api.glotto.dev`.

```sh
glotto breaking-changes --against baseline.openapi.yaml
glotto breaking-changes --against baseline.openapi.yaml --gate
```

## glotto verify

> **Available in the published CLI** — `npm i -g @glotto/cli`

Verify committed SDK output against a fresh regeneration and write the
[verification report](/docs/verification-report): per target, whether the committed files drifted
from what your spec produces, whether any checksum-stamped managed file was hand-edited, and — when
you feed it a check-results document — the real compile and contract status of each target. This is
the gate step of the CI workflow Glotto emits for your repository.

**It regenerates, so it runs on Glotto's infrastructure** and needs `glotto login` or a
`GLOTTO_API_TOKEN`. The comparison itself is local: your committed files are read and diffed on your
machine, and only the spec is sent.

- `--against <dir>` — the committed output tree to verify. `--against-default sdks` yields to a
  `.glotto/workspace.json` when one is in effect, and falls back to `sdks/`.
- `--provider <github|gitlab|bitbucket|azure>` with `--repo <ref>` and `--branch <branch>` — verify
  the output committed on a branch, with no checkout. `--prefix <path>` names the subpath it lives
  under.
- `--checks <path>` — fold in a check-results document produced by `verify-compile` /
  `verify-contract`, so the report carries real compile and contract statuses instead of "not run".
- `--out <dir>` — write `verification-report.json` and `verification-report.md` here.

```sh
glotto verify --against-default sdks --out glotto-verification
glotto verify --against-default sdks --checks glotto-verification/check-results.json --out glotto-verification
```

## glotto verify-compile

> **Available in the published CLI** — `npm i -g @glotto/cli`

Compile every committed SDK **in place**, with that SDK's own toolchain, and write the
check-results document `glotto verify` folds into its report. This is the first produce step of the
CI workflow Glotto emits for your repository.

**It runs entirely on your machine** and needs no Glotto account: it regenerates nothing and sends
nothing. What it does need is your targets' toolchains installed in the job — it invokes them.

Each target's command comes from `targets.<slug>.verify.compile` in your `glotto.yml`, or a built-in
default for the languages where compiling a committed SDK is unambiguous (TypeScript, React Native,
Go, Python, Rust). A target with neither is skipped rather than guessed at, and the document records
the omission.

- `--against <dir>` / `--against-default sdks` — the committed output tree to compile.
- `--out <dir>` — where to write `check-results.json`.

```sh
glotto verify-compile --against-default sdks --out glotto-verification
```

## glotto verify-contract

> **Available in the published CLI** — `npm i -g @glotto/cli`

Run the integration test suite each committed SDK ships — the self-contained suite Glotto emits
beside the client, which boots an in-process mock and round-trips the generated code against it —
and **merge** the contract statuses into the same check-results document `verify-compile` wrote.

**It runs entirely on your machine**, like `verify-compile`, and needs each target's dependencies
installed as well as its toolchain: the emitted suite imports the built SDK.

- `--against <dir>` / `--against-default sdks` — the committed output tree to test.
- `--out <dir>` — the directory holding the document to merge into (and to write).

```sh
glotto verify-contract --against-default sdks --out glotto-verification
```

## glotto verify-upload

> **Available in the published CLI** — `npm i -g @glotto/cli`

Publish a check-results document to the control-plane run for a commit, so the console's
surface-health panel, the run history and the
[verification attestation](/docs/verification-report) carry your real compile and contract
statuses instead of "not run". This is the **dashboard** step of the CI workflow Glotto emits for
you; leave it out and the report still reaches your pull request as a job summary and an artifact.

It is pure HTTP: it reads the produced document and posts it. It runs no generation and needs no
toolchain.

- `--checks <path>` — the check-results document to publish. Required.
- `--project <id>` — the control-plane project. Resolves `--project`, then `GLOTTO_PROJECT_ID`,
  then `hosted.project` in your `glotto.yml`.
- `--commit <sha>` — the commit the run belongs to. Read from the CI environment when omitted.

`GLOTTO_API_TOKEN` authenticates the upload. **Verified provenance is optional and turn-key**: set
`GLOTTO_CI_OIDC_TOKEN` to a pre-minted CI-run OIDC token (GitLab, Bitbucket, Azure Pipelines or
self-hosted), or grant GitHub Actions `id-token: write` — the dashboard then marks the run
*verified*, meaning it can prove which workflow produced the statuses, not merely that a valid
token uploaded them. The token's audience must equal your API URL, which must match the server's
`CI_OIDC_AUDIENCE` (default `https://api.glotto.dev`).

```sh
glotto verify-upload --checks glotto-verification/check-results.json
```

## glotto verify-attestation

> **Available in the published CLI** — `npm i -g @glotto/cli`

Verify a downloaded [verification attestation](/docs/verification-report) — the signed,
self-contained DSSE envelope that binds one verification report to the run that produced it. This
is the command a buyer's compliance reviewer runs: it turns a JSON file into a verdict without
requiring anyone to construct DSSE pre-authentication bytes by hand.

Exits `0` when the envelope is verified, `1` when it is not, and `2` on a usage or configuration
error — every usage error is reported *before* any network call, so a mistake never costs a round
trip and never looks like a failed verification.

- `--key <path>` — a public key file: one SPKI PEM, or a bundle of several concatenated.
  **Repeatable.** Keys are matched to signatures by their own derived fingerprint, never by filename
  or the order you pass them.
- `--offline` — never contact the API. With `--key` material covering every key the envelope names,
  verification is completely self-contained — which is the point of filing an attestation for years.
- `--api-url <url>` — control-plane base URL (env `GLOTTO_API_URL`; default `https://api.glotto.dev`).
  Used only for the keys `--key` did not resolve.
- `--allow-revoked` — report a signature made by a revoked key as a warning instead of a failure.
- `--require-countersignature` — fail unless at least one verified **non-issuing** signature is
  present (see below).
- `--countersign <pkcs8.pem>` — co-sign this envelope with your own Ed25519 key. Verification runs
  **first**: an envelope that failed is never counter-signed. Your private key is never printed,
  logged, or uploaded — only the public half and the signature.
- `--upload` — send the co-signature to the run the attestation itself names (needs
  `GLOTTO_API_TOKEN`). There is deliberately no `--project`/`--run` flag: the signature can only be
  filed against the run it describes.
- `--json` — machine-readable verdict.

```sh
# Verify against the key the API serves for this envelope
glotto verify-attestation attestation.json

# Fully offline, with a key you archived alongside the artifact
glotto verify-attestation attestation.json --key glotto-attestation.pem --offline

# Co-sign it with your own key, so the artifact no longer rests on Glotto's alone
glotto verify-attestation attestation.json --countersign my-key.pem --upload
```

**What the verdict means.** The first signature is Glotto's; any that follow are second-party
counter-signatures. A counter-signature that fails against a key you hold is **tampering** and fails
the whole verification; one whose key you simply do not hold is reported and does not fail anything —
holding fewer keys should not make an artifact look worse. If a key resolves as `revoked`, the
command tells you both facts separately: the signature is genuine, *and* the key is not to be
trusted. Under `--offline` no key carries a status at all, so the command says revocation was not
checked rather than implying a clean bill of health.

## glotto publish

> **Available in the published CLI** — `npm i -g @glotto/cli`

Publish your generated SDKs to their language package registries — npm, PyPI, crates.io, Hex, Maven
Central, NuGet, pub.dev and RubyGems. This is the single step of the release CI workflow Glotto
emits for your repository, which runs when a release pull request merges.

**It runs entirely on your machine, with your own registry credentials.** Nothing is sent to
Glotto, and Glotto never holds a registry token — each publisher reads the credential its registry
expects from the environment, exactly as you would publishing by hand.

- `--all` — publish every configured target.
- `--lang <slug>` — publish one target.
- `--dir <path>` — the committed output tree to publish from.
- `--version <version>` — the release version. Read from the committed manifest when omitted.
- `--dry-run` — print the plan without publishing.
- `--emit-workflow <github|gitlab|bitbucket|azure>` — print the release CI workflow.

```sh
glotto publish --all --dry-run
glotto publish --all
```

## glotto code-owners

> **Available in the published CLI** — `npm i -g @glotto/cli`

Apply your `glotto.yml`'s `code_owners` block to a repository's review configuration.

This exists because review ownership is a file on only half the forges Glotto manages: GitHub and
GitLab read the `CODEOWNERS` file `glotto generate` emits, so this command reports the path and
writes nothing. Bitbucket and Azure Repos have no such file — their equivalents are default
reviewers and a required-reviewers branch policy, both API configuration — so on those two this is
the only way a `code_owners` block takes effect at all.

**It runs on your machine** and talks to your forge, not to Glotto.

- `--provider <github|gitlab|bitbucket|azure>` — required.
- `--repo <ref>` — `owner/name`, or `org/project/repo` for Azure Repos. Required.
- `--host <host>` — self-managed GitLab or Azure DevOps host.
- `--config <path>` — path to `glotto.yml`.

Credentials come from `GLOTTO_VCS_TOKEN`, or `GITHUB_TOKEN` / `GITLAB_TOKEN` / `BITBUCKET_TOKEN` /
`AZURE_DEVOPS_TOKEN`.

```sh
glotto code-owners apply --provider bitbucket --repo acme/payments-sdks
```

## glotto login

> **Available in the published CLI** — `npm i -g @glotto/cli`

Authenticate to the Glotto control plane via the device-authorization (RFC 8628) flow and store the
resulting token under `~/.glotto/`. The published thin-client CLI requires this — `glotto generate`
dispatches codegen to the control plane.

- `--api-url <url>` — control-plane base URL (env `GLOTTO_API_URL`; default `https://api.glotto.dev`).
- `--scope <scope>` — OAuth scope to request; repeatable.
- `--refresh` — renew the stored session instead of starting a new device flow.

```sh
glotto login                                  # device-flow login to api.glotto.dev
glotto login --api-url https://api.example.com
glotto login --refresh                        # renew the stored session in place
```

A session token is valid for 30 days. `--refresh` renews it without a browser round-trip: the old
token is revoked and a replacement is stored, so a leaked copy stops working as soon as you renew.
The renewal inherits the existing token's scopes (so `--refresh` cannot be combined with `--scope`),
and it never falls back to an interactive flow. Renewals are capped at 90 days from the original
login — past that, `--refresh` says so and you sign in again with `glotto login`.

## glotto logout

> **Available in the published CLI** — `npm i -g @glotto/cli`

Remove the stored control-plane credential for the resolved API URL, leaving other URLs untouched.
Idempotent — exits `0` whether or not a credential was present.

- `--api-url <url>` — control-plane base URL (env `GLOTTO_API_URL`; default `https://api.glotto.dev`).

```sh
glotto logout
glotto logout --api-url https://api.example.com
```

## What Glotto also runs for you

Most of the capabilities above are also delivered without your running anything. Glotto produces
them on its own infrastructure or inside the CI workflow it emits for your repository, and puts the
result where you already look — a pull request, a job summary, your dashboard. The command and the
delivery are two routes to the same output, not two different features:

- **[Verification report](/docs/verification-report)** — what `glotto verify` writes, also produced
  for your release pull request and your dashboard, with a
  [signed attestation](/docs/verification-report#as-a-signed-attestation) you can check offline
  using `glotto verify-attestation`.
- **[Drift detection](/docs/drift-detection)** — the specific finding that committed SDK output no
  longer matches the spec it was generated from, including which files and why.
- **[Breaking-change detection](/docs/breaking-changes)** — every pull request that changes your
  spec is classified against the last build, whether or not you run `glotto breaking-changes`
  yourself.
- **[The release flow](/docs/multi-vcs-release)** — the release pull request that precedes the
  `glotto publish` step above.
- **Migration** — converting an existing project into a `glotto.yml`, non-destructively:
  [from Stainless](/docs/migrate-from-stainless) (`glotto migrate stainless`),
  [from Fern](/docs/migrate-from-fern), or
  [one endpoint at a time](/docs/endpoint-migration).

One capability is still delivery-only: the **Fern** conversion needs the spec pipeline, which runs
server-side, so `glotto migrate fern` refuses by name in the published CLI rather than half-running.
The boundary is which of Glotto's engines would have to ship to your machine, not which capabilities
we are willing to offer.
