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
npm i -D @enlitt/cli
npx enlitt --help
npx enlitt key rotate --help # one command's flags, with descriptions and defaultsThe 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
npx enlitt loginThe 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:
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.
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:
npx enlitt reauth --no-wait # prints a URL; the developer approves
npx enlitt reauth --wait
# rerun the key commandIn 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).
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:
{
"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:
npx enlitt login --no-wait
npx enlitt login --wait
npx enlitt whoamiThe 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 aserror: <message>andnext: <action>. - Exit codes.
0success.1any refusal or failure, includingconfirmation_requiredand adoctorrun with anerrorfinding.2a 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):
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 doctorsetup 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. It
prints variable names only. Then mount the login routes from
Login and sessions. 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:
npx enlitt app update --add-redirect-uri http://localhost:3001/auth/enlitt/callback \
--add-origin http://localhost:3001http://localhost:3000 and http://localhost:3001 are different callbacks;
see the redirect rules.
Diagnose a login that fails
npx enlitt doctor --callback-url http://localhost:3001/auth/enlitt/callbackdoctor 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.
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
npx enlitt key list # find the key id
npx enlitt key rotate <key_id> --write-env .env.localRotation 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:
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.localThen 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.
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.
{
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. |
--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. |
Output: { keys: ApiKeyMetadata[] }. Never a secret.
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. |
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, 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:
- Resolve the application:
--app, else the one inenlitt.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. Useapp updateto change an existing one. - Write
enlitt.json. - Write
ENLITT_CLIENT_IDto the env file. - Against staging, also write
ENLITT_BASE_URL, so the SDK calls the same deployment. - If the env file has no
ENLITT_SECRET_KEY, or with--replace-secret, create an App key namedcli 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.
enlitt.json and the env file
enlitt.json sits at your project root (the nearest folder with a package.json
or .git):
{
"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, 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.
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. --revealprints a secret in human output only. It is refused with--jsonand 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.