@@ -17,6 +17,7 @@ import { startMcpServer } from "../lib/mcp-server";
1717import { fetchIssues , fetchIssueComments , createIssueComment , fetchIssue } from "../lib/issues" ;
1818import { resolveBaseUrls } from "../lib/util" ;
1919import { applyInitPlan , buildInitPlan , connectWithSslFallback , DEFAULT_MONITORING_USER , redactPasswordsInSql , resolveAdminConnection , resolveMonitoringPassword , verifyInitSetup } from "../lib/init" ;
20+ import { REPORT_GENERATORS , CHECK_INFO , generateAllReports } from "../lib/checkup" ;
2021
2122const execPromise = promisify ( exec ) ;
2223const execFilePromise = promisify ( execFile ) ;
@@ -2251,5 +2252,164 @@ mcp
22512252 }
22522253 } ) ;
22532254
2255+ // Express checkup - direct database analysis
2256+ program
2257+ . command ( "checkup [conn]" )
2258+ . description ( "generate health check reports directly from PostgreSQL (express mode)" )
2259+ . option ( "--db-url <url>" , "PostgreSQL connection URL (deprecated; pass as positional arg)" )
2260+ . option ( "-h, --host <host>" , "PostgreSQL host" )
2261+ . option ( "-p, --port <port>" , "PostgreSQL port" )
2262+ . option ( "-U, --username <username>" , "PostgreSQL user" )
2263+ . option ( "-d, --dbname <dbname>" , "PostgreSQL database name" )
2264+ . option ( "--password <password>" , "PostgreSQL password (otherwise uses PGPASSWORD)" )
2265+ . option ( "--check-id <id>" , `specific check to run: ${ Object . keys ( CHECK_INFO ) . join ( ", " ) } , or ALL` , "ALL" )
2266+ . option ( "--node-name <name>" , "node name for reports" , "node-01" )
2267+ . option ( "--output <path>" , "output directory for JSON files (default: current directory)" )
2268+ . option ( "--json" , "output to stdout as JSON instead of files" )
2269+ . addHelpText (
2270+ "after" ,
2271+ [
2272+ "" ,
2273+ "Examples:" ,
2274+ " postgresai checkup postgresql://user:pass@host:5432/dbname" ,
2275+ " postgresai checkup -h localhost -p 5432 -U postgres -d mydb" ,
2276+ " postgresai checkup --db-url postgresql://... --check-id A003" ,
2277+ " postgresai checkup postgresql://... --json" ,
2278+ "" ,
2279+ "Available checks:" ,
2280+ ...Object . entries ( CHECK_INFO ) . map ( ( [ id , title ] ) => ` ${ id } : ${ title } ` ) ,
2281+ "" ,
2282+ "Express mode runs SQL queries directly against PostgreSQL," ,
2283+ "without requiring Prometheus. Useful for quick health checks." ,
2284+ ] . join ( "\n" )
2285+ )
2286+ . action ( async ( conn : string | undefined , opts : {
2287+ dbUrl ?: string ;
2288+ host ?: string ;
2289+ port ?: string ;
2290+ username ?: string ;
2291+ dbname ?: string ;
2292+ password ?: string ;
2293+ checkId : string ;
2294+ nodeName : string ;
2295+ output ?: string ;
2296+ json ?: boolean ;
2297+ } ) => {
2298+ // Build connection config
2299+ let connectionString : string | undefined ;
2300+ let connectionConfig : {
2301+ host ?: string ;
2302+ port ? : number ;
2303+ user ? : string ;
2304+ database ? : string ;
2305+ password ? : string ;
2306+ } | undefined ;
2307+
2308+ if ( conn ) {
2309+ connectionString = conn ;
2310+ } else if ( opts . dbUrl ) {
2311+ connectionString = opts . dbUrl ;
2312+ } else if ( opts . host || opts . username || opts . dbname ) {
2313+ connectionConfig = {
2314+ host : opts . host || process . env . PGHOST || "localhost" ,
2315+ port : parseInt ( opts . port || process . env . PGPORT || "5432" , 10 ) ,
2316+ user : opts . username || process . env . PGUSER || "postgres" ,
2317+ database : opts . dbname || process . env . PGDATABASE || "postgres" ,
2318+ password : opts . password || process . env . PGPASSWORD ,
2319+ } ;
2320+ } else {
2321+ // Try environment variables
2322+ if ( process . env . PGHOST || process . env . DATABASE_URL ) {
2323+ connectionString = process . env . DATABASE_URL ;
2324+ if ( ! connectionString ) {
2325+ connectionConfig = {
2326+ host : process . env . PGHOST ,
2327+ port : parseInt ( process . env . PGPORT || "5432" , 10 ) ,
2328+ user : process . env . PGUSER || "postgres" ,
2329+ database : process . env . PGDATABASE || "postgres" ,
2330+ password : process . env . PGPASSWORD ,
2331+ } ;
2332+ }
2333+ } else {
2334+ console. error ( "Error: Connection details required" ) ;
2335+ console . error ( "" ) ;
2336+ console . error ( "Provide connection via:" ) ;
2337+ console . error ( " positional argument: postgresai checkup postgresql://..." ) ;
2338+ console . error ( " flags: postgresai checkup -h host -p port -U user -d database" ) ;
2339+ console . error ( " environment: PGHOST, PGPORT, PGUSER, PGDATABASE, PGPASSWORD" ) ;
2340+ process . exitCode = 1 ;
2341+ return ;
2342+ }
2343+ }
2344+
2345+ // Validate check ID
2346+ const checkId = opts . checkId . toUpperCase ( ) ;
2347+ if ( checkId !== "ALL" && ! REPORT_GENERATORS [ checkId ] ) {
2348+ console . error ( `Error: Unknown check ID: ${ opts . checkId } ` ) ;
2349+ console . error ( `Available checks: ${ Object . keys ( CHECK_INFO ) . join ( ", " ) } , ALL` ) ;
2350+ process . exitCode = 1 ;
2351+ return ;
2352+ }
2353+
2354+ // Connect to database
2355+ const client = new Client ( connectionString ? { connectionString } : connectionConfig ) ;
2356+
2357+ try {
2358+ console . error ( "Connecting to PostgreSQL..." ) ;
2359+ await client . connect ( ) ;
2360+
2361+ const dbResult = await client . query ( "SELECT current_database() as db, version() as ver" ) ;
2362+ const dbName = dbResult . rows [ 0 ] ?. db ;
2363+ const dbVersion = dbResult . rows [ 0 ] ?. ver ;
2364+ console . error ( `Connected to: ${ dbName } ` ) ;
2365+ console . error ( `Version: ${ dbVersion } ` ) ;
2366+ console . error ( "" ) ;
2367+
2368+ if ( checkId === "ALL" ) {
2369+ // Generate all reports
2370+ console . error ( "Generating all reports..." ) ;
2371+ const reports = await generateAllReports ( client , opts . nodeName ) ;
2372+
2373+ if ( opts . json ) {
2374+ // Output all reports as JSON to stdout
2375+ console . log ( JSON . stringify ( reports , null , 2 ) ) ;
2376+ } else {
2377+ // Write each report to a file
2378+ const outputDir = opts . output || process . cwd ( ) ;
2379+ for ( const [ id , report ] of Object . entries ( reports ) ) {
2380+ const filePath = path . join ( outputDir , `${ id } .json` ) ;
2381+ fs . writeFileSync ( filePath , JSON . stringify ( report , null , 2 ) , "utf8" ) ;
2382+ console . error ( `Generated: ${ filePath } ` ) ;
2383+ }
2384+ console . error ( "" ) ;
2385+ console . error ( `All reports written to: ${ outputDir } ` ) ;
2386+ }
2387+ } else {
2388+ // Generate specific report
2389+ console . error ( `Generating ${ checkId } report...` ) ;
2390+ const generator = REPORT_GENERATORS [ checkId ] ;
2391+ const report = await generator ( client , opts . nodeName ) ;
2392+
2393+ if ( opts . json ) {
2394+ // Output to stdout
2395+ console . log ( JSON . stringify ( report , null , 2 ) ) ;
2396+ } else {
2397+ // Write to file
2398+ const outputDir = opts . output || process . cwd ( ) ;
2399+ const filePath = path . join ( outputDir , `${ checkId } .json` ) ;
2400+ fs . writeFileSync ( filePath , JSON . stringify ( report , null , 2 ) , "utf8" ) ;
2401+ console . error ( `Generated: ${ filePath } ` ) ;
2402+ }
2403+ }
2404+
2405+ } catch ( error ) {
2406+ const message = error instanceof Error ? error . message : String ( error ) ;
2407+ console . error ( `Error: ${ message } ` ) ;
2408+ process . exitCode = 1 ;
2409+ } finally {
2410+ await client . end ( ) ;
2411+ }
2412+ } ) ;
2413+
22542414program . parseAsync ( process . argv ) ;
22552415
0 commit comments