gencode-ai 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/agent/agent.d.ts +9 -2
  2. package/dist/agent/agent.d.ts.map +1 -1
  3. package/dist/agent/agent.js +37 -8
  4. package/dist/agent/agent.js.map +1 -1
  5. package/dist/agent/types.d.ts +5 -1
  6. package/dist/agent/types.d.ts.map +1 -1
  7. package/dist/cli/components/App.d.ts.map +1 -1
  8. package/dist/cli/components/App.js +15 -9
  9. package/dist/cli/components/App.js.map +1 -1
  10. package/dist/cli/components/Messages.js +1 -1
  11. package/dist/cli/components/Messages.js.map +1 -1
  12. package/dist/cli/components/ModelSelector.d.ts +4 -3
  13. package/dist/cli/components/ModelSelector.d.ts.map +1 -1
  14. package/dist/cli/components/ModelSelector.js +54 -37
  15. package/dist/cli/components/ModelSelector.js.map +1 -1
  16. package/dist/cli/components/ProviderManager.d.ts +2 -2
  17. package/dist/cli/components/ProviderManager.d.ts.map +1 -1
  18. package/dist/cli/components/ProviderManager.js +137 -156
  19. package/dist/cli/components/ProviderManager.js.map +1 -1
  20. package/dist/cli/index.js +30 -13
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/config/index.d.ts +2 -2
  23. package/dist/config/index.d.ts.map +1 -1
  24. package/dist/config/index.js +1 -1
  25. package/dist/config/index.js.map +1 -1
  26. package/dist/config/levels.d.ts +5 -5
  27. package/dist/config/levels.d.ts.map +1 -1
  28. package/dist/config/levels.js +20 -20
  29. package/dist/config/levels.js.map +1 -1
  30. package/dist/config/merger.js +1 -1
  31. package/dist/config/merger.js.map +1 -1
  32. package/dist/config/providers-config.d.ts +8 -5
  33. package/dist/config/providers-config.d.ts.map +1 -1
  34. package/dist/config/providers-config.js +19 -22
  35. package/dist/config/providers-config.js.map +1 -1
  36. package/dist/config/test-utils.d.ts +2 -2
  37. package/dist/config/test-utils.d.ts.map +1 -1
  38. package/dist/config/test-utils.js +4 -4
  39. package/dist/config/test-utils.js.map +1 -1
  40. package/dist/config/types.d.ts +23 -17
  41. package/dist/config/types.d.ts.map +1 -1
  42. package/dist/config/types.js +14 -14
  43. package/dist/config/types.js.map +1 -1
  44. package/dist/memory/memory-manager.d.ts +25 -12
  45. package/dist/memory/memory-manager.d.ts.map +1 -1
  46. package/dist/memory/memory-manager.js +241 -112
  47. package/dist/memory/memory-manager.js.map +1 -1
  48. package/dist/memory/test-utils.d.ts +1 -1
  49. package/dist/memory/test-utils.d.ts.map +1 -1
  50. package/dist/memory/test-utils.js +3 -3
  51. package/dist/memory/test-utils.js.map +1 -1
  52. package/dist/memory/types.d.ts +20 -10
  53. package/dist/memory/types.d.ts.map +1 -1
  54. package/dist/memory/types.js +13 -13
  55. package/dist/memory/types.js.map +1 -1
  56. package/dist/migration/migrate.d.ts +24 -0
  57. package/dist/migration/migrate.d.ts.map +1 -0
  58. package/dist/migration/migrate.js +164 -0
  59. package/dist/migration/migrate.js.map +1 -0
  60. package/dist/permissions/persistence.d.ts +2 -2
  61. package/dist/permissions/persistence.js +4 -4
  62. package/dist/permissions/persistence.js.map +1 -1
  63. package/dist/planning/plan-file.d.ts +1 -1
  64. package/dist/planning/plan-file.js +2 -2
  65. package/dist/planning/plan-file.js.map +1 -1
  66. package/dist/prompts/index.d.ts +5 -4
  67. package/dist/prompts/index.d.ts.map +1 -1
  68. package/dist/prompts/index.js +11 -8
  69. package/dist/prompts/index.js.map +1 -1
  70. package/dist/providers/anthropic.d.ts +2 -1
  71. package/dist/providers/anthropic.d.ts.map +1 -1
  72. package/dist/providers/anthropic.js +7 -0
  73. package/dist/providers/anthropic.js.map +1 -1
  74. package/dist/providers/gemini.d.ts +2 -1
  75. package/dist/providers/gemini.d.ts.map +1 -1
  76. package/dist/providers/gemini.js +7 -0
  77. package/dist/providers/gemini.js.map +1 -1
  78. package/dist/providers/index.d.ts +20 -10
  79. package/dist/providers/index.d.ts.map +1 -1
  80. package/dist/providers/index.js +48 -24
  81. package/dist/providers/index.js.map +1 -1
  82. package/dist/providers/openai.d.ts +2 -1
  83. package/dist/providers/openai.d.ts.map +1 -1
  84. package/dist/providers/openai.js +7 -0
  85. package/dist/providers/openai.js.map +1 -1
  86. package/dist/providers/registry.d.ts +48 -34
  87. package/dist/providers/registry.d.ts.map +1 -1
  88. package/dist/providers/registry.js +72 -88
  89. package/dist/providers/registry.js.map +1 -1
  90. package/dist/providers/store.d.ts +43 -17
  91. package/dist/providers/store.d.ts.map +1 -1
  92. package/dist/providers/store.js +112 -19
  93. package/dist/providers/store.js.map +1 -1
  94. package/dist/providers/types.d.ts +23 -0
  95. package/dist/providers/types.d.ts.map +1 -1
  96. package/dist/providers/vertex-ai.d.ts +15 -7
  97. package/dist/providers/vertex-ai.d.ts.map +1 -1
  98. package/dist/providers/vertex-ai.js +46 -13
  99. package/dist/providers/vertex-ai.js.map +1 -1
  100. package/dist/session/types.js +1 -1
  101. package/dist/session/types.js.map +1 -1
  102. package/docs/config-system-comparison.md +50 -50
  103. package/docs/cost-tracking-comparison.md +2 -2
  104. package/docs/memory-system.md +124 -31
  105. package/docs/permissions.md +2 -2
  106. package/docs/proposals/0006-memory-system.md +4 -4
  107. package/docs/proposals/0008-checkpointing.md +109 -2
  108. package/docs/proposals/0011-custom-commands.md +2 -1
  109. package/docs/proposals/0021-skills-system.md +2 -1
  110. package/docs/proposals/0023-permission-enhancements.md +2 -2
  111. package/docs/proposals/0033-enterprise-deployment.md +1 -1
  112. package/docs/proposals/0041-configuration-system.md +17 -19
  113. package/docs/proposals/0042-prompt-optimization.md +17 -9
  114. package/docs/proposals/README.md +5 -5
  115. package/docs/providers.md +94 -9
  116. package/package.json +3 -2
  117. package/scripts/migrate.ts +449 -0
  118. package/src/agent/agent.ts +51 -9
  119. package/src/agent/types.ts +5 -1
  120. package/src/cli/components/App.tsx +17 -8
  121. package/src/cli/components/Messages.tsx +1 -1
  122. package/src/cli/components/ModelSelector.tsx +62 -43
  123. package/src/cli/components/ProviderManager.tsx +278 -323
  124. package/src/cli/index.tsx +36 -17
  125. package/src/config/index.ts +5 -3
  126. package/src/config/levels.test.ts +22 -22
  127. package/src/config/levels.ts +22 -22
  128. package/src/config/loader.test.ts +14 -14
  129. package/src/config/manager.test.ts +19 -19
  130. package/src/config/merger.test.ts +23 -23
  131. package/src/config/merger.ts +1 -1
  132. package/src/config/providers-config.ts +23 -21
  133. package/src/config/test-utils.ts +6 -6
  134. package/src/config/types.ts +30 -20
  135. package/src/memory/memory-manager.test.ts +242 -24
  136. package/src/memory/memory-manager.ts +270 -141
  137. package/src/memory/test-utils.ts +4 -4
  138. package/src/memory/types.ts +28 -17
  139. package/src/permissions/persistence.ts +4 -4
  140. package/src/planning/plan-file.ts +2 -2
  141. package/src/prompts/index.ts +13 -9
  142. package/src/providers/anthropic.ts +9 -0
  143. package/src/providers/gemini.ts +9 -0
  144. package/src/providers/index.ts +76 -33
  145. package/src/providers/openai.ts +9 -0
  146. package/src/providers/registry.ts +116 -111
  147. package/src/providers/store.ts +130 -28
  148. package/src/providers/types.ts +33 -1
  149. package/src/providers/vertex-ai.ts +49 -13
  150. package/src/session/types.ts +1 -1
@@ -8,31 +8,32 @@ import { colors, icons } from './theme.js';
8
8
  import { LoadingSpinner } from './Spinner.js';
9
9
  import {
10
10
  getProvidersSorted,
11
- getAvailableConnections,
12
- isConnectionReady,
11
+ getProviderClasses,
12
+ isProviderReady,
13
13
  getSearchProvidersSorted,
14
- type ProviderDefinition,
15
- type ConnectionOption,
14
+ type ProviderClass,
15
+ type ProviderMeta,
16
16
  type SearchProviderDefinition,
17
17
  } from '../../providers/registry.js';
18
18
  import { getProviderStore, type ModelInfo } from '../../providers/store.js';
19
- import { createProvider, type ProviderName } from '../../providers/index.js';
19
+ import { createProvider, type Provider } from '../../providers/index.js';
20
20
  import { isSearchProviderAvailable, type SearchProviderName } from '../../providers/search/index.js';
21
+ import type { AuthMethod } from '../../providers/types.js';
21
22
 
22
23
  interface ProviderManagerProps {
23
24
  onClose: () => void;
24
- onProviderChange?: (providerId: ProviderName, model: string) => void;
25
+ onProviderChange?: (providerId: Provider, model: string) => void;
25
26
  }
26
27
 
27
28
  type View = 'list' | 'select-connection' | 'confirm-remove' | 'search-list';
28
29
  type Tab = 'llm' | 'search';
29
30
 
30
31
  interface ProviderItem {
31
- provider: ProviderDefinition;
32
+ providerMeta: ProviderMeta;
32
33
  connected: boolean;
33
34
  modelCount: number;
34
- connectionMethod?: string;
35
- readyConnections: ConnectionOption[];
35
+ authMethod?: AuthMethod;
36
+ availableClasses: ProviderClass[];
36
37
  }
37
38
 
38
39
  interface SearchProviderItem {
@@ -51,22 +52,24 @@ export function ProviderManager({ onClose }: ProviderManagerProps) {
51
52
  const [connectionIndex, setConnectionIndex] = useState(0);
52
53
  const [loading, setLoading] = useState(false);
53
54
  const [message, setMessage] = useState<string | null>(null);
54
- const [selectedProvider, setSelectedProvider] = useState<ProviderDefinition | null>(null);
55
+ const [selectedProviderMeta, setSelectedProviderMeta] = useState<ProviderMeta | null>(null);
55
56
  const [searchSelectedIndex, setSearchSelectedIndex] = useState(0);
56
57
 
57
58
  // Build provider list
58
59
  const buildProviderList = useCallback((): ProviderItem[] => {
59
60
  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);
61
+ return allProviders.map((providerMeta) => {
62
+ const connected = store.isConnected(providerMeta.id);
63
+ const connection = store.getConnection(providerMeta.id);
64
+ const availableClasses = getProviderClasses(providerMeta.id).filter((cls) =>
65
+ isProviderReady(cls)
66
+ );
64
67
  return {
65
- provider,
68
+ providerMeta,
66
69
  connected,
67
- modelCount: store.getModelCount(provider.id),
68
- connectionMethod: connection?.method,
69
- readyConnections,
70
+ modelCount: store.getModelCount(providerMeta.id),
71
+ authMethod: connection?.authMethod,
72
+ availableClasses,
70
73
  };
71
74
  });
72
75
  }, [store]);
@@ -105,8 +108,8 @@ export function ProviderManager({ onClose }: ProviderManagerProps) {
105
108
  const filterLower = filter.toLowerCase();
106
109
  const filteredProviders = providerList.filter(
107
110
  (item) =>
108
- item.provider.name.toLowerCase().includes(filterLower) ||
109
- item.provider.id.toLowerCase().includes(filterLower)
111
+ item.providerMeta.name.toLowerCase().includes(filterLower) ||
112
+ item.providerMeta.id.toLowerCase().includes(filterLower)
110
113
  );
111
114
 
112
115
  // Split into connected and available
@@ -121,61 +124,61 @@ export function ProviderManager({ onClose }: ProviderManagerProps) {
121
124
  setSelectedIndex(0);
122
125
  }, [filter]);
123
126
 
124
- // Fetch and cache models for a provider (use providerImpl if specified)
127
+ // Fetch and cache models for a provider with specific auth method
125
128
  const fetchModels = async (
126
- providerId: ProviderName,
127
- connOption?: ConnectionOption
129
+ providerId: Provider,
130
+ authMethod: AuthMethod
128
131
  ): Promise<ModelInfo[]> => {
129
132
  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 provider = createProvider({ provider: providerId, authMethod });
133
134
  const models = await provider.listModels();
134
- store.cacheModels(providerId, models);
135
+ store.cacheModels(providerId, authMethod, models);
135
136
  return models;
136
137
  } catch {
137
138
  return [];
138
139
  }
139
140
  };
140
141
 
141
- // Connect with a specific connection option
142
- const connectWithOption = async (item: ProviderItem, connOption: ConnectionOption) => {
142
+ // Connect with a specific provider class (auth method)
143
+ const connectWithClass = async (item: ProviderItem, providerClass: ProviderClass) => {
143
144
  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);
145
+ setMessage(`Connecting via ${providerClass.meta.displayName}...`);
146
+ store.connect(
147
+ item.providerMeta.id,
148
+ providerClass.meta.authMethod,
149
+ providerClass.meta.displayName
150
+ );
151
+ const models = await fetchModels(item.providerMeta.id, providerClass.meta.authMethod);
147
152
  setLoading(false);
148
153
  setMessage(`Connected! Cached ${models.length} models`);
149
154
  refreshList();
150
155
  setView('list');
151
- setSelectedProvider(null);
156
+ setSelectedProviderMeta(null);
152
157
  setTimeout(() => setMessage(null), 2000);
153
158
  };
154
159
 
155
160
  // Handle connect/refresh
156
161
  const handleConnect = async (item: ProviderItem) => {
157
162
  if (item.connected) {
158
- // Refresh: re-fetch models
163
+ // Refresh: re-fetch models using the saved authMethod
159
164
  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
+ setMessage(`Refreshing ${item.providerMeta.name}...`);
166
+ const authMethod = item.authMethod || 'api_key';
167
+ const models = await fetchModels(item.providerMeta.id, authMethod);
165
168
  setLoading(false);
166
169
  setMessage(`Cached ${models.length} models`);
167
170
  refreshList();
168
171
  setTimeout(() => setMessage(null), 2000);
169
172
  } else {
170
- // Check ready connections
171
- const readyConns = item.readyConnections;
173
+ // Check available provider classes
174
+ const availableClasses = item.availableClasses;
172
175
 
173
- if (readyConns.length === 1) {
174
- // One ready connection - auto-connect
175
- await connectWithOption(item, readyConns[0]);
176
+ if (availableClasses.length === 1) {
177
+ // One available auth method - auto-connect
178
+ await connectWithClass(item, availableClasses[0]);
176
179
  } else {
177
- // Zero or multiple ready connections - show selection view
178
- setSelectedProvider(item.provider);
180
+ // Zero or multiple auth methods - show selection view
181
+ setSelectedProviderMeta(item.providerMeta);
179
182
  setConnectionIndex(0);
180
183
  setView('select-connection');
181
184
  }
@@ -185,299 +188,260 @@ export function ProviderManager({ onClose }: ProviderManagerProps) {
185
188
  // Handle remove
186
189
  const handleRemove = (item: ProviderItem) => {
187
190
  if (!item.connected) return;
188
- setSelectedProvider(item.provider);
191
+ setSelectedProviderMeta(item.providerMeta);
189
192
  setView('confirm-remove');
190
193
  };
191
194
 
192
195
  // Confirm remove
193
196
  const confirmRemove = () => {
194
- if (selectedProvider) {
195
- store.disconnect(selectedProvider.id);
197
+ if (selectedProviderMeta) {
198
+ store.disconnect(selectedProviderMeta.id);
196
199
  refreshList();
197
200
  setView('list');
198
- setSelectedProvider(null);
201
+ setSelectedProviderMeta(null);
199
202
  setMessage('Provider removed');
200
203
  setTimeout(() => setMessage(null), 2000);
201
204
  }
202
205
  };
203
206
 
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
207
+ // Input handling for list view
234
208
  useInput(
235
209
  (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);
210
+ if (view === 'list' && tab === 'llm') {
211
+ if (key.downArrow || input === 'j') {
212
+ setSelectedIndex((i) => Math.min(i + 1, allItems.length - 1));
213
+ } else if (key.upArrow || input === 'k') {
214
+ setSelectedIndex((i) => Math.max(i - 1, 0));
215
+ } else if (key.return) {
216
+ const item = allItems[selectedIndex];
217
+ if (item) handleConnect(item);
218
+ } else if (input === 'r' || input === 'R') {
219
+ const item = allItems[selectedIndex];
220
+ if (item?.connected) handleConnect(item);
221
+ } else if (input === 'd' || input === 'D') {
222
+ const item = allItems[selectedIndex];
223
+ if (item) handleRemove(item);
224
+ } else if (input === 'q' || input === 'Q' || key.escape) {
225
+ onClose();
226
+ } else if (input === '/') {
227
+ // Start filter mode (handled by TextInput)
228
+ } else if (key.tab) {
229
+ setTab('search');
230
+ setSearchSelectedIndex(0);
248
231
  }
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);
232
+ } else if (view === 'list' && tab === 'search') {
233
+ if (key.downArrow || input === 'j') {
234
+ setSearchSelectedIndex((i) => Math.min(i + 1, searchProviders.length - 1));
235
+ } else if (key.upArrow || input === 'k') {
236
+ setSearchSelectedIndex((i) => Math.max(i - 1, 0));
237
+ } else if (key.return) {
238
+ const item = searchProviders[searchSelectedIndex];
239
+ if (item && item.isAvailable) {
240
+ selectSearchProvider(item.provider.id);
272
241
  }
242
+ } else if (input === 'q' || input === 'Q' || key.escape) {
243
+ onClose();
244
+ } else if (key.tab) {
245
+ setTab('llm');
246
+ setSelectedIndex(0);
247
+ }
248
+ } else if (view === 'select-connection') {
249
+ const item = allItems.find((p) => p.providerMeta.id === selectedProviderMeta?.id);
250
+ const availableClasses = item?.availableClasses || [];
251
+
252
+ if (key.downArrow || input === 'j') {
253
+ setConnectionIndex((i) => Math.min(i + 1, availableClasses.length - 1));
254
+ } else if (key.upArrow || input === 'k') {
255
+ setConnectionIndex((i) => Math.max(i - 1, 0));
256
+ } else if (key.return) {
257
+ if (item && availableClasses[connectionIndex]) {
258
+ connectWithClass(item, availableClasses[connectionIndex]);
259
+ }
260
+ } else if (key.escape) {
261
+ setView('list');
262
+ setSelectedProviderMeta(null);
263
+ }
264
+ } else if (view === 'confirm-remove') {
265
+ if (input === 'y' || input === 'Y') {
266
+ confirmRemove();
267
+ } else if (input === 'n' || input === 'N' || key.escape) {
268
+ setView('list');
269
+ setSelectedProviderMeta(null);
273
270
  }
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
271
  }
290
272
  },
291
- { isActive: view === 'confirm-remove' }
273
+ { isActive: !loading }
292
274
  );
293
275
 
276
+ // Render connection selection view
277
+ if (view === 'select-connection' && selectedProviderMeta) {
278
+ const item = allItems.find((p) => p.providerMeta.id === selectedProviderMeta.id);
279
+ const availableClasses = item?.availableClasses || [];
294
280
 
295
- // Loading state
296
- if (loading) {
297
281
  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.
282
+ <Box flexDirection="column" padding={1}>
283
+ <Text bold color={colors.primary}>
284
+ Select Authentication Method for {selectedProviderMeta.name}
314
285
  </Text>
286
+ <Text dimColor>Choose how to connect to this provider</Text>
287
+ <Box marginTop={1} flexDirection="column">
288
+ {availableClasses.length === 0 ? (
289
+ <Text color={colors.error}>
290
+ {icons.warning} No authentication methods available
291
+ </Text>
292
+ ) : (
293
+ availableClasses.map((providerClass, idx) => (
294
+ <Box key={providerClass.meta.authMethod}>
295
+ <Text color={idx === connectionIndex ? colors.success : undefined}>
296
+ {idx === connectionIndex ? icons.radio : ' '}{' '}
297
+ </Text>
298
+ <Text
299
+ bold={idx === connectionIndex}
300
+ color={idx === connectionIndex ? colors.success : undefined}
301
+ >
302
+ {providerClass.meta.displayName}
303
+ </Text>
304
+ <Text dimColor> - {providerClass.meta.description}</Text>
305
+ </Box>
306
+ ))
307
+ )}
308
+ </Box>
315
309
  <Box marginTop={1}>
316
- <Text color={colors.textMuted}>[Y] Confirm [N] Cancel</Text>
310
+ <Text dimColor>Press Enter to connect, Esc to cancel</Text>
317
311
  </Box>
318
312
  </Box>
319
313
  );
320
314
  }
321
315
 
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
-
316
+ // Render confirm remove view
317
+ if (view === 'confirm-remove' && selectedProviderMeta) {
328
318
  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
-
319
+ <Box flexDirection="column" padding={1}>
320
+ <Text bold color={colors.error}>
321
+ Remove {selectedProviderMeta.name}?
322
+ </Text>
323
+ <Text dimColor>This will clear all cached models for this provider</Text>
365
324
  <Box marginTop={1}>
366
- <Text color={colors.textMuted}>
367
- ↑↓ navigate · {isSelectedReady ? 'Enter connect · ' : ''}Esc back
368
- </Text>
325
+ <Text dimColor>Press Y to confirm, N to cancel</Text>
369
326
  </Box>
370
327
  </Box>
371
328
  );
372
329
  }
373
330
 
374
- // Main list view
331
+ // Render main list view
375
332
  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
333
+ <Box flexDirection="column" padding={1}>
334
+ {/* Title */}
335
+ <Box>
336
+ <Text bold color={colors.primary}>
337
+ Provider Manager
395
338
  </Text>
339
+ <Text dimColor> - Manage LLM and Search providers</Text>
396
340
  </Box>
397
341
 
398
- {message && (
399
- <Box marginTop={1}>
400
- <Text color={colors.success}>{icons.success} {message}</Text>
342
+ {/* Tabs */}
343
+ <Box marginTop={1}>
344
+ <Box marginRight={2}>
345
+ <Text bold={tab === 'llm'} color={tab === 'llm' ? colors.primary : 'gray'}>
346
+ {tab === 'llm' ? '▶' : ' '} LLM Providers
347
+ </Text>
401
348
  </Box>
402
- )}
349
+ <Box>
350
+ <Text bold={tab === 'search'} color={tab === 'search' ? colors.primary : 'gray'}>
351
+ {tab === 'search' ? '▶' : ' '} Search Providers
352
+ </Text>
353
+ </Box>
354
+ </Box>
403
355
 
404
356
  {/* LLM Providers Tab */}
405
357
  {tab === 'llm' && (
406
358
  <>
359
+ {/* Filter */}
407
360
  <Box marginTop={1}>
408
- <Text color={colors.textMuted}>{icons.prompt} </Text>
409
- <TextInput value={filter} onChange={setFilter} placeholder="Filter providers..." />
361
+ <Text dimColor>Filter: </Text>
362
+ <TextInput value={filter} onChange={setFilter} placeholder="Type to filter..." />
410
363
  </Box>
411
364
 
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:
365
+ {/* Status */}
366
+ {message && (
367
+ <Box marginTop={1}>
368
+ <Text color={colors.success}>{message}</Text>
369
+ </Box>
370
+ )}
371
+
372
+ {loading && (
373
+ <Box marginTop={1}>
374
+ <LoadingSpinner />
375
+ </Box>
376
+ )}
377
+
378
+ {/* Connected Providers */}
379
+ {connected.length > 0 && (
380
+ <>
381
+ <Box marginTop={1}>
382
+ <Text bold color={colors.success}>
383
+ Connected ({connected.length})
447
384
  </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}
385
+ </Box>
386
+ {connected.map((item, idx) => {
387
+ const globalIdx = idx;
388
+ const isSelected = globalIdx === selectedIndex;
389
+ return (
390
+ <Box key={item.providerMeta.id}>
391
+ <Text color={isSelected ? colors.success : undefined}>
392
+ {isSelected ? icons.radio : ' '}{' '}
393
+ </Text>
394
+ <Text bold={isSelected}>{item.providerMeta.name}</Text>
395
+ <Text dimColor> ({item.modelCount} models)</Text>
396
+ {item.authMethod && (
397
+ <Text dimColor> - {item.authMethod}</Text>
398
+ )}
399
+ </Box>
400
+ );
401
+ })}
402
+ </>
403
+ )}
404
+
405
+ {/* Available Providers */}
406
+ {available.length > 0 && (
407
+ <>
408
+ <Box marginTop={1}>
409
+ <Text bold dimColor>
410
+ Available ({available.length})
411
+ </Text>
412
+ </Box>
413
+ {available.map((item, idx) => {
414
+ const globalIdx = connected.length + idx;
415
+ const isSelected = globalIdx === selectedIndex;
416
+ const hasAuth = item.availableClasses.length > 0;
417
+ return (
418
+ <Box key={item.providerMeta.id}>
419
+ <Text color={isSelected ? colors.success : undefined}>
420
+ {isSelected ? icons.radio : ' '}{' '}
421
+ </Text>
422
+ <Text
423
+ bold={isSelected}
424
+ dimColor={!hasAuth}
425
+ color={!hasAuth ? colors.error : undefined}
426
+ >
427
+ {item.providerMeta.name}
428
+ </Text>
429
+ {!hasAuth && (
430
+ <Text dimColor color={colors.error}>
431
+ {' '}
432
+ (no credentials)
463
433
  </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>
434
+ )}
435
+ </Box>
436
+ );
437
+ })}
438
+ </>
439
+ )}
477
440
 
441
+ {/* Help */}
478
442
  <Box marginTop={1}>
479
- <Text color={colors.textMuted}>
480
- ↑↓ navigate · Enter connect · r remove · Tab/s search · Esc exit
443
+ <Text dimColor>
444
+ Enter: Connect/Refresh | R: Refresh | D: Remove | Tab: Switch | Q: Quit
481
445
  </Text>
482
446
  </Box>
483
447
  </>
@@ -486,46 +450,37 @@ export function ProviderManager({ onClose }: ProviderManagerProps) {
486
450
  {/* Search Providers Tab */}
487
451
  {tab === 'search' && (
488
452
  <>
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
- })}
453
+ <Box marginTop={1}>
454
+ <Text bold>Search Providers</Text>
523
455
  </Box>
524
456
 
457
+ {searchProviders.map((item, idx) => {
458
+ const isSelected = idx === searchSelectedIndex;
459
+ return (
460
+ <Box key={item.provider.id}>
461
+ <Text color={isSelected ? colors.success : undefined}>
462
+ {isSelected ? icons.radio : ' '}{' '}
463
+ </Text>
464
+ <Text
465
+ bold={isSelected}
466
+ color={item.isSelected ? colors.success : undefined}
467
+ dimColor={!item.isAvailable}
468
+ >
469
+ {item.provider.name}
470
+ </Text>
471
+ {item.isSelected && <Text color={colors.success}> (current)</Text>}
472
+ {!item.isAvailable && (
473
+ <Text dimColor color={colors.error}>
474
+ {' '}
475
+ (not available)
476
+ </Text>
477
+ )}
478
+ </Box>
479
+ );
480
+ })}
481
+
525
482
  <Box marginTop={1}>
526
- <Text color={colors.textMuted}>
527
- ↑↓ navigate · Enter select · Tab/l LLM providers · Esc exit
528
- </Text>
483
+ <Text dimColor>Enter: Select | Tab: Switch | Q: Quit</Text>
529
484
  </Box>
530
485
  </>
531
486
  )}