@matimo/core 0.1.0-alpha.10

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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/destructive-keywords.yaml +70 -0
  4. package/dist/approval/approval-handler.d.ts +74 -0
  5. package/dist/approval/approval-handler.d.ts.map +1 -0
  6. package/dist/approval/approval-handler.js +210 -0
  7. package/dist/approval/approval-handler.js.map +1 -0
  8. package/dist/auth/oauth2-config.d.ts +104 -0
  9. package/dist/auth/oauth2-config.d.ts.map +1 -0
  10. package/dist/auth/oauth2-config.js +38 -0
  11. package/dist/auth/oauth2-config.js.map +1 -0
  12. package/dist/auth/oauth2-handler.d.ts +130 -0
  13. package/dist/auth/oauth2-handler.d.ts.map +1 -0
  14. package/dist/auth/oauth2-handler.js +265 -0
  15. package/dist/auth/oauth2-handler.js.map +1 -0
  16. package/dist/auth/oauth2-provider-loader.d.ts +68 -0
  17. package/dist/auth/oauth2-provider-loader.d.ts.map +1 -0
  18. package/dist/auth/oauth2-provider-loader.js +120 -0
  19. package/dist/auth/oauth2-provider-loader.js.map +1 -0
  20. package/dist/core/schema.d.ts +259 -0
  21. package/dist/core/schema.d.ts.map +1 -0
  22. package/dist/core/schema.js +187 -0
  23. package/dist/core/schema.js.map +1 -0
  24. package/dist/core/tool-loader.d.ts +57 -0
  25. package/dist/core/tool-loader.d.ts.map +1 -0
  26. package/dist/core/tool-loader.js +250 -0
  27. package/dist/core/tool-loader.js.map +1 -0
  28. package/dist/core/tool-registry.d.ts +48 -0
  29. package/dist/core/tool-registry.d.ts.map +1 -0
  30. package/dist/core/tool-registry.js +93 -0
  31. package/dist/core/tool-registry.js.map +1 -0
  32. package/dist/core/types.d.ts +162 -0
  33. package/dist/core/types.d.ts.map +1 -0
  34. package/dist/core/types.js +5 -0
  35. package/dist/core/types.js.map +1 -0
  36. package/dist/decorators/index.d.ts +2 -0
  37. package/dist/decorators/index.d.ts.map +1 -0
  38. package/dist/decorators/index.js +2 -0
  39. package/dist/decorators/index.js.map +1 -0
  40. package/dist/decorators/tool-decorator.d.ts +97 -0
  41. package/dist/decorators/tool-decorator.d.ts.map +1 -0
  42. package/dist/decorators/tool-decorator.js +157 -0
  43. package/dist/decorators/tool-decorator.js.map +1 -0
  44. package/dist/encodings/parameter-encoding.d.ts +51 -0
  45. package/dist/encodings/parameter-encoding.d.ts.map +1 -0
  46. package/dist/encodings/parameter-encoding.js +124 -0
  47. package/dist/encodings/parameter-encoding.js.map +1 -0
  48. package/dist/errors/matimo-error.d.ts +41 -0
  49. package/dist/errors/matimo-error.d.ts.map +1 -0
  50. package/dist/errors/matimo-error.js +71 -0
  51. package/dist/errors/matimo-error.js.map +1 -0
  52. package/dist/executors/command-executor.d.ts +19 -0
  53. package/dist/executors/command-executor.d.ts.map +1 -0
  54. package/dist/executors/command-executor.js +98 -0
  55. package/dist/executors/command-executor.js.map +1 -0
  56. package/dist/executors/function-executor.d.ts +23 -0
  57. package/dist/executors/function-executor.d.ts.map +1 -0
  58. package/dist/executors/function-executor.js +181 -0
  59. package/dist/executors/function-executor.js.map +1 -0
  60. package/dist/executors/http-executor.d.ts +78 -0
  61. package/dist/executors/http-executor.d.ts.map +1 -0
  62. package/dist/executors/http-executor.js +279 -0
  63. package/dist/executors/http-executor.js.map +1 -0
  64. package/dist/index.d.ts +30 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +33 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/integrations/langchain.d.ts +46 -0
  69. package/dist/integrations/langchain.d.ts.map +1 -0
  70. package/dist/integrations/langchain.js +197 -0
  71. package/dist/integrations/langchain.js.map +1 -0
  72. package/dist/logging/index.d.ts +3 -0
  73. package/dist/logging/index.d.ts.map +1 -0
  74. package/dist/logging/index.js +3 -0
  75. package/dist/logging/index.js.map +1 -0
  76. package/dist/logging/logger.d.ts +96 -0
  77. package/dist/logging/logger.d.ts.map +1 -0
  78. package/dist/logging/logger.js +53 -0
  79. package/dist/logging/logger.js.map +1 -0
  80. package/dist/logging/winston-logger.d.ts +29 -0
  81. package/dist/logging/winston-logger.d.ts.map +1 -0
  82. package/dist/logging/winston-logger.js +73 -0
  83. package/dist/logging/winston-logger.js.map +1 -0
  84. package/dist/matimo-instance.d.ts +140 -0
  85. package/dist/matimo-instance.d.ts.map +1 -0
  86. package/dist/matimo-instance.js +412 -0
  87. package/dist/matimo-instance.js.map +1 -0
  88. package/package.json +96 -0
  89. package/tools/calculator/calculator.ts +145 -0
  90. package/tools/calculator/definition.yaml +70 -0
  91. package/tools/edit/definition.yaml +115 -0
  92. package/tools/edit/edit.ts +187 -0
  93. package/tools/execute/definition.yaml +90 -0
  94. package/tools/execute/execute.ts +207 -0
  95. package/tools/read/definition.yaml +106 -0
  96. package/tools/read/read.ts +118 -0
  97. package/tools/search/definition.yaml +148 -0
  98. package/tools/search/search.ts +192 -0
  99. package/tools/web/definition.yaml +132 -0
  100. package/tools/web/web.ts +134 -0
@@ -0,0 +1,70 @@
1
+ name: calculator
2
+ version: '1.0.0'
3
+ description: 'Perform basic arithmetic operations'
4
+
5
+ parameters:
6
+ operation:
7
+ type: string
8
+ description: 'The operation to perform. Accepts: add (addition, sum, plus), subtract (subtraction, minus), multiply (multiplication, times, product), divide (division)'
9
+ required: true
10
+ a:
11
+ type: number
12
+ description: 'First operand'
13
+ required: true
14
+ b:
15
+ type: number
16
+ description: 'Second operand'
17
+ required: true
18
+
19
+ execution:
20
+ type: function
21
+ code: './calculator.ts'
22
+
23
+ output_schema:
24
+ type: object
25
+ properties:
26
+ result:
27
+ type: number
28
+ description: 'Result of the operation'
29
+ operation:
30
+ type: string
31
+ operands:
32
+ type: object
33
+ properties:
34
+ a:
35
+ type: number
36
+ b:
37
+ type: number
38
+
39
+ error_handling:
40
+ retry: 2
41
+ backoff_type: exponential
42
+ initial_delay_ms: 500
43
+
44
+ examples:
45
+ - name: 'Simple addition'
46
+ description: 'Add 5 and 3'
47
+ params:
48
+ operation: 'add'
49
+ a: 5
50
+ b: 3
51
+ - name: 'Addition with variant'
52
+ description: "Add using 'addition' keyword"
53
+ params:
54
+ operation: 'addition'
55
+ a: 10
56
+ b: 20
57
+ - name: 'Subtraction'
58
+ description: 'Subtract 3 from 10'
59
+ params:
60
+ operation: 'subtract'
61
+ a: 10
62
+ b: 3
63
+ - name: 'Multiplication'
64
+ description: 'Multiply 4 and 7'
65
+ params:
66
+ operation: 'multiply'
67
+ a: 4
68
+ b: 7
69
+
70
+ tags: [math, arithmetic, basic]
@@ -0,0 +1,115 @@
1
+ name: edit
2
+ version: '1.0.0'
3
+ description: Edit file contents with precise line-based insertion, replacement, and deletion. Supports undo/backup creation.
4
+ requires_approval: true
5
+
6
+ parameters:
7
+ filePath:
8
+ type: string
9
+ description: Absolute or relative path to file to edit (supports ~ for home directory)
10
+ required: true
11
+ example: './src/config.ts'
12
+
13
+ operation:
14
+ type: string
15
+ description: 'Edit operation: insert, replace, delete, append'
16
+ required: true
17
+ example: 'replace'
18
+
19
+ content:
20
+ type: string
21
+ description: Content to insert or use for replacement (required for insert/replace/append, forbidden for delete)
22
+ required: false
23
+ example: 'const value = 42;'
24
+
25
+ startLine:
26
+ type: number
27
+ description: Starting line number (1-based). For insert, line to insert before. For replace/delete, starting line.
28
+ required: true
29
+ example: 10
30
+
31
+ endLine:
32
+ type: number
33
+ description: Ending line number (1-based, inclusive) for replace/delete operations. Not used for insert/append.
34
+ required: false
35
+ example: 15
36
+
37
+ backup:
38
+ type: boolean
39
+ description: Create backup file before editing (default true). Backup saved as {filename}.backup
40
+ required: false
41
+ default: true
42
+
43
+ execution:
44
+ type: function
45
+ code: './edit.ts'
46
+
47
+ output_schema:
48
+ type: object
49
+ properties:
50
+ success:
51
+ type: boolean
52
+ description: Whether edit operation succeeded
53
+ filePath:
54
+ type: string
55
+ description: Path to the edited file
56
+ operation:
57
+ type: string
58
+ description: Operation that was performed
59
+ linesAffected:
60
+ type: number
61
+ description: Number of lines modified
62
+ backupCreated:
63
+ type: boolean
64
+ description: Whether backup file was created
65
+ backupPath:
66
+ type: string
67
+ description: Path to backup file if created
68
+ previousContent:
69
+ type: string
70
+ description: Content before edit (for delete/replace operations)
71
+ newLineCount:
72
+ type: number
73
+ description: Total line count after edit
74
+ duration:
75
+ type: number
76
+ description: Edit operation duration in milliseconds
77
+ required: [success, filePath, operation, linesAffected]
78
+
79
+ error_handling:
80
+ retry: 0
81
+ backoff_type: exponential
82
+ initial_delay_ms: 300
83
+
84
+ examples:
85
+ - name: 'Replace lines'
86
+ description: 'Replace lines 10-12 with new content'
87
+ params:
88
+ filePath: './src/config.ts'
89
+ operation: 'replace'
90
+ startLine: 10
91
+ endLine: 12
92
+ content: 'const newValue = 99;'
93
+ - name: 'Insert content'
94
+ description: 'Insert new line before line 5'
95
+ params:
96
+ filePath: './src/app.ts'
97
+ operation: 'insert'
98
+ startLine: 5
99
+ content: '// New comment\nconst x = 10;'
100
+ - name: 'Delete lines'
101
+ description: 'Delete lines 20-25'
102
+ params:
103
+ filePath: './src/app.ts'
104
+ operation: 'delete'
105
+ startLine: 20
106
+ endLine: 25
107
+ - name: 'Append to file'
108
+ description: 'Add content at end of file'
109
+ params:
110
+ filePath: './data/log.txt'
111
+ operation: 'append'
112
+ startLine: 1
113
+ content: 'New log entry'
114
+
115
+ tags: [file-io, edit, write, filesystem, text-manipulation]
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Edit Tool - Edit files with insert/replace/delete/append operations
4
+ * Function-type tool: Exports default async function
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { MatimoError, ErrorCode } from '@matimo/core';
10
+
11
+ interface EditParams {
12
+ filePath: string;
13
+ operation: string;
14
+ content?: string;
15
+ startLine: number;
16
+ endLine?: number;
17
+ backup?: boolean;
18
+ }
19
+
20
+ interface EditResult {
21
+ success: boolean;
22
+ filePath: string;
23
+ operation: string;
24
+ linesAffected: number;
25
+ backupCreated: boolean;
26
+ backupPath?: string;
27
+ previousContent?: string;
28
+ newLineCount: number;
29
+ duration: number;
30
+ }
31
+
32
+ /**
33
+ * Edit file with insert/replace/delete/append operations
34
+ */
35
+ export default async function editTool(params: EditParams): Promise<EditResult> {
36
+ const { filePath, operation, content = '', startLine, endLine, backup = true } = params;
37
+ const startTime = Date.now();
38
+
39
+ // Validate required parameters
40
+ if (!filePath) {
41
+ throw new MatimoError('Missing required parameter', ErrorCode.INVALID_PARAMETER, {
42
+ reason: 'filePath is required',
43
+ });
44
+ }
45
+
46
+ if (!operation) {
47
+ throw new MatimoError('Missing required parameter', ErrorCode.INVALID_PARAMETER, {
48
+ reason: 'operation is required',
49
+ });
50
+ }
51
+
52
+ if (startLine === undefined) {
53
+ throw new MatimoError('Missing required parameter', ErrorCode.INVALID_PARAMETER, {
54
+ reason: 'startLine is required',
55
+ });
56
+ }
57
+
58
+ // Resolve path
59
+ const resolvedPath = filePath.startsWith('~')
60
+ ? path.join(process.env.HOME || '/', filePath.slice(1))
61
+ : path.isAbsolute(filePath)
62
+ ? filePath
63
+ : path.resolve(process.cwd(), filePath);
64
+
65
+ // Validate file exists
66
+ if (!fs.existsSync(resolvedPath)) {
67
+ throw new MatimoError('File not found', ErrorCode.FILE_NOT_FOUND, {
68
+ filePath: resolvedPath,
69
+ });
70
+ }
71
+
72
+ // Approval is handled by MatimoInstance based on 'requires_approval' in YAML
73
+
74
+ // Read original content
75
+ const originalContent = fs.readFileSync(resolvedPath, 'utf8');
76
+ const lines = originalContent.split('\n');
77
+
78
+ const result: EditResult = {
79
+ success: false,
80
+ filePath: resolvedPath,
81
+ operation,
82
+ linesAffected: 0,
83
+ backupCreated: false,
84
+ newLineCount: 0,
85
+ duration: 0,
86
+ };
87
+
88
+ const newLines: string[] = [...lines];
89
+ let backupPath: string | undefined;
90
+ let previousContent: string | undefined;
91
+
92
+ // Create backup if requested
93
+ if (backup) {
94
+ backupPath = `${resolvedPath}.backup`;
95
+ fs.writeFileSync(backupPath, originalContent, 'utf8');
96
+ result.backupCreated = true;
97
+ result.backupPath = backupPath;
98
+ }
99
+
100
+ // Validate line numbers (1-based)
101
+ if (startLine < 1) {
102
+ throw new MatimoError('Invalid line number', ErrorCode.INVALID_PARAMETER, {
103
+ startLine,
104
+ reason: 'startLine must be >= 1',
105
+ });
106
+ }
107
+
108
+ // Convert to 0-based indexing
109
+ const startIdx = startLine - 1;
110
+ const endIdx = endLine ? endLine - 1 : startIdx;
111
+
112
+ switch (operation) {
113
+ case 'insert': {
114
+ // Insert before startLine
115
+ if (startIdx > newLines.length) {
116
+ throw new MatimoError('Invalid line range', ErrorCode.INVALID_PARAMETER, {
117
+ startLine,
118
+ reason: `startLine ${startLine} is beyond file length ${newLines.length}`,
119
+ });
120
+ }
121
+ const contentLines = content.split('\n');
122
+ newLines.splice(startIdx, 0, ...contentLines);
123
+ result.linesAffected = contentLines.length;
124
+ break;
125
+ }
126
+
127
+ case 'replace': {
128
+ // Replace lines from startLine to endLine
129
+ if (startIdx >= newLines.length || endIdx >= newLines.length) {
130
+ throw new MatimoError('Invalid line range', ErrorCode.INVALID_PARAMETER, {
131
+ startLine,
132
+ endLine,
133
+ fileLineCount: newLines.length,
134
+ reason: `Line range ${startLine}-${endLine} out of bounds`,
135
+ });
136
+ }
137
+ previousContent = newLines.slice(startIdx, endIdx + 1).join('\n');
138
+ const contentLines = content.split('\n');
139
+ newLines.splice(startIdx, endIdx - startIdx + 1, ...contentLines);
140
+ result.linesAffected = endIdx - startIdx + 1;
141
+ result.previousContent = previousContent;
142
+ break;
143
+ }
144
+
145
+ case 'delete': {
146
+ // Delete lines from startLine to endLine
147
+ if (startIdx >= newLines.length || endIdx >= newLines.length) {
148
+ throw new MatimoError('Invalid line range', ErrorCode.INVALID_PARAMETER, {
149
+ startLine,
150
+ endLine,
151
+ fileLineCount: newLines.length,
152
+ reason: `Line range ${startLine}-${endLine} out of bounds`,
153
+ });
154
+ }
155
+ previousContent = newLines.slice(startIdx, endIdx + 1).join('\n');
156
+ newLines.splice(startIdx, endIdx - startIdx + 1);
157
+ result.linesAffected = endIdx - startIdx + 1;
158
+ result.previousContent = previousContent;
159
+ break;
160
+ }
161
+
162
+ case 'append': {
163
+ // Append to end of file
164
+ const contentLines = content.split('\n');
165
+ newLines.push(...contentLines);
166
+ result.linesAffected = contentLines.length;
167
+ break;
168
+ }
169
+
170
+ default: {
171
+ throw new MatimoError('Unknown operation', ErrorCode.INVALID_PARAMETER, {
172
+ operation,
173
+ supportedOperations: ['insert', 'replace', 'delete', 'append'],
174
+ });
175
+ }
176
+ }
177
+
178
+ // Write new content
179
+ const newContent = newLines.join('\n');
180
+ fs.writeFileSync(resolvedPath, newContent, 'utf8');
181
+
182
+ result.success = true;
183
+ result.newLineCount = newLines.length;
184
+ result.duration = Date.now() - startTime;
185
+
186
+ return result;
187
+ }
@@ -0,0 +1,90 @@
1
+ name: execute
2
+ version: '1.0.0'
3
+ description: Execute shell commands and capture output. Supports command execution with arguments and streaming output capture.
4
+ requires_approval: true
5
+
6
+ parameters:
7
+ command:
8
+ type: string
9
+ description: Shell command to execute (e.g., 'npm test', 'git status', 'ls -la')
10
+ required: true
11
+ example: 'npm test'
12
+
13
+ args:
14
+ type: array
15
+ description: Command arguments as array of strings (optional, can also embed in command)
16
+ required: false
17
+ example: ['--verbose', '--color']
18
+
19
+ cwd:
20
+ type: string
21
+ description: Working directory context for command execution (relative or absolute path)
22
+ required: false
23
+ example: '/project/src'
24
+
25
+ timeout:
26
+ type: number
27
+ description: Maximum execution time in milliseconds (default 30000)
28
+ required: false
29
+ example: 60000
30
+
31
+ shell:
32
+ type: string
33
+ description: Shell to use (e.g. '/bin/bash', '/bin/zsh'). Defaults to system shell.
34
+ required: false
35
+ example: '/bin/bash'
36
+
37
+ execution:
38
+ type: function
39
+ code: './execute.ts'
40
+
41
+ output_schema:
42
+ type: object
43
+ properties:
44
+ success:
45
+ type: boolean
46
+ description: Whether command executed successfully (exit code 0)
47
+ exitCode:
48
+ type: number
49
+ description: Command exit code
50
+ stdout:
51
+ type: string
52
+ description: Standard output from command execution
53
+ stderr:
54
+ type: string
55
+ description: Standard error from command execution
56
+ command:
57
+ type: string
58
+ description: Command that was executed
59
+ duration:
60
+ type: number
61
+ description: Execution duration in milliseconds
62
+ required: [success, exitCode, stdout, stderr, duration]
63
+
64
+ error_handling:
65
+ retry: 1
66
+ backoff_type: exponential
67
+ initial_delay_ms: 500
68
+
69
+ examples:
70
+ - name: 'Run npm test'
71
+ description: 'Execute npm test in current directory'
72
+ params:
73
+ command: 'npm test'
74
+ - name: 'List files with details'
75
+ description: 'List directory contents'
76
+ params:
77
+ command: 'ls'
78
+ args: ['-la', '/Users']
79
+ - name: 'Git status'
80
+ description: 'Check git repository status'
81
+ params:
82
+ command: 'git status'
83
+ cwd: '/project'
84
+ - name: 'Long running command with timeout'
85
+ description: 'Execute with custom timeout'
86
+ params:
87
+ command: 'sleep 5'
88
+ timeout: 10000
89
+
90
+ tags: [shell, command, system, execution, process]
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Execute Tool - Execute shell commands with full output capture
3
+ * LangChain-style: uses exec() directly from same process
4
+ * Cross-platform: Windows (cmd.exe), Unix/Linux/Mac (sh/bash)
5
+ */
6
+
7
+ import { exec } from 'child_process';
8
+ import { promisify } from 'util';
9
+ import { MatimoError, ErrorCode } from '../../src/errors/matimo-error';
10
+ import { getGlobalMatimoLogger } from '../../src/logging/logger';
11
+ import { getGlobalApprovalHandler } from '../../src/approval/approval-handler';
12
+
13
+ const execAsync = promisify(exec);
14
+
15
+ /**
16
+ * Basic injection detection - checks for common shell metacharacters
17
+ * that could be used for command injection attacks
18
+ */
19
+ function detectCommandInjection(command: string): boolean {
20
+ // Common injection patterns: command chaining, redirection, substitution
21
+ // Note: $\w+ pattern is handled separately below
22
+ const dangerousPatterns = [
23
+ /;/, // Command separator
24
+ /\|/, // Pipe
25
+ /&/, // Background/AND
26
+ /`/, // Command substitution (backticks)
27
+ /\$\(/, // Command substitution ($(command))
28
+ /</, // Input redirection
29
+ />/, // Output redirection
30
+ /\$\{/, // Variable expansion ${VAR}
31
+ ];
32
+
33
+ // Allow some safe variable expansions like $HOME, $PATH, but flag suspicious ones
34
+ const safeVars = /^\$(HOME|PATH|USER|PWD|SHELL|LANG|TERM)$/i;
35
+
36
+ // Check for dangerous patterns first
37
+ for (const pattern of dangerousPatterns) {
38
+ if (pattern.test(command)) {
39
+ return true;
40
+ }
41
+ }
42
+
43
+ // Special handling for environment variables: allow safe ones, flag suspicious ones
44
+ const variablePattern = /\$\w+/g;
45
+ const variables = command.match(variablePattern);
46
+ if (variables) {
47
+ for (const variable of variables) {
48
+ if (!safeVars.test(variable)) {
49
+ return true;
50
+ }
51
+ }
52
+ }
53
+
54
+ return false;
55
+ }
56
+
57
+ interface ExecuteParams {
58
+ command: string;
59
+ cwd?: string;
60
+ timeout?: number;
61
+ }
62
+
63
+ interface ExecuteResult {
64
+ success: boolean;
65
+ exitCode: number;
66
+ stdout: string;
67
+ stderr: string;
68
+ command: string;
69
+ duration: number;
70
+ }
71
+
72
+ /**
73
+ * Execute a shell command and return structured output
74
+ * Pattern based on LangChain.js exec/execSync approach
75
+ */
76
+ export default async function executeCommand(
77
+ params: ExecuteParams
78
+ ): Promise<ExecuteResult> {
79
+ const logger = getGlobalMatimoLogger();
80
+ const { command, cwd, timeout = 30000 } = params;
81
+ const startTime = Date.now();
82
+
83
+ logger.debug('Execute tool: Command received', {
84
+ command: command.substring(0, 100),
85
+ cwd,
86
+ timeout,
87
+ });
88
+
89
+ if (!command || command.trim().length === 0) {
90
+ logger.error('Execute tool: Empty command provided', {
91
+ reason: 'No command provided',
92
+ });
93
+ throw new MatimoError('Command required', ErrorCode.INVALID_PARAMETER, {
94
+ reason: 'No command provided',
95
+ });
96
+ }
97
+
98
+ // Check for potential command injection
99
+ if (detectCommandInjection(command)) {
100
+ logger.warn('Execute tool: Command injection detected', {
101
+ command: command.substring(0, 100),
102
+ reason: 'Contains potentially dangerous shell metacharacters',
103
+ });
104
+ throw new MatimoError('Command injection detected', ErrorCode.INVALID_PARAMETER, {
105
+ reason: 'Command contains potentially dangerous shell metacharacters',
106
+ command: command,
107
+ });
108
+ }
109
+
110
+ // Check if command appears to be destructive and request approval if needed
111
+ // ApprovalHandler checks against centralized destructive keywords from YAML
112
+ const approvalHandler = getGlobalApprovalHandler();
113
+
114
+ if (approvalHandler.requiresApproval(false, command)) {
115
+ logger.info('Execute tool: Destructive command detected - requesting approval', {
116
+ command: command.substring(0, 100),
117
+ });
118
+
119
+ // Request user approval before executing destructive command
120
+ if (!approvalHandler.isPreApproved('execute')) {
121
+ await approvalHandler.requestApproval({
122
+ toolName: 'execute',
123
+ description: `Execute shell command: ${command.substring(0, 100)}${command.length > 100 ? '...' : ''}`,
124
+ params: { command, cwd },
125
+ });
126
+ }
127
+ }
128
+
129
+ try {
130
+ // SECURITY WARNING: This tool executes arbitrary shell commands directly.
131
+ // The 'command' parameter is passed to exec() without sanitization, creating
132
+ // a command injection vulnerability if user input is not properly validated.
133
+ // Basic injection detection is performed above, but this is NOT foolproof.
134
+ // Only use with trusted input or implement additional validation layers.
135
+ // exec() auto-selects shell: cmd.exe on Windows, /bin/sh on Unix
136
+ const { stdout, stderr } = await execAsync(command, {
137
+ cwd,
138
+ timeout,
139
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
140
+ });
141
+
142
+ const duration = Date.now() - startTime;
143
+
144
+ // Convert Buffer to string if needed
145
+ const stdoutStr = typeof stdout === 'string' ? stdout : (stdout as unknown ? String(stdout) : '');
146
+ const stderrStr = typeof stderr === 'string' ? stderr : (stderr as unknown ? String(stderr) : '');
147
+
148
+ logger.info('Execute tool: Command completed successfully', {
149
+ command: command.substring(0, 100),
150
+ duration,
151
+ stdoutLength: stdoutStr.length,
152
+ stderrLength: stderrStr.length,
153
+ });
154
+
155
+ return {
156
+ success: true,
157
+ exitCode: 0,
158
+ stdout: stdoutStr.trim(),
159
+ stderr: stderrStr.trim(),
160
+ command,
161
+ duration,
162
+ };
163
+ } catch (error: unknown) {
164
+ const duration = Date.now() - startTime;
165
+
166
+ // Type guard for error object
167
+ const errorObj = error as Record<string, unknown> & {
168
+ killed?: boolean;
169
+ signal?: string;
170
+ code?: number;
171
+ stdout?: string;
172
+ stderr?: string;
173
+ message?: string;
174
+ };
175
+
176
+ const isTimeout = errorObj.killed || errorObj.signal === 'SIGTERM';
177
+
178
+ // If it's already a MatimoError, re-throw it
179
+ if (error instanceof MatimoError) {
180
+ throw error;
181
+ }
182
+
183
+ // Convert Buffer to string if needed
184
+ const stdoutStr = typeof errorObj.stdout === 'string' ? errorObj.stdout : (errorObj.stdout ? String(errorObj.stdout) : '');
185
+ const stderrStr = typeof errorObj.stderr === 'string' ? errorObj.stderr : (errorObj.stderr ? String(errorObj.stderr) : '');
186
+
187
+ logger.warn('Execute tool: Command execution failed', {
188
+ command: command.substring(0, 100),
189
+ duration,
190
+ exitCode: isTimeout ? -1 : (errorObj.code || 1),
191
+ isTimeout,
192
+ errorMessage: errorObj.message ? errorObj.message.substring(0, 100) : 'Unknown error',
193
+ stderrLength: stderrStr.length,
194
+ });
195
+
196
+ // For command execution failures, return structured result (not throw)
197
+ // This allows the agent to see what went wrong
198
+ return {
199
+ success: false,
200
+ exitCode: isTimeout ? -1 : (errorObj.code || 1),
201
+ stdout: stdoutStr.trim(),
202
+ stderr: stderrStr.trim(),
203
+ command,
204
+ duration,
205
+ };
206
+ }
207
+ }