@stan-chen/simple-cli 0.2.3 → 0.2.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/README.md +62 -63
- package/dist/anyllm.py +62 -0
- package/dist/builtins.d.ts +726 -0
- package/dist/builtins.js +481 -0
- package/dist/cli.d.ts +0 -4
- package/dist/cli.js +34 -493
- package/dist/engine.d.ts +33 -0
- package/dist/engine.js +138 -0
- package/dist/learnings.d.ts +15 -0
- package/dist/learnings.js +54 -0
- package/dist/llm.d.ts +18 -0
- package/dist/llm.js +66 -0
- package/dist/mcp.d.ts +132 -0
- package/dist/mcp.js +43 -0
- package/dist/skills.d.ts +5 -16
- package/dist/skills.js +91 -253
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +10 -0
- package/package.json +10 -6
- package/dist/claw/jit.d.ts +0 -5
- package/dist/claw/jit.js +0 -138
- package/dist/claw/management.d.ts +0 -3
- package/dist/claw/management.js +0 -107
- package/dist/commands/add.d.ts +0 -9
- package/dist/commands/add.js +0 -50
- package/dist/commands/git/commit.d.ts +0 -12
- package/dist/commands/git/commit.js +0 -98
- package/dist/commands/git/status.d.ts +0 -6
- package/dist/commands/git/status.js +0 -42
- package/dist/commands/index.d.ts +0 -16
- package/dist/commands/index.js +0 -377
- package/dist/commands/mcp/status.d.ts +0 -6
- package/dist/commands/mcp/status.js +0 -31
- package/dist/commands/swarm.d.ts +0 -36
- package/dist/commands/swarm.js +0 -236
- package/dist/commands.d.ts +0 -32
- package/dist/commands.js +0 -427
- package/dist/context.d.ts +0 -116
- package/dist/context.js +0 -337
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -109
- package/dist/lib/agent.d.ts +0 -99
- package/dist/lib/agent.js +0 -313
- package/dist/lib/editor.d.ts +0 -74
- package/dist/lib/editor.js +0 -441
- package/dist/lib/git.d.ts +0 -164
- package/dist/lib/git.js +0 -356
- package/dist/lib/shim.d.ts +0 -4
- package/dist/lib/shim.js +0 -30
- package/dist/lib/ui.d.ts +0 -159
- package/dist/lib/ui.js +0 -277
- package/dist/mcp/client.d.ts +0 -22
- package/dist/mcp/client.js +0 -81
- package/dist/mcp/manager.d.ts +0 -186
- package/dist/mcp/manager.js +0 -446
- package/dist/prompts/provider.d.ts +0 -22
- package/dist/prompts/provider.js +0 -79
- package/dist/providers/index.d.ts +0 -31
- package/dist/providers/index.js +0 -93
- package/dist/providers/multi.d.ts +0 -12
- package/dist/providers/multi.js +0 -28
- package/dist/registry.d.ts +0 -29
- package/dist/registry.js +0 -443
- package/dist/repoMap.d.ts +0 -5
- package/dist/repoMap.js +0 -79
- package/dist/router.d.ts +0 -41
- package/dist/router.js +0 -118
- package/dist/swarm/coordinator.d.ts +0 -86
- package/dist/swarm/coordinator.js +0 -257
- package/dist/swarm/index.d.ts +0 -28
- package/dist/swarm/index.js +0 -29
- package/dist/swarm/task.d.ts +0 -104
- package/dist/swarm/task.js +0 -221
- package/dist/swarm/types.d.ts +0 -132
- package/dist/swarm/types.js +0 -37
- package/dist/swarm/worker.d.ts +0 -109
- package/dist/swarm/worker.js +0 -369
- package/dist/tools/analyzeFile.d.ts +0 -16
- package/dist/tools/analyzeFile.js +0 -43
- package/dist/tools/analyze_file.d.ts +0 -16
- package/dist/tools/analyze_file.js +0 -43
- package/dist/tools/clawBrain.d.ts +0 -23
- package/dist/tools/clawBrain.js +0 -136
- package/dist/tools/claw_brain.d.ts +0 -23
- package/dist/tools/claw_brain.js +0 -139
- package/dist/tools/deleteFile.d.ts +0 -19
- package/dist/tools/deleteFile.js +0 -36
- package/dist/tools/delete_file.d.ts +0 -19
- package/dist/tools/delete_file.js +0 -36
- package/dist/tools/fileOps.d.ts +0 -22
- package/dist/tools/fileOps.js +0 -43
- package/dist/tools/file_ops.d.ts +0 -22
- package/dist/tools/file_ops.js +0 -43
- package/dist/tools/git.d.ts +0 -40
- package/dist/tools/git.js +0 -236
- package/dist/tools/glob.d.ts +0 -34
- package/dist/tools/glob.js +0 -165
- package/dist/tools/grep.d.ts +0 -53
- package/dist/tools/grep.js +0 -296
- package/dist/tools/linter.d.ts +0 -35
- package/dist/tools/linter.js +0 -407
- package/dist/tools/listDir.d.ts +0 -29
- package/dist/tools/listDir.js +0 -50
- package/dist/tools/list_dir.d.ts +0 -29
- package/dist/tools/list_dir.js +0 -50
- package/dist/tools/memory.d.ts +0 -34
- package/dist/tools/memory.js +0 -215
- package/dist/tools/organizer.d.ts +0 -1
- package/dist/tools/organizer.js +0 -65
- package/dist/tools/readFiles.d.ts +0 -25
- package/dist/tools/readFiles.js +0 -31
- package/dist/tools/read_files.d.ts +0 -25
- package/dist/tools/read_files.js +0 -31
- package/dist/tools/reloadTools.d.ts +0 -11
- package/dist/tools/reloadTools.js +0 -22
- package/dist/tools/reload_tools.d.ts +0 -11
- package/dist/tools/reload_tools.js +0 -22
- package/dist/tools/runCommand.d.ts +0 -32
- package/dist/tools/runCommand.js +0 -79
- package/dist/tools/run_command.d.ts +0 -32
- package/dist/tools/run_command.js +0 -103
- package/dist/tools/scheduler.d.ts +0 -25
- package/dist/tools/scheduler.js +0 -65
- package/dist/tools/scraper.d.ts +0 -31
- package/dist/tools/scraper.js +0 -211
- package/dist/tools/writeFiles.d.ts +0 -63
- package/dist/tools/writeFiles.js +0 -87
- package/dist/tools/write_files.d.ts +0 -84
- package/dist/tools/write_files.js +0 -91
- package/dist/tools/write_to_file.d.ts +0 -15
- package/dist/tools/write_to_file.js +0 -21
- package/dist/ui/server.d.ts +0 -5
- package/dist/ui/server.js +0 -74
- package/dist/watcher.d.ts +0 -35
- package/dist/watcher.js +0 -164
- /package/{docs/assets → assets}/logo.jpeg +0 -0
package/dist/builtins.js
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { readFile, writeFile, readdir, unlink, mkdir, stat } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join, resolve, relative, extname } from 'path';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import glob from 'fast-glob';
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
export const readFiles = {
|
|
10
|
+
name: 'read_files',
|
|
11
|
+
description: 'Read contents of one or more files',
|
|
12
|
+
inputSchema: z.object({ paths: z.array(z.string()) }),
|
|
13
|
+
execute: async (args) => {
|
|
14
|
+
let paths = args.paths;
|
|
15
|
+
if (!paths) {
|
|
16
|
+
if (args.path)
|
|
17
|
+
paths = [args.path];
|
|
18
|
+
else if (args.file)
|
|
19
|
+
paths = [args.file];
|
|
20
|
+
else if (args.filename)
|
|
21
|
+
paths = [args.filename];
|
|
22
|
+
}
|
|
23
|
+
if (!paths || !Array.isArray(paths)) {
|
|
24
|
+
return [{ error: "Invalid arguments: 'paths' array is required." }];
|
|
25
|
+
}
|
|
26
|
+
const results = [];
|
|
27
|
+
for (const p of paths) {
|
|
28
|
+
try {
|
|
29
|
+
if (existsSync(p)) {
|
|
30
|
+
const content = await readFile(p, 'utf-8');
|
|
31
|
+
results.push({ path: p, content });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
results.push({ path: p, error: `File not found: ${p}` });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
results.push({ path: p, error: e.message });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export const writeFiles = {
|
|
45
|
+
name: 'write_files',
|
|
46
|
+
description: 'Write or modify files. Use SEARCH/REPLACE blocks for partial edits.',
|
|
47
|
+
inputSchema: z.object({
|
|
48
|
+
files: z.array(z.object({
|
|
49
|
+
path: z.string(),
|
|
50
|
+
content: z.string().optional(),
|
|
51
|
+
searchReplace: z.array(z.object({
|
|
52
|
+
search: z.string(),
|
|
53
|
+
replace: z.string()
|
|
54
|
+
})).optional()
|
|
55
|
+
}))
|
|
56
|
+
}),
|
|
57
|
+
execute: async (args) => {
|
|
58
|
+
let files = args.files;
|
|
59
|
+
// Fallback: if agent passed single file attributes directly
|
|
60
|
+
if (!files) {
|
|
61
|
+
if (args.path)
|
|
62
|
+
files = [args];
|
|
63
|
+
else if (args.file)
|
|
64
|
+
files = [{ ...args, path: args.file }];
|
|
65
|
+
else if (args.filename)
|
|
66
|
+
files = [{ ...args, path: args.filename }];
|
|
67
|
+
}
|
|
68
|
+
else if (typeof files === 'object' && !Array.isArray(files)) {
|
|
69
|
+
// Handle dictionary format: { "file.txt": { content: "..." } }
|
|
70
|
+
files = Object.entries(files).map(([key, val]) => ({
|
|
71
|
+
path: key,
|
|
72
|
+
...val
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
if (!files || !Array.isArray(files)) {
|
|
76
|
+
throw new Error(`Invalid arguments for write_files: ${JSON.stringify(args)}`);
|
|
77
|
+
}
|
|
78
|
+
const results = [];
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
// Fix: Map 'name' to 'path' if path is missing (common hallucination)
|
|
81
|
+
if (!f.path && f.name)
|
|
82
|
+
f.path = f.name;
|
|
83
|
+
try {
|
|
84
|
+
if (!f.path) {
|
|
85
|
+
results.push({ success: false, message: 'File path missing' });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const dir = resolve(f.path, '..');
|
|
89
|
+
if (!existsSync(dir)) {
|
|
90
|
+
await mkdir(dir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
if (f.content !== undefined) {
|
|
93
|
+
await writeFile(f.path, f.content);
|
|
94
|
+
results.push({ path: f.path, success: true });
|
|
95
|
+
}
|
|
96
|
+
else if (f.searchReplace) {
|
|
97
|
+
if (!existsSync(f.path)) {
|
|
98
|
+
results.push({ path: f.path, success: false, message: `File not found: ${f.path}` });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
let content = await readFile(f.path, 'utf-8');
|
|
102
|
+
for (const { search, replace } of f.searchReplace) {
|
|
103
|
+
if (!content.includes(search)) {
|
|
104
|
+
throw new Error(`Search pattern not found in ${f.path}: "${search}"`);
|
|
105
|
+
}
|
|
106
|
+
content = content.split(search).join(replace);
|
|
107
|
+
}
|
|
108
|
+
await writeFile(f.path, content);
|
|
109
|
+
results.push({ path: f.path, success: true });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
results.push({ path: f.path, success: false, message: 'No content or searchReplace provided' });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
results.push({ path: f.path, success: false, message: e.message });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
export const createTool = {
|
|
123
|
+
name: 'create_tool',
|
|
124
|
+
description: 'Create a new tool from a script file',
|
|
125
|
+
inputSchema: z.object({
|
|
126
|
+
source_path: z.string(),
|
|
127
|
+
name: z.string(),
|
|
128
|
+
description: z.string(),
|
|
129
|
+
usage: z.string(),
|
|
130
|
+
scope: z.enum(['local', 'global']).default('local')
|
|
131
|
+
}),
|
|
132
|
+
execute: async ({ source_path, name, description, usage, scope }) => {
|
|
133
|
+
if (!existsSync(source_path))
|
|
134
|
+
return `Source file not found: ${source_path}`;
|
|
135
|
+
const content = await readFile(source_path, 'utf-8');
|
|
136
|
+
const ext = extname(source_path);
|
|
137
|
+
const filename = `${name}${ext}`;
|
|
138
|
+
let header = '';
|
|
139
|
+
if (ext === '.js' || ext === '.ts') {
|
|
140
|
+
header = `/**\n * ${name}\n * ${description}\n * Usage: ${usage}\n */\n\n`;
|
|
141
|
+
}
|
|
142
|
+
else if (ext === '.py') {
|
|
143
|
+
header = `"""\n${name}\n${description}\nUsage: ${usage}\n"""\n\n`;
|
|
144
|
+
}
|
|
145
|
+
const targetDir = scope === 'global'
|
|
146
|
+
? join(process.env.HOME || process.cwd(), '.agent', 'tools')
|
|
147
|
+
: join(process.cwd(), '.agent', 'tools');
|
|
148
|
+
await mkdir(targetDir, { recursive: true });
|
|
149
|
+
const targetPath = join(targetDir, filename);
|
|
150
|
+
await writeFile(targetPath, header + content);
|
|
151
|
+
return `Tool ${name} successfully saved to ${targetPath}`;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
export const scrapeUrl = {
|
|
155
|
+
name: 'scrape_url',
|
|
156
|
+
description: 'Scrape content from a URL',
|
|
157
|
+
inputSchema: z.object({
|
|
158
|
+
url: z.string().url(),
|
|
159
|
+
convertToMarkdown: z.boolean().default(true),
|
|
160
|
+
timeout: z.number().optional()
|
|
161
|
+
}),
|
|
162
|
+
execute: async ({ url, convertToMarkdown, timeout }) => {
|
|
163
|
+
try {
|
|
164
|
+
const controller = new AbortController();
|
|
165
|
+
const id = timeout ? setTimeout(() => controller.abort(), timeout) : undefined;
|
|
166
|
+
let res;
|
|
167
|
+
try {
|
|
168
|
+
res = await fetch(url, { signal: controller.signal });
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
if (id)
|
|
172
|
+
clearTimeout(id);
|
|
173
|
+
}
|
|
174
|
+
if (!res.ok)
|
|
175
|
+
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
176
|
+
const type = res.headers.get('content-type') || '';
|
|
177
|
+
let content = await res.text();
|
|
178
|
+
if (convertToMarkdown && type.includes('text/html')) {
|
|
179
|
+
content = content
|
|
180
|
+
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gmi, "")
|
|
181
|
+
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gmi, "")
|
|
182
|
+
.replace(/<pre><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, '```\n$1\n```\n')
|
|
183
|
+
.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n')
|
|
184
|
+
.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n')
|
|
185
|
+
.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n')
|
|
186
|
+
.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n')
|
|
187
|
+
.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
|
|
188
|
+
.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**')
|
|
189
|
+
.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*')
|
|
190
|
+
.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`')
|
|
191
|
+
.replace(/<[^>]+>/g, '')
|
|
192
|
+
.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').replace(/"/g, '"')
|
|
193
|
+
.trim();
|
|
194
|
+
}
|
|
195
|
+
return { url, content, contentType: type };
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return { url, error: e.message };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
export const listFiles = {
|
|
203
|
+
name: 'list_files',
|
|
204
|
+
description: 'List files using glob patterns. Supports recursive matching.',
|
|
205
|
+
inputSchema: z.object({
|
|
206
|
+
pattern: z.string().describe('Glob pattern (e.g. **/*.ts)'),
|
|
207
|
+
path: z.string().default('.').describe('Base directory'),
|
|
208
|
+
ignore: z.array(z.string()).default(['**/node_modules/**', '**/.git/**']),
|
|
209
|
+
includeDirectories: z.boolean().default(false),
|
|
210
|
+
maxResults: z.number().optional()
|
|
211
|
+
}),
|
|
212
|
+
execute: async ({ pattern, path, ignore, includeDirectories, maxResults }) => {
|
|
213
|
+
const files = await glob(pattern, {
|
|
214
|
+
cwd: path,
|
|
215
|
+
ignore: ignore,
|
|
216
|
+
onlyFiles: !includeDirectories,
|
|
217
|
+
dot: true,
|
|
218
|
+
absolute: false
|
|
219
|
+
});
|
|
220
|
+
const truncated = maxResults && files.length > maxResults;
|
|
221
|
+
const resultFiles = maxResults ? files.slice(0, maxResults) : files;
|
|
222
|
+
return {
|
|
223
|
+
matches: resultFiles,
|
|
224
|
+
count: resultFiles.length,
|
|
225
|
+
truncated: !!truncated
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
export const searchFiles = {
|
|
230
|
+
name: 'search_files',
|
|
231
|
+
description: 'Search for patterns in files (grep-like).',
|
|
232
|
+
inputSchema: z.object({
|
|
233
|
+
pattern: z.string().describe('Regex pattern to search for'),
|
|
234
|
+
path: z.string().default('.').describe('Directory or file to search'),
|
|
235
|
+
glob: z.string().default('**/*').describe('Glob pattern to filter files'),
|
|
236
|
+
ignoreCase: z.boolean().default(false),
|
|
237
|
+
contextLines: z.number().default(0),
|
|
238
|
+
maxResults: z.number().optional(),
|
|
239
|
+
filesOnly: z.boolean().default(false)
|
|
240
|
+
}),
|
|
241
|
+
execute: async ({ pattern, path, glob: globPattern, ignoreCase, contextLines, maxResults, filesOnly }) => {
|
|
242
|
+
let files = [];
|
|
243
|
+
try {
|
|
244
|
+
const stats = await stat(path);
|
|
245
|
+
if (stats.isFile()) {
|
|
246
|
+
files = [path];
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
files = await glob(globPattern, {
|
|
250
|
+
cwd: path,
|
|
251
|
+
ignore: ['**/node_modules/**', '**/.git/**'],
|
|
252
|
+
onlyFiles: true,
|
|
253
|
+
absolute: true
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
return { matches: [], count: 0, error: 'Path not found' };
|
|
259
|
+
}
|
|
260
|
+
const lineRegex = new RegExp(pattern, ignoreCase ? 'gi' : 'g');
|
|
261
|
+
const matches = [];
|
|
262
|
+
const matchedFiles = new Set();
|
|
263
|
+
let count = 0;
|
|
264
|
+
let truncated = false;
|
|
265
|
+
for (const file of files) {
|
|
266
|
+
try {
|
|
267
|
+
const content = await readFile(file, 'utf-8');
|
|
268
|
+
if (content.includes('\0'))
|
|
269
|
+
continue;
|
|
270
|
+
if (filesOnly) {
|
|
271
|
+
lineRegex.lastIndex = 0;
|
|
272
|
+
if (lineRegex.test(content)) {
|
|
273
|
+
matchedFiles.add(relative(process.cwd(), file));
|
|
274
|
+
if (maxResults && matchedFiles.size >= maxResults) {
|
|
275
|
+
truncated = true;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const lines = content.split('\n');
|
|
282
|
+
for (let i = 0; i < lines.length; i++) {
|
|
283
|
+
const line = lines[i];
|
|
284
|
+
lineRegex.lastIndex = 0;
|
|
285
|
+
const matchesOnLine = [...line.matchAll(lineRegex)];
|
|
286
|
+
for (const m of matchesOnLine) {
|
|
287
|
+
if (maxResults && count >= maxResults) {
|
|
288
|
+
truncated = true;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
const match = {
|
|
292
|
+
file: relative(process.cwd(), file),
|
|
293
|
+
line: i + 1,
|
|
294
|
+
text: line,
|
|
295
|
+
match: m[0]
|
|
296
|
+
};
|
|
297
|
+
if (contextLines > 0) {
|
|
298
|
+
match.contextBefore = lines.slice(Math.max(0, i - contextLines), i);
|
|
299
|
+
match.contextAfter = lines.slice(i + 1, i + 1 + contextLines);
|
|
300
|
+
}
|
|
301
|
+
matches.push(match);
|
|
302
|
+
matchedFiles.add(match.file);
|
|
303
|
+
count++;
|
|
304
|
+
}
|
|
305
|
+
if (truncated)
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
// Ignore read errors
|
|
311
|
+
}
|
|
312
|
+
if (truncated)
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
matches: matches,
|
|
317
|
+
count: filesOnly ? matchedFiles.size : matches.length,
|
|
318
|
+
files: Array.from(matchedFiles),
|
|
319
|
+
truncated: !!truncated
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
export const listDir = {
|
|
324
|
+
name: 'list_dir',
|
|
325
|
+
description: 'List contents of a directory',
|
|
326
|
+
inputSchema: z.object({ path: z.string().default('.') }),
|
|
327
|
+
execute: async ({ path }) => {
|
|
328
|
+
const items = await readdir(path, { withFileTypes: true });
|
|
329
|
+
return items.map(i => ({ name: i.name, isDir: i.isDirectory() }));
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
export const runCommand = {
|
|
333
|
+
name: 'run_command',
|
|
334
|
+
description: 'Run a shell command',
|
|
335
|
+
inputSchema: z.object({ command: z.string(), timeout: z.number().optional() }),
|
|
336
|
+
execute: async ({ command, timeout }) => {
|
|
337
|
+
try {
|
|
338
|
+
const { stdout, stderr } = await execAsync(command, { timeout });
|
|
339
|
+
return { stdout, stderr, exitCode: 0, timedOut: false };
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
const timedOut = e.killed && e.signal === 'SIGTERM';
|
|
343
|
+
return {
|
|
344
|
+
error: e.message,
|
|
345
|
+
stdout: e.stdout || '',
|
|
346
|
+
stderr: e.stderr || '',
|
|
347
|
+
exitCode: e.code || 1,
|
|
348
|
+
timedOut
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
export const deleteFile = {
|
|
354
|
+
name: 'delete_file',
|
|
355
|
+
description: 'Delete a file',
|
|
356
|
+
inputSchema: z.object({ path: z.string() }),
|
|
357
|
+
execute: async (args) => {
|
|
358
|
+
const path = args.path || args.file || args.filename;
|
|
359
|
+
if (!path)
|
|
360
|
+
return "Error: 'path' argument required";
|
|
361
|
+
if (existsSync(path)) {
|
|
362
|
+
await unlink(path);
|
|
363
|
+
return `Deleted ${path}`;
|
|
364
|
+
}
|
|
365
|
+
return `File not found: ${path}`;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
export const gitTool = {
|
|
369
|
+
name: 'git',
|
|
370
|
+
description: 'Run git operations',
|
|
371
|
+
inputSchema: z.object({
|
|
372
|
+
operation: z.enum(['status', 'add', 'commit', 'diff', 'log', 'branch']),
|
|
373
|
+
cwd: z.string().default('.'),
|
|
374
|
+
files: z.array(z.string()).optional(),
|
|
375
|
+
message: z.string().optional()
|
|
376
|
+
}),
|
|
377
|
+
execute: async ({ operation, cwd, files, message }) => {
|
|
378
|
+
const run = async (cmd) => {
|
|
379
|
+
try {
|
|
380
|
+
const { stdout } = await execAsync(cmd, { cwd });
|
|
381
|
+
return { success: true, output: stdout.trim() };
|
|
382
|
+
}
|
|
383
|
+
catch (e) {
|
|
384
|
+
return { success: false, error: e.message };
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
if (operation === 'status') {
|
|
388
|
+
return run('git status -s');
|
|
389
|
+
}
|
|
390
|
+
if (operation === 'add') {
|
|
391
|
+
if (!files || files.length === 0)
|
|
392
|
+
return { success: false, error: 'No files specified for add' };
|
|
393
|
+
return run(`git add ${files.join(' ')}`);
|
|
394
|
+
}
|
|
395
|
+
if (operation === 'commit') {
|
|
396
|
+
if (!message)
|
|
397
|
+
return { success: false, error: 'No commit message specified' };
|
|
398
|
+
return run(`git commit -m "${message}"`);
|
|
399
|
+
}
|
|
400
|
+
if (operation === 'diff') {
|
|
401
|
+
return run('git diff');
|
|
402
|
+
}
|
|
403
|
+
if (operation === 'log') {
|
|
404
|
+
return run('git log --oneline -n 10');
|
|
405
|
+
}
|
|
406
|
+
if (operation === 'branch') {
|
|
407
|
+
return run('git branch');
|
|
408
|
+
}
|
|
409
|
+
return { success: false, error: 'Unknown operation' };
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
export const linter = {
|
|
413
|
+
name: 'lint',
|
|
414
|
+
description: 'Lint a file',
|
|
415
|
+
inputSchema: z.object({ path: z.string() }),
|
|
416
|
+
execute: async ({ path }) => {
|
|
417
|
+
if (!existsSync(path))
|
|
418
|
+
return { passed: false, errors: [{ message: 'File not found' }] };
|
|
419
|
+
const ext = extname(path);
|
|
420
|
+
let cmd = '';
|
|
421
|
+
if (ext === '.js' || ext === '.ts')
|
|
422
|
+
cmd = `node --check "${path}"`;
|
|
423
|
+
else if (ext === '.py')
|
|
424
|
+
cmd = `python3 -m py_compile "${path}"`;
|
|
425
|
+
else if (ext === '.sh')
|
|
426
|
+
cmd = `shellcheck "${path}"`;
|
|
427
|
+
else
|
|
428
|
+
return { passed: true, language: 'unknown', errors: [], warnings: [], output: 'No linter for this file type' };
|
|
429
|
+
let language = 'unknown';
|
|
430
|
+
if (ext === '.js')
|
|
431
|
+
language = 'javascript';
|
|
432
|
+
if (ext === '.ts')
|
|
433
|
+
language = 'typescript';
|
|
434
|
+
if (ext === '.py')
|
|
435
|
+
language = 'python';
|
|
436
|
+
if (ext === '.sh')
|
|
437
|
+
language = 'shell';
|
|
438
|
+
try {
|
|
439
|
+
await execAsync(cmd);
|
|
440
|
+
return { passed: true, language, errors: [], warnings: [], output: 'Lint passed', file: path };
|
|
441
|
+
}
|
|
442
|
+
catch (e) {
|
|
443
|
+
const stderr = e.stderr || e.message;
|
|
444
|
+
const lineMatch = stderr.match(/line\s+(\d+)/i) || stderr.match(/:(\d+):/);
|
|
445
|
+
const line = lineMatch ? parseInt(lineMatch[1]) : 0;
|
|
446
|
+
return { passed: false, language, errors: [{ message: stderr, line }], warnings: [], output: stderr, file: path };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
// Git helpers
|
|
451
|
+
export const getCurrentBranch = async (cwd) => {
|
|
452
|
+
try {
|
|
453
|
+
const { stdout } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd });
|
|
454
|
+
return stdout.trim();
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
export const getChangedFiles = async (cwd) => {
|
|
461
|
+
try {
|
|
462
|
+
const { stdout: diff } = await execAsync('git diff --name-only', { cwd });
|
|
463
|
+
const { stdout: untracked } = await execAsync('git ls-files --others --exclude-standard', { cwd });
|
|
464
|
+
const { stdout: staged } = await execAsync('git diff --cached --name-only', { cwd });
|
|
465
|
+
const all = [diff, untracked, staged].map(s => s.trim()).join('\n');
|
|
466
|
+
return [...new Set(all.split('\n').filter(Boolean))];
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
return [];
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
export const getTrackedFiles = async (cwd) => {
|
|
473
|
+
try {
|
|
474
|
+
const { stdout } = await execAsync('git ls-files', { cwd });
|
|
475
|
+
return stdout.trim().split('\n').filter(Boolean);
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
export const allBuiltins = [readFiles, writeFiles, createTool, scrapeUrl, listFiles, searchFiles, listDir, runCommand, deleteFile, gitTool, linter];
|