# CLI

Build-time and CI tooling: upload debug symbols and symbolication artifacts, and report deployments. Node **18+**; installing globally exposes the `bugwatch` bin, or run everything with `npx @newinstance/bugwatch-cli`.

**Auth:** `--token` flag or `BUGWATCH_AUTH_TOKEN` env (flag wins), value `KEYID:secret`. `--api-url` / `BUGWATCH_API_URL` defaults to `https://api.newinstance.cloud`. The key travels only in the `x-api-key` header, never in URLs or logs; output shows a redacted form like `sk_l…3456`.

## Commands

| Command | Purpose | Scope |
|---------|---------|-------|
| `symbols upload <path>` | Binary debug symbols: a `.dSYM`, a directory (recursive, depth 4, dSYM bundles only), an `.xcarchive` (its dSYMs folder is walked), or a pre-made `.zip` (uploaded as-is). Android ELF `.so` files must be zipped first: bare `.so` files and `.so` directories are rejected | `symbols:upload` |
| `artifacts upload <path>` | Text artifacts: R8/ProGuard mapping, JS source map, Dart symbols. `--release`, `--platform`, `--type` required; optional `--build-id` | `symbols:upload` |
| `deploy start` | Create a deployment; prints the id and persists it | `deploy:write` |
| `deploy run --stage <key> -- <cmd…>` | Run a command as a stage: mark running, stream logs, mark result | `deploy:write` |
| `deploy finish` / `deploy fail [--summary <text>] [--stage <key>]` | Close the deployment; `--summary` defaults to "deployment failed" | `deploy:write` |

`symbols upload` flags: `--platform` (default `ios`; also macos, tvos, watchos, visionos, catalyst, android), `--release`, `--build-number`, `--bundle-id`, `--distribution`, `--environment`, `--commit`, `--upload-source` (default `cli`; `xcode-cloud` and `local-xcode` exist for those flows).

The two upload commands validate input by magic bytes: passing a `mapping.txt` or `.map` to `symbols upload` fails with `UNRECOGNIZED_ARCHIVE`. Packing a dSYM needs a macOS runner with `zip`; a pre-made `.zip` skips that requirement.

## How uploads work

1. Presign, 2. stream the file straight to object storage with flat memory, 3. complete. The key is sent only on steps 1 and 3, never to the storage host; the server verifies size and sha256 (`CHECKSUM_MISMATCH` on tamper). Retries: 3 attempts on 5xx, 429 and network errors with 500/1000/1500 ms backoff; 4xx never retries; the stream reopens per attempt and temp zips are always cleaned up. A duplicate sha256 skips the transfer entirely and exits 0. `artifacts upload` is idempotent per release, platform and type: re-uploading replaces the previous file. Server cap for symbol archives: 2 GB.

**Verify**: the CLI prints an `uploadId`; poll `GET /api/v1/bugwatch/debug-symbols/uploads/{id}` (scope `symbols:read`) until the status leaves QUEUED and PROCESSING.

## Deployments in CI

```bash
export BUGWATCH_AUTH_TOKEN="sk_live_KEYID:secret"

npx @newinstance/bugwatch-cli deploy start --release "2.4.1"
npx @newinstance/bugwatch-cli deploy run --stage migrate -- ./scripts/migrate.sh
npx @newinstance/bugwatch-cli deploy run --stage deploy  -- ./scripts/deploy.sh
npx @newinstance/bugwatch-cli deploy finish
```

- `deploy start` auto-detects repo, branch, commit and triggeredBy on GitHub Actions, GitLab CI and CodeBuild, defaults `release` to the commit SHA, persists the id to `.bugwatch-deploy`, and on GitHub Actions also appends `BUGWATCH_DEPLOY_ID` to `$GITHUB_ENV`. Id resolution order everywhere: `--id`, then `BUGWATCH_DEPLOY_ID`, then `.bugwatch-deploy`.
- `deploy run` passes the child output through to your console while shipping it as NDJSON batches (every 2 s or 64 KB) to the stage-logs endpoint; a failed stage attaches the last 50 lines as an error summary (truncated to 2000 chars). Its exit code is the child command's (127 if it cannot spawn); missing `--stage` or command is exit 1.
- Deploy reporting is deliberately non-fatal: `start`, `finish` and `fail` exit 0 even with no token, no id, or an API error, and `deploy run` still executes your command unreported. Your pipeline never breaks because telemetry did.

## Exit codes

0 accepted (or duplicate skip), 1 usage or input error, 2 upload failure after retries.

## Troubleshooting

- **Frames still unresolved**: the dSYM must come from the exact crashing build; compare `dwarfdump --uuid MyApp.dSYM` with the event's binary images. Upload the whole `.xcarchive` so framework dSYMs come along. Simulator builds produce no dSYM; the build setting must be DWARF with dSYM File.
- **Mapping uploaded but stacks unreadable**: the `--release` string must equal the release the SDK reports at runtime.

All commands call the endpoints documented in [Source Maps & Symbols](/source-maps-symbols) and [Deployments (CI/CD)](/deployments-ci-cd); the CLI is sugar over those endpoints.
