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,198 @@
1
+ /**
2
+ * Provider Registry - Provider definitions with connection options
3
+ */
4
+
5
+ import type { ProviderName } from './index.js';
6
+ import type { SearchProviderName } from './search/types.js';
7
+
8
+ export interface ConnectionOption {
9
+ method: string;
10
+ name: string;
11
+ envVars: string[];
12
+ description?: string;
13
+ providerImpl?: ProviderName; // Override provider implementation (e.g., vertex-ai for Anthropic via GCP)
14
+ }
15
+
16
+ export interface ProviderDefinition {
17
+ id: ProviderName;
18
+ name: string;
19
+ popularity: number; // Lower = more popular, used for sorting
20
+ connections: ConnectionOption[];
21
+ }
22
+
23
+ export interface SearchProviderDefinition {
24
+ id: SearchProviderName;
25
+ name: string;
26
+ popularity: number;
27
+ connections: ConnectionOption[];
28
+ requiresKey: boolean;
29
+ }
30
+
31
+ /**
32
+ * All supported providers with their connection options
33
+ */
34
+ export const PROVIDERS: ProviderDefinition[] = [
35
+ {
36
+ id: 'anthropic',
37
+ name: 'Anthropic',
38
+ popularity: 1,
39
+ connections: [
40
+ {
41
+ method: 'api_key',
42
+ name: 'API Key',
43
+ envVars: ['ANTHROPIC_API_KEY'],
44
+ description: 'Direct API access',
45
+ },
46
+ {
47
+ method: 'vertex',
48
+ name: 'Google Vertex AI',
49
+ envVars: ['ANTHROPIC_VERTEX_PROJECT_ID', 'GOOGLE_CLOUD_PROJECT'],
50
+ description: 'Claude via GCP',
51
+ providerImpl: 'vertex-ai',
52
+ },
53
+ {
54
+ method: 'bedrock',
55
+ name: 'Amazon Bedrock',
56
+ envVars: ['AWS_ACCESS_KEY_ID', 'AWS_PROFILE'],
57
+ description: 'Claude via AWS (coming soon)',
58
+ },
59
+ ],
60
+ },
61
+ {
62
+ id: 'openai',
63
+ name: 'OpenAI',
64
+ popularity: 2,
65
+ connections: [
66
+ {
67
+ method: 'api_key',
68
+ name: 'API Key',
69
+ envVars: ['OPENAI_API_KEY'],
70
+ description: 'Direct API access',
71
+ },
72
+ ],
73
+ },
74
+ {
75
+ id: 'gemini',
76
+ name: 'Google Gemini',
77
+ popularity: 3,
78
+ connections: [
79
+ {
80
+ method: 'api_key',
81
+ name: 'API Key',
82
+ envVars: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
83
+ description: 'Direct API access',
84
+ },
85
+ ],
86
+ },
87
+ ];
88
+
89
+ /**
90
+ * All supported search providers
91
+ */
92
+ export const SEARCH_PROVIDERS: SearchProviderDefinition[] = [
93
+ {
94
+ id: 'exa',
95
+ name: 'Exa AI',
96
+ popularity: 1,
97
+ connections: [
98
+ {
99
+ method: 'public',
100
+ name: 'Public API',
101
+ envVars: [],
102
+ description: 'No API key required',
103
+ },
104
+ ],
105
+ requiresKey: false,
106
+ },
107
+ {
108
+ id: 'serper',
109
+ name: 'Serper.dev',
110
+ popularity: 2,
111
+ connections: [
112
+ {
113
+ method: 'api_key',
114
+ name: 'API Key',
115
+ envVars: ['SERPER_API_KEY'],
116
+ description: 'Google Search via Serper',
117
+ },
118
+ ],
119
+ requiresKey: true,
120
+ },
121
+ {
122
+ id: 'brave',
123
+ name: 'Brave Search',
124
+ popularity: 3,
125
+ connections: [
126
+ {
127
+ method: 'api_key',
128
+ name: 'API Key',
129
+ envVars: ['BRAVE_API_KEY'],
130
+ description: 'Privacy-focused search',
131
+ },
132
+ ],
133
+ requiresKey: true,
134
+ },
135
+ ];
136
+
137
+ /**
138
+ * Get provider definition by ID
139
+ */
140
+ export function getProvider(id: ProviderName): ProviderDefinition | undefined {
141
+ return PROVIDERS.find((p) => p.id === id);
142
+ }
143
+
144
+ /**
145
+ * Get search provider definition by ID
146
+ */
147
+ export function getSearchProvider(id: SearchProviderName): SearchProviderDefinition | undefined {
148
+ return SEARCH_PROVIDERS.find((p) => p.id === id);
149
+ }
150
+
151
+ /**
152
+ * Get all search providers sorted by popularity
153
+ */
154
+ export function getSearchProvidersSorted(): SearchProviderDefinition[] {
155
+ return [...SEARCH_PROVIDERS].sort((a, b) => a.popularity - b.popularity);
156
+ }
157
+
158
+ /**
159
+ * Get all providers sorted by popularity
160
+ */
161
+ export function getProvidersSorted(): ProviderDefinition[] {
162
+ return [...PROVIDERS].sort((a, b) => a.popularity - b.popularity);
163
+ }
164
+
165
+ // Helper: check if any env var in the list is set
166
+ const hasAnyEnvVar = (envVars: string[]) => envVars.some((v) => !!process.env[v]);
167
+
168
+ /**
169
+ * Check if any of the provider's env vars are set
170
+ */
171
+ export function hasEnvVars(provider: ProviderDefinition): boolean {
172
+ return provider.connections.some((conn) => hasAnyEnvVar(conn.envVars));
173
+ }
174
+
175
+ /**
176
+ * Get the first available connection method (where env vars are set)
177
+ */
178
+ export function getAvailableConnection(
179
+ provider: ProviderDefinition
180
+ ): ConnectionOption | undefined {
181
+ return provider.connections.find((conn) => hasAnyEnvVar(conn.envVars));
182
+ }
183
+
184
+ /**
185
+ * Check if a specific connection option has its env vars set
186
+ */
187
+ export function isConnectionReady(conn: ConnectionOption): boolean {
188
+ return hasAnyEnvVar(conn.envVars);
189
+ }
190
+
191
+ /**
192
+ * Get all available (ready) connections for a provider
193
+ */
194
+ export function getAvailableConnections(
195
+ provider: ProviderDefinition
196
+ ): ConnectionOption[] {
197
+ return provider.connections.filter((conn) => hasAnyEnvVar(conn.envVars));
198
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Brave Search Provider
3
+ *
4
+ * Uses Brave Search API (same as Claude Code).
5
+ * Requires BRAVE_API_KEY environment variable.
6
+ */
7
+
8
+ import type { SearchProvider, SearchResult, SearchOptions } from './types.js';
9
+
10
+ const API_CONFIG = {
11
+ BASE_URL: 'https://api.search.brave.com',
12
+ ENDPOINT: '/res/v1/web/search',
13
+ DEFAULT_NUM_RESULTS: 10,
14
+ DEFAULT_TIMEOUT: 10000,
15
+ } as const;
16
+
17
+ interface BraveWebResult {
18
+ title: string;
19
+ url: string;
20
+ description: string;
21
+ is_source_local?: boolean;
22
+ is_source_both?: boolean;
23
+ }
24
+
25
+ interface BraveResponse {
26
+ web?: {
27
+ results: BraveWebResult[];
28
+ };
29
+ query?: {
30
+ original: string;
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Filter results by allowed/blocked domains
36
+ */
37
+ function filterByDomain(results: SearchResult[], options?: SearchOptions): SearchResult[] {
38
+ if (!options?.allowedDomains?.length && !options?.blockedDomains?.length) {
39
+ return results;
40
+ }
41
+
42
+ return results.filter((result) => {
43
+ try {
44
+ const domain = new URL(result.url).hostname;
45
+
46
+ if (options.allowedDomains?.length) {
47
+ return options.allowedDomains.some(
48
+ (allowed) => domain === allowed || domain.endsWith('.' + allowed)
49
+ );
50
+ }
51
+
52
+ if (options.blockedDomains?.length) {
53
+ return !options.blockedDomains.some(
54
+ (blocked) => domain === blocked || domain.endsWith('.' + blocked)
55
+ );
56
+ }
57
+
58
+ return true;
59
+ } catch {
60
+ return true;
61
+ }
62
+ });
63
+ }
64
+
65
+ export class BraveProvider implements SearchProvider {
66
+ readonly name = 'brave' as const;
67
+ private apiKey: string;
68
+
69
+ constructor(apiKey?: string) {
70
+ this.apiKey = apiKey ?? process.env.BRAVE_API_KEY ?? '';
71
+ if (!this.apiKey) {
72
+ throw new Error('BRAVE_API_KEY environment variable is required for Brave provider');
73
+ }
74
+ }
75
+
76
+ async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
77
+ const params = new URLSearchParams({
78
+ q: query,
79
+ count: String(options?.numResults ?? API_CONFIG.DEFAULT_NUM_RESULTS),
80
+ });
81
+
82
+ const controller = new AbortController();
83
+ const timeoutId = setTimeout(
84
+ () => controller.abort(),
85
+ options?.timeout ?? API_CONFIG.DEFAULT_TIMEOUT
86
+ );
87
+
88
+ try {
89
+ const signals = options?.abortSignal
90
+ ? [controller.signal, options.abortSignal]
91
+ : [controller.signal];
92
+
93
+ const response = await fetch(
94
+ `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINT}?${params.toString()}`,
95
+ {
96
+ method: 'GET',
97
+ headers: {
98
+ Accept: 'application/json',
99
+ 'Accept-Encoding': 'gzip',
100
+ 'X-Subscription-Token': this.apiKey,
101
+ },
102
+ signal: AbortSignal.any(signals),
103
+ }
104
+ );
105
+
106
+ clearTimeout(timeoutId);
107
+
108
+ if (!response.ok) {
109
+ const errorText = await response.text();
110
+ throw new Error(`Brave search error (${response.status}): ${errorText}`);
111
+ }
112
+
113
+ const data = (await response.json()) as BraveResponse;
114
+
115
+ const results: SearchResult[] = (data.web?.results || []).map((item) => ({
116
+ title: item.title,
117
+ url: item.url,
118
+ snippet: item.description,
119
+ }));
120
+
121
+ return filterByDomain(results, options);
122
+ } catch (error) {
123
+ clearTimeout(timeoutId);
124
+
125
+ if (error instanceof Error && error.name === 'AbortError') {
126
+ throw new Error('Search request timed out');
127
+ }
128
+
129
+ throw error;
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Exa AI Search Provider
3
+ *
4
+ * Uses Exa's public MCP endpoint (no API key required).
5
+ * Based on OpenCode's implementation pattern.
6
+ */
7
+
8
+ import type { SearchProvider, SearchResult, SearchOptions } from './types.js';
9
+
10
+ const API_CONFIG = {
11
+ BASE_URL: 'https://mcp.exa.ai',
12
+ ENDPOINT: '/mcp',
13
+ DEFAULT_NUM_RESULTS: 8,
14
+ DEFAULT_TIMEOUT: 25000,
15
+ } as const;
16
+
17
+ interface McpSearchRequest {
18
+ jsonrpc: string;
19
+ id: number;
20
+ method: string;
21
+ params: {
22
+ name: string;
23
+ arguments: {
24
+ query: string;
25
+ numResults?: number;
26
+ livecrawl?: 'fallback' | 'preferred';
27
+ type?: 'auto' | 'fast' | 'deep';
28
+ contextMaxCharacters?: number;
29
+ };
30
+ };
31
+ }
32
+
33
+ interface McpSearchResponse {
34
+ jsonrpc: string;
35
+ result: {
36
+ content: Array<{
37
+ type: string;
38
+ text: string;
39
+ }>;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Parse Exa's response text into structured search results
45
+ *
46
+ * Exa returns results in this format:
47
+ * Title: ...
48
+ * URL: ...
49
+ * Text: ...
50
+ */
51
+ function parseExaResults(text: string): SearchResult[] {
52
+ const results: SearchResult[] = [];
53
+ const lines = text.split('\n');
54
+
55
+ let currentResult: Partial<SearchResult> = {};
56
+ let collectingText = false;
57
+ let textBuffer: string[] = [];
58
+
59
+ for (const line of lines) {
60
+ const trimmed = line.trim();
61
+
62
+ // Check for "Title: ..." line
63
+ if (trimmed.startsWith('Title:')) {
64
+ // Save previous result if exists
65
+ if (currentResult.title && currentResult.url) {
66
+ results.push({
67
+ title: currentResult.title,
68
+ url: currentResult.url,
69
+ snippet: textBuffer.join(' ').trim().substring(0, 300),
70
+ });
71
+ }
72
+ currentResult = { title: trimmed.substring(6).trim() };
73
+ collectingText = false;
74
+ textBuffer = [];
75
+ continue;
76
+ }
77
+
78
+ // Check for "URL: ..." line
79
+ if (trimmed.startsWith('URL:')) {
80
+ currentResult.url = trimmed.substring(4).trim();
81
+ continue;
82
+ }
83
+
84
+ // Check for "Text: ..." line - start of snippet
85
+ if (trimmed.startsWith('Text:')) {
86
+ collectingText = true;
87
+ const initialText = trimmed.substring(5).trim();
88
+ if (initialText) {
89
+ textBuffer.push(initialText);
90
+ }
91
+ continue;
92
+ }
93
+
94
+ // Collect text lines until next Title
95
+ if (collectingText && trimmed && !trimmed.startsWith('Title:')) {
96
+ textBuffer.push(trimmed);
97
+ }
98
+ }
99
+
100
+ // Add the last result
101
+ if (currentResult.title && currentResult.url) {
102
+ results.push({
103
+ title: currentResult.title,
104
+ url: currentResult.url,
105
+ snippet: textBuffer.join(' ').trim().substring(0, 300),
106
+ });
107
+ }
108
+
109
+ return results;
110
+ }
111
+
112
+ /**
113
+ * Filter results by allowed/blocked domains
114
+ */
115
+ function filterByDomain(results: SearchResult[], options?: SearchOptions): SearchResult[] {
116
+ if (!options?.allowedDomains?.length && !options?.blockedDomains?.length) {
117
+ return results;
118
+ }
119
+
120
+ return results.filter((result) => {
121
+ try {
122
+ const domain = new URL(result.url).hostname;
123
+
124
+ if (options.allowedDomains?.length) {
125
+ return options.allowedDomains.some(
126
+ (allowed) => domain === allowed || domain.endsWith('.' + allowed)
127
+ );
128
+ }
129
+
130
+ if (options.blockedDomains?.length) {
131
+ return !options.blockedDomains.some(
132
+ (blocked) => domain === blocked || domain.endsWith('.' + blocked)
133
+ );
134
+ }
135
+
136
+ return true;
137
+ } catch {
138
+ return true;
139
+ }
140
+ });
141
+ }
142
+
143
+ export class ExaProvider implements SearchProvider {
144
+ readonly name = 'exa' as const;
145
+
146
+ async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
147
+ const searchRequest: McpSearchRequest = {
148
+ jsonrpc: '2.0',
149
+ id: 1,
150
+ method: 'tools/call',
151
+ params: {
152
+ name: 'web_search_exa',
153
+ arguments: {
154
+ query,
155
+ type: 'auto',
156
+ numResults: options?.numResults ?? API_CONFIG.DEFAULT_NUM_RESULTS,
157
+ livecrawl: 'fallback',
158
+ contextMaxCharacters: 10000,
159
+ },
160
+ },
161
+ };
162
+
163
+ const controller = new AbortController();
164
+ const timeoutId = setTimeout(
165
+ () => controller.abort(),
166
+ options?.timeout ?? API_CONFIG.DEFAULT_TIMEOUT
167
+ );
168
+
169
+ try {
170
+ const signals = options?.abortSignal
171
+ ? [controller.signal, options.abortSignal]
172
+ : [controller.signal];
173
+
174
+ const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINT}`, {
175
+ method: 'POST',
176
+ headers: {
177
+ Accept: 'application/json, text/event-stream',
178
+ 'Content-Type': 'application/json',
179
+ },
180
+ body: JSON.stringify(searchRequest),
181
+ signal: AbortSignal.any(signals),
182
+ });
183
+
184
+ clearTimeout(timeoutId);
185
+
186
+ if (!response.ok) {
187
+ const errorText = await response.text();
188
+ throw new Error(`Exa search error (${response.status}): ${errorText}`);
189
+ }
190
+
191
+ const responseText = await response.text();
192
+
193
+ // Parse SSE response
194
+ const lines = responseText.split('\n');
195
+ for (const line of lines) {
196
+ if (line.startsWith('data: ')) {
197
+ const data: McpSearchResponse = JSON.parse(line.substring(6));
198
+ if (data.result?.content?.length > 0) {
199
+ const text = data.result.content[0].text;
200
+ const results = parseExaResults(text);
201
+ return filterByDomain(results, options);
202
+ }
203
+ }
204
+ }
205
+
206
+ return [];
207
+ } catch (error) {
208
+ clearTimeout(timeoutId);
209
+
210
+ if (error instanceof Error && error.name === 'AbortError') {
211
+ throw new Error('Search request timed out');
212
+ }
213
+
214
+ throw error;
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Search Providers - Factory and exports
3
+ */
4
+
5
+ export * from './types.js';
6
+ export { ExaProvider } from './exa.js';
7
+ export { SerperProvider } from './serper.js';
8
+ export { BraveProvider } from './brave.js';
9
+
10
+ import type { SearchProvider, SearchProviderName } from './types.js';
11
+ import { ExaProvider } from './exa.js';
12
+ import { SerperProvider } from './serper.js';
13
+ import { BraveProvider } from './brave.js';
14
+ import { getProviderStore } from '../store.js';
15
+
16
+ /**
17
+ * Detect search provider from environment variables
18
+ */
19
+ function detectFromEnv(): SearchProviderName | undefined {
20
+ if (process.env.SERPER_API_KEY) return 'serper';
21
+ if (process.env.BRAVE_API_KEY) return 'brave';
22
+ return undefined;
23
+ }
24
+
25
+ /**
26
+ * Create a search provider instance
27
+ *
28
+ * Priority:
29
+ * 1. Explicit name parameter
30
+ * 2. Configured in provider store
31
+ * 3. Detected from environment variables
32
+ * 4. Default: Exa (no key required)
33
+ */
34
+ export function createSearchProvider(name?: SearchProviderName): SearchProvider {
35
+ const providerName = name ?? getProviderStore().getSearchProvider() ?? detectFromEnv() ?? 'exa';
36
+
37
+ switch (providerName) {
38
+ case 'serper':
39
+ return new SerperProvider();
40
+ case 'brave':
41
+ return new BraveProvider();
42
+ case 'exa':
43
+ default:
44
+ return new ExaProvider();
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Get the name of the current search provider
50
+ */
51
+ export function getCurrentSearchProviderName(): SearchProviderName {
52
+ return getProviderStore().getSearchProvider() ?? detectFromEnv() ?? 'exa';
53
+ }
54
+
55
+ /**
56
+ * Check if a search provider is available (has required API keys)
57
+ */
58
+ export function isSearchProviderAvailable(name: SearchProviderName): boolean {
59
+ switch (name) {
60
+ case 'exa':
61
+ return true; // Always available
62
+ case 'serper':
63
+ return !!process.env.SERPER_API_KEY;
64
+ case 'brave':
65
+ return !!process.env.BRAVE_API_KEY;
66
+ default:
67
+ return false;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Get all available search providers
73
+ */
74
+ export function getAvailableSearchProviders(): SearchProviderName[] {
75
+ const providers: SearchProviderName[] = ['exa']; // Always available
76
+ if (process.env.SERPER_API_KEY) providers.push('serper');
77
+ if (process.env.BRAVE_API_KEY) providers.push('brave');
78
+ return providers;
79
+ }