Run Joe from the CLI (pgai joe)
Joe is the PostgresAI SQL optimization assistant: it runs your
EXPLAIN / EXPLAIN ANALYZE requests on an ephemeral DBLab
thin clone of your database, so you can analyze and optimize queries with
production-identical plans without touching production. pgai joe brings Joe to
the terminal (and to scripts and AI agents): plan a query, get the real
execution plan, build real or hypothetical indexes, and iterate โ every result
also lands in the Joe history in the PostgresAI Console.
pgai joe and pgai projects ship in CLI 0.16, which is currently published
under the dev npm dist-tag โ that's why the examples below run
npx pgai@dev โฆ rather than plain npx pgai. Once 0.16 reaches latest, the
@dev suffix will no longer be needed.
Referenceโ
Prerequisitesโ
- Node.js 18+ (or Bun 1.0+) to run the CLI.
- A project in your organization with a registered, active Joe instance (see Joe setup).
- Your user must hold the AllFeaturesUser or Admin role in the
organization โ Joe CLI commands are rejected with
403 Forbiddenotherwise.
The CLI is published as two equivalent npm packages: postgresai (canonical)
and pgai (short wrapper). npm install -g postgresai@dev installs both the
postgresai and pgai binaries; npx pgai@dev โฆ runs without installing.
Authenticateโ
npx pgai@dev login
This opens your browser (OAuth with PKCE), asks you to pick an organization,
and stores the resulting API key in ~/.config/postgresai/config.json. All
joe commands authenticate with this key. See the
auth reference
for storing a key directly (--set-key), useful in CI.
Find a project with Joe readyโ
npx pgai@dev projects
PROJECT_ID ALIAS PROJECT JOE TUNNEL
12 main-db Main DB ready yes
15 analytics Analytics no no
A ready value in the JOE column means the project has an active Joe
instance โ those projects can be targeted with --project <id|alias> below.
For projects without one, register a Joe instance first, or target a Joe
instance directly with --instance-id <id>.
Examples below use the short pgai joe โฆ form for brevity; while 0.16 is on
the dev channel, run them as npx pgai@dev joe โฆ (or install globally with
npm install -g postgresai@dev).
Get a query plan (no execution)โ
plan returns the EXPLAIN plan without executing the query โ the fast,
safe default:
pgai joe plan "select * from users where email = '[email protected]'" \
--project main-db
command 3521 ยท ok
plan:
Seq Scan on users (cost=0.00..1877.10 rows=1 width=142)
Filter: ((email)::text = '[email protected]'::text)
โ client-side: Seq Scan on users โ no index serves this predicate; consider adding one.
The โ line is a lightweight client-side hint derived from the structured
plan; the full result (plan, statistics, recommendations) is also saved to the
Joe history in the Console.
Get the real execution planโ
explain runs EXPLAIN and EXPLAIN ANALYZE โ the query actually
executes, on the DBLab clone (never on your production database):
pgai joe explain "select * from users where email = '[email protected]'" \
--project main-db
command 3522 ยท ok
plan:
Seq Scan on users (cost=0.00..1877.10 rows=1 width=142)
Filter: ((email)::text = '[email protected]'::text)
execution plan (EXPLAIN ANALYZE):
Seq Scan on users (cost=0.00..1877.10 rows=1 width=142) (actual time=8.912..8.914 rows=1 loops=1)
Filter: ((email)::text = '[email protected]'::text)
Rows Removed by Filter: 99999
Planning Time: 0.176 ms
Execution Time: 8.987 ms
โฆ
(Sample output abridged โ Joe also returns buffer/timing statistics and optimization recommendations when available.) Because the clone shares the production data and planner configuration, plan structure and buffer numbers match production; timing may differ due to cache state โ see Joe bot for details.
Test an index ideaโ
Clones are writable: build a real index with exec, then re-check the plan.
pgai joe exec "create index i_users_email on users (email)" --project main-db
pgai joe explain "select * from users where email = '[email protected]'" \
--project main-db
Or use HypoPG hypothetical indexes โ no
actual index build, instant even on huge tables (affects plan cost estimates
only, not real execution):
pgai joe hypo "create index on users (email)" --project main-db
pgai joe plan "select * from users where email = '[email protected]'" --project main-db
pgai joe hypo reset --project main-db
(Every joe command except result needs a target: pass
--project <id|alias> or --instance-id <id> โ or set a default project
once with pgai set-default-project <project> and omit both.)
To start over from a pristine clone:
pgai joe reset --project main-db
Inspect and manage clone activityโ
# pg_stat_activity snapshot on the clone
pgai joe activity --project main-db
# terminate a runaway backend on the clone
pgai joe terminate 12345 --project main-db
# \d-family metadata: tables, indexes, sizes
pgai joe describe users --project main-db
pgai joe describe users --variant '\d+' --project main-db
Long-running commandsโ
Commands are synchronous: the CLI submits the command and polls for the result
for up to 25 seconds (configurable with --budget <seconds>). If the result
isn't ready in time โ e.g. a long EXPLAIN ANALYZE, or a cold clone being
provisioned โ the CLI exits successfully with a resume handle:
started 3523 ยท pending ยท budget 25s reached โ resume: pgai joe result 3523
Fetch the result later by command id:
pgai joe result 3523
Scripting and agentsโ
Every joe subcommand (and projects) accepts --json for machine-readable
output, and --debug to trace API calls:
pgai joe plan "select 1" --project main-db --json | jq '.plan_json'
Exit codes are script-friendly: 0 for a successful result (and for a
budget-expired one-shot โ resume by id), 1 for a failed command or any error.
Troubleshootingโ
Project not found for id/alias/name 'โฆ'โ runpgai projectsto see available projects; the match is case-insensitive on id, alias, and name.Project 'โฆ' has no Joe instanceโ--projectrequires the project to have a registered, active Joe instance. Register one, or target a Joe instance directly with--instance-id <id>.403 Forbidden/ "Joe API v2 requires the All Features role" โ ask an org admin to grant your user the AllFeaturesUser (or Admin) role.401โ your stored API key is missing or expired; re-runpgai login.- Environments behind Cloudflare Access (previews, some staging setups) โ
the CLI's plain HTTPS calls may be blocked by the access layer; you may need
extra setup (e.g. a service token) or to run from an allowed network. For
non-production API endpoints, see
environment variables
(
PGAI_API_BASE_URL).