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,534 @@
1
+ /**
2
+ * Provider Manager - Manage provider connections
3
+ */
4
+ import { useState, useEffect, useCallback } from 'react';
5
+ import { Box, Text, useInput } from 'ink';
6
+ import TextInput from 'ink-text-input';
7
+ import { colors, icons } from './theme.js';
8
+ import { LoadingSpinner } from './Spinner.js';
9
+ import {
10
+ getProvidersSorted,
11
+ getAvailableConnections,
12
+ isConnectionReady,
13
+ getSearchProvidersSorted,
14
+ type ProviderDefinition,
15
+ type ConnectionOption,
16
+ type SearchProviderDefinition,
17
+ } from '../../providers/registry.js';
18
+ import { getProviderStore, type ModelInfo } from '../../providers/store.js';
19
+ import { createProvider, type ProviderName } from '../../providers/index.js';
20
+ import { isSearchProviderAvailable, type SearchProviderName } from '../../providers/search/index.js';
21
+
22
+ interface ProviderManagerProps {
23
+ onClose: () => void;
24
+ onProviderChange?: (providerId: ProviderName, model: string) => void;
25
+ }
26
+
27
+ type View = 'list' | 'select-connection' | 'confirm-remove' | 'search-list';
28
+ type Tab = 'llm' | 'search';
29
+
30
+ interface ProviderItem {
31
+ provider: ProviderDefinition;
32
+ connected: boolean;
33
+ modelCount: number;
34
+ connectionMethod?: string;
35
+ readyConnections: ConnectionOption[];
36
+ }
37
+
38
+ interface SearchProviderItem {
39
+ provider: SearchProviderDefinition;
40
+ isSelected: boolean;
41
+ isAvailable: boolean;
42
+ }
43
+
44
+ export function ProviderManager({ onClose }: ProviderManagerProps) {
45
+ const store = getProviderStore();
46
+
47
+ const [view, setView] = useState<View>('list');
48
+ const [tab, setTab] = useState<Tab>('llm');
49
+ const [filter, setFilter] = useState('');
50
+ const [selectedIndex, setSelectedIndex] = useState(0);
51
+ const [connectionIndex, setConnectionIndex] = useState(0);
52
+ const [loading, setLoading] = useState(false);
53
+ const [message, setMessage] = useState<string | null>(null);
54
+ const [selectedProvider, setSelectedProvider] = useState<ProviderDefinition | null>(null);
55
+ const [searchSelectedIndex, setSearchSelectedIndex] = useState(0);
56
+
57
+ // Build provider list
58
+ const buildProviderList = useCallback((): ProviderItem[] => {
59
+ const allProviders = getProvidersSorted();
60
+ return allProviders.map((provider) => {
61
+ const connected = store.isConnected(provider.id);
62
+ const connection = store.getConnection(provider.id);
63
+ const readyConnections = getAvailableConnections(provider);
64
+ return {
65
+ provider,
66
+ connected,
67
+ modelCount: store.getModelCount(provider.id),
68
+ connectionMethod: connection?.method,
69
+ readyConnections,
70
+ };
71
+ });
72
+ }, [store]);
73
+
74
+ const [providerList, setProviderList] = useState<ProviderItem[]>(buildProviderList);
75
+
76
+ // Refresh list
77
+ const refreshList = useCallback(() => {
78
+ setProviderList(buildProviderList());
79
+ }, [buildProviderList]);
80
+
81
+ // Build search provider list
82
+ const buildSearchProviderList = useCallback((): SearchProviderItem[] => {
83
+ const currentSearch = store.getSearchProvider();
84
+ return getSearchProvidersSorted().map((provider) => ({
85
+ provider,
86
+ isSelected: provider.id === currentSearch || (!currentSearch && provider.id === 'exa'),
87
+ isAvailable: isSearchProviderAvailable(provider.id),
88
+ }));
89
+ }, [store]);
90
+
91
+ const searchProviders = buildSearchProviderList();
92
+
93
+ // Select search provider
94
+ const selectSearchProvider = (id: SearchProviderName) => {
95
+ if (id === 'exa') {
96
+ store.clearSearchProvider(); // Use default
97
+ } else {
98
+ store.setSearchProvider(id);
99
+ }
100
+ setMessage(`Search provider set to ${id}`);
101
+ setTimeout(() => setMessage(null), 2000);
102
+ };
103
+
104
+ // Filter providers
105
+ const filterLower = filter.toLowerCase();
106
+ const filteredProviders = providerList.filter(
107
+ (item) =>
108
+ item.provider.name.toLowerCase().includes(filterLower) ||
109
+ item.provider.id.toLowerCase().includes(filterLower)
110
+ );
111
+
112
+ // Split into connected and available
113
+ const connected = filteredProviders.filter((p) => p.connected);
114
+ const available = filteredProviders.filter((p) => !p.connected);
115
+
116
+ // Combined list for navigation
117
+ const allItems = [...connected, ...available];
118
+
119
+ // Reset selection when filter changes
120
+ useEffect(() => {
121
+ setSelectedIndex(0);
122
+ }, [filter]);
123
+
124
+ // Fetch and cache models for a provider (use providerImpl if specified)
125
+ const fetchModels = async (
126
+ providerId: ProviderName,
127
+ connOption?: ConnectionOption
128
+ ): Promise<ModelInfo[]> => {
129
+ try {
130
+ // Use providerImpl if specified, otherwise use the provider id
131
+ const implId = connOption?.providerImpl || providerId;
132
+ const provider = createProvider({ provider: implId });
133
+ const models = await provider.listModels();
134
+ store.cacheModels(providerId, models);
135
+ return models;
136
+ } catch {
137
+ return [];
138
+ }
139
+ };
140
+
141
+ // Connect with a specific connection option
142
+ const connectWithOption = async (item: ProviderItem, connOption: ConnectionOption) => {
143
+ setLoading(true);
144
+ setMessage(`Connecting via ${connOption.name}...`);
145
+ store.connect(item.provider.id, connOption.method);
146
+ const models = await fetchModels(item.provider.id, connOption);
147
+ setLoading(false);
148
+ setMessage(`Connected! Cached ${models.length} models`);
149
+ refreshList();
150
+ setView('list');
151
+ setSelectedProvider(null);
152
+ setTimeout(() => setMessage(null), 2000);
153
+ };
154
+
155
+ // Handle connect/refresh
156
+ const handleConnect = async (item: ProviderItem) => {
157
+ if (item.connected) {
158
+ // Refresh: re-fetch models
159
+ setLoading(true);
160
+ setMessage(`Refreshing ${item.provider.name}...`);
161
+ // Get the connection method to find the right provider impl
162
+ const connMethod = item.connectionMethod;
163
+ const connOption = item.provider.connections.find((c) => c.method === connMethod);
164
+ const models = await fetchModels(item.provider.id, connOption);
165
+ setLoading(false);
166
+ setMessage(`Cached ${models.length} models`);
167
+ refreshList();
168
+ setTimeout(() => setMessage(null), 2000);
169
+ } else {
170
+ // Check ready connections
171
+ const readyConns = item.readyConnections;
172
+
173
+ if (readyConns.length === 1) {
174
+ // One ready connection - auto-connect
175
+ await connectWithOption(item, readyConns[0]);
176
+ } else {
177
+ // Zero or multiple ready connections - show selection view
178
+ setSelectedProvider(item.provider);
179
+ setConnectionIndex(0);
180
+ setView('select-connection');
181
+ }
182
+ }
183
+ };
184
+
185
+ // Handle remove
186
+ const handleRemove = (item: ProviderItem) => {
187
+ if (!item.connected) return;
188
+ setSelectedProvider(item.provider);
189
+ setView('confirm-remove');
190
+ };
191
+
192
+ // Confirm remove
193
+ const confirmRemove = () => {
194
+ if (selectedProvider) {
195
+ store.disconnect(selectedProvider.id);
196
+ refreshList();
197
+ setView('list');
198
+ setSelectedProvider(null);
199
+ setMessage('Provider removed');
200
+ setTimeout(() => setMessage(null), 2000);
201
+ }
202
+ };
203
+
204
+ // Go back to list
205
+ const goBack = () => {
206
+ setView('list');
207
+ setSelectedProvider(null);
208
+ setConnectionIndex(0);
209
+ };
210
+
211
+ // Keyboard navigation for list view (LLM providers)
212
+ useInput(
213
+ (input, key) => {
214
+ if (key.tab || input === 's') {
215
+ // Switch to search tab
216
+ setTab('search');
217
+ setSearchSelectedIndex(0);
218
+ } else if (key.upArrow) {
219
+ setSelectedIndex((i) => Math.max(0, i - 1));
220
+ } else if (key.downArrow) {
221
+ setSelectedIndex((i) => Math.min(allItems.length - 1, i + 1));
222
+ } else if (key.return && allItems.length > 0) {
223
+ handleConnect(allItems[selectedIndex]);
224
+ } else if (input === 'r' && allItems.length > 0 && allItems[selectedIndex].connected) {
225
+ handleRemove(allItems[selectedIndex]);
226
+ } else if (key.escape) {
227
+ onClose();
228
+ }
229
+ },
230
+ { isActive: view === 'list' && tab === 'llm' && !loading }
231
+ );
232
+
233
+ // Keyboard navigation for search providers tab
234
+ useInput(
235
+ (input, key) => {
236
+ if (key.tab || input === 'l') {
237
+ // Switch to LLM tab
238
+ setTab('llm');
239
+ setSelectedIndex(0);
240
+ } else if (key.upArrow) {
241
+ setSearchSelectedIndex((i) => Math.max(0, i - 1));
242
+ } else if (key.downArrow) {
243
+ setSearchSelectedIndex((i) => Math.min(searchProviders.length - 1, i + 1));
244
+ } else if (key.return && searchProviders.length > 0) {
245
+ const selected = searchProviders[searchSelectedIndex];
246
+ if (selected.isAvailable) {
247
+ selectSearchProvider(selected.provider.id);
248
+ }
249
+ } else if (key.escape) {
250
+ onClose();
251
+ }
252
+ },
253
+ { isActive: view === 'list' && tab === 'search' && !loading }
254
+ );
255
+
256
+ // Keyboard for select-connection view
257
+ useInput(
258
+ (_input, key) => {
259
+ if (!selectedProvider) return;
260
+ const allConns = selectedProvider.connections;
261
+
262
+ if (key.upArrow) {
263
+ setConnectionIndex((i) => Math.max(0, i - 1));
264
+ } else if (key.downArrow) {
265
+ setConnectionIndex((i) => Math.min(allConns.length - 1, i + 1));
266
+ } else if (key.return && allConns.length > 0) {
267
+ const selectedConn = allConns[connectionIndex];
268
+ if (isConnectionReady(selectedConn)) {
269
+ const item = allItems.find((i) => i.provider.id === selectedProvider.id);
270
+ if (item) {
271
+ connectWithOption(item, selectedConn);
272
+ }
273
+ }
274
+ // If not ready, do nothing (user needs to set env vars first)
275
+ } else if (key.escape) {
276
+ goBack();
277
+ }
278
+ },
279
+ { isActive: view === 'select-connection' }
280
+ );
281
+
282
+ // Keyboard for confirm-remove view
283
+ useInput(
284
+ (input, key) => {
285
+ if (input === 'y' || input === 'Y') {
286
+ confirmRemove();
287
+ } else if (input === 'n' || input === 'N' || key.escape) {
288
+ goBack();
289
+ }
290
+ },
291
+ { isActive: view === 'confirm-remove' }
292
+ );
293
+
294
+
295
+ // Loading state
296
+ if (loading) {
297
+ return (
298
+ <Box flexDirection="column">
299
+ <Box>
300
+ <LoadingSpinner />
301
+ <Text color={colors.textMuted}> {message || 'Loading...'}</Text>
302
+ </Box>
303
+ </Box>
304
+ );
305
+ }
306
+
307
+ // Confirm remove view
308
+ if (view === 'confirm-remove' && selectedProvider) {
309
+ return (
310
+ <Box flexDirection="column">
311
+ <Text color={colors.warning}>Remove {selectedProvider.name}?</Text>
312
+ <Text color={colors.textMuted}>
313
+ This will clear cached models for this provider.
314
+ </Text>
315
+ <Box marginTop={1}>
316
+ <Text color={colors.textMuted}>[Y] Confirm [N] Cancel</Text>
317
+ </Box>
318
+ </Box>
319
+ );
320
+ }
321
+
322
+ // Select connection method view
323
+ if (view === 'select-connection' && selectedProvider) {
324
+ const allConns = selectedProvider.connections;
325
+ const selectedConn = allConns[connectionIndex];
326
+ const isSelectedReady = selectedConn ? isConnectionReady(selectedConn) : false;
327
+
328
+ return (
329
+ <Box flexDirection="column">
330
+ <Text color={colors.primary}>Connect to {selectedProvider.name}</Text>
331
+ <Text color={colors.textMuted}>Select connection method:</Text>
332
+
333
+ <Box flexDirection="column" marginTop={1}>
334
+ {allConns.map((conn, idx) => {
335
+ const isSelected = idx === connectionIndex;
336
+ const ready = isConnectionReady(conn);
337
+ return (
338
+ <Box key={conn.method} paddingLeft={2} flexDirection="column">
339
+ <Box>
340
+ <Text color={isSelected ? colors.primary : colors.textMuted}>
341
+ {isSelected ? icons.arrow : ' '}
342
+ </Text>
343
+ <Text color={isSelected ? colors.text : colors.textSecondary} bold={isSelected}>
344
+ {conn.name}
345
+ </Text>
346
+ {ready ? (
347
+ <Text color={colors.success}> (ready)</Text>
348
+ ) : (
349
+ <Text color={colors.textMuted}> (not configured)</Text>
350
+ )}
351
+ {conn.description && (
352
+ <Text color={colors.textMuted}> - {conn.description}</Text>
353
+ )}
354
+ </Box>
355
+ {isSelected && !ready && (
356
+ <Text color={colors.textMuted} dimColor>
357
+ {' '}Set: {conn.envVars.join(' or ')}
358
+ </Text>
359
+ )}
360
+ </Box>
361
+ );
362
+ })}
363
+ </Box>
364
+
365
+ <Box marginTop={1}>
366
+ <Text color={colors.textMuted}>
367
+ ↑↓ navigate · {isSelectedReady ? 'Enter connect · ' : ''}Esc back
368
+ </Text>
369
+ </Box>
370
+ </Box>
371
+ );
372
+ }
373
+
374
+ // Main list view
375
+ return (
376
+ <Box flexDirection="column">
377
+ <Text color={colors.primary} bold>
378
+ Provider Management
379
+ </Text>
380
+
381
+ {/* Tabs */}
382
+ <Box marginTop={1}>
383
+ <Text
384
+ color={tab === 'llm' ? colors.primary : colors.textMuted}
385
+ bold={tab === 'llm'}
386
+ >
387
+ [L] LLM Providers
388
+ </Text>
389
+ <Text color={colors.textMuted}> | </Text>
390
+ <Text
391
+ color={tab === 'search' ? colors.primary : colors.textMuted}
392
+ bold={tab === 'search'}
393
+ >
394
+ [S] Search Providers
395
+ </Text>
396
+ </Box>
397
+
398
+ {message && (
399
+ <Box marginTop={1}>
400
+ <Text color={colors.success}>{icons.success} {message}</Text>
401
+ </Box>
402
+ )}
403
+
404
+ {/* LLM Providers Tab */}
405
+ {tab === 'llm' && (
406
+ <>
407
+ <Box marginTop={1}>
408
+ <Text color={colors.textMuted}>{icons.prompt} </Text>
409
+ <TextInput value={filter} onChange={setFilter} placeholder="Filter providers..." />
410
+ </Box>
411
+
412
+ <Box flexDirection="column" marginTop={1}>
413
+ {/* Connected section */}
414
+ {connected.length > 0 && (
415
+ <>
416
+ <Text color={colors.textMuted}>Connected:</Text>
417
+ {connected.map((item, idx) => {
418
+ const isSelected = idx === selectedIndex;
419
+ // Find connection name
420
+ const connOption = item.provider.connections.find(
421
+ (c) => c.method === item.connectionMethod
422
+ );
423
+ const connName = connOption?.name || item.connectionMethod;
424
+ return (
425
+ <Box key={item.provider.id} paddingLeft={2}>
426
+ <Text color={isSelected ? colors.primary : colors.textMuted}>
427
+ {isSelected ? icons.arrow : ' '}{' '}
428
+ </Text>
429
+ <Text color={isSelected ? colors.text : colors.textSecondary} bold={isSelected}>
430
+ {item.provider.name}
431
+ </Text>
432
+ <Text color={colors.textMuted}>
433
+ {' '}({connName}) · {item.modelCount} models
434
+ </Text>
435
+ <Text color={colors.success}> {icons.success}</Text>
436
+ </Box>
437
+ );
438
+ })}
439
+ </>
440
+ )}
441
+
442
+ {/* Available section */}
443
+ {available.length > 0 && (
444
+ <>
445
+ <Text color={colors.textMuted} dimColor={connected.length > 0}>
446
+ {connected.length > 0 ? '\n' : ''}Available:
447
+ </Text>
448
+ {available.map((item, idx) => {
449
+ const actualIndex = connected.length + idx;
450
+ const isSelected = actualIndex === selectedIndex;
451
+ const hasReady = item.readyConnections.length > 0;
452
+
453
+ // Show which connection methods are ready
454
+ const readyNames = item.readyConnections.map((c) => c.name);
455
+
456
+ return (
457
+ <Box key={item.provider.id} paddingLeft={2}>
458
+ <Text color={isSelected ? colors.primary : colors.textMuted}>
459
+ {isSelected ? icons.arrow : ' '}{' '}
460
+ </Text>
461
+ <Text color={isSelected ? colors.text : colors.textSecondary} bold={isSelected}>
462
+ {item.provider.name}
463
+ </Text>
464
+ {hasReady && (
465
+ <Text color={colors.success}> ({readyNames.join(', ')})</Text>
466
+ )}
467
+ </Box>
468
+ );
469
+ })}
470
+ </>
471
+ )}
472
+
473
+ {allItems.length === 0 && (
474
+ <Text color={colors.textMuted}>No providers match "{filter}"</Text>
475
+ )}
476
+ </Box>
477
+
478
+ <Box marginTop={1}>
479
+ <Text color={colors.textMuted}>
480
+ ↑↓ navigate · Enter connect · r remove · Tab/s search · Esc exit
481
+ </Text>
482
+ </Box>
483
+ </>
484
+ )}
485
+
486
+ {/* Search Providers Tab */}
487
+ {tab === 'search' && (
488
+ <>
489
+ <Box flexDirection="column" marginTop={1}>
490
+ <Text color={colors.textMuted}>Select search provider:</Text>
491
+ {searchProviders.map((item, idx) => {
492
+ const isSelected = idx === searchSelectedIndex;
493
+ const envVars = item.provider.connections[0]?.envVars || [];
494
+
495
+ return (
496
+ <Box key={item.provider.id} paddingLeft={2} flexDirection="column">
497
+ <Box>
498
+ <Text color={isSelected ? colors.primary : colors.textMuted}>
499
+ {isSelected ? icons.arrow : ' '}{' '}
500
+ </Text>
501
+ <Text color={isSelected ? colors.text : colors.textSecondary} bold={isSelected}>
502
+ {item.provider.name}
503
+ </Text>
504
+ {!item.provider.requiresKey && (
505
+ <Text color={colors.success}> (no key required)</Text>
506
+ )}
507
+ {item.provider.requiresKey && item.isAvailable && (
508
+ <Text color={colors.success}> (configured)</Text>
509
+ )}
510
+ {item.provider.requiresKey && !item.isAvailable && (
511
+ <Text color={colors.textMuted}> (not configured)</Text>
512
+ )}
513
+ {item.isSelected && <Text color={colors.success}> {icons.success}</Text>}
514
+ </Box>
515
+ {isSelected && item.provider.requiresKey && !item.isAvailable && (
516
+ <Text color={colors.textMuted} dimColor>
517
+ {' '}Set: {envVars.join(' or ')}
518
+ </Text>
519
+ )}
520
+ </Box>
521
+ );
522
+ })}
523
+ </Box>
524
+
525
+ <Box marginTop={1}>
526
+ <Text color={colors.textMuted}>
527
+ ↑↓ navigate · Enter select · Tab/l LLM providers · Esc exit
528
+ </Text>
529
+ </Box>
530
+ </>
531
+ )}
532
+ </Box>
533
+ );
534
+ }