# npx bitelio init (/guides/cli-init)

One command, run in the root of your application:

```bash
npx bitelio@latest init
```

`@latest` matters if your project already depends on the `bitelio` SDK. `npx` prefers a copy already
installed over fetching one, and versions before 0.2.0 had no command in them — a plain
`npx bitelio init` there fails with npm's `could not determine executable to run`, which explains
nothing. Naming the version skips the local copy.

It signs you in, gives you a project and a key, reads your repository, proposes the transactional
emails your application should send, and opens a pull request with the code to send them.

Two things to know before anything else.

**The key it gives you is a test key.** A test key does everything a live key does and stops before
the provider: every send is recorded, none is delivered. That is what lets your first send work in
the same sitting rather than waiting on DNS — and it is also the one way this can bite you. A test
key in production configuration is an application that deploys, runs, reports success on every send,
and delivers nothing. Mint a live key in **Settings → API keys** when you are ready to send for real.

**New projects wait a few hours before they can send live mail.** Test sends are exempt, so nothing
here is blocked by it, but a live send from a project created minutes ago is refused. The CLI tells
you the time it unlocks.

## What it reads

Only files a rule can justify, and it shows you every one before anything leaves your machine:

| It looks for                        | To learn                                                          |
| ----------------------------------- | ----------------------------------------------------------------- |
| `package.json`                      | your framework, whether you use Stripe, and your test command     |
| `app/` or `pages/`                  | which router, which decides where generated code goes             |
| `stripe.webhooks.constructEvent`    | your Stripe handler — subscription, payment failure, cancellation |
| a Svix verification                 | your signup handler — signup, email verification                  |
| `schema.prisma` or a Drizzle schema | what your user model is called                                    |
| the file tree, paths only           | where everything else is, without reading it                      |

Handlers are found by the call that makes them handlers, not by their path, so it does not matter
where you file them.

**No `.env` file is ever opened**, whatever it is called — not even to inspect. Everything else
passes a secret filter first: anything shaped like a key is replaced with `<redacted>`, and the
screen tells you how many were replaced in each file. It is a heuristic and not a guarantee, which
is why you get to read each file exactly as it will be sent, and drop any of them.

To see all of that and nothing else, with no account and no network:

```bash
npx bitelio@latest init --dry-run
```

## What it writes

A branch of its own, from `HEAD`. If you have uncommitted changes it stops before doing anything, so
whatever it produces you can throw away with one command:

```bash
git branch -D bitelio-init
```

It runs your test command **before and after** the patch. A check that was already failing stays
failing, and the pull request says so rather than blaming the change. A check that passed and now
fails means the patch is undone and you are put back exactly where you were. If your repository has
no test command it runs nothing — and says so loudly in the pull request, because unverified and
verified deserve different amounts of trust.

**No key goes in the pull request.** Only the variable name and a placeholder; the key itself was
printed once in your terminal.

If the GitHub CLI is not installed or signed in, the branch stays and you get the compare URL.

## When it stops

`init` builds your lifecycle from your **Stripe** integration — that is what tells it which events
your application actually has. Without a `stripe` dependency it stops before uploading and says so,
because guessing a lifecycle out of a repository that has none produces a pull request nobody can
test.

**The framework does not matter.** Next.js, Express, Fastify, Remix, NestJS, Hono, or a plain Node
service: the handler is found by the call that makes it one, `stripe.webhooks.constructEvent`, not
by where you filed it. Naming your framework only helps it place NEW files idiomatically — with
Next it knows `app/` from `pages/`; elsewhere it follows how your project already organises itself,
and the consent screen tells you when it is going on convention rather than certainty.

In a monorepo it names the workspace to run it from instead of guessing at one.

## Running it unattended

The consent screen needs a person, so `init` does not upload from CI at all. `--dry-run` works
anywhere. For a scripted run on a machine with a terminal, `--project <id>` picks the project and
`--yes` takes the default where there is one; with several projects on the account and neither flag,
it refuses rather than write into the wrong one.

Three runs per account per day.
