CLI (data-peek doctor)
Run data-peek's Postgres schema checks from the terminal with npx, no install required
CLI: npx @data-peek/cli doctor
The @data-peek/cli npm package runs the same schema checks as Schema Intel in the app, from a terminal, against any Postgres database you can reach.
npx @data-peek/cli doctor postgres://user:pass@localhost:5432/appWith no connection string it reads DATABASE_URL.
data-peek doctor · app @ localhost:5432 · PostgreSQL 16.4 · 8 checks in 31 ms
✖ 1 invalid index
The planner ignores these. Drop, then rebuild with CREATE INDEX CONCURRENTLY.
idx_orders_email on orders
DROP INDEX "public"."idx_orders_email";
-- Then rebuild with: CREATE INDEX CONCURRENTLY ...
▲ 2 foreign keys without a supporting index
Deletes on the parent and joins over the key scan the whole child table.
payments(invoice_id)
CREATE INDEX "idx_payments_invoice_id" ON "public"."payments" ("invoice_id");
memberships(invited_by)
CREATE INDEX "idx_memberships_invited_by" ON "public"."memberships" ("invited_by");
● 1 bloated table
Over a fifth of the rows are dead tuples. VACUUM reclaims them.
events 41.2% dead · 380 MB
VACUUM (ANALYZE, VERBOSE) "public"."events";
● 3 nullable foreign keys
Fine when NULL means "no reference". Otherwise add NOT NULL to keep out orphans.
memberships(invited_by), projects(created_by), events(user_id)
✔ clean primary keys, duplicate indexes, unused indexes, vacuum
1 critical · 2 warnings · 4 info · 7 findings
Fix with a click: open app in data-peek → https://datapeek.devGrouped by check, most severe first. The reason is said once per group, every finding carries the SQL that fixes it, and the checks that passed are named too.
Checks
| Check | What it finds | Severity |
|---|---|---|
tables_without_pk | Tables with no primary key | warning |
missing_fk_indexes | Foreign keys whose columns are not the leading columns of any index | warning |
duplicate_indexes | Indexes with identical column lists, operator classes, and predicates | warning |
unused_indexes | Indexes over 1 MB with idx_scan = 0 since the last statistics reset | info |
invalid_indexes | Indexes left invalid by a failed CREATE INDEX CONCURRENTLY | critical |
bloated_tables | Tables with over 20 % dead tuples, estimated from n_dead_tup | info |
never_vacuumed | Tables over 1,000 rows with no vacuum or analyze on record | info |
nullable_fks | Foreign key columns that allow NULL | info |
Every check is a read-only query against the system catalogs. No extensions are needed, one connection is opened and closed, and nothing leaves your machine.
Options
| Flag | Effect |
|---|---|
--checks <a,b,c> | Run only the listed checks |
--json | Print the raw report as JSON |
--fail-on <level> | Exit with code 1 if any finding is at or above info, warning, or critical |
--no-color | Plain output. NO_COLOR in the environment does the same |
Exit codes: 0 clean or below the threshold, 1 findings at or above --fail-on, 2 usage or connection error, or a check that could not run while --fail-on is set. A gate that passes because the checks never ran is not a gate.
As a CI gate
Fail a build when a migration drops a foreign key’s index or leaves an index invalid:
- name: Schema checks
run: npx @data-peek/cli doctor "$DATABASE_URL" --fail-on warningDatabase support
Postgres only for now. MySQL and SQL Server connection strings print a clear message rather than a partial answer. The desktop app runs schema checks on all three.
Same checks as the app
The CLI and the desktop app import the same check file from the shared package, so they can never disagree about what a finding means. Open the connection in data-peek and the same findings are in Schema Intel, with the fix one click away.