@matimo/core 0.1.2 → 0.1.4
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/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/package.json +7 -2
- package/tools/calculator/calculator.js +111 -0
- package/tools/calculator/calculator.ts +1 -2
- package/tools/calculator/definition.yaml +1 -1
- package/tools/edit/definition.yaml +1 -1
- package/tools/edit/edit.js +144 -0
- package/tools/execute/definition.yaml +1 -1
- package/tools/execute/execute.js +157 -0
- package/tools/execute/execute.ts +6 -3
- package/tools/matimo_approve_tool/definition.yaml +1 -1
- package/tools/matimo_approve_tool/matimo_approve_tool.js +54 -0
- package/tools/matimo_create_skill/definition.yaml +1 -1
- package/tools/matimo_create_skill/matimo_create_skill.js +48 -0
- package/tools/matimo_create_skill/matimo_create_skill.ts +1 -1
- package/tools/matimo_create_tool/definition.yaml +1 -1
- package/tools/matimo_create_tool/matimo_create_tool.js +89 -0
- package/tools/matimo_get_skill/definition.yaml +1 -1
- package/tools/matimo_get_skill/matimo_get_skill.js +148 -0
- package/tools/matimo_get_skill/matimo_get_skill.ts +8 -1
- package/tools/matimo_get_tool/definition.yaml +1 -1
- package/tools/matimo_get_tool/matimo_get_tool.js +38 -0
- package/tools/matimo_get_tool_status/definition.yaml +1 -1
- package/tools/matimo_get_tool_status/matimo_get_tool_status.js +68 -0
- package/tools/matimo_list_skills/definition.yaml +1 -1
- package/tools/matimo_list_skills/matimo_list_skills.js +109 -0
- package/tools/matimo_list_user_tools/definition.yaml +1 -1
- package/tools/matimo_list_user_tools/matimo_list_user_tools.js +44 -0
- package/tools/matimo_reload_tools/definition.yaml +1 -1
- package/tools/matimo_reload_tools/matimo_reload_tools.js +21 -0
- package/tools/matimo_search_tools/definition.yaml +1 -1
- package/tools/matimo_search_tools/matimo_search_tools.js +59 -0
- package/tools/matimo_validate_skill/definition.yaml +1 -1
- package/tools/matimo_validate_skill/matimo_validate_skill.js +94 -0
- package/tools/matimo_validate_skill/matimo_validate_skill.ts +14 -3
- package/tools/matimo_validate_tool/definition.yaml +1 -1
- package/tools/matimo_validate_tool/matimo_validate_tool.js +134 -0
- package/tools/read/definition.yaml +1 -1
- package/tools/read/read.js +82 -0
- package/tools/search/definition.yaml +1 -1
- package/tools/search/search.js +140 -0
- package/tools/search/search.ts +14 -4
- package/tools/shared/skill-validation.js +251 -0
- package/tools/web/definition.yaml +1 -1
- package/tools/web/web.js +90 -0
- package/tools/web/web.ts +4 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-safe public surface for built-in tool files.
|
|
3
|
+
*
|
|
4
|
+
* Keep this entrypoint intentionally narrow to avoid coupling tools to the
|
|
5
|
+
* full package barrel and to reduce circular dependency risk.
|
|
6
|
+
*/
|
|
7
|
+
export { MatimoError, ErrorCode } from '../errors/matimo-error.js';
|
|
8
|
+
export { getGlobalMatimoLogger } from '../logging/index.js';
|
|
9
|
+
export { getGlobalApprovalHandler } from '../approval/approval-handler.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-safe public surface for built-in tool files.
|
|
3
|
+
*
|
|
4
|
+
* Keep this entrypoint intentionally narrow to avoid coupling tools to the
|
|
5
|
+
* full package barrel and to reduce circular dependency risk.
|
|
6
|
+
*/
|
|
7
|
+
export { MatimoError, ErrorCode } from '../errors/matimo-error.js';
|
|
8
|
+
export { getGlobalMatimoLogger } from '../logging/index.js';
|
|
9
|
+
export { getGlobalApprovalHandler } from '../approval/approval-handler.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matimo/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Core SDK for Matimo: Framework-agnostic YAML-driven tool ecosystem for AI agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./runtime": {
|
|
15
|
+
"types": "./dist/runtime/index.d.ts",
|
|
16
|
+
"default": "./dist/runtime/index.js"
|
|
17
|
+
},
|
|
14
18
|
"./mcp": {
|
|
15
19
|
"types": "./dist/mcp/index.d.ts",
|
|
16
20
|
"default": "./dist/mcp/index.js"
|
|
@@ -61,6 +65,7 @@
|
|
|
61
65
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
62
66
|
"axios": "^1.15.2",
|
|
63
67
|
"dotenv": "^17.2.3",
|
|
68
|
+
"glob": "^10.5.0",
|
|
64
69
|
"js-yaml": "^4.1.1",
|
|
65
70
|
"winston": "^3.19.0",
|
|
66
71
|
"yaml": "^2.8.2",
|
|
@@ -98,7 +103,7 @@
|
|
|
98
103
|
"typescript": "^5.9.3"
|
|
99
104
|
},
|
|
100
105
|
"scripts": {
|
|
101
|
-
"build": "tsc",
|
|
106
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.tools.json",
|
|
102
107
|
"watch": "tsc --watch",
|
|
103
108
|
"test": "jest",
|
|
104
109
|
"test:watch": "jest --watch",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculator Tool - Perform basic arithmetic operations
|
|
3
|
+
* Pattern: Function-based tool (same as execute)
|
|
4
|
+
*/
|
|
5
|
+
import { MatimoError, ErrorCode, getGlobalMatimoLogger } from '@matimo/core/runtime';
|
|
6
|
+
/**
|
|
7
|
+
* Normalize operation name to handle variations
|
|
8
|
+
*/
|
|
9
|
+
function normalizeOperation(op) {
|
|
10
|
+
const normalized = op.toLowerCase().trim();
|
|
11
|
+
// Map variations to canonical operation names
|
|
12
|
+
const operationMap = {
|
|
13
|
+
// Addition variants
|
|
14
|
+
add: 'add',
|
|
15
|
+
addition: 'add',
|
|
16
|
+
sum: 'add',
|
|
17
|
+
plus: 'add',
|
|
18
|
+
'+': 'add',
|
|
19
|
+
// Subtraction variants
|
|
20
|
+
subtract: 'subtract',
|
|
21
|
+
subtraction: 'subtract',
|
|
22
|
+
minus: 'subtract',
|
|
23
|
+
sub: 'subtract',
|
|
24
|
+
'-': 'subtract',
|
|
25
|
+
// Multiplication variants
|
|
26
|
+
multiply: 'multiply',
|
|
27
|
+
multiplication: 'multiply',
|
|
28
|
+
times: 'multiply',
|
|
29
|
+
product: 'multiply',
|
|
30
|
+
mul: 'multiply',
|
|
31
|
+
'*': 'multiply',
|
|
32
|
+
x: 'multiply',
|
|
33
|
+
// Division variants
|
|
34
|
+
divide: 'divide',
|
|
35
|
+
division: 'divide',
|
|
36
|
+
div: 'divide',
|
|
37
|
+
'/': 'divide',
|
|
38
|
+
};
|
|
39
|
+
return operationMap[normalized] || normalized;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Perform arithmetic calculation
|
|
43
|
+
*/
|
|
44
|
+
export default async function calculator(params) {
|
|
45
|
+
const logger = getGlobalMatimoLogger();
|
|
46
|
+
const { operation, a, b } = params;
|
|
47
|
+
logger.debug('Calculator tool invoked', {
|
|
48
|
+
operation,
|
|
49
|
+
a,
|
|
50
|
+
b,
|
|
51
|
+
});
|
|
52
|
+
if (typeof a !== 'number' || typeof b !== 'number') {
|
|
53
|
+
logger.error('Invalid calculator parameters', {
|
|
54
|
+
a,
|
|
55
|
+
b,
|
|
56
|
+
expectedTypes: 'numbers',
|
|
57
|
+
});
|
|
58
|
+
throw new MatimoError('Parameters a and b must be numbers', ErrorCode.INVALID_PARAMETER, {
|
|
59
|
+
a,
|
|
60
|
+
b,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const normalizedOp = normalizeOperation(operation);
|
|
64
|
+
let result;
|
|
65
|
+
switch (normalizedOp) {
|
|
66
|
+
case 'add':
|
|
67
|
+
result = a + b;
|
|
68
|
+
break;
|
|
69
|
+
case 'subtract':
|
|
70
|
+
result = a - b;
|
|
71
|
+
break;
|
|
72
|
+
case 'multiply':
|
|
73
|
+
result = a * b;
|
|
74
|
+
break;
|
|
75
|
+
case 'divide':
|
|
76
|
+
if (b === 0) {
|
|
77
|
+
logger.error('Division by zero attempted', {
|
|
78
|
+
a,
|
|
79
|
+
b,
|
|
80
|
+
});
|
|
81
|
+
throw new MatimoError('Division by zero', ErrorCode.EXECUTION_FAILED, {
|
|
82
|
+
a,
|
|
83
|
+
b,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
result = a / b;
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
logger.error('Unsupported calculator operation', {
|
|
90
|
+
operation: normalizedOp,
|
|
91
|
+
requested: operation,
|
|
92
|
+
});
|
|
93
|
+
throw new MatimoError('Invalid operation', ErrorCode.INVALID_PARAMETER, {
|
|
94
|
+
operation,
|
|
95
|
+
normalizedOperation: normalizedOp,
|
|
96
|
+
validOperations: ['add', 'addition', 'sum', 'plus', 'subtract', 'subtraction', 'minus', 'multiply', 'multiplication', 'times', 'divide', 'division', 'div'],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const returnValue = {
|
|
100
|
+
result,
|
|
101
|
+
operation: normalizedOp,
|
|
102
|
+
original_operation: operation,
|
|
103
|
+
operands: { a, b },
|
|
104
|
+
};
|
|
105
|
+
logger.info('Calculator operation completed', {
|
|
106
|
+
operation: normalizedOp,
|
|
107
|
+
operands: { a, b },
|
|
108
|
+
result,
|
|
109
|
+
});
|
|
110
|
+
return returnValue;
|
|
111
|
+
}
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Pattern: Function-based tool (same as execute)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { MatimoError, ErrorCode } from '
|
|
7
|
-
import { getGlobalMatimoLogger } from '../../src/logging/logger';
|
|
6
|
+
import { MatimoError, ErrorCode, getGlobalMatimoLogger } from '@matimo/core/runtime';
|
|
8
7
|
|
|
9
8
|
interface CalculatorParams {
|
|
10
9
|
operation: string;
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { MatimoError, ErrorCode } from '@matimo/core';
|
|
9
|
+
/**
|
|
10
|
+
* Edit file with insert/replace/delete/append operations
|
|
11
|
+
*/
|
|
12
|
+
export default async function editTool(params) {
|
|
13
|
+
const { filePath, operation, content = '', startLine, endLine, backup = true } = params;
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
// Validate required parameters
|
|
16
|
+
if (!filePath) {
|
|
17
|
+
throw new MatimoError('Missing required parameter', ErrorCode.INVALID_PARAMETER, {
|
|
18
|
+
reason: 'filePath is required',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (!operation) {
|
|
22
|
+
throw new MatimoError('Missing required parameter', ErrorCode.INVALID_PARAMETER, {
|
|
23
|
+
reason: 'operation is required',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (startLine === undefined) {
|
|
27
|
+
throw new MatimoError('Missing required parameter', ErrorCode.INVALID_PARAMETER, {
|
|
28
|
+
reason: 'startLine is required',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// Resolve path
|
|
32
|
+
const resolvedPath = filePath.startsWith('~')
|
|
33
|
+
? path.join(process.env.HOME || '/', filePath.slice(1))
|
|
34
|
+
: path.isAbsolute(filePath)
|
|
35
|
+
? filePath
|
|
36
|
+
: path.resolve(process.cwd(), filePath);
|
|
37
|
+
// Validate file exists
|
|
38
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
39
|
+
throw new MatimoError('File not found', ErrorCode.FILE_NOT_FOUND, {
|
|
40
|
+
filePath: resolvedPath,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Approval is handled by MatimoInstance based on 'requires_approval' in YAML
|
|
44
|
+
// Read original content
|
|
45
|
+
const originalContent = fs.readFileSync(resolvedPath, 'utf8');
|
|
46
|
+
const lines = originalContent.split('\n');
|
|
47
|
+
const result = {
|
|
48
|
+
success: false,
|
|
49
|
+
filePath: resolvedPath,
|
|
50
|
+
operation,
|
|
51
|
+
linesAffected: 0,
|
|
52
|
+
backupCreated: false,
|
|
53
|
+
newLineCount: 0,
|
|
54
|
+
duration: 0,
|
|
55
|
+
};
|
|
56
|
+
const newLines = [...lines];
|
|
57
|
+
let backupPath;
|
|
58
|
+
let previousContent;
|
|
59
|
+
// Create backup if requested
|
|
60
|
+
if (backup) {
|
|
61
|
+
backupPath = `${resolvedPath}.backup`;
|
|
62
|
+
fs.writeFileSync(backupPath, originalContent, 'utf8');
|
|
63
|
+
result.backupCreated = true;
|
|
64
|
+
result.backupPath = backupPath;
|
|
65
|
+
}
|
|
66
|
+
// Validate line numbers (1-based)
|
|
67
|
+
if (startLine < 1) {
|
|
68
|
+
throw new MatimoError('Invalid line number', ErrorCode.INVALID_PARAMETER, {
|
|
69
|
+
startLine,
|
|
70
|
+
reason: 'startLine must be >= 1',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Convert to 0-based indexing
|
|
74
|
+
const startIdx = startLine - 1;
|
|
75
|
+
const endIdx = endLine ? endLine - 1 : startIdx;
|
|
76
|
+
switch (operation) {
|
|
77
|
+
case 'insert': {
|
|
78
|
+
// Insert before startLine
|
|
79
|
+
if (startIdx > newLines.length) {
|
|
80
|
+
throw new MatimoError('Invalid line range', ErrorCode.INVALID_PARAMETER, {
|
|
81
|
+
startLine,
|
|
82
|
+
reason: `startLine ${startLine} is beyond file length ${newLines.length}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const contentLines = content.split('\n');
|
|
86
|
+
newLines.splice(startIdx, 0, ...contentLines);
|
|
87
|
+
result.linesAffected = contentLines.length;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'replace': {
|
|
91
|
+
// Replace lines from startLine to endLine
|
|
92
|
+
if (startIdx >= newLines.length || endIdx >= newLines.length) {
|
|
93
|
+
throw new MatimoError('Invalid line range', ErrorCode.INVALID_PARAMETER, {
|
|
94
|
+
startLine,
|
|
95
|
+
endLine,
|
|
96
|
+
fileLineCount: newLines.length,
|
|
97
|
+
reason: `Line range ${startLine}-${endLine} out of bounds`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
previousContent = newLines.slice(startIdx, endIdx + 1).join('\n');
|
|
101
|
+
const contentLines = content.split('\n');
|
|
102
|
+
newLines.splice(startIdx, endIdx - startIdx + 1, ...contentLines);
|
|
103
|
+
result.linesAffected = endIdx - startIdx + 1;
|
|
104
|
+
result.previousContent = previousContent;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'delete': {
|
|
108
|
+
// Delete lines from startLine to endLine
|
|
109
|
+
if (startIdx >= newLines.length || endIdx >= newLines.length) {
|
|
110
|
+
throw new MatimoError('Invalid line range', ErrorCode.INVALID_PARAMETER, {
|
|
111
|
+
startLine,
|
|
112
|
+
endLine,
|
|
113
|
+
fileLineCount: newLines.length,
|
|
114
|
+
reason: `Line range ${startLine}-${endLine} out of bounds`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
previousContent = newLines.slice(startIdx, endIdx + 1).join('\n');
|
|
118
|
+
newLines.splice(startIdx, endIdx - startIdx + 1);
|
|
119
|
+
result.linesAffected = endIdx - startIdx + 1;
|
|
120
|
+
result.previousContent = previousContent;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'append': {
|
|
124
|
+
// Append to end of file
|
|
125
|
+
const contentLines = content.split('\n');
|
|
126
|
+
newLines.push(...contentLines);
|
|
127
|
+
result.linesAffected = contentLines.length;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
default: {
|
|
131
|
+
throw new MatimoError('Unknown operation', ErrorCode.INVALID_PARAMETER, {
|
|
132
|
+
operation,
|
|
133
|
+
supportedOperations: ['insert', 'replace', 'delete', 'append'],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Write new content
|
|
138
|
+
const newContent = newLines.join('\n');
|
|
139
|
+
fs.writeFileSync(resolvedPath, newContent, 'utf8');
|
|
140
|
+
result.success = true;
|
|
141
|
+
result.newLineCount = newLines.length;
|
|
142
|
+
result.duration = Date.now() - startTime;
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { MatimoError, ErrorCode, getGlobalMatimoLogger, getGlobalApprovalHandler, } from '@matimo/core/runtime';
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
/**
|
|
11
|
+
* Basic injection detection - checks for common shell metacharacters
|
|
12
|
+
* that could be used for command injection attacks
|
|
13
|
+
*/
|
|
14
|
+
function detectCommandInjection(command) {
|
|
15
|
+
// Common injection patterns: command chaining, redirection, substitution
|
|
16
|
+
// Note: $\w+ pattern is handled separately below
|
|
17
|
+
const dangerousPatterns = [
|
|
18
|
+
/;/, // Command separator
|
|
19
|
+
/\|/, // Pipe
|
|
20
|
+
/&/, // Background/AND
|
|
21
|
+
/`/, // Command substitution (backticks)
|
|
22
|
+
/\$\(/, // Command substitution ($(command))
|
|
23
|
+
/</, // Input redirection
|
|
24
|
+
/>/, // Output redirection
|
|
25
|
+
/\$\{/, // Variable expansion ${VAR}
|
|
26
|
+
];
|
|
27
|
+
// Allow some safe variable expansions like $HOME, $PATH, but flag suspicious ones
|
|
28
|
+
const safeVars = /^\$(HOME|PATH|USER|PWD|SHELL|LANG|TERM)$/i;
|
|
29
|
+
// Check for dangerous patterns first
|
|
30
|
+
for (const pattern of dangerousPatterns) {
|
|
31
|
+
if (pattern.test(command)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Special handling for environment variables: allow safe ones, flag suspicious ones
|
|
36
|
+
const variablePattern = /\$\w+/g;
|
|
37
|
+
const variables = command.match(variablePattern);
|
|
38
|
+
if (variables) {
|
|
39
|
+
for (const variable of variables) {
|
|
40
|
+
if (!safeVars.test(variable)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Execute a shell command and return structured output
|
|
49
|
+
* Pattern based on LangChain.js exec/execSync approach
|
|
50
|
+
*/
|
|
51
|
+
export default async function executeCommand(params) {
|
|
52
|
+
const logger = getGlobalMatimoLogger();
|
|
53
|
+
const { command, cwd, timeout = 30000 } = params;
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
logger.debug('Execute tool: Command received', {
|
|
56
|
+
command: command.substring(0, 100),
|
|
57
|
+
cwd,
|
|
58
|
+
timeout,
|
|
59
|
+
});
|
|
60
|
+
if (!command || command.trim().length === 0) {
|
|
61
|
+
logger.error('Execute tool: Empty command provided', {
|
|
62
|
+
reason: 'No command provided',
|
|
63
|
+
});
|
|
64
|
+
throw new MatimoError('Command required', ErrorCode.INVALID_PARAMETER, {
|
|
65
|
+
reason: 'No command provided',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// Check for potential command injection
|
|
69
|
+
if (detectCommandInjection(command)) {
|
|
70
|
+
logger.warn('Execute tool: Command injection detected', {
|
|
71
|
+
command: command.substring(0, 100),
|
|
72
|
+
reason: 'Contains potentially dangerous shell metacharacters',
|
|
73
|
+
});
|
|
74
|
+
throw new MatimoError('Command injection detected', ErrorCode.INVALID_PARAMETER, {
|
|
75
|
+
reason: 'Command contains potentially dangerous shell metacharacters',
|
|
76
|
+
command: command,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Check if command appears to be destructive and request approval if needed
|
|
80
|
+
// ApprovalHandler checks against centralized destructive keywords from YAML
|
|
81
|
+
const approvalHandler = getGlobalApprovalHandler();
|
|
82
|
+
if (approvalHandler.requiresApproval(false, command)) {
|
|
83
|
+
logger.info('Execute tool: Destructive command detected - requesting approval', {
|
|
84
|
+
command: command.substring(0, 100),
|
|
85
|
+
});
|
|
86
|
+
// Request user approval before executing destructive command
|
|
87
|
+
if (!approvalHandler.isPreApproved('execute')) {
|
|
88
|
+
await approvalHandler.requestApproval({
|
|
89
|
+
toolName: 'execute',
|
|
90
|
+
description: `Execute shell command: ${command.substring(0, 100)}${command.length > 100 ? '...' : ''}`,
|
|
91
|
+
params: { command, cwd },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
// SECURITY WARNING: This tool executes arbitrary shell commands directly.
|
|
97
|
+
// The 'command' parameter is passed to exec() without sanitization, creating
|
|
98
|
+
// a command injection vulnerability if user input is not properly validated.
|
|
99
|
+
// Basic injection detection is performed above, but this is NOT foolproof.
|
|
100
|
+
// Only use with trusted input or implement additional validation layers.
|
|
101
|
+
// exec() auto-selects shell: cmd.exe on Windows, /bin/sh on Unix
|
|
102
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
103
|
+
cwd,
|
|
104
|
+
timeout,
|
|
105
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
|
|
106
|
+
});
|
|
107
|
+
const duration = Date.now() - startTime;
|
|
108
|
+
// Convert Buffer to string if needed
|
|
109
|
+
const stdoutStr = typeof stdout === 'string' ? stdout : (stdout ? String(stdout) : '');
|
|
110
|
+
const stderrStr = typeof stderr === 'string' ? stderr : (stderr ? String(stderr) : '');
|
|
111
|
+
logger.info('Execute tool: Command completed successfully', {
|
|
112
|
+
command: command.substring(0, 100),
|
|
113
|
+
duration,
|
|
114
|
+
stdoutLength: stdoutStr.length,
|
|
115
|
+
stderrLength: stderrStr.length,
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
exitCode: 0,
|
|
120
|
+
stdout: stdoutStr.trim(),
|
|
121
|
+
stderr: stderrStr.trim(),
|
|
122
|
+
command,
|
|
123
|
+
duration,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const duration = Date.now() - startTime;
|
|
128
|
+
// Type guard for error object
|
|
129
|
+
const errorObj = error;
|
|
130
|
+
const isTimeout = errorObj.killed || errorObj.signal === 'SIGTERM';
|
|
131
|
+
// If it's already a MatimoError, re-throw it
|
|
132
|
+
if (error instanceof MatimoError) {
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
// Convert Buffer to string if needed
|
|
136
|
+
const stdoutStr = typeof errorObj.stdout === 'string' ? errorObj.stdout : (errorObj.stdout ? String(errorObj.stdout) : '');
|
|
137
|
+
const stderrStr = typeof errorObj.stderr === 'string' ? errorObj.stderr : (errorObj.stderr ? String(errorObj.stderr) : '');
|
|
138
|
+
logger.warn('Execute tool: Command execution failed', {
|
|
139
|
+
command: command.substring(0, 100),
|
|
140
|
+
duration,
|
|
141
|
+
exitCode: isTimeout ? -1 : (errorObj.code || 1),
|
|
142
|
+
isTimeout,
|
|
143
|
+
errorMessage: errorObj.message ? errorObj.message.substring(0, 100) : 'Unknown error',
|
|
144
|
+
stderrLength: stderrStr.length,
|
|
145
|
+
});
|
|
146
|
+
// For command execution failures, return structured result (not throw)
|
|
147
|
+
// This allows the agent to see what went wrong
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
exitCode: isTimeout ? -1 : (errorObj.code || 1),
|
|
151
|
+
stdout: stdoutStr.trim(),
|
|
152
|
+
stderr: stderrStr.trim(),
|
|
153
|
+
command,
|
|
154
|
+
duration,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
package/tools/execute/execute.ts
CHANGED
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import { exec } from 'child_process';
|
|
8
8
|
import { promisify } from 'util';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import {
|
|
10
|
+
MatimoError,
|
|
11
|
+
ErrorCode,
|
|
12
|
+
getGlobalMatimoLogger,
|
|
13
|
+
getGlobalApprovalHandler,
|
|
14
|
+
} from '@matimo/core/runtime';
|
|
12
15
|
|
|
13
16
|
const execAsync = promisify(exec);
|
|
14
17
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { validateToolDefinition, validateToolContent, ApprovalManifest, getGlobalMatimoLogger, } from '@matimo/core';
|
|
5
|
+
export default async function matimoApproveTool(params, context) {
|
|
6
|
+
const logger = getGlobalMatimoLogger();
|
|
7
|
+
const toolDir = params.tool_dir || './matimo-tools';
|
|
8
|
+
// Step 1: Read tool definition
|
|
9
|
+
const defPath = path.join(toolDir, params.name, 'definition.yaml');
|
|
10
|
+
if (!fs.existsSync(defPath)) {
|
|
11
|
+
return { success: false, message: `Tool not found: ${defPath}` };
|
|
12
|
+
}
|
|
13
|
+
const yamlContent = fs.readFileSync(defPath, 'utf-8');
|
|
14
|
+
// Step 2: Parse and validate
|
|
15
|
+
let tool;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = yaml.load(yamlContent);
|
|
18
|
+
tool = validateToolDefinition(parsed);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return { success: false, message: `Validation failed: ${err.message}` };
|
|
22
|
+
}
|
|
23
|
+
// Step 3: Re-run content validator
|
|
24
|
+
const validation = validateToolContent(tool, { source: 'untrusted' });
|
|
25
|
+
const criticalOrHigh = validation.violations.filter((v) => v.severity === 'critical' || v.severity === 'high');
|
|
26
|
+
if (criticalOrHigh.length > 0) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
message: 'Tool has policy violations that must be resolved before approval',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Step 4: Approve via manifest
|
|
33
|
+
const approvalDir = path.resolve(toolDir);
|
|
34
|
+
const manifest = new ApprovalManifest(approvalDir, context?.credentials?.MATIMO_APPROVAL_SECRET);
|
|
35
|
+
const hash = manifest.computeHash(yamlContent);
|
|
36
|
+
manifest.approve(params.name, hash);
|
|
37
|
+
const approval = manifest.getApproval(params.name);
|
|
38
|
+
// Step 5: Update status in YAML
|
|
39
|
+
const parsed = yaml.load(yamlContent);
|
|
40
|
+
parsed.status = 'approved';
|
|
41
|
+
const updatedYaml = yaml.dump(parsed);
|
|
42
|
+
fs.writeFileSync(defPath, updatedYaml, 'utf-8');
|
|
43
|
+
logger.info('matimo_approve_tool: tool approved', {
|
|
44
|
+
name: params.name,
|
|
45
|
+
hash,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
name: params.name,
|
|
50
|
+
hash,
|
|
51
|
+
approvedAt: approval?.approvedAt,
|
|
52
|
+
message: 'Tool approved. Effective after reload or immediately if auto-reload is active.',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getGlobalMatimoLogger } from '@matimo/core';
|
|
4
|
+
import { validateSkillName, parseSkillContent, validateFrontmatter, } from '../shared/skill-validation.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create a new skill following the Agent Skills specification.
|
|
7
|
+
*
|
|
8
|
+
* Validates the name (lowercase, hyphens, max 64 chars), ensures frontmatter
|
|
9
|
+
* has required fields (name, description), and enforces that the frontmatter
|
|
10
|
+
* name matches the directory name.
|
|
11
|
+
*
|
|
12
|
+
* @see https://agentskills.io/specification
|
|
13
|
+
*/
|
|
14
|
+
export default async function matimoCreateSkill(params) {
|
|
15
|
+
const logger = getGlobalMatimoLogger();
|
|
16
|
+
const targetDir = params.target_dir || './matimo-tools/skills';
|
|
17
|
+
// Step 1: Validate the skill name against Agent Skills spec
|
|
18
|
+
const nameResult = validateSkillName(params.name);
|
|
19
|
+
if (!nameResult.valid) {
|
|
20
|
+
return { success: false, message: nameResult.error };
|
|
21
|
+
}
|
|
22
|
+
// Step 2: Parse and validate frontmatter
|
|
23
|
+
const parseResult = parseSkillContent(params.content);
|
|
24
|
+
if (!parseResult.success) {
|
|
25
|
+
return { success: false, message: parseResult.error };
|
|
26
|
+
}
|
|
27
|
+
const { frontmatter } = parseResult.parsed;
|
|
28
|
+
// Step 3: Validate frontmatter fields + name must match directory
|
|
29
|
+
const fmResult = validateFrontmatter(frontmatter, params.name);
|
|
30
|
+
if (!fmResult.valid) {
|
|
31
|
+
const firstError = fmResult.issues.find(i => i.severity === 'error');
|
|
32
|
+
return { success: false, message: firstError.message };
|
|
33
|
+
}
|
|
34
|
+
// Step 4: Write to disk
|
|
35
|
+
const skillDirPath = path.resolve(targetDir, params.name);
|
|
36
|
+
fs.mkdirSync(skillDirPath, { recursive: true });
|
|
37
|
+
const filePath = path.join(skillDirPath, 'SKILL.md');
|
|
38
|
+
fs.writeFileSync(filePath, params.content, 'utf-8');
|
|
39
|
+
logger.info('matimo_create_skill: skill created', {
|
|
40
|
+
name: params.name,
|
|
41
|
+
path: filePath,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
path: filePath,
|
|
46
|
+
message: `Skill "${params.name}" created successfully.`,
|
|
47
|
+
};
|
|
48
|
+
}
|