# 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 tour

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**, 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`, `logout`) |
| `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 |
| `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 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. `--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.

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`, or `empty`), `--template` picks a starter (`deals-tagger`, `empty`, `sync-source`), `--distribution` chooses `private` (default) or `marketplace`, and `--ui-extension` adds a starter app card (`--no-ui-extension` to skip). `--yes` accepts every default and installs dependencies without prompting; `--no-install` and `--force` do what they say.

```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` 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`). `--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 the code exchanged server-side by the control plane — no API token to create or paste. 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.

```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.

```sh
hs-x check
```

#### `hs-x dev`

Starts the local dev server. In a terminal it prompts for the portal id, public target origin, TTL, and which components to serve; scripted runs pass `--portal`, `--target-origin`, `--project-id`, `--account-id`, and `--ttl-seconds` directly. The server listens on port 8787 unless `--port` picks another. `dev status` lists active portal-scoped override sessions on the control plane and `dev cleanup --session-id <id>` removes one.

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

#### `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; 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. The running dev server exposes the same engine over HTTP at `POST /_hsx/invoke/<capability-id>`.

```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) and `--pak` supplies the HubSpot developer key when the environment doesn't. 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 crm-dev-applications/v3/apps
```

#### `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`. Scope defaults come from `hsx.config.ts` and the local Cloudflare pointer; override with `--project-id`, `--app-id`, `--environment`, or `--runtime-origin`.

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

## 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. `--plan` (or `--dry-run`) prints what would change without touching anything; `--yes` skips the confirmation and uses the default account. `--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. `--hubspot-upload` and `--cloudflare-deploy` select sides explicitly, `--build-only` stops after generating local artifacts, and `--promote-when-healthy` waits for a healthy deploy and promotes it in one run. `--heartbeat` opts unlinked Workers into anonymous heartbeat events.

```sh
hs-x deploy --plan        # see the plan first, then run with --yes
```

#### `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. 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 "keep forwarding?" / "confirm this surface" findings 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]` — card and extension renders, production frontend logger output, webhook deliveries, API calls, OAuth authorizations). 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`, and the two legs degrade independently — Workers Logs need a linked HS-X account, HubSpot logs need only a deployed app and HubSpot CLI credentials. Alongside `--project-id` and `--account-id` (or the active account): `--limit`, `--since-minutes`, `--trace` / `--invocation`, `--raw`, `--filter`, `--sample`, `--follow`, `--interval-seconds`, and `--json`, plus the HubSpot-side `--type <name>` (log type, repeatable or comma-separated), `--errors-only`, and `--app-id <id>` (defaults to the project's `.hs-x/hubspot.json` binding). Every row carries `origin=workers|hubspot`.

```sh
hs-x logs --project-id deal-tagger
hs-x logs --project-id deal-tagger --trace inv_01JY...
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`.

```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.

## 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: July 24, 2026. Reflects `@hs-x/cli` v0.3.0; flags and subcommands mirror the CLI's own curated help. Refreshed whenever the command surface changes.*

