> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parsaa.app/llms.txt
> Use this file to discover all available pages before exploring further.

# The orbit CLI

> Upload artifacts, bridge MCP over stdio, sync memory and query analytics

`orbit` is how CI and your terminal talk to Orbit. Artifacts like an `.xcresult` bundle or an Instruments `.trace` run to hundreds of megabytes, so they never go through a browser.

<Note>
  Do not confuse it with `parsaa-cli`, which is the Parsaa app's own, unrelated CLI.
</Note>

## Install

<CodeGroup>
  ```bash Homebrew theme={null}
  brew install parsaa-company/parsaa/orbit
  ```

  ```bash Go theme={null}
  go install github.com/Parsaa-Company/Orbit-CLI/cmd/orbit@latest
  ```

  ```bash Direct download theme={null}
  curl -sSL -o orbit.tar.gz \
    https://github.com/Parsaa-Company/Orbit-CLI/releases/latest/download/orbit_${VERSION}_linux_amd64.tar.gz
  tar xzf orbit.tar.gz
  ```
</CodeGroup>

Every release publishes a `checksums.txt` alongside per-platform archives.

## Credentials

```bash theme={null}
orbit auth --token orbit_ab12...   # a project token, for CI
orbit login                        # a session on your own account, for an editor
orbit whoami                       # which credential is in use, and for which project
```

A **project token** is created in the console under Settings, Tokens, and shown once. In CI it belongs in a secret, read as `ORBIT_TOKEN`. It names exactly one project, so no command takes a project argument.

A **session** from `orbit login` belongs to you, reaches every project you are a member of, and is the only way the paid MCP tools run from an editor. The two live side by side in `~/.config/orbit/config.json` (mode `0600`), and neither command disturbs the other's credential. See [MCP and agents](/orbit/mcp-and-agents).

## orbit upload

There is one upload command, not one per kind.

```bash theme={null}
orbit upload <path> [--kind <kind>] [--wait] [--build-id <uuid>]
```

`--kind` is inferred from the path when omitted.

| Path shape                                   | Inferred kind | Where it lands      |
| -------------------------------------------- | ------------- | ------------------- |
| `.ips`, `.crash`                             | `ips`         | Crashes             |
| A `.dSYM` directory, or a `.zip` holding one | `dsym`        | Settings, Symbols   |
| An `.xcresult` directory                     | `xcresult`    | Tests               |
| A `.trace` directory                         | `trace`       | Performance         |
| `.xcactivitylog`                             | `buildlog`    | Build               |
| A link map, with `--kind linkmap`            | `linkmap`     | Build, the Size tab |

```bash theme={null}
orbit upload MyApp.xcactivitylog --kind buildlog
# prints the build id once parsed
orbit upload MyApp-linkmap.txt --kind linkmap --build-id 5b2b1c4e-...
orbit upload build.xcresult --wait
orbit upload profile.trace --wait
```

`--build-id` is `linkmap` only. `--wait` blocks up to ten minutes for the server to finish parsing before exiting.

### What actually happens

Your bytes never stream through Orbit's own servers. The CLI hashes and sizes the file locally (zipping a bundle directory deterministically, so an unchanged build produces the same hash and dedups), presigns, `PUT`s straight to object storage, then confirms. A duplicate hash on the same project skips the transfer entirely.

## orbit compliance scan

```bash theme={null}
orbit compliance scan build/MyApp.xcarchive [--json] [--upload]
```

Runs entirely offline, with no network call at all unless `--upload` is passed, and then only the report JSON is sent, never the archive. It reads the app bundle, its frameworks and extensions, their Mach-O binaries and `Info.plist`s, and prints a report scored out of 100: required reason API declarations, third party SDK detection against Apple's list, entitlements, and a rule-based App Review checklist.

## orbit mcp

A stdio bridge to the HTTP MCP server, for editors that speak only stdio.

```jsonc theme={null}
{ "command": "orbit", "args": ["mcp"] }
```

It is a proxy and nothing else: it holds no tools of its own, so a tool added to the server is available through the bridge the same day without shipping a new CLI. It prefers the session over a config-file token, because a session can spend your credits and a project token never can, and a token you named yourself with `--token` or `$ORBIT_TOKEN` always wins. It renews an expired session before starting, and once on a `401` mid-run, so the editor sees the answer rather than the interruption.

## orbit memory

```bash theme={null}
orbit memory pull
orbit memory push [--activate] [--from <dir>]
```

**Pull** writes what the console compiled and nothing it did not: `AGENTS.md` and `CLAUDE.md` with the same bytes under a generated header, plus every active memory into `.serena/memories/`. It sends the ETag of the last pull, so an unchanged bundle costs one `304` and no rewrite. Deletion is bounded by a manifest in `.orbit/`, so a memory archived in the console leaves the repo and a file the CLI never wrote is never touched. Gitignore `.orbit/`; commit the generated files.

**Push** imports the markdown a repo already has. `CLAUDE.md` and `AGENTS.md` are split on `## ` into one instruction memory per section, titled and slugged from the heading, which is what makes a real instruction file import at all (a memory body is capped at 8 KB). Matched by slug, so pushing twice updates instead of duplicating. Imports land as drafts unless `--activate` is passed, and files carrying the generated header are skipped so a compiled bundle is never re-imported as new memories.

## orbit analytics query

```bash theme={null}
orbit analytics query dau --window 7d
orbit analytics query active.daily --breakdown app_version --csv
orbit analytics query "Weekly actives by version"
```

A metric id first, a saved query by name second. Flags are `--window`, `--unit`, `--breakdown` and `--csv`. There is no copy of the metric catalogue in the binary, so a metric added in the console is queryable without a new CLI. A day the rollup has not written is written `EMPTY` in the CSV, never `0`.

## Global flags

| Flag        | Default                                 | Meaning                                                         |
| ----------- | --------------------------------------- | --------------------------------------------------------------- |
| `--token`   | `$ORBIT_TOKEN`, then the config file    | The project token                                               |
| `--api-url` | `https://platform.parsaa.app/api/orbit` | The Orbit API base URL                                          |
| `--json`    | `false`                                 | One machine-readable JSON document on stdout, with stderr empty |

No flag anywhere in the CLI has a short form.

## Exit codes

| Code | Meaning                                                     | In CI                            |
| ---- | ----------------------------------------------------------- | -------------------------------- |
| `0`  | Success                                                     |                                  |
| `1`  | Bad flags, an unknown kind, a missing file                  | A configuration bug. Fail loudly |
| `2`  | No token, or an invalid or revoked one                      | The same                         |
| `3`  | The transfer failed, or the server marked the upload failed | Worth one retry with backoff     |
| `4`  | The API answered with an error the CLI cannot act on        | The same                         |

## In CI

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  - name: Upload test results to Orbit
    if: always()
    env:
      ORBIT_TOKEN: ${{ secrets.ORBIT_TOKEN }}
    run: |
      brew install parsaa-company/parsaa/orbit
      orbit upload build.xcresult --kind xcresult --wait
  ```

  ```sh Xcode Cloud theme={null}
  #!/bin/sh
  # ci_scripts/ci_post_xcodebuild.sh
  set -e
  brew install parsaa-company/parsaa/orbit

  if [ -n "$CI_RESULT_BUNDLE_PATH" ]; then
    orbit upload "$CI_RESULT_BUNDLE_PATH" --kind xcresult
  fi

  if [ -n "$CI_ARCHIVE_PATH" ]; then
    find "$CI_ARCHIVE_PATH" -name "*.dSYM" -maxdepth 3 -print0 |
      xargs -0 -I{} orbit upload "{}" --kind dsym
  fi
  ```
</CodeGroup>

<Tip>
  `if: always()` matters. A failing test run's `.xcresult` is exactly the one you want in Orbit, so the upload should run whether the prior step passed or not. In Xcode Cloud the two guards are load-bearing rather than defensive: a test-only run has no archive, and an archive-only run has no result bundle.
</Tip>

Do not loop retries indefinitely inside a CI step. The upload flow already retries the calls it makes internally; a CI-side retry is for the process itself not coming back clean.
