@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.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/destructive-keywords.yaml +70 -0
- package/dist/approval/approval-handler.d.ts +74 -0
- package/dist/approval/approval-handler.d.ts.map +1 -0
- package/dist/approval/approval-handler.js +210 -0
- package/dist/approval/approval-handler.js.map +1 -0
- package/dist/auth/oauth2-config.d.ts +104 -0
- package/dist/auth/oauth2-config.d.ts.map +1 -0
- package/dist/auth/oauth2-config.js +38 -0
- package/dist/auth/oauth2-config.js.map +1 -0
- package/dist/auth/oauth2-handler.d.ts +130 -0
- package/dist/auth/oauth2-handler.d.ts.map +1 -0
- package/dist/auth/oauth2-handler.js +265 -0
- package/dist/auth/oauth2-handler.js.map +1 -0
- package/dist/auth/oauth2-provider-loader.d.ts +68 -0
- package/dist/auth/oauth2-provider-loader.d.ts.map +1 -0
- package/dist/auth/oauth2-provider-loader.js +120 -0
- package/dist/auth/oauth2-provider-loader.js.map +1 -0
- package/dist/core/schema.d.ts +259 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +187 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/tool-loader.d.ts +57 -0
- package/dist/core/tool-loader.d.ts.map +1 -0
- package/dist/core/tool-loader.js +250 -0
- package/dist/core/tool-loader.js.map +1 -0
- package/dist/core/tool-registry.d.ts +48 -0
- package/dist/core/tool-registry.d.ts.map +1 -0
- package/dist/core/tool-registry.js +93 -0
- package/dist/core/tool-registry.js.map +1 -0
- package/dist/core/types.d.ts +162 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/tool-decorator.d.ts +97 -0
- package/dist/decorators/tool-decorator.d.ts.map +1 -0
- package/dist/decorators/tool-decorator.js +157 -0
- package/dist/decorators/tool-decorator.js.map +1 -0
- package/dist/encodings/parameter-encoding.d.ts +51 -0
- package/dist/encodings/parameter-encoding.d.ts.map +1 -0
- package/dist/encodings/parameter-encoding.js +124 -0
- package/dist/encodings/parameter-encoding.js.map +1 -0
- package/dist/errors/matimo-error.d.ts +41 -0
- package/dist/errors/matimo-error.d.ts.map +1 -0
- package/dist/errors/matimo-error.js +71 -0
- package/dist/errors/matimo-error.js.map +1 -0
- package/dist/executors/command-executor.d.ts +19 -0
- package/dist/executors/command-executor.d.ts.map +1 -0
- package/dist/executors/command-executor.js +98 -0
- package/dist/executors/command-executor.js.map +1 -0
- package/dist/executors/function-executor.d.ts +23 -0
- package/dist/executors/function-executor.d.ts.map +1 -0
- package/dist/executors/function-executor.js +181 -0
- package/dist/executors/function-executor.js.map +1 -0
- package/dist/executors/http-executor.d.ts +78 -0
- package/dist/executors/http-executor.d.ts.map +1 -0
- package/dist/executors/http-executor.js +279 -0
- package/dist/executors/http-executor.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/langchain.d.ts +46 -0
- package/dist/integrations/langchain.d.ts.map +1 -0
- package/dist/integrations/langchain.js +197 -0
- package/dist/integrations/langchain.js.map +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +96 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +53 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/winston-logger.d.ts +29 -0
- package/dist/logging/winston-logger.d.ts.map +1 -0
- package/dist/logging/winston-logger.js +73 -0
- package/dist/logging/winston-logger.js.map +1 -0
- package/dist/matimo-instance.d.ts +140 -0
- package/dist/matimo-instance.d.ts.map +1 -0
- package/dist/matimo-instance.js +412 -0
- package/dist/matimo-instance.js.map +1 -0
- package/package.json +96 -0
- package/tools/calculator/calculator.ts +145 -0
- package/tools/calculator/definition.yaml +70 -0
- package/tools/edit/definition.yaml +115 -0
- package/tools/edit/edit.ts +187 -0
- package/tools/execute/definition.yaml +90 -0
- package/tools/execute/execute.ts +207 -0
- package/tools/read/definition.yaml +106 -0
- package/tools/read/read.ts +118 -0
- package/tools/search/definition.yaml +148 -0
- package/tools/search/search.ts +192 -0
- package/tools/web/definition.yaml +132 -0
- 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
|
+
}
|