@batadata/cli
v0.2.10
Published
CLI for BataDB — serverless Postgres platform
Maintainers
Readme
@batadata/cli
Command-line interface for BataDB — a serverless Postgres
platform. Create projects, open psql, run queries, manage branches and API keys,
inspect cost, and generate types, all from your terminal.
Every command also runs headlessly with a single API key and --json output,
so an AI coding agent can provision and operate a database with no human in the
loop. See Headless / agent use.
Install
npm install -g @batadata/cliThis installs the bata command. Requires Node.js >= 20.
Authenticate
Either log in interactively:
bata login…or, for CI and agents, set an API key — every command works with just
BATA_API_KEY set, no login needed:
export BATA_API_KEY=bata_xxx # mint one with: bata api-keys createYou can also pass --api-key <key> per command, and point at a different
control plane with --api-url <url> or BATA_API_URL.
Quick start
bata create my-app # create a project and wait until it's ready
bata status # list all projects and their status
bata connect my-app # open psql (auto-wakes the compute if suspended)
bata usage # per-dimension cost for the current billing periodProject linking
Tired of threading --project <id> through every command? Link a directory to a
project once — Vercel/Neon-style — and every command run inside it (or any
subdirectory) targets that project automatically.
bata link my-app # resolves by name or id, writes .batadata/project.json
bata link # no argument, in a TTY: pick from a list
bata link --status # show what this directory resolves to
bata db query "SELECT 1" # no --project needed anymore
bata unlink # remove the linkbata link writes .batadata/project.json in the current directory:
{ "projectId": "proj_abc123", "branchId": null }Add .batadata/ to your .gitignore — the link is per-checkout, not shared.
Pin a branch
bata db branch checkout preview # writes the branch id into .batadata/project.json
bata db query "SELECT 1" # now runs against `preview` — no --branch flagResolution precedence
Everywhere a project or branch is resolved, the same rule applies — an explicit flag always wins, so linking never silently overrides an intentional request:
- an explicit
--project/--branchflag (or a positional id) .batadata/project.json, discovered by walking up from the current directory like git (stops at$HOMEor the filesystem root)defaultProjectin~/.batarc(the per-machine fallback)
Commands
| Command | Description |
|---------|-------------|
| create <name> | Create a project and wait for it to be ready |
| import --source <uri> | Migrate a Postgres/Neon database into BataDB (see Migrate from Neon) |
| connect <name> | Open psql to a project (auto-wakes if suspended) |
| status | Show all projects and their status |
| usage | Show per-dimension cost (compute, storage, transfer) for the current period |
| link [project] | Link the current directory to a project (see Project linking) |
| unlink | Remove the current directory's project link |
| login / logout / whoami | Manage your session |
| api-keys | Create / list / revoke API keys |
| projects | list, create, info, delete |
| db url | Print a connection string |
| db connect | Open psql to your database (interactive only) |
| db query <sql> | Run a SQL query and print the rows |
| db branches | List database branches |
| db branch create / db branch delete | Manage branches |
| db branch checkout <name-or-id> | Pin a branch into the directory's link |
| db studio | Open the table browser in your browser |
| restore points | List recovery points and the PITR window (see Point-in-time restore) |
| restore create | Restore a branch to a timestamp/LSN as a new branch |
| schema check <file> | Check a proposed schema change against live query traffic |
| generate | Generate types from your database schema (--watch for watch mode) |
| dev | Print the local development setup guide |
schema check is implemented today. The remaining schema subcommands
(init, push, pull, diff) and all of migrate are not implemented yet —
they exit 3 (NOT_IMPLEMENTED) and point you at schema check for migration
safety.
Run bata --help for the full, authoritative command list, or bata --version.
Migrate from Neon
bata import moves an existing Postgres database — Neon or anything else — into a
fresh BataDB project in one command. It shells out to the standard pg_dump /
psql client tools (nothing proprietary), streams the dump straight through, and
verifies the result.
bata import --source "postgresql://…@ep-x.neon.tech/neondb" # → a new project named `neondb`
bata import --source "$NEON_URL" --name my-app # name the new project
bata import --source "$NEON_URL" --project proj_abc123 --yes # into an existing projectPrerequisite: local pg_dump and psql whose major version is ≥ the
source server's (pg_dump 16 cannot dump a PostgreSQL 17 server). Neon defaults
to PostgreSQL 17, so install matching client tools:
brew install libpq # macOS (or: brew install postgresql@17)
sudo apt install postgresql-client-17 # UbuntuWhat it does, in order: inspects the source, checks your client tools, creates (or
resolves) the target project, waits for its compute, preflights extensions (any
source extension the target can't provide hard-fails before the restore starts,
rather than dying mid-restore at CREATE EXTENSION and half-importing), streams
pg_dump … --encoding=UTF8 | psql (the --encoding=UTF8 guarantees a valid-UTF-8
stream so non-UTF-8 source databases aren't corrupted in transit), then verifies
every table's row count and every sequence's value on both sides. It exits non-zero
on any mismatch. Importing into an existing --project that already holds user
objects requires --yes. --json emits a machine-readable result for agents.
PostgreSQL 16 vs 17 (honest caveat). BataDB runs PostgreSQL 16 today; Neon
defaults to 17. Standard schemas migrate cleanly — bata import transparently
strips the one PG17-only line pg_dump emits (SET transaction_timeout = 0;) that
PG16 would reject. But genuinely PG17-only schema features (a data type, syntax,
or option that only exists in 17) will fail loudly during restore — the command
surfaces the exact psql errors and exits non-zero rather than pretending the
migration succeeded. See docs/migrate-from-neon.md
for the full guide and the manual pg_dump | psql fallback.
Point-in-time restore (PITR)
Recover a branch to an earlier point in time. Restore is non-destructive: it creates a new branch at the chosen point and never overwrites the source branch's data.
bata restore points # what can I restore to?
bata restore create \
--branch main \
--at 2026-07-04T12:00:00Z \
--name before-the-bad-migration # → a new branch at that timestamp--at accepts either an ISO-8601 timestamp (e.g. 2026-07-04T12:00:00Z) or
a Postgres LSN (e.g. 0/15994B0); the CLI detects which you passed. If
--name is omitted the new branch is named restore-<branch>-<timestamp>. The
new branch's compute may still be provisioning when the command returns — poll
bata db branches before you query it.
How far back can I restore? (honest window)
Recovery points are timeline-metadata snapshots the control plane's backup
scheduler records about every 6 hours (BACKUP_INTERVAL_HOURS, default 6).
They are not full physical backups, and the earliest point restore points
lists is simply the oldest metadata row we have — not a guaranteed floor on
how far back you can restore. Actual restorability to an arbitrary timestamp
depends on how much WAL the storage engine still retains: if the WAL no longer
covers your timestamp, the server returns "No data available at the requested
timestamp" and the restore is refused. We surface that verbatim rather than
implying the window is deeper than it is.
Headless / agent use
Every command runs headlessly with just BATA_API_KEY set — no bata login,
no interactive prompts, no human in the loop. This is what makes BataDB
agent-native: an AI coding agent can provision a database, run SQL, branch, and
clean up entirely on its own.
--json— machine-readable output. The error envelope is JSON too and always the same shape:{ "error", "code", "hint" }.Exit codes — a stable contract so a script can branch on the failure mode:
| Code | Meaning | |------|---------| |
0| Success | |1| Generic error (CLI_ERROR) | |2| Gate tripped (schema check --fail-on) | |3| Not implemented (NOT_IMPLEMENTED) | |4| Auth / credentials (NO_CREDENTIALS,INVALID_KEY) | |5| Not found / bad input (NO_PROJECT,BRANCH_NOT_FOUND,INVALID_FLAG,INTERACTIVE_ONLY) | |6| Upstream / transient (API_UNAVAILABLE,TIMEOUT) — safe to retry |--yes/-y— skip confirmation prompts on destructive commands.
db connect and connect open an interactive psql session and exit 5
(INTERACTIVE_ONLY) when run headlessly. Agents should use db url for a
connection string or db query <sql> to execute SQL.
A full agent workflow
Provision, query, branch, check cost, and tear down — no human, no login:
export BATA_API_KEY=bata_xxx
# 1. Create a project and wait until it's ready
bata create my-agent-app --json
# 2. Run SQL headlessly (rows come back as JSON objects)
bata db query "SELECT now()" --json
# 3. Branch the database (copy-on-write)
bata db branch create preview --json
# 4. Inspect cost for the current period
bata usage --json
# 5. Clean up
bata projects delete --yes --jsondb branch create returns immediately with "ready": false — the branch row
exists but its compute may still be provisioning. Poll bata db branches --json
until the branch reports a ready status before connecting to it.
To avoid passing --project on every call, an agent can bata link <id> --json
once (it emits { "linked": true, "project_id", "project_name", "branch_id",
"link_file" }) and drop the flag from every subsequent command in that
directory. bata link --status --json reports the current link.
Cost truth
bata usage reports cost per dimension and is deliberately honest about what is
and isn't metered. Transfer (egress) is not metered yet, so it never shows a
priced $0 — in --json it reports { "metered": false, "value": null,
"cost_cents": null, "status": "not_metered_yet" }, and in the human view it
reads Not metered yet. You only ever see a dollar figure for a dimension we
genuinely meter end to end (compute and storage today).
bata usage --json | jq '.projects[].transfer.status' # "not_metered_yet"
bata status --json | jq '.projects[].name' # list project namesGlobal options
| Flag | Description |
|------|-------------|
| --json | Machine-readable JSON output (read commands) |
| --api-key <key> | API key (else BATA_API_KEY, else saved login) |
| --api-url <url> | API base URL (else BATA_API_URL, else config/default) |
| --yes, -y | Skip confirmation prompts |
| --help, -h | Show help |
| --version, -v | Show version |
License
MIT
