@bluehawks/cli 1.0.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.
Files changed (176) hide show
  1. package/.eslintrc.json +36 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +288 -0
  4. package/dist/cli/app.d.ts +12 -0
  5. package/dist/cli/app.d.ts.map +1 -0
  6. package/dist/cli/app.js +201 -0
  7. package/dist/cli/app.js.map +1 -0
  8. package/dist/cli/commands/index.d.ts +56 -0
  9. package/dist/cli/commands/index.d.ts.map +1 -0
  10. package/dist/cli/commands/index.js +201 -0
  11. package/dist/cli/commands/index.js.map +1 -0
  12. package/dist/config/constants.d.ts +32 -0
  13. package/dist/config/constants.d.ts.map +1 -0
  14. package/dist/config/constants.js +39 -0
  15. package/dist/config/constants.js.map +1 -0
  16. package/dist/config/index.d.ts +4 -0
  17. package/dist/config/index.d.ts.map +1 -0
  18. package/dist/config/index.js +4 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/config/schema.d.ts +56 -0
  21. package/dist/config/schema.d.ts.map +1 -0
  22. package/dist/config/schema.js +28 -0
  23. package/dist/config/schema.js.map +1 -0
  24. package/dist/config/settings.d.ts +20 -0
  25. package/dist/config/settings.d.ts.map +1 -0
  26. package/dist/config/settings.js +102 -0
  27. package/dist/config/settings.js.map +1 -0
  28. package/dist/core/agents/agent.d.ts +33 -0
  29. package/dist/core/agents/agent.d.ts.map +1 -0
  30. package/dist/core/agents/agent.js +156 -0
  31. package/dist/core/agents/agent.js.map +1 -0
  32. package/dist/core/agents/index.d.ts +3 -0
  33. package/dist/core/agents/index.d.ts.map +1 -0
  34. package/dist/core/agents/index.js +3 -0
  35. package/dist/core/agents/index.js.map +1 -0
  36. package/dist/core/agents/orchestrator.d.ts +56 -0
  37. package/dist/core/agents/orchestrator.d.ts.map +1 -0
  38. package/dist/core/agents/orchestrator.js +151 -0
  39. package/dist/core/agents/orchestrator.js.map +1 -0
  40. package/dist/core/api/client.d.ts +46 -0
  41. package/dist/core/api/client.d.ts.map +1 -0
  42. package/dist/core/api/client.js +223 -0
  43. package/dist/core/api/client.js.map +1 -0
  44. package/dist/core/api/index.d.ts +3 -0
  45. package/dist/core/api/index.d.ts.map +1 -0
  46. package/dist/core/api/index.js +3 -0
  47. package/dist/core/api/index.js.map +1 -0
  48. package/dist/core/api/types.d.ts +126 -0
  49. package/dist/core/api/types.d.ts.map +1 -0
  50. package/dist/core/api/types.js +16 -0
  51. package/dist/core/api/types.js.map +1 -0
  52. package/dist/core/hooks/index.d.ts +3 -0
  53. package/dist/core/hooks/index.d.ts.map +1 -0
  54. package/dist/core/hooks/index.js +3 -0
  55. package/dist/core/hooks/index.js.map +1 -0
  56. package/dist/core/hooks/manager.d.ts +43 -0
  57. package/dist/core/hooks/manager.d.ts.map +1 -0
  58. package/dist/core/hooks/manager.js +178 -0
  59. package/dist/core/hooks/manager.js.map +1 -0
  60. package/dist/core/hooks/types.d.ts +68 -0
  61. package/dist/core/hooks/types.d.ts.map +1 -0
  62. package/dist/core/hooks/types.js +6 -0
  63. package/dist/core/hooks/types.js.map +1 -0
  64. package/dist/core/mcp/client.d.ts +48 -0
  65. package/dist/core/mcp/client.d.ts.map +1 -0
  66. package/dist/core/mcp/client.js +139 -0
  67. package/dist/core/mcp/client.js.map +1 -0
  68. package/dist/core/mcp/index.d.ts +3 -0
  69. package/dist/core/mcp/index.d.ts.map +1 -0
  70. package/dist/core/mcp/index.js +3 -0
  71. package/dist/core/mcp/index.js.map +1 -0
  72. package/dist/core/mcp/manager.d.ts +46 -0
  73. package/dist/core/mcp/manager.d.ts.map +1 -0
  74. package/dist/core/mcp/manager.js +133 -0
  75. package/dist/core/mcp/manager.js.map +1 -0
  76. package/dist/core/plugins/index.d.ts +3 -0
  77. package/dist/core/plugins/index.d.ts.map +1 -0
  78. package/dist/core/plugins/index.js +3 -0
  79. package/dist/core/plugins/index.js.map +1 -0
  80. package/dist/core/plugins/loader.d.ts +63 -0
  81. package/dist/core/plugins/loader.d.ts.map +1 -0
  82. package/dist/core/plugins/loader.js +258 -0
  83. package/dist/core/plugins/loader.js.map +1 -0
  84. package/dist/core/plugins/types.d.ts +95 -0
  85. package/dist/core/plugins/types.d.ts.map +1 -0
  86. package/dist/core/plugins/types.js +6 -0
  87. package/dist/core/plugins/types.js.map +1 -0
  88. package/dist/core/session/index.d.ts +3 -0
  89. package/dist/core/session/index.d.ts.map +1 -0
  90. package/dist/core/session/index.js +3 -0
  91. package/dist/core/session/index.js.map +1 -0
  92. package/dist/core/session/manager.d.ts +57 -0
  93. package/dist/core/session/manager.d.ts.map +1 -0
  94. package/dist/core/session/manager.js +182 -0
  95. package/dist/core/session/manager.js.map +1 -0
  96. package/dist/core/session/storage.d.ts +42 -0
  97. package/dist/core/session/storage.d.ts.map +1 -0
  98. package/dist/core/session/storage.js +138 -0
  99. package/dist/core/session/storage.js.map +1 -0
  100. package/dist/core/tools/definitions/file.d.ts +6 -0
  101. package/dist/core/tools/definitions/file.d.ts.map +1 -0
  102. package/dist/core/tools/definitions/file.js +276 -0
  103. package/dist/core/tools/definitions/file.js.map +1 -0
  104. package/dist/core/tools/definitions/git.d.ts +6 -0
  105. package/dist/core/tools/definitions/git.d.ts.map +1 -0
  106. package/dist/core/tools/definitions/git.js +294 -0
  107. package/dist/core/tools/definitions/git.js.map +1 -0
  108. package/dist/core/tools/definitions/index.d.ts +11 -0
  109. package/dist/core/tools/definitions/index.d.ts.map +1 -0
  110. package/dist/core/tools/definitions/index.js +22 -0
  111. package/dist/core/tools/definitions/index.js.map +1 -0
  112. package/dist/core/tools/definitions/search.d.ts +6 -0
  113. package/dist/core/tools/definitions/search.d.ts.map +1 -0
  114. package/dist/core/tools/definitions/search.js +223 -0
  115. package/dist/core/tools/definitions/search.js.map +1 -0
  116. package/dist/core/tools/definitions/shell.d.ts +6 -0
  117. package/dist/core/tools/definitions/shell.d.ts.map +1 -0
  118. package/dist/core/tools/definitions/shell.js +190 -0
  119. package/dist/core/tools/definitions/shell.js.map +1 -0
  120. package/dist/core/tools/definitions/web.d.ts +6 -0
  121. package/dist/core/tools/definitions/web.d.ts.map +1 -0
  122. package/dist/core/tools/definitions/web.js +104 -0
  123. package/dist/core/tools/definitions/web.js.map +1 -0
  124. package/dist/core/tools/executor.d.ts +24 -0
  125. package/dist/core/tools/executor.d.ts.map +1 -0
  126. package/dist/core/tools/executor.js +111 -0
  127. package/dist/core/tools/executor.js.map +1 -0
  128. package/dist/core/tools/index.d.ts +4 -0
  129. package/dist/core/tools/index.d.ts.map +1 -0
  130. package/dist/core/tools/index.js +4 -0
  131. package/dist/core/tools/index.js.map +1 -0
  132. package/dist/core/tools/registry.d.ts +23 -0
  133. package/dist/core/tools/registry.d.ts.map +1 -0
  134. package/dist/core/tools/registry.js +28 -0
  135. package/dist/core/tools/registry.js.map +1 -0
  136. package/dist/index.d.ts +7 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +352 -0
  139. package/dist/index.js.map +1 -0
  140. package/package.json +62 -0
  141. package/src/cli/app.tsx +319 -0
  142. package/src/cli/commands/index.ts +261 -0
  143. package/src/config/constants.ts +45 -0
  144. package/src/config/index.ts +3 -0
  145. package/src/config/schema.ts +36 -0
  146. package/src/config/settings.ts +121 -0
  147. package/src/core/agents/agent.ts +205 -0
  148. package/src/core/agents/index.ts +2 -0
  149. package/src/core/agents/orchestrator.ts +223 -0
  150. package/src/core/api/client.ts +300 -0
  151. package/src/core/api/index.ts +2 -0
  152. package/src/core/api/types.ts +149 -0
  153. package/src/core/hooks/index.ts +2 -0
  154. package/src/core/hooks/manager.ts +212 -0
  155. package/src/core/hooks/types.ts +116 -0
  156. package/src/core/mcp/client.ts +198 -0
  157. package/src/core/mcp/index.ts +2 -0
  158. package/src/core/mcp/manager.ts +153 -0
  159. package/src/core/plugins/index.ts +2 -0
  160. package/src/core/plugins/loader.ts +312 -0
  161. package/src/core/plugins/types.ts +111 -0
  162. package/src/core/session/index.ts +2 -0
  163. package/src/core/session/manager.ts +246 -0
  164. package/src/core/session/storage.ts +184 -0
  165. package/src/core/tools/definitions/file.ts +312 -0
  166. package/src/core/tools/definitions/git.ts +326 -0
  167. package/src/core/tools/definitions/index.ts +24 -0
  168. package/src/core/tools/definitions/search.ts +266 -0
  169. package/src/core/tools/definitions/shell.ts +228 -0
  170. package/src/core/tools/definitions/web.ts +113 -0
  171. package/src/core/tools/executor.ts +145 -0
  172. package/src/core/tools/index.ts +3 -0
  173. package/src/core/tools/registry.ts +44 -0
  174. package/src/index.ts +407 -0
  175. package/tsconfig.json +40 -0
  176. package/vitest.config.ts +13 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Bluehawks CLI - Git Tools
3
+ * Tools for Git operations
4
+ */
5
+
6
+ import { simpleGit, type SimpleGit, type StatusResult } from 'simple-git';
7
+ import { toolRegistry, type ToolHandler } from '../registry.js';
8
+
9
+ function getGit(cwd?: string): SimpleGit {
10
+ return simpleGit(cwd || process.cwd());
11
+ }
12
+
13
+ const gitStatusTool: ToolHandler = {
14
+ name: 'git_status',
15
+ safeToAutoRun: true,
16
+ definition: {
17
+ type: 'function',
18
+ function: {
19
+ name: 'git_status',
20
+ description: 'Get the current Git status of the repository.',
21
+ parameters: {
22
+ type: 'object',
23
+ properties: {
24
+ cwd: {
25
+ type: 'string',
26
+ description: 'The working directory. Defaults to current directory.',
27
+ },
28
+ },
29
+ required: [],
30
+ },
31
+ },
32
+ },
33
+ async execute(args) {
34
+ const cwd = args.cwd as string | undefined;
35
+ const git = getGit(cwd);
36
+
37
+ const status: StatusResult = await git.status();
38
+
39
+ const lines: string[] = [];
40
+ lines.push(`Branch: ${status.current || 'unknown'}`);
41
+ lines.push(`Tracking: ${status.tracking || 'none'}`);
42
+
43
+ if (status.ahead > 0) lines.push(`Ahead: ${status.ahead} commits`);
44
+ if (status.behind > 0) lines.push(`Behind: ${status.behind} commits`);
45
+
46
+ if (status.staged.length > 0) {
47
+ lines.push('\nStaged changes:');
48
+ status.staged.forEach((f) => lines.push(` ✓ ${f}`));
49
+ }
50
+
51
+ if (status.modified.length > 0) {
52
+ lines.push('\nModified:');
53
+ status.modified.forEach((f) => lines.push(` M ${f}`));
54
+ }
55
+
56
+ if (status.not_added.length > 0) {
57
+ lines.push('\nUntracked:');
58
+ status.not_added.forEach((f) => lines.push(` ? ${f}`));
59
+ }
60
+
61
+ if (status.deleted.length > 0) {
62
+ lines.push('\nDeleted:');
63
+ status.deleted.forEach((f) => lines.push(` D ${f}`));
64
+ }
65
+
66
+ if (
67
+ status.staged.length === 0 &&
68
+ status.modified.length === 0 &&
69
+ status.not_added.length === 0 &&
70
+ status.deleted.length === 0
71
+ ) {
72
+ lines.push('\nWorking tree clean');
73
+ }
74
+
75
+ return lines.join('\n');
76
+ },
77
+ };
78
+
79
+ const gitDiffTool: ToolHandler = {
80
+ name: 'git_diff',
81
+ safeToAutoRun: true,
82
+ definition: {
83
+ type: 'function',
84
+ function: {
85
+ name: 'git_diff',
86
+ description: 'Show git diff for the repository or specific files.',
87
+ parameters: {
88
+ type: 'object',
89
+ properties: {
90
+ files: {
91
+ type: 'array',
92
+ items: { type: 'string' },
93
+ description: 'Optional list of files to show diff for.',
94
+ },
95
+ staged: {
96
+ type: 'boolean',
97
+ description: 'Show diff for staged changes. Default is false.',
98
+ },
99
+ cwd: {
100
+ type: 'string',
101
+ description: 'The working directory.',
102
+ },
103
+ },
104
+ required: [],
105
+ },
106
+ },
107
+ },
108
+ async execute(args) {
109
+ const files = args.files as string[] | undefined;
110
+ const staged = (args.staged as boolean) ?? false;
111
+ const cwd = args.cwd as string | undefined;
112
+
113
+ const git = getGit(cwd);
114
+
115
+ const options = staged ? ['--staged'] : [];
116
+ if (files && files.length > 0) {
117
+ options.push('--', ...files);
118
+ }
119
+
120
+ const diff = await git.diff(options);
121
+
122
+ if (!diff.trim()) {
123
+ return staged ? 'No staged changes' : 'No changes';
124
+ }
125
+
126
+ return diff;
127
+ },
128
+ };
129
+
130
+ const gitLogTool: ToolHandler = {
131
+ name: 'git_log',
132
+ safeToAutoRun: true,
133
+ definition: {
134
+ type: 'function',
135
+ function: {
136
+ name: 'git_log',
137
+ description: 'Show git commit history.',
138
+ parameters: {
139
+ type: 'object',
140
+ properties: {
141
+ count: {
142
+ type: 'number',
143
+ description: 'Number of commits to show. Default is 10.',
144
+ },
145
+ file: {
146
+ type: 'string',
147
+ description: 'Show history for a specific file.',
148
+ },
149
+ cwd: {
150
+ type: 'string',
151
+ description: 'The working directory.',
152
+ },
153
+ },
154
+ required: [],
155
+ },
156
+ },
157
+ },
158
+ async execute(args) {
159
+ const count = (args.count as number) || 10;
160
+ const file = args.file as string | undefined;
161
+ const cwd = args.cwd as string | undefined;
162
+
163
+ const git = getGit(cwd);
164
+
165
+ const options: string[] = [`-n`, `${count}`, '--oneline'];
166
+ if (file) {
167
+ options.push('--', file);
168
+ }
169
+
170
+ const log = await git.log(options);
171
+
172
+ if (!log.all || log.all.length === 0) {
173
+ return 'No commits found';
174
+ }
175
+
176
+ return log.all
177
+ .map((commit) => `${commit.hash.substring(0, 7)} ${commit.message}`)
178
+ .join('\n');
179
+ },
180
+ };
181
+
182
+ const gitCommitTool: ToolHandler = {
183
+ name: 'git_commit',
184
+ safeToAutoRun: false,
185
+ definition: {
186
+ type: 'function',
187
+ function: {
188
+ name: 'git_commit',
189
+ description: 'Create a Git commit with the staged changes.',
190
+ parameters: {
191
+ type: 'object',
192
+ properties: {
193
+ message: {
194
+ type: 'string',
195
+ description: 'The commit message.',
196
+ },
197
+ cwd: {
198
+ type: 'string',
199
+ description: 'The working directory.',
200
+ },
201
+ },
202
+ required: ['message'],
203
+ },
204
+ },
205
+ },
206
+ async execute(args) {
207
+ const message = args.message as string;
208
+ const cwd = args.cwd as string | undefined;
209
+
210
+ const git = getGit(cwd);
211
+
212
+ const result = await git.commit(message);
213
+
214
+ return `Committed: ${result.commit || 'unknown'}\nSummary: ${result.summary.changes} files changed, ${result.summary.insertions} insertions, ${result.summary.deletions} deletions`;
215
+ },
216
+ };
217
+
218
+ const gitAddTool: ToolHandler = {
219
+ name: 'git_add',
220
+ safeToAutoRun: false,
221
+ definition: {
222
+ type: 'function',
223
+ function: {
224
+ name: 'git_add',
225
+ description: 'Stage files for commit.',
226
+ parameters: {
227
+ type: 'object',
228
+ properties: {
229
+ files: {
230
+ type: 'array',
231
+ items: { type: 'string' },
232
+ description: 'Files to stage. Use ["."] to stage all changes.',
233
+ },
234
+ cwd: {
235
+ type: 'string',
236
+ description: 'The working directory.',
237
+ },
238
+ },
239
+ required: ['files'],
240
+ },
241
+ },
242
+ },
243
+ async execute(args) {
244
+ const files = args.files as string[];
245
+ const cwd = args.cwd as string | undefined;
246
+
247
+ const git = getGit(cwd);
248
+
249
+ await git.add(files);
250
+
251
+ return `Staged: ${files.join(', ')}`;
252
+ },
253
+ };
254
+
255
+ const gitBranchTool: ToolHandler = {
256
+ name: 'git_branch',
257
+ safeToAutoRun: true,
258
+ definition: {
259
+ type: 'function',
260
+ function: {
261
+ name: 'git_branch',
262
+ description: 'List, create, or switch Git branches.',
263
+ parameters: {
264
+ type: 'object',
265
+ properties: {
266
+ action: {
267
+ type: 'string',
268
+ enum: ['list', 'create', 'switch', 'delete'],
269
+ description: 'The action to perform. Default is "list".',
270
+ },
271
+ name: {
272
+ type: 'string',
273
+ description: 'Branch name (required for create, switch, delete).',
274
+ },
275
+ cwd: {
276
+ type: 'string',
277
+ description: 'The working directory.',
278
+ },
279
+ },
280
+ required: [],
281
+ },
282
+ },
283
+ },
284
+ async execute(args) {
285
+ const action = (args.action as string) || 'list';
286
+ const name = args.name as string | undefined;
287
+ const cwd = args.cwd as string | undefined;
288
+
289
+ const git = getGit(cwd);
290
+
291
+ switch (action) {
292
+ case 'list': {
293
+ const branches = await git.branch();
294
+ return branches.all
295
+ .map((b) => (b === branches.current ? `* ${b}` : ` ${b}`))
296
+ .join('\n');
297
+ }
298
+ case 'create': {
299
+ if (!name) throw new Error('Branch name is required');
300
+ await git.checkoutLocalBranch(name);
301
+ return `Created and switched to branch: ${name}`;
302
+ }
303
+ case 'switch': {
304
+ if (!name) throw new Error('Branch name is required');
305
+ await git.checkout(name);
306
+ return `Switched to branch: ${name}`;
307
+ }
308
+ case 'delete': {
309
+ if (!name) throw new Error('Branch name is required');
310
+ await git.deleteLocalBranch(name);
311
+ return `Deleted branch: ${name}`;
312
+ }
313
+ default:
314
+ throw new Error(`Unknown action: ${action}`);
315
+ }
316
+ },
317
+ };
318
+
319
+ export function registerGitTools(): void {
320
+ toolRegistry.register(gitStatusTool);
321
+ toolRegistry.register(gitDiffTool);
322
+ toolRegistry.register(gitLogTool);
323
+ toolRegistry.register(gitCommitTool);
324
+ toolRegistry.register(gitAddTool);
325
+ toolRegistry.register(gitBranchTool);
326
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Bluehawks CLI - Tool Definitions Index
3
+ * Register all tools
4
+ */
5
+
6
+ import { registerFileTools } from './file.js';
7
+ import { registerShellTools } from './shell.js';
8
+ import { registerSearchTools } from './search.js';
9
+ import { registerGitTools } from './git.js';
10
+ import { registerWebTools } from './web.js';
11
+
12
+ export function registerAllTools(): void {
13
+ registerFileTools();
14
+ registerShellTools();
15
+ registerSearchTools();
16
+ registerGitTools();
17
+ registerWebTools();
18
+ }
19
+
20
+ export * from './file.js';
21
+ export * from './shell.js';
22
+ export * from './search.js';
23
+ export * from './git.js';
24
+ export * from './web.js';
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Bluehawks CLI - Search Tools
3
+ * Tools for searching files and code
4
+ */
5
+
6
+ import * as fs from 'node:fs/promises';
7
+ import * as path from 'node:path';
8
+ import { toolRegistry, type ToolHandler } from '../registry.js';
9
+
10
+ interface SearchMatch {
11
+ file: string;
12
+ line: number;
13
+ content: string;
14
+ }
15
+
16
+ async function searchInFile(
17
+ filePath: string,
18
+ pattern: RegExp,
19
+ maxMatches: number
20
+ ): Promise<SearchMatch[]> {
21
+ const matches: SearchMatch[] = [];
22
+
23
+ try {
24
+ const content = await fs.readFile(filePath, 'utf-8');
25
+ const lines = content.split('\n');
26
+
27
+ for (let i = 0; i < lines.length && matches.length < maxMatches; i++) {
28
+ if (pattern.test(lines[i])) {
29
+ matches.push({
30
+ file: filePath,
31
+ line: i + 1,
32
+ content: lines[i].trim().substring(0, 200),
33
+ });
34
+ }
35
+ }
36
+ } catch {
37
+ // Skip files that can't be read
38
+ }
39
+
40
+ return matches;
41
+ }
42
+
43
+ async function walkDirectory(
44
+ dir: string,
45
+ includes: string[],
46
+ excludes: string[],
47
+ maxDepth: number,
48
+ currentDepth = 0
49
+ ): Promise<string[]> {
50
+ if (currentDepth > maxDepth) return [];
51
+
52
+ const files: string[] = [];
53
+
54
+ try {
55
+ const entries = await fs.readdir(dir, { withFileTypes: true });
56
+
57
+ for (const entry of entries) {
58
+ const fullPath = path.join(dir, entry.name);
59
+
60
+ // Skip excluded patterns
61
+ if (excludes.some((ex) => entry.name === ex || entry.name.includes(ex))) {
62
+ continue;
63
+ }
64
+
65
+ if (entry.isDirectory()) {
66
+ const subFiles = await walkDirectory(fullPath, includes, excludes, maxDepth, currentDepth + 1);
67
+ files.push(...subFiles);
68
+ } else if (entry.isFile()) {
69
+ // Check includes
70
+ if (includes.length === 0 || includes.some((inc) => entry.name.endsWith(inc))) {
71
+ files.push(fullPath);
72
+ }
73
+ }
74
+ }
75
+ } catch {
76
+ // Skip directories we can't read
77
+ }
78
+
79
+ return files;
80
+ }
81
+
82
+ const grepSearchTool: ToolHandler = {
83
+ name: 'grep_search',
84
+ safeToAutoRun: true,
85
+ definition: {
86
+ type: 'function',
87
+ function: {
88
+ name: 'grep_search',
89
+ description:
90
+ 'Search for a pattern in files. Returns matching lines with file paths and line numbers.',
91
+ parameters: {
92
+ type: 'object',
93
+ properties: {
94
+ pattern: {
95
+ type: 'string',
96
+ description: 'The search pattern (regex supported).',
97
+ },
98
+ path: {
99
+ type: 'string',
100
+ description: 'The directory to search in. Defaults to current directory.',
101
+ },
102
+ includes: {
103
+ type: 'array',
104
+ items: { type: 'string' },
105
+ description: 'File extensions to include (e.g., [".ts", ".js"]).',
106
+ },
107
+ excludes: {
108
+ type: 'array',
109
+ items: { type: 'string' },
110
+ description: 'Patterns to exclude (e.g., ["node_modules", ".git"]).',
111
+ },
112
+ case_insensitive: {
113
+ type: 'boolean',
114
+ description: 'Whether to perform case-insensitive search. Default is false.',
115
+ },
116
+ max_results: {
117
+ type: 'number',
118
+ description: 'Maximum number of results to return. Default is 50.',
119
+ },
120
+ },
121
+ required: ['pattern'],
122
+ },
123
+ },
124
+ },
125
+ async execute(args) {
126
+ const patternStr = args.pattern as string;
127
+ const searchPath = (args.path as string) || process.cwd();
128
+ const includes = (args.includes as string[]) || [];
129
+ const excludes = (args.excludes as string[]) || ['node_modules', '.git', 'dist', 'build'];
130
+ const caseInsensitive = (args.case_insensitive as boolean) ?? false;
131
+ const maxResults = (args.max_results as number) || 50;
132
+
133
+ const absolutePath = path.resolve(process.cwd(), searchPath);
134
+ const flags = caseInsensitive ? 'gi' : 'g';
135
+ const pattern = new RegExp(patternStr, flags);
136
+
137
+ const files = await walkDirectory(absolutePath, includes, excludes, 10);
138
+ const allMatches: SearchMatch[] = [];
139
+
140
+ for (const file of files) {
141
+ if (allMatches.length >= maxResults) break;
142
+
143
+ const matches = await searchInFile(file, pattern, maxResults - allMatches.length);
144
+ for (const match of matches) {
145
+ allMatches.push({
146
+ ...match,
147
+ file: path.relative(process.cwd(), match.file),
148
+ });
149
+ }
150
+ }
151
+
152
+ if (allMatches.length === 0) {
153
+ return `No matches found for pattern: ${patternStr}`;
154
+ }
155
+
156
+ const output = allMatches
157
+ .map((m) => `${m.file}:${m.line}: ${m.content}`)
158
+ .join('\n');
159
+
160
+ return `Found ${allMatches.length} matches:\n\n${output}`;
161
+ },
162
+ };
163
+
164
+ const findFilesTool: ToolHandler = {
165
+ name: 'find_files',
166
+ safeToAutoRun: true,
167
+ definition: {
168
+ type: 'function',
169
+ function: {
170
+ name: 'find_files',
171
+ description: 'Find files by name or pattern in a directory.',
172
+ parameters: {
173
+ type: 'object',
174
+ properties: {
175
+ pattern: {
176
+ type: 'string',
177
+ description: 'The filename pattern to search for (supports wildcards like *.ts).',
178
+ },
179
+ path: {
180
+ type: 'string',
181
+ description: 'The directory to search in. Defaults to current directory.',
182
+ },
183
+ type: {
184
+ type: 'string',
185
+ enum: ['file', 'directory', 'any'],
186
+ description: 'Type of entries to find. Default is "any".',
187
+ },
188
+ max_depth: {
189
+ type: 'number',
190
+ description: 'Maximum directory depth to search. Default is 10.',
191
+ },
192
+ max_results: {
193
+ type: 'number',
194
+ description: 'Maximum number of results. Default is 100.',
195
+ },
196
+ },
197
+ required: ['pattern'],
198
+ },
199
+ },
200
+ },
201
+ async execute(args) {
202
+ const pattern = args.pattern as string;
203
+ const searchPath = (args.path as string) || process.cwd();
204
+ const type = (args.type as string) || 'any';
205
+ const maxDepth = (args.max_depth as number) || 10;
206
+ const maxResults = (args.max_results as number) || 100;
207
+
208
+ const absolutePath = path.resolve(process.cwd(), searchPath);
209
+ const results: string[] = [];
210
+
211
+ // Convert glob pattern to regex
212
+ const regexPattern = pattern
213
+ .replace(/\./g, '\\.')
214
+ .replace(/\*/g, '.*')
215
+ .replace(/\?/g, '.');
216
+ const regex = new RegExp(`^${regexPattern}$`, 'i');
217
+
218
+ async function search(dir: string, depth: number): Promise<void> {
219
+ if (depth > maxDepth || results.length >= maxResults) return;
220
+
221
+ try {
222
+ const entries = await fs.readdir(dir, { withFileTypes: true });
223
+
224
+ for (const entry of entries) {
225
+ if (results.length >= maxResults) return;
226
+
227
+ // Skip common excludes
228
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
229
+
230
+ const fullPath = path.join(dir, entry.name);
231
+ const relativePath = path.relative(absolutePath, fullPath);
232
+
233
+ const isMatch = regex.test(entry.name);
234
+ const matchesType =
235
+ type === 'any' ||
236
+ (type === 'file' && entry.isFile()) ||
237
+ (type === 'directory' && entry.isDirectory());
238
+
239
+ if (isMatch && matchesType) {
240
+ const prefix = entry.isDirectory() ? '📁 ' : '📄 ';
241
+ results.push(prefix + relativePath);
242
+ }
243
+
244
+ if (entry.isDirectory()) {
245
+ await search(fullPath, depth + 1);
246
+ }
247
+ }
248
+ } catch {
249
+ // Skip directories we can't read
250
+ }
251
+ }
252
+
253
+ await search(absolutePath, 0);
254
+
255
+ if (results.length === 0) {
256
+ return `No files found matching pattern: ${pattern}`;
257
+ }
258
+
259
+ return `Found ${results.length} matches:\n\n${results.join('\n')}`;
260
+ },
261
+ };
262
+
263
+ export function registerSearchTools(): void {
264
+ toolRegistry.register(grepSearchTool);
265
+ toolRegistry.register(findFilesTool);
266
+ }