Convert natural language questions into SQL queries and run them against the Blipp staging or production PostgreSQL database. Use this skill whenever the user asks about data in the database, wants to look up records, check counts, investigate pipeline state, debug user issues, or explore any data that lives in Neon/PostgreSQL. Triggers on phrases like 'how many users', 'check the database', 'query prod', 'look up episode', 'what's in the pipeline', 'find the user', 'show me all X', or any question that implies reading from the database. Also use when the user says 'db', 'sql', 'query', 'staging db', 'prod db', or references specific tables like PipelineJob, Episode, User, etc. Default to staging unless the user says 'prod' or 'production'.
Convert natural language into SQL and execute it against the Blipp Neon PostgreSQL database.
Use the query script at scripts/db-query.ts. It connects via the pg package using connection strings from .env (staging) or neon-config.env (production).
Staging (default):
npx tsx scripts/db-query.ts 'SELECT "id", "email" FROM "User" LIMIT 5'
Production (only when user explicitly asks):
npx tsx scripts/db-query.ts --prod 'SELECT COUNT(*) FROM "User"'
The database uses Prisma, so table names are PascalCase (e.g., "User", "Episode", "PipelineJob"). Column names are camelCase. You must double-quote table and column names in SQL since PostgreSQL folds unquoted identifiers to lowercase.
Users & Auth:
"User" — id mod externalId (Clerk ID), email, name, planId, isAdmin, createdAt"Plan" — id, name, slug, limits (briefingsPerDay, etc.)"Subscription" — userId, podcastId, durationTier, isActiveContent:
"Podcast" — id, title, author, feedUrl, imageUrl, isActive"Episode" — id, podcastId, title, publishedAt, contentStatus (PENDING/TRANSCRIPT_READY/AUDIO_READY/NOT_DELIVERABLE)"Distillation" — id, episodeId, status, claimsJson, transcript"Clip" — id, episodeId, durationTier, status, narrative, audioUrl, voicePresetIdPipeline:
"PipelineJob" — id, episodeId, briefingRequestId, status, currentStage, createdAt"PipelineStep" — id, pipelineJobId, stage, status, model, provider, inputTokens, outputTokens, cost, durationMs, error"PipelineEvent" — id, pipelineStepId, level, message, data"BriefingRequest" — id, userId, status, items (JSON), targetMinutesFeed:
"FeedItem" — id, userId, episodeId, clipId, source (SUBSCRIPTION/ON_DEMAND), status"Briefing" — id, userId, clipId, feedItemIdAI Models:
"AiModel" — id, stage, modelId, label, developer, isActive"AiModelProvider" — id, aiModelId, provider, providerModelId, pricing fields"PlatformConfig" — key, value (runtime config including prompts)"PromptVersion" — id, stage, version, values (JSON), labelAdmin/Ops:
"AuditLog" — id, actorId, action, entityType, entityId, before, after"CronRun" — id, jobKey, status, startedAt, finishedAt"AiServiceError" — service, provider, model, category, severity, errorMessageSELECT "id", "email" FROM "User"LIMIT 20 by defaultSELECT COUNT(*) FROM "Episode"NOW() - INTERVAL '...': WHERE "createdAt" > NOW() - INTERVAL '24 hours'"userId", "podcastId", "episodeId")-> and ->> operatorsILIKE '%term%'User: "how many users signed up this week?"
SELECT COUNT(*) FROM "User" WHERE "createdAt" > NOW() - INTERVAL '7 days';
User: "show me failed pipeline jobs from today"
SELECT j."id", j."status", j."currentStage", j."error", j."createdAt",
e."title" as episode_title
FROM "PipelineJob" j
LEFT JOIN "Episode" e ON j."episodeId" = e."id"
WHERE j."status" = 'FAILED' AND j."createdAt" > NOW() - INTERVAL '24 hours'
ORDER BY j."createdAt" DESC LIMIT 20;
User: "what podcasts does user X subscribe to?"
SELECT p."title", s."durationTier", s."isActive", s."createdAt"
FROM "Subscription" s
JOIN "Podcast" p ON s."podcastId" = p."id"
WHERE s."userId" = 'USER_ID_HERE'
ORDER BY s."createdAt" DESC;