CLI reference
The oe command. Installation, every command and flag, the exit codes, the environment variables, the JSON envelope, and agent mode.
oe is the OpenE2EE command-line interface. It creates and links Relay projects, and keeps open-e2ee.config.ts and the console in step. It also stages iOS notification profiles. oe help prints the commands, global flags, exit codes, and environment variables on this page. oe help --json returns them as data.
Install
oe needs Node.js 22.18 or later. Install it globally:
npm install --global @open-e2ee/oeTo run one command without an installation, use npx @open-e2ee/oe@latest in place of oe:
npx @open-e2ee/oe@latest auth loginDo not run npx oe. The npm package oe is not an OpenE2EE package.
@open-e2ee/oe is a small Node.js launcher. npm installs the native package for macOS, Linux, or Windows on arm64 or x64 as an optional dependency. Installation does not run a postinstall download.
Commands
Global flags can come before or after the command. oe COMMAND --help shows the usage of one command.
| Command | What it does |
|---|---|
oe new [signal-relay] [--project SLUG] [--name NAME] [--env-var NAME] [--dry-run] | Creates a project and its Sandbox environment. Writes open-e2ee.config.ts, the Relay connection in .env.local, and the @open-e2ee/oe devDependency. |
oe auth login [--accept-terms] [--organization ORG] [--timeout DURATION] | Logs in with a browser and stores the session in the operating-system keychain. --accept-terms accepts the OpenE2EE terms for the organization. --organization puts the session in that organization. Without it, a person at a terminal who belongs to more than one organization chooses one. --timeout defaults to 5 minutes. |
oe auth status | Shows the person, the organization, the terms state, and where the session is. When the control API lists the organizations, the last line names each one with its role and marks the current one. Exits with code 4 without a session. |
oe auth logout | Removes the stored session. |
oe org list | Lists the organizations of the account with the role of each, and marks the current one with *. |
oe org switch [ORG] | Moves the stored session to another organization. ORG is an ID, or a name that only one organization has. Without ORG, a person at a terminal chooses one. |
oe doctor [--env sandbox|production] [--wait] [--timeout DURATION] | Checks the config, session, project, and Relay connection of the selected environment. --wait then waits for the first acknowledged Sandbox message. |
oe link [PROJECT] [--env-var NAME] [--yes] [--dry-run] | Links the directory to a Relay project that exists, and writes the Relay connection of each active environment to its env file. Without PROJECT, it rewrites only the env files. |
oe project list | Lists the Relay projects of the organization of the session. When the list is empty and the person belongs to another organization, next is oe org switch. |
oe project show [PROJECT] [--env sandbox|production] | Reads one project and the state of each environment. |
oe project connection [PROJECT] [--env sandbox|production] | Prints the Relay connection URL of one environment. Text mode prints only the URL. |
oe config push [--env sandbox|production] [--yes] [--dry-run] | Applies the Sandbox section of open-e2ee.config.ts, then the Production section when the file has one. |
oe config pull [--env sandbox|production] [--yes] [--dry-run] | Writes the Relay policy of each active environment into open-e2ee.config.ts. |
oe notifications status [--env sandbox|production] | Shows the allowed notification profiles and the providers of the environment. |
oe notifications setup ios [--profile background-only|visible-alert] [--env sandbox|production] | Stages an iOS notification profile. The default profile is background-only. |
oe notifications add-nse [--env sandbox|production] | Stages a generic Notification Service Extension. |
oe notifications verify ios [--app-bundle PATH] [--env sandbox|production] | Verifies the iOS notification setup. --app-bundle inspects a signed .app bundle. |
oe notifications apple-filtering-request | Gives the URL of the Apple request for the notification-filtering entitlement in data.requestUrl of the JSON output. |
oe agent setup [--scope project|user] [--check] | Installs the OpenE2EE skills for coding agents. --check reports each skill as current, missing, or changed, and writes nothing. |
oe version | Prints the CLI version. |
oe help [COMMAND] | Shows all commands, or the usage of one command. |
In oe config, a Production change or a replaced value needs --yes or the answer of a person at a terminal. --dry-run changes nothing.
Notifications
Push is a best-effort wake. The durable Relay mailbox, authenticated pull, and acknowledgment are delivery authority. Delivery and push describes the iOS workflow.
oe notifications status reads the notification settings of the environment. Its JSON data has allowedProfiles, environment, and providers.
oe notifications apple-filtering-request puts https://developer.apple.com/contact/request/notification-service/ in data.requestUrl. Run it with --json to see the URL, because text mode prints only a note. Apple's notification-filtering entitlement lets an approved, signed extension suppress an alert. It does not improve APNs delivery or execution. oe keeps filtering unavailable until Apple approval, signed extension inspection, and physical-device suppression evidence all pass.
Organizations
A person can belong to more than one organization. The session is in one organization at a time. Each project command reads only the projects of that organization.
oe org list
oe org switch "Beta LLC"
oe auth login --organization "Beta LLC"In JSON, oe org list puts the ID of the current organization in data.current. Each entry of data.organizations has id, name, role, current, and switchable.
oe org switch matches a name in any letter case. Without ORG, a person at a terminal chooses from a list, and Enter keeps the current organization. After a switch, next is oe project list. An unknown or ambiguous ORG fails with USAGE_ERROR, and next is oe org list. For an unknown ORG, data.candidates lists every organization of the account. For an ambiguous ORG, it lists the organizations with that name.
oe auth login --organization ORG logs in, then switches to ORG when the session is in another organization. Without the flag, a person at a terminal who belongs to more than one organization chooses one after a new login.
Global flags
| Flag | Effect |
|---|---|
--json | Writes one JSON document to stdout for the result, for success and for failure. oe auth login writes one pending event before it. |
--json-stream | Writes newline-delimited JSON progress events, then one final event, to stdout. |
--agent yes|no|auto | Says whether a coding agent runs oe. The default, auto, reads the environment variables that coding agents set. |
--env sandbox|production, -e | Selects the environment. |
--control-url URL | Uses another control API. The URL must use HTTPS, except on loopback. |
-h, --help | Shows help. |
Without --env, oe doctor, oe project, and oe notifications read OE_ENV, then use sandbox. oe new always uses sandbox. oe config pull reads each active environment, and oe config push applies each environment section of the config. Only --env narrows a pull or a push to one environment. OE_ENV does not.
Environment variables
| Variable | Effect |
|---|---|
OE_ACCESS_TOKEN | A scoped access token for CI or an agent. oe keeps it in memory and never stores it. When it is set, oe uses it in place of the stored login. |
OE_ACCESS_TOKEN_SCOPES | The scopes of OE_ACCESS_TOKEN, separated by commas or spaces. When it is set, oe checks the scope of each command before the request. |
OE_ENV | The environment of oe doctor, oe project, and oe notifications when --env is not given: sandbox or production. oe new and oe config ignore it. |
OE_OPERATION_ID | The idempotency key for each remote mutation of one run. Set it only to retry one mutation. |
An access token is short-lived. When OE_ACCESS_TOKEN has a value and oe exits with code 4, get a new token and set it in OE_ACCESS_TOKEN again. An agent that has no login gets its token through the auth.md registration and token exchange. oe auth login and oe auth logout do not change OE_ACCESS_TOKEN. To use the stored login, unset OE_ACCESS_TOKEN.
Exit codes
| Code | Meaning |
|---|---|
0 | The command succeeded. |
1 | The command failed. code tells why. |
2 | The command line is invalid, or a required input is missing, for example --yes. |
4 | Authentication is required. Run next, which is oe auth login. After a failed switch, next adds --organization ORG. ACCESS_TOKEN_INVALID has no next. For ACCESS_TOKEN_INVALID, set OE_ACCESS_TOKEN to a new token, or unset it and run oe auth login. |
5 | A person must act before the command can continue. error tells what to do. When action.url is present, it is the page that the person opens. |
6 | The failure is temporary. next is the same command. Run it again later. |
JSON output
In JSON mode, each run writes one JSON document to stdout. The document has these fields:
| Field | Value |
|---|---|
status | ok, error, pending, or progress. |
command | The command, for example config push. |
message | A summary for a person. |
data | The result of the command. |
error | The error text. |
code | The error code. |
next | The command that moves the task forward. |
action | A step for a person: kind, url, and reason. |
{"status":"ok","command":"project","message":"...","data":{}}
{"status":"error","command":"config push","error":"...","code":"CONFIG_NOT_FOUND","next":"oe new"}Switch on code, not on the text of error. When action is present, give action.url to a person. In text mode, a failure goes to stderr as error:, action:, and next: lines, and stdout stays clean.
These are the error codes that a script usually handles:
| Code | Meaning |
|---|---|
AUTHENTICATION_REQUIRED | No session and no OE_ACCESS_TOKEN. Exit code 4. |
SESSION_EXPIRED | The WorkOS session expired. Exit code 4. next is oe auth login. When the session expires during an organization switch, next is oe auth login --organization ORG. |
ORGANIZATION_REQUIRED | The command needs an organization, and the session is in none. Exit code 1. next is oe org switch. With OE_ACCESS_TOKEN, there is no next. |
ORGANIZATION_SIGN_IN_REQUIRED | The organization needs its own sign-in, for example SSO or MFA enrollment. Exit code 5. The stored session does not change. next is oe auth login --organization ORG. The person chooses that organization on the sign-in page. |
ORGANIZATION_SWITCH_FAILED | The control API confirmed another organization after a switch. Exit code 1. next is oe org list. |
CONTROL_UPDATE_REQUIRED | The control API does not list organizations yet, so oe org and oe auth login --organization cannot run. Exit code 1. |
TERMS_REQUIRED | The organization has not accepted the OpenE2EE terms. Exit code 5. data.retry is the command to run again after the acceptance. |
TERMS_PERMISSION_REQUIRED | Only an administrator of the organization can accept the terms. Exit code 5. |
ALREADY_SET_UP | oe new ran in a directory that has open-e2ee.config.ts. |
PROJECT_EXISTS | The organization already uses the project slug. |
PROJECT_NOT_FOUND | No project that this account can read has the slug in the organization of the session. Exit code 1. next is oe project list. When the account belongs to more than one organization, error says that the project can be in another one. In oe new, next is oe new with another project slug. |
CONFIG_NOT_FOUND | The directory has no open-e2ee.config.ts. |
ENVIRONMENT_NOT_ACTIVE | The selected environment of the project is not active. For Sandbox, next is oe new. For Production, next is oe config push. |
CONFIRMATION_REQUIRED | A change needs --yes. Review the change, then run the command again with --yes. |
BILLING_PERMISSION_REQUIRED, FREE_PROJECT_LIMIT, CARD_REQUIRED | A Production activation needs the billing permission, an open Free plan slot, or a card on the organization. Exit code 5. For CARD_REQUIRED, action.url is the card page. |
NODE_REQUIRED | oe needs Node.js 22.18 or later. Exit code 5. |
CONTROL_UNAVAILABLE | The control API is temporarily unavailable. Exit code 6. |
Agent mode
oe detects a coding agent from the environment variables that the agent sets:
| Variable | Agent |
|---|---|
OPENCODE=1 | OpenCode |
QWEN_CODE_SESSION_ID | Qwen Code |
PI_CODING_AGENT=true or AI_AGENT=pi | Pi |
CURSOR_AGENT=1 | Cursor Agent |
CLAUDECODE=1 | Claude Code |
CODEX_THREAD_ID | OpenAI Codex |
AMP_CURRENT_THREAD_ID | Amp |
GEMINI_CLI=1 | Gemini CLI |
AUGMENT_AGENT=1 | Auggie |
CRUSH=1 or AI_AGENT=crush | Crush |
COPILOT_AGENT=1 or AI_AGENT=github_copilot_vscode_agent | GitHub Copilot in VS Code |
OZ_RUN_ID | Warp |
A variable without a value in this table matches any value except an empty value, 0, false, no, or off, in any letter case. Under an agent, the default output is JSON, and oe never prompts and never opens a browser. Pass --agent yes for an agent that oe does not detect. --agent no restores text output.
Under an agent, oe auth login writes a pending event, then the result after the person approves. Start the command in the background. Give the person action.url and data.userCode. The person opens the URL and confirms that the page shows the code. On another device, the person goes to data.bareVerificationUrl and enters the code. data.expiresInSeconds is the time that the code stays valid.
Under an agent or --json, oe auth login does not ask for an organization. The session keeps its organization. When the control API lists the organizations, data.organizations has each one. To change the organization, run oe auth login --organization ORG or oe org switch ORG. Without ORG, oe org switch fails with USAGE_ERROR, and data.organizations lists the choices. An agent session belongs to one organization and cannot switch. oe org switch and --organization refuse a session from OE_ACCESS_TOKEN. These refusals exit with code 2.
oe agent setup installs the OpenE2EE skills for coding agents. It writes .agents/skills/<name> and links .claude/skills/<name> to it.
Relay connection variable
oe new and oe link write the Sandbox connection URL to .env.local. The variable name comes from the dependencies and devDependencies of package.json. The first row of this table that matches sets the name:
| Dependency | Variable |
|---|---|
next | NEXT_PUBLIC_OPEN_E2EE_RELAY_URL |
expo | EXPO_PUBLIC_OPEN_E2EE_RELAY_URL |
vite | VITE_OPEN_E2EE_RELAY_URL |
None of these, or no package.json | OPEN_E2EE_RELAY_URL |
--env-var NAME sets a different name. The URL is public configuration. It is not a credential. oe config push writes .env.production.local.
Errors
The complete error taxonomy — base classes, every EncryptionErrorCode, specialized classes with their type guards, and the retry rules.
Security and protocol policy
Spec revision pins, post-quantum policy defaults, the deliberate divergence from Signal Messenger, the identity trust model, audit status, and the responsibility split.