db — Postgres CLI for agents
scripts/db.js is the single entry point to the database. Use it instead of psql or inline pg scripts.
Why
- Reads
DATABASE_URLfrom.envautomatically. No manual sourcing, no leaked credentials. - Read-only by default. Writes require explicit
--write. Prevents accidental destructive SQL. - JSON output. Easy to parse, token-efficient.
- Portable. Works on any machine that cloned the repo and ran onboarding (no
psqlin PATH required). - Never prints the connection string.
--pingshows maskeduser@host/dbonly.
Commands
# Read (default, JSON array out)
node scripts/db.js "SELECT * FROM users WHERE id = <user_id>"
node scripts/db.js "SELECT data->'profile' AS profile FROM users WHERE id = <user_id>"
# Write (requires --write)
node scripts/db.js "INSERT INTO applications (user_id, platform, company, role, url, status, data) VALUES (<user_id>, 'linkedin', 'Acme', 'Eng Manager', 'https://...', 'applied', '{}'::jsonb)" --write
node scripts/db.js "UPDATE users SET data = jsonb_set(data, '{linkedin_profile}', '\"https://...\"') WHERE id = <user_id>" --write
# Introspection
node scripts/db.js --tables
node scripts/db.js --schema applications
node scripts/db.js --ping
Rules
- Always use this CLI for DB access. Never write inline
pgscripts in skills, never callpsqldirectly. - Read-only by default. A statement that does not start with
SELECT/WITH/SHOW/EXPLAIN/VALUES/DESCRIBEis refused without--write. - Output is JSON. SELECTs print
[{...}, ...]. Writes print{rowCount, command}. - Never echo
DATABASE_URL. If you need to confirm connectivity, use--ping(masked). - Parametrize when needed by interpolating safely into the SQL string. This CLI has no parameter binding for CLI args; for user-supplied strings, escape single quotes (
'->''). - JSONB access: use
data->'key'(jsonb) ordata->>'key'(text). Usejsonb_setfor updates.
Schema reference
Tables live in public. Discover with --tables and --schema <table>. Known tables:
users— single row (repo owner).dataJSONB holdsprofile,job_preferences,style_profile,platforms,linkedin_profile.applications—user_id, platform, company, role, url, status, applied_at, data.messages— recruiter / contact messages.
applications table
Columns: id, user_id, platform, company, role, url, status, applied_at, data
Platforms used:
linkedin= Easy Apply jobslinkedin_invite= connection requestsemail= direct emails to recruitersteamtailor= applications via Teamtailor (with LinkedIn auth, email verification, Connect)humand= applications via Humand.co<company>_career_site, etc. = specific career sites
Status values (pipeline stages, canonical):
Active stages (left to right in the kanban):
discovered= found but no action takencontacted= invite/email sent, no formal applicationapplied= application submittedin_review= company reviewing, no responsescreening= screening call scheduled/doneinterview= technical interview in progressoffer= offer received, negotiatinghired= accepted, starting
Closed stages (shown with --closed):
rejected= company rejectedwithdrawn= user withdrewskipped= decided not to apply / not a fit
data JSONB: include source, match (high/medium/low), location, tech array, stage_history array (audit trail of status changes), and any relevant metadata.
Helper library
lib/browser-helpers.js provides atomic helpers that prevent session death between browser calls:
const {
goto, evalJS, evalJSON, clickByText,
waitFor, waitForSelector, waitForText,
snapshotReliable, dismissModals,
dbQuery, dbWrite,
// Atomic helpers (prevent session death between calls)
openAndEval, openAndEvalJSON,
gotoAndEval, gotoAndEvalJSON,
} = require('./lib/browser-helpers');
Atomic helpers chain open/goto + eval in a single shell command to prevent session death between calls:
// Open browser + extract in one call (no session death risk)
const jobs = openAndEvalJSON(
'https://www.linkedin.com/jobs/search/?keywords=...',
`(function(){ ... return JSON.stringify(...); })()`,
{ session: 'my-session', headed: true }
);
// Navigate + extract in one call (browser already open)
const emails = gotoAndEvalJSON(
'https://mail.google.com/mail/u/0/#all',
`(function(){ ... return JSON.stringify(...); })()`,
{ session: 'my-session' }
);
Dependencies
- Depends on
onboarding(.env+ DB must exist). - Consumed by every flow that reads or writes DB:
profile,apply,news,daily.