
# The hs-x CLI.

One binary covers the whole lifecycle: scaffold a project, run it locally against a real portal, deploy to HubSpot and your own Cloudflare account, migrate a legacy app, and read what production is doing. Every command works unlinked, straight against HubSpot and Cloudflare; logging into an HS-X account adds the platform surfaces (promote, rollback, logs, drift, audit) on top. This page lists every shipped command with the flags that actually matter.

## The 30-second answer

Installing `@hs-x/cli` provides both `hs-x` and its shorter `hsx` alias. They are
identical; this reference uses `hs-x` as the canonical spelling.

Four commands carry a first session end to end:

```sh
hs-x init my-app --yes      # scaffold a project
hs-x connect                # link HubSpot + Cloudflare credentials
hs-x dev                    # run it locally against a real portal
hs-x deploy --yes           # ship it
```

Everything else is listed below, grouped the way you reach for it. **Every command accepts `--json` for machine-readable output** (or `--json-stream` for newline-delimited events; never both), and `hs-x help <command>` prints the same curated help as `<command> --help`.

| Command | What it does |
| --- | --- |
| `hs-x init` | Scaffold a new HS-X project (alias: `create`) |
| `hs-x connect` | Connect HubSpot and Cloudflare credentials |
| `hs-x login` | Authenticate an HS-X account with a dashboard token |
| `hs-x accounts` | List and switch HS-X accounts (also `whoami`) |
| `hs-x logout` | Revoke the active account's session and clear its local token |
| `hs-x link` / `unlink` | Attach unlinked deploy state to an account / clear the session |
| `hs-x check` | Validate project source (alias: `validate`) |
| `hs-x dev` | Local dev server, plus `dev invoke` for one-shot capability runs, `dev status` and `dev cleanup` for override sessions, and `dev start\|logs\|stop\|restart` for background sessions |
| `hs-x react` | React and HubSpot UI-extension health checks: `doctor`, `review`, `rules` |
| `hs-x api` | Raw calls to the HS-X control plane or HubSpot developer API |
| `hs-x flags` | List, create, enable, disable, archive feature flags |
| `hs-x deploy` | Build artifacts and deploy to HubSpot and Cloudflare |
| `hs-x project` | HubSpot-compatible native project commands: `dev`, `info`, `list-builds`, `upload`, `deploy`, `logs`, `validate` |
| `hs-x promote` | Promote a recorded deploy (alias: `deploy promote`) |
| `hs-x rollback` | Roll an environment back to an earlier deploy |
| `hs-x routes` | Show active deploy routes |
| `hs-x secrets` | Store runtime secrets, today `hubspot-oauth set` |
| `hs-x migrate` | Migrate a legacy HubSpot app: `run`, `pull`, `cutover`, `inspect`, `report`, `cards`, `swap-cards` |
| `hs-x list` | List your HS-X projects and their ids |
| `hs-x status` | Project health and discovered capabilities |
| `hs-x logs` | One timeline: deployed Worker invocations plus HubSpot's app logs |
| `hs-x checkpoint` | Aggregate runtime telemetry from the control plane |
| `hs-x drift` | Project drift state from the control plane |
| `hs-x audit list` | Redacted account audit rows |
| `hs-x doctor` | Diagnose local setup |
| `hs-x history` | Recent local command runs |
| `hs-x update` | Update the CLI to the latest npm release (alias: `upgrade`) |
| `hs-x completion` | Shell completion for bash, zsh, fish |

## Flags that work everywhere

A few flags are global rather than per command. `--json` switches any command to a machine-readable result envelope on stdout; `--json-stream` emits newline-delimited events instead (never together with `--json`). `--no-color` strips ANSI styling. `--cwd <dir>` runs a command as if invoked from another directory. `--quiet` suppresses progress output, `--verbose` and `--debug` add detail to errors, and `--no-telemetry` skips the anonymous usage event for that run. Usage telemetry contains the command/subcommand, CLI version, exit code, duration, output mode, OS/architecture/runtime, and CI boolean; it contains no argv, paths, project data, or stable machine identifier. It can also be disabled with `HSX_TELEMETRY_OPTOUT=1` or `DO_NOT_TRACK=1`; a one-time notice on first run says so.

Help is curated by hand, and it hides escape hatches by default. `hs-x <command> --help` shows the flags you normally need; `--help-all` reveals the advanced section (control-plane overrides, local mocks, attribution flags). `hs-x --version` prints the version; `hs-x help <command> [subcommand]` works as a spelled-out alternative to `--help`.

```sh
hs-x deploy --help-all    # include the advanced flags the short help hides
```

Commands that read from the HS-X control plane (`logs`, `drift`, `audit`, and friends) talk to the platform endpoint, `api.hs-x.dev`, by default; the `--control-plane-url` flag and `HSX_CONTROL_PLANE_URL` environment variable exist so an internal build can point at a dev control plane, and end users never set them. Identity flags work the way you'd expect. Most commands accept `--account-id` and `--project-id` (`--project` is an accepted spelling), with `HSX_ACCOUNT_ID` and `HSX_PROJECT_ID` as fallbacks. The project-scoped reads (`status`, `logs`, `drift`, `routes`) fall back one step further, to the `.hs-x/project.json` binding that `link` and `deploy` maintain. Inside a deployed project directory the flag can be omitted entirely.

## Project and account setup

### `hs-x init [name]`

Scaffolds a new HS-X project; `create` is the same command. `--type` picks the app shape (`workflow-action`, `sync-source`, `mcp-server`, or `empty`), `--template` picks a starter (`deals-tagger`, `empty`, `mcp-server`, `sync-source`), `--distribution` chooses `private` (default) or `marketplace`, and `--ui-extension` adds a starter app card (`--no-ui-extension` to skip). `--object-type` picks the CRM object the starter action enrolls (`contact`, `deal` (default), `company`, or `ticket`), `--auth` picks `oauth` (default) or `static`, and `--app-name` sets the HubSpot display name. In a terminal without `--yes`, unanswered choices become a short wizard (Esc goes back, Ctrl+C cancels); `--yes` accepts every default and installs dependencies without prompting; `--no-install` and `--force` do what they say. When you're logged in, init also registers the project with the platform and writes `.hs-x/project.json`; `--no-link` skips that. The `mcp-server` starter registers an external HubSpot Breeze MCP service and requires OAuth; see the [Breeze MCP guide](/docs/guides/breeze-mcp-server).

```sh
hs-x init deal-tagger --type workflow-action --yes
```

### `hs-x connect`

Connects this project to HubSpot and Cloudflare. Run bare in a terminal for the interactive flow, or target one provider with the `hubspot` and `cloudflare` subcommands.

For HubSpot, the key flags are `--developer-account-id`, `--display-name`, and `--pak` (the developer personal access key used for project upload and deploy; interactive runs can discover it from HubSpot CLI config after `hs accounts auth`, or open HubSpot's personal-access-key page for you). Scripted runs must pass all three; a non-TTY run without them exits with `HSX_E_INPUT_MISSING_CONNECT_FLAGS`. `--developer-api-key` is separate and only serves endpoints that document hapikey auth.

For Cloudflare, the connection is OAuth-only: a browser PKCE flow against Cloudflare's consent screen, with no API token to create or paste. Stored-direct keeps the refresh token in local custody; a logged-in run lets the control plane broker the exchange server-side instead. Without a TTY the CLI prints the authorize URL instead of opening a browser. The Cloudflare account id is discovered when `--cloudflare-account-id` is omitted, and `--display-name` labels the stored credential.

```sh
hs-x connect cloudflare
```

### `hs-x login`

Authenticates an HS-X account with an API token you generate in the dashboard. Interactive runs open the token page and prompt for a paste; scripted runs pass `--token` or set `HSX_API_TOKEN`. The token binds to one account (pick with `--account` when the token grants several) and that account becomes the active default.

```sh
hs-x login --token "$HSX_API_TOKEN"
```

### `hs-x accounts`

Manages the local account store: `list`, `current`, `switch <id>`, and `logout <id>` (`account`, `ls`, `set`, and `remove` are accepted spellings). `hs-x whoami` is shorthand for `accounts current`; bare `hs-x logout` removes the active account.

```sh
hs-x accounts switch acct_456
```

### `hs-x link` and `hs-x unlink`

`link` attaches a project's unlinked (`local_*`) deploy state to your HS-X account; it needs a logged-in session and a deployed project. `--account-id` overrides the active account and `--yes` skips the confirmation. `unlink` clears the local session token (with a best-effort server-side revoke) while leaving project state in your Cloudflare account untouched.

```sh
hs-x link --yes
```

## Validate, run, poke

### `hs-x check`

Validates project source and reports diagnostics; `validate` is the same command. Exits non-zero on findings, which makes it the natural CI gate. `--profile`/`-p <name>` validates a native HubSpot project under one of its profiles.

```sh
hs-x check
```

### `hs-x dev`

Starts the local dev server. Inside an HS-X project it starts without prompting: the portal comes from the HubSpot account bound by `hs-x connect hubspot` (or the HubSpot CLI's default account), the dev override on the deployed Worker defaults to *observe* mode (stream its request and `logger.*` lines into the terminal) with a two-hour TTL, and every component runs; `--pick` opens a component picker. From a parent workspace a terminal asks which local project to run. Scripted runs pass `--portal`, `--project-id`, `--account-id`, and `--ttl-seconds` directly. The server listens on port 8787 unless `--port` picks another, and the unified log sidecar that card loggers forward to listens on 9099 unless `--hsx-log-port` moves it. `--only <selector>` scopes the log stream (a lane such as `frontend`, `backend`, `request`, or `hubspot`; a level such as `errors` or `warnings`; `cap:<id>`; or free text; repeatable), `--capability <id>` (repeatable) scopes the override to specific capability ids, `--force` clears matching overrides before registering, `--telemetry-origin <url>` replaces the auto-managed `cloudflared` quick tunnel with your own, `--target-origin <url>` flips the override to *proxy* mode (live invocations forwarded to an origin that serves `POST /capabilities/<id>/invoke`), `--no-hubspot` skips the HubSpot dev integration, `--no-ui-extensions` skips UI-extension bridging, and `--detach` is `dev start`. Live log streaming needs `cloudflared` on your PATH.

In a tree with `hsproject.json` and no `hsx.config.ts`, `hs-x dev` runs HubSpot's own dev session for the native project (`hs-x project dev` is the same command) and follows `hs project dev`'s startup semantics. The project account is the HubSpot CLI's selected account unless `--project-account` overrides it. The testing account comes from `--testing-account`; without it, a terminal asks which configured account to test on, listing the project account's developer test accounts first, then its sandboxes, then the project account itself marked as production (a project account that is already a test account or sandbox is used without asking). `--yes`, `--json`, and non-TTY runs skip the picker, test on the project account, and warn `HSX_W_DEV_TESTING_ON_PRODUCTION_ACCOUNT`; hs-x lists only accounts the HubSpot CLI already knows, so create a sandbox with `hs sandbox create` or a developer test account from your developer account and `hs account auth` it. For marketplace apps hs-x then counts the portals that have the app installed (test portals excluded); when there are any it prints the count as `HSX_W_DEV_PRODUCTION_APP_INSTALLS` and a terminal confirms `Proceed with local development of this production app?` (default No; `--yes` proceeds with the warning, and the JSON payload carries `productionInstallCount`). `--component <uid>` (repeatable) picks components, `--profile`/`-p` picks a profile, and if HubSpot's dev port is occupied an interactive run identifies the process and offers to stop it before retrying. `--local-proxy <auto|always|never>` controls the app's `local.json` proxy map: `auto` (default) applies it only while every target is listening and otherwise keeps HubSpot's own proxy for every `hubspot.fetch`, `always` applies it regardless and enters proxy mode even without a `local.json`, and `never` ignores the file. For apps where HubSpot cannot run app functions locally (anything but private static-auth on 2026.03+), card console and logger output reaches the terminal through that same local proxy mode via an hs-x relay mapping, so the `frontend` lane needs a live local target or `--local-proxy always`; in proxy mode, fetches to unmapped origins leave your machine and are signed only when `HUBSPOT_CLIENT_SECRET` (or `CLIENT_SECRET`) is set. A register conflict with a session left behind by a crashed run is replaced automatically.

`dev status` probes the local dev server on `--port` (its `/_hsx/health` and manifest) and works unlinked; pass `--account-id` and `--control-plane-url` (or `HSX_ACCOUNT_ID` and `HSX_CONTROL_PLANE_URL`; there is no stored-account fallback) to additionally list the portal-scoped override sessions recorded on the control plane. `dev cleanup --control-plane-url <url> --account-id <id> --session-id <id>` removes one; a terminal confirms (default No) and `--yes` is required for JSON or non-TTY runs. `dev status --session <id>` reports on a managed background session instead (next entry).

```sh
hs-x dev --portal 46993937
```

### `hs-x dev start`, `logs`, `stop`, `restart`

The agent-managed form of the same server. `hs-x dev start` (or `hs-x dev --detach`) launches it in the background and returns a stable session id; `--wait-seconds <n>` (default 60) bounds how long start waits for the health probe. `hs-x dev logs --session <id>` reads the session's append-only event stream, with `--follow` to keep reading, `--lane <lane>` (`frontend`, `backend`, `request`, `hubspot`; repeatable) and `--level <level>` (`debug`, `info`, `warn`, `error`; repeatable) to filter, `--search <text>` for a case-insensitive match, `--since <time>` (an ISO timestamp or a duration such as `30s`, `5m`, `2h`), `--limit <n>`, `--cursor <n>` to resume from a byte offset, and `--json-stream` for NDJSON in follow mode (`--follow --json` is refused, since JSON is a finite document). `hs-x dev status --session <id>` reports whether that session is alive. `hs-x dev stop --session <id>` and `hs-x dev restart --session <id>` verify the saved PID still identifies the exact session before signalling it; non-TTY and `--json` mutations require `--yes`, and `--force` escalates to SIGKILL only after a graceful shutdown times out.

```sh
hs-x dev start --json
hs-x dev logs --session dev_01JYQ4V9K3M8Z2R7T6B5N1PXWA --follow --lane backend
hs-x dev stop --session dev_01JYQ4V9K3M8Z2R7T6B5N1PXWA --yes
```

### `hs-x dev invoke <capability-id>`

Runs one capability through the production runtime router, in process: payload validation, context construction, handler, and result envelope are exactly what a deployed Worker executes. Fixture defaults fill the dispatch payload (each declared input field gets its `default`, else a typed sample); override them with `--input`, `--object`, and `--install` (each takes JSON), or replay the last recorded invocation with `--last`. `--remote` dispatches the same invocation at the deployed Worker instead — real install tokens, real portal — with `--url <origin>` to pin the Worker origin rather than resolving it through the Cloudflare API (which needs `CLOUDFLARE_API_TOKEN`). `--cwd <dir>` picks the project root. The exit code is 1 when the dispatch fails or the runtime envelope reports `ok: false`. Under Bun the worker source is re-imported on every dispatch. The running dev server exposes the same engine over HTTP at `POST /_hsx/invoke/<capability-id>`; the [dev HTTP reference](/docs/dev-http) has the envelopes.

```sh
hs-x dev invoke tag-high-value-deals --object '{"id":"d1","objectType":"deals","properties":{"amount":"99000"}}'
```

### `hs-x api <path>`

Raw HTTP against the HS-X control plane, or against the HubSpot developer-account API with the `hubspot` target. Request bodies build from arguments httpie-style: `field=value` for strings, `field:=value` for typed JSON, `field==value` for query parameters, `Header:Value` for headers. `-X` sets the method (default GET), `--pak` supplies the HubSpot developer key when the environment doesn't, and `--base-url` overrides the target's base URL (`HSX_CONTROL_PLANE_URL` and `HSX_HUBSPOT_BASE_URL` are the environment equivalents). Per-portal CRM data is out of reach for both targets; that traffic goes through the control plane after the install account connects.

```sh
hs-x api hubspot apps-dev/external/public/v3/full/portal
```

### `hs-x flags`

Manages feature flags on the project's tenant runtime: `list`, `create --file <path>` (a JSON flag definition; `define` is an alias), and `enable`, `disable`, `archive`, each taking `--key`. In a terminal, bare `hs-x flags` chooses an action, `create` without `--file` runs a guided boolean-flag wizard, and `--key` may be omitted to pick from a list; every mutation shows a default-No confirmation, and JSON or non-TTY runs require `--yes`. Scope defaults come from `.hs-x/project.json`, `.hs-x/hubspot.json`, and the active deploy record; override with `--project-id`, `--app-id`, `--environment`, `--runtime-origin`, or `--cloudflare-api-token`.

```sh
hs-x flags enable --key new-pricing
```

### `hs-x react`

Inspects HubSpot React UI extensions against the shared HS-UIX rule catalog, with no HS-X login, Cloudflare account, network, or migration required. `react doctor` runs generic React health checks (hooks, memoization, correctness, performance, code quality) plus HubSpot UI-extension checks with stable rule ids: `--scope full|files|changed|lines` with `--base <ref>` narrows to a Git diff, `--staged` scans staged files, `--ci` reports only findings introduced against the base, `--recursive` discovers every nested `hsproject.json`, `--deep` adds dead files, exports, dependencies, and cycles, `--hubspot-only` skips the generic checks, and `--strict` fails on review-tier findings too. `react doctor why <file:line>` explains every finding at one location. `react review` builds an agent-facing review packet (deterministic findings, component evidence, and the versioned rule sources; `--deep` includes archived trees). `react rules` shows how each catalog rule is handled, and `react rules explain <rule-id>` explains one.

```sh
hs-x react doctor --scope changed --base main
hs-x react doctor why src/app/cards/Card.tsx:42
```

## Deploy, promote, roll back

### `hs-x deploy`

Builds artifacts and deploys to HubSpot and Cloudflare. Without a linked account it runs unlinked, straight against the providers. A bare `hs-x deploy` keeps the combined HubSpot + Cloudflare workflow.

**Plan and consent.** `--plan` (or `--dry-run`) prints what would change without touching anything. In a terminal the deploy shows its plan and stops at `Apply this plan?`; `--yes` skips that confirmation and uses the default account, and a JSON or non-TTY run without `--yes` is refused with `HSX_E_INPUT_INVALID`.

**Targets.** Provider flags make the target set explicit: `--cloudflare-deploy` deploys only to Cloudflare and never infers a HubSpot upload, `--hubspot-upload` selects only HubSpot, and passing both selects a combined deploy. Plan and JSON output report `deploymentMode` plus the requested targets. `--build-only` stops after generating local artifacts; `--cloudflare-dry-run` renders the Cloudflare program without pushing it.

**HubSpot-direct.** In a tree with `hsproject.json` and no `hsx.config.ts`, `hs-x deploy` validates the native project, uploads it, waits for the build, and deploys it using the account the HubSpot CLI selected — no Cloudflare, control plane, or login. Its flags: `--account`/`-a`, `--config`/`-c <path>`, `--use-env`, `--pak <token>`, `--message`/`-m <text>`, `--hubspot-upload-only` (alias `--skip-deploy`, which uploads and builds with `skipAutoDeploy`), and `--force`/`-f`. Flags that need Cloudflare or the control plane fail clearly in this mode.

**Exit codes.** 0 on success; 1 on failure; 10 for input and consent errors; 20 when the Worker shipped but the HubSpot upload failed (`HSX_W_DEPLOY_PARTIAL`).

**Environment.** `--env <value>` (also `--environment`) bakes `dev`, `staging`, or `production` into the generated Worker; the default is `production`. What that value scopes is the [environments reference](/docs/environments-reference).

**Schema.** `--portal-schema-live` diffs each sync's declared schema against the live portal and prints a `PLAN` line per difference; `--apply-schema` applies that plan (creating or altering the portal properties). The pair belongs to a first deploy, and applying needs the `crm.schemas.<object>.write` scope on your personal access key.

**Recording and promotion.** On a linked deploy the revision is recorded in the control plane by default; `--no-record` skips that, and `--record-local` forces it. `--promote-when-healthy` waits for a healthy attestation and promotes in one run, with `--promotion-timeout-ms` raising the default 60-second wait. `--heartbeat` opts unlinked Workers into anonymous heartbeat events; `--no-heartbeat` disables the generated attestation wiring on linked ones. The [deploy lifecycle reference](/docs/deploy-lifecycle) explains what each of these changes.

The advanced flags (HubSpot-only deploys, local mocks, control-plane overrides, CI attribution) stay out of the short help; `hs-x deploy --help-all` lists them.

```sh
hs-x deploy --plan --cloudflare-deploy                   # Cloudflare only
hs-x deploy --plan --cloudflare-deploy --hubspot-upload  # explicit combined deploy
hs-x deploy --env staging --promote-when-healthy         # staging, recorded and promoted
```

### `hs-x project`

HubSpot-compatible commands for a native HubSpot project (an `hsproject.json` tree with no `hsx.config.ts`). They use the account the HubSpot CLI has selected and never contact Cloudflare or the HS-X control plane, so no migration, linking, or HS-X login is needed. `project info` shows the current project, deployed build, components, app id, auto-deploy setting, selected account, and dashboard URL (`--details` lists every component). `project list-builds` lists recent builds (`--project <name>`, `--limit <n>`, 1 to 100). `project upload` uploads the sources and creates a build, the drop-in counterpart of `hs project upload`: `--profile <name>` picks an `hsprofile.<name>.json`, `--message <text>` sets the build message, `--skip-auto-deploy` builds without deploying, and `--plan` shows the account, project, and action without writing. `project deploy` deploys an existing successful build (`--build <id>`, or `--deploy-latest-build` to skip the picker). `project logs` follows the app's HubSpot execution logs (`--function <name>`, `--tail`), and `project validate` parses and validates the project locally. `project dev` is `hs-x dev` for a native project. Upload and deploy also take `--force-create` (create without accepting skipped metadata), `--skip-npm-audit`, and `--timeout-ms <ms>` (build/deploy polling, default 120000); the read commands take `--json-schema` to print their result schema without a request. All of them accept `--account`, `--config`, `--use-env`, `--json`, and HubSpot's `--format-output-as-json` spelling.

```sh
hs-x project upload --profile staging
hs-x project deploy --deploy-latest-build --yes --json
```

### `hs-x promote`

Promotes a recorded deploy to an environment; `deploy promote` is the same command. Takes `--deploy-id` plus `--project-id` and `--account-id` for context.

```sh
hs-x promote --deploy-id dep_0042 --project-id deal-tagger
```

### `hs-x rollback`

Rolls an environment back to an earlier deploy. Linked (after `hs-x login`), it is traffic-first: a membership-gated control-plane plan resolves the target's verified Cloudflare Worker version, the CLI creates a 100% deployment of it and health-probes it (restoring the prior version if the probe fails), and only then records the move and advances the active pointer; pass `--account-id`, `--project-id`, `--deploy-id`, and `--force` to skip prompts, and provide a Cloudflare API token (`HSX_CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_API_TOKEN`). Unlinked, it updates the pointer in tenant Cloudflare KV with `--project-id`, `--deploy-id`, and optionally `--environment`. Unlinked rollback is a bookkeeping change: without revisioned routing, redeploy or re-route Cloudflare traffic yourself for it to affect live requests.

```sh
hs-x rollback --project-id deal-tagger --deploy-id dep_0041
```

### `hs-x routes`

Shows the active deploy routes for a project (`route` and `routing` are accepted spellings). Takes `--project-id`.

```sh
hs-x routes --project-id deal-tagger
```

### `hs-x secrets`

Stores deployment secrets HS-X passes into the tenant Cloudflare runtime. The one shipped scope is HubSpot OAuth app credentials: `secrets hubspot-oauth set` with `--account-id`, `--project-id`, `--hubspot-app-id`, `--client-id`, `--client-secret`, and optionally `--env` (`production`, `staging`, or `dev`). In a terminal, missing fields are prompted and the scope is confirmed before writing; each flag also has an `HSX_*` environment fallback.

```sh
hs-x secrets hubspot-oauth set --env staging
```

## Bring a legacy app over

The migrate family moves a legacy HubSpot app (platform 2023.2, 2025.1, or 2025.2) to a 2026.03 HS-X project. The generated project is a net-new dupe for testing in `hs-x dev`; the original app keeps running untouched until cutover. Bare `hs-x migrate` in a terminal is a guided journey: it discovers `hsproject.json` and `migration.json` sources beneath the workspace, inspects the one you pick, previews its output, and asks for confirmation (default No) before writing a dupe; it never deploys or cuts over. The [migration guide](/docs/guides/migrate) walks the whole journey; this is the command surface.

### `hs-x migrate run [path]`

The one-command migration: detects the legacy source (a project tree with `hsproject.json`, or a `migration.json` input), classifies every component, and generates the dupe from everything portable, including app cards, serverless functions, webhook subscriptions, and workflow actions. Pieces needing a human decision are listed with doc links; they gate cutover, not the dupe. `--out` names the output directory (default `migrated-app`), and re-running after resolving findings is safe.

```sh
hs-x migrate run ./legacy-app --out migrated-app
```

### `hs-x migrate pull`

Pulls a live, UI-configured legacy app's definition from HubSpot — cards, webhook subscriptions, workflow actions — and writes the `migration.json` input that `migrate run` consumes. In a terminal, omit `--app-id` to pick the app from a list of your developer account's migratable apps; `--app-name` overrides the recorded name and `--out` names the output file (default `migration.json`). Needs the HubSpot credentials `hs-x connect hubspot` stores.

```sh
hs-x migrate pull --app-id 123456
```

### `hs-x migrate cutover [path]`

Plans or performs the in-place cutover of the original app to 2026.03, keeping its app id, installs, OAuth grants, and listing. Bare, it prints the dry-run plan: the ordered steps, the one-way door (no downgrade from 2026.03), and every open decision. With `--execute` it performs the cutover once no *blocking* decision remains. A blocker is a source that's missing something cutover needs (a URL, entrypoint, or label) or carries an unsupported component. Advisory findings (“keep forwarding?”, “confirm this surface”) don't block, since an in-place cutover keeps the source as-is. Because it's one-way, `--execute` asks you to type the project name to confirm (`--yes` skips that in automation; a non-TTY run without `--yes` is refused). A 2025.2 source takes the config-only path (platformVersion bump + in-place re-upload, rolled back if the build fails); 2023.2/2025.1 sources run HubSpot's own interactive `hs project migrate`, which needs a terminal to answer its feature prompts.

```sh
hs-x migrate cutover ./legacy-app             # dry-run plan
hs-x migrate cutover ./legacy-app --execute   # perform it (type the project name to confirm)
```

### `hs-x migrate inspect <path>` and `hs-x migrate report <path>`

`inspect` classifies a migration input: what migrates automatically, what needs human review, and whether the replacement needs the Cloudflare runtime. It exits 1 while human-review findings remain, so CI can gate on it. `report` summarizes the same analysis as readiness counts and suggested next steps.

```sh
hs-x migrate inspect ./legacy-app --json
```

### `hs-x migrate cards <path>`

Generates a replacement App Card project from a legacy CRM card migration input, then validates it. `--project` names the output directory. Fails while the input still has human-review findings; resolve those via `inspect` first.

```sh
hs-x migrate cards ./migration.json --project migrated-app
```

### `hs-x migrate swap-cards`

Swaps a live legacy CRM card for its replacement App Card through the HubSpot developer API. Requires `--app-id`, `--legacy-card-id`, and `--replacement-card-id`; `--helpdesk-card-id` covers the help-desk variant when there is one.

```sh
hs-x migrate swap-cards --app-id 123456 --legacy-card-id 1001 --replacement-card-id 2001
```

## Watch, diagnose, maintain

### `hs-x list`

Lists your HS-X projects and their ids — the ids every project-scoped command on this page takes (`status --project`, `logs`, `drift`, `routes`). A linked-platform surface: it reads from the control plane and defaults to the active account, with `--account-id` to pick another.

```sh
hs-x list
```

### `hs-x status`

Bare `hs-x status` validates the local project tree: diagnostics plus the worker capabilities discovered in source. Inside a deployed project it also appends the deployed project's health — active routes, drift, and runtime telemetry — best-effort, so a dead network never breaks the local check. `--project <id>` shows that remote view for any project.

```sh
hs-x status
```

### `hs-x doctor`

Diagnoses local setup: accounts, HubSpot CLI auth, control-plane reachability, and recent HS-X activity. The first command to run when something feels off.

```sh
hs-x doctor
```

### `hs-x logs`

One timeline for everything your app does: Workers Logs rows from your deployed runtime (`[cf]`) merged with HubSpot's own app logs (`[hs]`). Run bare in a terminal for a guided project, source, log-type, and time-range picker; a project bound in the current directory is selected by default. `--source` chooses `all` (default), `workers`, or `hubspot`. Alongside `--project-id` and `--account-id` (or the active account): `--limit` (default 50, clamped to 1–200), `--since-minutes` (default 15, clamped to 1–10080), `--trace` / `--invocation`, `--raw`, `--filter`, `--sample`, `--follow` (alias `--tail`) with `--interval-seconds` (default 3, clamped to 1–60), and `--json`, plus the HubSpot-side `--type <name>`, `--errors-only`, and `--app-id <id>`. What each leg reads, how they degrade, and how to read a trace is [the observability reference](/docs/observability#logs).

```sh
hs-x logs --project-id deal-tagger
hs-x logs --project-id deal-tagger --trace inv_01JYQ4V9K3M8Z2R7T6B5N1PXWA
hs-x logs --project-id deal-tagger --follow --filter source=action,status>=500
hs-x logs --source hubspot --errors-only
```

### `hs-x checkpoint`

Reads the project's aggregate runtime telemetry checkpoint from the control plane: recent failures grouped by fingerprint (occurrences, affected portals, last seen), sampled successes, totals, and latency percentiles. Takes `--project-id`; the same aggregate also appears inside `hs-x status --project <id>`.

```sh
hs-x checkpoint --project-id deal-tagger
```

### `hs-x drift` and `hs-x audit list`

`drift` reports the project's drift state as recorded by the control plane and requires a linked account; it takes `--project-id`. `audit list` prints redacted audit rows for an account and takes `--account-id`, with `--action`, `--resource <type[/id]>`, `--actor <user-id>`, `--outcome` (`success`, `failure`, or `in_progress`), `--since` (`30m`, `24h`, `7d`, or an ISO timestamp), and `--limit` (default 50, clamped to 1–200) as filters. Bare `hs-x audit` in a terminal opens an interactive account, time-range, filter, and event-detail explorer.

```sh
hs-x drift --project-id deal-tagger
```

### `hs-x history`

Shows recent command runs recorded by the local CLI; `--limit` caps the count (default 20).

```sh
hs-x history --limit 5
```

### `hs-x update` and `hs-x completion <shell>`

`update` checks npm for a newer CLI release and installs it, using the package manager that owns the running binary (npm, bun, pnpm, or yarn); `--check` reports without installing, and `--json` is check-only by design (`upgrade` is an alias). `completion` prints a completion script for `bash`, `zsh`, or `fish` and registers both the `hs-x` and `hsx` command names.

```sh
hs-x completion zsh >> ~/.zshrc
```

**When a command misbehaves**

Errors carry a stable `HSX_E_*` code, a hint, and a docs link. Re-run with `--verbose` for the inferred cause, `--debug` for wire-level request and response detail, or `--json` to capture the full error envelope for a bug report. Exit codes follow one convention: 10 for input and consent errors (a missing flag, a mutation refused without `--yes`), 130 for a cancelled prompt, 2 for an unknown `dev` subcommand or a bad `--only` selector, 20 for a partial deploy, and 1 for everything else.

## Where the commands lead

- [Getting started](/docs/guides/getting-started) — the first session, narrated instead of listed.
- [Local dev](/docs/guides/dev-mode) — what `hs-x dev` actually wires up against a real portal.
- [Migrate an app](/docs/guides/migrate) — the journey around `migrate run`, the dupe, and cutover.
- [Environments](/docs/guides/environments) — how deploy, promote, and rollback fit together.
- [Secrets](/docs/guides/secrets) — why the runtime needs `hubspot-oauth set` and what it unlocks.
- [Feature flags](/docs/guides/feature-flags) — authoring the flag definitions `hs-x flags create` consumes.

---

*Last updated: September 1, 2026. Reflects `@hs-x/cli` v0.4.11; flags are copied by hand from `hs-x <command> --help-all`. Refreshed whenever the command surface changes.*

