```bash
npx @cosmoops/connect-sdk init      # scaffold the bridge route, /plans and .env.local
npx @cosmoops/connect-sdk doctor    # check an existing setup and report what is missing
```

Both take an optional project directory and default to the current one. A bare
path runs `init`, so `connect-sdk init ./my-app` and `connect-sdk ./my-app` are
the same command. `--help` prints the pair.

## `init`

Writes three files into a Next.js App Router project:

| File | What it is |
| --- | --- |
| `app/api/connect/[...connect]/route.ts` | The bridge — the only place your API key is used |
| `app/plans/page.tsx` | The full pricing page, one line of it |
| `.env.local` | The two variables, ready to fill in |

**Nothing is ever overwritten.** A file that already exists is reported as
skipped, so re-running after a partial setup is safe, and running it on an app
that already has Connect wired up does nothing at all.

It then prints the three steps a scaffolder must *not* do for you, and the
reason each is excluded is worth knowing:

1. **Wrapping your layout in `ConnectProvider`** would mean parsing and
   rewriting JSX somebody else wrote.
2. **Importing the theme** has to happen in *your* stylesheet, in a specific
   position — Tailwind, then `@heroui/styles`, then
   `@cosmoops/connect-sdk/theme.css` so its values win. Imported from
   `node_modules` instead, Tailwind roots its content scan there and skips your
   app.
3. **Registering the redirect URI** happens inside Connect, not in your repo.

<Callout title="Why this is a CLI and not a plugin">
Next has no plugin API that injects routes. `next.config` can rewrite and
redirect, but both need a real route to point at, and the App Router discovers
routes by walking your own `app/` directory and nothing else — so a package
cannot add `/plans` to an app by being installed. What it can do is make the
file that must exist a single line, and then write that file for you.
</Callout>

## `doctor`

`check` is an alias for the same command, if that reads better in a CI step.

Checks the setup `init` cannot write, which is exactly where integrations
break. Each of those failures is silent or misdirected on its own: a missing
provider throws from a hook, a mis-ordered theme import quietly loses to
HeroUI's defaults, and an unregistered redirect URI only surfaces as an OAuth
error after a round trip to Connect.

```
  ok    Bridge route handler (app/api/connect/[...connect]/route.ts)
  ok    ConnectProvider in a layout (app/layout.tsx)
  ok    Theme stylesheet imported (app/globals.css)
  ok    CONNECT_API_KEY set
  FAIL  CONNECT_CLIENT_ID set
 note   Redirect URI registered in Connect
```

- **It is offline and needs no credentials**, so it is safe to run in CI on a
  pull request. Connect exposes no endpoint listing a client's registered
  redirect URIs, so there is nothing to check remotely that would justify the
  cost.
- **`.env.local` is read from disk**, not through `process.env` — the
  interesting case is a developer running this in a shell that never loaded it,
  which is exactly the shell `next dev` will inherit. A variable declared but
  left empty is reported as missing, not as set.
- **The bridge is found by what it exports**, not by where it sits, because
  `createConnectHandlers({ basePath })` exists precisely so the route can be
  mounted elsewhere.
- **The exit code is the point.** `FAIL` means the app is broken and returns
  `1`; `note` means this cannot tell from the outside and returns `0`. The
  redirect URI is always a note.
- **It catches the split integration.** If `CONNECT_APP_URL` is set and your
  layout passes no `appUrl`, the provider check downgrades to a note: your
  server talks to that instance while every link the browser builds opens
  production Connect. It is a note rather than a failure because the prop may
  be threaded from the layout into a `"use client"` shell, which this cannot
  see. See [Environments](/docs/environments#the-browser-half-points-somewhere-too).

```
 note   ConnectProvider in a layout
        CONNECT_APP_URL is set, but app/layout.tsx passes no appUrl - so your
        server talks to that instance while every link the browser builds opens
        production Connect. Spread it in:
               <ConnectProvider {...connectProviderProps()}>
```

## Next steps

- **[Getting started](/docs/getting-started)** — what `init` writes, by hand,
  with the reasoning.
- **[Environments](/docs/environments)** — the variables `doctor` checks, and
  which Connect instance they point at.