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,253 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ============================================================================
4
+ * READ TOOL - 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 when to read files
12
+ * 3. Generates appropriate file paths and line ranges based on context
13
+ * 4. Executes tools autonomously
14
+ * 5. Processes results and responds naturally
15
+ *
16
+ * SETUP:
17
+ * ─────────────────────────────────────────────────────────────────────────
18
+ * 1. Create .env file in examples/tools/:
19
+ * OPENAI_API_KEY=sk-xxxxxxxxxxxxx
20
+ *
21
+ * 2. Install dependencies:
22
+ * cd examples/tools && npm install
23
+ *
24
+ * USAGE:
25
+ * ─────────────────────────────────────────────────────────────────────────
26
+ * # From root directory:
27
+ * pnpm read:langchain
28
+ *
29
+ * # Or from examples/tools directory:
30
+ * npm run read:langchain
31
+ *
32
+ * ============================================================================
33
+ */
34
+
35
+ import 'dotenv/config';
36
+ import fs from 'fs';
37
+ import path from 'path';
38
+ import { fileURLToPath } from 'url';
39
+ import readline from 'readline';
40
+ import { createAgent } from 'langchain';
41
+ import { ChatOpenAI } from '@langchain/openai';
42
+ import {
43
+ MatimoInstance,
44
+ convertToolsToLangChain,
45
+ type ToolDefinition,
46
+ getPathApprovalManager,
47
+ } from '@matimo/core';
48
+
49
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
50
+
51
+ // Create readline interface for interactive approval prompts
52
+ const rl = readline.createInterface({
53
+ input: process.stdin,
54
+ output: process.stdout,
55
+ });
56
+
57
+ let isReadlineClosed = false;
58
+
59
+ // Track when readline closes (e.g., piped input ends)
60
+ rl.on('close', () => {
61
+ isReadlineClosed = true;
62
+ });
63
+
64
+ /**
65
+ * Prompt user for approval decision
66
+ */
67
+ async function promptForApproval(
68
+ filePath: string,
69
+ mode: 'read' | 'write' | 'search'
70
+ ): Promise<boolean> {
71
+ return new Promise((resolve) => {
72
+ // If readline is closed (e.g., non-TTY/piped input), auto-approve
73
+ if (isReadlineClosed) {
74
+ console.info(
75
+ `[${mode.toUpperCase()}] Access to ${filePath} auto-approved (non-interactive mode)`
76
+ );
77
+ resolve(true);
78
+ return;
79
+ }
80
+ rl.question(`[${mode.toUpperCase()}] Approve access to ${filePath}? (y/n): `, (answer) => {
81
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
82
+ });
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Run AI Agent with Read tool
88
+ * The agent receives natural language requests and decides which files to read
89
+ */
90
+ async function runReadAIAgent() {
91
+ console.info('\n╔════════════════════════════════════════════════════════╗');
92
+ console.info('║ Read Tool AI Agent - LangChain + OpenAI ║');
93
+ console.info('║ True autonomous agent with LLM reasoning ║');
94
+ console.info('╚════════════════════════════════════════════════════════╝\n');
95
+
96
+ // Check required environment variables
97
+ const openaiKey = process.env.OPENAI_API_KEY;
98
+ if (!openaiKey) {
99
+ console.error('❌ Error: OPENAI_API_KEY not set in .env');
100
+ console.info(' Set it: export OPENAI_API_KEY="sk-..."');
101
+ process.exit(1);
102
+ }
103
+
104
+ console.info('🤖 Using OpenAI (GPT-4o-mini) as the AI agent\n');
105
+
106
+ // Create a sample file for the agent to read
107
+ const sampleFile = path.join(__dirname, 'sample-code.ts');
108
+ fs.writeFileSync(
109
+ sampleFile,
110
+ `// Sample TypeScript file
111
+ interface User {
112
+ id: string;
113
+ name: string;
114
+ email: string;
115
+ }
116
+
117
+ function getUserInfo(userId: string): User {
118
+ // Fetch user from database
119
+ return {
120
+ id: userId,
121
+ name: 'John Doe',
122
+ email: 'john@example.com',
123
+ };
124
+ }
125
+
126
+ function validateEmail(email: string): boolean {
127
+ const pattern = /^[^@]+@[^@]+\\.[^@]+$/;
128
+ return pattern.test(email);
129
+ }
130
+
131
+ export { getUserInfo, validateEmail };
132
+ `
133
+ );
134
+
135
+ try {
136
+ // Initialize Matimo with auto-discovery
137
+ console.info('🚀 Initializing Matimo...');
138
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
139
+
140
+ // Set up approval callback for interactive approval
141
+ const approvalManager = getPathApprovalManager();
142
+ approvalManager.setApprovalCallback(promptForApproval);
143
+
144
+ // Get read tool
145
+ console.info('💬 Loading read tool...');
146
+ const matimoTools = matimo.listTools();
147
+ const readTools = matimoTools.filter((t) => t.name === 'read') as ToolDefinition[];
148
+ console.info(`✅ Loaded ${readTools.length} read tool(s)\n`);
149
+
150
+ if (readTools.length === 0) {
151
+ console.error('❌ Read tool not found');
152
+ process.exit(1);
153
+ }
154
+
155
+ // Convert to LangChain tools using the built-in converter
156
+ const langchainTools = await convertToolsToLangChain(readTools, matimo);
157
+
158
+ // Initialize OpenAI LLM
159
+ console.info('🤖 Initializing OpenAI (GPT-4o-mini) LLM...');
160
+ const model = new ChatOpenAI({
161
+ modelName: 'gpt-4o-mini',
162
+ temperature: 0.7,
163
+ });
164
+
165
+ // Create agent
166
+ console.info('🔧 Creating agent...\n');
167
+ const agent = await createAgent({
168
+ model,
169
+ tools: langchainTools as any,
170
+ });
171
+
172
+ // Define agent tasks (natural language requests)
173
+ const userRequests = [
174
+ {
175
+ title: 'Example 1: Read function definition',
176
+ request: `Read the getUserInfo function from file ${sampleFile}`,
177
+ },
178
+ {
179
+ title: 'Example 2: Read specific lines',
180
+ request: `Read lines 1-10 from file ${sampleFile} and tell me what interfaces are defined`,
181
+ },
182
+ {
183
+ title: 'Example 3: Read entire file',
184
+ request: `Show me the complete content of file ${sampleFile}`,
185
+ },
186
+ ];
187
+
188
+ console.info('🧪 Running AI Agent Tasks');
189
+ console.info('═'.repeat(60) + '\n');
190
+
191
+ // Run each task through the agent
192
+ for (const task of userRequests) {
193
+ console.info(`${task.title}`);
194
+ console.info('─'.repeat(60));
195
+ console.info(`👤 User: "${task.request}"\n`);
196
+
197
+ try {
198
+ const response = await agent.invoke({
199
+ messages: [
200
+ {
201
+ role: 'user',
202
+ content: task.request,
203
+ },
204
+ ],
205
+ });
206
+
207
+ // Get the last message from the agent
208
+ const lastMessage = response.messages[response.messages.length - 1];
209
+ if (lastMessage) {
210
+ const content =
211
+ typeof lastMessage.content === 'string'
212
+ ? lastMessage.content
213
+ : String(lastMessage.content);
214
+
215
+ if (content && content.trim()) {
216
+ console.info(`🤖 Agent: ${content}\n`);
217
+ } else {
218
+ console.info('🤖 Agent: (File read successfully)\n');
219
+ }
220
+ }
221
+ } catch (error) {
222
+ const errorMsg = error instanceof Error ? error.message : String(error);
223
+ console.info(`⚠️ Agent error: ${errorMsg}\n`);
224
+ }
225
+ }
226
+
227
+ console.info('═'.repeat(60));
228
+ console.info('\n✨ AI Agent Examples Complete!\n');
229
+ console.info('Key Features:');
230
+ console.info(' ✅ Real LLM (OpenAI) decides which tools to use');
231
+ console.info(' ✅ Natural language requests, not API calls');
232
+ console.info(' ✅ LLM determines file paths and line ranges');
233
+ console.info(' ✅ Agentic reasoning and decision-making\n');
234
+ } catch (error) {
235
+ console.error('❌ Error:', error instanceof Error ? error.message : String(error));
236
+ if (error instanceof Error && error.stack) {
237
+ console.error('Stack:', error.stack);
238
+ }
239
+ process.exit(1);
240
+ } finally {
241
+ // Clean up
242
+ if (fs.existsSync(sampleFile)) {
243
+ fs.unlinkSync(sampleFile);
244
+ }
245
+ if (!isReadlineClosed) {
246
+ rl.close();
247
+ isReadlineClosed = true;
248
+ }
249
+ }
250
+ }
251
+
252
+ // Run the AI agent
253
+ runReadAIAgent().catch(console.error);
@@ -0,0 +1,154 @@
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: Search tool using @tool decorator pattern
50
+ * Demonstrates class-based file search with automatic decoration
51
+ */
52
+ class FileSearcher {
53
+ @tool('search')
54
+ async findPattern(
55
+ query: string,
56
+ directory: string,
57
+ filePattern: string,
58
+ maxResults?: number
59
+ ): Promise<unknown> {
60
+ // Decorator automatically intercepts and executes via Matimo with:
61
+ // { query, directory, filePattern, maxResults }
62
+ // Positional args map to parameters in tool definition order
63
+ return undefined;
64
+ }
65
+
66
+ @tool('search')
67
+ async searchInDirectory(
68
+ query: string,
69
+ directory: string,
70
+ filePattern?: string
71
+ ): Promise<unknown> {
72
+ // Decorator automatically intercepts and executes via Matimo with:
73
+ // { query, directory, filePattern }
74
+ return undefined;
75
+ }
76
+
77
+ @tool('search')
78
+ async regexSearch(
79
+ query: string,
80
+ directory: string,
81
+ filePattern: string,
82
+ isRegex: boolean = true,
83
+ maxResults?: number
84
+ ): Promise<unknown> {
85
+ // Decorator automatically intercepts and executes via Matimo with:
86
+ // { query, directory, filePattern, isRegex, maxResults }
87
+ return undefined;
88
+ }
89
+ }
90
+
91
+ async function decoratorExample() {
92
+ // Set up decorator support with autoDiscover
93
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
94
+ setGlobalMatimoInstance(matimo);
95
+
96
+ // Set up approval callback for interactive approval
97
+ const approvalManager = getPathApprovalManager();
98
+ approvalManager.setApprovalCallback(promptForApproval);
99
+
100
+ console.info('=== Search Tool - Decorator Pattern (Interactive Approval) ===\n');
101
+
102
+ // Get the workspace root (parent of examples directory)
103
+ // File is in examples/tools/search/, so go up 3 levels
104
+ const workspaceRoot = path.resolve(__dirname, '../../..');
105
+
106
+ const searcher = new FileSearcher();
107
+
108
+ try {
109
+ // Example 1: Find pattern through decorated method
110
+ console.info('1. Finding "export" in TypeScript files\n');
111
+ const result1 = await searcher.findPattern(
112
+ 'export',
113
+ path.join(workspaceRoot, 'packages/core/src'),
114
+ '*.ts',
115
+ 5
116
+ );
117
+ console.info('Total matches:', (result1 as any).totalMatches);
118
+ console.info('Matches found:', (result1 as any).matches?.length);
119
+ console.info('---\n');
120
+
121
+ // Example 2: Search in specific directory
122
+ console.info('2. Searching in examples directory\n');
123
+ const result2 = await searcher.searchInDirectory(
124
+ 'async',
125
+ path.join(workspaceRoot, 'examples/tools'),
126
+ '*.ts'
127
+ );
128
+ console.info('Total matches:', (result2 as any).totalMatches);
129
+ console.info('Matches found:', (result2 as any).matches?.length);
130
+ console.info('---\n');
131
+
132
+ // Example 3: Regex search
133
+ console.info('3. Using regex to find patterns\n');
134
+ const result3 = await searcher.regexSearch(
135
+ 'console\\.info',
136
+ path.join(workspaceRoot, 'packages/core/src'),
137
+ '*.ts',
138
+ true,
139
+ 5
140
+ );
141
+ console.info('Total matches:', (result3 as any).totalMatches);
142
+ console.info('Matches found:', (result3 as any).matches?.length);
143
+ console.info('---\n');
144
+ } catch (error: any) {
145
+ console.error('Error:', error.message);
146
+ } finally {
147
+ if (!isReadlineClosed) {
148
+ rl.close();
149
+ isReadlineClosed = true;
150
+ }
151
+ }
152
+ }
153
+
154
+ decoratorExample();
@@ -0,0 +1,129 @@
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
+ // Create readline interface once for reuse across 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
+ * Prompt user for approval decision
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: Search tool using factory pattern
46
+ * Demonstrates searching files for patterns and content with interactive approval
47
+ */
48
+ async function searchExample() {
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('=== Search Tool - Factory Pattern (Interactive Approval) ===\n');
57
+
58
+ // Get the workspace root (parent of examples directory)
59
+ // File is in examples/tools/search/, so go up 3 levels
60
+ const workspaceRoot = path.resolve(__dirname, '../../..');
61
+
62
+ try {
63
+ // Example 1: Search for pattern in TypeScript files
64
+ console.info('1. Searching for "import" in examples\n');
65
+ const result1 = await matimo.execute('search', {
66
+ query: 'import',
67
+ directory: path.join(workspaceRoot, 'examples/tools'),
68
+ filePattern: '*.ts',
69
+ maxResults: 5,
70
+ });
71
+
72
+ if ((result1 as any).success) {
73
+ console.info('Total matches:', (result1 as any).totalMatches);
74
+ console.info('Matches found:', (result1 as any).matches?.length);
75
+ if ((result1 as any).matches) {
76
+ (result1 as any).matches.slice(0, 3).forEach((match: any) => {
77
+ console.info(` - ${match.filePath}:${match.lineNumber}: ${match.lineContent}`);
78
+ });
79
+ }
80
+ } else {
81
+ console.info('Search denied:', (result1 as any).error);
82
+ }
83
+ console.info('---\n');
84
+
85
+ // Example 2: Search for function definitions
86
+ console.info('2. Searching for function definitions\n');
87
+ const result2 = await matimo.execute('search', {
88
+ query: 'export function',
89
+ directory: path.join(workspaceRoot, 'packages/core/src'),
90
+ filePattern: '*.ts',
91
+ maxResults: 10,
92
+ });
93
+
94
+ if ((result2 as any).success) {
95
+ console.info('Total matches:', (result2 as any).totalMatches);
96
+ console.info('Matches found:', (result2 as any).matches?.length);
97
+ } else {
98
+ console.info('Search denied:', (result2 as any).error);
99
+ }
100
+ console.info('---\n');
101
+
102
+ // Example 3: Search in specific directory with regex
103
+ console.info('3. Searching for "console.info" patterns\n');
104
+ const result3 = await matimo.execute('search', {
105
+ query: 'console\\.info',
106
+ directory: path.join(workspaceRoot, 'packages/core/src'),
107
+ filePattern: '*.ts',
108
+ isRegex: true,
109
+ maxResults: 5,
110
+ });
111
+
112
+ if ((result3 as any).success) {
113
+ console.info('Total matches:', (result3 as any).totalMatches);
114
+ console.info('Matches found:', (result3 as any).matches?.length);
115
+ } else {
116
+ console.info('Search denied:', (result3 as any).error);
117
+ }
118
+ console.info('---\n');
119
+ } catch (error: any) {
120
+ console.error('Error searching files:', error.message);
121
+ } finally {
122
+ if (!isReadlineClosed) {
123
+ rl.close();
124
+ isReadlineClosed = true;
125
+ }
126
+ }
127
+ }
128
+
129
+ searchExample();