scorpion-cli 0.1.0
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 +152 -0
- package/install.ps1 +22 -0
- package/install.sh +19 -0
- package/package.json +44 -0
- package/src/agent.js +226 -0
- package/src/commands.js +193 -0
- package/src/index.js +86 -0
- package/src/ollama.js +126 -0
- package/src/registry.js +112 -0
- package/src/tools/context.js +432 -0
- package/src/tools/deep-research.js +425 -0
- package/src/tools/documents.js +299 -0
- package/src/tools/filesystem.js +402 -0
- package/src/tools/shell.js +134 -0
- package/src/tools/system.js +379 -0
- package/src/tools/web.js +433 -0
- package/src/ui/charts.js +213 -0
- package/src/ui/export.js +141 -0
- package/src/ui/formatter.js +365 -0
- package/src/ui/images.js +153 -0
- package/src/ui/panels.js +150 -0
- package/src/ui/progress.js +131 -0
- package/src/ui/repl.js +477 -0
- package/src/ui/spinner.js +99 -0
- package/src/ui/table.js +145 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Tools
|
|
3
|
+
* Create summaries, reports, and documents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
// Tool: save_report
|
|
10
|
+
export const saveReport = {
|
|
11
|
+
schema: {
|
|
12
|
+
type: 'function',
|
|
13
|
+
function: {
|
|
14
|
+
name: 'save_report',
|
|
15
|
+
description: 'Save a report or document to a file. Use this to create text files, markdown documents, or reports with structured content.',
|
|
16
|
+
parameters: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
required: ['title', 'content', 'path'],
|
|
19
|
+
properties: {
|
|
20
|
+
title: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Title of the report'
|
|
23
|
+
},
|
|
24
|
+
content: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Main content of the report'
|
|
27
|
+
},
|
|
28
|
+
path: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'File path to save the report (e.g., "report.txt" or "C:/Users/Amaan/report.md")'
|
|
31
|
+
},
|
|
32
|
+
format: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
enum: ['txt', 'md'],
|
|
35
|
+
description: 'Output format (default: based on file extension)'
|
|
36
|
+
},
|
|
37
|
+
include_timestamp: {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
description: 'Include generation timestamp (default: true)'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
execute: async ({ title, content, path: filePath, format, include_timestamp = true }) => {
|
|
46
|
+
try {
|
|
47
|
+
const absolutePath = path.resolve(filePath);
|
|
48
|
+
const ext = format || path.extname(absolutePath).slice(1) || 'txt';
|
|
49
|
+
const isMarkdown = ext === 'md';
|
|
50
|
+
|
|
51
|
+
let fileContent = '';
|
|
52
|
+
|
|
53
|
+
if (isMarkdown) {
|
|
54
|
+
fileContent = `# ${title}\n\n`;
|
|
55
|
+
if (include_timestamp) {
|
|
56
|
+
fileContent += `*Generated: ${new Date().toLocaleString()}*\n\n---\n\n`;
|
|
57
|
+
}
|
|
58
|
+
fileContent += content;
|
|
59
|
+
} else {
|
|
60
|
+
fileContent = `${title}\n${'='.repeat(title.length)}\n\n`;
|
|
61
|
+
if (include_timestamp) {
|
|
62
|
+
fileContent += `Generated: ${new Date().toLocaleString()}\n\n`;
|
|
63
|
+
}
|
|
64
|
+
fileContent += content;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
68
|
+
await fs.writeFile(absolutePath, fileContent, 'utf-8');
|
|
69
|
+
|
|
70
|
+
const stats = await fs.stat(absolutePath);
|
|
71
|
+
|
|
72
|
+
return JSON.stringify({
|
|
73
|
+
success: true,
|
|
74
|
+
path: absolutePath,
|
|
75
|
+
title,
|
|
76
|
+
format: ext,
|
|
77
|
+
size: formatBytes(stats.size),
|
|
78
|
+
message: `Report saved successfully to ${absolutePath}`
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Tool: create_summary
|
|
87
|
+
export const createSummary = {
|
|
88
|
+
schema: {
|
|
89
|
+
type: 'function',
|
|
90
|
+
function: {
|
|
91
|
+
name: 'create_summary',
|
|
92
|
+
description: 'Format content as a structured summary. Returns formatted text that can be displayed or saved.',
|
|
93
|
+
parameters: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
required: ['content'],
|
|
96
|
+
properties: {
|
|
97
|
+
content: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'Content to summarize/format'
|
|
100
|
+
},
|
|
101
|
+
format: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
enum: ['text', 'markdown', 'bullets'],
|
|
104
|
+
description: 'Output format (default: text)'
|
|
105
|
+
},
|
|
106
|
+
title: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'Optional title for the summary'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
execute: async ({ content, format = 'text', title }) => {
|
|
115
|
+
try {
|
|
116
|
+
let formatted = '';
|
|
117
|
+
|
|
118
|
+
switch (format) {
|
|
119
|
+
case 'markdown':
|
|
120
|
+
if (title) {
|
|
121
|
+
formatted = `## ${title}\n\n${content}`;
|
|
122
|
+
} else {
|
|
123
|
+
formatted = content;
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case 'bullets':
|
|
128
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
129
|
+
if (title) {
|
|
130
|
+
formatted = `${title}:\n`;
|
|
131
|
+
}
|
|
132
|
+
formatted += lines.map(l => `• ${l.trim()}`).join('\n');
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case 'text':
|
|
136
|
+
default:
|
|
137
|
+
if (title) {
|
|
138
|
+
formatted = `${title}\n${'-'.repeat(title.length)}\n\n${content}`;
|
|
139
|
+
} else {
|
|
140
|
+
formatted = content;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return JSON.stringify({
|
|
146
|
+
success: true,
|
|
147
|
+
format,
|
|
148
|
+
summary: formatted,
|
|
149
|
+
characterCount: formatted.length
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Tool: create_table
|
|
158
|
+
export const createTable = {
|
|
159
|
+
schema: {
|
|
160
|
+
type: 'function',
|
|
161
|
+
function: {
|
|
162
|
+
name: 'create_table',
|
|
163
|
+
description: 'Create a formatted table from data. Useful for presenting structured information.',
|
|
164
|
+
parameters: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
required: ['headers', 'rows'],
|
|
167
|
+
properties: {
|
|
168
|
+
headers: {
|
|
169
|
+
type: 'array',
|
|
170
|
+
items: { type: 'string' },
|
|
171
|
+
description: 'Column headers'
|
|
172
|
+
},
|
|
173
|
+
rows: {
|
|
174
|
+
type: 'array',
|
|
175
|
+
items: {
|
|
176
|
+
type: 'array',
|
|
177
|
+
items: { type: 'string' }
|
|
178
|
+
},
|
|
179
|
+
description: 'Data rows (array of arrays)'
|
|
180
|
+
},
|
|
181
|
+
format: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
enum: ['markdown', 'text'],
|
|
184
|
+
description: 'Output format (default: markdown)'
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
execute: async ({ headers, rows, format = 'markdown' }) => {
|
|
191
|
+
try {
|
|
192
|
+
let table = '';
|
|
193
|
+
|
|
194
|
+
if (format === 'markdown') {
|
|
195
|
+
// Markdown table
|
|
196
|
+
table = '| ' + headers.join(' | ') + ' |\n';
|
|
197
|
+
table += '| ' + headers.map(() => '---').join(' | ') + ' |\n';
|
|
198
|
+
for (const row of rows) {
|
|
199
|
+
table += '| ' + row.join(' | ') + ' |\n';
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// Text table
|
|
203
|
+
const widths = headers.map((h, i) => {
|
|
204
|
+
const maxRowWidth = Math.max(...rows.map(r => String(r[i] || '').length));
|
|
205
|
+
return Math.max(h.length, maxRowWidth);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const separator = '+' + widths.map(w => '-'.repeat(w + 2)).join('+') + '+';
|
|
209
|
+
|
|
210
|
+
table = separator + '\n';
|
|
211
|
+
table += '| ' + headers.map((h, i) => h.padEnd(widths[i])).join(' | ') + ' |\n';
|
|
212
|
+
table += separator + '\n';
|
|
213
|
+
|
|
214
|
+
for (const row of rows) {
|
|
215
|
+
table += '| ' + row.map((c, i) => String(c || '').padEnd(widths[i])).join(' | ') + ' |\n';
|
|
216
|
+
}
|
|
217
|
+
table += separator;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return JSON.stringify({
|
|
221
|
+
success: true,
|
|
222
|
+
format,
|
|
223
|
+
table,
|
|
224
|
+
rowCount: rows.length,
|
|
225
|
+
columnCount: headers.length
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Tool: append_to_document
|
|
234
|
+
export const appendToDocument = {
|
|
235
|
+
schema: {
|
|
236
|
+
type: 'function',
|
|
237
|
+
function: {
|
|
238
|
+
name: 'append_to_document',
|
|
239
|
+
description: 'Append content to an existing document or create it if it does not exist.',
|
|
240
|
+
parameters: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
required: ['path', 'content'],
|
|
243
|
+
properties: {
|
|
244
|
+
path: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
description: 'Path to the document'
|
|
247
|
+
},
|
|
248
|
+
content: {
|
|
249
|
+
type: 'string',
|
|
250
|
+
description: 'Content to append'
|
|
251
|
+
},
|
|
252
|
+
add_separator: {
|
|
253
|
+
type: 'boolean',
|
|
254
|
+
description: 'Add a separator line before the content (default: true)'
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
execute: async ({ path: filePath, content, add_separator = true }) => {
|
|
261
|
+
try {
|
|
262
|
+
const absolutePath = path.resolve(filePath);
|
|
263
|
+
|
|
264
|
+
let existingContent = '';
|
|
265
|
+
try {
|
|
266
|
+
existingContent = await fs.readFile(absolutePath, 'utf-8');
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// File doesn't exist, will be created
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let newContent = content;
|
|
272
|
+
if (existingContent && add_separator) {
|
|
273
|
+
newContent = '\n\n---\n\n' + content;
|
|
274
|
+
} else if (existingContent) {
|
|
275
|
+
newContent = '\n\n' + content;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await fs.appendFile(absolutePath, newContent, 'utf-8');
|
|
279
|
+
|
|
280
|
+
return JSON.stringify({
|
|
281
|
+
success: true,
|
|
282
|
+
path: absolutePath,
|
|
283
|
+
message: existingContent ? 'Content appended' : 'Document created',
|
|
284
|
+
appendedLength: newContent.length
|
|
285
|
+
});
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Helper function
|
|
293
|
+
function formatBytes(bytes) {
|
|
294
|
+
if (bytes === 0) return '0 B';
|
|
295
|
+
const k = 1024;
|
|
296
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
297
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
298
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
299
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Tools
|
|
3
|
+
* File and directory operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { glob } from 'fs/promises';
|
|
9
|
+
|
|
10
|
+
// Tool: create_file
|
|
11
|
+
export const createFile = {
|
|
12
|
+
schema: {
|
|
13
|
+
type: 'function',
|
|
14
|
+
function: {
|
|
15
|
+
name: 'create_file',
|
|
16
|
+
description: 'Create a new file with the specified content. Creates parent directories if they do not exist.',
|
|
17
|
+
parameters: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
required: ['path', 'content'],
|
|
20
|
+
properties: {
|
|
21
|
+
path: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Absolute or relative path for the new file'
|
|
24
|
+
},
|
|
25
|
+
content: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Content to write to the file'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
execute: async ({ path: filePath, content }) => {
|
|
34
|
+
try {
|
|
35
|
+
const absolutePath = path.resolve(filePath);
|
|
36
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
37
|
+
await fs.writeFile(absolutePath, content, 'utf-8');
|
|
38
|
+
|
|
39
|
+
return JSON.stringify({
|
|
40
|
+
success: true,
|
|
41
|
+
path: absolutePath,
|
|
42
|
+
message: `File created successfully at ${absolutePath}`
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Tool: read_file
|
|
51
|
+
export const readFile = {
|
|
52
|
+
schema: {
|
|
53
|
+
type: 'function',
|
|
54
|
+
function: {
|
|
55
|
+
name: 'read_file',
|
|
56
|
+
description: 'Read the contents of a file',
|
|
57
|
+
parameters: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
required: ['path'],
|
|
60
|
+
properties: {
|
|
61
|
+
path: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'Path to the file to read'
|
|
64
|
+
},
|
|
65
|
+
encoding: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'File encoding (default: utf-8)'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
execute: async ({ path: filePath, encoding = 'utf-8' }) => {
|
|
74
|
+
try {
|
|
75
|
+
const absolutePath = path.resolve(filePath);
|
|
76
|
+
const content = await fs.readFile(absolutePath, encoding);
|
|
77
|
+
const stats = await fs.stat(absolutePath);
|
|
78
|
+
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
success: true,
|
|
81
|
+
path: absolutePath,
|
|
82
|
+
content,
|
|
83
|
+
size: stats.size,
|
|
84
|
+
modified: stats.mtime.toISOString()
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Tool: write_file
|
|
93
|
+
export const writeFile = {
|
|
94
|
+
schema: {
|
|
95
|
+
type: 'function',
|
|
96
|
+
function: {
|
|
97
|
+
name: 'write_file',
|
|
98
|
+
description: 'Write or append content to a file',
|
|
99
|
+
parameters: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
required: ['path', 'content'],
|
|
102
|
+
properties: {
|
|
103
|
+
path: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Path to the file'
|
|
106
|
+
},
|
|
107
|
+
content: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'Content to write'
|
|
110
|
+
},
|
|
111
|
+
append: {
|
|
112
|
+
type: 'boolean',
|
|
113
|
+
description: 'If true, append to file instead of overwriting (default: false)'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
execute: async ({ path: filePath, content, append = false }) => {
|
|
120
|
+
try {
|
|
121
|
+
const absolutePath = path.resolve(filePath);
|
|
122
|
+
|
|
123
|
+
if (append) {
|
|
124
|
+
await fs.appendFile(absolutePath, content, 'utf-8');
|
|
125
|
+
} else {
|
|
126
|
+
await fs.writeFile(absolutePath, content, 'utf-8');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return JSON.stringify({
|
|
130
|
+
success: true,
|
|
131
|
+
path: absolutePath,
|
|
132
|
+
message: append ? 'Content appended' : 'File written'
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Tool: list_directory
|
|
141
|
+
export const listDirectory = {
|
|
142
|
+
schema: {
|
|
143
|
+
type: 'function',
|
|
144
|
+
function: {
|
|
145
|
+
name: 'list_directory',
|
|
146
|
+
description: 'List contents of a directory with file information',
|
|
147
|
+
parameters: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
required: ['path'],
|
|
150
|
+
properties: {
|
|
151
|
+
path: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: 'Directory path to list'
|
|
154
|
+
},
|
|
155
|
+
recursive: {
|
|
156
|
+
type: 'boolean',
|
|
157
|
+
description: 'If true, list recursively (default: false)'
|
|
158
|
+
},
|
|
159
|
+
limit: {
|
|
160
|
+
type: 'integer',
|
|
161
|
+
description: 'Maximum number of items to return (default: 100)'
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
execute: async ({ path: dirPath, recursive = false, limit = 100 }) => {
|
|
168
|
+
try {
|
|
169
|
+
const absolutePath = path.resolve(dirPath);
|
|
170
|
+
const items = [];
|
|
171
|
+
|
|
172
|
+
async function listDir(dir, depth = 0) {
|
|
173
|
+
if (items.length >= limit) return;
|
|
174
|
+
|
|
175
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
176
|
+
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
if (items.length >= limit) break;
|
|
179
|
+
|
|
180
|
+
const fullPath = path.join(dir, entry.name);
|
|
181
|
+
const relativePath = path.relative(absolutePath, fullPath);
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const stats = await fs.stat(fullPath);
|
|
185
|
+
items.push({
|
|
186
|
+
name: entry.name,
|
|
187
|
+
path: relativePath || entry.name,
|
|
188
|
+
type: entry.isDirectory() ? 'directory' : 'file',
|
|
189
|
+
size: entry.isFile() ? stats.size : undefined,
|
|
190
|
+
modified: stats.mtime.toISOString()
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (recursive && entry.isDirectory() && depth < 5) {
|
|
194
|
+
await listDir(fullPath, depth + 1);
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// Skip inaccessible items
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await listDir(absolutePath);
|
|
203
|
+
|
|
204
|
+
return JSON.stringify({
|
|
205
|
+
success: true,
|
|
206
|
+
path: absolutePath,
|
|
207
|
+
count: items.length,
|
|
208
|
+
truncated: items.length >= limit,
|
|
209
|
+
items
|
|
210
|
+
});
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Tool: search_files
|
|
218
|
+
export const searchFiles = {
|
|
219
|
+
schema: {
|
|
220
|
+
type: 'function',
|
|
221
|
+
function: {
|
|
222
|
+
name: 'search_files',
|
|
223
|
+
description: 'Search for files by name pattern in a directory',
|
|
224
|
+
parameters: {
|
|
225
|
+
type: 'object',
|
|
226
|
+
required: ['path', 'pattern'],
|
|
227
|
+
properties: {
|
|
228
|
+
path: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
description: 'Directory to search in'
|
|
231
|
+
},
|
|
232
|
+
pattern: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description: 'Search pattern (e.g., "*.txt", "report*", "**/*.js")'
|
|
235
|
+
},
|
|
236
|
+
limit: {
|
|
237
|
+
type: 'integer',
|
|
238
|
+
description: 'Maximum results (default: 50)'
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
execute: async ({ path: searchPath, pattern, limit = 50 }) => {
|
|
245
|
+
try {
|
|
246
|
+
const absolutePath = path.resolve(searchPath);
|
|
247
|
+
const results = [];
|
|
248
|
+
|
|
249
|
+
// Simple recursive search
|
|
250
|
+
async function searchDir(dir) {
|
|
251
|
+
if (results.length >= limit) return;
|
|
252
|
+
|
|
253
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
254
|
+
|
|
255
|
+
for (const entry of entries) {
|
|
256
|
+
if (results.length >= limit) break;
|
|
257
|
+
|
|
258
|
+
const fullPath = path.join(dir, entry.name);
|
|
259
|
+
|
|
260
|
+
// Simple pattern matching
|
|
261
|
+
const regex = new RegExp(
|
|
262
|
+
pattern
|
|
263
|
+
.replace(/\./g, '\\.')
|
|
264
|
+
.replace(/\*/g, '.*')
|
|
265
|
+
.replace(/\?/g, '.'),
|
|
266
|
+
'i'
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
if (regex.test(entry.name)) {
|
|
270
|
+
try {
|
|
271
|
+
const stats = await fs.stat(fullPath);
|
|
272
|
+
results.push({
|
|
273
|
+
name: entry.name,
|
|
274
|
+
path: fullPath,
|
|
275
|
+
type: entry.isDirectory() ? 'directory' : 'file',
|
|
276
|
+
size: entry.isFile() ? stats.size : undefined
|
|
277
|
+
});
|
|
278
|
+
} catch (e) {
|
|
279
|
+
// Skip inaccessible
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (entry.isDirectory()) {
|
|
284
|
+
try {
|
|
285
|
+
await searchDir(fullPath);
|
|
286
|
+
} catch (e) {
|
|
287
|
+
// Skip inaccessible directories
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await searchDir(absolutePath);
|
|
294
|
+
|
|
295
|
+
return JSON.stringify({
|
|
296
|
+
success: true,
|
|
297
|
+
pattern,
|
|
298
|
+
searchPath: absolutePath,
|
|
299
|
+
count: results.length,
|
|
300
|
+
results
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Tool: delete_file
|
|
309
|
+
export const deleteFile = {
|
|
310
|
+
schema: {
|
|
311
|
+
type: 'function',
|
|
312
|
+
function: {
|
|
313
|
+
name: 'delete_file',
|
|
314
|
+
description: 'Delete a file or empty directory',
|
|
315
|
+
parameters: {
|
|
316
|
+
type: 'object',
|
|
317
|
+
required: ['path'],
|
|
318
|
+
properties: {
|
|
319
|
+
path: {
|
|
320
|
+
type: 'string',
|
|
321
|
+
description: 'Path to the file or directory to delete'
|
|
322
|
+
},
|
|
323
|
+
recursive: {
|
|
324
|
+
type: 'boolean',
|
|
325
|
+
description: 'If true, delete directories recursively (use with caution)'
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
execute: async ({ path: targetPath, recursive = false }) => {
|
|
332
|
+
try {
|
|
333
|
+
const absolutePath = path.resolve(targetPath);
|
|
334
|
+
const stats = await fs.stat(absolutePath);
|
|
335
|
+
|
|
336
|
+
if (stats.isDirectory()) {
|
|
337
|
+
await fs.rm(absolutePath, { recursive });
|
|
338
|
+
} else {
|
|
339
|
+
await fs.unlink(absolutePath);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return JSON.stringify({
|
|
343
|
+
success: true,
|
|
344
|
+
path: absolutePath,
|
|
345
|
+
message: `Deleted ${stats.isDirectory() ? 'directory' : 'file'}: ${absolutePath}`
|
|
346
|
+
});
|
|
347
|
+
} catch (error) {
|
|
348
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Tool: get_file_info
|
|
354
|
+
export const getFileInfo = {
|
|
355
|
+
schema: {
|
|
356
|
+
type: 'function',
|
|
357
|
+
function: {
|
|
358
|
+
name: 'get_file_info',
|
|
359
|
+
description: 'Get detailed information about a file or directory',
|
|
360
|
+
parameters: {
|
|
361
|
+
type: 'object',
|
|
362
|
+
required: ['path'],
|
|
363
|
+
properties: {
|
|
364
|
+
path: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: 'Path to the file or directory'
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
execute: async ({ path: targetPath }) => {
|
|
373
|
+
try {
|
|
374
|
+
const absolutePath = path.resolve(targetPath);
|
|
375
|
+
const stats = await fs.stat(absolutePath);
|
|
376
|
+
|
|
377
|
+
return JSON.stringify({
|
|
378
|
+
success: true,
|
|
379
|
+
path: absolutePath,
|
|
380
|
+
name: path.basename(absolutePath),
|
|
381
|
+
type: stats.isDirectory() ? 'directory' : 'file',
|
|
382
|
+
size: stats.size,
|
|
383
|
+
sizeFormatted: formatBytes(stats.size),
|
|
384
|
+
created: stats.birthtime.toISOString(),
|
|
385
|
+
modified: stats.mtime.toISOString(),
|
|
386
|
+
accessed: stats.atime.toISOString(),
|
|
387
|
+
isReadOnly: !(stats.mode & 0o200)
|
|
388
|
+
});
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return JSON.stringify({ success: false, error: error.message });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Helper function
|
|
396
|
+
function formatBytes(bytes) {
|
|
397
|
+
if (bytes === 0) return '0 B';
|
|
398
|
+
const k = 1024;
|
|
399
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
400
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
401
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
402
|
+
}
|