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.
@@ -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,250 @@
1
+ /**
2
+ * Postgres Tool Example with Real Database and SQL Approval Flow
3
+ *
4
+ * This example demonstrates:
5
+ * 1. Connecting to a real Postgres database
6
+ * 2. Executing non-destructive queries (SELECT)
7
+ * 3. Executing destructive queries with approval flow
8
+ * 4. Setting up approval callbacks
9
+ *
10
+ * Setup Instructions:
11
+ * 1. Make sure you have a Postgres instance running locally or accessible
12
+ * 2. Set the connection string via environment variable:
13
+ * export MATIMO_POSTGRES_URL="postgresql://user:password@localhost:5432/dbname"
14
+ * OR set individual components:
15
+ * export MATIMO_POSTGRES_HOST="localhost"
16
+ * export MATIMO_POSTGRES_PORT="5432"
17
+ * export MATIMO_POSTGRES_USER="user"
18
+ * export MATIMO_POSTGRES_PASSWORD="password"
19
+ * export MATIMO_POSTGRES_DB="dbname"
20
+ *
21
+ * 3. Run the example:
22
+ * pnpm tsx examples/tools/postgres/postgres-with-approval.ts
23
+ */
24
+
25
+ import 'dotenv/config';
26
+ import { MatimoInstance, getSQLApprovalManager, setSQLApprovalManager } from '@matimo/core';
27
+ import * as path from 'path';
28
+ import * as readline from 'readline';
29
+
30
+ // Interactive approval callback with better input handling
31
+ function createInteractiveApprovalCallback() {
32
+ return async (sql: string, mode: string): Promise<boolean> => {
33
+ // Check if stdin is a TTY (interactive terminal)
34
+ const isInteractive = process.stdin.isTTY;
35
+
36
+ console.info(`\n⚠️ Approval Required for ${mode.toUpperCase()} operation:`);
37
+ console.info(`SQL: ${sql}`);
38
+ console.info('');
39
+
40
+ if (!isInteractive) {
41
+ console.info('❌ REJECTED - Non-interactive mode (no terminal input available)');
42
+ console.info('');
43
+ console.info('To run this example interactively:');
44
+ console.info(' 1. Run directly: pnpm postgres:approval');
45
+ console.info(' 2. Or set MATIMO_SQL_AUTO_APPROVE=true for automated approval');
46
+ console.info('');
47
+ return false;
48
+ }
49
+
50
+ const rl = readline.createInterface({
51
+ input: process.stdin,
52
+ output: process.stdout,
53
+ terminal: true,
54
+ });
55
+
56
+ return new Promise((resolve) => {
57
+ rl.question('Do you approve? (yes/no): ', (answer) => {
58
+ const approved = answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y';
59
+ console.info(`Result: ${approved ? '✅ APPROVED' : '❌ REJECTED'}\n`);
60
+ rl.close();
61
+ resolve(approved);
62
+ });
63
+ });
64
+ };
65
+ }
66
+
67
+ async function main() {
68
+ // Check if database connection is available
69
+ if (
70
+ !process.env.MATIMO_POSTGRES_URL &&
71
+ (!process.env.MATIMO_POSTGRES_HOST ||
72
+ !process.env.MATIMO_POSTGRES_USER ||
73
+ !process.env.MATIMO_POSTGRES_PASSWORD ||
74
+ !process.env.MATIMO_POSTGRES_DB)
75
+ ) {
76
+ console.error('❌ Database connection not configured.');
77
+ console.error('Please set one of:');
78
+ console.error(' - MATIMO_POSTGRES_URL="postgresql://user:password@host:port/db"');
79
+ console.error(
80
+ ' - MATIMO_POSTGRES_HOST, MATIMO_POSTGRES_PORT, MATIMO_POSTGRES_USER, MATIMO_POSTGRES_PASSWORD, MATIMO_POSTGRES_DB'
81
+ );
82
+ process.exit(1);
83
+ }
84
+
85
+ // Initialize Matimo with postgres tools
86
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
87
+
88
+ console.info('🚀 Postgres Tool Example with Approval Flow');
89
+ console.info('='.repeat(60));
90
+
91
+ // Configure SQL approval manager
92
+ const approvalManager = getSQLApprovalManager();
93
+ approvalManager.setApprovalCallback(createInteractiveApprovalCallback());
94
+
95
+ try {
96
+ // 1. List available tools
97
+ const tools = matimo.listTools();
98
+ const postgresTools = tools.filter((t) => t.name.startsWith('postgres'));
99
+ console.info(`\n📋 Available Postgres tools: ${postgresTools.map((t) => t.name).join(', ')}`);
100
+
101
+ // Sequential discovery workflow
102
+
103
+ // STEP 1: Discover tables (SELECT - no approval needed)
104
+ console.info('\n\n1️⃣ DISCOVER TABLES (Step 1/3 - No approval needed)');
105
+ console.info('-'.repeat(60));
106
+ console.info('SQL: SELECT table_name FROM information_schema.tables WHERE table_schema = $1\n');
107
+
108
+ let discoveredTables: string[] = [];
109
+ try {
110
+ const tablesResult = await matimo.execute('postgres-execute-sql', {
111
+ sql: `
112
+ SELECT table_name
113
+ FROM information_schema.tables
114
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
115
+ ORDER BY table_name;
116
+ `,
117
+ });
118
+ if (
119
+ tablesResult &&
120
+ typeof tablesResult === 'object' &&
121
+ 'success' in tablesResult &&
122
+ (tablesResult as any).success === false
123
+ ) {
124
+ throw new Error((tablesResult as any).error || 'Query failed');
125
+ }
126
+ console.info('✅ Query executed successfully (no approval needed for SELECT)');
127
+ if (tablesResult && typeof tablesResult === 'object' && 'rows' in tablesResult) {
128
+ const rows = (tablesResult as any).rows;
129
+ if (rows && rows.length > 0) {
130
+ console.info('Tables found:');
131
+ rows.forEach((row: any) => {
132
+ console.info(` - ${row.table_name}`);
133
+ discoveredTables.push(row.table_name);
134
+ });
135
+ } else {
136
+ console.info('(No tables found in public schema)');
137
+ }
138
+ } else {
139
+ console.info('(No tables found in public schema)');
140
+ }
141
+ } catch (err: any) {
142
+ console.error('❌ Error:', err.message);
143
+ }
144
+
145
+ // STEP 2: Get row counts (SELECT - no approval needed)
146
+ console.info('\n\n2️⃣ COUNT RECORDS (Step 2/3 - No approval needed)');
147
+ console.info('-'.repeat(60));
148
+ console.info('SQL: SELECT tablename, n_live_tup FROM pg_stat_user_tables\n');
149
+
150
+ try {
151
+ const countsResult = await matimo.execute('postgres-execute-sql', {
152
+ sql: `
153
+ SELECT
154
+ table_name,
155
+ (SELECT count(*) FROM information_schema.columns
156
+ WHERE information_schema.columns.table_name = t.table_name) as columns
157
+ FROM information_schema.tables t
158
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
159
+ ORDER BY table_name;
160
+ `,
161
+ });
162
+ if (
163
+ countsResult &&
164
+ typeof countsResult === 'object' &&
165
+ 'success' in countsResult &&
166
+ (countsResult as any).success === false
167
+ ) {
168
+ throw new Error((countsResult as any).error || 'Query failed');
169
+ }
170
+ console.info('✅ Query executed successfully (no approval needed for SELECT)');
171
+ if (countsResult && typeof countsResult === 'object' && 'rows' in countsResult) {
172
+ const rows = (countsResult as any).rows;
173
+ if (rows && rows.length > 0) {
174
+ console.info('Tables found:');
175
+ rows.forEach((row: any) => {
176
+ console.info(` - ${row.table_name} (${row.columns} columns)`);
177
+ });
178
+ } else {
179
+ console.info('(No tables found in public schema)');
180
+ }
181
+ } else {
182
+ console.info('(No tables found in public schema)');
183
+ }
184
+ } catch (err: any) {
185
+ console.error('❌ Error:', err.message);
186
+ }
187
+
188
+ // STEP 3: Try a destructive operation that requires approval
189
+ console.info('\n\n3️⃣ DESTRUCTIVE OPERATION (Step 3/3 - Requires approval)');
190
+ console.info('-'.repeat(60));
191
+ console.info('Attempting DELETE on first discovered table (if any)...\n');
192
+
193
+ if (discoveredTables.length > 0) {
194
+ const tableName = discoveredTables[0];
195
+ console.info(`SQL: DELETE FROM ${tableName} WHERE 1=0 (safe - matches no rows)\n`);
196
+
197
+ try {
198
+ // Safe DELETE that matches no rows (WHERE 1=0)
199
+ const deleteResult = await matimo.execute('postgres-execute-sql', {
200
+ sql: `DELETE FROM ${tableName} WHERE 1=0;`,
201
+ });
202
+ console.info('✅ DELETE approved and executed successfully');
203
+ console.info(' (0 rows deleted - safe WHERE clause)\n');
204
+ } catch (err: any) {
205
+ const errorMsg = err?.message || String(err);
206
+ if (
207
+ errorMsg.includes('not approved') ||
208
+ errorMsg.includes('approval') ||
209
+ errorMsg.includes('callback')
210
+ ) {
211
+ console.info('⚠️ DELETE was REJECTED by user');
212
+ console.info(
213
+ ' Approval is required for destructive operations (CREATE/DROP/ALTER/DELETE/UPDATE)\n'
214
+ );
215
+ } else {
216
+ console.error('❌ Error:', errorMsg);
217
+ }
218
+ }
219
+ } else {
220
+ console.info('⚠️ No tables found to demonstrate destructive operation');
221
+ console.info(' (Would require approval when attempting DELETE/UPDATE/CREATE/DROP/ALTER)\n');
222
+ }
223
+
224
+ // STEP 4: Show approval configuration
225
+ console.info('\n4️⃣ APPROVAL CONFIGURATION');
226
+ console.info('-'.repeat(60));
227
+ console.info('Current approval settings:');
228
+ console.info(
229
+ ` - MATIMO_SQL_AUTO_APPROVE=${process.env.MATIMO_SQL_AUTO_APPROVE || '(not set)'}`
230
+ );
231
+ console.info(` - Interactive approval callback: ${approvalManager ? 'Enabled' : 'Disabled'}`);
232
+ console.info('\nFor CI/automated environments, set:');
233
+ console.info(' export MATIMO_SQL_AUTO_APPROVE=true\n');
234
+
235
+ console.info('✨ Sequential discovery with approval workflow complete!');
236
+ console.info('='.repeat(60));
237
+ console.info('Pattern: 1) Discover tables 2) Count records 3) Attempt destructive op\n');
238
+ } catch (err: any) {
239
+ console.error('\n❌ Error:', err.message);
240
+ if (err.code === 'ECONNREFUSED') {
241
+ console.error('\nDatabase connection refused. Make sure your Postgres instance is running.');
242
+ }
243
+ process.exit(1);
244
+ }
245
+ }
246
+
247
+ main().catch((err) => {
248
+ console.error('Fatal error:', err);
249
+ process.exit(1);
250
+ });
@@ -0,0 +1,107 @@
1
+ import {
2
+ MatimoInstance,
3
+ setGlobalMatimoInstance,
4
+ tool,
5
+ getPathApprovalManager,
6
+ } from '@matimo/core';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import readline from 'readline';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ // Create readline interface for interactive approval prompts
14
+ const rl = readline.createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout,
17
+ });
18
+
19
+ let isReadlineClosed = false;
20
+
21
+ // Track when readline closes (e.g., piped input ends)
22
+ rl.on('close', () => {
23
+ isReadlineClosed = true;
24
+ });
25
+
26
+ /**
27
+ * Prompt user for approval decision
28
+ */
29
+ async function promptForApproval(
30
+ filePath: string,
31
+ mode: 'read' | 'write' | 'search'
32
+ ): Promise<boolean> {
33
+ return new Promise((resolve) => {
34
+ // If readline is closed (e.g., non-TTY/piped input), auto-approve
35
+ if (isReadlineClosed) {
36
+ console.info(
37
+ `[${mode.toUpperCase()}] Access to ${filePath} auto-approved (non-interactive mode)`
38
+ );
39
+ resolve(true);
40
+ return;
41
+ }
42
+ rl.question(`[${mode.toUpperCase()}] Approve access to ${filePath}? (y/n): `, (answer) => {
43
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
44
+ });
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Example: Read tool using @tool decorator pattern
50
+ * Demonstrates class-based file reading with automatic decoration
51
+ */
52
+ class FileReader {
53
+ @tool('read')
54
+ async readFile(filePath: string, startLine: number, endLine: number): Promise<unknown> {
55
+ // Decorator automatically intercepts and executes via Matimo
56
+ return undefined;
57
+ }
58
+
59
+ @tool('read')
60
+ async getFileMetadata(filePath: string): Promise<unknown> {
61
+ // Decorator automatically intercepts and executes via Matimo
62
+ return undefined;
63
+ }
64
+ }
65
+
66
+ async function decoratorExample() {
67
+ // Set up decorator support with autoDiscover
68
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
69
+ setGlobalMatimoInstance(matimo);
70
+
71
+ // Set up approval callback for interactive approval
72
+ const approvalManager = getPathApprovalManager();
73
+ approvalManager.setApprovalCallback(promptForApproval);
74
+
75
+ console.info('=== Read Tool - Decorator Pattern (Interactive Approval) ===\n');
76
+
77
+ const reader = new FileReader();
78
+
79
+ try {
80
+ // Example 1: Read file through decorated method
81
+ console.info('1. Reading file: read-factory.ts (lines 1-15)\n');
82
+ const result1 = await reader.readFile(path.join(__dirname, './read-factory.ts'), 1, 15);
83
+ console.info('File:', (result1 as any).filePath);
84
+ console.info('Lines read:', (result1 as any).readLines);
85
+ console.info('Content preview:');
86
+ console.info((result1 as any).content?.substring(0, 200));
87
+ console.info('---\n');
88
+
89
+ // Example 2: Read another file
90
+ console.info('2. Reading file: package.json (lines 1-10)\n');
91
+ const result2 = await reader.getFileMetadata(path.join(__dirname, '../../../package.json'));
92
+ console.info('File:', (result2 as any).filePath);
93
+ console.info('Lines read:', (result2 as any).readLines);
94
+ console.info('Content preview:');
95
+ console.info((result2 as any).content?.substring(0, 200));
96
+ console.info('---\n');
97
+ } catch (error: any) {
98
+ console.error('Error:', error.message);
99
+ } finally {
100
+ if (!isReadlineClosed) {
101
+ rl.close();
102
+ isReadlineClosed = true;
103
+ }
104
+ }
105
+ }
106
+
107
+ decoratorExample();
@@ -0,0 +1,104 @@
1
+ import { MatimoInstance } from '@matimo/core';
2
+ import { getPathApprovalManager } from '@matimo/core';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import readline from 'readline';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ // Reusable readline interface kept open for multiple prompts
10
+ const rl = readline.createInterface({
11
+ input: process.stdin,
12
+ output: process.stdout,
13
+ });
14
+
15
+ let isReadlineClosed = false;
16
+
17
+ // Track when readline closes (e.g., piped input ends)
18
+ rl.on('close', () => {
19
+ isReadlineClosed = true;
20
+ });
21
+
22
+ /**
23
+ * Interactive prompt helper for approval decisions
24
+ */
25
+ async function promptForApproval(
26
+ filePath: string,
27
+ mode: 'read' | 'write' | 'search'
28
+ ): Promise<boolean> {
29
+ return new Promise((resolve) => {
30
+ // If readline is closed (e.g., non-TTY/piped input), auto-approve
31
+ if (isReadlineClosed) {
32
+ console.info(
33
+ `[${mode.toUpperCase()}] Access to ${filePath} auto-approved (non-interactive mode)`
34
+ );
35
+ resolve(true);
36
+ return;
37
+ }
38
+ rl.question(`[${mode.toUpperCase()}] Approve access to ${filePath}? (y/n): `, (answer) => {
39
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
40
+ });
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Example: Read tool using factory pattern
46
+ * Demonstrates reading file contents and metadata with interactive approval
47
+ */
48
+ async function readExample() {
49
+ // Initialize Matimo with autoDiscover to find all tools (core + providers)
50
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
51
+
52
+ // Set up approval callback for examples with INTERACTIVE PROMPTS
53
+ const approvalManager = getPathApprovalManager();
54
+ approvalManager.setApprovalCallback(promptForApproval);
55
+
56
+ console.info('=== Read Tool - Factory Pattern (Interactive Approval) ===\n');
57
+
58
+ try {
59
+ // Example 1: Read this example file
60
+ console.info('1. Reading read-factory.ts\n');
61
+ const result1 = await matimo.execute('read', {
62
+ filePath: path.join(__dirname, './read-factory.ts'),
63
+ startLine: 1,
64
+ endLine: 20,
65
+ });
66
+
67
+ if ((result1 as any).success) {
68
+ console.info('File:', (result1 as any).filePath);
69
+ console.info('Lines read:', (result1 as any).readLines);
70
+ console.info('Content preview:');
71
+ console.info((result1 as any).content?.substring(0, 300));
72
+ } else {
73
+ console.info('Access denied:', (result1 as any).error);
74
+ }
75
+ console.info('---\n');
76
+
77
+ // Example 2: Read package.json
78
+ console.info('2. Reading package.json\n');
79
+ const result2 = await matimo.execute('read', {
80
+ filePath: path.join(__dirname, '../../../package.json'),
81
+ startLine: 1,
82
+ endLine: 15,
83
+ });
84
+
85
+ if ((result2 as any).success) {
86
+ console.info('File:', (result2 as any).filePath);
87
+ console.info('Lines read:', (result2 as any).readLines);
88
+ console.info('Content preview:');
89
+ console.info((result2 as any).content?.substring(0, 200));
90
+ } else {
91
+ console.info('Access denied:', (result2 as any).error);
92
+ }
93
+ console.info('---\n');
94
+ } catch (error: any) {
95
+ console.error('Error reading file:', error.message, error.code, error.details);
96
+ } finally {
97
+ if (!isReadlineClosed) {
98
+ rl.close();
99
+ isReadlineClosed = true;
100
+ }
101
+ }
102
+ }
103
+
104
+ readExample();