@mastra/agent-builder 0.0.1-alpha.1 → 0.0.1-alpha.2

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/agent/index.d.ts +5885 -0
  3. package/dist/agent/index.d.ts.map +1 -0
  4. package/dist/defaults.d.ts +6529 -0
  5. package/dist/defaults.d.ts.map +1 -0
  6. package/dist/index.d.ts +4 -4
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +1810 -36
  9. package/dist/index.js.map +1 -0
  10. package/dist/processors/tool-summary.d.ts +29 -0
  11. package/dist/processors/tool-summary.d.ts.map +1 -0
  12. package/dist/processors/write-file.d.ts +10 -0
  13. package/dist/processors/write-file.d.ts.map +1 -0
  14. package/dist/types.d.ts +1121 -0
  15. package/dist/types.d.ts.map +1 -0
  16. package/dist/utils.d.ts +63 -0
  17. package/dist/utils.d.ts.map +1 -0
  18. package/dist/workflows/index.d.ts +5 -0
  19. package/dist/workflows/index.d.ts.map +1 -0
  20. package/dist/workflows/shared/schema.d.ts +139 -0
  21. package/dist/workflows/shared/schema.d.ts.map +1 -0
  22. package/dist/workflows/task-planning/prompts.d.ts +37 -0
  23. package/dist/workflows/task-planning/prompts.d.ts.map +1 -0
  24. package/dist/workflows/task-planning/schema.d.ts +548 -0
  25. package/dist/workflows/task-planning/schema.d.ts.map +1 -0
  26. package/dist/workflows/task-planning/task-planning.d.ts +992 -0
  27. package/dist/workflows/task-planning/task-planning.d.ts.map +1 -0
  28. package/dist/workflows/template-builder/template-builder.d.ts +1910 -0
  29. package/dist/workflows/template-builder/template-builder.d.ts.map +1 -0
  30. package/dist/workflows/workflow-builder/prompts.d.ts +44 -0
  31. package/dist/workflows/workflow-builder/prompts.d.ts.map +1 -0
  32. package/dist/workflows/workflow-builder/schema.d.ts +1170 -0
  33. package/dist/workflows/workflow-builder/schema.d.ts.map +1 -0
  34. package/dist/workflows/workflow-builder/tools.d.ts +309 -0
  35. package/dist/workflows/workflow-builder/tools.d.ts.map +1 -0
  36. package/dist/workflows/workflow-builder/workflow-builder.d.ts +2714 -0
  37. package/dist/workflows/workflow-builder/workflow-builder.d.ts.map +1 -0
  38. package/dist/workflows/workflow-map.d.ts +3735 -0
  39. package/dist/workflows/workflow-map.d.ts.map +1 -0
  40. package/package.json +20 -9
  41. package/.turbo/turbo-build.log +0 -12
  42. package/dist/_tsup-dts-rollup.d.cts +0 -14933
  43. package/dist/_tsup-dts-rollup.d.ts +0 -14933
  44. package/dist/index.cjs +0 -4357
  45. package/dist/index.d.cts +0 -4
  46. package/eslint.config.js +0 -11
  47. package/integration-tests/CHANGELOG.md +0 -9
  48. package/integration-tests/README.md +0 -154
  49. package/integration-tests/docker-compose.yml +0 -39
  50. package/integration-tests/package.json +0 -38
  51. package/integration-tests/src/agent-template-behavior.test.ts +0 -103
  52. package/integration-tests/src/fixtures/minimal-mastra-project/env.example +0 -6
  53. package/integration-tests/src/fixtures/minimal-mastra-project/package.json +0 -17
  54. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/agents/weather.ts +0 -34
  55. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/index.ts +0 -15
  56. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/mcp/index.ts +0 -46
  57. package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/tools/weather.ts +0 -14
  58. package/integration-tests/src/fixtures/minimal-mastra-project/tsconfig.json +0 -17
  59. package/integration-tests/src/template-integration.test.ts +0 -312
  60. package/integration-tests/tsconfig.json +0 -9
  61. package/integration-tests/vitest.config.ts +0 -18
  62. package/src/agent/index.ts +0 -187
  63. package/src/agent-builder.test.ts +0 -313
  64. package/src/defaults.ts +0 -2876
  65. package/src/index.ts +0 -3
  66. package/src/processors/tool-summary.ts +0 -145
  67. package/src/processors/write-file.ts +0 -17
  68. package/src/types.ts +0 -305
  69. package/src/utils.ts +0 -409
  70. package/src/workflows/index.ts +0 -1
  71. package/src/workflows/template-builder.ts +0 -1682
  72. package/tsconfig.json +0 -5
  73. package/vitest.config.ts +0 -11
package/src/defaults.ts DELETED
@@ -1,2876 +0,0 @@
1
- import { spawn as nodeSpawn } from 'child_process';
2
- import { readFile, writeFile, mkdir, stat, readdir } from 'fs/promises';
3
- import { join, dirname, relative, isAbsolute, resolve } from 'path';
4
- import { createTool } from '@mastra/core/tools';
5
- import ignore from 'ignore';
6
- import { z } from 'zod';
7
- import { exec, execFile, spawnSWPM, spawnWithOutput } from './utils';
8
-
9
- export class AgentBuilderDefaults {
10
- static DEFAULT_INSTRUCTIONS = (
11
- projectPath?: string,
12
- ) => `You are a Mastra Expert Agent, specialized in building production-ready AI applications using the Mastra framework. You excel at creating agents, tools, workflows, and complete applications with real, working implementations.
13
-
14
- ## Core Identity & Capabilities
15
-
16
- **Primary Role:** Transform natural language requirements into working Mastra applications
17
- **Key Strength:** Deep knowledge of Mastra patterns, conventions, and best practices
18
- **Output Quality:** Production-ready code that follows Mastra ecosystem standards
19
-
20
- ## Workflow: The MASTRA Method
21
-
22
- Follow this sequence for every coding task:
23
-
24
- IF NO PROJECT EXISTS, USE THE MANAGEPROJECT TOOL TO CREATE A NEW PROJECT
25
-
26
- DO NOT INCLUDE TODOS IN THE CODE, UNLESS SPECIFICALLY ASKED TO DO SO, CREATE REAL WORLD CODE
27
-
28
- ### 1. 🔍 **UNDERSTAND** (Information Gathering)
29
- - **Explore Mastra Docs**: Use docs tools to understand relevant Mastra patterns and APIs
30
- - **Analyze Project**: Use file exploration to understand existing codebase structure
31
- - **Web Research**: Search for packages, examples, or solutions when docs are insufficient
32
- - **Clarify Requirements**: Ask targeted questions only when critical information is missing
33
-
34
- ### 2. 📋 **PLAN** (Strategy & Design)
35
- - **Architecture**: Design using Mastra conventions (agents, tools, workflows, memory)
36
- - **Dependencies**: Identify required packages and Mastra components
37
- - **Integration**: Plan how to integrate with existing project structure
38
- - **Validation**: Define how to test and verify the implementation
39
-
40
- ### 3. 🛠️ **BUILD** (Implementation)
41
- - **Install First**: Use \`manageProject\` tool to install required packages
42
- - **Follow Patterns**: Implement using established Mastra conventions
43
- - **Real Code Only**: Build actual working functionality, never mock implementations
44
- - **Environment Setup**: Create proper .env configuration and documentation
45
-
46
- ### 4. ✅ **VALIDATE** (Quality Assurance)
47
- - **Code Validation**: Run \`validateCode\` with types and lint checks
48
- - **Testing**: Execute tests if available
49
- - **Server Testing**: Use \`manageServer\` and \`httpRequest\` for API validation
50
- - **Fix Issues**: Address all errors before completion
51
-
52
- ## Mastra-Specific Guidelines
53
-
54
- ### Framework Knowledge
55
- - **Agents**: Use \`@mastra/core/agent\` with proper configuration
56
- - **Tools**: Create tools with \`@mastra/core/tools\` and proper schemas
57
- - **Memory**: Implement memory with \`@mastra/memory\` and appropriate processors
58
- - **Workflows**: Build workflows with \`@mastra/core/workflows\`
59
- - **Integrations**: Leverage Mastra's extensive integration ecosystem
60
-
61
- ### Code Standards
62
- - **TypeScript First**: All code must be properly typed
63
- - **Zod Schemas**: Use Zod for all data validation
64
- - **Environment Variables**: Proper .env configuration with examples
65
- - **Error Handling**: Comprehensive error handling with meaningful messages
66
- - **Security**: Never expose credentials or sensitive data
67
-
68
- ### Project Structure
69
- - Follow Mastra project conventions (\`src/mastra/\`, config files)
70
- - Use proper file organization (agents, tools, workflows in separate directories)
71
- - Maintain consistent naming conventions
72
- - Include proper exports and imports
73
-
74
- ## Communication Style
75
-
76
- **Conciseness**: Keep responses focused and actionable
77
- **Clarity**: Explain complex concepts in simple terms
78
- **Directness**: State what you're doing and why
79
- **No Fluff**: Avoid unnecessary explanations or apologies
80
-
81
- ### Response Format
82
- 1. **Brief Status**: One line stating what you're doing
83
- 2. **Tool Usage**: Execute necessary tools
84
- 3. **Results Summary**: Concise summary of what was accomplished
85
- 4. **Next Steps**: Clear indication of completion or next actions
86
-
87
- ## Tool Usage Strategy
88
-
89
- ### File Operations
90
- - **Project-Relative Paths**: All file paths are resolved relative to the project directory (unless absolute paths are used)
91
- - **Read First**: Always read files before editing to understand context
92
- - **Precise Edits**: Use exact text matching for search/replace operations
93
- - **Batch Operations**: Group related file operations when possible
94
-
95
- ### Project Management
96
- - **manageProject**: Use for package installation, project creation, dependency management
97
- - **validateCode**: Always run after code changes to ensure quality
98
- - **manageServer**: Use for testing Mastra server functionality
99
- - **httpRequest**: Test API endpoints and integrations
100
-
101
- ### Information Gathering
102
- - **Mastra Docs**: Primary source for Mastra-specific information
103
- - **Web Search**: Secondary source for packages and external solutions
104
- - **File Exploration**: Understand existing project structure and patterns
105
-
106
- ## Error Handling & Recovery
107
-
108
- ### Validation Failures
109
- - Fix TypeScript errors immediately
110
- - Address linting issues systematically
111
- - Re-validate until clean
112
-
113
- ### Build Issues
114
- - Check dependencies and versions
115
- - Verify Mastra configuration
116
- - Test in isolation when needed
117
-
118
- ### Integration Problems
119
- - Verify API keys and environment setup
120
- - Test connections independently
121
- - Debug with logging and error messages
122
-
123
- ## Security & Best Practices
124
-
125
- **Never:**
126
- - Hard-code API keys or secrets
127
- - Generate mock or placeholder implementations
128
- - Skip error handling
129
- - Ignore TypeScript errors
130
- - Create insecure code patterns
131
- - ask for file paths, you should be able to use the provided tools to explore the file system
132
-
133
- **Always:**
134
- - Use environment variables for configuration
135
- - Implement proper input validation
136
- - Follow security best practices
137
- - Create complete, working implementations
138
- - Test thoroughly before completion
139
-
140
- ## Output Requirements
141
-
142
- ### Code Quality
143
- - ✅ TypeScript compilation passes
144
- - ✅ ESLint validation passes
145
- - ✅ Proper error handling implemented
146
- - ✅ Environment variables configured
147
- - ✅ Tests included when appropriate
148
-
149
- ### Documentation
150
- - ✅ Clear setup instructions
151
- - ✅ Environment variable documentation
152
- - ✅ Usage examples provided
153
- - ✅ API documentation for custom tools
154
-
155
- ### Integration
156
- - ✅ Follows Mastra conventions
157
- - ✅ Integrates with existing project
158
- - ✅ Proper imports and exports
159
- - ✅ Compatible with Mastra ecosystem
160
-
161
- ## Project Context
162
-
163
- **Working Directory**: ${projectPath}
164
- **Focus**: Mastra framework applications
165
- **Goal**: Production-ready implementations
166
-
167
- Remember: You are building real applications, not prototypes. Every implementation should be complete, secure, and ready for production use.
168
-
169
- ## Enhanced Tool Set
170
-
171
- You have access to an enhanced set of tools based on production coding agent patterns:
172
-
173
- ### Task Management
174
- - **taskManager**: Create and track multi-step coding tasks with states (pending, in_progress, completed, blocked). Use this for complex projects that require systematic progress tracking.
175
-
176
- ### Code Discovery & Analysis
177
- - **codeAnalyzer**: Analyze codebase structure, discover definitions (functions, classes, interfaces), map dependencies, and understand architectural patterns.
178
- - **smartSearch**: Intelligent search with context awareness, pattern matching, and relevance scoring.
179
-
180
- ### Advanced File Operations
181
- - **readFile**: Read files with optional line ranges, encoding support, metadata
182
- - **writeFile**: Write files with directory creation
183
- - **listDirectory**: Directory listing with filtering, recursion, metadata
184
- - **multiEdit**: Perform multiple search-replace operations across files atomically with backup creation
185
- - **executeCommand**: Execute shell commands with proper error handling and working directory support
186
-
187
- **Important**: All file paths are resolved relative to the project directory unless absolute paths are provided.
188
-
189
- ### Communication & Workflow
190
- - **askClarification**: Ask users for clarification when requirements are unclear or multiple options exist.
191
- - **attemptCompletion**: Signal task completion with validation status and confidence metrics.
192
-
193
- ### Guidelines for Enhanced Tools:
194
-
195
- 1. **Use taskManager proactively** for any task requiring 3+ steps or complex coordination
196
- 2. **Start with codeAnalyzer** when working with unfamiliar codebases to understand structure
197
- 3. **Use smartSearch** for intelligent pattern discovery across the codebase
198
- 4. **Apply multiEdit** for systematic refactoring across multiple files
199
- 5. **Ask for clarification** when requirements are ambiguous rather than making assumptions
200
- 6. **Signal completion** with comprehensive summaries and validation status
201
-
202
- Use the following basic examples to guide your implementation.
203
-
204
- <examples>
205
- ### Weather Agent
206
- \`\`\`
207
- // ./src/agents/weather-agent.ts
208
- import { openai } from '@ai-sdk/openai';
209
- import { Agent } from '@mastra/core/agent';
210
- import { Memory } from '@mastra/memory';
211
- import { LibSQLStore } from '@mastra/libsql';
212
- import { weatherTool } from '../tools/weather-tool';
213
-
214
- export const weatherAgent = new Agent({
215
- name: 'Weather Agent',
216
- instructions: \${instructions},
217
- model: openai('gpt-4o-mini'),
218
- tools: { weatherTool },
219
- memory: new Memory({
220
- storage: new LibSQLStore({
221
- url: 'file:../mastra.db', // ask user what database to use, use this as the default
222
- }),
223
- }),
224
- });
225
- \`\`\`
226
-
227
- ### Weather Tool
228
- \`\`\`
229
- // ./src/tools/weather-tool.ts
230
- import { createTool } from '@mastra/core/tools';
231
- import { z } from 'zod';
232
- import { getWeather } from '../tools/weather-tool';
233
-
234
- export const weatherTool = createTool({
235
- id: 'get-weather',
236
- description: 'Get current weather for a location',
237
- inputSchema: z.object({
238
- location: z.string().describe('City name'),
239
- }),
240
- outputSchema: z.object({
241
- temperature: z.number(),
242
- feelsLike: z.number(),
243
- humidity: z.number(),
244
- windSpeed: z.number(),
245
- windGust: z.number(),
246
- conditions: z.string(),
247
- location: z.string(),
248
- }),
249
- execute: async ({ context }) => {
250
- return await getWeather(context.location);
251
- },
252
- });
253
- \`\`\`
254
-
255
- ### Weather Workflow
256
- \`\`\`
257
- // ./src/workflows/weather-workflow.ts
258
- import { createStep, createWorkflow } from '@mastra/core/workflows';
259
- import { z } from 'zod';
260
-
261
- const fetchWeather = createStep({
262
- id: 'fetch-weather',
263
- description: 'Fetches weather forecast for a given city',
264
- inputSchema: z.object({
265
- city: z.string().describe('The city to get the weather for'),
266
- }),
267
- outputSchema: forecastSchema,
268
- execute: async ({ inputData }) => {
269
- if (!inputData) {
270
- throw new Error('Input data not found');
271
- }
272
-
273
- const geocodingUrl = \`https://geocoding-api.open-meteo.com/v1/search?name=\${encodeURIComponent(inputData.city)}&count=1\`;
274
- const geocodingResponse = await fetch(geocodingUrl);
275
- const geocodingData = (await geocodingResponse.json()) as {
276
- results: { latitude: number; longitude: number; name: string }[];
277
- };
278
-
279
- if (!geocodingData.results?.[0]) {
280
- throw new Error(\`Location '\${inputData.city}' not found\`);
281
- }
282
-
283
- const { latitude, longitude, name } = geocodingData.results[0];
284
-
285
- const weatherUrl = \`https://api.open-meteo.com/v1/forecast?latitude=\${latitude}&longitude=\${longitude}&current=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m\`
286
- const response = await fetch(weatherUrl);
287
- const data = (await response.json()) as {
288
- current: {
289
- time: string;
290
- precipitation: number;
291
- weathercode: number;
292
- };
293
- hourly: {
294
- precipitation_probability: number[];
295
- temperature_2m: number[];
296
- };
297
- };
298
-
299
- const forecast = {
300
- date: new Date().toISOString(),
301
- maxTemp: Math.max(...data.hourly.temperature_2m),
302
- minTemp: Math.min(...data.hourly.temperature_2m),
303
- condition: getWeatherCondition(data.current.weathercode),
304
- precipitationChance: data.hourly.precipitation_probability.reduce(
305
- (acc, curr) => Math.max(acc, curr),
306
- 0,
307
- ),
308
- location: name,
309
- };
310
-
311
- return forecast;
312
- },
313
- });
314
-
315
- const planActivities = createStep({
316
- id: 'plan-activities',
317
- description: 'Suggests activities based on weather conditions',
318
- inputSchema: forecastSchema,
319
- outputSchema: z.object({
320
- activities: z.string(),
321
- }),
322
- execute: async ({ inputData, mastra }) => {
323
- const forecast = inputData;
324
-
325
- if (!forecast) {
326
- throw new Error('Forecast data not found');
327
- }
328
-
329
- const agent = mastra?.getAgent('weatherAgent');
330
- if (!agent) {
331
- throw new Error('Weather agent not found');
332
- }
333
-
334
- const prompt = \${weatherWorkflowPrompt}
335
-
336
- const response = await agent.stream([
337
- {
338
- role: 'user',
339
- content: prompt,
340
- },
341
- ]);
342
-
343
- let activitiesText = '';
344
-
345
- for await (const chunk of response.textStream) {
346
- process.stdout.write(chunk);
347
- activitiesText += chunk;
348
- }
349
-
350
- return {
351
- activities: activitiesText,
352
- };
353
- },
354
- });
355
-
356
- const weatherWorkflow = createWorkflow({
357
- id: 'weather-workflow',
358
- inputSchema: z.object({
359
- city: z.string().describe('The city to get the weather for'),
360
- }),
361
- outputSchema: z.object({
362
- activities: z.string(),
363
- }),
364
- })
365
- .then(fetchWeather)
366
- .then(planActivities);
367
-
368
- weatherWorkflow.commit();
369
- \`\`\`
370
- export { weatherWorkflow };
371
- \`\`\`
372
-
373
- ### Mastra instance
374
- \`\`\`
375
- // ./src/mastra.ts
376
-
377
- import { Mastra } from '@mastra/core/mastra';
378
- import { PinoLogger } from '@mastra/loggers';
379
- import { LibSQLStore } from '@mastra/libsql';
380
- import { weatherWorkflow } from './workflows/weather-workflow';
381
- import { weatherAgent } from './agents/weather-agent';
382
-
383
- export const mastra = new Mastra({
384
- workflows: { weatherWorkflow },
385
- agents: { weatherAgent },
386
- storage: new LibSQLStore({
387
- // stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
388
- url: ":memory:",
389
- }),
390
- logger: new PinoLogger({
391
- name: 'Mastra',
392
- level: 'info',
393
- }),
394
- });
395
- \`\`\`
396
-
397
- </examples>`;
398
-
399
- static DEFAULT_MEMORY_CONFIG = {
400
- lastMessages: 20,
401
- };
402
-
403
- static DEFAULT_FOLDER_STRUCTURE = {
404
- agent: 'src/mastra/agents',
405
- workflow: 'src/mastra/workflows',
406
- tool: 'src/mastra/tools',
407
- 'mcp-server': 'src/mastra/mcp',
408
- network: 'src/mastra/networks',
409
- };
410
-
411
- static DEFAULT_TOOLS = async (projectPath: string, mode: 'template' | 'code-editor' = 'code-editor') => {
412
- const agentBuilderTools = {
413
- readFile: createTool({
414
- id: 'read-file',
415
- description: 'Read contents of a file with optional line range selection.',
416
- inputSchema: z.object({
417
- filePath: z.string().describe('Path to the file to read'),
418
- startLine: z.number().optional().describe('Starting line number (1-indexed)'),
419
- endLine: z.number().optional().describe('Ending line number (1-indexed, inclusive)'),
420
- encoding: z.string().default('utf-8').describe('File encoding'),
421
- }),
422
- outputSchema: z.object({
423
- success: z.boolean(),
424
- content: z.string().optional(),
425
- lines: z.array(z.string()).optional(),
426
- metadata: z
427
- .object({
428
- size: z.number(),
429
- totalLines: z.number(),
430
- encoding: z.string(),
431
- lastModified: z.string(),
432
- })
433
- .optional(),
434
- error: z.string().optional(),
435
- }),
436
- execute: async ({ context }) => {
437
- return await AgentBuilderDefaults.readFile({ ...context, projectPath });
438
- },
439
- }),
440
-
441
- writeFile: createTool({
442
- id: 'write-file',
443
- description: 'Write content to a file, with options for creating directories.',
444
- inputSchema: z.object({
445
- filePath: z.string().describe('Path to the file to write'),
446
- content: z.string().describe('Content to write to the file'),
447
- createDirs: z.boolean().default(true).describe("Create parent directories if they don't exist"),
448
- encoding: z.string().default('utf-8').describe('File encoding'),
449
- }),
450
- outputSchema: z.object({
451
- success: z.boolean(),
452
- filePath: z.string(),
453
- bytesWritten: z.number().optional(),
454
- message: z.string(),
455
- error: z.string().optional(),
456
- }),
457
- execute: async ({ context }) => {
458
- return await AgentBuilderDefaults.writeFile({ ...context, projectPath });
459
- },
460
- }),
461
-
462
- listDirectory: createTool({
463
- id: 'list-directory',
464
- description: 'List contents of a directory with filtering and metadata options.',
465
- inputSchema: z.object({
466
- path: z.string().describe('Directory path to list'),
467
- recursive: z.boolean().default(false).describe('List subdirectories recursively'),
468
- includeHidden: z.boolean().default(false).describe('Include hidden files and directories'),
469
- pattern: z.string().optional().describe('Glob pattern to filter files'),
470
- maxDepth: z.number().default(10).describe('Maximum recursion depth'),
471
- includeMetadata: z.boolean().default(true).describe('Include file metadata'),
472
- }),
473
- outputSchema: z.object({
474
- success: z.boolean(),
475
- items: z.array(
476
- z.object({
477
- name: z.string(),
478
- path: z.string(),
479
- type: z.enum(['file', 'directory', 'symlink']),
480
- size: z.number().optional(),
481
- lastModified: z.string().optional(),
482
- permissions: z.string().optional(),
483
- }),
484
- ),
485
- totalItems: z.number(),
486
- path: z.string(),
487
- message: z.string(),
488
- error: z.string().optional(),
489
- }),
490
- execute: async ({ context }) => {
491
- return await AgentBuilderDefaults.listDirectory({ ...context, projectPath });
492
- },
493
- }),
494
-
495
- executeCommand: createTool({
496
- id: 'execute-command',
497
- description: 'Execute shell commands with proper error handling and output capture.',
498
- inputSchema: z.object({
499
- command: z.string().describe('Shell command to execute'),
500
- workingDirectory: z.string().optional().describe('Working directory for command execution'),
501
- timeout: z.number().default(30000).describe('Timeout in milliseconds'),
502
- captureOutput: z.boolean().default(true).describe('Capture command output'),
503
- shell: z.string().optional().describe('Shell to use (defaults to system shell)'),
504
- env: z.record(z.string()).optional().describe('Environment variables'),
505
- }),
506
- outputSchema: z.object({
507
- success: z.boolean(),
508
- exitCode: z.number().optional(),
509
- stdout: z.string().optional(),
510
- stderr: z.string().optional(),
511
- command: z.string(),
512
- workingDirectory: z.string().optional(),
513
- executionTime: z.number().optional(),
514
- error: z.string().optional(),
515
- }),
516
- execute: async ({ context }) => {
517
- return await AgentBuilderDefaults.executeCommand({
518
- ...context,
519
- workingDirectory: context.workingDirectory || projectPath,
520
- });
521
- },
522
- }),
523
- // Enhanced Task Management (Critical for complex coding tasks)
524
- taskManager: createTool({
525
- id: 'task-manager',
526
- description:
527
- 'Create and manage structured task lists for coding sessions. Use this for complex multi-step tasks to track progress and ensure thoroughness.',
528
- inputSchema: z.object({
529
- action: z.enum(['create', 'update', 'list', 'complete', 'remove']).describe('Task management action'),
530
- tasks: z
531
- .array(
532
- z.object({
533
- id: z.string().describe('Unique task identifier'),
534
- content: z.string().describe('Task description, optional if just updating the status').optional(),
535
- status: z.enum(['pending', 'in_progress', 'completed', 'blocked']).describe('Task status'),
536
- priority: z.enum(['high', 'medium', 'low']).default('medium').describe('Task priority'),
537
- dependencies: z.array(z.string()).optional().describe('IDs of tasks this depends on'),
538
- notes: z.string().optional().describe('Additional notes or context'),
539
- }),
540
- )
541
- .optional()
542
- .describe('Tasks to create or update'),
543
- taskId: z.string().optional().describe('Specific task ID for single task operations'),
544
- }),
545
- outputSchema: z.object({
546
- success: z.boolean(),
547
- tasks: z.array(
548
- z.object({
549
- id: z.string(),
550
- content: z.string(),
551
- status: z.string(),
552
- priority: z.string(),
553
- dependencies: z.array(z.string()).optional(),
554
- notes: z.string().optional(),
555
- createdAt: z.string(),
556
- updatedAt: z.string(),
557
- }),
558
- ),
559
- message: z.string(),
560
- }),
561
- execute: async ({ context }) => {
562
- return await AgentBuilderDefaults.manageTaskList(context);
563
- },
564
- }),
565
-
566
- // Advanced File Operations
567
- multiEdit: createTool({
568
- id: 'multi-edit',
569
- description: 'Perform multiple search-replace operations on one or more files in a single atomic operation.',
570
- inputSchema: z.object({
571
- operations: z
572
- .array(
573
- z.object({
574
- filePath: z.string().describe('Path to the file to edit'),
575
- edits: z
576
- .array(
577
- z.object({
578
- oldString: z.string().describe('Exact text to replace'),
579
- newString: z.string().describe('Replacement text'),
580
- replaceAll: z.boolean().default(false).describe('Replace all occurrences'),
581
- }),
582
- )
583
- .describe('List of edit operations for this file'),
584
- }),
585
- )
586
- .describe('File edit operations to perform'),
587
- createBackup: z.boolean().default(false).describe('Create backup files before editing'),
588
- }),
589
- outputSchema: z.object({
590
- success: z.boolean(),
591
- results: z.array(
592
- z.object({
593
- filePath: z.string(),
594
- editsApplied: z.number(),
595
- errors: z.array(z.string()),
596
- backup: z.string().optional(),
597
- }),
598
- ),
599
- message: z.string(),
600
- }),
601
- execute: async ({ context }) => {
602
- return await AgentBuilderDefaults.performMultiEdit({ ...context, projectPath });
603
- },
604
- }),
605
-
606
- replaceLines: createTool({
607
- id: 'replace-lines',
608
- description:
609
- 'Replace specific line ranges in files with new content. Perfect for fixing multiline imports, function signatures, or other structured code.',
610
- inputSchema: z.object({
611
- filePath: z.string().describe('Path to the file to edit'),
612
- startLine: z.number().describe('Starting line number to replace (1-indexed)'),
613
- endLine: z.number().describe('Ending line number to replace (1-indexed, inclusive)'),
614
- newContent: z.string().describe('New content to replace the lines with'),
615
- createBackup: z.boolean().default(false).describe('Create backup file before editing'),
616
- }),
617
- outputSchema: z.object({
618
- success: z.boolean(),
619
- message: z.string(),
620
- linesReplaced: z.number().optional(),
621
- backup: z.string().optional(),
622
- error: z.string().optional(),
623
- }),
624
- execute: async ({ context }) => {
625
- return await AgentBuilderDefaults.replaceLines({ ...context, projectPath });
626
- },
627
- }),
628
-
629
- // Interactive Communication
630
- askClarification: createTool({
631
- id: 'ask-clarification',
632
- description: 'Ask the user for clarification when requirements are unclear or when multiple options exist.',
633
- inputSchema: z.object({
634
- question: z.string().describe('The specific question to ask'),
635
- options: z
636
- .array(
637
- z.object({
638
- id: z.string(),
639
- description: z.string(),
640
- implications: z.string().optional(),
641
- }),
642
- )
643
- .optional()
644
- .describe('Multiple choice options if applicable'),
645
- context: z.string().optional().describe('Additional context about why clarification is needed'),
646
- urgency: z.enum(['low', 'medium', 'high']).default('medium').describe('How urgent the clarification is'),
647
- }),
648
- outputSchema: z.object({
649
- questionId: z.string(),
650
- question: z.string(),
651
- options: z
652
- .array(
653
- z.object({
654
- id: z.string(),
655
- description: z.string(),
656
- }),
657
- )
658
- .optional(),
659
- awaitingResponse: z.boolean(),
660
- }),
661
- execute: async ({ context }) => {
662
- return await AgentBuilderDefaults.askClarification(context);
663
- },
664
- }),
665
-
666
- // Enhanced Pattern Search
667
- smartSearch: createTool({
668
- id: 'smart-search',
669
- description: 'Intelligent search across codebase with context awareness and pattern matching.',
670
- inputSchema: z.object({
671
- query: z.string().describe('Search query or pattern'),
672
- type: z.enum(['text', 'regex', 'fuzzy', 'semantic']).default('text').describe('Type of search to perform'),
673
- scope: z
674
- .object({
675
- paths: z.array(z.string()).optional().describe('Specific paths to search'),
676
- fileTypes: z.array(z.string()).optional().describe('File extensions to include'),
677
- excludePaths: z.array(z.string()).optional().describe('Paths to exclude'),
678
- maxResults: z.number().default(50).describe('Maximum number of results'),
679
- })
680
- .optional(),
681
- context: z
682
- .object({
683
- beforeLines: z.number().default(2).describe('Lines of context before match'),
684
- afterLines: z.number().default(2).describe('Lines of context after match'),
685
- includeDefinitions: z.boolean().default(false).describe('Include function/class definitions'),
686
- })
687
- .optional(),
688
- }),
689
- outputSchema: z.object({
690
- success: z.boolean(),
691
- matches: z.array(
692
- z.object({
693
- file: z.string(),
694
- line: z.number(),
695
- column: z.number().optional(),
696
- match: z.string(),
697
- context: z.object({
698
- before: z.array(z.string()),
699
- after: z.array(z.string()),
700
- }),
701
- relevance: z.number().optional(),
702
- }),
703
- ),
704
- summary: z.object({
705
- totalMatches: z.number(),
706
- filesSearched: z.number(),
707
- patterns: z.array(z.string()),
708
- }),
709
- }),
710
- execute: async ({ context }) => {
711
- return await AgentBuilderDefaults.performSmartSearch(context, projectPath);
712
- },
713
- }),
714
-
715
- validateCode: createTool({
716
- id: 'validate-code',
717
- description:
718
- 'Validates generated code through TypeScript compilation, ESLint, schema validation, and other checks',
719
- inputSchema: z.object({
720
- projectPath: z.string().optional().describe('Path to the project to validate (defaults to current project)'),
721
- validationType: z
722
- .array(z.enum(['types', 'lint', 'schemas', 'tests', 'build']))
723
- .describe('Types of validation to perform'),
724
- files: z
725
- .array(z.string())
726
- .optional()
727
- .describe('Specific files to validate (if not provided, validates entire project)'),
728
- }),
729
- outputSchema: z.object({
730
- valid: z.boolean(),
731
- errors: z.array(
732
- z.object({
733
- type: z.enum(['typescript', 'eslint', 'schema', 'test', 'build']),
734
- severity: z.enum(['error', 'warning', 'info']),
735
- message: z.string(),
736
- file: z.string().optional(),
737
- line: z.number().optional(),
738
- column: z.number().optional(),
739
- code: z.string().optional(),
740
- }),
741
- ),
742
- summary: z.object({
743
- totalErrors: z.number(),
744
- totalWarnings: z.number(),
745
- validationsPassed: z.array(z.string()),
746
- validationsFailed: z.array(z.string()),
747
- }),
748
- }),
749
- execute: async ({ context }) => {
750
- const { projectPath: validationProjectPath, validationType, files } = context;
751
- const targetPath = validationProjectPath || projectPath;
752
- return await AgentBuilderDefaults.validateCode({
753
- projectPath: targetPath,
754
- validationType,
755
- files,
756
- });
757
- },
758
- }),
759
- };
760
-
761
- if (mode === 'template') {
762
- return agentBuilderTools;
763
- } else {
764
- return {
765
- ...agentBuilderTools,
766
-
767
- // Web Search (replaces MCP web search)
768
- webSearch: createTool({
769
- id: 'web-search',
770
- description: 'Search the web for current information and return structured results.',
771
- inputSchema: z.object({
772
- query: z.string().describe('Search query'),
773
- maxResults: z.number().default(10).describe('Maximum number of results to return'),
774
- region: z.string().default('us').describe('Search region/country code'),
775
- language: z.string().default('en').describe('Search language'),
776
- includeImages: z.boolean().default(false).describe('Include image results'),
777
- dateRange: z.enum(['day', 'week', 'month', 'year', 'all']).default('all').describe('Date range filter'),
778
- }),
779
- outputSchema: z.object({
780
- success: z.boolean(),
781
- query: z.string(),
782
- results: z.array(
783
- z.object({
784
- title: z.string(),
785
- url: z.string(),
786
- snippet: z.string(),
787
- domain: z.string(),
788
- publishDate: z.string().optional(),
789
- relevanceScore: z.number().optional(),
790
- }),
791
- ),
792
- totalResults: z.number(),
793
- searchTime: z.number(),
794
- suggestions: z.array(z.string()).optional(),
795
- error: z.string().optional(),
796
- }),
797
- execute: async ({ context }) => {
798
- return await AgentBuilderDefaults.webSearch(context);
799
- },
800
- }),
801
-
802
- // Enhanced Code Discovery
803
- codeAnalyzer: createTool({
804
- id: 'code-analyzer',
805
- description: 'Analyze codebase structure, discover definitions, and understand architecture patterns.',
806
- inputSchema: z.object({
807
- action: z
808
- .enum(['definitions', 'dependencies', 'patterns', 'structure'])
809
- .describe('Type of analysis to perform'),
810
- path: z.string().describe('Directory or file path to analyze'),
811
- language: z.string().optional().describe('Programming language filter'),
812
- depth: z.number().default(3).describe('Directory traversal depth'),
813
- includeTests: z.boolean().default(false).describe('Include test files in analysis'),
814
- }),
815
- outputSchema: z.object({
816
- success: z.boolean(),
817
- analysis: z.object({
818
- definitions: z
819
- .array(
820
- z.object({
821
- name: z.string(),
822
- type: z.string(),
823
- file: z.string(),
824
- line: z.number().optional(),
825
- scope: z.string().optional(),
826
- }),
827
- )
828
- .optional(),
829
- dependencies: z
830
- .array(
831
- z.object({
832
- name: z.string(),
833
- type: z.enum(['import', 'require', 'include']),
834
- source: z.string(),
835
- target: z.string(),
836
- }),
837
- )
838
- .optional(),
839
- patterns: z
840
- .array(
841
- z.object({
842
- pattern: z.string(),
843
- description: z.string(),
844
- files: z.array(z.string()),
845
- }),
846
- )
847
- .optional(),
848
- structure: z
849
- .object({
850
- directories: z.number(),
851
- files: z.number(),
852
- languages: z.record(z.number()),
853
- complexity: z.string(),
854
- })
855
- .optional(),
856
- }),
857
- message: z.string(),
858
- }),
859
- execute: async ({ context }) => {
860
- return await AgentBuilderDefaults.analyzeCode(context);
861
- },
862
- }),
863
-
864
- // Task Completion Signaling
865
- attemptCompletion: createTool({
866
- id: 'attempt-completion',
867
- description: 'Signal that you believe the requested task has been completed and provide a summary.',
868
- inputSchema: z.object({
869
- summary: z.string().describe('Summary of what was accomplished'),
870
- changes: z
871
- .array(
872
- z.object({
873
- type: z.enum([
874
- 'file_created',
875
- 'file_modified',
876
- 'file_deleted',
877
- 'command_executed',
878
- 'dependency_added',
879
- ]),
880
- description: z.string(),
881
- path: z.string().optional(),
882
- }),
883
- )
884
- .describe('List of changes made'),
885
- validation: z
886
- .object({
887
- testsRun: z.boolean().default(false),
888
- buildsSuccessfully: z.boolean().default(false),
889
- manualTestingRequired: z.boolean().default(false),
890
- })
891
- .describe('Validation status'),
892
- nextSteps: z.array(z.string()).optional().describe('Suggested next steps or follow-up actions'),
893
- }),
894
- outputSchema: z.object({
895
- completionId: z.string(),
896
- status: z.enum(['completed', 'needs_review', 'needs_testing']),
897
- summary: z.string(),
898
- confidence: z.number().min(0).max(100),
899
- }),
900
- execute: async ({ context }) => {
901
- return await AgentBuilderDefaults.signalCompletion(context);
902
- },
903
- }),
904
-
905
- manageProject: createTool({
906
- id: 'manage-project',
907
- description:
908
- 'Handles project management including creating project structures, managing dependencies, and package operations.',
909
- inputSchema: z.object({
910
- action: z.enum(['create', 'install', 'upgrade']).describe('The action to perform'),
911
- features: z
912
- .array(z.string())
913
- .optional()
914
- .describe('Mastra features to include (e.g., ["agents", "memory", "workflows"])'),
915
- packages: z
916
- .array(
917
- z.object({
918
- name: z.string(),
919
- version: z.string().optional(),
920
- }),
921
- )
922
- .optional()
923
- .describe('Packages to install/upgrade'),
924
- }),
925
- outputSchema: z.object({
926
- success: z.boolean(),
927
- installed: z.array(z.string()).optional(),
928
- upgraded: z.array(z.string()).optional(),
929
- warnings: z.array(z.string()).optional(),
930
- message: z.string().optional(),
931
- details: z.string().optional(),
932
- error: z.string().optional(),
933
- }),
934
- execute: async ({ context }) => {
935
- const { action, features, packages } = context;
936
- try {
937
- switch (action) {
938
- case 'create':
939
- return await AgentBuilderDefaults.createMastraProject({
940
- projectName: projectPath,
941
- features,
942
- });
943
- case 'install':
944
- if (!packages?.length) {
945
- return {
946
- success: false,
947
- message: 'Packages array is required for install action',
948
- };
949
- }
950
- return await AgentBuilderDefaults.installPackages({
951
- packages,
952
- projectPath,
953
- });
954
- case 'upgrade':
955
- if (!packages?.length) {
956
- return {
957
- success: false,
958
- message: 'Packages array is required for upgrade action',
959
- };
960
- }
961
- return await AgentBuilderDefaults.upgradePackages({
962
- packages,
963
- projectPath,
964
- });
965
- // case 'check':
966
- // return await AgentBuilderDefaults.checkProject({
967
- // projectPath,
968
- // });
969
- default:
970
- return {
971
- success: false,
972
- message: `Unknown action: ${action}`,
973
- };
974
- }
975
- } catch (error) {
976
- return {
977
- success: false,
978
- message: `Error executing ${action}: ${error instanceof Error ? error.message : String(error)}`,
979
- };
980
- }
981
- },
982
- }),
983
- manageServer: createTool({
984
- id: 'manage-server',
985
- description:
986
- 'Manages the Mastra server - start, stop, restart, and check status, use the terminal tool to make curl requests to the server. There is an openapi spec for the server at http://localhost:{port}/openapi.json',
987
- inputSchema: z.object({
988
- action: z.enum(['start', 'stop', 'restart', 'status']).describe('Server management action'),
989
- port: z.number().optional().default(4200).describe('Port to run the server on'),
990
- }),
991
- outputSchema: z.object({
992
- success: z.boolean(),
993
- status: z.enum(['running', 'stopped', 'starting', 'stopping', 'unknown']),
994
- pid: z.number().optional(),
995
- port: z.number().optional(),
996
- url: z.string().optional(),
997
- message: z.string().optional(),
998
- stdout: z.array(z.string()).optional().describe('Server output lines captured during startup'),
999
- error: z.string().optional(),
1000
- }),
1001
- execute: async ({ context }) => {
1002
- const { action, port } = context;
1003
- try {
1004
- switch (action) {
1005
- case 'start':
1006
- return await AgentBuilderDefaults.startMastraServer({
1007
- port,
1008
- projectPath,
1009
- });
1010
- case 'stop':
1011
- return await AgentBuilderDefaults.stopMastraServer({
1012
- port,
1013
- projectPath,
1014
- });
1015
- case 'restart':
1016
- const stopResult = await AgentBuilderDefaults.stopMastraServer({
1017
- port,
1018
- projectPath,
1019
- });
1020
- if (!stopResult.success) {
1021
- return {
1022
- success: false,
1023
- status: 'unknown' as const,
1024
- message: `Failed to restart: could not stop server on port ${port}`,
1025
- error: stopResult.error || 'Unknown stop error',
1026
- };
1027
- }
1028
- await new Promise(resolve => setTimeout(resolve, 500));
1029
- const startResult = await AgentBuilderDefaults.startMastraServer({
1030
- port,
1031
- projectPath,
1032
- });
1033
- if (!startResult.success) {
1034
- return {
1035
- success: false,
1036
- status: 'stopped' as const,
1037
- message: `Failed to restart: server stopped successfully but failed to start on port ${port}`,
1038
- error: startResult.error || 'Unknown start error',
1039
- };
1040
- }
1041
- return {
1042
- ...startResult,
1043
- message: `Mastra server restarted successfully on port ${port}`,
1044
- };
1045
- case 'status':
1046
- return await AgentBuilderDefaults.checkMastraServerStatus({
1047
- port,
1048
- projectPath,
1049
- });
1050
- default:
1051
- return {
1052
- success: false,
1053
- status: 'unknown' as const,
1054
- message: `Unknown action: ${action}`,
1055
- };
1056
- }
1057
- } catch (error) {
1058
- return {
1059
- success: false,
1060
- status: 'unknown' as const,
1061
- message: `Error managing server: ${error instanceof Error ? error.message : String(error)}`,
1062
- };
1063
- }
1064
- },
1065
- }),
1066
- httpRequest: createTool({
1067
- id: 'http-request',
1068
- description: 'Makes HTTP requests to the Mastra server or external APIs for testing and integration',
1069
- inputSchema: z.object({
1070
- method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).describe('HTTP method'),
1071
- url: z.string().describe('Full URL or path (if baseUrl provided)'),
1072
- baseUrl: z.string().optional().describe('Base URL for the server (e.g., http://localhost:4200)'),
1073
- headers: z.record(z.string()).optional().describe('HTTP headers'),
1074
- body: z.any().optional().describe('Request body (will be JSON stringified if object)'),
1075
- timeout: z.number().optional().default(30000).describe('Request timeout in milliseconds'),
1076
- }),
1077
- outputSchema: z.object({
1078
- success: z.boolean(),
1079
- status: z.number().optional(),
1080
- statusText: z.string().optional(),
1081
- headers: z.record(z.string()).optional(),
1082
- data: z.any().optional(),
1083
- error: z.string().optional(),
1084
- url: z.string(),
1085
- method: z.string(),
1086
- }),
1087
- execute: async ({ context }) => {
1088
- const { method, url, baseUrl, headers, body, timeout } = context;
1089
- try {
1090
- return await AgentBuilderDefaults.makeHttpRequest({
1091
- method,
1092
- url,
1093
- baseUrl,
1094
- headers,
1095
- body,
1096
- timeout,
1097
- });
1098
- } catch (error) {
1099
- return {
1100
- success: false,
1101
- url: baseUrl ? `${baseUrl}${url}` : url,
1102
- method,
1103
- error: error instanceof Error ? error.message : String(error),
1104
- };
1105
- }
1106
- },
1107
- }),
1108
- };
1109
- }
1110
- };
1111
-
1112
- /**
1113
- * Create a new Mastra project using create-mastra CLI
1114
- */
1115
- static async createMastraProject({ features, projectName }: { features?: string[]; projectName?: string }) {
1116
- try {
1117
- const args = ['pnpx', 'create-mastra@latest', projectName?.replace(/[;&|`$(){}\[\]]/g, '') ?? '', '-l', 'openai'];
1118
- if (features && features.length > 0) {
1119
- args.push('--components', features.join(','));
1120
- }
1121
- args.push('--example');
1122
-
1123
- const { stdout, stderr } = await spawnWithOutput(args[0]!, args.slice(1), {});
1124
-
1125
- return {
1126
- success: true,
1127
- projectPath: `./${projectName}`,
1128
- message: `Successfully created Mastra project: ${projectName}.`,
1129
- details: stdout,
1130
- error: stderr,
1131
- };
1132
- } catch (error) {
1133
- console.log(error);
1134
- return {
1135
- success: false,
1136
- message: `Failed to create project: ${error instanceof Error ? error.message : String(error)}`,
1137
- };
1138
- }
1139
- }
1140
-
1141
- /**
1142
- * Install packages using the detected package manager
1143
- */
1144
- static async installPackages({
1145
- packages,
1146
- projectPath,
1147
- }: {
1148
- packages: Array<{ name: string; version?: string }>;
1149
- projectPath?: string;
1150
- }) {
1151
- try {
1152
- console.log('Installing packages:', JSON.stringify(packages, null, 2));
1153
-
1154
- const packageStrings = packages.map(p => `${p.name}`);
1155
-
1156
- await spawnSWPM(projectPath || '', 'add', packageStrings);
1157
-
1158
- return {
1159
- success: true,
1160
- installed: packageStrings,
1161
- message: `Successfully installed ${packages.length} package(s).`,
1162
- details: '',
1163
- };
1164
- } catch (error) {
1165
- return {
1166
- success: false,
1167
- message: `Failed to install packages: ${error instanceof Error ? error.message : String(error)}`,
1168
- };
1169
- }
1170
- }
1171
-
1172
- /**
1173
- * Upgrade packages using the detected package manager
1174
- */
1175
- static async upgradePackages({
1176
- packages,
1177
- projectPath,
1178
- }: {
1179
- packages?: Array<{ name: string; version?: string }>;
1180
- projectPath?: string;
1181
- }) {
1182
- try {
1183
- console.log('Upgrading specific packages:', JSON.stringify(packages, null, 2));
1184
-
1185
- let packageNames: string[] = [];
1186
-
1187
- if (packages && packages.length > 0) {
1188
- packageNames = packages.map(p => `${p.name}`);
1189
- }
1190
- await spawnSWPM(projectPath || '', 'upgrade', packageNames);
1191
-
1192
- return {
1193
- success: true,
1194
- upgraded: packages?.map(p => p.name) || ['all packages'],
1195
- message: `Packages upgraded successfully.`,
1196
- details: '',
1197
- };
1198
- } catch (error) {
1199
- return {
1200
- success: false,
1201
- message: `Failed to upgrade packages: ${error instanceof Error ? error.message : String(error)}`,
1202
- };
1203
- }
1204
- }
1205
-
1206
- // /**
1207
- // * Check project health and status
1208
- // */
1209
- // static async checkProject({ projectPath }: { projectPath?: string }) {
1210
- // try {
1211
- // const execOptions = projectPath ? { cwd: projectPath } : {};
1212
-
1213
- // let hasPackageJson = false;
1214
- // let hasMastraConfig = false;
1215
-
1216
- // try {
1217
- // await exec('test -f package.json', execOptions);
1218
- // hasPackageJson = true;
1219
- // } catch {
1220
- // // ignore
1221
- // }
1222
-
1223
- // try {
1224
- // await exec('test -f mastra.config.* || test -d src/mastra || test -d mastra', execOptions);
1225
- // hasMastraConfig = true;
1226
- // } catch {
1227
- // // ignore
1228
- // }
1229
-
1230
- // const warnings: string[] = [];
1231
- // if (!hasPackageJson) {
1232
- // warnings.push('No package.json found - this may not be a Node.js project');
1233
- // }
1234
- // if (!hasMastraConfig) {
1235
- // warnings.push('No Mastra configuration found - run "npx create-mastra" to initialize');
1236
- // }
1237
-
1238
- // return {
1239
- // success: true,
1240
- // message: `Project health check completed for ${projectPath || 'current directory'}`,
1241
- // warnings,
1242
- // checks: {
1243
- // hasPackageJson,
1244
- // hasMastraConfig,
1245
- // },
1246
- // };
1247
- // } catch (error) {
1248
- // return {
1249
- // success: false,
1250
- // message: `Failed to check project: ${error instanceof Error ? error.message : String(error)}`,
1251
- // };
1252
- // }
1253
- // }
1254
-
1255
- /**
1256
- * Start the Mastra server
1257
- */
1258
- static async startMastraServer({
1259
- port = 4200,
1260
- projectPath,
1261
- env = {},
1262
- }: {
1263
- port?: number;
1264
- projectPath?: string;
1265
- env?: Record<string, string>;
1266
- }) {
1267
- try {
1268
- const serverEnv = { ...process.env, ...env, PORT: port.toString() };
1269
- const execOptions = {
1270
- cwd: projectPath || process.cwd(),
1271
- env: serverEnv,
1272
- };
1273
-
1274
- const serverProcess = nodeSpawn('pnpm', ['run', 'dev'], {
1275
- ...execOptions,
1276
- detached: true,
1277
- stdio: 'pipe',
1278
- });
1279
-
1280
- const stdoutLines: string[] = [];
1281
-
1282
- const serverStarted = new Promise<any>((resolve, reject) => {
1283
- const timeout = setTimeout(() => {
1284
- reject(new Error(`Server startup timeout after 30 seconds. Output: ${stdoutLines.join('\n')}`));
1285
- }, 30000);
1286
-
1287
- serverProcess.stdout?.on('data', data => {
1288
- const output = data.toString();
1289
- const lines = output.split('\n').filter((line: string) => line.trim());
1290
- stdoutLines.push(...lines);
1291
-
1292
- if (output.includes('Mastra API running on port')) {
1293
- clearTimeout(timeout);
1294
- resolve({
1295
- success: true,
1296
- status: 'running' as const,
1297
- pid: serverProcess.pid,
1298
- port,
1299
- url: `http://localhost:${port}`,
1300
- message: `Mastra server started successfully on port ${port}`,
1301
- stdout: stdoutLines,
1302
- });
1303
- }
1304
- });
1305
-
1306
- serverProcess.stderr?.on('data', data => {
1307
- const errorOutput = data.toString();
1308
- stdoutLines.push(`[STDERR] ${errorOutput}`);
1309
- clearTimeout(timeout);
1310
- reject(new Error(`Server startup failed with error: ${errorOutput}`));
1311
- });
1312
-
1313
- serverProcess.on('error', error => {
1314
- clearTimeout(timeout);
1315
- reject(error);
1316
- });
1317
-
1318
- serverProcess.on('exit', (code, signal) => {
1319
- clearTimeout(timeout);
1320
- if (code !== 0 && code !== null) {
1321
- reject(
1322
- new Error(
1323
- `Server process exited with code ${code}${signal ? ` (signal: ${signal})` : ''}. Output: ${stdoutLines.join('\n')}`,
1324
- ),
1325
- );
1326
- }
1327
- });
1328
- });
1329
-
1330
- return await serverStarted;
1331
- } catch (error) {
1332
- return {
1333
- success: false,
1334
- status: 'stopped' as const,
1335
- error: error instanceof Error ? error.message : String(error),
1336
- };
1337
- }
1338
- }
1339
-
1340
- /**
1341
- * Stop the Mastra server
1342
- */
1343
- static async stopMastraServer({ port = 4200, projectPath: _projectPath }: { port?: number; projectPath?: string }) {
1344
- // Validate port to ensure it is a safe integer
1345
- if (typeof port !== 'number' || !Number.isInteger(port) || port < 1 || port > 65535) {
1346
- return {
1347
- success: false,
1348
- status: 'error' as const,
1349
- error: `Invalid port value: ${String(port)}`,
1350
- };
1351
- }
1352
- try {
1353
- // Run lsof safely without shell interpretation
1354
- const { stdout } = await execFile('lsof', ['-ti', String(port)]);
1355
- // If no output, treat as "No process found"
1356
- const effectiveStdout = stdout.trim() ? stdout : 'No process found';
1357
-
1358
- if (!effectiveStdout || effectiveStdout === 'No process found') {
1359
- return {
1360
- success: true,
1361
- status: 'stopped' as const,
1362
- message: `No Mastra server found running on port ${port}`,
1363
- };
1364
- }
1365
-
1366
- const pids = stdout
1367
- .trim()
1368
- .split('\n')
1369
- .filter(pid => pid.trim());
1370
- const killedPids: number[] = [];
1371
- const failedPids: number[] = [];
1372
-
1373
- for (const pidStr of pids) {
1374
- const pid = parseInt(pidStr.trim());
1375
- if (isNaN(pid)) continue;
1376
-
1377
- try {
1378
- process.kill(pid, 'SIGTERM');
1379
- killedPids.push(pid);
1380
- } catch (e) {
1381
- failedPids.push(pid);
1382
- console.warn(`Failed to kill process ${pid}:`, e);
1383
- }
1384
- }
1385
-
1386
- // If some processes failed to be killed, still report partial success
1387
- // but include warning about failed processes
1388
-
1389
- if (killedPids.length === 0) {
1390
- return {
1391
- success: false,
1392
- status: 'unknown' as const,
1393
- message: `Failed to stop any processes on port ${port}`,
1394
- error: `Could not kill PIDs: ${failedPids.join(', ')}`,
1395
- };
1396
- }
1397
-
1398
- // Report partial success if some processes were killed but others failed
1399
- if (failedPids.length > 0) {
1400
- console.warn(
1401
- `Killed ${killedPids.length} processes but failed to kill ${failedPids.length} processes: ${failedPids.join(', ')}`,
1402
- );
1403
- }
1404
-
1405
- // Wait a bit and check if processes are still running
1406
- await new Promise(resolve => setTimeout(resolve, 2000));
1407
-
1408
- try {
1409
- const { stdout: checkStdoutRaw } = await execFile('lsof', ['-ti', String(port)]);
1410
- const checkStdout = checkStdoutRaw.trim() ? checkStdoutRaw : 'No process found';
1411
- if (checkStdout && checkStdout !== 'No process found') {
1412
- // Force kill remaining processes
1413
- const remainingPids = checkStdout
1414
- .trim()
1415
- .split('\n')
1416
- .filter(pid => pid.trim());
1417
- for (const pidStr of remainingPids) {
1418
- const pid = parseInt(pidStr.trim());
1419
- if (!isNaN(pid)) {
1420
- try {
1421
- process.kill(pid, 'SIGKILL');
1422
- } catch {
1423
- // ignore
1424
- }
1425
- }
1426
- }
1427
-
1428
- // Final check
1429
- await new Promise(resolve => setTimeout(resolve, 1000));
1430
- const { stdout: finalCheckRaw } = await execFile('lsof', ['-ti', String(port)]);
1431
- const finalCheck = finalCheckRaw.trim() ? finalCheckRaw : 'No process found';
1432
- if (finalCheck && finalCheck !== 'No process found') {
1433
- return {
1434
- success: false,
1435
- status: 'unknown' as const,
1436
- message: `Server processes still running on port ${port} after stop attempts`,
1437
- error: `Remaining PIDs: ${finalCheck.trim()}`,
1438
- };
1439
- }
1440
- }
1441
- } catch (error) {
1442
- console.warn('Failed to verify server stop:', error);
1443
- }
1444
-
1445
- return {
1446
- success: true,
1447
- status: 'stopped' as const,
1448
- message: `Mastra server stopped successfully (port ${port}). Killed PIDs: ${killedPids.join(', ')}`,
1449
- };
1450
- } catch (error) {
1451
- return {
1452
- success: false,
1453
- status: 'unknown' as const,
1454
- error: error instanceof Error ? error.message : String(error),
1455
- };
1456
- }
1457
- }
1458
-
1459
- /**
1460
- * Check Mastra server status
1461
- */
1462
- static async checkMastraServerStatus({
1463
- port = 4200,
1464
- projectPath: _projectPath,
1465
- }: {
1466
- port?: number;
1467
- projectPath?: string;
1468
- }) {
1469
- try {
1470
- const controller = new AbortController();
1471
- const timeoutId = setTimeout(() => controller.abort(), 5000);
1472
-
1473
- const response = await fetch(`http://localhost:${port}/health`, {
1474
- method: 'GET',
1475
- signal: controller.signal,
1476
- });
1477
-
1478
- clearTimeout(timeoutId);
1479
-
1480
- if (response.ok) {
1481
- return {
1482
- success: true,
1483
- status: 'running' as const,
1484
- port,
1485
- url: `http://localhost:${port}`,
1486
- message: 'Mastra server is running and healthy',
1487
- };
1488
- } else {
1489
- return {
1490
- success: false,
1491
- status: 'unknown' as const,
1492
- port,
1493
- message: `Server responding but not healthy (status: ${response.status})`,
1494
- };
1495
- }
1496
- } catch {
1497
- // Check if process exists on port
1498
- try {
1499
- const { stdout } = await execFile('lsof', ['-ti', String(port)]);
1500
- const effectiveStdout = stdout.trim() ? stdout : 'No process found';
1501
- const hasProcess = effectiveStdout && effectiveStdout !== 'No process found';
1502
-
1503
- return {
1504
- success: Boolean(hasProcess),
1505
- status: hasProcess ? ('starting' as const) : ('stopped' as const),
1506
- port,
1507
- message: hasProcess
1508
- ? 'Server process exists but not responding to health checks'
1509
- : 'No server process found on specified port',
1510
- };
1511
- } catch {
1512
- return {
1513
- success: false,
1514
- status: 'stopped' as const,
1515
- port,
1516
- message: 'Server is not running',
1517
- };
1518
- }
1519
- }
1520
- }
1521
-
1522
- /**
1523
- * Validate code using TypeScript, ESLint, and other tools
1524
- */
1525
- static async validateCode({
1526
- projectPath,
1527
- validationType,
1528
- files,
1529
- }: {
1530
- projectPath?: string;
1531
- validationType: Array<'types' | 'lint' | 'schemas' | 'tests' | 'build'>;
1532
- files?: string[];
1533
- }) {
1534
- const errors: Array<{
1535
- type: 'typescript' | 'eslint' | 'schema' | 'test' | 'build';
1536
- severity: 'error' | 'warning' | 'info';
1537
- message: string;
1538
- file?: string;
1539
- line?: number;
1540
- column?: number;
1541
- code?: string;
1542
- }> = [];
1543
- const validationsPassed: string[] = [];
1544
- const validationsFailed: string[] = [];
1545
-
1546
- const execOptions = { cwd: projectPath };
1547
-
1548
- // TypeScript validation
1549
- if (validationType.includes('types')) {
1550
- try {
1551
- const fileArgs = files?.length ? files : [];
1552
- // Use execFile for safe argument passing to avoid shell interpretation
1553
- const args = ['tsc', '--noEmit', ...fileArgs];
1554
- await execFile('npx', args, execOptions);
1555
- validationsPassed.push('types');
1556
- } catch (error: any) {
1557
- let tsOutput = '';
1558
- if (error.stdout) {
1559
- tsOutput = error.stdout;
1560
- } else if (error.stderr) {
1561
- tsOutput = error.stderr;
1562
- } else if (error.message) {
1563
- tsOutput = error.message;
1564
- }
1565
-
1566
- errors.push({
1567
- type: 'typescript',
1568
- severity: 'error',
1569
- message: tsOutput.trim() || `TypeScript validation failed: ${error.message || String(error)}`,
1570
- });
1571
- validationsFailed.push('types');
1572
- }
1573
- }
1574
-
1575
- // ESLint validation
1576
- if (validationType.includes('lint')) {
1577
- try {
1578
- const fileArgs = files?.length ? files : ['.'];
1579
- const eslintArgs = ['eslint', ...fileArgs, '--format', 'json'];
1580
- const { stdout } = await execFile('npx', eslintArgs, execOptions);
1581
-
1582
- if (stdout) {
1583
- const eslintResults = JSON.parse(stdout);
1584
- const eslintErrors = AgentBuilderDefaults.parseESLintErrors(eslintResults);
1585
- errors.push(...eslintErrors);
1586
-
1587
- if (eslintErrors.some(e => e.severity === 'error')) {
1588
- validationsFailed.push('lint');
1589
- } else {
1590
- validationsPassed.push('lint');
1591
- }
1592
- } else {
1593
- validationsPassed.push('lint');
1594
- }
1595
- } catch (error: any) {
1596
- const errorMessage = error instanceof Error ? error.message : String(error);
1597
-
1598
- if (errorMessage.includes('"filePath"') || errorMessage.includes('messages')) {
1599
- try {
1600
- const eslintResults = JSON.parse(errorMessage);
1601
- const eslintErrors = AgentBuilderDefaults.parseESLintErrors(eslintResults);
1602
- errors.push(...eslintErrors);
1603
- validationsFailed.push('lint');
1604
- } catch {
1605
- errors.push({
1606
- type: 'eslint',
1607
- severity: 'error',
1608
- message: `ESLint validation failed: ${errorMessage}`,
1609
- });
1610
- validationsFailed.push('lint');
1611
- }
1612
- } else {
1613
- validationsPassed.push('lint');
1614
- }
1615
- }
1616
- }
1617
-
1618
- // Build validation
1619
- // if (validationType.includes('build')) {
1620
- // try {
1621
- // await spawnSWPM(execOptions.cwd!, 'build', []);
1622
- // validationsPassed.push('build');
1623
- // } catch (error) {
1624
- // const errorMessage = error instanceof Error ? error.message : String(error);
1625
- // errors.push({
1626
- // type: 'build',
1627
- // severity: 'error',
1628
- // message: `Build failed: ${errorMessage}`,
1629
- // });
1630
- // validationsFailed.push('build');
1631
- // }
1632
- // }
1633
-
1634
- // Test validation
1635
- // if (validationType.includes('tests')) {
1636
- // try {
1637
- // const testCommand = files?.length ? `npx vitest run ${files.join(' ')}` : 'npm test || pnpm test || yarn test';
1638
- // await exec(testCommand, execOptions);
1639
- // validationsPassed.push('tests');
1640
- // } catch (error) {
1641
- // const errorMessage = error instanceof Error ? error.message : String(error);
1642
- // errors.push({
1643
- // type: 'test',
1644
- // severity: 'error',
1645
- // message: `Tests failed: ${errorMessage}`,
1646
- // });
1647
- // validationsFailed.push('tests');
1648
- // }
1649
- // }
1650
-
1651
- const totalErrors = errors.filter(e => e.severity === 'error').length;
1652
- const totalWarnings = errors.filter(e => e.severity === 'warning').length;
1653
- const isValid = totalErrors === 0;
1654
-
1655
- return {
1656
- valid: isValid,
1657
- errors,
1658
- summary: {
1659
- totalErrors,
1660
- totalWarnings,
1661
- validationsPassed,
1662
- validationsFailed,
1663
- },
1664
- };
1665
- }
1666
-
1667
- /**
1668
- * Parse ESLint errors from JSON output
1669
- */
1670
- static parseESLintErrors(eslintResults: any[]): Array<{
1671
- type: 'eslint';
1672
- severity: 'error' | 'warning';
1673
- message: string;
1674
- file?: string;
1675
- line?: number;
1676
- column?: number;
1677
- code?: string;
1678
- }> {
1679
- const errors: Array<{
1680
- type: 'eslint';
1681
- severity: 'error' | 'warning';
1682
- message: string;
1683
- file?: string;
1684
- line?: number;
1685
- column?: number;
1686
- code?: string;
1687
- }> = [];
1688
-
1689
- for (const result of eslintResults) {
1690
- for (const message of result.messages || []) {
1691
- if (message.message) {
1692
- errors.push({
1693
- type: 'eslint',
1694
- severity: message.severity === 1 ? 'warning' : 'error',
1695
- message: message.message,
1696
- file: result.filePath || undefined,
1697
- line: message.line || undefined,
1698
- column: message.column || undefined,
1699
- code: message.ruleId || undefined,
1700
- });
1701
- }
1702
- }
1703
- }
1704
-
1705
- return errors;
1706
- }
1707
-
1708
- /**
1709
- * Make HTTP request to server or external API
1710
- */
1711
- static async makeHttpRequest({
1712
- method,
1713
- url,
1714
- baseUrl,
1715
- headers = {},
1716
- body,
1717
- timeout = 30000,
1718
- }: {
1719
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
1720
- url: string;
1721
- baseUrl?: string;
1722
- headers?: Record<string, string>;
1723
- body?: any;
1724
- timeout?: number;
1725
- }) {
1726
- try {
1727
- const fullUrl = baseUrl ? `${baseUrl}${url}` : url;
1728
-
1729
- const controller = new AbortController();
1730
- const timeoutId = setTimeout(() => controller.abort(), timeout);
1731
-
1732
- const requestOptions: RequestInit = {
1733
- method,
1734
- headers: {
1735
- 'Content-Type': 'application/json',
1736
- ...headers,
1737
- },
1738
- signal: controller.signal,
1739
- };
1740
-
1741
- if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
1742
- requestOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
1743
- }
1744
-
1745
- const response = await fetch(fullUrl, requestOptions);
1746
- clearTimeout(timeoutId);
1747
-
1748
- let data: any;
1749
- const contentType = response.headers.get('content-type');
1750
- if (contentType?.includes('application/json')) {
1751
- data = await response.json();
1752
- } else {
1753
- data = await response.text();
1754
- }
1755
-
1756
- const responseHeaders: Record<string, string> = {};
1757
- response.headers.forEach((value, key) => {
1758
- responseHeaders[key] = value;
1759
- });
1760
-
1761
- return {
1762
- success: response.ok,
1763
- status: response.status,
1764
- statusText: response.statusText,
1765
- headers: responseHeaders,
1766
- data,
1767
- url: fullUrl,
1768
- method,
1769
- };
1770
- } catch (error) {
1771
- return {
1772
- success: false,
1773
- url: baseUrl ? `${baseUrl}${url}` : url,
1774
- method,
1775
- error: error instanceof Error ? error.message : String(error),
1776
- };
1777
- }
1778
- }
1779
-
1780
- /**
1781
- * Enhanced task management system for complex coding tasks
1782
- */
1783
- static async manageTaskList(context: {
1784
- action: 'create' | 'update' | 'list' | 'complete' | 'remove';
1785
- tasks?: Array<{
1786
- id: string;
1787
- content?: string;
1788
- status: 'pending' | 'in_progress' | 'completed' | 'blocked';
1789
- priority: 'high' | 'medium' | 'low';
1790
- dependencies?: string[];
1791
- notes?: string;
1792
- }>;
1793
- taskId?: string;
1794
- }) {
1795
- // In-memory task storage with cleanup (could be enhanced with persistent storage)
1796
- if (!AgentBuilderDefaults.taskStorage) {
1797
- AgentBuilderDefaults.taskStorage = new Map();
1798
- }
1799
-
1800
- // Cleanup old sessions to prevent memory leaks
1801
- // Keep only the last 10 sessions
1802
- const sessions = Array.from(AgentBuilderDefaults.taskStorage.keys());
1803
- if (sessions.length > 10) {
1804
- const sessionsToRemove = sessions.slice(0, sessions.length - 10);
1805
- sessionsToRemove.forEach(session => AgentBuilderDefaults.taskStorage.delete(session));
1806
- }
1807
-
1808
- const sessionId = 'current'; // Could be enhanced with proper session management
1809
- const existingTasks = AgentBuilderDefaults.taskStorage.get(sessionId) || [];
1810
-
1811
- try {
1812
- switch (context.action) {
1813
- case 'create':
1814
- if (!context.tasks?.length) {
1815
- return {
1816
- success: false,
1817
- tasks: existingTasks,
1818
- message: 'No tasks provided for creation',
1819
- };
1820
- }
1821
-
1822
- const newTasks = context.tasks.map(task => ({
1823
- ...task,
1824
- createdAt: new Date().toISOString(),
1825
- updatedAt: new Date().toISOString(),
1826
- }));
1827
-
1828
- const allTasks = [...existingTasks, ...newTasks];
1829
- AgentBuilderDefaults.taskStorage.set(sessionId, allTasks);
1830
-
1831
- return {
1832
- success: true,
1833
- tasks: allTasks,
1834
- message: `Created ${newTasks.length} new task(s)`,
1835
- };
1836
-
1837
- case 'update':
1838
- if (!context.tasks?.length) {
1839
- return {
1840
- success: false,
1841
- tasks: existingTasks,
1842
- message: 'No tasks provided for update',
1843
- };
1844
- }
1845
-
1846
- const updatedTasks = existingTasks.map(existing => {
1847
- const update = context.tasks!.find(t => t.id === existing.id);
1848
- return update ? { ...existing, ...update, updatedAt: new Date().toISOString() } : existing;
1849
- });
1850
-
1851
- AgentBuilderDefaults.taskStorage.set(sessionId, updatedTasks);
1852
-
1853
- return {
1854
- success: true,
1855
- tasks: updatedTasks,
1856
- message: 'Tasks updated successfully',
1857
- };
1858
-
1859
- case 'complete':
1860
- if (!context.taskId) {
1861
- return {
1862
- success: false,
1863
- tasks: existingTasks,
1864
- message: 'Task ID required for completion',
1865
- };
1866
- }
1867
-
1868
- const completedTasks = existingTasks.map(task =>
1869
- task.id === context.taskId
1870
- ? { ...task, status: 'completed' as const, updatedAt: new Date().toISOString() }
1871
- : task,
1872
- );
1873
-
1874
- AgentBuilderDefaults.taskStorage.set(sessionId, completedTasks);
1875
-
1876
- return {
1877
- success: true,
1878
- tasks: completedTasks,
1879
- message: `Task ${context.taskId} marked as completed`,
1880
- };
1881
-
1882
- case 'remove':
1883
- if (!context.taskId) {
1884
- return {
1885
- success: false,
1886
- tasks: existingTasks,
1887
- message: 'Task ID required for removal',
1888
- };
1889
- }
1890
-
1891
- const filteredTasks = existingTasks.filter(task => task.id !== context.taskId);
1892
- AgentBuilderDefaults.taskStorage.set(sessionId, filteredTasks);
1893
-
1894
- return {
1895
- success: true,
1896
- tasks: filteredTasks,
1897
- message: `Task ${context.taskId} removed`,
1898
- };
1899
-
1900
- case 'list':
1901
- default:
1902
- return {
1903
- success: true,
1904
- tasks: existingTasks,
1905
- message: `Found ${existingTasks.length} task(s)`,
1906
- };
1907
- }
1908
- } catch (error) {
1909
- return {
1910
- success: false,
1911
- tasks: existingTasks,
1912
- message: `Task management error: ${error instanceof Error ? error.message : String(error)}`,
1913
- };
1914
- }
1915
- }
1916
-
1917
- /**
1918
- * Analyze codebase structure and patterns
1919
- */
1920
- static async analyzeCode(context: {
1921
- action: 'definitions' | 'dependencies' | 'patterns' | 'structure';
1922
- path: string;
1923
- language?: string;
1924
- depth?: number;
1925
- includeTests?: boolean;
1926
- }) {
1927
- try {
1928
- const { action, path, language, depth = 3 } = context;
1929
-
1930
- // Use ripgrep for fast searching
1931
- // const excludePatterns = includeTests ? [] : ['*test*', '*spec*', '__tests__'];
1932
-
1933
- // Only allow a list of known extensions/language types to prevent shell injection
1934
- const ALLOWED_LANGUAGES = [
1935
- 'js',
1936
- 'ts',
1937
- 'jsx',
1938
- 'tsx',
1939
- 'py',
1940
- 'java',
1941
- 'go',
1942
- 'cpp',
1943
- 'c',
1944
- 'cs',
1945
- 'rb',
1946
- 'php',
1947
- 'rs',
1948
- 'kt',
1949
- 'swift',
1950
- 'm',
1951
- 'scala',
1952
- 'sh',
1953
- 'json',
1954
- 'yaml',
1955
- 'yml',
1956
- 'toml',
1957
- 'ini',
1958
- ];
1959
- let languagePattern = '*';
1960
- if (language && ALLOWED_LANGUAGES.includes(language)) {
1961
- languagePattern = `*.${language}`;
1962
- }
1963
-
1964
- switch (action) {
1965
- case 'definitions':
1966
- // Search for function/class/interface definitions
1967
- const definitionPatterns = [
1968
- 'function\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1969
- 'class\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1970
- 'interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1971
- 'const\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*=',
1972
- 'export\\s+(function|class|interface|const)\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
1973
- ];
1974
-
1975
- const definitions: Array<{ name: string; type: string; file: string; line?: number; scope?: string }> = [];
1976
-
1977
- for (const pattern of definitionPatterns) {
1978
- try {
1979
- const { stdout } = await execFile('rg', [
1980
- '-n',
1981
- pattern,
1982
- path,
1983
- '--type',
1984
- languagePattern,
1985
- '--max-depth',
1986
- String(depth),
1987
- ]);
1988
- const matches = stdout.split('\n').filter(line => line.trim());
1989
-
1990
- matches.forEach(match => {
1991
- const parts = match.split(':');
1992
- if (parts.length >= 3) {
1993
- const file = parts[0];
1994
- const lineStr = parts[1];
1995
- const line = parseInt(lineStr || '0');
1996
- const content = parts.slice(2).join(':');
1997
- const nameMatch = content.match(/([a-zA-Z_][a-zA-Z0-9_]*)/);
1998
-
1999
- if (nameMatch && nameMatch[1]) {
2000
- definitions.push({
2001
- name: nameMatch[1],
2002
- type: pattern.includes('function')
2003
- ? 'function'
2004
- : pattern.includes('class')
2005
- ? 'class'
2006
- : pattern.includes('interface')
2007
- ? 'interface'
2008
- : 'variable',
2009
- file: file || '',
2010
- line,
2011
- scope: 'top-level',
2012
- });
2013
- }
2014
- }
2015
- });
2016
- } catch {
2017
- // Continue with other patterns if one fails
2018
- }
2019
- }
2020
-
2021
- return {
2022
- success: true,
2023
- analysis: { definitions },
2024
- message: `Found ${definitions.length} code definitions`,
2025
- };
2026
-
2027
- case 'dependencies':
2028
- // Search for import/require statements
2029
- const depPatterns = [
2030
- 'import\\s+.*\\s+from\\s+[\'"]([^\'"]+)[\'"]',
2031
- 'require\\([\'"]([^\'"]+)[\'"]\\)',
2032
- '#include\\s+[<"]([^>"]+)[>"]',
2033
- ];
2034
-
2035
- const dependencies: Array<{
2036
- name: string;
2037
- type: 'import' | 'require' | 'include';
2038
- source: string;
2039
- target: string;
2040
- }> = [];
2041
-
2042
- for (const pattern of depPatterns) {
2043
- try {
2044
- const { stdout } = await execFile('rg', ['-n', pattern, path, '--type', languagePattern]);
2045
- const matches = stdout.split('\n').filter(line => line.trim());
2046
-
2047
- matches.forEach(match => {
2048
- const parts = match.split(':');
2049
- if (parts.length >= 3) {
2050
- const file = parts[0];
2051
- const content = parts.slice(2).join(':');
2052
- const depMatch = content.match(new RegExp(pattern));
2053
-
2054
- if (depMatch && depMatch[1]) {
2055
- dependencies.push({
2056
- name: depMatch[1],
2057
- type: pattern.includes('import') ? 'import' : pattern.includes('require') ? 'require' : 'include',
2058
- source: file || '',
2059
- target: depMatch[1],
2060
- });
2061
- }
2062
- }
2063
- });
2064
- } catch {
2065
- // Continue with other patterns
2066
- }
2067
- }
2068
-
2069
- return {
2070
- success: true,
2071
- analysis: { dependencies },
2072
- message: `Found ${dependencies.length} dependencies`,
2073
- };
2074
-
2075
- case 'structure':
2076
- // Use execFile for find commands to avoid shell injection
2077
- const { stdout: lsOutput } = await execFile('find', [path, '-type', 'f', '-name', languagePattern]);
2078
- const allFiles = lsOutput.split('\n').filter(line => line.trim());
2079
- const files = allFiles.slice(0, 1000); // Limit to 1000 files like head -1000
2080
-
2081
- const { stdout: dirOutput } = await execFile('find', [path, '-type', 'd']);
2082
- const directories = dirOutput.split('\n').filter(line => line.trim()).length;
2083
-
2084
- // Count languages by file extension
2085
- const languages: Record<string, number> = {};
2086
- files.forEach(file => {
2087
- const ext = file.split('.').pop();
2088
- if (ext) {
2089
- languages[ext] = (languages[ext] || 0) + 1;
2090
- }
2091
- });
2092
-
2093
- const complexity = files.length > 1000 ? 'high' : files.length > 100 ? 'medium' : 'low';
2094
-
2095
- return {
2096
- success: true,
2097
- analysis: {
2098
- structure: {
2099
- directories,
2100
- files: files.length,
2101
- languages,
2102
- complexity,
2103
- },
2104
- },
2105
- message: `Analyzed project structure: ${files.length} files in ${directories} directories`,
2106
- };
2107
-
2108
- default:
2109
- return {
2110
- success: false,
2111
- analysis: {},
2112
- message: `Unknown analysis action: ${action}`,
2113
- };
2114
- }
2115
- } catch (error) {
2116
- return {
2117
- success: false,
2118
- analysis: {},
2119
- message: `Code analysis error: ${error instanceof Error ? error.message : String(error)}`,
2120
- };
2121
- }
2122
- }
2123
-
2124
- /**
2125
- * Perform multiple edits across files atomically
2126
- */
2127
- static async performMultiEdit(context: {
2128
- operations: Array<{
2129
- filePath: string;
2130
- edits: Array<{
2131
- oldString: string;
2132
- newString: string;
2133
- replaceAll?: boolean;
2134
- }>;
2135
- }>;
2136
- createBackup?: boolean;
2137
- projectPath?: string;
2138
- }) {
2139
- const { operations, createBackup = false, projectPath = process.cwd() } = context;
2140
- const results: Array<{
2141
- filePath: string;
2142
- editsApplied: number;
2143
- errors: string[];
2144
- backup?: string;
2145
- }> = [];
2146
-
2147
- try {
2148
- for (const operation of operations) {
2149
- const filePath = isAbsolute(operation.filePath) ? operation.filePath : join(projectPath, operation.filePath);
2150
- let editsApplied = 0;
2151
- const errors: string[] = [];
2152
- let backup: string | undefined;
2153
-
2154
- try {
2155
- // Create backup if requested
2156
- if (createBackup) {
2157
- const backupPath = `${filePath}.backup.${Date.now()}`;
2158
- const originalContent = await readFile(filePath, 'utf-8');
2159
- await writeFile(backupPath, originalContent, 'utf-8');
2160
- backup = backupPath;
2161
- }
2162
-
2163
- // Read current file content
2164
- let content = await readFile(filePath, 'utf-8');
2165
-
2166
- // Apply each edit
2167
- for (const edit of operation.edits) {
2168
- const { oldString, newString, replaceAll = false } = edit;
2169
-
2170
- if (replaceAll) {
2171
- const regex = new RegExp(oldString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
2172
- const matches = content.match(regex);
2173
- if (matches) {
2174
- content = content.replace(regex, newString);
2175
- editsApplied += matches.length;
2176
- }
2177
- } else {
2178
- if (content.includes(oldString)) {
2179
- content = content.replace(oldString, newString);
2180
- editsApplied++;
2181
- } else {
2182
- errors.push(`String not found: "${oldString.substring(0, 50)}${oldString.length > 50 ? '...' : ''}"`);
2183
- }
2184
- }
2185
- }
2186
-
2187
- // Write updated content back
2188
- await writeFile(filePath, content, 'utf-8');
2189
- } catch (error) {
2190
- errors.push(`File operation error: ${error instanceof Error ? error.message : String(error)}`);
2191
- }
2192
-
2193
- results.push({
2194
- filePath: operation.filePath,
2195
- editsApplied,
2196
- errors,
2197
- backup,
2198
- });
2199
- }
2200
-
2201
- const totalEdits = results.reduce((sum, r) => sum + r.editsApplied, 0);
2202
- const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
2203
-
2204
- return {
2205
- success: totalErrors === 0,
2206
- results,
2207
- message: `Applied ${totalEdits} edits across ${operations.length} files${totalErrors > 0 ? ` with ${totalErrors} errors` : ''}`,
2208
- };
2209
- } catch (error) {
2210
- return {
2211
- success: false,
2212
- results,
2213
- message: `Multi-edit operation failed: ${error instanceof Error ? error.message : String(error)}`,
2214
- };
2215
- }
2216
- }
2217
-
2218
- /**
2219
- * Replace specific line ranges in a file with new content
2220
- */
2221
- static async replaceLines(context: {
2222
- filePath: string;
2223
- startLine: number;
2224
- endLine: number;
2225
- newContent: string;
2226
- createBackup?: boolean;
2227
- projectPath?: string;
2228
- }) {
2229
- const { filePath, startLine, endLine, newContent, createBackup = false, projectPath = process.cwd() } = context;
2230
-
2231
- try {
2232
- const fullPath = isAbsolute(filePath) ? filePath : join(projectPath, filePath);
2233
-
2234
- // Read current file content
2235
- const content = await readFile(fullPath, 'utf-8');
2236
- const lines = content.split('\n');
2237
-
2238
- // Validate line numbers
2239
- if (startLine < 1 || endLine < 1 || startLine > lines.length || endLine > lines.length) {
2240
- return {
2241
- success: false,
2242
- message: `Invalid line range: ${startLine}-${endLine}. File has ${lines.length} lines.`,
2243
- error: 'Invalid line range',
2244
- };
2245
- }
2246
-
2247
- if (startLine > endLine) {
2248
- return {
2249
- success: false,
2250
- message: `Start line (${startLine}) cannot be greater than end line (${endLine}).`,
2251
- error: 'Invalid line range',
2252
- };
2253
- }
2254
-
2255
- // Create backup if requested
2256
- let backup: string | undefined;
2257
- if (createBackup) {
2258
- const backupPath = `${fullPath}.backup.${Date.now()}`;
2259
- await writeFile(backupPath, content, 'utf-8');
2260
- backup = backupPath;
2261
- }
2262
-
2263
- // Replace the specified line range
2264
- const beforeLines = lines.slice(0, startLine - 1);
2265
- const afterLines = lines.slice(endLine);
2266
- const newLines = newContent ? newContent.split('\n') : [];
2267
-
2268
- const updatedLines = [...beforeLines, ...newLines, ...afterLines];
2269
- const updatedContent = updatedLines.join('\n');
2270
-
2271
- // Write updated content back
2272
- await writeFile(fullPath, updatedContent, 'utf-8');
2273
-
2274
- const linesReplaced = endLine - startLine + 1;
2275
-
2276
- return {
2277
- success: true,
2278
- message: `Successfully replaced ${linesReplaced} lines (${startLine}-${endLine}) in ${filePath}`,
2279
- linesReplaced,
2280
- backup,
2281
- };
2282
- } catch (error) {
2283
- return {
2284
- success: false,
2285
- message: `Failed to replace lines: ${error instanceof Error ? error.message : String(error)}`,
2286
- error: error instanceof Error ? error.message : String(error),
2287
- };
2288
- }
2289
- }
2290
-
2291
- /**
2292
- * Ask user for clarification
2293
- */
2294
- static async askClarification(context: {
2295
- question: string;
2296
- options?: Array<{ id: string; description: string; implications?: string }>;
2297
- context?: string;
2298
- urgency?: 'low' | 'medium' | 'high';
2299
- }) {
2300
- const questionId = `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2301
-
2302
- // Store question for potential follow-up (in real implementation, this might be stored in session state)
2303
- if (!AgentBuilderDefaults.pendingQuestions) {
2304
- AgentBuilderDefaults.pendingQuestions = new Map();
2305
- }
2306
-
2307
- AgentBuilderDefaults.pendingQuestions.set(questionId, {
2308
- ...context,
2309
- timestamp: new Date().toISOString(),
2310
- });
2311
-
2312
- return {
2313
- questionId,
2314
- question: context.question,
2315
- options: context.options?.map(opt => ({ id: opt.id, description: opt.description })),
2316
- awaitingResponse: true,
2317
- };
2318
- }
2319
-
2320
- /**
2321
- * Signal task completion
2322
- */
2323
- static async signalCompletion(context: {
2324
- summary: string;
2325
- changes: Array<{
2326
- type: 'file_created' | 'file_modified' | 'file_deleted' | 'command_executed' | 'dependency_added';
2327
- description: string;
2328
- path?: string;
2329
- }>;
2330
- validation: {
2331
- testsRun?: boolean;
2332
- buildsSuccessfully?: boolean;
2333
- manualTestingRequired?: boolean;
2334
- };
2335
- nextSteps?: string[];
2336
- }) {
2337
- const completionId = `completion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2338
-
2339
- // Calculate confidence based on validation status
2340
- let confidence = 70; // Base confidence
2341
- if (context.validation.testsRun) confidence += 15;
2342
- if (context.validation.buildsSuccessfully) confidence += 15;
2343
- if (context.validation.manualTestingRequired) confidence -= 10;
2344
-
2345
- // Determine status
2346
- let status: 'completed' | 'needs_review' | 'needs_testing';
2347
- if (context.validation.testsRun && context.validation.buildsSuccessfully) {
2348
- status = 'completed';
2349
- } else if (context.validation.manualTestingRequired) {
2350
- status = 'needs_testing';
2351
- } else {
2352
- status = 'needs_review';
2353
- }
2354
-
2355
- return {
2356
- completionId,
2357
- status,
2358
- summary: context.summary,
2359
- confidence: Math.min(100, Math.max(0, confidence)),
2360
- };
2361
- }
2362
-
2363
- /**
2364
- * Perform intelligent search with context
2365
- */
2366
- static async performSmartSearch(
2367
- context: {
2368
- query: string;
2369
- type?: 'text' | 'regex' | 'fuzzy' | 'semantic';
2370
- scope?: {
2371
- paths?: string[];
2372
- fileTypes?: string[];
2373
- excludePaths?: string[];
2374
- maxResults?: number;
2375
- };
2376
- context?: {
2377
- beforeLines?: number;
2378
- afterLines?: number;
2379
- includeDefinitions?: boolean;
2380
- };
2381
- },
2382
- projectPath: string,
2383
- ) {
2384
- try {
2385
- const { query, type = 'text', scope = {}, context: searchContext = {} } = context;
2386
-
2387
- const { paths = ['.'], fileTypes = [], excludePaths = [], maxResults = 50 } = scope;
2388
-
2389
- const { beforeLines = 2, afterLines = 2 } = searchContext;
2390
-
2391
- // Build command and arguments array safely
2392
- const rgArgs: string[] = [];
2393
-
2394
- // Add context lines
2395
- if (beforeLines > 0) {
2396
- rgArgs.push('-B', beforeLines.toString());
2397
- }
2398
- if (afterLines > 0) {
2399
- rgArgs.push('-A', afterLines.toString());
2400
- }
2401
-
2402
- // Add line numbers
2403
- rgArgs.push('-n');
2404
-
2405
- // Handle search type
2406
- if (type === 'regex') {
2407
- rgArgs.push('-e');
2408
- } else if (type === 'fuzzy') {
2409
- rgArgs.push('--fixed-strings');
2410
- }
2411
-
2412
- // Add file type filters
2413
- if (fileTypes.length > 0) {
2414
- fileTypes.forEach(ft => {
2415
- rgArgs.push('--type-add', `custom:*.${ft}`, '-t', 'custom');
2416
- });
2417
- }
2418
-
2419
- // Add exclude patterns
2420
- excludePaths.forEach(path => {
2421
- rgArgs.push('--glob', `!${path}`);
2422
- });
2423
-
2424
- // Add max count
2425
- rgArgs.push('-m', maxResults.toString());
2426
-
2427
- // Add the search query and paths
2428
- rgArgs.push(query);
2429
- rgArgs.push(...paths);
2430
-
2431
- // Execute safely using execFile
2432
- const { stdout } = await execFile('rg', rgArgs, {
2433
- cwd: projectPath,
2434
- });
2435
- const lines = stdout.split('\n').filter(line => line.trim());
2436
-
2437
- const matches: Array<{
2438
- file: string;
2439
- line: number;
2440
- column?: number;
2441
- match: string;
2442
- context: { before: string[]; after: string[] };
2443
- relevance?: number;
2444
- }> = [];
2445
-
2446
- let currentMatch: any = null;
2447
-
2448
- lines.forEach(line => {
2449
- if (line.includes(':') && !line.startsWith('-')) {
2450
- // This is a match line
2451
- const parts = line.split(':');
2452
- if (parts.length >= 3) {
2453
- // Save previous match if exists
2454
- if (currentMatch) {
2455
- matches.push(currentMatch);
2456
- }
2457
-
2458
- currentMatch = {
2459
- file: parts[0] || '',
2460
- line: parseInt(parts[1] || '0'),
2461
- match: parts.slice(2).join(':'),
2462
- context: { before: [], after: [] },
2463
- relevance: type === 'fuzzy' ? Math.random() * 100 : undefined,
2464
- };
2465
- }
2466
- } else if (line.startsWith('-') && currentMatch) {
2467
- // This is a context line
2468
- const contextLine = line.substring(1);
2469
- if (currentMatch.context.before.length < beforeLines) {
2470
- currentMatch.context.before.push(contextLine);
2471
- } else {
2472
- currentMatch.context.after.push(contextLine);
2473
- }
2474
- }
2475
- });
2476
-
2477
- // Add the last match
2478
- if (currentMatch) {
2479
- matches.push(currentMatch);
2480
- }
2481
-
2482
- // Count files searched (approximate)
2483
- const filesSearched = new Set(matches.map(m => m.file)).size;
2484
-
2485
- return {
2486
- success: true,
2487
- matches: matches.slice(0, maxResults),
2488
- summary: {
2489
- totalMatches: matches.length,
2490
- filesSearched,
2491
- patterns: [query],
2492
- },
2493
- };
2494
- } catch {
2495
- return {
2496
- success: false,
2497
- matches: [],
2498
- summary: {
2499
- totalMatches: 0,
2500
- filesSearched: 0,
2501
- patterns: [context.query],
2502
- },
2503
- };
2504
- }
2505
- }
2506
-
2507
- // Static storage properties
2508
- private static taskStorage: Map<string, any[]>;
2509
- private static pendingQuestions: Map<string, any>;
2510
-
2511
- /**
2512
- * Read file contents with optional line range
2513
- */
2514
- static async readFile(context: {
2515
- filePath: string;
2516
- startLine?: number;
2517
- endLine?: number;
2518
- encoding?: string;
2519
- projectPath?: string;
2520
- }) {
2521
- try {
2522
- const { filePath, startLine, endLine, encoding = 'utf-8', projectPath } = context;
2523
-
2524
- // Resolve path relative to project directory if it's not absolute
2525
- const resolvedPath = isAbsolute(filePath) ? filePath : resolve(projectPath || process.cwd(), filePath);
2526
-
2527
- const stats = await stat(resolvedPath);
2528
- const content = await readFile(resolvedPath, { encoding: encoding as BufferEncoding });
2529
- const lines = content.split('\n');
2530
-
2531
- let resultContent = content;
2532
- let resultLines = lines;
2533
-
2534
- if (startLine !== undefined || endLine !== undefined) {
2535
- const start = Math.max(0, (startLine || 1) - 1);
2536
- const end = endLine !== undefined ? Math.min(lines.length, endLine) : lines.length;
2537
- resultLines = lines.slice(start, end);
2538
- resultContent = resultLines.join('\n');
2539
- }
2540
-
2541
- return {
2542
- success: true,
2543
- content: resultContent,
2544
- lines: resultLines,
2545
- metadata: {
2546
- size: stats.size,
2547
- totalLines: lines.length,
2548
- encoding,
2549
- lastModified: stats.mtime.toISOString(),
2550
- },
2551
- };
2552
- } catch (error) {
2553
- return {
2554
- success: false,
2555
- error: error instanceof Error ? error.message : String(error),
2556
- };
2557
- }
2558
- }
2559
-
2560
- /**
2561
- * Write content to file with directory creation and backup options
2562
- */
2563
- static async writeFile(context: {
2564
- filePath: string;
2565
- content: string;
2566
- createDirs?: boolean;
2567
- encoding?: string;
2568
- projectPath?: string;
2569
- }) {
2570
- try {
2571
- const { filePath, content, createDirs = true, encoding = 'utf-8', projectPath } = context;
2572
-
2573
- // Resolve path relative to project directory if it's not absolute
2574
- const resolvedPath = isAbsolute(filePath) ? filePath : resolve(projectPath || process.cwd(), filePath);
2575
- const dir = dirname(resolvedPath);
2576
-
2577
- // Create directories if needed
2578
- if (createDirs) {
2579
- await mkdir(dir, { recursive: true });
2580
- }
2581
-
2582
- // Write the file
2583
- await writeFile(resolvedPath, content, { encoding: encoding as BufferEncoding });
2584
-
2585
- return {
2586
- success: true,
2587
- filePath: resolvedPath,
2588
- bytesWritten: Buffer.byteLength(content, encoding as BufferEncoding),
2589
- message: `Successfully wrote ${Buffer.byteLength(content, encoding as BufferEncoding)} bytes to ${filePath}`,
2590
- };
2591
- } catch (error) {
2592
- return {
2593
- success: false,
2594
- filePath: context.filePath,
2595
- message: `Failed to write file: ${error instanceof Error ? error.message : String(error)}`,
2596
- error: error instanceof Error ? error.message : String(error),
2597
- };
2598
- }
2599
- }
2600
-
2601
- /**
2602
- * List directory contents with filtering and metadata
2603
- */
2604
- static async listDirectory(context: {
2605
- path: string;
2606
- recursive?: boolean;
2607
- includeHidden?: boolean;
2608
- pattern?: string;
2609
- maxDepth?: number;
2610
- includeMetadata?: boolean;
2611
- projectPath?: string;
2612
- }) {
2613
- try {
2614
- const {
2615
- path,
2616
- recursive = false,
2617
- includeHidden = false,
2618
- pattern,
2619
- maxDepth = 10,
2620
- includeMetadata = true,
2621
- projectPath,
2622
- } = context;
2623
-
2624
- const gitignorePath = join(projectPath || process.cwd(), '.gitignore');
2625
- let gitignoreFilter: ignore.Ignore | undefined;
2626
-
2627
- try {
2628
- const gitignoreContent = await readFile(gitignorePath, 'utf-8');
2629
- gitignoreFilter = ignore().add(gitignoreContent);
2630
- } catch (err: any) {
2631
- if (err.code !== 'ENOENT') {
2632
- console.error(`Error reading .gitignore file:`, err);
2633
- }
2634
- // If .gitignore doesn't exist, gitignoreFilter remains undefined, meaning no files are ignored by gitignore.
2635
- }
2636
-
2637
- // Resolve path relative to project directory if it's not absolute
2638
- const resolvedPath = isAbsolute(path) ? path : resolve(projectPath || process.cwd(), path);
2639
-
2640
- const items: Array<{
2641
- name: string;
2642
- path: string;
2643
- type: 'file' | 'directory' | 'symlink';
2644
- size?: number;
2645
- lastModified?: string;
2646
- permissions?: string;
2647
- }> = [];
2648
-
2649
- async function processDirectory(dirPath: string, currentDepth: number = 0) {
2650
- const relativeToProject = relative(projectPath || process.cwd(), dirPath);
2651
- if (gitignoreFilter?.ignores(relativeToProject)) return;
2652
- if (currentDepth > maxDepth) return;
2653
-
2654
- const entries = await readdir(dirPath);
2655
-
2656
- for (const entry of entries) {
2657
- const entryPath = join(dirPath, entry);
2658
- const relativeEntryPath = relative(projectPath || process.cwd(), entryPath);
2659
- if (gitignoreFilter?.ignores(relativeEntryPath)) continue;
2660
- if (!includeHidden && entry.startsWith('.')) continue;
2661
-
2662
- const fullPath = entryPath;
2663
- const relativePath = relative(resolvedPath, fullPath);
2664
-
2665
- if (pattern) {
2666
- // Simple pattern matching
2667
- const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
2668
- if (!new RegExp(regexPattern).test(entry)) continue;
2669
- }
2670
-
2671
- let stats;
2672
- let type: 'file' | 'directory' | 'symlink';
2673
-
2674
- try {
2675
- stats = await stat(fullPath);
2676
- if (stats.isDirectory()) {
2677
- type = 'directory';
2678
- } else if (stats.isSymbolicLink()) {
2679
- type = 'symlink';
2680
- } else {
2681
- type = 'file';
2682
- }
2683
- } catch {
2684
- continue; // Skip entries we can't stat
2685
- }
2686
-
2687
- const item: any = {
2688
- name: entry,
2689
- path: relativePath || entry,
2690
- type,
2691
- };
2692
-
2693
- if (includeMetadata) {
2694
- item.size = stats.size;
2695
- item.lastModified = stats.mtime.toISOString();
2696
- item.permissions = `0${(stats.mode & parseInt('777', 8)).toString(8)}`;
2697
- }
2698
-
2699
- items.push(item);
2700
-
2701
- // Recurse into directories if requested
2702
- if (recursive && type === 'directory') {
2703
- await processDirectory(fullPath, currentDepth + 1);
2704
- }
2705
- }
2706
- }
2707
-
2708
- await processDirectory(resolvedPath);
2709
-
2710
- return {
2711
- success: true,
2712
- items,
2713
- totalItems: items.length,
2714
- path: resolvedPath,
2715
- message: `Listed ${items.length} items in ${resolvedPath}`,
2716
- };
2717
- } catch (error) {
2718
- return {
2719
- success: false,
2720
- items: [],
2721
- totalItems: 0,
2722
- path: context.path,
2723
- message: `Failed to list directory: ${error instanceof Error ? error.message : String(error)}`,
2724
- error: error instanceof Error ? error.message : String(error),
2725
- };
2726
- }
2727
- }
2728
-
2729
- /**
2730
- * Execute shell commands with proper error handling
2731
- */
2732
- static async executeCommand(context: {
2733
- command: string;
2734
- workingDirectory?: string;
2735
- timeout?: number;
2736
- captureOutput?: boolean;
2737
- shell?: string;
2738
- env?: Record<string, string>;
2739
- }) {
2740
- const startTime = Date.now();
2741
- try {
2742
- const { command, workingDirectory, timeout = 30000, captureOutput = true, shell, env } = context;
2743
-
2744
- const execOptions: any = {
2745
- timeout,
2746
- env: { ...process.env, ...env },
2747
- };
2748
-
2749
- if (workingDirectory) {
2750
- execOptions.cwd = workingDirectory;
2751
- }
2752
-
2753
- if (shell) {
2754
- execOptions.shell = shell;
2755
- }
2756
-
2757
- const { stdout, stderr } = await exec(command, execOptions);
2758
- const executionTime = Date.now() - startTime;
2759
-
2760
- return {
2761
- success: true,
2762
- exitCode: 0,
2763
- stdout: captureOutput ? String(stdout) : undefined,
2764
- stderr: captureOutput ? String(stderr) : undefined,
2765
- command,
2766
- workingDirectory,
2767
- executionTime,
2768
- };
2769
- } catch (error: any) {
2770
- const executionTime = Date.now() - startTime;
2771
-
2772
- return {
2773
- success: false,
2774
- exitCode: error.code || 1,
2775
- stdout: String(error.stdout || ''),
2776
- stderr: String(error.stderr || ''),
2777
- command: context.command,
2778
- workingDirectory: context.workingDirectory,
2779
- executionTime,
2780
- error: error instanceof Error ? error.message : String(error),
2781
- };
2782
- }
2783
- }
2784
-
2785
- /**
2786
- * Web search using a simple search approach
2787
- */
2788
- static async webSearch(context: {
2789
- query: string;
2790
- maxResults?: number;
2791
- region?: string;
2792
- language?: string;
2793
- includeImages?: boolean;
2794
- dateRange?: 'day' | 'week' | 'month' | 'year' | 'all';
2795
- }) {
2796
- try {
2797
- const {
2798
- query,
2799
- maxResults = 10,
2800
- // region = 'us',
2801
- // language = 'en',
2802
- // includeImages = false,
2803
- // dateRange = 'all',
2804
- } = context;
2805
-
2806
- const startTime = Date.now();
2807
-
2808
- // For now, implement a basic search using DuckDuckGo's instant answer API
2809
- // In a real implementation, you'd want to use a proper search API
2810
- const searchUrl = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_redirect=1&skip_disambig=1`;
2811
-
2812
- const response = await fetch(searchUrl);
2813
- const data: any = await response.json();
2814
-
2815
- const results: Array<{
2816
- title: string;
2817
- url: string;
2818
- snippet: string;
2819
- domain: string;
2820
- publishDate?: string;
2821
- relevanceScore?: number;
2822
- }> = [];
2823
-
2824
- // Parse DuckDuckGo results
2825
- if (data.RelatedTopics && Array.isArray(data.RelatedTopics)) {
2826
- for (const topic of data.RelatedTopics.slice(0, maxResults)) {
2827
- if (topic.FirstURL && topic.Text) {
2828
- const url = new URL(topic.FirstURL);
2829
- results.push({
2830
- title: topic.Text.split(' - ')[0] || topic.Text.substring(0, 60),
2831
- url: topic.FirstURL,
2832
- snippet: topic.Text,
2833
- domain: url.hostname,
2834
- relevanceScore: Math.random() * 100, // Placeholder scoring
2835
- });
2836
- }
2837
- }
2838
- }
2839
-
2840
- // Add abstract as first result if available
2841
- if (data.Abstract && data.AbstractURL) {
2842
- const url = new URL(data.AbstractURL);
2843
- results.unshift({
2844
- title: data.Heading || 'Main Result',
2845
- url: data.AbstractURL,
2846
- snippet: data.Abstract,
2847
- domain: url.hostname,
2848
- relevanceScore: 100,
2849
- });
2850
- }
2851
-
2852
- const searchTime = Date.now() - startTime;
2853
-
2854
- return {
2855
- success: true,
2856
- query,
2857
- results: results.slice(0, maxResults),
2858
- totalResults: results.length,
2859
- searchTime,
2860
- suggestions:
2861
- data.RelatedTopics?.slice(maxResults, maxResults + 3)
2862
- ?.map((t: any) => t.Text?.split(' - ')[0] || t.Text?.substring(0, 30))
2863
- .filter(Boolean) || [],
2864
- };
2865
- } catch (error) {
2866
- return {
2867
- success: false,
2868
- query: context.query,
2869
- results: [],
2870
- totalResults: 0,
2871
- searchTime: 0,
2872
- error: error instanceof Error ? error.message : String(error),
2873
- };
2874
- }
2875
- }
2876
- }