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.
@@ -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
+ }