agent-sory 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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +265 -0
  3. package/dist/commands/base.d.ts +26 -0
  4. package/dist/commands/base.d.ts.map +1 -0
  5. package/dist/commands/base.js +3 -0
  6. package/dist/commands/base.js.map +1 -0
  7. package/dist/commands/definitions/clear.d.ts +3 -0
  8. package/dist/commands/definitions/clear.d.ts.map +1 -0
  9. package/dist/commands/definitions/clear.js +12 -0
  10. package/dist/commands/definitions/clear.js.map +1 -0
  11. package/dist/commands/definitions/help.d.ts +3 -0
  12. package/dist/commands/definitions/help.d.ts.map +1 -0
  13. package/dist/commands/definitions/help.js +28 -0
  14. package/dist/commands/definitions/help.js.map +1 -0
  15. package/dist/commands/definitions/init.d.ts +3 -0
  16. package/dist/commands/definitions/init.d.ts.map +1 -0
  17. package/dist/commands/definitions/init.js +23 -0
  18. package/dist/commands/definitions/init.js.map +1 -0
  19. package/dist/commands/definitions/login.d.ts +3 -0
  20. package/dist/commands/definitions/login.d.ts.map +1 -0
  21. package/dist/commands/definitions/login.js +8 -0
  22. package/dist/commands/definitions/login.js.map +1 -0
  23. package/dist/commands/definitions/model.d.ts +3 -0
  24. package/dist/commands/definitions/model.d.ts.map +1 -0
  25. package/dist/commands/definitions/model.js +10 -0
  26. package/dist/commands/definitions/model.js.map +1 -0
  27. package/dist/commands/definitions/reasoning.d.ts +3 -0
  28. package/dist/commands/definitions/reasoning.d.ts.map +1 -0
  29. package/dist/commands/definitions/reasoning.js +21 -0
  30. package/dist/commands/definitions/reasoning.js.map +1 -0
  31. package/dist/commands/definitions/stats.d.ts +3 -0
  32. package/dist/commands/definitions/stats.d.ts.map +1 -0
  33. package/dist/commands/definitions/stats.js +22 -0
  34. package/dist/commands/definitions/stats.js.map +1 -0
  35. package/dist/commands/index.d.ts +6 -0
  36. package/dist/commands/index.d.ts.map +1 -0
  37. package/dist/commands/index.js +38 -0
  38. package/dist/commands/index.js.map +1 -0
  39. package/dist/core/agent.d.ts +41 -0
  40. package/dist/core/agent.d.ts.map +1 -0
  41. package/dist/core/agent.js +266 -0
  42. package/dist/core/agent.js.map +1 -0
  43. package/dist/core/cli.d.ts +3 -0
  44. package/dist/core/cli.d.ts.map +1 -0
  45. package/dist/core/cli.js +48 -0
  46. package/dist/core/cli.js.map +1 -0
  47. package/dist/core/llm/factory.d.ts +12 -0
  48. package/dist/core/llm/factory.d.ts.map +1 -0
  49. package/dist/core/llm/factory.js +44 -0
  50. package/dist/core/llm/factory.js.map +1 -0
  51. package/dist/core/llm/providers/gemini.d.ts +22 -0
  52. package/dist/core/llm/providers/gemini.d.ts.map +1 -0
  53. package/dist/core/llm/providers/gemini.js +167 -0
  54. package/dist/core/llm/providers/gemini.js.map +1 -0
  55. package/dist/core/llm/providers/groq.d.ts +23 -0
  56. package/dist/core/llm/providers/groq.d.ts.map +1 -0
  57. package/dist/core/llm/providers/groq.js +96 -0
  58. package/dist/core/llm/providers/groq.js.map +1 -0
  59. package/dist/core/llm/providers/openai-compatible.d.ts +24 -0
  60. package/dist/core/llm/providers/openai-compatible.d.ts.map +1 -0
  61. package/dist/core/llm/providers/openai-compatible.js +86 -0
  62. package/dist/core/llm/providers/openai-compatible.js.map +1 -0
  63. package/dist/core/llm/types.d.ts +37 -0
  64. package/dist/core/llm/types.d.ts.map +1 -0
  65. package/dist/core/llm/types.js +2 -0
  66. package/dist/core/llm/types.js.map +1 -0
  67. package/dist/core/prompts/system.d.ts +6 -0
  68. package/dist/core/prompts/system.d.ts.map +1 -0
  69. package/dist/core/prompts/system.js +36 -0
  70. package/dist/core/prompts/system.js.map +1 -0
  71. package/dist/tests/proxy-config.test.d.ts +2 -0
  72. package/dist/tests/proxy-config.test.d.ts.map +1 -0
  73. package/dist/tests/proxy-config.test.js +169 -0
  74. package/dist/tests/proxy-config.test.js.map +1 -0
  75. package/dist/tools/tool-schemas.d.ts +36 -0
  76. package/dist/tools/tool-schemas.d.ts.map +1 -0
  77. package/dist/tools/tool-schemas.js +482 -0
  78. package/dist/tools/tool-schemas.js.map +1 -0
  79. package/dist/tools/tools.d.ts +114 -0
  80. package/dist/tools/tools.d.ts.map +1 -0
  81. package/dist/tools/tools.js +1015 -0
  82. package/dist/tools/tools.js.map +1 -0
  83. package/dist/tools/utils/edit-logic.d.ts +9 -0
  84. package/dist/tools/utils/edit-logic.d.ts.map +1 -0
  85. package/dist/tools/utils/edit-logic.js +149 -0
  86. package/dist/tools/utils/edit-logic.js.map +1 -0
  87. package/dist/tools/utils/ripgrep-runner.d.ts +15 -0
  88. package/dist/tools/utils/ripgrep-runner.d.ts.map +1 -0
  89. package/dist/tools/utils/ripgrep-runner.js +90 -0
  90. package/dist/tools/utils/ripgrep-runner.js.map +1 -0
  91. package/dist/tools/validators.d.ts +4 -0
  92. package/dist/tools/validators.d.ts.map +1 -0
  93. package/dist/tools/validators.js +18 -0
  94. package/dist/tools/validators.js.map +1 -0
  95. package/dist/ui/App.d.ts +7 -0
  96. package/dist/ui/App.d.ts.map +1 -0
  97. package/dist/ui/App.js +12 -0
  98. package/dist/ui/App.js.map +1 -0
  99. package/dist/ui/components/core/Chat.d.ts +7 -0
  100. package/dist/ui/components/core/Chat.d.ts.map +1 -0
  101. package/dist/ui/components/core/Chat.js +60 -0
  102. package/dist/ui/components/core/Chat.js.map +1 -0
  103. package/dist/ui/components/core/MessageHistory.d.ts +7 -0
  104. package/dist/ui/components/core/MessageHistory.d.ts.map +1 -0
  105. package/dist/ui/components/core/MessageHistory.js +39 -0
  106. package/dist/ui/components/core/MessageHistory.js.map +1 -0
  107. package/dist/ui/components/core/MessageInput.d.ts +10 -0
  108. package/dist/ui/components/core/MessageInput.d.ts.map +1 -0
  109. package/dist/ui/components/core/MessageInput.js +130 -0
  110. package/dist/ui/components/core/MessageInput.js.map +1 -0
  111. package/dist/ui/components/display/DiffPreview.d.ts +15 -0
  112. package/dist/ui/components/display/DiffPreview.d.ts.map +1 -0
  113. package/dist/ui/components/display/DiffPreview.js +298 -0
  114. package/dist/ui/components/display/DiffPreview.js.map +1 -0
  115. package/dist/ui/components/display/Stats.d.ts +19 -0
  116. package/dist/ui/components/display/Stats.d.ts.map +1 -0
  117. package/dist/ui/components/display/Stats.js +31 -0
  118. package/dist/ui/components/display/Stats.js.map +1 -0
  119. package/dist/ui/components/display/TokenMetrics.d.ts +11 -0
  120. package/dist/ui/components/display/TokenMetrics.d.ts.map +1 -0
  121. package/dist/ui/components/display/TokenMetrics.js +16 -0
  122. package/dist/ui/components/display/TokenMetrics.js.map +1 -0
  123. package/dist/ui/components/display/ToolHistoryItem.d.ts +7 -0
  124. package/dist/ui/components/display/ToolHistoryItem.d.ts.map +1 -0
  125. package/dist/ui/components/display/ToolHistoryItem.js +102 -0
  126. package/dist/ui/components/display/ToolHistoryItem.js.map +1 -0
  127. package/dist/ui/components/input-overlays/ErrorRetry.d.ts +8 -0
  128. package/dist/ui/components/input-overlays/ErrorRetry.d.ts.map +1 -0
  129. package/dist/ui/components/input-overlays/ErrorRetry.js +6 -0
  130. package/dist/ui/components/input-overlays/ErrorRetry.js.map +1 -0
  131. package/dist/ui/components/input-overlays/Login.d.ts +7 -0
  132. package/dist/ui/components/input-overlays/Login.d.ts.map +1 -0
  133. package/dist/ui/components/input-overlays/Login.js +32 -0
  134. package/dist/ui/components/input-overlays/Login.js.map +1 -0
  135. package/dist/ui/components/input-overlays/MaxIterationsContinue.d.ts +8 -0
  136. package/dist/ui/components/input-overlays/MaxIterationsContinue.d.ts.map +1 -0
  137. package/dist/ui/components/input-overlays/MaxIterationsContinue.js +32 -0
  138. package/dist/ui/components/input-overlays/MaxIterationsContinue.js.map +1 -0
  139. package/dist/ui/components/input-overlays/ModelSelector.d.ts +12 -0
  140. package/dist/ui/components/input-overlays/ModelSelector.d.ts.map +1 -0
  141. package/dist/ui/components/input-overlays/ModelSelector.js +114 -0
  142. package/dist/ui/components/input-overlays/ModelSelector.js.map +1 -0
  143. package/dist/ui/components/input-overlays/PendingToolApproval.d.ts +10 -0
  144. package/dist/ui/components/input-overlays/PendingToolApproval.d.ts.map +1 -0
  145. package/dist/ui/components/input-overlays/PendingToolApproval.js +51 -0
  146. package/dist/ui/components/input-overlays/PendingToolApproval.js.map +1 -0
  147. package/dist/ui/components/input-overlays/SlashCommandSuggestions.d.ts +8 -0
  148. package/dist/ui/components/input-overlays/SlashCommandSuggestions.d.ts.map +1 -0
  149. package/dist/ui/components/input-overlays/SlashCommandSuggestions.js +13 -0
  150. package/dist/ui/components/input-overlays/SlashCommandSuggestions.js.map +1 -0
  151. package/dist/ui/hooks/useAgent.d.ts +41 -0
  152. package/dist/ui/hooks/useAgent.d.ts.map +1 -0
  153. package/dist/ui/hooks/useAgent.js +205 -0
  154. package/dist/ui/hooks/useAgent.js.map +1 -0
  155. package/dist/ui/hooks/useMouseScroll.d.ts +3 -0
  156. package/dist/ui/hooks/useMouseScroll.d.ts.map +1 -0
  157. package/dist/ui/hooks/useMouseScroll.js +32 -0
  158. package/dist/ui/hooks/useMouseScroll.js.map +1 -0
  159. package/dist/ui/hooks/useSessionStats.d.ts +20 -0
  160. package/dist/ui/hooks/useSessionStats.d.ts.map +1 -0
  161. package/dist/ui/hooks/useSessionStats.js +36 -0
  162. package/dist/ui/hooks/useSessionStats.js.map +1 -0
  163. package/dist/ui/hooks/useTokenMetrics.d.ts +21 -0
  164. package/dist/ui/hooks/useTokenMetrics.d.ts.map +1 -0
  165. package/dist/ui/hooks/useTokenMetrics.js +102 -0
  166. package/dist/ui/hooks/useTokenMetrics.js.map +1 -0
  167. package/dist/ui/utils/CodeColorizer.d.ts +5 -0
  168. package/dist/ui/utils/CodeColorizer.d.ts.map +1 -0
  169. package/dist/ui/utils/CodeColorizer.js +47 -0
  170. package/dist/ui/utils/CodeColorizer.js.map +1 -0
  171. package/dist/utils/constants.d.ts +8 -0
  172. package/dist/utils/constants.d.ts.map +1 -0
  173. package/dist/utils/constants.js +104 -0
  174. package/dist/utils/constants.js.map +1 -0
  175. package/dist/utils/context.d.ts +36 -0
  176. package/dist/utils/context.d.ts.map +1 -0
  177. package/dist/utils/context.js +239 -0
  178. package/dist/utils/context.js.map +1 -0
  179. package/dist/utils/file-ops.d.ts +21 -0
  180. package/dist/utils/file-ops.d.ts.map +1 -0
  181. package/dist/utils/file-ops.js +125 -0
  182. package/dist/utils/file-ops.js.map +1 -0
  183. package/dist/utils/local-settings.d.ts +18 -0
  184. package/dist/utils/local-settings.d.ts.map +1 -0
  185. package/dist/utils/local-settings.js +176 -0
  186. package/dist/utils/local-settings.js.map +1 -0
  187. package/dist/utils/markdown.d.ts +13 -0
  188. package/dist/utils/markdown.d.ts.map +1 -0
  189. package/dist/utils/markdown.js +122 -0
  190. package/dist/utils/markdown.js.map +1 -0
  191. package/dist/utils/proxy-config.d.ts +25 -0
  192. package/dist/utils/proxy-config.d.ts.map +1 -0
  193. package/dist/utils/proxy-config.js +145 -0
  194. package/dist/utils/proxy-config.js.map +1 -0
  195. package/package.json +76 -0
@@ -0,0 +1,1015 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import fg from 'fast-glob';
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import { writeFile, createDirectory, displayTree } from '../utils/file-ops.js';
7
+ import { setReadFilesTracker } from './validators.js';
8
+ import { calculateReplacement } from './utils/edit-logic.js';
9
+ import { runRipGrep } from './utils/ripgrep-runner.js';
10
+ import { convert } from 'html-to-text';
11
+ const execAsync = promisify(exec);
12
+ // Global task state
13
+ let currentTaskList = null;
14
+ // Track which files have been read in the current session
15
+ const readFiles = new Set();
16
+ // Export readFiles for validator access
17
+ export function getReadFilesTracker() {
18
+ return readFiles;
19
+ }
20
+ // Initialize validator with readFiles tracker
21
+ setReadFilesTracker(readFiles);
22
+ /**
23
+ * Format key parameters for tool call display
24
+ */
25
+ export function formatToolParams(toolName, toolArgs, options = {}) {
26
+ const { includePrefix = true, separator = '=' } = options;
27
+ const paramMappings = {
28
+ read_file: ['file_path'],
29
+ create_file: ['file_path'],
30
+ edit_file: ['file_path'],
31
+ delete_file: ['file_path'],
32
+ list_files: ['directory'],
33
+ search_files: ['pattern'],
34
+ execute_command: ['command'],
35
+ create_tasks: [],
36
+ update_tasks: [],
37
+ };
38
+ const keyParams = paramMappings[toolName] || [];
39
+ const parts = [];
40
+ // First, add mapped key params
41
+ keyParams.forEach(param => {
42
+ if (param in toolArgs) {
43
+ let value = toolArgs[param];
44
+ if (typeof value === 'string' && value.length > 50)
45
+ value = value.substring(0, 47) + '...';
46
+ parts.push(`${param}${separator}${JSON.stringify(value)}`);
47
+ }
48
+ });
49
+ // Then, add any OTHER params that weren't mapped (except big ones like 'content')
50
+ Object.keys(toolArgs).forEach(key => {
51
+ if (!keyParams.includes(key) && key !== 'content' && key !== 'old_text' && key !== 'new_text') {
52
+ let value = toolArgs[key];
53
+ if (typeof value === 'string' && value.length > 30)
54
+ value = value.substring(0, 27) + '...';
55
+ parts.push(`${key}${separator}${JSON.stringify(value)}`);
56
+ }
57
+ });
58
+ if (parts.length === 0) {
59
+ // If we have arguments but nothing was formatted (e.g. only 'content'), show a summary
60
+ if (Object.keys(toolArgs).length > 0) {
61
+ return includePrefix ? `Args: ${Object.keys(toolArgs).join(', ')}` : Object.keys(toolArgs).join(', ');
62
+ }
63
+ return '';
64
+ }
65
+ const formatted = parts.join(', ');
66
+ return includePrefix ? `Params: ${formatted}` : formatted;
67
+ }
68
+ /**
69
+ * Create a standardized tool response format
70
+ */
71
+ export function createToolResponse(success, data, message = '', error = '') {
72
+ const response = { success };
73
+ if (success) {
74
+ if (data !== undefined) {
75
+ response.content = data;
76
+ }
77
+ if (message) {
78
+ response.message = message;
79
+ }
80
+ }
81
+ else {
82
+ response.error = error;
83
+ if (message) {
84
+ response.message = message;
85
+ }
86
+ }
87
+ return response;
88
+ }
89
+ /**
90
+ * Read the contents of a file, optionally specifying line range
91
+ */
92
+ export async function readFile(filePath, startLine, endLine) {
93
+ try {
94
+ const resolvedPath = path.resolve(filePath);
95
+ // Check if file exists
96
+ try {
97
+ await fs.promises.access(resolvedPath);
98
+ }
99
+ catch {
100
+ const parentDir = path.dirname(filePath);
101
+ const suggestions = await displayTree(parentDir, '*', false, false);
102
+ return createToolResponse(false, undefined, '', `Error: File '${filePath}' not found. Directory '${parentDir}' contains:\n${suggestions}`);
103
+ }
104
+ const stats = await fs.promises.stat(resolvedPath);
105
+ if (!stats.isFile()) {
106
+ return createToolResponse(false, undefined, '', `Error: '${filePath}' is not a file. If it is a directory, use list_files.`);
107
+ }
108
+ // Check file size (50MB limit)
109
+ if (stats.size > 50 * 1024 * 1024) {
110
+ return createToolResponse(false, undefined, '', 'Error: File too large (max 50MB)');
111
+ }
112
+ const content = await fs.promises.readFile(resolvedPath, 'utf-8');
113
+ const lines = content.split('\n');
114
+ // Handle line range if specified
115
+ if (startLine !== undefined) {
116
+ const startIdx = Math.max(0, startLine - 1); // Convert to 0-indexed
117
+ let endIdx = lines.length;
118
+ if (endLine !== undefined) {
119
+ endIdx = Math.min(lines.length, endLine);
120
+ }
121
+ if (startIdx >= lines.length) {
122
+ return createToolResponse(false, undefined, '', 'Error: Start line exceeds file length');
123
+ }
124
+ const selectedLines = lines.slice(startIdx, endIdx);
125
+ const selectedContent = selectedLines.join('\n');
126
+ // Add file to read tracking for partial reads too
127
+ readFiles.add(resolvedPath);
128
+ const message = `Read lines ${startLine}-${endIdx} from ${filePath}`;
129
+ return createToolResponse(true, selectedContent, message);
130
+ }
131
+ else {
132
+ // Add file to read tracking
133
+ readFiles.add(resolvedPath);
134
+ const message = `Read ${lines.length} lines from ${filePath}`;
135
+ return createToolResponse(true, content, message);
136
+ }
137
+ }
138
+ catch (error) {
139
+ if (error.code === 'ENOENT') {
140
+ return createToolResponse(false, undefined, '', 'Error: File not found');
141
+ }
142
+ return createToolResponse(false, undefined, '', 'Error: Failed to read file');
143
+ }
144
+ }
145
+ /**
146
+ * Read multiple files at once
147
+ */
148
+ export async function readManyFiles(filePaths) {
149
+ try {
150
+ const results = await Promise.all(filePaths.map(async (filePath) => {
151
+ try {
152
+ const resolvedPath = path.resolve(filePath);
153
+ const content = await fs.promises.readFile(resolvedPath, 'utf-8');
154
+ readFiles.add(resolvedPath);
155
+ return { filePath, content, success: true };
156
+ }
157
+ catch (error) {
158
+ return { filePath, error: String(error), success: false };
159
+ }
160
+ }));
161
+ const successfulCount = results.filter(r => r.success).length;
162
+ return createToolResponse(true, results, `Successfully read ${successfulCount}/${filePaths.length} files`);
163
+ }
164
+ catch (error) {
165
+ return createToolResponse(false, undefined, '', `Error: Failed to read multiple files - ${error}`);
166
+ }
167
+ }
168
+ /**
169
+ * Create a new file or directory with specified content
170
+ */
171
+ export async function createFile(filePath, content, fileType = 'file', overwrite = false) {
172
+ try {
173
+ const targetPath = path.resolve(filePath);
174
+ // Check if file exists and handle overwrite
175
+ const exists = await fs.promises.access(targetPath).then(() => true).catch(() => false);
176
+ if (exists && !overwrite) {
177
+ return createToolResponse(false, undefined, '', 'Error: File already exists, use overwrite=true');
178
+ }
179
+ if (fileType === 'directory') {
180
+ const result = await createDirectory(targetPath);
181
+ if (result) {
182
+ return createToolResponse(true, { path: targetPath, type: 'directory' }, `Directory created: ${filePath}`);
183
+ }
184
+ else {
185
+ return createToolResponse(false, undefined, '', 'Error: Failed to create directory');
186
+ }
187
+ }
188
+ else if (fileType === 'file') {
189
+ const result = await writeFile(targetPath, content, overwrite, true);
190
+ if (result) {
191
+ return createToolResponse(true, undefined, `File created: ${filePath}`);
192
+ }
193
+ else {
194
+ return createToolResponse(false, undefined, '', 'Error: Failed to create file');
195
+ }
196
+ }
197
+ else {
198
+ return createToolResponse(false, undefined, '', "Error: Invalid file_type, must be 'file' or 'directory'");
199
+ }
200
+ }
201
+ catch (error) {
202
+ return createToolResponse(false, undefined, '', 'Error: Failed to create file or directory');
203
+ }
204
+ }
205
+ /**
206
+ * Edit a file by replacing text strings using multiple strategies (exact, flexible, regex)
207
+ */
208
+ export async function editFile(filePath, oldString, newString, expectedReplacements = 1) {
209
+ try {
210
+ const resolvedPath = path.resolve(filePath);
211
+ // Read current content
212
+ const originalContent = await fs.promises.readFile(resolvedPath, 'utf-8');
213
+ // Detect line endings
214
+ const isCRLF = originalContent.includes('\r\n');
215
+ const normalizedContent = originalContent.replace(/\r\n/g, '\n');
216
+ // Perform the replacement using advanced logic
217
+ const result = await calculateReplacement(normalizedContent, oldString, newString);
218
+ if (result.occurrences === 0) {
219
+ return createToolResponse(false, undefined, '', `Error: Could not find the string to replace in ${filePath}. Check whitespace and indentation.`);
220
+ }
221
+ if (expectedReplacements > 0 && result.occurrences !== expectedReplacements) {
222
+ return createToolResponse(false, undefined, '', `Error: Expected ${expectedReplacements} replacement(s) but found ${result.occurrences} in ${filePath}.`);
223
+ }
224
+ // Restore line endings if necessary
225
+ let finalContent = result.newContent;
226
+ if (isCRLF) {
227
+ finalContent = finalContent.replace(/\n/g, '\r\n');
228
+ }
229
+ // Write the updated content
230
+ const writeResult = await writeFile(filePath, finalContent, true, true);
231
+ if (writeResult) {
232
+ return createToolResponse(true, undefined, `Successfully modified ${filePath} (${result.occurrences} replacement(s)).`);
233
+ }
234
+ else {
235
+ return createToolResponse(false, undefined, '', 'Error: Failed to write changes to file');
236
+ }
237
+ }
238
+ catch (error) {
239
+ return createToolResponse(false, undefined, '', `Error: Failed to edit file - ${error}`);
240
+ }
241
+ }
242
+ /**
243
+ * Delete a file or directory with safety checks
244
+ */
245
+ export async function deleteFile(filePath, recursive = false) {
246
+ try {
247
+ const targetPath = path.resolve(filePath);
248
+ const currentWorkingDir = path.resolve(process.cwd());
249
+ // Safety check 1: Never delete the root directory itself
250
+ if (targetPath === currentWorkingDir) {
251
+ return createToolResponse(false, undefined, '', 'Error: Cannot delete the root project directory');
252
+ }
253
+ // Safety check 2: Never delete anything outside the current working directory
254
+ if (!targetPath.startsWith(currentWorkingDir)) {
255
+ return createToolResponse(false, undefined, '', 'Error: Cannot delete files outside the project directory');
256
+ }
257
+ const exists = await fs.promises.access(targetPath).then(() => true).catch(() => false);
258
+ if (!exists) {
259
+ return createToolResponse(false, undefined, '', 'Error: Path not found');
260
+ }
261
+ const stats = await fs.promises.stat(targetPath);
262
+ if (stats.isDirectory() && !recursive) {
263
+ // Check if directory is empty
264
+ const items = await fs.promises.readdir(targetPath);
265
+ if (items.length > 0) {
266
+ return createToolResponse(false, undefined, '', 'Error: Directory not empty, use recursive=true');
267
+ }
268
+ }
269
+ // Perform deletion
270
+ if (stats.isDirectory()) {
271
+ await fs.promises.rmdir(targetPath, { recursive });
272
+ }
273
+ else {
274
+ await fs.promises.unlink(targetPath);
275
+ }
276
+ const fileType = stats.isDirectory() ? 'directory' : 'file';
277
+ return createToolResponse(true, undefined, `Deleted ${fileType}: ${filePath}`);
278
+ }
279
+ catch (error) {
280
+ return createToolResponse(false, undefined, '', 'Error: Failed to delete');
281
+ }
282
+ }
283
+ /**
284
+ * List files and directories in a path with tree-style display
285
+ */
286
+ export async function listFiles(directory = '.', pattern = '*', recursive = false, showHidden = false) {
287
+ try {
288
+ const dirPath = path.resolve(directory);
289
+ const exists = await fs.promises.access(dirPath).then(() => true).catch(() => false);
290
+ if (!exists) {
291
+ const parentDir = path.dirname(directory);
292
+ const suggestions = await displayTree(parentDir === '.' ? '.' : parentDir, '*', false, false);
293
+ return createToolResponse(false, undefined, '', `Error: Directory '${directory}' not found. Current location contains:\n${suggestions}`);
294
+ }
295
+ const stats = await fs.promises.stat(dirPath);
296
+ if (!stats.isDirectory()) {
297
+ return createToolResponse(false, undefined, '', `Error: '${directory}' is a file, not a directory. Use read_file instead.`);
298
+ }
299
+ // Get tree display output
300
+ const treeOutput = await displayTree(directory, pattern, recursive, showHidden);
301
+ return createToolResponse(true, treeOutput, `Listed ${directory}`);
302
+ }
303
+ catch (error) {
304
+ return createToolResponse(false, undefined, '', `Failed to list files: ${error}`);
305
+ }
306
+ }
307
+ /**
308
+ * Search for text patterns in files with advanced filtering and matching options
309
+ */
310
+ export async function searchFiles(pattern, filePattern = '*', directory = '.', caseSensitive = false, patternType = 'substring', fileTypes, excludeDirs, excludeFiles, maxResults = 100, contextLines = 0, groupByFile = false) {
311
+ try {
312
+ const searchDir = path.resolve(directory);
313
+ // Check if directory exists
314
+ const exists = await fs.promises.access(searchDir).then(() => true).catch(() => false);
315
+ if (!exists) {
316
+ return createToolResponse(false, undefined, '', 'Error: Directory not found');
317
+ }
318
+ const stats = await fs.promises.stat(searchDir);
319
+ if (!stats.isDirectory()) {
320
+ return createToolResponse(false, undefined, '', 'Error: Path is not a directory');
321
+ }
322
+ // Default exclusions
323
+ const defaultExcludeDirs = ['.git', 'node_modules', '.next', 'dist', 'build', '.cache'];
324
+ const defaultExcludeFiles = ['*.log', '*.tmp', '*.cache', '*.lock'];
325
+ const finalExcludeDirs = [...defaultExcludeDirs, ...(excludeDirs || [])];
326
+ const finalExcludeFiles = [...defaultExcludeFiles, ...(excludeFiles || [])];
327
+ // Prepare search regex
328
+ let searchRegex;
329
+ try {
330
+ switch (patternType) {
331
+ case 'exact':
332
+ searchRegex = new RegExp(escapeRegex(pattern), caseSensitive ? 'g' : 'gi');
333
+ break;
334
+ case 'regex':
335
+ searchRegex = new RegExp(pattern, caseSensitive ? 'g' : 'gi');
336
+ break;
337
+ case 'fuzzy':
338
+ // Simple fuzzy search, insert .* between characters
339
+ const fuzzyPattern = pattern.split('').map(escapeRegex).join('.*');
340
+ searchRegex = new RegExp(fuzzyPattern, caseSensitive ? 'g' : 'gi');
341
+ break;
342
+ case 'substring':
343
+ default:
344
+ searchRegex = new RegExp(escapeRegex(pattern), caseSensitive ? 'g' : 'gi');
345
+ break;
346
+ }
347
+ }
348
+ catch (error) {
349
+ return createToolResponse(false, undefined, '', 'Error: Invalid regex pattern');
350
+ }
351
+ // Collect all files to search
352
+ const filesToSearch = await collectFiles(searchDir, filePattern, fileTypes, finalExcludeDirs, finalExcludeFiles);
353
+ if (filesToSearch.length === 0) {
354
+ return createToolResponse(true, [], 'No files found matching criteria');
355
+ }
356
+ // Search through files
357
+ const results = [];
358
+ let totalMatches = 0;
359
+ for (const filePath of filesToSearch) {
360
+ if (totalMatches >= maxResults) {
361
+ break;
362
+ }
363
+ try {
364
+ const content = await fs.promises.readFile(filePath, 'utf-8');
365
+ const lines = content.split('\n');
366
+ const fileMatches = [];
367
+ for (let i = 0; i < lines.length && totalMatches < maxResults; i++) {
368
+ const line = lines[i];
369
+ const matches = Array.from(line.matchAll(searchRegex));
370
+ if (matches.length > 0) {
371
+ const contextStart = Math.max(0, i - contextLines);
372
+ const contextEnd = Math.min(lines.length - 1, i + contextLines);
373
+ const contextLinesArray = [];
374
+ for (let j = contextStart; j <= contextEnd; j++) {
375
+ contextLinesArray.push(lines[j]);
376
+ }
377
+ fileMatches.push({
378
+ lineNumber: i + 1,
379
+ lineContent: line,
380
+ contextLines: contextLines > 0 ? contextLinesArray : undefined,
381
+ matchPositions: matches.map(match => ({
382
+ start: match.index || 0,
383
+ end: (match.index || 0) + match[0].length,
384
+ text: match[0]
385
+ }))
386
+ });
387
+ totalMatches++;
388
+ }
389
+ }
390
+ if (fileMatches.length > 0) {
391
+ results.push({
392
+ filePath: path.relative(process.cwd(), filePath),
393
+ matches: fileMatches,
394
+ totalMatches: fileMatches.length
395
+ });
396
+ }
397
+ }
398
+ catch (error) {
399
+ // Skip files that can't be read (binary files, permission issues, etc.)
400
+ continue;
401
+ }
402
+ }
403
+ // Format results
404
+ let formattedResults;
405
+ if (groupByFile) {
406
+ formattedResults = results;
407
+ }
408
+ else {
409
+ // Flatten results
410
+ formattedResults = results.flatMap(fileResult => fileResult.matches.map(match => ({
411
+ filePath: fileResult.filePath,
412
+ lineNumber: match.lineNumber,
413
+ lineContent: match.lineContent,
414
+ contextLines: match.contextLines,
415
+ matchPositions: match.matchPositions
416
+ })));
417
+ }
418
+ const message = `Found ${totalMatches} match(es) in ${results.length} file(s)`;
419
+ return createToolResponse(true, formattedResults, message);
420
+ }
421
+ catch (error) {
422
+ return createToolResponse(false, undefined, '', 'Error: Failed to search files');
423
+ }
424
+ }
425
+ // Helper function to escape regex special characters
426
+ function escapeRegex(string) {
427
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
428
+ }
429
+ // Helper function to collect files based on patterns and filters
430
+ async function collectFiles(directory, filePattern, fileTypes, excludeDirs, excludeFiles) {
431
+ const files = [];
432
+ async function walkDirectory(dir) {
433
+ try {
434
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
435
+ for (const entry of entries) {
436
+ const fullPath = path.join(dir, entry.name);
437
+ if (entry.isDirectory()) {
438
+ // Check if directory should be excluded
439
+ if (excludeDirs && excludeDirs.some(pattern => matchesPattern(entry.name, pattern))) {
440
+ continue;
441
+ }
442
+ // Skip hidden directories unless explicitly included
443
+ if (entry.name.startsWith('.') && !entry.name.match(/^\.(config|env)$/)) {
444
+ continue;
445
+ }
446
+ await walkDirectory(fullPath);
447
+ }
448
+ else if (entry.isFile()) {
449
+ // Check file type filters
450
+ if (fileTypes && fileTypes.length > 0) {
451
+ const ext = path.extname(entry.name).slice(1);
452
+ if (!fileTypes.includes(ext)) {
453
+ continue;
454
+ }
455
+ }
456
+ // Check file pattern
457
+ if (!matchesPattern(entry.name, filePattern)) {
458
+ continue;
459
+ }
460
+ // Check exclusions
461
+ if (excludeFiles && excludeFiles.some(pattern => matchesPattern(entry.name, pattern))) {
462
+ continue;
463
+ }
464
+ // Skip obviously binary files
465
+ if (isBinaryFile(entry.name)) {
466
+ continue;
467
+ }
468
+ files.push(fullPath);
469
+ }
470
+ }
471
+ }
472
+ catch (error) {
473
+ // Skip directories we can't read
474
+ }
475
+ }
476
+ await walkDirectory(directory);
477
+ return files;
478
+ }
479
+ // Helper function to match glob-like patterns
480
+ function matchesPattern(filename, pattern) {
481
+ if (pattern === '*')
482
+ return true;
483
+ // Simple glob matching, convert * to .* and ? to .
484
+ const regexPattern = pattern
485
+ .replace(/\./g, '\\.')
486
+ .replace(/\*/g, '.*')
487
+ .replace(/\?/g, '.');
488
+ return new RegExp(`^${regexPattern}$`, 'i').test(filename);
489
+ }
490
+ // Helper function to detect binary files
491
+ function isBinaryFile(filename) {
492
+ const binaryExtensions = [
493
+ '.exe', '.dll', '.so', '.dylib', '.bin', '.obj', '.o', '.a', '.lib',
494
+ '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg', '.webp',
495
+ '.mp3', '.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm',
496
+ '.zip', '.tar', '.gz', '.bz2', '.rar', '.7z',
497
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'
498
+ ];
499
+ const ext = path.extname(filename).toLowerCase();
500
+ return binaryExtensions.includes(ext);
501
+ }
502
+ /**
503
+ * Execute a shell command or run code
504
+ */
505
+ export async function executeCommand(command, commandType, workingDirectory, timeout = 30000) {
506
+ try {
507
+ // Validate command type
508
+ if (!['bash', 'python', 'setup', 'run'].includes(commandType)) {
509
+ return createToolResponse(false, undefined, '', 'Error: Invalid command_type');
510
+ }
511
+ let originalCwd;
512
+ if (workingDirectory) {
513
+ const wdPath = path.resolve(workingDirectory);
514
+ const exists = await fs.promises.access(wdPath).then(() => true).catch(() => false);
515
+ if (!exists) {
516
+ return createToolResponse(false, undefined, '', 'Error: Working directory not found');
517
+ }
518
+ originalCwd = process.cwd();
519
+ process.chdir(workingDirectory);
520
+ }
521
+ try {
522
+ let execCommand;
523
+ if (commandType === 'python') {
524
+ execCommand = `python -c "${command.replace(/"/g, '\\"')}"`;
525
+ }
526
+ else {
527
+ execCommand = command;
528
+ }
529
+ const { stdout, stderr } = await execAsync(execCommand, { timeout });
530
+ const success = true; // If no error was thrown, consider it successful
531
+ return createToolResponse(success, `stdout: ${stdout}\nstderr: ${stderr}`, `Command executed successfully`);
532
+ }
533
+ finally {
534
+ // Restore original working directory
535
+ if (originalCwd) {
536
+ process.chdir(originalCwd);
537
+ }
538
+ }
539
+ }
540
+ catch (error) {
541
+ const isTimeout = error.killed && error.signal === 'SIGTERM';
542
+ if (isTimeout) {
543
+ return createToolResponse(false, undefined, '', 'Error: Command timed out');
544
+ }
545
+ return createToolResponse(false, undefined, '', 'Error: Failed to execute command');
546
+ }
547
+ }
548
+ /**
549
+ * Create a task list of subtasks to complete the user's request
550
+ */
551
+ export async function createTasks(userQuery, tasks) {
552
+ try {
553
+ // Validate task structure
554
+ for (let i = 0; i < tasks.length; i++) {
555
+ const task = tasks[i];
556
+ if (!task.id || !task.description) {
557
+ return createToolResponse(false, undefined, '', `Error: Task ${i} missing required fields (id, description)`);
558
+ }
559
+ // Set default status if not provided
560
+ if (!task.status) {
561
+ task.status = 'pending';
562
+ }
563
+ // Validate status
564
+ if (!['pending', 'in_progress', 'completed'].includes(task.status)) {
565
+ return createToolResponse(false, undefined, '', `Error: Invalid status '${task.status}' for task ${task.id}`);
566
+ }
567
+ }
568
+ // Store the task list globally
569
+ currentTaskList = {
570
+ user_query: userQuery,
571
+ tasks: tasks,
572
+ created_at: new Date().toISOString()
573
+ };
574
+ // Return a deep copy to prevent mutation of historical displays
575
+ const snapshot = {
576
+ user_query: currentTaskList.user_query,
577
+ tasks: currentTaskList.tasks.map(task => ({ ...task })),
578
+ created_at: currentTaskList.created_at
579
+ };
580
+ return createToolResponse(true, snapshot, `Created task list with ${tasks.length} tasks for: ${userQuery}`);
581
+ }
582
+ catch (error) {
583
+ return createToolResponse(false, undefined, '', `Error: Failed to create tasks - ${error}`);
584
+ }
585
+ }
586
+ /**
587
+ * Update the status of one or more tasks in the task list
588
+ */
589
+ export async function updateTasks(taskUpdates) {
590
+ try {
591
+ if (!currentTaskList) {
592
+ return createToolResponse(false, undefined, '', 'Error: No task list exists. Create tasks first.');
593
+ }
594
+ // Track updates made
595
+ const updatesMade = [];
596
+ for (const update of taskUpdates) {
597
+ if (!update.id || !update.status) {
598
+ return createToolResponse(false, undefined, '', 'Error: Task update missing required fields (id, status)');
599
+ }
600
+ // Validate status
601
+ if (!['pending', 'in_progress', 'completed'].includes(update.status)) {
602
+ return createToolResponse(false, undefined, '', `Error: Invalid status '${update.status}'`);
603
+ }
604
+ // Find and update the task
605
+ let taskFound = false;
606
+ for (const task of currentTaskList.tasks) {
607
+ if (task.id === update.id) {
608
+ const oldStatus = task.status;
609
+ task.status = update.status;
610
+ // Add notes if provided
611
+ if (update.notes) {
612
+ task.notes = update.notes;
613
+ }
614
+ // Add update timestamp
615
+ task.updated_at = new Date().toISOString();
616
+ updatesMade.push({
617
+ id: update.id,
618
+ description: task.description,
619
+ old_status: oldStatus,
620
+ new_status: update.status
621
+ });
622
+ taskFound = true;
623
+ break;
624
+ }
625
+ }
626
+ if (!taskFound) {
627
+ return createToolResponse(false, undefined, '', `Error: Task '${update.id}' not found`);
628
+ }
629
+ }
630
+ // Return a deep copy to prevent mutation of historical displays
631
+ const snapshot = {
632
+ user_query: currentTaskList.user_query,
633
+ tasks: currentTaskList.tasks.map(task => ({ ...task })),
634
+ created_at: currentTaskList.created_at
635
+ };
636
+ return createToolResponse(true, snapshot, `Updated ${updatesMade.length} task(s)`);
637
+ }
638
+ catch (error) {
639
+ return createToolResponse(false, undefined, '', `Error: Failed to update tasks - ${error}`);
640
+ }
641
+ }
642
+ /**
643
+ * Find files matching a glob pattern
644
+ */
645
+ export async function glob(pattern) {
646
+ try {
647
+ const files = await fg(pattern, {
648
+ ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
649
+ dot: true
650
+ });
651
+ return createToolResponse(true, files, `Found ${files.length} file(s) matching ${pattern}`);
652
+ }
653
+ catch (error) {
654
+ return createToolResponse(false, undefined, '', `Glob error: ${error}`);
655
+ }
656
+ }
657
+ /**
658
+ * Search text in files using ripgrep
659
+ */
660
+ export async function grep(pattern, include, context = 2, caseSensitive = false, directory = '.', fixedStrings = false) {
661
+ try {
662
+ const matches = await runRipGrep({
663
+ pattern,
664
+ directory,
665
+ include,
666
+ caseSensitive,
667
+ fixedStrings,
668
+ context
669
+ });
670
+ if (matches.length === 0) {
671
+ return createToolResponse(true, [], `No matches found for "${pattern}"`);
672
+ }
673
+ const wasTruncated = matches.length >= 1000;
674
+ const results = wasTruncated ? matches.slice(0, 1000) : matches;
675
+ const message = `Found ${matches.length} match(es)${wasTruncated ? ' (truncated to 1000)' : ''}`;
676
+ return createToolResponse(true, results, message);
677
+ }
678
+ catch (error) {
679
+ return createToolResponse(false, undefined, '', `Grep error: ${error}`);
680
+ }
681
+ }
682
+ /**
683
+ * Save a fact to the project's long-term memory (SORY.md)
684
+ */
685
+ export async function saveMemory(fact) {
686
+ try {
687
+ const memoryFilePath = path.join(process.cwd(), 'SORY.md');
688
+ const timestamp = new Date().toISOString().split('T')[0];
689
+ const entry = `\n- [${timestamp}] ${fact}`;
690
+ let content = '';
691
+ if (fs.existsSync(memoryFilePath)) {
692
+ content = await fs.promises.readFile(memoryFilePath, 'utf-8');
693
+ }
694
+ if (!content.includes('# SORY MEMORY')) {
695
+ content = `# SORY MEMORY\nCette liste contient des faits importants mémorisés par l'IA pour ce projet.\n${content}`;
696
+ }
697
+ await fs.promises.writeFile(memoryFilePath, content + entry);
698
+ return createToolResponse(true, undefined, `J'ai mémorisé : "${fact}" dans SORY.md`);
699
+ }
700
+ catch (error) {
701
+ return createToolResponse(false, undefined, '', `Erreur lors de la mémorisation : ${error}`);
702
+ }
703
+ }
704
+ /**
705
+ * Search Wikipedia (Free API)
706
+ */
707
+ async function searchWikipedia(query) {
708
+ try {
709
+ const url = `https://fr.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&format=json&origin=*`;
710
+ const response = await fetch(url);
711
+ const data = await response.json();
712
+ return (data.query?.search || []).map((res) => ({
713
+ title: `[Wikipedia] ${res.title}`,
714
+ url: `https://fr.wikipedia.org/wiki/${encodeURIComponent(res.title)}`,
715
+ description: res.snippet.replace(/<[^>]*>/g, ''),
716
+ source: 'wikipedia'
717
+ }));
718
+ }
719
+ catch {
720
+ return [];
721
+ }
722
+ }
723
+ /**
724
+ * Search NPM Packages (Public API)
725
+ */
726
+ async function searchNPM(query) {
727
+ try {
728
+ const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=5`;
729
+ const response = await fetch(url);
730
+ const data = await response.json();
731
+ return (data.objects || []).map((res) => ({
732
+ title: `[NPM Package] ${res.package.name} (v${res.package.version})`,
733
+ url: `https://www.npmjs.com/package/${res.package.name}`,
734
+ description: res.package.description,
735
+ source: 'npm'
736
+ }));
737
+ }
738
+ catch {
739
+ return [];
740
+ }
741
+ }
742
+ /**
743
+ * Search PyPI Packages (Python)
744
+ */
745
+ async function searchPyPI(query) {
746
+ try {
747
+ const url = `https://pypi.org/pypi/${encodeURIComponent(query)}/json`;
748
+ const response = await fetch(url);
749
+ if (!response.ok)
750
+ return [];
751
+ const data = await response.json();
752
+ return [{
753
+ title: `[PyPI Package] ${data.info.name} (v${data.info.version})`,
754
+ url: data.info.package_url,
755
+ description: data.info.summary,
756
+ source: 'pypi'
757
+ }];
758
+ }
759
+ catch {
760
+ return [];
761
+ }
762
+ }
763
+ /**
764
+ * Search GitHub with high inclusivity and version detection
765
+ */
766
+ /**
767
+ * Search GitHub with high inclusivity and version detection
768
+ */
769
+ async function searchGitHub(query) {
770
+ const results = [];
771
+ try {
772
+ // Nettoyage léger pour ne pas casser la recherche
773
+ const q = query.replace(/cli|version|dernier|latest/gi, '').trim();
774
+ // Recherche de dépôts par nom, description ou readme
775
+ const url = `https://api.github.com/search/repositories?q=${encodeURIComponent(q)}+in:name,description&sort=stars&order=desc&per_page=10`;
776
+ const response = await fetch(url, {
777
+ headers: {
778
+ 'Accept': 'application/vnd.github.v3+json',
779
+ 'User-Agent': 'Mozilla/5.0 (Sory-CLI)'
780
+ }
781
+ });
782
+ if (!response.ok)
783
+ return [];
784
+ const data = await response.json();
785
+ if (data.items) {
786
+ for (const repo of data.items) {
787
+ results.push({
788
+ title: `[GitHub Repo] ${repo.full_name}`,
789
+ url: repo.html_url,
790
+ description: repo.description || "Pas de description disponible.",
791
+ source: 'github'
792
+ });
793
+ // Tentative de récupération de la version si mentionnée
794
+ if (query.toLowerCase().match(/version|dernier|latest/)) {
795
+ try {
796
+ const relUrl = `https://api.github.com/repos/${repo.full_name}/releases/latest`;
797
+ const relRes = await fetch(relUrl, { headers: { 'User-Agent': 'Sory-CLI' } });
798
+ if (relRes.ok) {
799
+ const relData = await relRes.json();
800
+ results.push({
801
+ title: `[GitHub Release] ${repo.full_name} ${relData.tag_name}`,
802
+ url: relData.html_url,
803
+ description: `Version : ${relData.name || relData.tag_name}. Publiée le ${new Date(relData.published_at).toLocaleDateString('fr-FR')}`,
804
+ source: 'github'
805
+ });
806
+ }
807
+ }
808
+ catch { }
809
+ }
810
+ }
811
+ }
812
+ }
813
+ catch { /* ignore error */ }
814
+ return results;
815
+ }
816
+ /**
817
+ * Search the web using multiple sources with technical prioritization and manual override
818
+ */
819
+ export async function googleWebSearch(query, source = 'auto') {
820
+ try {
821
+ const isTechnical = /code|api|repo|git|version|framework|lib|package|software|install|dernier|cli|opencode/i.test(query);
822
+ let github = [], npm = [], pypi = [], wiki = [], ddg = [];
823
+ // Lancer toutes les sources spécialisées + web en parallèle
824
+ const tasks = [];
825
+ if (source === 'github' || source === 'auto')
826
+ tasks.push(searchGitHub(query).then(res => github = res));
827
+ if (source === 'npm' || source === 'auto')
828
+ tasks.push(searchNPM(query).then(res => npm = res));
829
+ if (source === 'pypi' || source === 'auto')
830
+ tasks.push(searchPyPI(query).then(res => pypi = res));
831
+ if (source === 'wikipedia' || source === 'auto')
832
+ tasks.push(searchWikipedia(query).then(res => wiki = res));
833
+ if (source === 'web' || source === 'auto')
834
+ tasks.push(fetchDuckDuckGo(query, isTechnical).then(res => ddg = res));
835
+ await Promise.all(tasks);
836
+ // Fusion intelligente : Priorité absolue aux paquets et repos
837
+ let combined = [...npm, ...github, ...pypi, ...ddg, ...wiki];
838
+ // Supprimer les doublons et filtrage drastique du bruit
839
+ const seenUrls = new Set();
840
+ combined = combined.filter(res => {
841
+ if (!res.url || seenUrls.has(res.url))
842
+ return false;
843
+ seenUrls.add(res.url);
844
+ // Si c'est une recherche technique et qu'on a des résultats tech, on vire le Wikipedia générique
845
+ if (res.source === 'wikipedia' && isTechnical && (github.length > 0 || npm.length > 0 || ddg.some(d => d.url.includes('github') || d.url.includes('npm')))) {
846
+ const queryLower = query.toLowerCase();
847
+ const firstWord = queryLower.split(' ')[0];
848
+ // On ne garde Wikipedia que si le titre correspond vraiment à l'outil
849
+ if (!res.title.toLowerCase().includes(firstWord))
850
+ return false;
851
+ }
852
+ return true;
853
+ });
854
+ if (combined.length === 0) {
855
+ return createToolResponse(true, [], `Désolé, aucune information trouvée pour "${query}".`);
856
+ }
857
+ const sourceMsg = source === 'auto' ? "Analyse multi-sources" : `Recherche ciblée ${source.toUpperCase()}`;
858
+ return createToolResponse(true, combined.slice(0, 10), `${sourceMsg} pour "${query}"`);
859
+ }
860
+ catch (error) {
861
+ return createToolResponse(false, undefined, '', `Erreur de recherche technique : ${error}`);
862
+ }
863
+ }
864
+ /**
865
+ * Enhanced DuckDuckGo Scraper with site-specific capabilities
866
+ */
867
+ async function fetchDuckDuckGo(query, prioritizeTech) {
868
+ try {
869
+ // Si c'est technique, on ajoute des sites de confiance à la recherche
870
+ const enhancedQuery = prioritizeTech
871
+ ? `${query} (site:github.com OR site:npmjs.com OR site:pypi.org OR site:stackoverflow.com)`
872
+ : query;
873
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(enhancedQuery)}`;
874
+ const response = await fetch(url, {
875
+ headers: {
876
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
877
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
878
+ }
879
+ });
880
+ if (!response.ok)
881
+ return [];
882
+ const html = await response.text();
883
+ const results = [];
884
+ const resultRegex = /<a class="result__a" href="([^"]+)">([^<]+)<\/a>/g;
885
+ let match;
886
+ while ((match = resultRegex.exec(html)) !== null && results.length < 8) {
887
+ let link = match[1];
888
+ if (link.includes('uddg=')) {
889
+ const parts = link.split('uddg=');
890
+ if (parts[1])
891
+ link = decodeURIComponent(parts[1].split('&')[0]);
892
+ }
893
+ if (!link.includes('w3.org') && !link.includes('dtd')) {
894
+ results.push({
895
+ title: match[2].trim(),
896
+ url: link,
897
+ source: 'web'
898
+ });
899
+ }
900
+ }
901
+ return results;
902
+ }
903
+ catch {
904
+ return [];
905
+ }
906
+ }
907
+ /**
908
+ * Fetch and extract text from a website
909
+ */
910
+ export async function webFetch(url) {
911
+ try {
912
+ const response = await fetch(url, {
913
+ headers: {
914
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
915
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
916
+ },
917
+ redirect: 'follow'
918
+ });
919
+ if (!response.ok)
920
+ throw new Error(`HTTP ${response.status}`);
921
+ const html = await response.text();
922
+ const text = convert(html, {
923
+ wordwrap: 130,
924
+ selectors: [
925
+ { selector: 'a', options: { ignoreHref: true } },
926
+ { selector: 'img', format: 'skip' },
927
+ { selector: 'nav', format: 'skip' },
928
+ { selector: 'footer', format: 'skip' },
929
+ { selector: 'script', format: 'skip' },
930
+ { selector: 'style', format: 'skip' }
931
+ ]
932
+ });
933
+ const cleanText = text.substring(0, 15000); // Augmentation de la limite à 15k
934
+ return createToolResponse(true, cleanText, `Contenu extrait de ${url}`);
935
+ }
936
+ catch (error) {
937
+ return createToolResponse(false, undefined, '', `Erreur de lecture web : ${error}`);
938
+ }
939
+ }
940
+ // Tool Registry: maps tool names to functions
941
+ export const TOOL_REGISTRY = {
942
+ read_file: readFile,
943
+ create_file: createFile,
944
+ edit_file: editFile,
945
+ delete_file: deleteFile,
946
+ list_files: listFiles,
947
+ search_files: searchFiles,
948
+ execute_command: executeCommand,
949
+ create_tasks: createTasks,
950
+ update_tasks: updateTasks,
951
+ grep: grep,
952
+ glob: glob,
953
+ read_many_files: readManyFiles,
954
+ save_memory: saveMemory,
955
+ google_web_search: googleWebSearch,
956
+ web_fetch: webFetch,
957
+ };
958
+ /**
959
+ * Execute a tool by name with given arguments
960
+ */
961
+ export async function executeTool(toolName, toolArgs) {
962
+ if (!(toolName in TOOL_REGISTRY)) {
963
+ return createToolResponse(false, undefined, '', 'Error: Unknown tool');
964
+ }
965
+ try {
966
+ const toolFunction = TOOL_REGISTRY[toolName];
967
+ // Call the function with the appropriate arguments based on the tool
968
+ switch (toolName) {
969
+ case 'read_file':
970
+ return await toolFunction(toolArgs.file_path, toolArgs.start_line, toolArgs.end_line);
971
+ case 'read_many_files':
972
+ return await toolFunction(toolArgs.file_paths);
973
+ case 'create_file':
974
+ return await toolFunction(toolArgs.file_path, toolArgs.content, toolArgs.file_type, toolArgs.overwrite);
975
+ case 'edit_file':
976
+ const oldText = toolArgs.old_string || toolArgs.old_text;
977
+ const newText = toolArgs.new_string || toolArgs.new_text;
978
+ const expected = toolArgs.expected_replacements !== undefined
979
+ ? toolArgs.expected_replacements
980
+ : (toolArgs.replace_all ? 0 : 1);
981
+ return await toolFunction(toolArgs.file_path, oldText, newText, expected);
982
+ case 'delete_file':
983
+ return await toolFunction(toolArgs.file_path, toolArgs.recursive);
984
+ case 'list_files':
985
+ return await toolFunction(toolArgs.directory, toolArgs.pattern, toolArgs.recursive, toolArgs.show_hidden);
986
+ case 'search_files':
987
+ return await toolFunction(toolArgs.pattern, toolArgs.file_pattern, toolArgs.directory, toolArgs.case_sensitive, toolArgs.pattern_type, toolArgs.file_types, toolArgs.exclude_dirs, toolArgs.exclude_files, toolArgs.max_results, toolArgs.context_lines, toolArgs.group_by_file);
988
+ case 'execute_command':
989
+ return await toolFunction(toolArgs.command, toolArgs.command_type, toolArgs.working_directory, toolArgs.timeout);
990
+ case 'create_tasks':
991
+ return await toolFunction(toolArgs.user_query, toolArgs.tasks);
992
+ case 'update_tasks':
993
+ return await toolFunction(toolArgs.task_updates);
994
+ case 'grep':
995
+ return await toolFunction(toolArgs.pattern, toolArgs.include, toolArgs.context, toolArgs.case_sensitive, toolArgs.dir_path || toolArgs.directory || '.', toolArgs.fixed_strings);
996
+ case 'glob':
997
+ return await toolFunction(toolArgs.pattern);
998
+ case 'save_memory':
999
+ return await toolFunction(toolArgs.fact);
1000
+ case 'google_web_search':
1001
+ return await toolFunction(toolArgs.query, toolArgs.source);
1002
+ case 'web_fetch':
1003
+ return await toolFunction(toolArgs.url);
1004
+ default:
1005
+ return createToolResponse(false, undefined, '', 'Error: Tool not implemented');
1006
+ }
1007
+ }
1008
+ catch (error) {
1009
+ if (error instanceof TypeError) {
1010
+ return createToolResponse(false, undefined, '', 'Error: Invalid tool arguments');
1011
+ }
1012
+ return createToolResponse(false, undefined, '', 'Error: Unexpected tool error');
1013
+ }
1014
+ }
1015
+ //# sourceMappingURL=tools.js.map