UNPKG

sequelae-mcp

Version:

Let Claude, Cursor, and other AI agents run real SQL queries on live Postgres databases. No more copy-pasting SQL, stale schema docs, or hallucinated DB adapters — just raw, real-time access. Now with MCP support!

901 lines (900 loc) • 35.4 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SqlAgentError = void 0; exports.handleVersion = handleVersion; exports.handleHelp = handleHelp; exports.formatError = formatError; exports.handleExit = handleExit; exports.parseArguments = parseArguments; exports.validateDatabaseUrl = validateDatabaseUrl; exports.createPool = createPool; exports.getCommandInfo = getCommandInfo; exports.validateCommandArgument = validateCommandArgument; exports.validateFile = validateFile; exports.readSqlFile = readSqlFile; exports.formatQueryResultsJson = formatQueryResultsJson; exports.createNoCommandError = createNoCommandError; exports.createNoSqlQueryError = createNoSqlQueryError; exports.createNoFilePathError = createNoFilePathError; exports.createFileNotFoundError = createFileNotFoundError; exports.formatSqlError = formatSqlError; exports.formatCommandResult = formatCommandResult; exports.buildSchemaCondition = buildSchemaCondition; exports.buildTableList = buildTableList; exports.isDirectSqlCommand = isDirectSqlCommand; exports.main = main; const pg_1 = require("pg"); const pool_manager_1 = require("./core/pool-manager"); const dotenv_1 = require("dotenv"); const fs_1 = require("fs"); const path_1 = require("path"); const packageJson = __importStar(require("../package.json")); const sql_executor_1 = require("./core/sql-executor"); const logger_1 = require("./utils/logger"); const jsonb_analyzer_1 = require("./jsonb-analyzer"); // Load .env from the package root (handles both root and subdirectory execution) const envPath = (0, path_1.resolve)(__dirname, '../.env'); (0, dotenv_1.config)({ path: envPath }); // CLI output helpers that maintain console output but also log const cliOutput = { log: (message) => { console.log(message); logger_1.logger.debug('CLI output:', { message }); }, error: (message) => { console.error(message); logger_1.logger.error('CLI error:', { message }); }, table: (data) => { console.table(data); logger_1.logger.debug('CLI table output:', { rowCount: Array.isArray(data) ? data.length : 'N/A' }); }, json: (data) => { const json = JSON.stringify(data); console.log(json); logger_1.logger.debug('CLI JSON output:', { data }); }, }; function handleVersion(jsonMode) { if (jsonMode) { return JSON.stringify({ version: packageJson.version }); } else { return `sequelae-mcp v${packageJson.version}`; } } function handleHelp(jsonMode) { if (jsonMode) { return JSON.stringify({ usage: [ 'sequelae exec "SQL query" Execute a SQL query', 'sequelae file path/to/query.sql Execute SQL from file', 'sequelae schema Show all tables in public schema', 'sequelae schema [tables] Show specific table(s) - comma separated', 'sequelae schema --all Show all schemas including system tables', 'sequelae backup Create a database backup', 'sequelae exit Exit sequelae', 'sequelae --json Output results in JSON format', ], examples: [ 'sequelae exec "SELECT * FROM users"', 'sequelae exec "CREATE TABLE posts (id serial primary key, title text)"', 'sequelae file migrations/001_init.sql', 'sequelae schema', 'sequelae schema users,posts', 'sequelae backup --output db_backup.sql', 'sequelae backup --tables users,posts --format custom', 'sequelae --json exec "SELECT * FROM users"', ], }); } else { return ` Usage: sequelae exec "SQL query" Execute a SQL query sequelae file path/to/query.sql Execute SQL from file sequelae schema Show all tables in public schema sequelae schema [tables] Show specific table(s) - comma separated sequelae schema --all Show all schemas including system tables sequelae backup Create a database backup sequelae exit Exit sequelae sequelae --json Output results in JSON format sequelae --no-transaction Disable automatic transactions sequelae --timeout <ms> Set query timeout in milliseconds Examples: sequelae exec "SELECT * FROM users" sequelae exec "CREATE TABLE posts (id serial primary key, title text)" sequelae file migrations/001_init.sql sequelae schema sequelae schema users,posts sequelae backup --output db_backup.sql sequelae backup --tables users,posts --format custom sequelae --json exec "SELECT * FROM users" `; } } function formatError(error, jsonMode, hint) { if (jsonMode) { return JSON.stringify({ error, ...(hint && { hint }) }); } else { let output = `Error: ${error}`; if (hint) { output += `\n${hint}`; } return output; } } function handleExit(jsonMode) { if (jsonMode) { return JSON.stringify({ message: 'Goodbye!' }); } else { return 'Goodbye!'; } } function parseArguments(args) { const jsonMode = args.includes('--json'); const allSchemas = args.includes('--all'); const noTransaction = args.includes('--no-transaction'); // Extract timeout value let timeout; let timeoutValueIndex; const timeoutIndex = args.indexOf('--timeout'); if (timeoutIndex !== -1 && timeoutIndex + 1 < args.length) { const possibleValue = args[timeoutIndex + 1]; // Only treat as timeout value if it's a valid number and not another flag/command if (!possibleValue.startsWith('-')) { const timeoutValue = parseInt(possibleValue); if (!isNaN(timeoutValue) && timeoutValue > 0) { timeout = timeoutValue; } // Always mark the next value for removal if it doesn't start with '-' timeoutValueIndex = timeoutIndex + 1; } } const filteredArgs = args.filter((arg, index) => { // Remove flags if (arg === '--json' || arg === '--all' || arg === '--no-transaction') { return false; } // Remove --timeout and its value if (arg === '--timeout') { return false; } if (timeoutValueIndex !== undefined && index === timeoutValueIndex) { return false; } return true; }); return { jsonMode, allSchemas, noTransaction, timeout, filteredArgs, }; } function validateDatabaseUrl(databaseUrl, jsonMode) { if (!databaseUrl) { return formatError('DATABASE_URL environment variable is not set', jsonMode, 'Make sure you have a .env file with DATABASE_URL from your Supabase project'); } return null; } function createPool(connectionString) { return new pg_1.Pool({ connectionString, ssl: { rejectUnauthorized: false }, }); } function getCommandInfo(command) { const commands = { exec: { command: 'exec', needsArgument: true, argumentName: 'SQL query' }, file: { command: 'file', needsArgument: true, argumentName: 'file path' }, schema: { command: 'schema', needsArgument: false }, }; return commands[command] || null; } function validateCommandArgument(commandInfo, argument, jsonMode) { if (commandInfo.needsArgument && !argument) { return formatError(`No ${commandInfo.argumentName} provided`, jsonMode); } return null; } function validateFile(filepath, jsonMode) { if (!(0, fs_1.existsSync)(filepath)) { return formatError(`File not found: ${filepath}`, jsonMode); } return null; } function readSqlFile(filepath) { return (0, fs_1.readFileSync)(filepath, 'utf8'); } function formatQueryResultsJson(result, duration) { const output = { success: true, command: result.command || 'Query executed', rowCount: result.rowCount || 0, rows: result.rows || [], duration: duration, }; return JSON.stringify(output); } class SqlAgentError extends Error { constructor(message, code, hint) { super(message); this.code = code; this.hint = hint; this.name = 'SqlAgentError'; } } exports.SqlAgentError = SqlAgentError; function createNoCommandError() { return new SqlAgentError('No command provided', 'NO_COMMAND', 'Run sequelae --help for usage information'); } function createNoSqlQueryError() { return new SqlAgentError('No SQL query provided', 'NO_SQL_QUERY'); } function createNoFilePathError() { return new SqlAgentError('No file path provided', 'NO_FILE_PATH'); } function createFileNotFoundError(filepath) { return new SqlAgentError(`File not found: ${filepath}`, 'FILE_NOT_FOUND'); } function formatSqlError(error, jsonMode) { if (jsonMode) { const errorOutput = { success: false, error: error.message, position: error.position, }; return JSON.stringify(errorOutput); } else { let output = `\nError: ${error.message}`; if (error.position) { output += `\nPosition: ${error.position}`; } return output; } } function formatCommandResult(command, rowCount, duration) { const commandText = command || 'Query executed'; const rowCountText = rowCount ? `(${rowCount} rows)` : ''; return `\nāœ“ ${commandText} ${rowCountText} - ${duration}ms`; } function buildSchemaCondition(allSchemas) { return allSchemas ? "table_schema NOT IN ('pg_catalog', 'information_schema')" : "table_schema = 'public'"; } async function cleanupPool(pool) { try { await pool.end(); } catch (error) { cliOutput.error(`Error closing database pool: ${error}`); } } // Cleanup function for shared pool // Currently not used as the pool is shared across all SqlExecutor instances // and will be cleaned up when the process exits async function _cleanupSharedPool() { const poolManager = pool_manager_1.PoolManager.getInstance(); await poolManager.close(); } function buildTableList(tables) { return tables .split(',') .map(t => t.trim()) .filter(t => t.length > 0); } function isDirectSqlCommand(command) { const sqlKeywords = [ 'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER', 'TRUNCATE', ]; const upperCommand = command.toUpperCase(); return sqlKeywords.some(keyword => upperCommand.startsWith(keyword)); } async function main() { const args = process.argv.slice(2); // Parse arguments const { jsonMode, allSchemas, noTransaction, timeout, filteredArgs } = parseArguments(args); // Skip header when running in Jest or JSON mode if (typeof jest === 'undefined' && !jsonMode) { cliOutput.log('šŸ”— sequelae-mcp - PostgreSQL SQL executor\n'); } // Handle no arguments if (filteredArgs.length === 0) { const error = createNoCommandError(); const output = formatError(error.message, jsonMode, error.hint); if (jsonMode) { cliOutput.json(JSON.parse(output)); } else { cliOutput.error(output); } process.exit(1); } // Handle help if (filteredArgs[0] === '--help' || filteredArgs[0] === '-h') { const output = handleHelp(jsonMode); if (jsonMode) { cliOutput.json(JSON.parse(output)); } else { cliOutput.log(output); } process.exit(0); } // Handle version if (filteredArgs[0] === '--version' || filteredArgs[0] === '-v') { const output = handleVersion(jsonMode); if (jsonMode) { cliOutput.json(JSON.parse(output)); } else { cliOutput.log(output); } process.exit(0); } // Handle exit command if (filteredArgs[0] === 'exit' || filteredArgs[0] === 'quit') { const output = handleExit(jsonMode); if (jsonMode) { cliOutput.json(JSON.parse(output)); } else { cliOutput.log(output); } process.exit(0); } const databaseUrl = process.env.DATABASE_URL; // Set timeout environment variable if provided if (timeout) { process.env.POSTGRES_STATEMENT_TIMEOUT = timeout.toString(); } const dbError = validateDatabaseUrl(databaseUrl, jsonMode); if (dbError) { if (jsonMode) { cliOutput.json(JSON.parse(dbError)); } else { cliOutput.error(dbError); } process.exit(1); } // At this point, databaseUrl is guaranteed to be defined const pool = createPool(databaseUrl); try { let sql; if (filteredArgs[0] === 'exec') { if (!filteredArgs[1]) { const error = createNoSqlQueryError(); const output = formatError(error.message, jsonMode, error.hint); if (jsonMode) { cliOutput.json(JSON.parse(output)); } else { cliOutput.error(output); } await cleanupPool(pool); process.exit(1); } sql = filteredArgs[1]; } else if (filteredArgs[0] === 'file') { if (!filteredArgs[1]) { const error = createNoFilePathError(); const output = formatError(error.message, jsonMode, error.hint); if (jsonMode) { cliOutput.json(JSON.parse(output)); } else { cliOutput.error(output); } await cleanupPool(pool); process.exit(1); } const filepath = (0, path_1.resolve)(process.cwd(), filteredArgs[1]); // Check if file exists if (!(0, fs_1.existsSync)(filepath)) { const error = createFileNotFoundError(filepath); const output = formatError(error.message, jsonMode, error.hint); if (jsonMode) { cliOutput.json(JSON.parse(output)); } else { cliOutput.error(output); } await cleanupPool(pool); process.exit(1); } sql = (0, fs_1.readFileSync)(filepath, 'utf8'); } else if (filteredArgs[0] === 'schema') { // Schema command - show database structure // Join all remaining arguments as they might be space-separated table names const specificTables = filteredArgs.slice(1).join(' '); // Could be comma-separated list if (specificTables) { // Schema for specific tables const tableList = specificTables .split(',') .map(t => t.trim()) .filter(t => t.length > 0); // Remove empty strings if (tableList.length === 0) { if (jsonMode) { cliOutput.json({ error: 'No table names provided' }); } else { cliOutput.error('Error: No table names provided'); } await cleanupPool(pool); process.exit(1); } const tableCondition = tableList.map(t => `'${t}'`).join(','); sql = ` WITH requested_tables AS ( SELECT unnest(ARRAY[${tableCondition}]) as table_name ), existing_tables AS ( SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND ${allSchemas ? "table_schema NOT IN ('pg_catalog', 'information_schema')" : "table_schema = 'public'"} ), table_info AS ( SELECT t.table_schema, t.table_name, json_agg( json_build_object( 'column_name', c.column_name, 'data_type', c.data_type, 'is_nullable', c.is_nullable, 'column_default', c.column_default, 'character_maximum_length', c.character_maximum_length ) ORDER BY c.ordinal_position )::text as columns FROM information_schema.tables t JOIN information_schema.columns c ON t.table_schema = c.table_schema AND t.table_name = c.table_name JOIN requested_tables rt ON t.table_name = rt.table_name WHERE ${allSchemas ? "t.table_schema NOT IN ('pg_catalog', 'information_schema')" : "t.table_schema = 'public'"} AND t.table_type = 'BASE TABLE' GROUP BY t.table_schema, t.table_name ), constraint_info AS ( SELECT tc.table_schema, tc.table_name, json_agg( json_build_object( 'constraint_name', tc.constraint_name, 'constraint_type', tc.constraint_type, 'column_name', kcu.column_name ) )::text as constraints FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN requested_tables rt ON tc.table_name = rt.table_name WHERE ${allSchemas ? "tc.table_schema NOT IN ('pg_catalog', 'information_schema')" : "tc.table_schema = 'public'"} GROUP BY tc.table_schema, tc.table_name ), missing_tables AS ( SELECT rt.table_name as missing_table, string_agg(et.table_name, ', ') as suggestions FROM requested_tables rt LEFT JOIN existing_tables et ON rt.table_name = et.table_name WHERE et.table_name IS NULL GROUP BY rt.table_name ) SELECT 'found' as type, ti.table_schema, ti.table_name, ti.columns, COALESCE(ci.constraints, '[]') as constraints, NULL as missing_table, NULL as suggestions FROM table_info ti LEFT JOIN constraint_info ci ON ti.table_schema = ci.table_schema AND ti.table_name = ci.table_name UNION ALL SELECT 'missing' as type, NULL as table_schema, NULL as table_name, NULL as columns, NULL as constraints, mt.missing_table, (SELECT string_agg(tn, ', ') FROM ( SELECT table_name as tn FROM existing_tables WHERE LOWER(table_name) LIKE LOWER(LEFT(mt.missing_table, 3) || '%') OR LOWER(table_name) LIKE '%' || LOWER(LEFT(mt.missing_table, 3)) || '%' ORDER BY CASE WHEN LOWER(table_name) LIKE LOWER(LEFT(mt.missing_table, 3) || '%') THEN 0 ELSE 1 END, LENGTH(table_name) LIMIT 3 ) s) as suggestions FROM missing_tables mt ORDER BY type, table_schema, table_name; `; } else { // Show all tables sql = ` WITH table_info AS ( SELECT t.table_schema, t.table_name, json_agg( json_build_object( 'column_name', c.column_name, 'data_type', c.data_type, 'is_nullable', c.is_nullable, 'column_default', c.column_default, 'character_maximum_length', c.character_maximum_length ) ORDER BY c.ordinal_position )::text as columns FROM information_schema.tables t JOIN information_schema.columns c ON t.table_schema = c.table_schema AND t.table_name = c.table_name WHERE ${allSchemas ? "t.table_schema NOT IN ('pg_catalog', 'information_schema')" : "t.table_schema = 'public'"} AND t.table_type = 'BASE TABLE' GROUP BY t.table_schema, t.table_name ), constraint_info AS ( SELECT tc.table_schema, tc.table_name, json_agg( json_build_object( 'constraint_name', tc.constraint_name, 'constraint_type', tc.constraint_type, 'column_name', kcu.column_name ) )::text as constraints FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema WHERE ${allSchemas ? "tc.table_schema NOT IN ('pg_catalog', 'information_schema')" : "tc.table_schema = 'public'"} GROUP BY tc.table_schema, tc.table_name ) SELECT 'found' as type, ti.table_schema, ti.table_name, ti.columns, COALESCE(ci.constraints, '[]') as constraints, NULL as missing_table, NULL as suggestions FROM table_info ti LEFT JOIN constraint_info ci ON ti.table_schema = ci.table_schema AND ti.table_name = ci.table_name ORDER BY ti.table_schema, ti.table_name; `; } } else if (filteredArgs[0] === 'exec' || filteredArgs[0] === 'file') { // Command recognized but missing argument if (jsonMode) { cliOutput.json({ error: `Missing argument for ${filteredArgs[0]} command` }); } else { cliOutput.error(`Error: Missing argument for ${filteredArgs[0]} command`); } await cleanupPool(pool); process.exit(1); } else { // Check if it looks like a SQL command const sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER']; const firstWord = filteredArgs[0].toUpperCase(); if (sqlKeywords.includes(firstWord)) { // Direct SQL command sql = filteredArgs.join(' '); } else if (filteredArgs[0] === 'backup') { // Handle backup command const executor = new sql_executor_1.SqlExecutor(databaseUrl); try { // Parse backup options const options = {}; for (let i = 1; i < filteredArgs.length; i++) { const arg = filteredArgs[i]; if (arg === '--format' && i + 1 < filteredArgs.length) { options.format = filteredArgs[++i]; } else if (arg === '--tables' && i + 1 < filteredArgs.length) { options.tables = filteredArgs[++i].split(',').map(t => t.trim()); } else if (arg === '--schemas' && i + 1 < filteredArgs.length) { options.schemas = filteredArgs[++i].split(',').map(s => s.trim()); } else if (arg === '--output' && i + 1 < filteredArgs.length) { options.outputPath = filteredArgs[++i]; } else if (arg === '--data-only') { options.dataOnly = true; } else if (arg === '--schema-only') { options.schemaOnly = true; } else if (arg === '--compress') { options.compress = true; } } // Show progress if (!jsonMode) { cliOutput.log('Creating backup...'); } const result = await executor.backup(options); if (result.success) { if (jsonMode) { cliOutput.json({ success: true, outputPath: result.outputPath, size: result.size, duration: result.duration, }); } else { cliOutput.log(`\nāœ… Backup completed successfully!`); cliOutput.log(`šŸ“ Output: ${result.outputPath}`); if (result.size) { cliOutput.log(`šŸ“Š Size: ${(result.size / 1024 / 1024).toFixed(2)} MB`); } cliOutput.log(`ā±ļø Duration: ${(result.duration / 1000).toFixed(2)}s`); } await cleanupPool(pool); process.exit(0); } else { if (jsonMode) { cliOutput.json({ success: false, error: result.error, }); } else { cliOutput.error(`\nāŒ Backup failed: ${result.error}`); } await cleanupPool(pool); process.exit(1); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (jsonMode) { cliOutput.json({ error: errorMessage }); } else { cliOutput.error(`Error: ${errorMessage}`); } await cleanupPool(pool); process.exit(1); } finally { await executor.close(); } } else { // Unknown command if (jsonMode) { cliOutput.json({ error: `Unknown command: ${filteredArgs[0]}` }); } else { cliOutput.error(`Error: Unknown command: ${filteredArgs[0]}`); cliOutput.error('Run sequelae --help for usage information'); } await cleanupPool(pool); process.exit(1); } } // Execute the query using SqlExecutor const executor = new sql_executor_1.SqlExecutor(databaseUrl); let result; try { if (filteredArgs[0] === 'file') { result = await executor.executeFile(filteredArgs[1], !noTransaction, timeout); } else { result = await executor.executeQuery(sql, !noTransaction, timeout); } } finally { await executor.close(); } // Display results if (jsonMode) { const output = { success: true, command: result.command || 'Query executed', rowCount: result.rowCount || 0, rows: result.rows || [], duration: result.duration || 0, }; cliOutput.json(output); } else { // Special handling for schema command if (filteredArgs[0] === 'schema' && result.rows && result.rows.length > 0) { cliOutput.log('DATABASE SCHEMA:\n'); // Separate found and missing tables const foundTables = result.rows.filter(r => r.type === 'found'); const missingTables = result.rows.filter(r => r.type === 'missing'); // Display found tables for (const table of foundTables) { cliOutput.log(`šŸ“‹ ${table.table_schema}.${table.table_name}`); // Display columns const columns = JSON.parse(table.columns); cliOutput.log(' Columns:'); // Collect JSONB columns for analysis const jsonbColumns = []; for (const col of columns) { const nullable = col.is_nullable === 'YES' ? ' (nullable)' : ''; const dataType = col.character_maximum_length ? `${col.data_type}(${col.character_maximum_length})` : col.data_type; const defaultVal = col.column_default ? ` DEFAULT ${col.column_default}` : ''; cliOutput.log(` - ${col.column_name}: ${dataType}${nullable}${defaultVal}`); // Check if this is a JSONB column if (col.data_type === 'jsonb') { jsonbColumns.push({ column: col, tableName: table.table_name, schemaName: table.table_schema, }); } } // Analyze JSONB columns if any if (jsonbColumns.length > 0) { const client = await pool.connect(); try { for (const { column, tableName, schemaName } of jsonbColumns) { const samples = await (0, jsonb_analyzer_1.sampleJsonbColumn)(client, `${schemaName}.${tableName}`, column.column_name, 10); const structure = (0, jsonb_analyzer_1.analyzeJsonStructure)(samples); const formatted = (0, jsonb_analyzer_1.formatJsonStructure)(structure); if (formatted.trim()) { cliOutput.log(` Structure of ${column.column_name}:`); cliOutput.log(formatted); } } } finally { client.release(); } } // Display constraints const constraints = JSON.parse(table.constraints); if (constraints.length > 0) { cliOutput.log(' Constraints:'); const constraintsByType = constraints.reduce((acc, c) => { if (!acc[c.constraint_type]) acc[c.constraint_type] = []; acc[c.constraint_type].push(c); return acc; }, {}); for (const [type, consts] of Object.entries(constraintsByType)) { const columns = consts.map(c => c.column_name).join(', '); cliOutput.log(` - ${type}: ${columns}`); } } cliOutput.log(''); } // Display missing tables with suggestions if (missingTables.length > 0) { cliOutput.log('āŒ TABLES NOT FOUND:\n'); for (const missing of missingTables) { cliOutput.log(` - "${missing.missing_table}"`); if (missing.suggestions) { cliOutput.log(` Did you mean: ${missing.suggestions}?`); } } cliOutput.log(''); } } else if (result.rows && result.rows.length > 0) { cliOutput.table(result.rows); } // Show execution info const command = result.command || 'Query executed'; cliOutput.log(`\nāœ“ ${command} ${result.rowCount ? `(${result.rowCount} rows)` : ''} - ${result.duration}ms`); } } catch (error) { const err = error; if (jsonMode) { const errorOutput = { success: false, error: err.message, position: err.position, }; cliOutput.json(errorOutput); } else { cliOutput.error(`\nError: ${err.message}`); if (err.position) { cliOutput.error(`Position: ${err.position}`); } } await pool.end(); process.exit(1); } // Success - close pool and exit cleanly try { await pool.end(); } catch (error) { cliOutput.error(`Error closing database pool: ${error}`); } process.exit(0); } // Only run if this module is executed directly if (require.main === module) { // Run main and handle unhandled errors main().catch(async (error) => { cliOutput.error(String(error)); process.exit(1); }); // Handle process errors properly process.on('unhandledRejection', (reason, promise) => { cliOutput.error(`Unhandled Rejection at: ${promise}, reason: ${reason}`); process.exit(1); }); process.on('uncaughtException', error => { cliOutput.error(`Uncaught Exception: ${error}`); process.exit(1); }); } //# sourceMappingURL=cli.js.map