gencode-ai 0.1.0 → 0.1.2

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 (356) hide show
  1. package/.gencode/settings.local.json +7 -0
  2. package/README.md +20 -102
  3. package/dist/agent/agent.d.ts +43 -2
  4. package/dist/agent/agent.d.ts.map +1 -1
  5. package/dist/agent/agent.js +90 -17
  6. package/dist/agent/agent.js.map +1 -1
  7. package/dist/agent/types.d.ts +9 -1
  8. package/dist/agent/types.d.ts.map +1 -1
  9. package/dist/cli/components/AllModelsSelector.d.ts +11 -0
  10. package/dist/cli/components/AllModelsSelector.d.ts.map +1 -0
  11. package/dist/cli/components/AllModelsSelector.js +153 -0
  12. package/dist/cli/components/AllModelsSelector.js.map +1 -0
  13. package/dist/cli/components/App.d.ts +8 -1
  14. package/dist/cli/components/App.d.ts.map +1 -1
  15. package/dist/cli/components/App.js +276 -40
  16. package/dist/cli/components/App.js.map +1 -1
  17. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  18. package/dist/cli/components/CommandSuggestions.js +3 -0
  19. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  20. package/dist/cli/components/Header.d.ts +1 -1
  21. package/dist/cli/components/Header.d.ts.map +1 -1
  22. package/dist/cli/components/Header.js +4 -6
  23. package/dist/cli/components/Header.js.map +1 -1
  24. package/dist/cli/components/Logo.d.ts +1 -0
  25. package/dist/cli/components/Logo.d.ts.map +1 -1
  26. package/dist/cli/components/Logo.js +16 -3
  27. package/dist/cli/components/Logo.js.map +1 -1
  28. package/dist/cli/components/Messages.d.ts +17 -3
  29. package/dist/cli/components/Messages.d.ts.map +1 -1
  30. package/dist/cli/components/Messages.js +70 -18
  31. package/dist/cli/components/Messages.js.map +1 -1
  32. package/dist/cli/components/ModelSelector.d.ts +7 -7
  33. package/dist/cli/components/ModelSelector.d.ts.map +1 -1
  34. package/dist/cli/components/ModelSelector.js +116 -33
  35. package/dist/cli/components/ModelSelector.js.map +1 -1
  36. package/dist/cli/components/PermissionPrompt.d.ts +60 -0
  37. package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
  38. package/dist/cli/components/PermissionPrompt.js +192 -0
  39. package/dist/cli/components/PermissionPrompt.js.map +1 -0
  40. package/dist/cli/components/ProviderManager.d.ts +8 -0
  41. package/dist/cli/components/ProviderManager.d.ts.map +1 -0
  42. package/dist/cli/components/ProviderManager.js +280 -0
  43. package/dist/cli/components/ProviderManager.js.map +1 -0
  44. package/dist/cli/components/Spinner.d.ts +7 -2
  45. package/dist/cli/components/Spinner.d.ts.map +1 -1
  46. package/dist/cli/components/Spinner.js +116 -25
  47. package/dist/cli/components/Spinner.js.map +1 -1
  48. package/dist/cli/components/TodoList.d.ts +7 -0
  49. package/dist/cli/components/TodoList.d.ts.map +1 -0
  50. package/dist/cli/components/TodoList.js +34 -0
  51. package/dist/cli/components/TodoList.js.map +1 -0
  52. package/dist/cli/components/index.d.ts +1 -0
  53. package/dist/cli/components/index.d.ts.map +1 -1
  54. package/dist/cli/components/index.js +1 -0
  55. package/dist/cli/components/index.js.map +1 -1
  56. package/dist/cli/components/markdown.d.ts +9 -0
  57. package/dist/cli/components/markdown.d.ts.map +1 -0
  58. package/dist/cli/components/markdown.js +129 -0
  59. package/dist/cli/components/markdown.js.map +1 -0
  60. package/dist/cli/components/theme.d.ts +5 -0
  61. package/dist/cli/components/theme.d.ts.map +1 -1
  62. package/dist/cli/components/theme.js +7 -0
  63. package/dist/cli/components/theme.js.map +1 -1
  64. package/dist/cli/index.js +66 -12
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/config/index.d.ts +14 -4
  67. package/dist/config/index.d.ts.map +1 -1
  68. package/dist/config/index.js +19 -3
  69. package/dist/config/index.js.map +1 -1
  70. package/dist/config/levels.d.ts +49 -0
  71. package/dist/config/levels.d.ts.map +1 -0
  72. package/dist/config/levels.js +222 -0
  73. package/dist/config/levels.js.map +1 -0
  74. package/dist/config/loader.d.ts +46 -0
  75. package/dist/config/loader.d.ts.map +1 -0
  76. package/dist/config/loader.js +153 -0
  77. package/dist/config/loader.js.map +1 -0
  78. package/dist/config/manager.d.ts +115 -15
  79. package/dist/config/manager.d.ts.map +1 -1
  80. package/dist/config/manager.js +260 -34
  81. package/dist/config/manager.js.map +1 -1
  82. package/dist/config/manager.test.d.ts +5 -0
  83. package/dist/config/manager.test.d.ts.map +1 -0
  84. package/dist/config/manager.test.js +192 -0
  85. package/dist/config/manager.test.js.map +1 -0
  86. package/dist/config/merger.d.ts +56 -0
  87. package/dist/config/merger.d.ts.map +1 -0
  88. package/dist/config/merger.js +177 -0
  89. package/dist/config/merger.js.map +1 -0
  90. package/dist/config/providers-config.d.ts +28 -0
  91. package/dist/config/providers-config.d.ts.map +1 -0
  92. package/dist/config/providers-config.js +79 -0
  93. package/dist/config/providers-config.js.map +1 -0
  94. package/dist/config/test-utils.d.ts +24 -0
  95. package/dist/config/test-utils.d.ts.map +1 -0
  96. package/dist/config/test-utils.js +55 -0
  97. package/dist/config/test-utils.js.map +1 -0
  98. package/dist/config/types.d.ts +108 -9
  99. package/dist/config/types.d.ts.map +1 -1
  100. package/dist/config/types.js +53 -2
  101. package/dist/config/types.js.map +1 -1
  102. package/dist/memory/import-resolver.d.ts +46 -0
  103. package/dist/memory/import-resolver.d.ts.map +1 -0
  104. package/dist/memory/import-resolver.js +117 -0
  105. package/dist/memory/import-resolver.js.map +1 -0
  106. package/dist/memory/index.d.ts +7 -6
  107. package/dist/memory/index.d.ts.map +1 -1
  108. package/dist/memory/index.js +7 -5
  109. package/dist/memory/index.js.map +1 -1
  110. package/dist/memory/init-prompt.d.ts +22 -0
  111. package/dist/memory/init-prompt.d.ts.map +1 -0
  112. package/dist/memory/init-prompt.js +103 -0
  113. package/dist/memory/init-prompt.js.map +1 -0
  114. package/dist/memory/memory-manager.d.ts +119 -0
  115. package/dist/memory/memory-manager.d.ts.map +1 -0
  116. package/dist/memory/memory-manager.js +587 -0
  117. package/dist/memory/memory-manager.js.map +1 -0
  118. package/dist/memory/rules-parser.d.ts +38 -0
  119. package/dist/memory/rules-parser.d.ts.map +1 -0
  120. package/dist/memory/rules-parser.js +69 -0
  121. package/dist/memory/rules-parser.js.map +1 -0
  122. package/dist/memory/test-utils.d.ts +20 -0
  123. package/dist/memory/test-utils.d.ts.map +1 -0
  124. package/dist/memory/test-utils.js +44 -0
  125. package/dist/memory/test-utils.js.map +1 -0
  126. package/dist/memory/types.d.ts +70 -63
  127. package/dist/memory/types.d.ts.map +1 -1
  128. package/dist/memory/types.js +42 -2
  129. package/dist/memory/types.js.map +1 -1
  130. package/dist/permissions/audit.d.ts +82 -0
  131. package/dist/permissions/audit.d.ts.map +1 -0
  132. package/dist/permissions/audit.js +229 -0
  133. package/dist/permissions/audit.js.map +1 -0
  134. package/dist/permissions/index.d.ts +11 -1
  135. package/dist/permissions/index.d.ts.map +1 -1
  136. package/dist/permissions/index.js +15 -0
  137. package/dist/permissions/index.js.map +1 -1
  138. package/dist/permissions/manager.d.ts +149 -13
  139. package/dist/permissions/manager.d.ts.map +1 -1
  140. package/dist/permissions/manager.js +480 -35
  141. package/dist/permissions/manager.js.map +1 -1
  142. package/dist/permissions/manager.test.d.ts +5 -0
  143. package/dist/permissions/manager.test.d.ts.map +1 -0
  144. package/dist/permissions/manager.test.js +213 -0
  145. package/dist/permissions/manager.test.js.map +1 -0
  146. package/dist/permissions/persistence.d.ts +74 -0
  147. package/dist/permissions/persistence.d.ts.map +1 -0
  148. package/dist/permissions/persistence.js +248 -0
  149. package/dist/permissions/persistence.js.map +1 -0
  150. package/dist/permissions/persistence.test.d.ts +5 -0
  151. package/dist/permissions/persistence.test.d.ts.map +1 -0
  152. package/dist/permissions/persistence.test.js +171 -0
  153. package/dist/permissions/persistence.test.js.map +1 -0
  154. package/dist/permissions/prompt-matcher.d.ts +64 -0
  155. package/dist/permissions/prompt-matcher.d.ts.map +1 -0
  156. package/dist/permissions/prompt-matcher.js +415 -0
  157. package/dist/permissions/prompt-matcher.js.map +1 -0
  158. package/dist/permissions/prompt-matcher.test.d.ts +5 -0
  159. package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
  160. package/dist/permissions/prompt-matcher.test.js +107 -0
  161. package/dist/permissions/prompt-matcher.test.js.map +1 -0
  162. package/dist/permissions/types.d.ts +157 -0
  163. package/dist/permissions/types.d.ts.map +1 -1
  164. package/dist/permissions/types.js +43 -8
  165. package/dist/permissions/types.js.map +1 -1
  166. package/dist/prompts/index.d.ts +92 -0
  167. package/dist/prompts/index.d.ts.map +1 -0
  168. package/dist/prompts/index.js +241 -0
  169. package/dist/prompts/index.js.map +1 -0
  170. package/dist/providers/gemini.d.ts.map +1 -1
  171. package/dist/providers/gemini.js +14 -3
  172. package/dist/providers/gemini.js.map +1 -1
  173. package/dist/providers/index.d.ts +5 -3
  174. package/dist/providers/index.d.ts.map +1 -1
  175. package/dist/providers/index.js +13 -1
  176. package/dist/providers/index.js.map +1 -1
  177. package/dist/providers/registry.d.ts +66 -0
  178. package/dist/providers/registry.d.ts.map +1 -0
  179. package/dist/providers/registry.js +158 -0
  180. package/dist/providers/registry.js.map +1 -0
  181. package/dist/providers/search/brave.d.ts +14 -0
  182. package/dist/providers/search/brave.d.ts.map +1 -0
  183. package/dist/providers/search/brave.js +87 -0
  184. package/dist/providers/search/brave.js.map +1 -0
  185. package/dist/providers/search/exa.d.ts +12 -0
  186. package/dist/providers/search/exa.d.ts.map +1 -0
  187. package/dist/providers/search/exa.js +158 -0
  188. package/dist/providers/search/exa.js.map +1 -0
  189. package/dist/providers/search/index.d.ts +31 -0
  190. package/dist/providers/search/index.d.ts.map +1 -0
  191. package/dist/providers/search/index.js +75 -0
  192. package/dist/providers/search/index.js.map +1 -0
  193. package/dist/providers/search/serper.d.ts +14 -0
  194. package/dist/providers/search/serper.d.ts.map +1 -0
  195. package/dist/providers/search/serper.js +87 -0
  196. package/dist/providers/search/serper.js.map +1 -0
  197. package/dist/providers/search/types.d.ts +21 -0
  198. package/dist/providers/search/types.d.ts.map +1 -0
  199. package/dist/providers/search/types.js +5 -0
  200. package/dist/providers/search/types.js.map +1 -0
  201. package/dist/providers/store.d.ts +104 -0
  202. package/dist/providers/store.d.ts.map +1 -0
  203. package/dist/providers/store.js +171 -0
  204. package/dist/providers/store.js.map +1 -0
  205. package/dist/providers/types.d.ts +7 -1
  206. package/dist/providers/types.d.ts.map +1 -1
  207. package/dist/providers/vertex-ai.d.ts +33 -0
  208. package/dist/providers/vertex-ai.d.ts.map +1 -0
  209. package/dist/providers/vertex-ai.js +407 -0
  210. package/dist/providers/vertex-ai.js.map +1 -0
  211. package/dist/tools/builtin/bash.d.ts.map +1 -1
  212. package/dist/tools/builtin/bash.js +2 -1
  213. package/dist/tools/builtin/bash.js.map +1 -1
  214. package/dist/tools/builtin/edit.d.ts.map +1 -1
  215. package/dist/tools/builtin/edit.js +2 -1
  216. package/dist/tools/builtin/edit.js.map +1 -1
  217. package/dist/tools/builtin/glob.d.ts.map +1 -1
  218. package/dist/tools/builtin/glob.js +2 -1
  219. package/dist/tools/builtin/glob.js.map +1 -1
  220. package/dist/tools/builtin/grep.d.ts.map +1 -1
  221. package/dist/tools/builtin/grep.js +2 -1
  222. package/dist/tools/builtin/grep.js.map +1 -1
  223. package/dist/tools/builtin/read.d.ts.map +1 -1
  224. package/dist/tools/builtin/read.js +2 -1
  225. package/dist/tools/builtin/read.js.map +1 -1
  226. package/dist/tools/builtin/todowrite.d.ts +15 -0
  227. package/dist/tools/builtin/todowrite.d.ts.map +1 -0
  228. package/dist/tools/builtin/todowrite.js +88 -0
  229. package/dist/tools/builtin/todowrite.js.map +1 -0
  230. package/dist/tools/builtin/webfetch.d.ts +20 -0
  231. package/dist/tools/builtin/webfetch.d.ts.map +1 -0
  232. package/dist/tools/builtin/webfetch.js +228 -0
  233. package/dist/tools/builtin/webfetch.js.map +1 -0
  234. package/dist/tools/builtin/websearch.d.ts +17 -0
  235. package/dist/tools/builtin/websearch.d.ts.map +1 -0
  236. package/dist/tools/builtin/websearch.js +87 -0
  237. package/dist/tools/builtin/websearch.js.map +1 -0
  238. package/dist/tools/builtin/write.d.ts.map +1 -1
  239. package/dist/tools/builtin/write.js +2 -1
  240. package/dist/tools/builtin/write.js.map +1 -1
  241. package/dist/tools/index.d.ts +18 -0
  242. package/dist/tools/index.d.ts.map +1 -1
  243. package/dist/tools/index.js +28 -2
  244. package/dist/tools/index.js.map +1 -1
  245. package/dist/tools/types.d.ts +41 -0
  246. package/dist/tools/types.d.ts.map +1 -1
  247. package/dist/tools/types.js +16 -0
  248. package/dist/tools/types.js.map +1 -1
  249. package/dist/tools/utils/ssrf.d.ts +18 -0
  250. package/dist/tools/utils/ssrf.d.ts.map +1 -0
  251. package/dist/tools/utils/ssrf.js +70 -0
  252. package/dist/tools/utils/ssrf.js.map +1 -0
  253. package/docs/README.md +5 -4
  254. package/docs/config-system-comparison.md +707 -0
  255. package/docs/memory-system.md +238 -0
  256. package/docs/permissions.md +368 -0
  257. package/docs/proposals/0001-web-fetch-tool.md +32 -2
  258. package/docs/proposals/0002-web-search-tool.md +59 -2
  259. package/docs/proposals/0005-todo-system.md +350 -85
  260. package/docs/proposals/0006-memory-system.md +11 -10
  261. package/docs/proposals/0012-ask-user-question.md +941 -206
  262. package/docs/proposals/0023-permission-enhancements.md +61 -2
  263. package/docs/proposals/0041-configuration-system.md +587 -0
  264. package/docs/proposals/0042-prompt-optimization.md +866 -0
  265. package/docs/proposals/README.md +8 -6
  266. package/docs/providers.md +220 -0
  267. package/jest.config.js +26 -0
  268. package/package.json +14 -3
  269. package/src/agent/agent.ts +120 -18
  270. package/src/agent/types.ts +9 -1
  271. package/src/cli/components/App.tsx +369 -47
  272. package/src/cli/components/CommandSuggestions.tsx +3 -0
  273. package/src/cli/components/Header.tsx +11 -17
  274. package/src/cli/components/Logo.tsx +76 -9
  275. package/src/cli/components/Messages.tsx +146 -38
  276. package/src/cli/components/ModelSelector.tsx +169 -52
  277. package/src/cli/components/PermissionPrompt.tsx +388 -0
  278. package/src/cli/components/ProviderManager.tsx +534 -0
  279. package/src/cli/components/Spinner.tsx +138 -25
  280. package/src/cli/components/TodoList.tsx +54 -0
  281. package/src/cli/components/index.ts +6 -0
  282. package/src/cli/components/markdown.ts +157 -0
  283. package/src/cli/components/theme.ts +7 -0
  284. package/src/cli/index.tsx +76 -13
  285. package/src/config/index.ts +79 -4
  286. package/src/config/levels.test.ts +163 -0
  287. package/src/config/levels.ts +285 -0
  288. package/src/config/loader.test.ts +120 -0
  289. package/src/config/loader.ts +178 -0
  290. package/src/config/manager.test.ts +215 -0
  291. package/src/config/manager.ts +328 -40
  292. package/src/config/merger.test.ts +360 -0
  293. package/src/config/merger.ts +221 -0
  294. package/src/config/providers-config.ts +85 -0
  295. package/src/config/test-utils.ts +79 -0
  296. package/src/config/types.ts +186 -9
  297. package/src/memory/import-resolver.test.ts +117 -0
  298. package/src/memory/import-resolver.ts +149 -0
  299. package/src/memory/index.ts +11 -0
  300. package/src/memory/init-prompt.ts +113 -0
  301. package/src/memory/memory-manager.test.ts +198 -0
  302. package/src/memory/memory-manager.ts +716 -0
  303. package/src/memory/rules-parser.test.ts +182 -0
  304. package/src/memory/rules-parser.ts +82 -0
  305. package/src/memory/test-utils.ts +60 -0
  306. package/src/memory/types.ts +119 -0
  307. package/src/permissions/audit.ts +284 -0
  308. package/src/permissions/index.ts +20 -1
  309. package/src/permissions/manager.test.ts +260 -0
  310. package/src/permissions/manager.ts +592 -40
  311. package/src/permissions/persistence.test.ts +220 -0
  312. package/src/permissions/persistence.ts +301 -0
  313. package/src/permissions/prompt-matcher.test.ts +213 -0
  314. package/src/permissions/prompt-matcher.ts +472 -0
  315. package/src/permissions/types.ts +236 -8
  316. package/src/prompts/index.test.ts +279 -0
  317. package/src/prompts/index.ts +306 -0
  318. package/src/prompts/system/anthropic.txt +29 -0
  319. package/src/prompts/system/base.txt +124 -0
  320. package/src/prompts/system/gemini.txt +35 -0
  321. package/src/prompts/system/generic.txt +128 -0
  322. package/src/prompts/system/openai.txt +29 -0
  323. package/src/prompts/tools/bash.txt +60 -0
  324. package/src/prompts/tools/edit.txt +29 -0
  325. package/src/prompts/tools/glob.txt +35 -0
  326. package/src/prompts/tools/grep.txt +43 -0
  327. package/src/prompts/tools/read.txt +22 -0
  328. package/src/prompts/tools/todowrite.txt +71 -0
  329. package/src/prompts/tools/webfetch.txt +34 -0
  330. package/src/prompts/tools/websearch.txt +41 -0
  331. package/src/prompts/tools/write.txt +23 -0
  332. package/src/providers/gemini.ts +20 -4
  333. package/src/providers/index.ts +18 -3
  334. package/src/providers/registry.ts +198 -0
  335. package/src/providers/search/brave.ts +132 -0
  336. package/src/providers/search/exa.ts +217 -0
  337. package/src/providers/search/index.ts +79 -0
  338. package/src/providers/search/serper.ts +133 -0
  339. package/src/providers/search/types.ts +24 -0
  340. package/src/providers/store.ts +216 -0
  341. package/src/providers/types.ts +9 -1
  342. package/src/providers/vertex-ai.ts +594 -0
  343. package/src/tools/builtin/bash.ts +2 -1
  344. package/src/tools/builtin/edit.ts +2 -1
  345. package/src/tools/builtin/glob.ts +2 -1
  346. package/src/tools/builtin/grep.ts +2 -1
  347. package/src/tools/builtin/read.ts +2 -1
  348. package/src/tools/builtin/todowrite.ts +102 -0
  349. package/src/tools/builtin/webfetch.ts +261 -0
  350. package/src/tools/builtin/websearch.ts +103 -0
  351. package/src/tools/builtin/write.ts +2 -1
  352. package/src/tools/index.ts +28 -2
  353. package/src/tools/types.ts +32 -0
  354. package/src/tools/utils/ssrf.ts +79 -0
  355. package/tsconfig.json +1 -1
  356. package/CLAUDE.md +0 -70
@@ -0,0 +1,102 @@
1
+ /**
2
+ * TodoWrite Tool - Manage task list for tracking progress
3
+ */
4
+
5
+ import type { Tool, ToolResult, TodoItem } from '../types.js';
6
+ import { TodoWriteInputSchema, type TodoWriteInput } from '../types.js';
7
+ import { loadToolDescription } from '../../prompts/index.js';
8
+
9
+ // Global todo state - shared across tool invocations
10
+ let currentTodos: TodoItem[] = [];
11
+
12
+ /**
13
+ * Get the current todo list
14
+ */
15
+ export function getTodos(): TodoItem[] {
16
+ return [...currentTodos];
17
+ }
18
+
19
+ /**
20
+ * Clear all todos
21
+ */
22
+ export function clearTodos(): void {
23
+ currentTodos = [];
24
+ }
25
+
26
+ /**
27
+ * Format todos for display
28
+ */
29
+ function formatTodos(todos: TodoItem[]): string {
30
+ if (todos.length === 0) {
31
+ return 'Todo list is empty.';
32
+ }
33
+
34
+ const statusIcons: Record<string, string> = {
35
+ pending: '[ ]',
36
+ in_progress: '[>]',
37
+ completed: '[x]',
38
+ };
39
+
40
+ const lines = todos.map((todo, index) => {
41
+ const icon = statusIcons[todo.status] || '[ ]';
42
+ return `${index + 1}. ${icon} ${todo.content}`;
43
+ });
44
+
45
+ return lines.join('\n');
46
+ }
47
+
48
+ /**
49
+ * Validate todo list rules
50
+ */
51
+ function validateTodos(todos: TodoItem[]): string | null {
52
+ const inProgress = todos.filter((t) => t.status === 'in_progress');
53
+ if (inProgress.length > 1) {
54
+ return `Only one task should be in_progress at a time. Found ${inProgress.length} tasks in progress.`;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ export const todowriteTool: Tool<TodoWriteInput> = {
60
+ name: 'TodoWrite',
61
+ description: loadToolDescription('todowrite'),
62
+ parameters: TodoWriteInputSchema,
63
+
64
+ async execute(input): Promise<ToolResult> {
65
+ try {
66
+ // Validate the todo list
67
+ const validationError = validateTodos(input.todos);
68
+ if (validationError) {
69
+ return {
70
+ success: false,
71
+ output: '',
72
+ error: validationError,
73
+ };
74
+ }
75
+
76
+ // Update the global todo state (auto-generate id if missing)
77
+ currentTodos = input.todos.map((todo, index) => ({
78
+ ...todo,
79
+ id: todo.id || `todo-${index + 1}`,
80
+ }));
81
+
82
+ // Count statistics
83
+ const pending = currentTodos.filter((t) => t.status === 'pending').length;
84
+ const inProgress = currentTodos.filter((t) => t.status === 'in_progress').length;
85
+ const completed = currentTodos.filter((t) => t.status === 'completed').length;
86
+
87
+ const summary = `Todos updated: ${completed} completed, ${inProgress} in progress, ${pending} pending`;
88
+ const formatted = formatTodos(currentTodos);
89
+
90
+ return {
91
+ success: true,
92
+ output: `${summary}\n\n${formatted}`,
93
+ };
94
+ } catch (error) {
95
+ return {
96
+ success: false,
97
+ output: '',
98
+ error: `Failed to update todos: ${error instanceof Error ? error.message : String(error)}`,
99
+ };
100
+ }
101
+ },
102
+ };
@@ -0,0 +1,261 @@
1
+ /**
2
+ * WebFetch Tool - Fetch and convert web content
3
+ */
4
+
5
+ import TurndownService from 'turndown';
6
+ import { z } from 'zod';
7
+ import type { Tool, ToolContext, ToolResult } from '../types.js';
8
+ import { getErrorMessage } from '../types.js';
9
+ import { validateUrl } from '../utils/ssrf.js';
10
+ import { loadToolDescription } from '../../prompts/index.js';
11
+
12
+ // Constants
13
+ const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
14
+ const DEFAULT_TIMEOUT = 30 * 1000; // 30 seconds
15
+ const MAX_TIMEOUT = 120 * 1000; // 2 minutes
16
+ const MAX_LINE_LENGTH = 2000;
17
+ const MAX_OUTPUT_LENGTH = 50000;
18
+
19
+ // Input schema
20
+ export const WebFetchInputSchema = z.object({
21
+ url: z.string().describe('The URL to fetch content from (http:// or https://)'),
22
+ format: z
23
+ .enum(['text', 'markdown', 'html'])
24
+ .optional()
25
+ .describe('Output format: markdown (default), text, or html'),
26
+ timeout: z.number().optional().describe('Timeout in seconds (default: 30, max: 120)'),
27
+ });
28
+ export type WebFetchInput = z.infer<typeof WebFetchInputSchema>;
29
+
30
+ /**
31
+ * Get Accept header based on requested format
32
+ */
33
+ function getAcceptHeader(format: string): string {
34
+ switch (format) {
35
+ case 'markdown':
36
+ return 'text/markdown, text/plain, text/html;q=0.9, */*;q=0.1';
37
+ case 'text':
38
+ return 'text/plain, text/html;q=0.8, */*;q=0.1';
39
+ case 'html':
40
+ return 'text/html, application/xhtml+xml, */*;q=0.1';
41
+ default:
42
+ return 'text/html, */*;q=0.1';
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Convert HTML to Markdown using Turndown
48
+ */
49
+ function convertHtmlToMarkdown(html: string): string {
50
+ const turndown = new TurndownService({
51
+ headingStyle: 'atx',
52
+ hr: '---',
53
+ bulletListMarker: '-',
54
+ codeBlockStyle: 'fenced',
55
+ emDelimiter: '*',
56
+ });
57
+
58
+ // Remove script, style, meta, link, noscript tags
59
+ turndown.remove(['script', 'style', 'meta', 'link', 'noscript']);
60
+
61
+ return turndown.turndown(html);
62
+ }
63
+
64
+ /**
65
+ * Extract plain text from HTML
66
+ */
67
+ function extractTextFromHtml(html: string): string {
68
+ return (
69
+ html
70
+ // Remove script and style content
71
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
72
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
73
+ .replace(/<noscript[^>]*>[\s\S]*?<\/noscript>/gi, '')
74
+ // Remove all tags
75
+ .replace(/<[^>]+>/g, ' ')
76
+ // Decode common HTML entities
77
+ .replace(/&nbsp;/g, ' ')
78
+ .replace(/&lt;/g, '<')
79
+ .replace(/&gt;/g, '>')
80
+ .replace(/&amp;/g, '&')
81
+ .replace(/&quot;/g, '"')
82
+ .replace(/&#(\d+);/g, (_, num) => String.fromCharCode(parseInt(num)))
83
+ // Normalize whitespace
84
+ .replace(/\s+/g, ' ')
85
+ .trim()
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Process content based on content type and requested format
91
+ */
92
+ function processContent(content: string, contentType: string, format: string): string {
93
+ const isHtml = contentType.includes('text/html') || contentType.includes('application/xhtml');
94
+
95
+ switch (format) {
96
+ case 'markdown':
97
+ if (isHtml) {
98
+ return convertHtmlToMarkdown(content);
99
+ }
100
+ return content;
101
+
102
+ case 'text':
103
+ if (isHtml) {
104
+ return extractTextFromHtml(content);
105
+ }
106
+ return content;
107
+
108
+ case 'html':
109
+ return content;
110
+
111
+ default:
112
+ return content;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Format bytes to human-readable size
118
+ */
119
+ function formatSize(bytes: number): string {
120
+ if (bytes < 1024) return `${bytes}B`;
121
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
122
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
123
+ }
124
+
125
+ /**
126
+ * Truncate output to prevent excessive content
127
+ */
128
+ function truncateOutput(output: string): string {
129
+ // Truncate long lines
130
+ const lines = output.split('\n').map((line) => {
131
+ if (line.length > MAX_LINE_LENGTH) {
132
+ return line.slice(0, MAX_LINE_LENGTH) + '... (truncated)';
133
+ }
134
+ return line;
135
+ });
136
+
137
+ let result = lines.join('\n');
138
+
139
+ // Truncate overall output
140
+ if (result.length > MAX_OUTPUT_LENGTH) {
141
+ result = result.slice(0, MAX_OUTPUT_LENGTH) + '\n\n... (output truncated)';
142
+ }
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * WebFetch Tool
149
+ */
150
+ export const webfetchTool: Tool<WebFetchInput> = {
151
+ name: 'WebFetch',
152
+ description: loadToolDescription('webfetch'),
153
+ parameters: WebFetchInputSchema,
154
+
155
+ async execute(input: WebFetchInput, context: ToolContext): Promise<ToolResult> {
156
+ const startTime = Date.now();
157
+
158
+ try {
159
+ // Validate URL (SSRF protection)
160
+ validateUrl(input.url);
161
+
162
+ // Calculate timeout
163
+ const timeoutMs = input.timeout
164
+ ? Math.min(input.timeout * 1000, MAX_TIMEOUT)
165
+ : DEFAULT_TIMEOUT;
166
+
167
+ // Create abort controller for timeout
168
+ const controller = new AbortController();
169
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
170
+
171
+ // Combine with context abort signal if present
172
+ const signal = context.abortSignal
173
+ ? AbortSignal.any([controller.signal, context.abortSignal])
174
+ : controller.signal;
175
+
176
+ try {
177
+ // Fetch with appropriate headers
178
+ const response = await fetch(input.url, {
179
+ signal,
180
+ headers: {
181
+ 'User-Agent': 'GenCode/1.0 (+https://github.com/gencode)',
182
+ Accept: getAcceptHeader(input.format ?? 'markdown'),
183
+ 'Accept-Language': 'en-US,en;q=0.9',
184
+ },
185
+ redirect: 'follow',
186
+ });
187
+
188
+ clearTimeout(timeoutId);
189
+
190
+ if (!response.ok) {
191
+ return {
192
+ success: false,
193
+ output: '',
194
+ error: `HTTP ${response.status}: ${response.statusText}`,
195
+ };
196
+ }
197
+
198
+ // Check content length header
199
+ const contentLength = response.headers.get('content-length');
200
+ if (contentLength && parseInt(contentLength) > MAX_RESPONSE_SIZE) {
201
+ return {
202
+ success: false,
203
+ output: '',
204
+ error: `Response too large: ${contentLength} bytes (max: ${MAX_RESPONSE_SIZE})`,
205
+ };
206
+ }
207
+
208
+ // Read response body with size limit
209
+ const arrayBuffer = await response.arrayBuffer();
210
+ if (arrayBuffer.byteLength > MAX_RESPONSE_SIZE) {
211
+ return {
212
+ success: false,
213
+ output: '',
214
+ error: `Response too large: ${arrayBuffer.byteLength} bytes (max: ${MAX_RESPONSE_SIZE})`,
215
+ };
216
+ }
217
+
218
+ const content = new TextDecoder().decode(arrayBuffer);
219
+ const contentType = response.headers.get('content-type') || '';
220
+
221
+ // Process content based on format
222
+ let output = processContent(content, contentType, input.format ?? 'markdown');
223
+
224
+ // Truncate long lines and overall output
225
+ output = truncateOutput(output);
226
+
227
+ // Build result with metadata for improved display
228
+ const size = arrayBuffer.byteLength;
229
+ const duration = Date.now() - startTime;
230
+
231
+ return {
232
+ success: true,
233
+ output: output,
234
+ metadata: {
235
+ title: `Fetch(${input.url})`,
236
+ subtitle: `Received ${formatSize(size)} (${response.status} ${response.statusText})`,
237
+ size,
238
+ statusCode: response.status,
239
+ contentType: contentType,
240
+ duration,
241
+ },
242
+ };
243
+ } finally {
244
+ clearTimeout(timeoutId);
245
+ }
246
+ } catch (error) {
247
+ if (error instanceof Error && error.name === 'AbortError') {
248
+ return {
249
+ success: false,
250
+ output: '',
251
+ error: 'Request timed out',
252
+ };
253
+ }
254
+ return {
255
+ success: false,
256
+ output: '',
257
+ error: `Fetch failed: ${getErrorMessage(error)}`,
258
+ };
259
+ }
260
+ },
261
+ };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * WebSearch Tool - Search the web for current information
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import type { Tool, ToolContext, ToolResult } from '../types.js';
7
+ import { getErrorMessage } from '../types.js';
8
+ import {
9
+ createSearchProvider,
10
+ getCurrentSearchProviderName,
11
+ type SearchResult,
12
+ } from '../../providers/search/index.js';
13
+ import { loadToolDescription } from '../../prompts/index.js';
14
+
15
+ // Constants
16
+ const DEFAULT_NUM_RESULTS = 10;
17
+
18
+ // Input schema
19
+ export const WebSearchInputSchema = z.object({
20
+ query: z
21
+ .string()
22
+ .min(2)
23
+ .describe('The search query (minimum 2 characters)'),
24
+ allowed_domains: z
25
+ .array(z.string())
26
+ .optional()
27
+ .describe('Only include results from these domains'),
28
+ blocked_domains: z
29
+ .array(z.string())
30
+ .optional()
31
+ .describe('Exclude results from these domains'),
32
+ num_results: z
33
+ .number()
34
+ .optional()
35
+ .describe(`Number of results to return (default: ${DEFAULT_NUM_RESULTS})`),
36
+ });
37
+ export type WebSearchInput = z.infer<typeof WebSearchInputSchema>;
38
+
39
+ /**
40
+ * Format search results as markdown
41
+ */
42
+ function formatResults(results: SearchResult[], query: string): string {
43
+ if (results.length === 0) {
44
+ return `No results found for "${query}".`;
45
+ }
46
+
47
+ const lines: string[] = [`Found ${results.length} results for "${query}":\n`];
48
+
49
+ results.forEach((result, index) => {
50
+ lines.push(`${index + 1}. [${result.title}](${result.url})`);
51
+ if (result.snippet) {
52
+ lines.push(` ${result.snippet}\n`);
53
+ } else {
54
+ lines.push('');
55
+ }
56
+ });
57
+
58
+ return lines.join('\n');
59
+ }
60
+
61
+ /**
62
+ * WebSearch Tool
63
+ */
64
+ export const websearchTool: Tool<WebSearchInput> = {
65
+ name: 'WebSearch',
66
+ description: loadToolDescription('websearch'),
67
+ parameters: WebSearchInputSchema,
68
+
69
+ async execute(input: WebSearchInput, context: ToolContext): Promise<ToolResult> {
70
+ const startTime = Date.now();
71
+
72
+ try {
73
+ const provider = createSearchProvider();
74
+
75
+ const results = await provider.search(input.query, {
76
+ numResults: input.num_results ?? DEFAULT_NUM_RESULTS,
77
+ allowedDomains: input.allowed_domains,
78
+ blockedDomains: input.blocked_domains,
79
+ abortSignal: context.abortSignal,
80
+ });
81
+
82
+ const output = formatResults(results, input.query);
83
+ const duration = Date.now() - startTime;
84
+ const providerName = getCurrentSearchProviderName();
85
+
86
+ return {
87
+ success: true,
88
+ output,
89
+ metadata: {
90
+ title: `Search("${input.query}")`,
91
+ subtitle: `Found ${results.length} results via ${providerName}`,
92
+ duration,
93
+ },
94
+ };
95
+ } catch (error) {
96
+ return {
97
+ success: false,
98
+ output: '',
99
+ error: `Search failed: ${getErrorMessage(error)}`,
100
+ };
101
+ }
102
+ },
103
+ };
@@ -6,10 +6,11 @@ import * as fs from 'fs/promises';
6
6
  import * as path from 'path';
7
7
  import type { Tool, ToolResult } from '../types.js';
8
8
  import { WriteInputSchema, type WriteInput, resolvePath, getErrorMessage } from '../types.js';
9
+ import { loadToolDescription } from '../../prompts/index.js';
9
10
 
10
11
  export const writeTool: Tool<WriteInput> = {
11
12
  name: 'Write',
12
- description: 'Write content to a file. Creates the file if it does not exist, overwrites if it does.',
13
+ description: loadToolDescription('write'),
13
14
  parameters: WriteInputSchema,
14
15
 
15
16
  async execute(input, context): Promise<ToolResult> {
@@ -12,6 +12,9 @@ export { editTool } from './builtin/edit.js';
12
12
  export { bashTool } from './builtin/bash.js';
13
13
  export { globTool } from './builtin/glob.js';
14
14
  export { grepTool } from './builtin/grep.js';
15
+ export { webfetchTool } from './builtin/webfetch.js';
16
+ export { websearchTool } from './builtin/websearch.js';
17
+ export { todowriteTool, getTodos, clearTodos } from './builtin/todowrite.js';
15
18
 
16
19
  import { ToolRegistry } from './registry.js';
17
20
  import { readTool } from './builtin/read.js';
@@ -20,17 +23,40 @@ import { editTool } from './builtin/edit.js';
20
23
  import { bashTool } from './builtin/bash.js';
21
24
  import { globTool } from './builtin/glob.js';
22
25
  import { grepTool } from './builtin/grep.js';
26
+ import { webfetchTool } from './builtin/webfetch.js';
27
+ import { websearchTool } from './builtin/websearch.js';
28
+ import { todowriteTool } from './builtin/todowrite.js';
23
29
 
24
30
  /**
25
31
  * Create a registry with all built-in tools
26
32
  */
27
33
  export function createDefaultRegistry(): ToolRegistry {
28
34
  const registry = new ToolRegistry();
29
- registry.registerAll([readTool, writeTool, editTool, bashTool, globTool, grepTool]);
35
+ registry.registerAll([
36
+ readTool,
37
+ writeTool,
38
+ editTool,
39
+ bashTool,
40
+ globTool,
41
+ grepTool,
42
+ webfetchTool,
43
+ websearchTool,
44
+ todowriteTool,
45
+ ]);
30
46
  return registry;
31
47
  }
32
48
 
33
49
  /**
34
50
  * All built-in tools
35
51
  */
36
- export const builtinTools = [readTool, writeTool, editTool, bashTool, globTool, grepTool];
52
+ export const builtinTools = [
53
+ readTool,
54
+ writeTool,
55
+ editTool,
56
+ bashTool,
57
+ globTool,
58
+ grepTool,
59
+ webfetchTool,
60
+ websearchTool,
61
+ todowriteTool,
62
+ ];
@@ -14,10 +14,20 @@ export interface ToolContext {
14
14
  abortSignal?: AbortSignal;
15
15
  }
16
16
 
17
+ export interface ToolResultMetadata {
18
+ title?: string; // Short title, e.g., "Fetch(url)"
19
+ subtitle?: string; // Subtitle, e.g., "Received 540.3KB (200 OK)"
20
+ size?: number; // Response size in bytes
21
+ statusCode?: number; // HTTP status code
22
+ contentType?: string; // Content-Type header
23
+ duration?: number; // Duration in milliseconds
24
+ }
25
+
17
26
  export interface ToolResult {
18
27
  success: boolean;
19
28
  output: string;
20
29
  error?: string;
30
+ metadata?: ToolResultMetadata;
21
31
  }
22
32
 
23
33
  export interface Tool<TInput = unknown> {
@@ -88,6 +98,28 @@ export const GrepInputSchema = z.object({
88
98
  });
89
99
  export type GrepInput = z.infer<typeof GrepInputSchema>;
90
100
 
101
+ export const WebFetchInputSchema = z.object({
102
+ url: z.string().describe('The URL to fetch content from (http:// or https://)'),
103
+ format: z
104
+ .enum(['text', 'markdown', 'html'])
105
+ .optional()
106
+ .describe('Output format: markdown (default), text, or html'),
107
+ timeout: z.number().optional().describe('Timeout in seconds (default: 30, max: 120)'),
108
+ });
109
+ export type WebFetchInput = z.infer<typeof WebFetchInputSchema>;
110
+
111
+ export const TodoItemSchema = z.object({
112
+ content: z.string().min(1).describe('The task description'),
113
+ status: z.enum(['pending', 'in_progress', 'completed']).describe('Current status of the task'),
114
+ id: z.string().optional().describe('Unique task identifier'),
115
+ });
116
+ export type TodoItem = z.infer<typeof TodoItemSchema>;
117
+
118
+ export const TodoWriteInputSchema = z.object({
119
+ todos: z.array(TodoItemSchema).describe('The complete todo list to write'),
120
+ });
121
+ export type TodoWriteInput = z.infer<typeof TodoWriteInputSchema>;
122
+
91
123
  // ============================================================================
92
124
  // JSON Schema Conversion
93
125
  // ============================================================================
@@ -0,0 +1,79 @@
1
+ /**
2
+ * SSRF Protection Utilities
3
+ * Prevents Server-Side Request Forgery by blocking internal/private addresses
4
+ */
5
+
6
+ // Private IP ranges (RFC 1918 + loopback + link-local + cloud metadata)
7
+ const PRIVATE_IP_PATTERNS = [
8
+ /^127\./, // Loopback (127.0.0.0/8)
9
+ /^10\./, // Class A private (10.0.0.0/8)
10
+ /^172\.(1[6-9]|2[0-9]|3[01])\./, // Class B private (172.16.0.0/12)
11
+ /^192\.168\./, // Class C private (192.168.0.0/16)
12
+ /^169\.254\./, // Link-local (169.254.0.0/16)
13
+ /^0\./, // "This" network
14
+ /^::1$/, // IPv6 loopback
15
+ /^fe80:/i, // IPv6 link-local
16
+ /^fc00:/i, // IPv6 unique local
17
+ /^fd[0-9a-f]{2}:/i, // IPv6 unique local
18
+ ];
19
+
20
+ const BLOCKED_HOSTNAMES = [
21
+ 'localhost',
22
+ 'localhost.localdomain',
23
+ 'metadata.google.internal', // GCP metadata
24
+ '169.254.169.254', // AWS/GCP/Azure metadata
25
+ ];
26
+
27
+ /**
28
+ * Check if an IP address is in a private range
29
+ */
30
+ export function isPrivateIP(ip: string): boolean {
31
+ return PRIVATE_IP_PATTERNS.some((pattern) => pattern.test(ip));
32
+ }
33
+
34
+ /**
35
+ * Check if a hostname is blocked
36
+ */
37
+ export function isBlockedHostname(hostname: string): boolean {
38
+ const lower = hostname.toLowerCase();
39
+
40
+ // Direct match
41
+ if (BLOCKED_HOSTNAMES.includes(lower)) {
42
+ return true;
43
+ }
44
+
45
+ // Check .local suffix
46
+ if (lower.endsWith('.local')) {
47
+ return true;
48
+ }
49
+
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * Validate a URL for SSRF protection
55
+ * Throws an error if the URL is not allowed
56
+ */
57
+ export function validateUrl(urlString: string): void {
58
+ let parsed: URL;
59
+ try {
60
+ parsed = new URL(urlString);
61
+ } catch {
62
+ throw new Error('Invalid URL format');
63
+ }
64
+
65
+ // Only allow http/https protocols
66
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
67
+ throw new Error('Only http:// and https:// URLs are supported');
68
+ }
69
+
70
+ // Check hostname blocklist
71
+ if (isBlockedHostname(parsed.hostname)) {
72
+ throw new Error('Access to internal/local addresses is not allowed');
73
+ }
74
+
75
+ // Check if hostname is a private IP
76
+ if (isPrivateIP(parsed.hostname)) {
77
+ throw new Error('Access to private IP addresses is not allowed');
78
+ }
79
+ }
package/tsconfig.json CHANGED
@@ -17,5 +17,5 @@
17
17
  "jsx": "react-jsx"
18
18
  },
19
19
  "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist", "examples"]
20
+ "exclude": ["node_modules", "dist", "examples", "**/*.test.ts"]
21
21
  }