@benzsiangco/jarvis 1.0.0 → 1.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 +5 -0
- package/bin/{jarvis.js → jarvis} +1 -1
- package/dist/cli.js +476 -350
- package/dist/electron/main.js +160 -0
- package/dist/electron/preload.js +19 -0
- package/package.json +21 -8
- package/skills.md +147 -0
- package/src/agents/index.ts +248 -0
- package/src/brain/loader.ts +136 -0
- package/src/cli.ts +411 -0
- package/src/config/index.ts +363 -0
- package/src/core/executor.ts +222 -0
- package/src/core/plugins.ts +148 -0
- package/src/core/types.ts +217 -0
- package/src/electron/main.ts +192 -0
- package/src/electron/preload.ts +25 -0
- package/src/electron/types.d.ts +20 -0
- package/src/index.ts +12 -0
- package/src/providers/antigravity-loader.ts +233 -0
- package/src/providers/antigravity.ts +585 -0
- package/src/providers/index.ts +523 -0
- package/src/sessions/index.ts +194 -0
- package/src/tools/index.ts +436 -0
- package/src/tui/index.tsx +784 -0
- package/src/utils/auth-prompt.ts +394 -0
- package/src/utils/index.ts +180 -0
- package/src/utils/native-picker.ts +71 -0
- package/src/utils/skills.ts +99 -0
- package/src/utils/table-integration-examples.ts +617 -0
- package/src/utils/table-utils.ts +401 -0
- package/src/web/build-ui.ts +27 -0
- package/src/web/server.ts +674 -0
- package/src/web/ui/dist/.gitkeep +0 -0
- package/src/web/ui/dist/main.css +1 -0
- package/src/web/ui/dist/main.js +320 -0
- package/src/web/ui/dist/main.js.map +20 -0
- package/src/web/ui/index.html +46 -0
- package/src/web/ui/src/App.tsx +143 -0
- package/src/web/ui/src/Modules/Safety/GuardianModal.tsx +83 -0
- package/src/web/ui/src/components/Layout/ContextPanel.tsx +243 -0
- package/src/web/ui/src/components/Layout/Header.tsx +91 -0
- package/src/web/ui/src/components/Layout/ModelSelector.tsx +235 -0
- package/src/web/ui/src/components/Layout/SessionStats.tsx +369 -0
- package/src/web/ui/src/components/Layout/Sidebar.tsx +895 -0
- package/src/web/ui/src/components/Modules/Chat/ChatStage.tsx +620 -0
- package/src/web/ui/src/components/Modules/Chat/MessageItem.tsx +446 -0
- package/src/web/ui/src/components/Modules/Editor/CommandInspector.tsx +71 -0
- package/src/web/ui/src/components/Modules/Editor/DiffViewer.tsx +83 -0
- package/src/web/ui/src/components/Modules/Terminal/TabbedTerminal.tsx +202 -0
- package/src/web/ui/src/components/Settings/SettingsModal.tsx +935 -0
- package/src/web/ui/src/config/models.ts +70 -0
- package/src/web/ui/src/main.tsx +13 -0
- package/src/web/ui/src/store/agentStore.ts +41 -0
- package/src/web/ui/src/store/uiStore.ts +64 -0
- package/src/web/ui/src/types/index.ts +54 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
// Tool definitions for Jarvis
|
|
2
|
+
import type { Tool, ToolContext, ToolResult } from '../core/types';
|
|
3
|
+
import { execSync, spawn } from 'child_process';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, statSync, readdirSync } from 'fs';
|
|
5
|
+
import { resolve, relative, join } from 'path';
|
|
6
|
+
import { glob as globSync } from 'glob';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
// Helper to create a tool result
|
|
10
|
+
function success(content: string, metadata?: Record<string, unknown>): ToolResult {
|
|
11
|
+
return { success: true, content, metadata };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function error(message: string): ToolResult {
|
|
15
|
+
return { success: false, content: '', error: message };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Bash tool
|
|
19
|
+
export const bashTool: Tool = {
|
|
20
|
+
name: 'bash',
|
|
21
|
+
description: 'Execute a bash/shell command. Use for git, npm, system commands, etc.',
|
|
22
|
+
parameters: z.object({
|
|
23
|
+
command: z.string().describe('The command to execute'),
|
|
24
|
+
workdir: z.string().optional().describe('Working directory for the command'),
|
|
25
|
+
timeout: z.number().optional().describe('Timeout in milliseconds (default: 120000)'),
|
|
26
|
+
description: z.string().describe('Brief description of what this command does'),
|
|
27
|
+
}),
|
|
28
|
+
async execute(args, context) {
|
|
29
|
+
const command = args['command'] as string;
|
|
30
|
+
const workdir = (args['workdir'] as string) || context.workdir;
|
|
31
|
+
const timeout = (args['timeout'] as number) || 120000;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const result = execSync(command, {
|
|
35
|
+
cwd: workdir,
|
|
36
|
+
timeout,
|
|
37
|
+
encoding: 'utf-8',
|
|
38
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash',
|
|
41
|
+
});
|
|
42
|
+
return success(result);
|
|
43
|
+
} catch (err: unknown) {
|
|
44
|
+
const execError = err as { stderr?: string; stdout?: string; message?: string };
|
|
45
|
+
const output = execError.stdout || execError.stderr || execError.message || 'Command failed';
|
|
46
|
+
return error(output);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Read tool
|
|
52
|
+
export const readTool: Tool = {
|
|
53
|
+
name: 'read',
|
|
54
|
+
description: 'Read contents of a file',
|
|
55
|
+
parameters: z.object({
|
|
56
|
+
filePath: z.string().describe('Absolute path to the file to read'),
|
|
57
|
+
offset: z.number().optional().describe('Line number to start reading from (0-based)'),
|
|
58
|
+
limit: z.number().optional().describe('Number of lines to read (default: 2000)'),
|
|
59
|
+
}),
|
|
60
|
+
async execute(args, context) {
|
|
61
|
+
const filePath = args['filePath'] as string;
|
|
62
|
+
const offset = (args['offset'] as number) || 0;
|
|
63
|
+
const limit = (args['limit'] as number) || 2000;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const absolutePath = resolve(context.workdir, filePath);
|
|
67
|
+
|
|
68
|
+
if (!existsSync(absolutePath)) {
|
|
69
|
+
return error(`File not found: ${filePath}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const content = readFileSync(absolutePath, 'utf-8');
|
|
73
|
+
const lines = content.split('\n');
|
|
74
|
+
const selectedLines = lines.slice(offset, offset + limit);
|
|
75
|
+
|
|
76
|
+
// Add line numbers
|
|
77
|
+
const numberedLines = selectedLines.map((line, i) => {
|
|
78
|
+
const lineNum = String(offset + i + 1).padStart(5, ' ');
|
|
79
|
+
return `${lineNum}| ${line}`;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return success(numberedLines.join('\n'), {
|
|
83
|
+
totalLines: lines.length,
|
|
84
|
+
readLines: selectedLines.length,
|
|
85
|
+
});
|
|
86
|
+
} catch (err: unknown) {
|
|
87
|
+
return error((err as Error).message);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Write tool
|
|
93
|
+
export const writeTool: Tool = {
|
|
94
|
+
name: 'write',
|
|
95
|
+
description: 'Write content to a file (creates or overwrites)',
|
|
96
|
+
parameters: z.object({
|
|
97
|
+
filePath: z.string().describe('Absolute path to the file to write'),
|
|
98
|
+
content: z.string().describe('Content to write to the file'),
|
|
99
|
+
}),
|
|
100
|
+
async execute(args, context) {
|
|
101
|
+
const filePath = args['filePath'] as string;
|
|
102
|
+
const content = args['content'] as string;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const absolutePath = resolve(context.workdir, filePath);
|
|
106
|
+
writeFileSync(absolutePath, content, 'utf-8');
|
|
107
|
+
return success(`Successfully wrote to ${filePath}`);
|
|
108
|
+
} catch (err: unknown) {
|
|
109
|
+
return error((err as Error).message);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Edit tool
|
|
115
|
+
export const editTool: Tool = {
|
|
116
|
+
name: 'edit',
|
|
117
|
+
description: 'Replace text in a file. oldString must match exactly.',
|
|
118
|
+
parameters: z.object({
|
|
119
|
+
filePath: z.string().describe('Absolute path to the file to edit'),
|
|
120
|
+
oldString: z.string().describe('The text to replace (must match exactly)'),
|
|
121
|
+
newString: z.string().describe('The replacement text'),
|
|
122
|
+
replaceAll: z.boolean().optional().describe('Replace all occurrences (default: false)'),
|
|
123
|
+
}),
|
|
124
|
+
async execute(args, context) {
|
|
125
|
+
const filePath = args['filePath'] as string;
|
|
126
|
+
const oldString = args['oldString'] as string;
|
|
127
|
+
const newString = args['newString'] as string;
|
|
128
|
+
const replaceAll = args['replaceAll'] as boolean || false;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const absolutePath = resolve(context.workdir, filePath);
|
|
132
|
+
|
|
133
|
+
if (!existsSync(absolutePath)) {
|
|
134
|
+
return error(`File not found: ${filePath}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let content = readFileSync(absolutePath, 'utf-8');
|
|
138
|
+
|
|
139
|
+
if (!content.includes(oldString)) {
|
|
140
|
+
return error('oldString not found in content');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const occurrences = content.split(oldString).length - 1;
|
|
144
|
+
|
|
145
|
+
if (occurrences > 1 && !replaceAll) {
|
|
146
|
+
return error('oldString found multiple times. Use replaceAll: true or provide more context.');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (replaceAll) {
|
|
150
|
+
content = content.split(oldString).join(newString);
|
|
151
|
+
} else {
|
|
152
|
+
content = content.replace(oldString, newString);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
writeFileSync(absolutePath, content, 'utf-8');
|
|
156
|
+
return success(`Successfully edited ${filePath}`, { replacements: replaceAll ? occurrences : 1 });
|
|
157
|
+
} catch (err: unknown) {
|
|
158
|
+
return error((err as Error).message);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Glob tool
|
|
164
|
+
export const globTool: Tool = {
|
|
165
|
+
name: 'glob',
|
|
166
|
+
description: 'Find files matching a glob pattern',
|
|
167
|
+
parameters: z.object({
|
|
168
|
+
pattern: z.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx")'),
|
|
169
|
+
path: z.string().optional().describe('Directory to search in'),
|
|
170
|
+
}),
|
|
171
|
+
async execute(args, context) {
|
|
172
|
+
const pattern = args['pattern'] as string;
|
|
173
|
+
const searchPath = (args['path'] as string) || context.workdir;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const files = await globSync(pattern, {
|
|
177
|
+
cwd: searchPath,
|
|
178
|
+
nodir: true,
|
|
179
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (files.length === 0) {
|
|
183
|
+
return success('No files found matching pattern.');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return success(files.join('\n'), { count: files.length });
|
|
187
|
+
} catch (err: unknown) {
|
|
188
|
+
return error((err as Error).message);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Grep tool
|
|
194
|
+
export const grepTool: Tool = {
|
|
195
|
+
name: 'grep',
|
|
196
|
+
description: 'Search for a regex pattern in files',
|
|
197
|
+
parameters: z.object({
|
|
198
|
+
pattern: z.string().describe('Regex pattern to search for'),
|
|
199
|
+
path: z.string().optional().describe('Directory to search in'),
|
|
200
|
+
include: z.string().optional().describe('File pattern to include (e.g., "*.ts")'),
|
|
201
|
+
}),
|
|
202
|
+
async execute(args, context) {
|
|
203
|
+
const pattern = args['pattern'] as string;
|
|
204
|
+
const searchPath = (args['path'] as string) || context.workdir;
|
|
205
|
+
const include = args['include'] as string || '*';
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// Use ripgrep if available, fallback to basic search
|
|
209
|
+
const rgCommand = `rg --line-number --no-heading "${pattern}" --glob "${include}" --glob "!node_modules" --glob "!.git"`;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const result = execSync(rgCommand, {
|
|
213
|
+
cwd: searchPath,
|
|
214
|
+
encoding: 'utf-8',
|
|
215
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
216
|
+
});
|
|
217
|
+
return success(result);
|
|
218
|
+
} catch {
|
|
219
|
+
// Fallback: basic file search
|
|
220
|
+
const files = await globSync(`**/${include}`, {
|
|
221
|
+
cwd: searchPath,
|
|
222
|
+
nodir: true,
|
|
223
|
+
ignore: ['node_modules/**', '.git/**'],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const regex = new RegExp(pattern, 'g');
|
|
227
|
+
const results: string[] = [];
|
|
228
|
+
|
|
229
|
+
for (const file of files.slice(0, 100)) { // Limit files
|
|
230
|
+
try {
|
|
231
|
+
const content = readFileSync(join(searchPath, file), 'utf-8');
|
|
232
|
+
const lines = content.split('\n');
|
|
233
|
+
|
|
234
|
+
lines.forEach((line, i) => {
|
|
235
|
+
if (regex.test(line)) {
|
|
236
|
+
results.push(`${file}:${i + 1}:${line.substring(0, 200)}`);
|
|
237
|
+
}
|
|
238
|
+
regex.lastIndex = 0; // Reset regex state
|
|
239
|
+
});
|
|
240
|
+
} catch {
|
|
241
|
+
// Skip unreadable files
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (results.length === 0) {
|
|
246
|
+
return success('No matches found.');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return success(results.join('\n'), { count: results.length });
|
|
250
|
+
}
|
|
251
|
+
} catch (err: unknown) {
|
|
252
|
+
return error((err as Error).message);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// List tool (directory listing)
|
|
258
|
+
export const listTool: Tool = {
|
|
259
|
+
name: 'list',
|
|
260
|
+
description: 'List files and directories in a path',
|
|
261
|
+
parameters: z.object({
|
|
262
|
+
path: z.string().describe('Directory path to list'),
|
|
263
|
+
recursive: z.boolean().optional().describe('List recursively'),
|
|
264
|
+
}),
|
|
265
|
+
async execute(args, context) {
|
|
266
|
+
const dirPath = (args['path'] as string) || context.workdir;
|
|
267
|
+
const recursive = args['recursive'] as boolean || false;
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const absolutePath = resolve(context.workdir, dirPath);
|
|
271
|
+
|
|
272
|
+
if (!existsSync(absolutePath)) {
|
|
273
|
+
return error(`Directory not found: ${dirPath}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (recursive) {
|
|
277
|
+
const files = await globSync('**/*', {
|
|
278
|
+
cwd: absolutePath,
|
|
279
|
+
ignore: ['node_modules/**', '.git/**'],
|
|
280
|
+
});
|
|
281
|
+
return success(files.join('\n'), { count: files.length });
|
|
282
|
+
} else {
|
|
283
|
+
const entries = readdirSync(absolutePath, { withFileTypes: true });
|
|
284
|
+
const output = entries.map(e => {
|
|
285
|
+
const type = e.isDirectory() ? '[D]' : '[F]';
|
|
286
|
+
return `${type} ${e.name}`;
|
|
287
|
+
});
|
|
288
|
+
return success(output.join('\n'), { count: entries.length });
|
|
289
|
+
}
|
|
290
|
+
} catch (err: unknown) {
|
|
291
|
+
return error((err as Error).message);
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// WebFetch tool
|
|
297
|
+
export const webfetchTool: Tool = {
|
|
298
|
+
name: 'webfetch',
|
|
299
|
+
description: 'Fetch content from a URL',
|
|
300
|
+
parameters: z.object({
|
|
301
|
+
url: z.string().describe('URL to fetch'),
|
|
302
|
+
format: z.enum(['text', 'markdown', 'html']).optional().describe('Response format (default: markdown)'),
|
|
303
|
+
timeout: z.number().optional().describe('Timeout in seconds (max 120)'),
|
|
304
|
+
}),
|
|
305
|
+
async execute(args, context) {
|
|
306
|
+
const url = args['url'] as string;
|
|
307
|
+
const format = (args['format'] as string) || 'markdown';
|
|
308
|
+
const timeout = ((args['timeout'] as number) || 30) * 1000;
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const controller = new AbortController();
|
|
312
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
313
|
+
|
|
314
|
+
const response = await fetch(url, {
|
|
315
|
+
headers: {
|
|
316
|
+
'User-Agent': 'Jarvis-AI/1.0',
|
|
317
|
+
},
|
|
318
|
+
signal: controller.signal,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
clearTimeout(timeoutId);
|
|
322
|
+
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
return error(`HTTP ${response.status}: ${response.statusText}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const content = await response.text();
|
|
328
|
+
|
|
329
|
+
// For now, return raw content. In future, add markdown conversion
|
|
330
|
+
return success(content.substring(0, 50000), {
|
|
331
|
+
status: response.status,
|
|
332
|
+
contentType: response.headers.get('content-type'),
|
|
333
|
+
});
|
|
334
|
+
} catch (err: unknown) {
|
|
335
|
+
return error((err as Error).message);
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// Task tool (spawn subagent)
|
|
341
|
+
export const taskTool: Tool = {
|
|
342
|
+
name: 'task',
|
|
343
|
+
description: 'Launch a subagent to handle a complex task',
|
|
344
|
+
parameters: z.object({
|
|
345
|
+
description: z.string().describe('Short description of the task (3-5 words)'),
|
|
346
|
+
prompt: z.string().describe('Detailed instructions for the subagent'),
|
|
347
|
+
subagent_type: z.string().describe('Type of agent: "general" or "explore"'),
|
|
348
|
+
session_id: z.string().optional().describe('Existing session to continue'),
|
|
349
|
+
}),
|
|
350
|
+
async execute(args, context) {
|
|
351
|
+
// This will be implemented by the agent executor
|
|
352
|
+
return success('Task tool placeholder - will be implemented by agent executor');
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Question tool
|
|
357
|
+
export const questionTool: Tool = {
|
|
358
|
+
name: 'question',
|
|
359
|
+
description: 'Ask the user a question to gather preferences or clarify instructions',
|
|
360
|
+
parameters: z.object({
|
|
361
|
+
questions: z.array(z.object({
|
|
362
|
+
question: z.string().describe('The question text'),
|
|
363
|
+
header: z.string().describe('Short label (max 12 chars)'),
|
|
364
|
+
options: z.array(z.object({
|
|
365
|
+
label: z.string(),
|
|
366
|
+
description: z.string(),
|
|
367
|
+
})),
|
|
368
|
+
multiple: z.boolean().optional().describe('Allow multiple selections'),
|
|
369
|
+
})),
|
|
370
|
+
}),
|
|
371
|
+
async execute(args, context) {
|
|
372
|
+
// This will be handled by the TUI
|
|
373
|
+
return success('Question tool placeholder - will be handled by TUI');
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// TodoRead tool
|
|
378
|
+
export const todoReadTool: Tool = {
|
|
379
|
+
name: 'todoread',
|
|
380
|
+
description: 'Read the current todo list',
|
|
381
|
+
parameters: z.object({
|
|
382
|
+
_placeholder: z.boolean().describe('Always pass true'),
|
|
383
|
+
}),
|
|
384
|
+
async execute(args, context) {
|
|
385
|
+
// Todo state is managed by the session
|
|
386
|
+
return success('TodoRead placeholder - managed by session');
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// TodoWrite tool
|
|
391
|
+
export const todoWriteTool: Tool = {
|
|
392
|
+
name: 'todowrite',
|
|
393
|
+
description: 'Create or update the todo list',
|
|
394
|
+
parameters: z.object({
|
|
395
|
+
todos: z.array(z.object({
|
|
396
|
+
id: z.string(),
|
|
397
|
+
content: z.string(),
|
|
398
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']),
|
|
399
|
+
priority: z.enum(['high', 'medium', 'low']),
|
|
400
|
+
})),
|
|
401
|
+
}),
|
|
402
|
+
async execute(args, context) {
|
|
403
|
+
// Todo state is managed by the session
|
|
404
|
+
return success('TodoWrite placeholder - managed by session');
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Tool registry
|
|
409
|
+
const tools = new Map<string, Tool>();
|
|
410
|
+
|
|
411
|
+
export function initializeTools(): void {
|
|
412
|
+
tools.clear();
|
|
413
|
+
tools.set('bash', bashTool);
|
|
414
|
+
tools.set('read', readTool);
|
|
415
|
+
tools.set('write', writeTool);
|
|
416
|
+
tools.set('edit', editTool);
|
|
417
|
+
tools.set('glob', globTool);
|
|
418
|
+
tools.set('grep', grepTool);
|
|
419
|
+
tools.set('list', listTool);
|
|
420
|
+
tools.set('webfetch', webfetchTool);
|
|
421
|
+
tools.set('task', taskTool);
|
|
422
|
+
tools.set('question', questionTool);
|
|
423
|
+
tools.set('todoread', todoReadTool);
|
|
424
|
+
tools.set('todowrite', todoWriteTool);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function getTool(name: string): Tool | undefined {
|
|
428
|
+
return tools.get(name);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function getAllTools(): Tool[] {
|
|
432
|
+
return Array.from(tools.values());
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Initialize on module load
|
|
436
|
+
initializeTools();
|