# CLI

The `upseam` CLI runs the same detector as the App, locally or in your CI,
with no account and no server. It is published as the `@upseam/cli` npm
package with the first release; the installed command is `upseam`:

```sh
npx @upseam/cli inspect stripe ../your-repo
```

It needs Node.js 22 or later.

## Commands

| Command                                                          | What it does                                                          |
| ---------------------------------------------------------------- | --------------------------------------------------------------------- |
| `upseam inspect <provider> [path] [--api-version <v>]`           | SDK, API version, changes since, matched lines.                       |
| `upseam inspect internal --spec <path> --from <ref> [--to <ref>] [--repo <path>] [consumer...]` | Diff your OpenAPI or GraphQL contract and search the consumer paths. |
| `upseam report [path] (--dry-run \| --github <owner/repo>)`      | Print the issue body, or create or update it with `GITHUB_TOKEN`.     |
| `upseam fix [path] (--dry-run \| --github <owner/repo>)`         | Patch one mechanical change with your model: print the diff or open a pull request. |
| `upseam fix [path] --group <key> --sha <sha> [--head <sha>] (--dry-run \| --github <owner/repo>)` | Write the patch for one App group and push `upseam/<group>`, as the Action does when [generating in your GitHub Actions](models.md#generate-in-your-github-actions). |
| `upseam init [path]`                                             | Set up Upseam in a repository; see below.                             |
| `upseam agent route "<task>"`                                    | Rate a coding task and pick a model tier. Needs `TYPESAFE_API_KEY`.   |
| `upseam agent prune --task "<task>" <fragments.json>`            | Drop context fragments irrelevant to the task. Needs `TYPESAFE_API_KEY`. |

`<provider>` is one of `stripe`, `shopify`, `hubspot`, `twilio`, `github`,
`openai`, `anthropic`, `gemini` and `mistral`. `--api-version` overrides the
detected version for `stripe`, `shopify`, `hubspot`, `github` and `twilio`;
`openai`, `anthropic`, `gemini` and `mistral` have no API version, and
`--api-version` exits with code 2 for them. When the provider's SDK is not
found in the repository, `--api-version` has no effect.

## upseam init

`upseam init` prepares a repository for the App without a model, the same way
the [coding agent setup](agent-setup.md) does:

```sh
npx @upseam/cli init
```

1. Checks that the path (default: the current directory) is a git repository
   with a GitHub remote, and lists the supported vendor SDKs and the OpenAPI
   and GraphQL files it finds. With neither, it says "Upseam has nothing to
   watch in this repository yet", asks nothing and writes nothing.
2. Proposes [`.github/upseam.yml`](configuration.md) with only `internal` and
   `ignore`, checked with the same parser as the App. It asks for consumer
   repositories and never guesses them. When nothing is needed, it writes no
   file and says so.
3. Asks how fixes are written: by the model you connect on the settings page,
   or by [your GitHub Actions](models.md#generate-in-your-github-actions) with
   the `upseam-generate` workflow and `upseam/action` pinned to a commit SHA
   you give. Without a SHA it writes no workflow.
4. Shows every file before writing it and asks to confirm. It never commits or
   pushes; it prints the `git` commands instead.
5. Lists what is left in the browser: install the App, connect a model or
   choose Actions, optionally connect Slack. While the App is in private
   beta, it says so instead of printing an install link.

| Flag | Meaning |
| --- | --- |
| `--dry-run` | Ask nothing and write nothing; print what would be written. |
| `--yes` | Ask nothing and write the proposed files: the [stored-key method](models.md#key-stored-with-upseam), no consumers, nothing ignored. |
| `--json` | Print one JSON object with the findings, files, notes and next steps. Needs `--yes` or `--dry-run`. |
| `--force` | Replace an existing file whose content differs. Without it the file is kept. |
| `--internal <spec>=<owner/repo>[,<owner/repo>...]` | An `internal` entry; repeat per contract. |
| `--ignore-provider <id>`, `--ignore-path <pattern>` | `ignore` entries; repeat for more. |
| `--method model\|actions` | How fixes are written. |
| `--action-sha <sha>`, `--vendor anthropic\|openai`, `--model <id>`, `--base-url <url>` | For Actions: the `upseam/action` commit and the model the workflow uses. `--vendor openai` needs `--model`. |

Run by an agent, without a terminal, it needs `--yes` or `--dry-run`. It
refuses to read or write `.github/upseam.yml` or
`.github/workflows/upseam-generate.yml` through a symbolic link or a
non-regular file. It reads no environment variables or secrets, makes no
network calls and sends no telemetry.

## Contract checks

`upseam inspect internal` also takes:

- `--consumer-repo <owner/repo[@ref][:path]>`, repeatable, to search other
  repositories;
- `--fail-on breaking|matched|never`, which exits with code 3 when it
  triggers;
- `--dry-run` to print the pull request comment, or
  `--github <owner/repo> --pr <number>` to create or update it with
  `GITHUB_TOKEN`.

The `--spec` extension picks the format: `.yaml` or `.yml` is OpenAPI in YAML,
`.graphql`, `.graphqls` or `.gql` is a GraphQL schema, anything else is
OpenAPI in JSON. `--from` and `--to` are git refs or file paths; `--to`
defaults to `HEAD`.

```sh
npx @upseam/cli inspect internal --spec api/openapi.yaml --from v1.4 --to HEAD ../frontend ../mobile
```

## Options and exit codes

- `inspect` and `agent` take `--json`. `upseam --help` prints the commands
  grouped by task, with examples. `upseam --version`, `-v` or
  `upseam version` prints only the package version.
- `inspect` in a terminal lists the changes matched in code first, then the
  first 10 unmatched breaking changes; `--all` lists every breaking or
  matched change. `--all` changes only this terminal output: plain and
  `--json` output always list everything. A retirement date shows a
  countdown, for example `retires 2026-10-23 (in 26 days)`.
- `fix` also takes `--vendor`, `--model` and `--base-url`. The Action runs
  it as separate steps with `--pulls <file>`, `--stage <dir>` and
  `--publish <dir> --manifest <sha256>`, so the token and the model key are
  never in the same step. `--verify` is refused: code the model wrote never
  runs in a job that can push, so your tests run in your usual pull request
  workflow with `contents: read`.
- Exit codes: `0` done, `1` error, `2` usage, `3` `--fail-on` triggered.

## Terminal output

In an interactive terminal the output has colour, links you can click and,
while `inspect`, `report`, `init` or `fix` scan the repository, a progress
bar on stderr. The percentage counts the files read; the other steps show
their name only. The bar appears only after a quarter of a second and is
cleared before the result is printed.

- `NO_COLOR` with any value, or `TERM=dumb`, turns colour off; the bar stays
  in black and white, except with `TERM=dumb`, where there is none.
- When stdout is not a terminal or `CI` is set, the output is plain text, one
  line per fact, without colour, links or a progress bar. The GitHub Action
  and your CI logs get this form.
- `--json` output never changes with the terminal.
- Text taken from the repository, the change data or a model reply (file
  names, code lines, titles, versions, contract keys, error messages from
  parsing a spec, and the `fix` diff) is cleaned before it is printed:
  control characters, escape sequences, bidirectional and zero-width
  characters and line breaks inside a value are removed, a line that would
  start with `::` gets a zero-width space between the colons and every
  `##[` gets one after `##`. So it cannot change your terminal or run a
  GitHub Actions workflow command. `--json` output is not changed; a JSON
  string can still contain `##[`. Links are clickable only for plain
  `http` and `https` addresses.
- In the GitHub Action every step that runs the CLI also turns workflow
  commands off (`::stop-commands::` with a random token) while it runs; the
  Action itself prints the warnings about skipped consumers afterwards.
- Ctrl-C clears the progress bar and exits with code 130. During a step that
  does not yield, such as reading the change data or matching, it takes
  effect when that step ends, usually within a second.

## Data and privacy

- `inspect` and `report` read the change database that ships inside the
  package and fetch no change data at run time.
- **Local only:** `init`, `inspect <provider>`, `inspect internal` without
  `--consumer-repo` and `--github`, and `report --dry-run`.
- **GitHub, with your token:** `inspect internal --consumer-repo` downloads
  each consumer repository as an archive, using `UPSEAM_CONSUMERS_TOKEN` or
  `GITHUB_TOKEN`. `--github` on `inspect internal` and `report` sends the
  rendered comment or issue body to the GitHub API. `fix --pulls` looks up
  Upseam's pull requests through the GitHub API.
- **Your model vendor, with your key:** `fix` sends the change and the matched
  files, except with `--pulls`, `--publish`, or `--group` with `--edits`.
  `fix --github` then pushes one branch to your `origin` and opens a pull
  request. With `--stage <dir>` it only writes the patch to that directory and
  pushes nothing; the separate `--publish` step pushes and opens the pull
  request. With `--group` it only pushes the branch, and the App opens the
  pull request.
- `agent route` and `agent prune` send the task and the fragments to TypeSafe,
  with your `TYPESAFE_API_KEY`. Keep secrets out of them.
