matimo-examples 0.1.0-alpha.11
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/.env.example +49 -0
- package/LICENSE +21 -0
- package/README.md +525 -0
- package/agents/decorator-pattern-agent.ts +368 -0
- package/agents/factory-pattern-agent.ts +253 -0
- package/agents/langchain-agent.ts +146 -0
- package/edit/edit-decorator.ts +178 -0
- package/edit/edit-factory.ts +138 -0
- package/edit/edit-langchain.ts +292 -0
- package/execute/execute-decorator.ts +49 -0
- package/execute/execute-factory.ts +46 -0
- package/execute/execute-langchain.ts +232 -0
- package/github/github-decorator.ts +326 -0
- package/github/github-factory.ts +355 -0
- package/github/github-langchain.ts +206 -0
- package/github/github-with-approval.ts +228 -0
- package/gmail/README.md +345 -0
- package/gmail/gmail-decorator.ts +216 -0
- package/gmail/gmail-factory.ts +231 -0
- package/gmail/gmail-langchain.ts +201 -0
- package/hubspot/README.md +316 -0
- package/hubspot/hubspot-decorator.ts +180 -0
- package/hubspot/hubspot-factory.ts +188 -0
- package/hubspot/hubspot-langchain.ts +222 -0
- package/logger-example.ts +40 -0
- package/mailchimp/README.md +321 -0
- package/mailchimp/mailchimp-decorator.ts +277 -0
- package/mailchimp/mailchimp-factory.ts +187 -0
- package/mailchimp/mailchimp-langchain.ts +155 -0
- package/notion/README.md +293 -0
- package/notion/notion-decorator.ts +275 -0
- package/notion/notion-factory.ts +256 -0
- package/notion/notion-langchain.ts +237 -0
- package/package.json +79 -0
- package/postgres/README.md +188 -0
- package/postgres/postgres-decorator.ts +198 -0
- package/postgres/postgres-factory.ts +180 -0
- package/postgres/postgres-langchain.ts +213 -0
- package/postgres/postgres-with-approval.ts +344 -0
- package/read/read-decorator.ts +154 -0
- package/read/read-factory.ts +121 -0
- package/read/read-langchain.ts +273 -0
- package/search/search-decorator.ts +206 -0
- package/search/search-factory.ts +146 -0
- package/search/search-langchain.ts +255 -0
- package/slack/README.md +339 -0
- package/slack/slack-decorator.ts +245 -0
- package/slack/slack-factory.ts +226 -0
- package/slack/slack-langchain.ts +242 -0
- package/tsconfig.json +20 -0
- package/twilio/README.md +309 -0
- package/twilio/twilio-decorator.ts +288 -0
- package/twilio/twilio-factory.ts +238 -0
- package/twilio/twilio-langchain.ts +218 -0
- package/web/web-decorator.ts +52 -0
- package/web/web-factory.ts +70 -0
- package/web/web-langchain.ts +163 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ============================================================================
|
|
4
|
+
* POSTGRES TOOLS - LANGCHAIN AI AGENT EXAMPLE
|
|
5
|
+
* ============================================================================
|
|
6
|
+
*
|
|
7
|
+
* PATTERN: True AI Agent with OpenAI + LangChain
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* This is a REAL AI agent that:
|
|
10
|
+
* 1. Takes natural language user requests
|
|
11
|
+
* 2. Uses OpenAI LLM (GPT-4o-mini) to decide which Postgres tools to use
|
|
12
|
+
* 3. Generates appropriate SQL based on context
|
|
13
|
+
* 4. Executes queries autonomously
|
|
14
|
+
* 5. Processes results and responds naturally
|
|
15
|
+
*
|
|
16
|
+
* CRITICAL: Database credentials are NEVER exposed to the LLM
|
|
17
|
+
* - LLM only sees tool descriptions and results
|
|
18
|
+
* - Matimo handles all credential loading from environment variables
|
|
19
|
+
* - Tool execution is secure and isolated
|
|
20
|
+
*
|
|
21
|
+
* Use this pattern when:
|
|
22
|
+
* ✅ Building true autonomous AI agents
|
|
23
|
+
* ✅ LLM should decide which tools to use
|
|
24
|
+
* ✅ Complex workflows with LLM reasoning
|
|
25
|
+
* ✅ Multi-step agentic processes
|
|
26
|
+
* ✅ User gives high-level instructions (not SQL queries)
|
|
27
|
+
*
|
|
28
|
+
* SETUP:
|
|
29
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
30
|
+
* 1. Make sure Postgres is running (see POSTGRES_EXAMPLE_SETUP.md)
|
|
31
|
+
* 2. Create .env file with database credentials:
|
|
32
|
+
* MATIMO_POSTGRES_HOST=localhost
|
|
33
|
+
* MATIMO_POSTGRES_PORT=5432
|
|
34
|
+
* MATIMO_POSTGRES_USER=matimo
|
|
35
|
+
* MATIMO_POSTGRES_PASSWORD=development
|
|
36
|
+
* MATIMO_POSTGRES_DB=matimo
|
|
37
|
+
* OPENAI_API_KEY=sk-xxxxxxxxxxxxx
|
|
38
|
+
*
|
|
39
|
+
* USAGE:
|
|
40
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
41
|
+
* pnpm postgres:langchain
|
|
42
|
+
*
|
|
43
|
+
* WHAT IT DOES:
|
|
44
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
45
|
+
* This example shows an AI agent that can:
|
|
46
|
+
* 1. Explore database tables and schema
|
|
47
|
+
* 2. Execute SELECT queries to analyze data
|
|
48
|
+
* 3. Respond naturally in conversation style
|
|
49
|
+
* 4. Multi-step reasoning without exposing credentials
|
|
50
|
+
*
|
|
51
|
+
* Example:
|
|
52
|
+
* User: "What tables exist in matimo?"
|
|
53
|
+
* AI Agent: "Let me query the database to see what tables are available..."
|
|
54
|
+
* [Agent calls postgres-execute-sql tool with SELECT query]
|
|
55
|
+
* AI Agent: "The matimo database contains these tables: ..."
|
|
56
|
+
*
|
|
57
|
+
* ============================================================================
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
import 'dotenv/config';
|
|
61
|
+
import { MatimoInstance, convertToolsToLangChain, ToolDefinition } from 'matimo';
|
|
62
|
+
import { createAgent } from 'langchain';
|
|
63
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Run AI Agent with Postgres tools
|
|
67
|
+
* The agent receives natural language requests and decides which Postgres tools to use
|
|
68
|
+
*/
|
|
69
|
+
async function runPostgresAIAgent() {
|
|
70
|
+
console.info('\n╔════════════════════════════════════════════════════════╗');
|
|
71
|
+
console.info('║ Postgres AI Agent - LangChain + OpenAI ║');
|
|
72
|
+
console.info('║ True autonomous agent with LLM reasoning ║');
|
|
73
|
+
console.info('║ Database credentials: NEVER exposed to LLM ║');
|
|
74
|
+
console.info('╚════════════════════════════════════════════════════════╝\n');
|
|
75
|
+
|
|
76
|
+
// Check required environment variables
|
|
77
|
+
const pgConnected =
|
|
78
|
+
process.env.MATIMO_POSTGRES_HOST &&
|
|
79
|
+
process.env.MATIMO_POSTGRES_USER &&
|
|
80
|
+
process.env.MATIMO_POSTGRES_PASSWORD &&
|
|
81
|
+
process.env.MATIMO_POSTGRES_DB;
|
|
82
|
+
|
|
83
|
+
if (!pgConnected) {
|
|
84
|
+
console.error('❌ Error: Postgres connection not configured in .env');
|
|
85
|
+
console.info(' Required environment variables:');
|
|
86
|
+
console.info(' MATIMO_POSTGRES_HOST');
|
|
87
|
+
console.info(' MATIMO_POSTGRES_USER');
|
|
88
|
+
console.info(' MATIMO_POSTGRES_PASSWORD');
|
|
89
|
+
console.info(' MATIMO_POSTGRES_DB');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
94
|
+
if (!openaiKey) {
|
|
95
|
+
console.error('❌ Error: OPENAI_API_KEY not set in .env');
|
|
96
|
+
console.info(' Set it: export OPENAI_API_KEY="sk-..."');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.info(
|
|
101
|
+
`📍 Postgres: ${process.env.MATIMO_POSTGRES_DB}@${process.env.MATIMO_POSTGRES_HOST}`
|
|
102
|
+
);
|
|
103
|
+
console.info(`🤖 Using OpenAI (GPT-4o-mini) as the AI agent\n`);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// Initialize Matimo with auto-discovery
|
|
107
|
+
console.info('🚀 Initializing Matimo...');
|
|
108
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
109
|
+
|
|
110
|
+
// Get Postgres tools and convert to LangChain format
|
|
111
|
+
console.info('💬 Loading Postgres tools...');
|
|
112
|
+
const matimoTools = matimo.listTools();
|
|
113
|
+
const postgresTools = matimoTools.filter((t) => t.name.startsWith('postgres'));
|
|
114
|
+
console.info(`✅ Loaded ${postgresTools.length} Postgres tool(s)\n`);
|
|
115
|
+
|
|
116
|
+
// ✅ Convert Matimo tools to LangChain format using the built-in integration
|
|
117
|
+
const langchainTools = await convertToolsToLangChain(
|
|
118
|
+
postgresTools as ToolDefinition[],
|
|
119
|
+
matimo,
|
|
120
|
+
{
|
|
121
|
+
// Pass database credentials so LLM tool calls can execute securely
|
|
122
|
+
// The LLM will NEVER see these values - only Matimo tool execution will use them
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Initialize OpenAI LLM
|
|
127
|
+
console.info('🤖 Initializing OpenAI (GPT-4o-mini) LLM...');
|
|
128
|
+
const model = new ChatOpenAI({
|
|
129
|
+
modelName: 'gpt-4o-mini',
|
|
130
|
+
temperature: 0.7,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Create agent
|
|
134
|
+
console.info('🔧 Creating agent...\n');
|
|
135
|
+
const agent = await createAgent({
|
|
136
|
+
model,
|
|
137
|
+
tools: langchainTools as any[], // Type casting for LangChain tools
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Define agent tasks (sequential - each task uses results from previous)
|
|
141
|
+
const userRequests = [
|
|
142
|
+
{
|
|
143
|
+
title: 'Step 1: Discover tables',
|
|
144
|
+
request:
|
|
145
|
+
'Get a list of all tables in the matimo database. Query the information_schema to show me all tables and their row counts.',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
title: 'Step 2: Analyze data volume',
|
|
149
|
+
request:
|
|
150
|
+
'Now that you know which tables exist, tell me the total number of records across all tables and which tables have the most data.',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
title: 'Step 3: Show schema for main table',
|
|
154
|
+
request:
|
|
155
|
+
'For the table with the most records, show me the column structure (column names and data types) so I understand what data is stored there.',
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
console.info('🧪 Running AI Agent Tasks (Sequential Workflow)');
|
|
160
|
+
console.info('═'.repeat(60));
|
|
161
|
+
|
|
162
|
+
// Run each task through the agent sequentially
|
|
163
|
+
// Each task builds on previous results instead of making assumptions
|
|
164
|
+
for (const task of userRequests) {
|
|
165
|
+
console.info(`\n${task.title}`);
|
|
166
|
+
console.info('─'.repeat(60));
|
|
167
|
+
console.info(`👤 User: "${task.request}"\n`);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const response = await agent.invoke({
|
|
171
|
+
messages: [
|
|
172
|
+
{
|
|
173
|
+
role: 'user',
|
|
174
|
+
content: task.request,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Get the last message from the agent
|
|
180
|
+
const lastMessage = response.messages[response.messages.length - 1];
|
|
181
|
+
if (lastMessage) {
|
|
182
|
+
if (typeof lastMessage.content === 'string') {
|
|
183
|
+
console.info(`🤖 Agent: ${lastMessage.content}\n`);
|
|
184
|
+
} else {
|
|
185
|
+
console.info(`🤖 Agent:`, lastMessage.content, '\n');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
190
|
+
console.info(`⚠️ Agent error: ${errorMsg}\n`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.info('═'.repeat(60));
|
|
195
|
+
console.info('✨ AI Agent Examples Complete!\n');
|
|
196
|
+
console.info('Key Features:');
|
|
197
|
+
console.info(' ✅ Real LLM (OpenAI) decides which tools to use');
|
|
198
|
+
console.info(' ✅ Natural language requests, not SQL queries');
|
|
199
|
+
console.info(' ✅ LLM generates SQL based on context');
|
|
200
|
+
console.info(' ✅ Database credentials NEVER exposed to LLM');
|
|
201
|
+
console.info(' ✅ Matimo handles secure credential injection');
|
|
202
|
+
console.info(' ✅ Agentic reasoning and decision-making\n');
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('❌ Error:', error instanceof Error ? error.message : String(error));
|
|
205
|
+
if (error instanceof Error && error.stack) {
|
|
206
|
+
console.error('Stack:', error.stack);
|
|
207
|
+
}
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Run the AI agent
|
|
213
|
+
runPostgresAIAgent().catch(console.error);
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres Tool Example with Real Database and Approval Flow
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates:
|
|
5
|
+
* 1. Connecting to a real Postgres database
|
|
6
|
+
* 2. Executing non-destructive queries (SELECT) - no approval needed
|
|
7
|
+
* 3. Executing destructive queries - approval required and handled
|
|
8
|
+
* 4. Setting up a single generic approval callback
|
|
9
|
+
*
|
|
10
|
+
* Approval Flow (Generic for ALL tools):
|
|
11
|
+
* - Tool declares requires_approval in YAML OR contains destructive keywords
|
|
12
|
+
* - Check MATIMO_AUTO_APPROVE=true (approve all)
|
|
13
|
+
* - Check MATIMO_APPROVED_PATTERNS="pattern*" (pre-approved patterns)
|
|
14
|
+
* - Call single approval callback for user/policy approval
|
|
15
|
+
* - No per-tool, per-provider custom logic needed
|
|
16
|
+
*
|
|
17
|
+
* Setup Instructions:
|
|
18
|
+
* 1. Make sure you have a Postgres instance running locally or accessible
|
|
19
|
+
* 2. Set the connection string via environment variable:
|
|
20
|
+
* export MATIMO_POSTGRES_URL="postgresql://user:password@localhost:5432/dbname"
|
|
21
|
+
* OR set individual components:
|
|
22
|
+
* export MATIMO_POSTGRES_HOST="localhost"
|
|
23
|
+
* export MATIMO_POSTGRES_PORT="5432"
|
|
24
|
+
* export MATIMO_POSTGRES_USER="user"
|
|
25
|
+
* export MATIMO_POSTGRES_PASSWORD="password"
|
|
26
|
+
* export MATIMO_POSTGRES_DB="dbname"
|
|
27
|
+
*
|
|
28
|
+
* 3. Run the example:
|
|
29
|
+
* pnpm postgres:approval
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import 'dotenv/config';
|
|
33
|
+
import { MatimoInstance, getGlobalApprovalHandler, type ApprovalRequest } from '@matimo/core';
|
|
34
|
+
import * as readline from 'readline';
|
|
35
|
+
|
|
36
|
+
// Interactive approval callback - prompts user when MATIMO_AUTO_APPROVE is not set
|
|
37
|
+
function createApprovalCallback() {
|
|
38
|
+
return async (request: ApprovalRequest): Promise<boolean> => {
|
|
39
|
+
const isInteractive = process.stdin.isTTY;
|
|
40
|
+
|
|
41
|
+
console.info('\n' + '='.repeat(70));
|
|
42
|
+
console.info('🔒 APPROVAL REQUIRED FOR DESTRUCTIVE OPERATION');
|
|
43
|
+
console.info('='.repeat(70));
|
|
44
|
+
console.info(`\n📋 Tool: ${request.toolName}`);
|
|
45
|
+
console.info(`📝 Description: ${request.description || '(no description provided)'}`);
|
|
46
|
+
|
|
47
|
+
// Show SQL content if available
|
|
48
|
+
if (request.params && typeof request.params.sql === 'string') {
|
|
49
|
+
console.info(`\n📄 SQL to execute:`);
|
|
50
|
+
console.info(' ' + request.params.sql.split('\n').join('\n '));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!isInteractive) {
|
|
54
|
+
console.info('\n❌ REJECTED - Non-interactive environment (no terminal)');
|
|
55
|
+
console.info('\n💡 To enable auto-approval in CI/scripts:');
|
|
56
|
+
console.info(' export MATIMO_AUTO_APPROVE=true');
|
|
57
|
+
console.info('\n💡 Or approve specific patterns:');
|
|
58
|
+
console.info(' export MATIMO_APPROVED_PATTERNS="postgres-execute-sql"');
|
|
59
|
+
console.info('\n' + '='.repeat(70) + '\n');
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Interactive mode: prompt user
|
|
64
|
+
const rl = readline.createInterface({
|
|
65
|
+
input: process.stdin,
|
|
66
|
+
output: process.stdout,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
console.info('\n❓ User Action Required');
|
|
71
|
+
const question = ' Type "yes" to approve or "no" to reject: ';
|
|
72
|
+
|
|
73
|
+
rl.question(question, (answer) => {
|
|
74
|
+
const approved = answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y';
|
|
75
|
+
|
|
76
|
+
if (approved) {
|
|
77
|
+
console.info(' ✅ Operation APPROVED by user');
|
|
78
|
+
} else {
|
|
79
|
+
console.info(' ❌ Operation REJECTED by user');
|
|
80
|
+
}
|
|
81
|
+
console.info('='.repeat(70) + '\n');
|
|
82
|
+
|
|
83
|
+
rl.close();
|
|
84
|
+
resolve(approved);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function main() {
|
|
91
|
+
// Check if database connection is available
|
|
92
|
+
if (
|
|
93
|
+
!process.env.MATIMO_POSTGRES_URL &&
|
|
94
|
+
(!process.env.MATIMO_POSTGRES_HOST ||
|
|
95
|
+
!process.env.MATIMO_POSTGRES_USER ||
|
|
96
|
+
!process.env.MATIMO_POSTGRES_PASSWORD ||
|
|
97
|
+
!process.env.MATIMO_POSTGRES_DB)
|
|
98
|
+
) {
|
|
99
|
+
console.error('❌ Database connection not configured.');
|
|
100
|
+
console.error('Please set one of:');
|
|
101
|
+
console.error(' - MATIMO_POSTGRES_URL="postgresql://user:password@host:port/db"');
|
|
102
|
+
console.error(
|
|
103
|
+
' - MATIMO_POSTGRES_HOST, MATIMO_POSTGRES_PORT, MATIMO_POSTGRES_USER, MATIMO_POSTGRES_PASSWORD, MATIMO_POSTGRES_DB'
|
|
104
|
+
);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Initialize Matimo with postgres tools
|
|
109
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
110
|
+
|
|
111
|
+
console.info('\n' + '='.repeat(70));
|
|
112
|
+
console.info('🚀 Postgres Tool Example with Approval Flow');
|
|
113
|
+
console.info('='.repeat(70));
|
|
114
|
+
|
|
115
|
+
// Configure centralized approval handler
|
|
116
|
+
const approvalHandler = getGlobalApprovalHandler();
|
|
117
|
+
approvalHandler.setApprovalCallback(createApprovalCallback());
|
|
118
|
+
|
|
119
|
+
// Show current approval mode
|
|
120
|
+
const autoApproveEnabled = process.env.MATIMO_AUTO_APPROVE === 'true';
|
|
121
|
+
const approvedPatterns = process.env.MATIMO_APPROVED_PATTERNS;
|
|
122
|
+
|
|
123
|
+
console.info('\n🔐 APPROVAL CONFIGURATION:');
|
|
124
|
+
if (autoApproveEnabled) {
|
|
125
|
+
console.info(' ✅ MATIMO_AUTO_APPROVE=true');
|
|
126
|
+
console.info(' → All destructive operations will be AUTO-APPROVED');
|
|
127
|
+
} else if (approvedPatterns) {
|
|
128
|
+
console.info(` ✅ MATIMO_APPROVED_PATTERNS="${approvedPatterns}"`);
|
|
129
|
+
console.info(' → Matching operations will be auto-approved');
|
|
130
|
+
} else {
|
|
131
|
+
console.info(' ⚠️ INTERACTIVE MODE ENABLED');
|
|
132
|
+
console.info(' → You will be prompted to approve destructive operations');
|
|
133
|
+
console.info(' → Type "yes" or "no" when prompted');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// 1. List available tools
|
|
138
|
+
const tools = matimo.listTools();
|
|
139
|
+
const postgresTools = tools.filter((t) => t.name.startsWith('postgres'));
|
|
140
|
+
console.info(`\n📋 Available Postgres tools: ${postgresTools.map((t) => t.name).join(', ')}`);
|
|
141
|
+
|
|
142
|
+
// Sequential discovery workflow
|
|
143
|
+
|
|
144
|
+
// STEP 1: Discover tables (SELECT - no approval needed)
|
|
145
|
+
console.info('\n\n1️⃣ DISCOVER TABLES (Step 1/3 - No approval needed)');
|
|
146
|
+
console.info('-'.repeat(70));
|
|
147
|
+
|
|
148
|
+
let discoveredTables: string[] = [];
|
|
149
|
+
try {
|
|
150
|
+
console.info('Executing: SELECT table_name FROM information_schema.tables...');
|
|
151
|
+
console.info('(This is a SELECT - no approval will be requested)\n');
|
|
152
|
+
const tablesResult = await matimo.execute('postgres-execute-sql', {
|
|
153
|
+
sql: `
|
|
154
|
+
SELECT table_name
|
|
155
|
+
FROM information_schema.tables
|
|
156
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
|
157
|
+
ORDER BY table_name;
|
|
158
|
+
`,
|
|
159
|
+
});
|
|
160
|
+
if (
|
|
161
|
+
tablesResult &&
|
|
162
|
+
typeof tablesResult === 'object' &&
|
|
163
|
+
'success' in tablesResult &&
|
|
164
|
+
(tablesResult as any).success === false
|
|
165
|
+
) {
|
|
166
|
+
throw new Error((tablesResult as any).error || 'Query failed');
|
|
167
|
+
}
|
|
168
|
+
console.info('✅ Query executed successfully (no approval required for SELECT)');
|
|
169
|
+
if (tablesResult && typeof tablesResult === 'object' && 'rows' in tablesResult) {
|
|
170
|
+
const rows = (tablesResult as any).rows;
|
|
171
|
+
if (rows && rows.length > 0) {
|
|
172
|
+
console.info('Tables found:');
|
|
173
|
+
rows.forEach((row: any) => {
|
|
174
|
+
console.info(` - ${row.table_name}`);
|
|
175
|
+
discoveredTables.push(row.table_name);
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
console.info('(No tables found in public schema)');
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
console.info('(No tables found in public schema)');
|
|
182
|
+
}
|
|
183
|
+
} catch (err: any) {
|
|
184
|
+
console.error('❌ Error:', err.message);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// STEP 2: Get row counts (SELECT - no approval needed)
|
|
188
|
+
console.info('\n\n2️⃣ COUNT RECORDS (Step 2/3 - No approval needed)');
|
|
189
|
+
console.info('-'.repeat(60));
|
|
190
|
+
console.info('SQL: SELECT tablename, n_live_tup FROM pg_stat_user_tables\n');
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const countsResult = await matimo.execute('postgres-execute-sql', {
|
|
194
|
+
sql: `
|
|
195
|
+
SELECT
|
|
196
|
+
table_name,
|
|
197
|
+
(SELECT count(*) FROM information_schema.columns
|
|
198
|
+
WHERE information_schema.columns.table_name = t.table_name) as columns
|
|
199
|
+
FROM information_schema.tables t
|
|
200
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
|
201
|
+
ORDER BY table_name;
|
|
202
|
+
`,
|
|
203
|
+
});
|
|
204
|
+
if (
|
|
205
|
+
countsResult &&
|
|
206
|
+
typeof countsResult === 'object' &&
|
|
207
|
+
'success' in countsResult &&
|
|
208
|
+
(countsResult as any).success === false
|
|
209
|
+
) {
|
|
210
|
+
throw new Error((countsResult as any).error || 'Query failed');
|
|
211
|
+
}
|
|
212
|
+
console.info('✅ Query executed successfully (no approval needed for SELECT)');
|
|
213
|
+
if (countsResult && typeof countsResult === 'object' && 'rows' in countsResult) {
|
|
214
|
+
const rows = (countsResult as any).rows;
|
|
215
|
+
if (rows && rows.length > 0) {
|
|
216
|
+
console.info('Tables found:');
|
|
217
|
+
rows.forEach((row: any) => {
|
|
218
|
+
console.info(` - ${row.table_name} (${row.columns} columns)`);
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
console.info('(No tables found in public schema)');
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
console.info('(No tables found in public schema)');
|
|
225
|
+
}
|
|
226
|
+
} catch (err: any) {
|
|
227
|
+
console.error('❌ Error:', err.message);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// STEP 3: Try a destructive operation that requires approval
|
|
231
|
+
console.info('\n\n3️⃣ DESTRUCTIVE OPERATION (Step 3/3 - Requires approval)');
|
|
232
|
+
console.info('-'.repeat(70));
|
|
233
|
+
console.info('Attempting DELETE operation - this WILL trigger approval...\n');
|
|
234
|
+
|
|
235
|
+
if (discoveredTables.length > 0) {
|
|
236
|
+
const tableName = discoveredTables[0];
|
|
237
|
+
const deleteSql = `DELETE FROM ${tableName} WHERE 1=0;`;
|
|
238
|
+
console.info(`📄 SQL Query: ${deleteSql}`);
|
|
239
|
+
console.info('⚠️ NOTE: This is a DESTRUCTIVE operation (DELETE keyword detected)');
|
|
240
|
+
console.info('⚠️ NOTE: WHERE 1=0 matches no rows, so it is SAFE\n');
|
|
241
|
+
|
|
242
|
+
if (autoApproveEnabled) {
|
|
243
|
+
console.info('ℹ️ MATIMO_AUTO_APPROVE=true → Will auto-approve this operation\n');
|
|
244
|
+
} else if (approvedPatterns) {
|
|
245
|
+
console.info(`ℹ️ MATIMO_APPROVED_PATTERNS set → Checking pattern matching...\n`);
|
|
246
|
+
} else {
|
|
247
|
+
console.info('ℹ️ INTERACTIVE MODE → You will be prompted to approve/reject\n');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
// Safe DELETE that matches no rows (WHERE 1=0)
|
|
252
|
+
const deleteResult = await matimo.execute('postgres-execute-sql', {
|
|
253
|
+
sql: deleteSql,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Check if operation was rejected by user approval
|
|
257
|
+
if (
|
|
258
|
+
deleteResult &&
|
|
259
|
+
typeof deleteResult === 'object' &&
|
|
260
|
+
'approved' in deleteResult &&
|
|
261
|
+
(deleteResult as any).approved === false
|
|
262
|
+
) {
|
|
263
|
+
console.info('\n⚠️ DELETE was REJECTED by user approval');
|
|
264
|
+
console.info(` Reason: ${(deleteResult as any).reason}`);
|
|
265
|
+
console.info(' The operation was cancelled due to approval denial\n');
|
|
266
|
+
} else {
|
|
267
|
+
console.info('\n✅ DELETE APPROVED AND EXECUTED');
|
|
268
|
+
console.info(' Operation was approved (or auto-approved)');
|
|
269
|
+
console.info(' (0 rows deleted - safe WHERE clause)\n');
|
|
270
|
+
}
|
|
271
|
+
} catch (err: any) {
|
|
272
|
+
const errorMsg = err?.message || String(err);
|
|
273
|
+
|
|
274
|
+
if (
|
|
275
|
+
errorMsg.includes('Operation rejected') ||
|
|
276
|
+
errorMsg.includes('not approved') ||
|
|
277
|
+
errorMsg.includes('User or policy rejected')
|
|
278
|
+
) {
|
|
279
|
+
console.info('\n⚠️ DELETE was REJECTED by user');
|
|
280
|
+
console.info(` Reason: ${errorMsg}`);
|
|
281
|
+
console.info(' You chose to reject this destructive operation\n');
|
|
282
|
+
} else if (errorMsg.includes('approval') && errorMsg.includes('required')) {
|
|
283
|
+
console.info('\n⚠️ DELETE REQUIRES APPROVAL');
|
|
284
|
+
console.info(` Error: ${errorMsg}`);
|
|
285
|
+
console.info(' Set MATIMO_AUTO_APPROVE=true to auto-approve, or run interactively\n');
|
|
286
|
+
} else {
|
|
287
|
+
console.error(`\n❌ Unexpected error: ${errorMsg}\n`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
console.info('\n⚠️ No tables found to demonstrate destructive operation');
|
|
292
|
+
console.info(' (Would require approval when attempting DELETE/UPDATE/CREATE/DROP/ALTER)\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// STEP 4: Show approval configuration and summary
|
|
296
|
+
console.info('\n\n4️⃣ APPROVAL SYSTEM SUMMARY');
|
|
297
|
+
console.info('='.repeat(70));
|
|
298
|
+
|
|
299
|
+
console.info('\n📋 Current Settings:');
|
|
300
|
+
if (autoApproveEnabled) {
|
|
301
|
+
console.info(' ✅ MATIMO_AUTO_APPROVE=true → All destructive ops AUTO-APPROVED');
|
|
302
|
+
} else if (approvedPatterns) {
|
|
303
|
+
console.info(` ✅ MATIMO_APPROVED_PATTERNS="${approvedPatterns}"`);
|
|
304
|
+
console.info(' → Matching patterns AUTO-APPROVED, others require user input');
|
|
305
|
+
} else {
|
|
306
|
+
console.info(' ⚠️ Interactive Mode (RECOMMENDED FOR HUMANS)');
|
|
307
|
+
console.info(' → All destructive operations require your "yes/no" input');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.info('\n💡 How to Use:');
|
|
311
|
+
console.info(' 1. Interactive (default): pnpm postgres:approval');
|
|
312
|
+
console.info(
|
|
313
|
+
' 2. Auto-approve in CI: MATIMO_AUTO_APPROVE=true pnpm postgres:approval'
|
|
314
|
+
);
|
|
315
|
+
console.info(
|
|
316
|
+
' 3. Pre-approved patterns: MATIMO_APPROVED_PATTERNS="postgres*" pnpm postgres:approval'
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
console.info('\n🔐 Supported Approval Modes:');
|
|
320
|
+
console.info(' • MATIMO_AUTO_APPROVE=true → Approve all destructive operations');
|
|
321
|
+
console.info(
|
|
322
|
+
' • MATIMO_APPROVED_PATTERNS → Approve only matching tool names (glob pattern)'
|
|
323
|
+
);
|
|
324
|
+
console.info(' • Interactive (no env vars) → Prompt user for each operation');
|
|
325
|
+
|
|
326
|
+
console.info('\n✨ Workflow Complete! You tested:');
|
|
327
|
+
console.info(' 1. ✅ SELECT (non-destructive) → No approval needed');
|
|
328
|
+
console.info(' 2. ✅ SELECT (non-destructive) → No approval needed');
|
|
329
|
+
console.info(' 3. 🔒 DELETE (destructive) → Approval required/tested');
|
|
330
|
+
|
|
331
|
+
console.info('\n' + '='.repeat(70) + '\n');
|
|
332
|
+
} catch (err: any) {
|
|
333
|
+
console.error('\n❌ Error:', err.message);
|
|
334
|
+
if (err.code === 'ECONNREFUSED') {
|
|
335
|
+
console.error('\nDatabase connection refused. Make sure your Postgres instance is running.');
|
|
336
|
+
}
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
main().catch((err) => {
|
|
342
|
+
console.error('Fatal error:', err);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
});
|