matimo-examples 0.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "matimo-examples",
3
+ "version": "0.1.0-alpha.7",
4
+ "description": "Matimo SDK examples - Factory Pattern, Decorator Pattern, and LangChain integration with Slack and Gmail",
5
+ "type": "module",
6
+ "dependencies": {
7
+ "@langchain/core": "^1.1.18",
8
+ "@langchain/openai": "^1.2.4",
9
+ "langchain": "^1.2.16",
10
+ "dotenv": "^17.2.3",
11
+ "zod": "^4.3.6",
12
+ "matimo": "0.1.0-alpha.7",
13
+ "@matimo/gmail": "0.1.0-alpha.7",
14
+ "@matimo/postgres": "0.1.0-alpha.7",
15
+ "@matimo/slack": "0.1.0-alpha.7"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^25.1.0",
19
+ "better-sqlite3": "^12.6.2",
20
+ "typescript": "^5.9.3",
21
+ "tsx": "^4.21.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "scripts": {
27
+ "agent:decorator": "tsx agents/decorator-pattern-agent.ts",
28
+ "agent:factory": "tsx agents/factory-pattern-agent.ts",
29
+ "agent:langchain": "tsx agents/langchain-agent.ts",
30
+ "gmail:factory": "tsx gmail/gmail-factory.ts",
31
+ "gmail:decorator": "tsx gmail/gmail-decorator.ts",
32
+ "gmail:langchain": "tsx gmail/gmail-langchain.ts",
33
+ "slack:factory": "tsx slack/slack-factory.ts",
34
+ "slack:decorator": "tsx slack/slack-decorator.ts",
35
+ "slack:langchain": "tsx slack/slack-langchain.ts",
36
+ "postgres:factory": "tsx postgres/postgres-factory.ts",
37
+ "postgres:decorator": "tsx postgres/postgres-decorator.ts",
38
+ "postgres:langchain": "tsx postgres/postgres-langchain.ts",
39
+ "postgres:approval": "tsx postgres/postgres-with-approval.ts",
40
+ "execute:factory": "tsx execute/execute-factory.ts",
41
+ "execute:decorator": "tsx execute/execute-decorator.ts",
42
+ "execute:langchain": "tsx execute/execute-langchain.ts",
43
+ "read:factory": "tsx read/read-factory.ts",
44
+ "read:decorator": "tsx read/read-decorator.ts",
45
+ "read:langchain": "tsx read/read-langchain.ts",
46
+ "edit:factory": "tsx edit/edit-factory.ts",
47
+ "edit:decorator": "tsx edit/edit-decorator.ts",
48
+ "edit:langchain": "tsx edit/edit-langchain.ts",
49
+ "search:factory": "tsx search/search-factory.ts",
50
+ "search:decorator": "tsx search/search-decorator.ts",
51
+ "search:langchain": "tsx search/search-langchain.ts",
52
+ "web:factory": "tsx web/web-factory.ts",
53
+ "web:decorator": "tsx web/web-decorator.ts",
54
+ "web:langchain": "tsx web/web-langchain.ts",
55
+ "build": "tsc",
56
+ "clean": "rm -rf dist"
57
+ }
58
+ }
@@ -0,0 +1,188 @@
1
+ # Postgres Tool Examples
2
+
3
+ These examples demonstrate how to use the Matimo Postgres execute-sql tool with different patterns and approval flows.
4
+
5
+ ## ⚡ Quick Setup
6
+
7
+
8
+
9
+ ## Prerequisites
10
+
11
+ Before running any examples, you need a Postgres database instance. You can either:
12
+
13
+ ### Option 1: Docker (Recommended - One Command)
14
+
15
+ ```bash
16
+ docker run --name postgres-matimo -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres
17
+ ```
18
+
19
+ ### Option 2: Local Installation
20
+
21
+ **macOS with Homebrew:**
22
+ ```bash
23
+ brew install postgresql
24
+ brew services start postgresql
25
+ ```
26
+
27
+ **Linux (Ubuntu/Debian):**
28
+ ```bash
29
+ sudo apt-get install postgresql
30
+ sudo systemctl start postgresql
31
+ ```
32
+
33
+ ### Configure Connection String
34
+
35
+ The examples use these environment variables from `.env`:
36
+
37
+ ```bash
38
+ MATIMO_POSTGRES_HOST=localhost
39
+ MATIMO_POSTGRES_PORT=5432
40
+ MATIMO_POSTGRES_USER=your_user_name
41
+ MATIMO_POSTGRES_PASSWORD=your_db_password
42
+ MATIMO_POSTGRES_DB=your_db_name
43
+ ```
44
+
45
+ Or use a full connection string:
46
+
47
+ ```bash
48
+ MATIMO_POSTGRES_URL="your connection string"
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Running the Examples
54
+
55
+ ### 1. Factory Pattern (Simple Usage)
56
+
57
+ ```bash
58
+ pnpm tsx examples/tools/postgres/postgres-factory.ts
59
+ or
60
+ pnpm postgres:factory
61
+ ```
62
+
63
+ Basic example showing how to initialize Matimo and execute a tool.
64
+
65
+ ### 2. Decorator Pattern (Class-Based)
66
+
67
+ ```bash
68
+ pnpm tsx examples/tools/postgres/postgres-decorator.ts
69
+ or
70
+ pnpm postgres:decorator
71
+ ```
72
+
73
+ Example using the `@tool` decorator for a class-based approach.
74
+
75
+ ### 3. LangChain Integration
76
+
77
+ ```bash
78
+ pnpm tsx examples/tools/postgres/postgres-langchain.ts
79
+ or
80
+ pnpm postgres:langchain
81
+ ```
82
+
83
+ Example converting Postgres tools to LangChain-compatible schemas.
84
+
85
+ ### 4. Real Database with Approval Flow (Interactive)
86
+
87
+ ```bash
88
+ pnpm tsx examples/tools/postgres/postgres-with-approval.ts
89
+ or
90
+ pnpm postgres:approval
91
+ ```
92
+
93
+ **Most comprehensive example** that demonstrates:
94
+ - ✅ Non-destructive queries (SELECT) - no approval needed
95
+ - ✅ Destructive queries (INSERT, UPDATE, DELETE) - interactive approval
96
+ - ✅ Interactive approval callback that asks for user confirmation
97
+ - ✅ Auto-approval mode for CI/automated environments
98
+ - ✅ Error handling and proper feedback
99
+
100
+ This example **requires a real Postgres connection** and will:
101
+ 1. Ask for permission before executing destructive SQL operations
102
+ 2. Show actual query results
103
+ 3. Demonstrate approval rejection workflow
104
+
105
+ **Example session:**
106
+ ```
107
+ 🚀 Postgres Tool Example with Approval Flow
108
+ ==================================================
109
+
110
+ 1️⃣ Non-destructive query (SELECT - no approval needed)
111
+ --------------------------------------------------
112
+ ✅ Query executed successfully
113
+ Result: { command: 'SELECT', rowCount: 1, rows: [{...}] }
114
+
115
+ 2️⃣ Destructive query (INSERT - requires approval)
116
+ --------------------------------------------------
117
+ ⚠️ Approval Required for WRITE operation:
118
+ SQL: INSERT INTO test_table ...
119
+ Do you approve? (yes/no): yes
120
+ Result: ✅ APPROVED
121
+ ✅ INSERT approved and executed successfully
122
+ ```
123
+
124
+ ## Approval Modes
125
+
126
+ ### Interactive Mode (Default)
127
+
128
+ In interactive mode, the tool will prompt you before executing destructive SQL:
129
+
130
+ ```bash
131
+ pnpm tsx examples/tools/postgres/postgres-with-approval.ts
132
+ ```
133
+
134
+ ### CI/Automated Mode
135
+
136
+ For automated environments or CI pipelines, enable auto-approval:
137
+
138
+ ```bash
139
+ export MATIMO_SQL_AUTO_APPROVE=true
140
+ pnpm tsx examples/tools/postgres/postgres-with-approval.ts
141
+ ```
142
+
143
+ ### Permanent Approval Patterns
144
+
145
+ Define SQL patterns that are permanently approved:
146
+
147
+ ```bash
148
+ export MATIMO_SQL_APPROVED_PATTERNS="DELETE FROM logs|DROP TABLE test_.*|TRUNCATE.*"
149
+ pnpm tsx examples/tools/postgres/postgres-with-approval.ts
150
+ ```
151
+
152
+ ## Destructive SQL Detection
153
+
154
+ The Postgres tool automatically detects and requires approval for these operations:
155
+ - `CREATE` - Create tables, indexes, etc.
156
+ - `DROP` - Drop tables, indexes, etc.
157
+ - `ALTER` - Alter tables, columns, etc.
158
+ - `TRUNCATE` - Truncate tables
159
+ - `DELETE` - Delete rows
160
+ - `UPDATE` - Update rows
161
+
162
+ **SELECT, INSERT (without parameter templating), and other read-only operations are not flagged as destructive.**
163
+
164
+ ## Troubleshooting
165
+
166
+ ### "Connection refused" Error
167
+
168
+ Make sure Postgres is running:
169
+
170
+ ```bash
171
+ # Check if running
172
+ psql -U postgres -c "SELECT version();"
173
+
174
+ # Or start Docker container
175
+ docker run --name postgres-matimo -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres
176
+ ```
177
+
178
+ ### "Permission denied" for CREATE TABLE
179
+
180
+ Use a superuser or ensure the user has CREATE permissions:
181
+
182
+ ```sql
183
+ ALTER USER username CREATEDB;
184
+ ```
185
+
186
+ ### Questions?
187
+
188
+ Check the [Matimo documentation](https://github.com/tallclub/matimo/docs) or [GitHub issues](https://github.com/tallclub/matimo/issues).
@@ -0,0 +1,198 @@
1
+ import 'dotenv/config';
2
+ import { MatimoInstance, setGlobalMatimoInstance, tool } from 'matimo';
3
+
4
+ async function main() {
5
+ console.info('🚀 Postgres Decorator Pattern Example');
6
+ console.info('='.repeat(60));
7
+
8
+ try {
9
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
10
+ setGlobalMatimoInstance(matimo);
11
+
12
+ console.info('\n📦 Initializing Matimo with decorators...');
13
+
14
+ // Class-based database client using Matimo
15
+ class PostgresAgent {
16
+ private matimo: MatimoInstance;
17
+
18
+ constructor(matimo: MatimoInstance) {
19
+ this.matimo = matimo;
20
+ }
21
+
22
+ // Sequential step 1: Discover tables
23
+ async discoverTables(): Promise<string[]> {
24
+ console.info('\n1️⃣ DISCOVER TABLES (Step 1/3)');
25
+ console.info('-'.repeat(60));
26
+ console.info('SQL: Getting all tables from information_schema...\n');
27
+
28
+ const result = await this.matimo.execute('postgres-execute-sql', {
29
+ sql: `
30
+ SELECT table_name
31
+ FROM information_schema.tables
32
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
33
+ ORDER BY table_name;
34
+ `,
35
+ });
36
+
37
+ this.handleResult(result as any);
38
+
39
+ if (result && (result as any).rows && (result as any).rows.length > 0) {
40
+ return (result as any).rows.map((row: any) => row.table_name);
41
+ }
42
+ return [];
43
+ }
44
+
45
+ // Sequential step 2: Get row counts
46
+ async getTableRowCounts(): Promise<void> {
47
+ console.info('\n2️⃣ COUNT RECORDS (Step 2/3)');
48
+ console.info('-'.repeat(60));
49
+ console.info('SQL: Getting record counts for tables...\n');
50
+
51
+ // Try pg_stat_user_tables first, fall back to manual counts
52
+ try {
53
+ const result = await this.matimo.execute('postgres-execute-sql', {
54
+ sql: `
55
+ SELECT
56
+ tablename,
57
+ n_live_tup as row_count
58
+ FROM pg_stat_user_tables
59
+ WHERE schemaname = 'public'
60
+ ORDER BY n_live_tup DESC;
61
+ `,
62
+ });
63
+
64
+ this.handleResult(result);
65
+ } catch (err: any) {
66
+ // Fall back to information_schema method
67
+ console.info('⚠️ Using information_schema for row counts (pg_stat not available)');
68
+ const result = await this.matimo.execute('postgres-execute-sql', {
69
+ sql: `
70
+ SELECT
71
+ table_name,
72
+ (SELECT count(*) FROM information_schema.columns
73
+ WHERE information_schema.columns.table_name = t.table_name) as columns
74
+ FROM information_schema.tables t
75
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
76
+ ORDER BY table_name;
77
+ `,
78
+ });
79
+
80
+ this.handleResult(result);
81
+ }
82
+ }
83
+
84
+ // Sequential step 3: Analyze first table structure
85
+ async analyzeTableStructure(tableName: string): Promise<void> {
86
+ console.info(`\n3️⃣ ANALYZE TABLE STRUCTURE (Step 3/3)`);
87
+ console.info('-'.repeat(60));
88
+ console.info(`SQL: Getting column structure for table "${tableName}"...\n`);
89
+
90
+ const result = await this.matimo.execute('postgres-execute-sql', {
91
+ sql: `
92
+ SELECT
93
+ column_name,
94
+ data_type,
95
+ is_nullable,
96
+ column_default
97
+ FROM information_schema.columns
98
+ WHERE table_name = $1
99
+ ORDER BY ordinal_position;
100
+ `,
101
+ params: [tableName],
102
+ });
103
+
104
+ this.handleResult(result);
105
+ }
106
+
107
+ // Bonus: Show database health
108
+ async showDatabaseHealth(): Promise<void> {
109
+ console.info('\n📊 DATABASE HEALTH');
110
+ console.info('-'.repeat(60));
111
+ console.info('SQL: Getting database and connection info...\n');
112
+
113
+ const result = await this.matimo.execute('postgres-execute-sql', {
114
+ sql: `
115
+ SELECT
116
+ current_database() as database_name,
117
+ version() as postgres_version,
118
+ now() as current_time,
119
+ (SELECT count(*) FROM pg_stat_activity) as active_connections;
120
+ `,
121
+ });
122
+
123
+ this.handleResult(result);
124
+ }
125
+
126
+ private handleResult(result: any) {
127
+ if (
128
+ result &&
129
+ typeof result === 'object' &&
130
+ 'success' in result &&
131
+ (result as any).success === false
132
+ ) {
133
+ throw new Error((result as any).error || 'Query failed');
134
+ }
135
+ if (result && typeof result === 'object' && 'rows' in result) {
136
+ const rows = (result as any).rows;
137
+ if (rows && Array.isArray(rows)) {
138
+ console.info(`✅ Found ${rows.length} row(s):`);
139
+ rows.forEach((row: any, idx: number) => {
140
+ console.info(` [${idx + 1}] ${JSON.stringify(row)}`);
141
+ });
142
+ } else {
143
+ console.info('✅ Query executed successfully');
144
+ }
145
+ } else {
146
+ console.info('✅ Query executed successfully');
147
+ }
148
+ }
149
+ }
150
+
151
+ const agent = new PostgresAgent(matimo);
152
+
153
+ console.info('✅ Agent initialized\n');
154
+
155
+ // Execute sequential workflow
156
+ const tables = await agent.discoverTables();
157
+
158
+ await agent.getTableRowCounts();
159
+
160
+ // Analyze first discovered table if any exist
161
+ if (tables.length > 0) {
162
+ await agent.analyzeTableStructure(tables[0]);
163
+ } else {
164
+ console.info('\n3️⃣ ANALYZE TABLE STRUCTURE (Step 3/3)');
165
+ console.info('-'.repeat(60));
166
+ console.info('⚠️ No tables found to analyze\n');
167
+ }
168
+
169
+ // Show additional database health info
170
+ await agent.showDatabaseHealth();
171
+
172
+ console.info('\n\n✨ Sequential discovery complete!');
173
+ console.info('='.repeat(60));
174
+ console.info('Pattern: 1) Get tables 2) Count rows 3) Analyze discovered table\n');
175
+ } catch (err: any) {
176
+ console.error('\n❌ Error:');
177
+ const message = err.message || String(err);
178
+ console.error('Message:', message);
179
+
180
+ if (message.includes('ECONNREFUSED')) {
181
+ console.error('\n💡 Connection refused - Postgres not running?');
182
+ } else if (message.includes('does not exist')) {
183
+ console.error('\n💡 Database or user does not exist');
184
+ }
185
+
186
+ console.error('\nCurrent .env Postgres settings:');
187
+ console.error(` MATIMO_POSTGRES_HOST=${process.env.MATIMO_POSTGRES_HOST || '(not set)'}`);
188
+ console.error(` MATIMO_POSTGRES_USER=${process.env.MATIMO_POSTGRES_USER || '(not set)'}`);
189
+ console.error(` MATIMO_POSTGRES_DB=${process.env.MATIMO_POSTGRES_DB || '(not set)'}`);
190
+
191
+ process.exit(1);
192
+ }
193
+ }
194
+
195
+ main().catch((err) => {
196
+ console.error('Fatal error:', err);
197
+ process.exit(1);
198
+ });
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { MatimoInstance } from 'matimo';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ async function main() {
10
+ console.info('🚀 Postgres Factory Pattern Example');
11
+ console.info('='.repeat(60));
12
+
13
+ try {
14
+ // Initialize Matimo with auto-discovery
15
+ console.info('\n📦 Initializing Matimo...');
16
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
17
+
18
+ // Get postgres tools
19
+ const tools = matimo.listTools();
20
+ const postgresTools = tools.filter((t) => t.name.startsWith('postgres'));
21
+ console.info(`✅ Found ${postgresTools.length} Postgres tool(s)`);
22
+
23
+ // STEP 1: Discover available tables
24
+ console.info('\n\n1️⃣ DISCOVER TABLES (Step 1/3)');
25
+ console.info('-'.repeat(60));
26
+ console.info('SQL: Getting all tables from information_schema...\n');
27
+
28
+ const tablesResult = await matimo.execute('postgres-execute-sql', {
29
+ sql: `
30
+ SELECT table_name,
31
+ (SELECT count(*) FROM information_schema.columns
32
+ WHERE information_schema.columns.table_name = t.table_name) as column_count
33
+ FROM information_schema.tables t
34
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
35
+ ORDER BY table_name;
36
+ `,
37
+ });
38
+
39
+ if (
40
+ tablesResult &&
41
+ typeof tablesResult === 'object' &&
42
+ 'success' in tablesResult &&
43
+ (tablesResult as any).success === false
44
+ ) {
45
+ throw new Error((tablesResult as any).error || 'Query failed');
46
+ }
47
+
48
+ let tableNames: string[] = [];
49
+ console.info('✅ Tables found:');
50
+ if (tablesResult && typeof tablesResult === 'object' && 'rows' in tablesResult) {
51
+ const rows = (tablesResult as any).rows;
52
+ if (rows && rows.length > 0) {
53
+ rows.forEach((row: any) => {
54
+ tableNames.push(row.table_name);
55
+ console.info(` - ${row.table_name} (${row.column_count} columns)`);
56
+ });
57
+ } else {
58
+ console.info(' (No tables in public schema)');
59
+ }
60
+ } else {
61
+ console.info(' (No tables in public schema)');
62
+ }
63
+
64
+ // STEP 2: Get row counts for each table
65
+ console.info('\n\n2️⃣ COUNT RECORDS (Step 2/3)');
66
+ console.info('-'.repeat(60));
67
+
68
+ if (tableNames.length > 0) {
69
+ console.info(`SQL: Getting record counts for ${tableNames.length} table(s)...\n`);
70
+
71
+ const countsResult = await matimo.execute('postgres-execute-sql', {
72
+ sql: `
73
+ SELECT
74
+ schemaname,
75
+ tablename,
76
+ n_live_tup as row_count
77
+ FROM pg_stat_user_tables
78
+ WHERE schemaname = 'public'
79
+ ORDER BY n_live_tup DESC;
80
+ `,
81
+ params: [],
82
+ });
83
+
84
+ if (
85
+ countsResult &&
86
+ typeof countsResult === 'object' &&
87
+ 'success' in countsResult &&
88
+ (countsResult as any).success === false
89
+ ) {
90
+ console.info('⚠️ Could not get row counts');
91
+ } else {
92
+ console.info('✅ Record counts by table:');
93
+ if (countsResult && typeof countsResult === 'object' && 'rows' in countsResult) {
94
+ const rows = (countsResult as any).rows;
95
+ if (rows && rows.length > 0) {
96
+ rows.forEach((row: any) => {
97
+ console.info(` - ${row.tablename}: ${row.row_count} rows`);
98
+ });
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ // STEP 3: Analyze the first discovered table
105
+ console.info('\n\n3️⃣ ANALYZE TABLE STRUCTURE (Step 3/3)');
106
+ console.info('-'.repeat(60));
107
+
108
+ if (tableNames.length > 0) {
109
+ const firstTable = tableNames[0];
110
+ console.info(`SQL: Getting column structure for table "${firstTable}"...\n`);
111
+
112
+ const columnsResult = await matimo.execute('postgres-execute-sql', {
113
+ sql: `
114
+ SELECT
115
+ column_name,
116
+ data_type,
117
+ is_nullable,
118
+ column_default
119
+ FROM information_schema.columns
120
+ WHERE table_name = $1
121
+ ORDER BY ordinal_position;
122
+ `,
123
+ params: [firstTable],
124
+ });
125
+
126
+ if (
127
+ columnsResult &&
128
+ typeof columnsResult === 'object' &&
129
+ 'success' in columnsResult &&
130
+ (columnsResult as any).success === false
131
+ ) {
132
+ console.info('⚠️ Could not get column structure');
133
+ } else {
134
+ console.info(`✅ Columns in "${firstTable}" table:`);
135
+ if (columnsResult && typeof columnsResult === 'object' && 'rows' in columnsResult) {
136
+ const rows = (columnsResult as any).rows;
137
+ if (rows && rows.length > 0) {
138
+ rows.forEach((row: any) => {
139
+ const nullable = row.is_nullable === 'YES' ? 'nullable' : 'NOT NULL';
140
+ const defaultVal = row.column_default ? ` = ${row.column_default}` : '';
141
+ console.info(` - ${row.column_name} (${row.data_type}) ${nullable}${defaultVal}`);
142
+ });
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ console.info('\n\n✨ Sequential discovery complete!');
149
+ console.info('='.repeat(60));
150
+ console.info('Pattern: 1) Get tables 2) Count rows 3) Analyze discovered table\n');
151
+ } catch (err: any) {
152
+ console.error('\n❌ Error:');
153
+
154
+ // Try to extract helpful information
155
+ const message = err.message || String(err);
156
+ console.error('Message:', message);
157
+
158
+ // Provide specific hints based on error type
159
+ if (message.includes('ECONNREFUSED') || message.includes('Connection refused')) {
160
+ console.error('\n💡 Connection refused - Postgres not running?');
161
+ console.error(' Check docker-compose or your Postgres instance');
162
+ } else if (message.includes('does not exist')) {
163
+ console.error('\n💡 Database or user does not exist');
164
+ console.error(' Verify MATIMO_POSTGRES_* env vars in .env');
165
+ }
166
+
167
+ console.error('\nCurrent .env Postgres settings:');
168
+ console.error(` MATIMO_POSTGRES_HOST=${process.env.MATIMO_POSTGRES_HOST || '(not set)'}`);
169
+ console.error(` MATIMO_POSTGRES_PORT=${process.env.MATIMO_POSTGRES_PORT || '(not set)'}`);
170
+ console.error(` MATIMO_POSTGRES_USER=${process.env.MATIMO_POSTGRES_USER || '(not set)'}`);
171
+ console.error(` MATIMO_POSTGRES_DB=${process.env.MATIMO_POSTGRES_DB || '(not set)'}`);
172
+
173
+ process.exit(1);
174
+ }
175
+ }
176
+
177
+ main().catch((err) => {
178
+ console.error('Fatal error:', err);
179
+ process.exit(1);
180
+ });