@primo-ai/tools 0.1.3 → 0.1.5
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/calculator.d.ts +7 -0
- package/dist/calculator.js +30 -0
- package/dist/datetime.d.ts +10 -0
- package/dist/datetime.js +43 -0
- package/dist/file-edit.d.ts +10 -0
- package/dist/file-edit.js +41 -0
- package/dist/file-read.d.ts +11 -0
- package/dist/file-read.js +39 -0
- package/dist/file-write.d.ts +10 -0
- package/dist/file-write.js +36 -0
- package/dist/glob.d.ts +8 -0
- package/dist/glob.js +25 -0
- package/dist/grep.d.ts +14 -0
- package/dist/grep.js +70 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.js +49 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/json.d.ts +9 -0
- package/dist/json.js +49 -0
- package/dist/shell.d.ts +10 -0
- package/dist/shell.js +38 -0
- package/package.json +3 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const calculatorTool = {
|
|
3
|
+
name: 'calculator',
|
|
4
|
+
description: 'Evaluate a mathematical expression. Supports +, -, *, /, **, %, parentheses, and Math functions.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
expression: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe('Math expression (e.g. "2 + 3 * 4", "Math.sqrt(144)", "3.14 * 10 ** 2")'),
|
|
9
|
+
}),
|
|
10
|
+
requireApproval: false,
|
|
11
|
+
async execute(input) {
|
|
12
|
+
const { expression } = input;
|
|
13
|
+
const sanitized = expression.replace(/\s+/g, ' ').trim();
|
|
14
|
+
if (/[^0-9+\-*/().%\s^,a-zA-Z.]/.test(sanitized.replace(/Math\.\w+/g, ''))) {
|
|
15
|
+
throw new Error(`Disallowed characters in expression: "${expression}"`);
|
|
16
|
+
}
|
|
17
|
+
const fn = new Function(`"use strict"; return (${sanitized});`);
|
|
18
|
+
const result = fn();
|
|
19
|
+
if (typeof result !== 'number' || !Number.isFinite(result)) {
|
|
20
|
+
throw new Error(`Expression did not evaluate to a finite number: ${result}`);
|
|
21
|
+
}
|
|
22
|
+
return { result, expression };
|
|
23
|
+
},
|
|
24
|
+
renderCall(input) {
|
|
25
|
+
return `calc: ${input.expression}`;
|
|
26
|
+
},
|
|
27
|
+
renderResult(output) {
|
|
28
|
+
return `${output.expression} = ${output.result}`;
|
|
29
|
+
},
|
|
30
|
+
};
|
package/dist/datetime.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const datetimeTool = {
|
|
3
|
+
name: 'datetime',
|
|
4
|
+
description: 'Get the current date and time. Supports timezone and format options.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
format: z
|
|
7
|
+
.enum(['iso', 'locale', 'unix', 'date', 'time'])
|
|
8
|
+
.optional()
|
|
9
|
+
.default('iso')
|
|
10
|
+
.describe('Output format'),
|
|
11
|
+
timezone: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('IANA timezone (e.g. "Asia/Shanghai", "America/New_York")'),
|
|
15
|
+
}),
|
|
16
|
+
requireApproval: false,
|
|
17
|
+
async execute(input) {
|
|
18
|
+
const { format = 'iso', timezone } = input;
|
|
19
|
+
const now = new Date();
|
|
20
|
+
const tz = timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
21
|
+
const formatted = format === 'iso'
|
|
22
|
+
? now.toISOString()
|
|
23
|
+
: format === 'unix'
|
|
24
|
+
? String(Math.floor(now.getTime() / 1000))
|
|
25
|
+
: format === 'date'
|
|
26
|
+
? now.toLocaleDateString('en-US', { timeZone: tz })
|
|
27
|
+
: format === 'time'
|
|
28
|
+
? now.toLocaleTimeString('en-US', { timeZone: tz })
|
|
29
|
+
: now.toLocaleString('en-US', { timeZone: tz });
|
|
30
|
+
return {
|
|
31
|
+
iso: now.toISOString(),
|
|
32
|
+
formatted,
|
|
33
|
+
timezone: tz,
|
|
34
|
+
unix: Math.floor(now.getTime() / 1000),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
renderCall(input) {
|
|
38
|
+
return `datetime (${input.timezone ?? 'local'}, ${input.format ?? 'iso'})`;
|
|
39
|
+
},
|
|
40
|
+
renderResult(output) {
|
|
41
|
+
return output.formatted;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const fileEditTool = {
|
|
3
|
+
name: 'fileEdit',
|
|
4
|
+
description: 'Perform exact string replacement in a file. Set replaceAll to replace all occurrences.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
path: z.string().describe('Absolute file path'),
|
|
7
|
+
oldString: z.string().describe('Exact string to find'),
|
|
8
|
+
newString: z.string().describe('Replacement string'),
|
|
9
|
+
replaceAll: z
|
|
10
|
+
.boolean()
|
|
11
|
+
.optional()
|
|
12
|
+
.default(false)
|
|
13
|
+
.describe('Replace all occurrences'),
|
|
14
|
+
}),
|
|
15
|
+
requireApproval: true,
|
|
16
|
+
async execute(input) {
|
|
17
|
+
const fs = await import('node:fs/promises');
|
|
18
|
+
const { path, oldString, newString, replaceAll = false } = input;
|
|
19
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
20
|
+
if (!content.includes(oldString)) {
|
|
21
|
+
throw new Error(`String not found in ${path}`);
|
|
22
|
+
}
|
|
23
|
+
if (!replaceAll && content.split(oldString).length - 1 > 1) {
|
|
24
|
+
throw new Error(`Multiple matches found in ${path}. Use replaceAll: true or provide more context.`);
|
|
25
|
+
}
|
|
26
|
+
const updated = replaceAll
|
|
27
|
+
? content.replaceAll(oldString, newString)
|
|
28
|
+
: content.replace(oldString, newString);
|
|
29
|
+
await fs.writeFile(path, updated, 'utf-8');
|
|
30
|
+
const replacements = replaceAll
|
|
31
|
+
? content.split(oldString).length - 1
|
|
32
|
+
: 1;
|
|
33
|
+
return { path, replacements };
|
|
34
|
+
},
|
|
35
|
+
renderCall(input) {
|
|
36
|
+
return `edit ${input.path}: "${input.oldString.slice(0, 40)}..." → "${input.newString.slice(0, 40)}..."`;
|
|
37
|
+
},
|
|
38
|
+
renderResult(output) {
|
|
39
|
+
return `${output.path} (${output.replacements} replacement(s))`;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const fileReadTool = {
|
|
3
|
+
name: 'fileRead',
|
|
4
|
+
description: 'Read file contents from the local filesystem. Supports line range via offset/limit.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
path: z.string().describe('Absolute file path'),
|
|
7
|
+
encoding: z
|
|
8
|
+
.enum(['utf-8', 'ascii', 'base64', 'hex', 'latin1'])
|
|
9
|
+
.optional()
|
|
10
|
+
.default('utf-8')
|
|
11
|
+
.describe('File encoding'),
|
|
12
|
+
offset: z.number().optional().describe('Start line (1-indexed)'),
|
|
13
|
+
limit: z.number().optional().describe('Max lines to read'),
|
|
14
|
+
}),
|
|
15
|
+
requireApproval: false,
|
|
16
|
+
async execute(input) {
|
|
17
|
+
const fs = await import('node:fs/promises');
|
|
18
|
+
const { path, encoding = 'utf-8', offset, limit } = input;
|
|
19
|
+
const raw = await fs.readFile(path, { encoding: encoding });
|
|
20
|
+
if (offset == null && limit == null) {
|
|
21
|
+
const lines = raw.split('\n').length;
|
|
22
|
+
return { content: raw, lines, path };
|
|
23
|
+
}
|
|
24
|
+
const allLines = raw.split('\n');
|
|
25
|
+
const start = (offset ?? 1) - 1;
|
|
26
|
+
const end = limit != null ? start + limit : allLines.length;
|
|
27
|
+
const sliced = allLines.slice(start, end);
|
|
28
|
+
const numbered = sliced
|
|
29
|
+
.map((line, i) => `${start + i + 1}\t${line}`)
|
|
30
|
+
.join('\n');
|
|
31
|
+
return { content: numbered, lines: allLines.length, path };
|
|
32
|
+
},
|
|
33
|
+
renderCall(input) {
|
|
34
|
+
return `read ${input.path}`;
|
|
35
|
+
},
|
|
36
|
+
renderResult(output) {
|
|
37
|
+
return `[${output.path}] ${output.lines} lines`;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const fileWriteTool = {
|
|
3
|
+
name: 'fileWrite',
|
|
4
|
+
description: 'Write content to a file on the local filesystem. Creates parent directories if needed.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
path: z.string().describe('Absolute file path'),
|
|
7
|
+
content: z.string().describe('Content to write'),
|
|
8
|
+
encoding: z
|
|
9
|
+
.enum(['utf-8', 'ascii', 'base64', 'hex', 'latin1'])
|
|
10
|
+
.optional()
|
|
11
|
+
.default('utf-8')
|
|
12
|
+
.describe('File encoding'),
|
|
13
|
+
append: z
|
|
14
|
+
.boolean()
|
|
15
|
+
.optional()
|
|
16
|
+
.default(false)
|
|
17
|
+
.describe('Append to file instead of overwriting'),
|
|
18
|
+
}),
|
|
19
|
+
requireApproval: true,
|
|
20
|
+
async execute(input) {
|
|
21
|
+
const fs = await import('node:fs/promises');
|
|
22
|
+
const pathModule = await import('node:path');
|
|
23
|
+
const { path, content, encoding = 'utf-8', append = false } = input;
|
|
24
|
+
await fs.mkdir(pathModule.dirname(path), { recursive: true });
|
|
25
|
+
const flag = append ? 'a' : 'w';
|
|
26
|
+
await fs.writeFile(path, content, { encoding: encoding, flag });
|
|
27
|
+
const bytes = Buffer.byteLength(content, encoding);
|
|
28
|
+
return { path, bytes };
|
|
29
|
+
},
|
|
30
|
+
renderCall(input) {
|
|
31
|
+
return `${input.append ? 'append' : 'write'} ${input.path} (${input.content.length} chars)`;
|
|
32
|
+
},
|
|
33
|
+
renderResult(output) {
|
|
34
|
+
return `${output.path} (${output.bytes} bytes)`;
|
|
35
|
+
},
|
|
36
|
+
};
|
package/dist/glob.d.ts
ADDED
package/dist/glob.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const globTool = {
|
|
3
|
+
name: 'glob',
|
|
4
|
+
description: 'Find files matching a glob pattern. Returns file paths sorted by modification time.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
pattern: z.string().describe('Glob pattern (e.g. "**/*.ts", "src/**/*.js")'),
|
|
7
|
+
path: z.string().optional().describe('Base directory to search in'),
|
|
8
|
+
}),
|
|
9
|
+
requireApproval: false,
|
|
10
|
+
async execute(input) {
|
|
11
|
+
const { glob } = await import('node:fs/promises');
|
|
12
|
+
const { pattern, path: cwd } = input;
|
|
13
|
+
const files = [];
|
|
14
|
+
for await (const entry of glob(pattern, { cwd })) {
|
|
15
|
+
files.push(entry);
|
|
16
|
+
}
|
|
17
|
+
return { files, count: files.length };
|
|
18
|
+
},
|
|
19
|
+
renderCall(input) {
|
|
20
|
+
return `glob ${input.pattern}`;
|
|
21
|
+
},
|
|
22
|
+
renderResult(output) {
|
|
23
|
+
return `${output.count} files matched`;
|
|
24
|
+
},
|
|
25
|
+
};
|
package/dist/grep.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Tool } from '@primo-ai/sdk';
|
|
2
|
+
export declare const grepTool: Tool<{
|
|
3
|
+
pattern: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
include?: string;
|
|
6
|
+
maxResults?: number;
|
|
7
|
+
}, {
|
|
8
|
+
matches: {
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
text: string;
|
|
12
|
+
}[];
|
|
13
|
+
count: number;
|
|
14
|
+
}>;
|
package/dist/grep.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const grepTool = {
|
|
3
|
+
name: 'grep',
|
|
4
|
+
description: 'Search file contents by regex pattern. Returns matching lines with file paths and line numbers.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
pattern: z.string().describe('Regex pattern to search for'),
|
|
7
|
+
path: z.string().optional().describe('Directory to search in'),
|
|
8
|
+
include: z.string().optional().describe('File glob filter (e.g. "*.ts")'),
|
|
9
|
+
maxResults: z.number().optional().default(100).describe('Max results'),
|
|
10
|
+
}),
|
|
11
|
+
requireApproval: false,
|
|
12
|
+
async execute(input) {
|
|
13
|
+
const { readFile, readdir } = await import('node:fs/promises');
|
|
14
|
+
const { join, relative } = await import('node:path');
|
|
15
|
+
const { pattern, path: cwd = '.', include, maxResults = 100 } = input;
|
|
16
|
+
const regex = new RegExp(pattern, 'i');
|
|
17
|
+
const matches = [];
|
|
18
|
+
const includeRegex = include
|
|
19
|
+
? globToRegex(include)
|
|
20
|
+
: null;
|
|
21
|
+
async function walk(dir) {
|
|
22
|
+
if (matches.length >= maxResults)
|
|
23
|
+
return;
|
|
24
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (matches.length >= maxResults)
|
|
27
|
+
return;
|
|
28
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
29
|
+
continue;
|
|
30
|
+
const full = join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
await walk(full);
|
|
33
|
+
}
|
|
34
|
+
else if (entry.isFile()) {
|
|
35
|
+
if (includeRegex && !includeRegex.test(entry.name))
|
|
36
|
+
continue;
|
|
37
|
+
try {
|
|
38
|
+
const content = await readFile(full, 'utf-8');
|
|
39
|
+
const lines = content.split('\n');
|
|
40
|
+
for (let i = 0; i < lines.length; i++) {
|
|
41
|
+
if (matches.length >= maxResults)
|
|
42
|
+
return;
|
|
43
|
+
if (regex.test(lines[i])) {
|
|
44
|
+
matches.push({ file: relative(cwd, full), line: i + 1, text: lines[i].trim() });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// skip binary / unreadable files
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await walk(cwd);
|
|
55
|
+
return { matches, count: matches.length };
|
|
56
|
+
},
|
|
57
|
+
renderCall(input) {
|
|
58
|
+
return `grep "${input.pattern}"`;
|
|
59
|
+
},
|
|
60
|
+
renderResult(output) {
|
|
61
|
+
return `${output.count} matches`;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
function globToRegex(glob) {
|
|
65
|
+
const escaped = glob
|
|
66
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
67
|
+
.replace(/\*/g, '.*')
|
|
68
|
+
.replace(/\?/g, '.');
|
|
69
|
+
return new RegExp(`^${escaped}$`);
|
|
70
|
+
}
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const httpTool = {
|
|
3
|
+
name: 'http',
|
|
4
|
+
description: 'Make HTTP requests. Supports GET, POST, PUT, PATCH, DELETE. Returns status, headers, and body.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
url: z.string().describe('The URL to request'),
|
|
7
|
+
method: z
|
|
8
|
+
.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
|
|
9
|
+
.optional()
|
|
10
|
+
.default('GET')
|
|
11
|
+
.describe('HTTP method'),
|
|
12
|
+
headers: z
|
|
13
|
+
.record(z.string(), z.string())
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Request headers'),
|
|
16
|
+
body: z.string().optional().describe('Request body (for POST/PUT/PATCH)'),
|
|
17
|
+
}),
|
|
18
|
+
outputSchema: z.object({
|
|
19
|
+
status: z.number(),
|
|
20
|
+
headers: z.record(z.string(), z.string()),
|
|
21
|
+
body: z.string(),
|
|
22
|
+
}),
|
|
23
|
+
requireApproval: false,
|
|
24
|
+
async execute(input) {
|
|
25
|
+
const { url, method = 'GET', headers = {}, body } = input;
|
|
26
|
+
const reqHeaders = new Headers();
|
|
27
|
+
reqHeaders.set('User-Agent', 'AgentForge/1.0');
|
|
28
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
29
|
+
reqHeaders.set(k, v);
|
|
30
|
+
}
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
method,
|
|
33
|
+
headers: reqHeaders,
|
|
34
|
+
body: ['GET', 'DELETE'].includes(method) ? undefined : body,
|
|
35
|
+
});
|
|
36
|
+
const resHeaders = {};
|
|
37
|
+
res.headers.forEach((v, k) => {
|
|
38
|
+
resHeaders[k] = v;
|
|
39
|
+
});
|
|
40
|
+
const resBody = await res.text();
|
|
41
|
+
return { status: res.status, headers: resHeaders, body: resBody };
|
|
42
|
+
},
|
|
43
|
+
renderCall(input) {
|
|
44
|
+
return `${input.method ?? 'GET'} ${input.url}`;
|
|
45
|
+
},
|
|
46
|
+
renderResult(output) {
|
|
47
|
+
return `[${output.status}] ${output.body.slice(0, 200)}`;
|
|
48
|
+
},
|
|
49
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
1
|
export { echoTool } from './echo.js';
|
|
2
|
+
export { httpTool } from './http.js';
|
|
3
|
+
export { fileReadTool } from './file-read.js';
|
|
4
|
+
export { fileWriteTool } from './file-write.js';
|
|
5
|
+
export { fileEditTool } from './file-edit.js';
|
|
6
|
+
export { globTool } from './glob.js';
|
|
7
|
+
export { grepTool } from './grep.js';
|
|
8
|
+
export { shellTool } from './shell.js';
|
|
9
|
+
export { calculatorTool } from './calculator.js';
|
|
10
|
+
export { datetimeTool } from './datetime.js';
|
|
11
|
+
export { jsonTool } from './json.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
// @primo-ai/tools — Built-in tools
|
|
2
2
|
export { echoTool } from './echo.js';
|
|
3
|
+
export { httpTool } from './http.js';
|
|
4
|
+
export { fileReadTool } from './file-read.js';
|
|
5
|
+
export { fileWriteTool } from './file-write.js';
|
|
6
|
+
export { fileEditTool } from './file-edit.js';
|
|
7
|
+
export { globTool } from './glob.js';
|
|
8
|
+
export { grepTool } from './grep.js';
|
|
9
|
+
export { shellTool } from './shell.js';
|
|
10
|
+
export { calculatorTool } from './calculator.js';
|
|
11
|
+
export { datetimeTool } from './datetime.js';
|
|
12
|
+
export { jsonTool } from './json.js';
|
package/dist/json.d.ts
ADDED
package/dist/json.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const jsonTool = {
|
|
3
|
+
name: 'json',
|
|
4
|
+
description: 'Parse, stringify, or query JSON data. Query uses dot-notation paths (e.g. "users.0.name").',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
operation: z
|
|
7
|
+
.enum(['parse', 'stringify', 'query'])
|
|
8
|
+
.describe('Operation to perform'),
|
|
9
|
+
data: z.string().describe('JSON string or value to process'),
|
|
10
|
+
path: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Dot-notation path for query (e.g. "users.0.name")'),
|
|
14
|
+
indent: z.number().optional().default(2).describe('Indentation for stringify'),
|
|
15
|
+
}),
|
|
16
|
+
requireApproval: false,
|
|
17
|
+
async execute(input) {
|
|
18
|
+
const { operation, data, path, indent = 2 } = input;
|
|
19
|
+
switch (operation) {
|
|
20
|
+
case 'parse': {
|
|
21
|
+
const parsed = JSON.parse(data);
|
|
22
|
+
return { result: JSON.stringify(parsed, null, indent) };
|
|
23
|
+
}
|
|
24
|
+
case 'stringify': {
|
|
25
|
+
const parsed = JSON.parse(data);
|
|
26
|
+
return { result: JSON.stringify(parsed, null, indent) };
|
|
27
|
+
}
|
|
28
|
+
case 'query': {
|
|
29
|
+
if (!path)
|
|
30
|
+
throw new Error('path is required for query operation');
|
|
31
|
+
const parsed = JSON.parse(data);
|
|
32
|
+
const result = path.split('.').reduce((obj, key) => {
|
|
33
|
+
if (obj == null)
|
|
34
|
+
return undefined;
|
|
35
|
+
return obj[key];
|
|
36
|
+
}, parsed);
|
|
37
|
+
return { result: JSON.stringify(result, null, indent) };
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
renderCall(input) {
|
|
44
|
+
return `json ${input.operation}${input.path ? ` .${input.path}` : ''}`;
|
|
45
|
+
},
|
|
46
|
+
renderResult(output) {
|
|
47
|
+
return output.result.slice(0, 200);
|
|
48
|
+
},
|
|
49
|
+
};
|
package/dist/shell.d.ts
ADDED
package/dist/shell.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const shellTool = {
|
|
3
|
+
name: 'shell',
|
|
4
|
+
description: 'Execute a shell command. Returns stdout, stderr, and exit code. Supports timeout.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
command: z.string().describe('Shell command to execute'),
|
|
7
|
+
cwd: z.string().optional().describe('Working directory'),
|
|
8
|
+
timeout: z
|
|
9
|
+
.number()
|
|
10
|
+
.optional()
|
|
11
|
+
.default(30_000)
|
|
12
|
+
.describe('Timeout in milliseconds (default 30s)'),
|
|
13
|
+
}),
|
|
14
|
+
requireApproval: true,
|
|
15
|
+
async execute(input) {
|
|
16
|
+
const { exec } = await import('node:child_process');
|
|
17
|
+
const { command, cwd, timeout = 30_000 } = input;
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const child = exec(command, { cwd, timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
20
|
+
resolve({
|
|
21
|
+
exitCode: error ? 1 : 0,
|
|
22
|
+
stdout: stdout ?? '',
|
|
23
|
+
stderr: stderr ?? '',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
child.on('error', (err) => {
|
|
27
|
+
resolve({ exitCode: 1, stdout: '', stderr: err.message });
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
renderCall(input) {
|
|
32
|
+
return `$ ${input.command}`;
|
|
33
|
+
},
|
|
34
|
+
renderResult(output) {
|
|
35
|
+
const out = output.stdout.slice(0, 200);
|
|
36
|
+
return `[exit ${output.exitCode}] ${out}`;
|
|
37
|
+
},
|
|
38
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primo-ai/tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.15.0",
|
|
17
18
|
"typescript": "^5.7.3",
|
|
18
19
|
"vitest": "^3.0.5",
|
|
19
|
-
"@primo-ai/sdk": "0.1.
|
|
20
|
+
"@primo-ai/sdk": "0.1.5"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"zod": "^4.4.3"
|