# CLI

Manage your business account, applications, and keys from the terminal with the Enlitt CLI.

The Enlitt CLI is the terminal client for your business account. A coding agent
can drive it without ever seeing a credential. You approve the sign-in in your
browser, and the CLI writes secret keys straight to a file you name.

This page is the complete reference. It covers every command, flag, default,
output shape, finding, and error code, plus recipes for the common tasks.

## Install

```bash
npm i -D @enlitt/cli
npx enlitt --help
npx enlitt key rotate --help   # one command's flags, with descriptions and defaults
```

The CLI needs Node.js 22 or later and has no runtime dependencies.

## Choose the deployment: `--env`

Every command talks to one Enlitt deployment, chosen by `--env`:

| `--env`                | Enlitt site                  | API                              |
| ---------------------- | ---------------------------- | -------------------------------- |
| `production` (default) | `https://enlitt.com`         | `https://api.enlitt.com`         |
| `staging`              | `https://staging.enlitt.com` | `https://staging-api.enlitt.com` |

Each deployment has its own accounts, applications, keys, and CLI sign-in, kept
in a separate credential file. **When you work against staging, pass
`--env staging` on every command**, including `login`, `reauth`, `doctor`, and
the commands the CLI prints for you. A command without it acts on production.
The commands the CLI prints already carry the right `--env`.

## Sign in and approve

```bash
npx enlitt login
```

The CLI prints a URL and a short code (`XXXX-XXXX`) and opens your browser.
Check that the code matches, then approve while signed in to your business
account. If you have no business yet, the approval page lets you create one.
The CLI never asks for a password, sign-in code, Google credential, or cookie.

A coding agent's shell call times out long before a person finishes approving,
so split the step in two:

```bash
npx enlitt login --no-wait    # prints the URL and code once, then exits 0
# ... the developer opens the URL and approves ...
npx enlitt login --wait       # resumes and finishes sign-in
```

`--no-wait` saves the pending approval locally. `--wait` resumes it and polls
until you approve or deny. `--timeout <seconds>` bounds one `--wait`. A timeout
fails with `authorization_pending` but keeps the pending approval, so run
`--wait` again. An approval left unanswered expires after about 10 minutes; then
start again with `--no-wait`.

A sign-in is valid for **90 days** or until revoked. It is bound to one business:
the one you chose when approving. To work in another business, see
[Several businesses](#several-businesses).

### Fresh approval for key commands

Creating, rotating, and revoking a key, and listing Read-only keys, also need an
approval from the **last 15 minutes**. When the last one is older, the command
fails with `reauthentication_required` and changes nothing. Then:

```bash
npx enlitt reauth --no-wait   # prints a URL; the developer approves
npx enlitt reauth --wait
# rerun the key command
```

In an interactive terminal, the key command runs this flow itself and retries
once. One approval covers every key command for the next 15 minutes.

## Roles: what your sign-in can do

The CLI acts with your role in the business, and the role decides which commands
run. `npx enlitt whoami` prints your role and, in `business.allowed_commands`, the
commands you can run.

| Command                                                                | `viewer` | `developer` | `admin` | `owner` |
| ---------------------------------------------------------------------- | -------- | ----------- | ------- | ------- |
| `login`, `reauth`, `logout`, `whoami`, `status`, `doctor`              | Yes      | Yes         | Yes     | Yes     |
| `business info`, `traffic`, `app list`, `app show`, `key list`         | Yes      | Yes         | Yes     | Yes     |
| `app create`, `app update`                                             | No       | Yes         | Yes     | Yes     |
| `setup`                                                                | Link¹    | Yes²        | Yes²    | Yes     |
| `key create`, `key rotate`, `key revoke`, `key list --scope read-only` | No       | No          | No      | Yes     |

¹ A viewer can rerun `setup` on an application that already exists, which links
the project and writes `ENLITT_CLIENT_ID`, but cannot create one.
² Without the owner role, `setup` does everything except create the key. See below.

A command your role cannot run fails with `insufficient_permissions` before it
changes anything. The message names your role and the business, and `action` says
who can do it:

* **Key commands:** only the owner can create, rotate, or revoke keys. Ask the
  owner to run the command, or to create the key on the dashboard's **Keys** page.
* **App commands:** ask the owner or an admin to make you a developer or admin, or
  to run the command for you.

The server checks your role on every request, so a role change applies to your
next command, and removing you from the business stops your sign-in at once.
Members, payouts, and ownership stay in the dashboard for every role (see
[What the CLI cannot do](#what-the-cli-cannot-do)).

### `setup` as a developer or admin

`setup` registers and links the application and writes `ENLITT_CLIENT_ID`, then
skips the key and exits `0`. Its output lists the key under `missing`, with the
action to take:

```json
{
  "wrote": ["ENLITT_CLIENT_ID"],
  "missing": [
    {
      "variable": "ENLITT_SECRET_KEY",
      "action": "Only the business owner can create keys. Ask the owner to create an app key for My App (app_…) and put it in ENLITT_SECRET_KEY in .env.local."
    }
  ]
}
```

Ask the owner for the key, and let them put it in the env file themselves; never
send it through a chat. Until then, `doctor` reports `secret_key_missing`.

### Several businesses

One login can own a business and be a member of others. A CLI sign-in acts for
exactly one of them: the one you choose on the approval page. When you belong to
more than one, that page shows a **Business** picker with your role in each.
Choosing a business there also switches the business your dashboard shows.

To work in another business, sign in again and pick it when approving:

```bash
npx enlitt login --no-wait
npx enlitt login --wait
npx enlitt whoami
```

The new sign-in replaces the old one for that `--env`. In the dashboard, switch
businesses from the business menu at the top of the sidebar; **All businesses and
invitations** lists every business you can reach and the invitations waiting
for you.

## Output, exit codes, and global flags

| Flag                        | Meaning                                                                                                  |
| --------------------------- | -------------------------------------------------------------------------------------------------------- |
| `--json`                    | stdout carries exactly one JSON document: the result, or `{ "error": { "code", "message", "action" } }`. |
| `--env production\|staging` | Which Enlitt deployment and sign-in to use. Default `production`.                                        |
| `--help`, `-h`              | The command list, or one command's flags and defaults (`npx enlitt setup --help`).                       |
| `--version`                 | The CLI version.                                                                                         |

* **Streams.** Results go to stdout. Progress text, such as a URL to open or
  "waiting for approval…", always goes to stderr, so it never corrupts a JSON
  document. Without `--json`, an error goes to stderr as `error: <message>` and
  `next: <action>`.
* **Exit codes.** `0` success. `1` any refusal or failure, including
  `confirmation_required` and a `doctor` run with an `error` finding. `2` a usage
  error: an unknown command or flag, a missing flag, or a malformed value.
* **Stable JSON.** A result may gain fields but never loses or renames one. IDs
  are Enlitt's wire IDs. Money is a NANOUSD integer string in JSON
  (`1 USD = 1,000,000,000 NANOUSD`) and USD in human output.
* **Dates** in flags are `YYYY-MM-DD`, UTC, inclusive. Timestamps in output are
  RFC 3339.

## Common tasks

### Wire a new project

From the project root (the folder with `package.json`):

```bash
npx enlitt login --no-wait && npx enlitt login --wait
npx enlitt setup --name "My App" \
  --redirect-uri http://localhost:3000/auth/enlitt/callback \
  --origin http://localhost:3000
npx enlitt doctor
```

`setup` registers a `test` application, writes `enlitt.json`, and writes
`ENLITT_CLIENT_ID` and a new App key (`ENLITT_SECRET_KEY`) to `.env.local`. Only
the owner can create the key; for other roles see
[`setup` as a developer or admin](#setup-as-a-developer-or-admin). It
prints variable names only. Then mount the login routes from
[Login and sessions](/docs/authentication.md). Against staging, add
`--env staging` to each command; `setup` then also writes `ENLITT_BASE_URL`.

### Add a callback URL or a port

Register both the callback URL and its origin. Leave `enlitt.json` linked so the
CLI knows the application:

```bash
npx enlitt app update --add-redirect-uri http://localhost:3001/auth/enlitt/callback \
  --add-origin http://localhost:3001
```

`http://localhost:3000` and `http://localhost:3001` are different callbacks;
see [the redirect rules](/docs/application-registration.md#redirect-url-rules).

### Diagnose a login that fails

```bash
npx enlitt doctor --callback-url http://localhost:3001/auth/enlitt/callback
```

`doctor` checks the sign-in, the project link, the env file, the client ID, the
secret key, and whether the callback and origin are registered exactly. Each
finding carries the exact command that fixes it. See
[doctor findings](#doctor-findings).

### Answer a question about the account

| Question                                                  | Command                                                        |
| --------------------------------------------------------- | -------------------------------------------------------------- |
| Who am I signed in as, and which business?                | `npx enlitt whoami`                                            |
| Is this project wired up?                                 | `npx enlitt status`, then `npx enlitt doctor`                  |
| How much have I earned? Can I be paid out?                | `npx enlitt business info`                                     |
| How many visitors and calls per day? For one application? | `npx enlitt traffic --days 7`, `npx enlitt traffic --app <id>` |
| Which applications exist, and their client IDs?           | `npx enlitt app list`                                          |
| What callbacks and origins are registered?                | `npx enlitt app show`                                          |
| Which keys exist, and when were they last used?           | `npx enlitt key list`                                          |

### Replace a lost or leaked secret

```bash
npx enlitt key list                                   # find the key id
npx enlitt key rotate <key_id> --write-env .env.local
```

Rotation keeps the key's identity and writes the new secret over the old value
in the env file. The output says when the old secret stops working
(`previous_key.expires_at`), so you know how long a deployed server can keep the
old value. Update every deployment's secret store before then. A `live` key
needs `--confirm`.

### Move to live

A live application is a separate registration with its own client ID and key.
Keep the test one for development:

```bash
npx enlitt app create --name "My App" --environment live \
  --redirect-uri https://my-app.example/auth/enlitt/callback \
  --origin https://my-app.example --confirm
npx enlitt key create --app <live_app_id> --name production \
  --write-env .env.production.local
```

Then copy that secret into the production server's secret manager. A coding
agent must ask you before passing `--confirm`.

## Command reference

### `login`

`npx enlitt login [--label <text>] [--no-browser] [--no-wait | --wait] [--timeout <seconds>]`

| Flag           | Meaning                                                                                                |
| -------------- | ------------------------------------------------------------------------------------------------------ |
| `--label`      | Name shown on the approval page and under **Settings, CLI access**. Default `<hostname> (<platform>)`. |
| `--no-browser` | Do not open a browser; only print the URL. Also the behavior when `CI` is set or `BROWSER=none`.       |
| `--no-wait`    | Start the approval, print the URL and code, save it as pending, and exit `0`.                          |
| `--wait`       | Resume the pending approval and poll until a decision.                                                 |
| `--timeout`    | Seconds one wait may poll. Default: until the approval expires.                                        |

Without `--no-wait` or `--wait`, it starts and waits in one run. Output:
`{ login: { email }, business: { id, name }, credential_id, expires_at }`. With
`--no-wait`: `{ status: "pending", verification_uri_complete, user_code, expires_at }`.
A new sign-in replaces and revokes the previous one for the same `--env`.

### `reauth`

`npx enlitt reauth [--no-browser] [--no-wait | --wait] [--timeout <seconds>]`

Same flags as `login`. Needs a sign-in. Must be approved by the same login, for
the same business. Output: `{ reauthenticated_at }`.

### `logout`

`npx enlitt logout` revokes this CLI sign-in on the server and deletes the local
credential, even if the server call failed. Output: `{ logged_out, revoked }`.
Without a sign-in it prints `{ logged_out: false, revoked: false }` and exits `0`.

### `whoami`

`npx enlitt whoami` prints
`{ login: { email }, business: { id, name, role, permissions, allowed_commands }, environment, api_origin, credential_expires_at }`.
`allowed_commands` lists the commands your role can run, such as `"app create"`.
The human form prints them as "You can run: …" and "Not with your role: …".

### `status`

`npx enlitt status` is `whoami` plus the project link, and works when signed
out. Output:
`{ logged_in, login?, business?, project: { linked, application_id?, env_file?, env_file_has: { ENLITT_SECRET_KEY, ENLITT_CLIENT_ID } } }`.
It reports whether each variable is present, never its value.

### `business info`

`npx enlitt business info [--from <date>] [--to <date>]`

| Flag     | Meaning                                                                        |
| -------- | ------------------------------------------------------------------------------ |
| `--from` | First UTC day of the traffic and usage window. Default: 30 days before `--to`. |
| `--to`   | Last UTC day, inclusive. Default: today.                                       |

Output: `{ business, earnings, payout, traffic: { usage }, range, data_fresh_at, application_count }`.
`earnings.earnings_nano_usd` is the balance you can be paid now, and `payout`
is today's eligibility; neither is limited to the window. The
fields are explained in [Business reporting](/docs/business-reporting.md#response).
In words, it states earnings, whether a payout is possible, and, when not, why
(`blocked_reason`) and when it next can be (`next_eligible_at`).

### `traffic`

`npx enlitt traffic [--app <id>] [--days <n> | --from <date> --to <date>]`

| Flag              | Meaning                                                          |
| ----------------- | ---------------------------------------------------------------- |
| `--app`           | One application. Default: the whole business.                    |
| `--days`          | The last `n` UTC days including today, 1 to 3,660. Default `30`. |
| `--from` / `--to` | An explicit inclusive UTC day range, instead of `--days`.        |

Output:
`{ range, usage, days: [{ date, visitors, standard_requests, premium_requests, premium_earnings_nano_usd }] }`,
one row per day, with zeros for a day with no calls. With `--app`, each day also
has `standard_used_nano_usd` and `premium_used_nano_usd`, and `usage` is that
application's total.

**Visitors** are distinct signed-in users who made at least one call that UTC
day. The same person on two days counts on both, so summing days does not give
unique visitors for a week or month, and no command reports a unique count over
a range. Report the daily figures, or say "visitor-days" for a sum. Business-wide
visitors are not the sum of the per-application counts either.

### `app list`

`npx enlitt app list` prints
`{ applications: [{ id, name, environment, status, client_id, active_key_count }] }`.

### `app show`

`npx enlitt app show [<id>]` prints one application. The ID defaults to the one
linked in `enlitt.json`; without a link it fails with `project_not_linked`.

```ts
{
  id: string;
  client_id: string;            // public; ENLITT_CLIENT_ID
  name: string;
  environment: "test" | "live";
  status: string;               // "active" when sign-in works
  info: { icon_url?: string; description?: string } | null;
  redirect_uris: string[];      // callback URLs, matched exactly
  allowed_origins: string[];
  created_at: string;
  api_keys: ApiKeyMetadata[];   // see key list
}
```

### `app create`

`npx enlitt app create --name <n> --environment test|live [options]`

| Flag                       | Meaning                                                                               |
| -------------------------- | ------------------------------------------------------------------------------------- |
| `--name` (required)        | Name users see on the consent screen.                                                 |
| `--environment` (required) | `test` or `live`. Both consume real credits; see [Test and live](/docs/environments.md). |
| `--redirect-uri <url>`     | A callback URL. Repeat for several.                                                   |
| `--origin <origin>`        | An allowed origin (scheme, host, port). Repeat for several.                           |
| `--icon-url <url>`         | Icon on the consent screen.                                                           |
| `--description <text>`     | Description on the consent screen.                                                    |
| `--link`                   | Also write `enlitt.json` for this application.                                        |
| `--confirm`                | Required with `--environment live`, because real users sign in to it.                 |

Prints the application, as `app show` does.

### `app update`

`npx enlitt app update [<id>] [options]`. The ID defaults to the linked
application. Only the lists and fields you name change.

| Flag                                                        | Meaning                                     |
| ----------------------------------------------------------- | ------------------------------------------- |
| `--name <n>`                                                | Rename.                                     |
| `--redirect-uri <url>`…                                     | Replace the whole callback list with these. |
| `--add-redirect-uri <url>`…, `--remove-redirect-uri <url>`… | Add to or remove from the callback list.    |
| `--origin <origin>`…                                        | Replace the whole origin list.              |
| `--add-origin <origin>`…, `--remove-origin <origin>`…       | Add to or remove from the origin list.      |
| `--icon-url <url>` / `--clear-icon-url`                     | Set or clear the icon.                      |
| `--description <t>` / `--clear-description`                 | Set or clear the description.               |

Mixing a replace form with an add or remove form for one list is a usage error.
A change takes effect at the next sign-in and signs no one out. The linked
`enlitt.json` is updated to match.

### `key list`

`npx enlitt key list [--app <id>] [--scope app|read-only]`

| Flag      | Meaning                                                                                                 |
| --------- | ------------------------------------------------------------------------------------------------------- |
| `--app`   | Only this application's keys. Default: every application's App keys.                                    |
| `--scope` | `app` (default) or `read-only`. `read-only` needs a [fresh approval](#fresh-approval-for-key-commands). |

Output: `{ keys: ApiKeyMetadata[] }`. Never a secret.

```ts
interface ApiKeyMetadata {
  id: string;
  name: string;
  prefix: string; // first characters of the secret, to recognize it
  scope: "model" | "reporting"; // wire values: "model" is an App key, "reporting" a Read-only key
  application_id: string | null; // null for a Read-only key
  environment: "test" | "live" | null;
  status: "active" | "revoked" | "expired";
  created_at: string;
  expires_at: string | null; // set on a rotated-out key: when it stops working
  last_used_at: string | null;
}
```

### `key create`

`npx enlitt key create (--app <id> | --scope read-only) --name <n> --write-env <file> [--replace] [--var <NAME>]`

| Flag                 | Meaning                                                                                          |
| -------------------- | ------------------------------------------------------------------------------------------------ |
| `--app <id>`         | Create an App key for this application.                                                          |
| `--scope read-only`  | Create a business-wide Read-only key instead.                                                    |
| `--name` (required)  | A label for the key.                                                                             |
| `--write-env <file>` | The env file to write the secret to, inside the project.                                         |
| `--var <NAME>`       | Variable name. Default `ENLITT_SECRET_KEY` (App key) or `ENLITT_REPORTING_KEY` (Read-only key).  |
| `--replace`          | Overwrite the variable if the file already has it. Default: refuse with `env_key_exists`.        |
| `--reveal`           | Print the secret instead of writing it. Human terminals only; see [secret rules](#secret-rules). |

Output: `{ key: ApiKeyMetadata, secret_written_to: { file, variable }, gitignored }`.
Without `--write-env` or `--reveal` it refuses with `secret_destination_required`
before any request, so no key is created that nobody captured. Needs a fresh
approval.

### `key rotate`

`npx enlitt key rotate <key_id> --write-env <file> [--var <NAME>] [--confirm]`

Same destination flags as `key create&#x60;, with one difference: **`--replace` is on
by default**, because replacing the old value is the point. Rotation keeps the
key's ID, name, type, and application, and issues a new secret. The old secret
keeps working until `previous_key.expires_at`. A `live` App key and every
Read-only key need `--confirm`. Needs a fresh approval. Output:
`{ key, secret_written_to, gitignored, previous_key }`.

### `key revoke`

`npx enlitt key revoke <key_id> --confirm` disables a key immediately and
permanently; calls with it fail at once. Always needs `--confirm` and a fresh
approval. Output: `{ revoked: ApiKeyMetadata }`.

### `setup`

`npx enlitt setup [options]` wires a project in one step and is safe to rerun.

| Flag                       | Meaning                                                                                      |
| -------------------------- | -------------------------------------------------------------------------------------------- |
| `--app <id>`               | Use this existing application.                                                               |
| `--name <n>`               | Name for a new application. Required when neither `--app` nor `enlitt.json` names one.       |
| `--environment test\|live` | Environment of a new application. Default `test`.                                            |
| `--redirect-uri <url>`…    | Callback URLs of a new application.                                                          |
| `--origin <origin>`…       | Allowed origins of a new application.                                                        |
| `--env-file <file>`        | The env file to write. Default `.env.local`.                                                 |
| `--replace-secret`         | Create a new App key even if the env file already has `ENLITT_SECRET_KEY`, and overwrite it. |
| `--confirm`                | Required only when `setup` creates a live application.                                       |

It runs these steps in order:

1. Resolve the application: `--app`, else the one in `enlitt.json`, else create
   one from the flags. When the application already exists, the creation flags
   (`--name`, `--environment`, `--redirect-uri`, `--origin`) are ignored, and
   stderr says so. Use `app update` to change an existing one.
2. Write `enlitt.json`.
3. Write `ENLITT_CLIENT_ID` to the env file.
4. Against staging, also write `ENLITT_BASE_URL`, so the SDK calls the same deployment.
5. If the env file has no `ENLITT_SECRET_KEY`, or with `--replace-secret`,
   create an App key named `cli setup <date>` and write it.

An existing secret is never rotated or overwritten without `--replace-secret`.
Output: `{ application: { id, name, environment, client_id }, env_file, wrote, missing, gitignored, project_file, next }`.
`wrote` lists the variable names written, never values. `missing` lists what
`setup` could not write with your role, each with an `action`; it is `[]` for the
owner. `next` is the docs page to read next. Creating the key needs the owner
role and a fresh approval.

### `doctor`

`npx enlitt doctor [--app <id>] [--env-file <file>] [--callback-url <url>]…`

| Flag             | Meaning                                                                                                     |
| ---------------- | ----------------------------------------------------------------------------------------------------------- |
| `--app`          | Check this application. Default: the linked one.                                                            |
| `--env-file`     | The env file to check. Default: the one in `enlitt.json`, else `.env.local`.                                |
| `--callback-url` | A callback URL your app will use. Repeat for several. Checked together with `enlitt.json`'s `redirectUris`. |

Output: `{ ok, findings: [{ code, severity, message, action }] }`. Exits `1`
when any finding is an `error`. It reads the secret only to compare it locally
and never prints it.

## Doctor findings

Checks run in this order. A check that depends on a failed one is skipped, so fix
the first finding and run `doctor` again.

| Code                     | Severity | Meaning and fix                                                                                                          |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `not_logged_in`          | error    | No CLI sign-in for this `--env`. Run `npx enlitt login`.                                                                 |
| `project_not_linked`     | warning  | No `enlitt.json` and no `--app`. Run `npx enlitt setup`.                                                                 |
| `application_not_found`  | error    | The application is not in the signed-in business. Check `--env` and the business in `whoami`.                            |
| `application_not_active` | error    | The application is not active, so sign-in to it fails.                                                                   |
| `env_file_missing`       | error    | The env file does not exist. Run `npx enlitt setup`.                                                                     |
| `env_file_tracked`       | error    | git tracks the env file. Remove it from git and use an untracked file.                                                   |
| `env_file_not_ignored`   | warning  | git does not ignore the env file. Add it to `.gitignore`.                                                                |
| `env_file_permissions`   | warning  | Others can read the env file. Run `chmod 600` on it.                                                                     |
| `client_id_missing`      | error    | No `ENLITT_CLIENT_ID`. Run `npx enlitt setup`.                                                                           |
| `client_id_mismatch`     | error    | `ENLITT_CLIENT_ID` belongs to another application. Run `npx enlitt setup` for the right one.                             |
| `secret_key_missing`     | error    | No `ENLITT_SECRET_KEY`. Run `npx enlitt setup`.                                                                          |
| `secret_key_not_active`  | error    | The secret is not an active key of this application: another application's, revoked, or expired. Create or rotate a key. |
| `callback_mismatch`      | error    | A callback URL is not registered exactly. The action is the `app update` command that adds it and its origin.            |
| `origin_mismatch`        | error    | An origin in `enlitt.json` is not registered. The action adds it.                                                        |
| `environment_mismatch`   | warning  | `enlitt.json` says `test` and the application is `live`, or the reverse.                                                 |

## Errors and their actions

With `--json` an error is `{ "error": { "code", "message", "action" } }`. Without
it, stderr gets `error: <message>` and `next: <action>`. Do what `action` says;
its commands already carry your `--env`.

| Code                                        | Action                                                                                                                     |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `not_logged_in`, `dashboard_login_required` | Run `npx enlitt login`.                                                                                                    |
| `reauthentication_required`                 | Run `npx enlitt reauth --no-wait`, have the developer approve the printed URL, run `npx enlitt reauth --wait`, then retry. |
| `insufficient_permissions`                  | Your role does not allow this, or the CLI cannot do it. Ask the business owner, or use the dashboard.                      |
| `business_selection_required`               | You no longer have access to this business. Run `npx enlitt login` again.                                                  |
| `confirmation_required`                     | Ask the developer to confirm, then rerun with `--confirm`.                                                                 |
| `secret_destination_required`               | Rerun with `--write-env <file>`.                                                                                           |
| `env_file_tracked`                          | The file is committed to git. Use an untracked file such as `.env.local`.                                                  |
| `env_key_exists`                            | The file already holds this variable. Rerun with `--replace` only if the developer wants it overwritten.                   |
| `insecure_credential_file`                  | Run `chmod 600 <path>`, then retry.                                                                                        |
| `project_not_linked`                        | Run `npx enlitt setup` or pass `--app <id>`.                                                                               |
| `no_pending_authorization`                  | Run `--no-wait` first; there is no pending approval to resume, or it expired.                                              |
| `authorization_pending`                     | The developer has not approved yet. Run the same command with `--wait` again.                                              |
| `access_denied`                             | The developer denied the request in the browser.                                                                           |
| `expired_token`                             | The approval expired. Start again with `--no-wait`.                                                                        |
| `reveal_requires_terminal`                  | `--reveal` works only in a human terminal. Use `--write-env <file>`.                                                       |
| `usage_error`                               | Fix the command; `--help` lists the flags. Exit code `2`.                                                                  |
| `network_error`                             | Check the network and retry.                                                                                               |

Any other code: see [Errors and recovery](/docs/errors.md).

## enlitt.json and the env file

`enlitt.json` sits at your project root (the nearest folder with a `package.json`
or `.git`):

```json
{
  "applicationId": "…",
  "environment": "test",
  "redirectUris": ["http://localhost:3000/auth/enlitt/callback"],
  "allowedOrigins": ["http://localhost:3000"],
  "envFile": ".env.local"
}
```

It records what the project expects and never holds a secret, so it is safe to
commit. `doctor` compares it with what Enlitt has registered, and `app update`
keeps it in sync.

The env file gets at most these variables:

| Variable               | Written by                          | Secret? |
| ---------------------- | ----------------------------------- | ------- |
| `ENLITT_CLIENT_ID`     | `setup`                             | No      |
| `ENLITT_SECRET_KEY`    | `setup`, `key create`, `key rotate` | Yes     |
| `ENLITT_REPORTING_KEY` | `key create --scope read-only`      | Yes     |
| `ENLITT_BASE_URL`      | `setup`, against staging only       | No      |

The file must stay inside the project. The CLI refuses a file that git tracks.
For an untracked file that git does not ignore, it adds the file to `.gitignore`
and reports `gitignored: true`. Every other line of the file is kept unchanged.
The file is written with owner-only permissions. Your deployed server needs the
same variables in its own secret storage; the CLI writes only local files.

## Where the credential lives, and how to revoke it

The CLI stores its sign-in in `cli-<env>.json` in `~/.config/enlitt` (or
`$XDG_CONFIG_HOME/enlitt`; `%APPDATA%\enlitt` on Windows), readable only by you.
It is a CLI credential, not a browser session. Within your
[role](#roles-what-your-sign-in-can-do), it can read the business, its
applications and traffic, and manage applications and keys. It cannot do the
things listed under [What the CLI cannot do](#what-the-cli-cannot-do).

Revoke it with `npx enlitt logout`. You can also revoke it under **Settings, CLI
access** in the business dashboard, which works for a machine you no longer have.
Removing a member from the business stops their CLI sign-in at once.

## Secret rules

* A raw API key is written only to the file you named. It never appears on stdout
  or stderr, in JSON, in `enlitt.json`, in an error, or in a log.
* `--reveal` prints a secret in human output only. It is refused with `--json`
  and whenever stdout is not a terminal, so an agent's shell cannot use it.
* Do not paste a key into a chat, an issue, or a commit.

## What the CLI cannot do

The following are dashboard-only. The server refuses them to a CLI sign-in even
if you hold the owner role:

* payouts and payout onboarding;
* members and invitations;
* ownership transfer;
* business creation;
* account settings.

`--confirm` is the only confirmation. There is no prompt, so the rule is the
same with and without a terminal.
