Note: this is the CLI of a service under development — don't use it for production projects yet. Ping us at hello@extraorbital.dev if you think it can be useful to you!
Provision databases, storage, queues, and model keys in one command. Credentials land in .env.
npx extraorbital provision .
Also available as eo after npm i -g extraorbital.
Autopilot
provision looks at the directory, finds variables it can fill, skips ones that already have a value, and creates the rest.
$ npx extraorbital provision .
Provisioning 2 resources for .
s3 S3_BUCKET +1 · used in src/upload.ts
mongo MONGODB_URI · declared in .env.example
Left alone: 1 variable that already has a value
✓ Created project cosmic-otter → .extraorbital.json
✓ Provisioned s3/default (bucket cosmic-otter-default, us-east-1)
✓ Wrote 5 variables to .env
S3_BUCKET, S3_ENDPOINT, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY
✓ Provisioned mongo/default (database cosmic-otter-default, us-east-1)
✓ Wrote 1 variable to .env — MONGODB_URI
2 resources created · Prototype plan · no card required
It looks at, in order:
- keys in
.env.example, .env.sample, or .env.template
- empty or placeholder values in
.env / .env.local (your-bucket, and the like)
- the exact name in source (
process.env.MONGODB_URI, os.environ["MONGODB_URI"], any language)
--dry-run prints the plan and why each line is there. Everything is created at slug default.
One resource
npx extraorbital add mongodb
npx extraorbital add s3 ./web
npx extraorbital add redis --slug cache
Safe to re-run. The same project, service, and slug always return the same resource.
$ npx extraorbital add s3
→ s3/default already exists — returned existing credentials
→ .env unchanged
What gets written
A fenced block, so later runs can update or remove it without touching the rest of the file:
# >>> extraorbital resource=s3 project=cosmic-otter slug=default
S3_BUCKET=cosmic-otter-default
S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=AKIA7F2A91C4E8B3D6QZ
S3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEX
# <<< extraorbital resource=s3
Writes go to --env-file, else .env.local if it exists, else .env. An empty MONGODB_URI= above the fence would win, so extraorbital comments that line out and tells you.
The file goes into .gitignore, created if there is none — beside the file when there is no repository yet, so the first git add . already skips it. If git tracks it already, extraorbital says so, with git rm --cached to take it out.
Secrets nothing issues
BETTER_AUTH_SECRET, JWT_SECRET, SECRET_KEY_BASE and the rest have no vendor. provision generates them first, offline, into the same file — each with a comment naming its counterparty, who else must hold it, and a keypair's public half beside the private one. A value that is set is never regenerated. npx extraorbital generate does only that step, with no account.
Resources
s3 | S3_BUCKET, S3_ENDPOINT, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY | --region, --public |
mongo (mongodb) | MONGODB_URI | --region |
redis | UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN, REDIS_URL | --region |
vector | UPSTASH_VECTOR_REST_URL, UPSTASH_VECTOR_REST_TOKEN | --dimensions, --embedding-model, --similarity-function, --region |
qstash | QSTASH_URL, QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY | — |
openai | OPENAI_BASE_URL, OPENAI_API_KEY | --max-budget, --model-tier |
anthropic | ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY | --max-budget, --model-tier |
git | GIT_URL, GIT_USERNAME, GIT_TOKEN | — |
stripe | STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET | — |
--max-budget is still accepted, and no longer applied: every AI Gateway key a team holds spends from the team's budget, which the gateway holds all of them to at once. --model-tier is small or full; on the Prototype plan the tier is forced to small. Options apply at creation and are ignored if the resource already exists.
git is experimental — good for agent state, not yet somewhere to keep the only copy of anything.
stripe is shared, and the only resource in the catalog that is: every project provisioning it gets the same test-mode keys to one Stripe account we run. No real money moves, but nothing you create in it is private or guaranteed to survive, and STRIPE_WEBHOOK_SECRET is only written where an endpoint is configured. The CLI prints the caveat next to the credentials, and GET /services marks the entry supply: "shared". Use your own keys before you take a real payment.
Project
A directory belongs to one team and project, stored in .extraorbital.json. Commit it — it is not a secret. After that, commands need no flags:
npx extraorbital init --team vercel --project skills.dev
npx extraorbital provision
provision and add create a project if there isn't one. To point the directory at another team:
npx extraorbital teams switch acme
switch --team acme --project skills.dev moves both at once. Any team you belong to can be named — by --team, EXTRAORBITAL_TEAM or the file — and a machine you linked with login reaches the same teams.
A --team or --project that disagrees with the file exits instead of writing into a different home.
npx extraorbital add mongodb --team vercel --project skills.dev --slug analytics
Commands
provision [dir] | Create everything the project needs and is missing |
generate [dir] | Mint the secrets no vendor issues (JWT_SECRET, …) |
add <resource> [dir] | Create one resource |
list (ls) | Resources in the project |
check [dir] | Verify each fenced block is healthy (--fix to repair) |
remove <resource> [dir] (rm) | Delete the resource and its block |
init [name] [dir] | Set the team and project |
switch [dir] | Point this directory at another team or project |
teams switch <slug> | Point this directory at another team |
login · whoami | Link this machine to you / see who you are |
projects · teams | List, create, delete, add members |
ledger (usage) | Spend this month |
link · open | Enable billing / open the dashboard |
help [command] | Flags for a command |
--yes skips confirmations. --no-env prints KEY=value to stdout and writes nothing. --json prints one JSON object to stdout.
Billing
Prototype allowances first. Past them, the command exits 3 with a URL for a human. Signed in, they see their teams: one already on a paid plan takes the project in one click, with no charge and no new team, and only when none can does the page offer to subscribe this one.
$ npx extraorbital add s3
! Your team has spent its Prototype allowance and cannot add s3. Have a human open the
link below to move this project into a team they already pay for, or to move this team
to a paid plan — then you are billed only for what you use.
A human must open this link to continue:
https://extraorbital.dev/link/fl_8a2c91d4e7b3
Link expires in 24h · npx extraorbital link for a fresh URL
Exit code 3 (payment required)
link mints that URL before anything is blocked. After a move, teams switch <slug> points the directory at the project's new team. ledger shows spend.
Unattended
Nothing here waits for a human. The first run registers this machine — an Ed25519
keypair, kept in ~/.extraorbital/machine.json at mode 0600 — and provisions
in a sandbox of its own on the Prototype plan. There is no account behind it, no
browser opens and no approval is requested.
A person becomes necessary only when the sandbox reaches a Prototype limit: the
command exits 3 with a link that links this machine to whoever opens it, and from
then on it works in their teams. The sandbox comes along — its projects, resources
and spend move into that person's default team, with credentials unchanged — and the
next run in a directory still pointing at the sandbox rewrites .extraorbital.json
to match. A sandbox is never paid for, and nobody can be
emailed about it, so whoami, add and provision print its warnings — spend
against the allowance, and resources about to be deleted for disuse.
npx extraorbital provision .
npx extraorbital check --quiet || npx extraorbital check --fix
Keep that file and the machine keeps its account and its spend. Lose it — a fresh
container, a wiped home directory — and the next run is a new machine with a new
account. Pass --token or EXTRAORBITAL_TOKEN when you want a specific identity
regardless, and --login when you want to attach your own.
0 | Success |
1 | Failure / check found something unhealthy |
2 | Bad usage (including a team/project mismatch) |
3 | Payment required — surface the printed URL |
4 | Not signed in, with --no-login set |
5 | Provisioning in progress — retry |
6 | Rate limited — wait, then retry |
7 | Upstream failure — retry |
Token for one run: --token or EXTRAORBITAL_TOKEN (never written to disk). To act
as yourself rather than as the machine, login once: it prints a URL, and when you
open it and say yes, this machine acts as you — in your teams, with nothing to renew —
until you unlink it under Settings → Machines. login --no-browser prints the URL
without opening anything, for SSH.
login --human signs you in with a device code instead, and that sign-in lasts a
week. When it ends, the next run says so on stderr: at a terminal it offers to sign
you in again, and anywhere else it carries on as the machine. whoami opens with the
identity a run acts as — for a machine, whether it is linked and to whom — and add
and provision name the team they bill before they create anything.