@sylphx/flow 1.0.1 → 1.0.3

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 (229) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -9
  3. package/src/commands/codebase-command.ts +168 -0
  4. package/src/commands/flow-command.ts +1137 -0
  5. package/src/commands/flow-orchestrator.ts +296 -0
  6. package/src/commands/hook-command.ts +444 -0
  7. package/src/commands/init-command.ts +92 -0
  8. package/src/commands/init-core.ts +322 -0
  9. package/src/commands/knowledge-command.ts +161 -0
  10. package/src/commands/run-command.ts +120 -0
  11. package/src/components/benchmark-monitor.tsx +331 -0
  12. package/src/components/reindex-progress.tsx +261 -0
  13. package/src/composables/functional/index.ts +14 -0
  14. package/src/composables/functional/useEnvironment.ts +171 -0
  15. package/src/composables/functional/useFileSystem.ts +139 -0
  16. package/src/composables/index.ts +5 -0
  17. package/src/composables/useEnv.ts +13 -0
  18. package/src/composables/useRuntimeConfig.ts +27 -0
  19. package/src/composables/useTargetConfig.ts +45 -0
  20. package/src/config/ai-config.ts +376 -0
  21. package/src/config/constants.ts +35 -0
  22. package/src/config/index.ts +27 -0
  23. package/src/config/rules.ts +43 -0
  24. package/src/config/servers.ts +371 -0
  25. package/src/config/targets.ts +126 -0
  26. package/src/core/agent-loader.ts +141 -0
  27. package/src/core/agent-manager.ts +174 -0
  28. package/src/core/ai-sdk.ts +603 -0
  29. package/src/core/app-factory.ts +381 -0
  30. package/src/core/builtin-agents.ts +9 -0
  31. package/src/core/command-system.ts +550 -0
  32. package/src/core/config-system.ts +550 -0
  33. package/src/core/connection-pool.ts +390 -0
  34. package/src/core/di-container.ts +155 -0
  35. package/src/core/error-handling.ts +519 -0
  36. package/src/core/formatting/bytes.test.ts +115 -0
  37. package/src/core/formatting/bytes.ts +64 -0
  38. package/src/core/functional/async.ts +313 -0
  39. package/src/core/functional/either.ts +109 -0
  40. package/src/core/functional/error-handler.ts +135 -0
  41. package/src/core/functional/error-types.ts +311 -0
  42. package/src/core/functional/index.ts +19 -0
  43. package/src/core/functional/option.ts +142 -0
  44. package/src/core/functional/pipe.ts +189 -0
  45. package/src/core/functional/result.ts +204 -0
  46. package/src/core/functional/validation.ts +138 -0
  47. package/src/core/headless-display.ts +96 -0
  48. package/src/core/index.ts +6 -0
  49. package/src/core/installers/file-installer.ts +303 -0
  50. package/src/core/installers/mcp-installer.ts +213 -0
  51. package/src/core/interfaces/index.ts +22 -0
  52. package/src/core/interfaces/repository.interface.ts +91 -0
  53. package/src/core/interfaces/service.interface.ts +133 -0
  54. package/src/core/interfaces.ts +129 -0
  55. package/src/core/loop-controller.ts +200 -0
  56. package/src/core/result.ts +351 -0
  57. package/src/core/rule-loader.ts +147 -0
  58. package/src/core/rule-manager.ts +240 -0
  59. package/src/core/service-config.ts +252 -0
  60. package/src/core/session-service.ts +121 -0
  61. package/src/core/state-detector.ts +389 -0
  62. package/src/core/storage-factory.ts +115 -0
  63. package/src/core/stream-handler.ts +288 -0
  64. package/src/core/target-manager.ts +161 -0
  65. package/src/core/type-utils.ts +427 -0
  66. package/src/core/unified-storage.ts +456 -0
  67. package/src/core/upgrade-manager.ts +300 -0
  68. package/src/core/validation/limit.test.ts +155 -0
  69. package/src/core/validation/limit.ts +46 -0
  70. package/src/core/validation/query.test.ts +44 -0
  71. package/src/core/validation/query.ts +20 -0
  72. package/src/db/auto-migrate.ts +322 -0
  73. package/src/db/base-database-client.ts +144 -0
  74. package/src/db/cache-db.ts +218 -0
  75. package/src/db/cache-schema.ts +75 -0
  76. package/src/db/database.ts +70 -0
  77. package/src/db/index.ts +252 -0
  78. package/src/db/memory-db.ts +153 -0
  79. package/src/db/memory-schema.ts +29 -0
  80. package/src/db/schema.ts +289 -0
  81. package/src/db/session-repository.ts +733 -0
  82. package/src/domains/codebase/index.ts +5 -0
  83. package/src/domains/codebase/tools.ts +139 -0
  84. package/src/domains/index.ts +8 -0
  85. package/src/domains/knowledge/index.ts +10 -0
  86. package/src/domains/knowledge/resources.ts +537 -0
  87. package/src/domains/knowledge/tools.ts +174 -0
  88. package/src/domains/utilities/index.ts +6 -0
  89. package/src/domains/utilities/time/index.ts +5 -0
  90. package/src/domains/utilities/time/tools.ts +291 -0
  91. package/src/index.ts +211 -0
  92. package/src/services/agent-service.ts +273 -0
  93. package/src/services/claude-config-service.ts +252 -0
  94. package/src/services/config-service.ts +258 -0
  95. package/src/services/evaluation-service.ts +271 -0
  96. package/src/services/functional/evaluation-logic.ts +296 -0
  97. package/src/services/functional/file-processor.ts +273 -0
  98. package/src/services/functional/index.ts +12 -0
  99. package/src/services/index.ts +13 -0
  100. package/src/services/mcp-service.ts +432 -0
  101. package/src/services/memory.service.ts +476 -0
  102. package/src/services/search/base-indexer.ts +156 -0
  103. package/src/services/search/codebase-indexer-types.ts +38 -0
  104. package/src/services/search/codebase-indexer.ts +647 -0
  105. package/src/services/search/embeddings-provider.ts +455 -0
  106. package/src/services/search/embeddings.ts +316 -0
  107. package/src/services/search/functional-indexer.ts +323 -0
  108. package/src/services/search/index.ts +27 -0
  109. package/src/services/search/indexer.ts +380 -0
  110. package/src/services/search/knowledge-indexer.ts +422 -0
  111. package/src/services/search/semantic-search.ts +244 -0
  112. package/src/services/search/tfidf.ts +559 -0
  113. package/src/services/search/unified-search-service.ts +888 -0
  114. package/src/services/smart-config-service.ts +385 -0
  115. package/src/services/storage/cache-storage.ts +487 -0
  116. package/src/services/storage/drizzle-storage.ts +581 -0
  117. package/src/services/storage/index.ts +15 -0
  118. package/src/services/storage/lancedb-vector-storage.ts +494 -0
  119. package/src/services/storage/memory-storage.ts +268 -0
  120. package/src/services/storage/separated-storage.ts +467 -0
  121. package/src/services/storage/vector-storage.ts +13 -0
  122. package/src/shared/agents/index.ts +63 -0
  123. package/src/shared/files/index.ts +99 -0
  124. package/src/shared/index.ts +32 -0
  125. package/src/shared/logging/index.ts +24 -0
  126. package/src/shared/processing/index.ts +153 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/targets/claude-code.ts +574 -0
  129. package/src/targets/functional/claude-code-logic.ts +185 -0
  130. package/src/targets/functional/index.ts +6 -0
  131. package/src/targets/opencode.ts +529 -0
  132. package/src/types/agent.types.ts +32 -0
  133. package/src/types/api/batch.ts +108 -0
  134. package/src/types/api/errors.ts +118 -0
  135. package/src/types/api/index.ts +55 -0
  136. package/src/types/api/requests.ts +76 -0
  137. package/src/types/api/responses.ts +180 -0
  138. package/src/types/api/websockets.ts +85 -0
  139. package/src/types/api.types.ts +9 -0
  140. package/src/types/benchmark.ts +49 -0
  141. package/src/types/cli.types.ts +87 -0
  142. package/src/types/common.types.ts +35 -0
  143. package/src/types/database.types.ts +510 -0
  144. package/src/types/mcp-config.types.ts +448 -0
  145. package/src/types/mcp.types.ts +69 -0
  146. package/src/types/memory-types.ts +63 -0
  147. package/src/types/provider.types.ts +28 -0
  148. package/src/types/rule.types.ts +24 -0
  149. package/src/types/session.types.ts +214 -0
  150. package/src/types/target-config.types.ts +295 -0
  151. package/src/types/target.types.ts +140 -0
  152. package/src/types/todo.types.ts +25 -0
  153. package/src/types.ts +40 -0
  154. package/src/utils/advanced-tokenizer.ts +191 -0
  155. package/src/utils/agent-enhancer.ts +114 -0
  156. package/src/utils/ai-model-fetcher.ts +19 -0
  157. package/src/utils/async-file-operations.ts +516 -0
  158. package/src/utils/audio-player.ts +345 -0
  159. package/src/utils/cli-output.ts +266 -0
  160. package/src/utils/codebase-helpers.ts +211 -0
  161. package/src/utils/console-ui.ts +79 -0
  162. package/src/utils/database-errors.ts +140 -0
  163. package/src/utils/debug-logger.ts +49 -0
  164. package/src/utils/error-handler.ts +53 -0
  165. package/src/utils/file-operations.ts +310 -0
  166. package/src/utils/file-scanner.ts +259 -0
  167. package/src/utils/functional/array.ts +355 -0
  168. package/src/utils/functional/index.ts +15 -0
  169. package/src/utils/functional/object.ts +279 -0
  170. package/src/utils/functional/string.ts +281 -0
  171. package/src/utils/functional.ts +543 -0
  172. package/src/utils/help.ts +20 -0
  173. package/src/utils/immutable-cache.ts +106 -0
  174. package/src/utils/index.ts +78 -0
  175. package/src/utils/jsonc.ts +158 -0
  176. package/src/utils/logger.ts +396 -0
  177. package/src/utils/mcp-config.ts +249 -0
  178. package/src/utils/memory-tui.ts +414 -0
  179. package/src/utils/models-dev.ts +91 -0
  180. package/src/utils/notifications.ts +169 -0
  181. package/src/utils/object-utils.ts +51 -0
  182. package/src/utils/parallel-operations.ts +487 -0
  183. package/src/utils/paths.ts +143 -0
  184. package/src/utils/process-manager.ts +155 -0
  185. package/src/utils/prompts.ts +120 -0
  186. package/src/utils/search-tool-builder.ts +214 -0
  187. package/src/utils/secret-utils.ts +179 -0
  188. package/src/utils/security.ts +537 -0
  189. package/src/utils/session-manager.ts +168 -0
  190. package/src/utils/session-title.ts +87 -0
  191. package/src/utils/settings.ts +182 -0
  192. package/src/utils/simplified-errors.ts +410 -0
  193. package/src/utils/sync-utils.ts +159 -0
  194. package/src/utils/target-config.ts +570 -0
  195. package/src/utils/target-utils.ts +394 -0
  196. package/src/utils/template-engine.ts +94 -0
  197. package/src/utils/test-audio.ts +71 -0
  198. package/src/utils/todo-context.ts +46 -0
  199. package/src/utils/token-counter.ts +288 -0
  200. package/dist/index.d.ts +0 -10
  201. package/dist/index.js +0 -59554
  202. package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
  203. package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
  204. package/dist/shared/chunk-25dwp0dp.js +0 -89
  205. package/dist/shared/chunk-3pjb6063.js +0 -208
  206. package/dist/shared/chunk-4d6ydpw7.js +0 -2854
  207. package/dist/shared/chunk-4wjcadjk.js +0 -225
  208. package/dist/shared/chunk-5j4w74t6.js +0 -30
  209. package/dist/shared/chunk-5j8m3dh3.js +0 -58
  210. package/dist/shared/chunk-5thh3qem.js +0 -91
  211. package/dist/shared/chunk-6g9xy73m.js +0 -252
  212. package/dist/shared/chunk-7eq34c42.js +0 -23
  213. package/dist/shared/chunk-c2gwgx3r.js +0 -115
  214. package/dist/shared/chunk-cjd3mk4c.js +0 -1320
  215. package/dist/shared/chunk-g5cv6703.js +0 -368
  216. package/dist/shared/chunk-hpkhykhq.js +0 -574
  217. package/dist/shared/chunk-m2322pdk.js +0 -122
  218. package/dist/shared/chunk-nd5fdvaq.js +0 -26
  219. package/dist/shared/chunk-pgd3m6zf.js +0 -108
  220. package/dist/shared/chunk-qk8n91hw.js +0 -494
  221. package/dist/shared/chunk-rkkn8szp.js +0 -16855
  222. package/dist/shared/chunk-t16rfxh0.js +0 -61
  223. package/dist/shared/chunk-t4fbfa5v.js +0 -19
  224. package/dist/shared/chunk-t77h86w6.js +0 -276
  225. package/dist/shared/chunk-v0ez4aef.js +0 -71
  226. package/dist/shared/chunk-v29j2r3s.js +0 -32051
  227. package/dist/shared/chunk-vfbc6ew5.js +0 -765
  228. package/dist/shared/chunk-vmeqwm1c.js +0 -204
  229. package/dist/shared/chunk-x66eh37x.js +0 -137
@@ -0,0 +1,570 @@
1
+ import { createInterface } from 'node:readline';
2
+ import {
3
+ getAllEnvVars,
4
+ getOptionalEnvVars,
5
+ getRequiredEnvVars,
6
+ getSecretEnvVars,
7
+ MCP_SERVER_REGISTRY,
8
+ type MCPServerID,
9
+ } from '../config/servers.js';
10
+ import { targetManager } from '../core/target-manager.js';
11
+ import { resolveConfig } from '../services/mcp-service.js';
12
+ import { deleteNestedProperty, getNestedProperty, setNestedProperty } from './object-utils.js';
13
+ import { secretUtils } from './secret-utils.js';
14
+
15
+ /**
16
+ * Target-specific MCP configuration utilities
17
+ */
18
+
19
+ /**
20
+ * Add MCP servers to a target's configuration
21
+ */
22
+ export async function addMCPServersToTarget(
23
+ cwd: string,
24
+ targetId: string,
25
+ serverTypes: MCPServerID[]
26
+ ): Promise<void> {
27
+ const targetOption = targetManager.getTarget(targetId);
28
+
29
+ if (targetOption._tag === 'None') {
30
+ throw new Error(`Target not found: ${targetId}`);
31
+ }
32
+
33
+ const target = targetOption.value;
34
+
35
+ if (!target.setupMCP) {
36
+ throw new Error(`Target ${targetId} does not support MCP servers`);
37
+ }
38
+
39
+ // Read current configuration
40
+ const config = await target.readConfig(cwd);
41
+
42
+ // Initialize MCP section if it doesn't exist
43
+ const mcpConfigPath = target.config.mcpConfigPath;
44
+ const mcpSection = getNestedProperty(config, mcpConfigPath) || {};
45
+ setNestedProperty(config, mcpConfigPath, mcpSection);
46
+
47
+ let addedCount = 0;
48
+
49
+ // Add each requested server
50
+ for (const serverType of serverTypes) {
51
+ const server = MCP_SERVER_REGISTRY[serverType];
52
+ if (!server) {
53
+ console.warn(`Warning: Unknown MCP server type: ${serverType}`);
54
+ continue;
55
+ }
56
+
57
+ if (mcpSection[server.name]) {
58
+ console.log(`ā„¹ļø MCP server already exists: ${server.name}`);
59
+ } else {
60
+ const _transformedConfig = target.transformMCPConfig(server.config, server.id);
61
+
62
+ // Apply dynamic command and args generation (only for CLI servers)
63
+ let resolvedCommand: unknown;
64
+ let resolvedArgs: unknown;
65
+
66
+ if (server.config.command || server.config.args) {
67
+ resolvedCommand = server.config.command
68
+ ? await resolveConfig(server.config.command)
69
+ : undefined;
70
+ resolvedArgs = server.config.args ? await resolveConfig(server.config.args) : [];
71
+ }
72
+
73
+ // Update the config with resolved values - target will handle format
74
+ const updatedConfig = {
75
+ ...server.config,
76
+ command: resolvedCommand,
77
+ args: resolvedArgs,
78
+ };
79
+
80
+ // Let target handle the transformation
81
+ const finalConfig = target.transformMCPConfig(updatedConfig, server.id);
82
+
83
+ mcpSection[server.name] = finalConfig;
84
+ console.log(`šŸ“¦ Added MCP server: ${server.name} (${server.description})`);
85
+ addedCount++;
86
+ }
87
+ }
88
+
89
+ // Write the updated configuration
90
+ await target.writeConfig(cwd, config);
91
+ console.log(`āœ“ Updated ${target.config.configFile} with ${addedCount} new MCP server(s)`);
92
+ }
93
+
94
+ /**
95
+ * Remove MCP servers from a target's configuration
96
+ */
97
+ export async function removeMCPServersFromTarget(
98
+ cwd: string,
99
+ targetId: string,
100
+ serverTypes: MCPServerID[]
101
+ ): Promise<void> {
102
+ const targetOption = targetManager.getTarget(targetId);
103
+
104
+ if (targetOption._tag === 'None') {
105
+ throw new Error(`Target not found: ${targetId}`);
106
+ }
107
+
108
+ const target = targetOption.value;
109
+
110
+ // Read current configuration
111
+ const config = await target.readConfig(cwd);
112
+
113
+ // Get MCP section
114
+ const mcpConfigPath = target.config.mcpConfigPath;
115
+ const mcpSection = getNestedProperty(config, mcpConfigPath);
116
+
117
+ if (!mcpSection) {
118
+ console.log('ā„¹ļø No MCP servers configured');
119
+ return;
120
+ }
121
+
122
+ let removedCount = 0;
123
+
124
+ // Remove each requested server
125
+ for (const serverType of serverTypes) {
126
+ const server = MCP_SERVER_REGISTRY[serverType];
127
+ if (!server) {
128
+ console.warn(`Warning: Unknown MCP server type: ${serverType}`);
129
+ continue;
130
+ }
131
+
132
+ if (mcpSection[server.name]) {
133
+ delete mcpSection[server.name];
134
+ console.log(`šŸ—‘ļø Removed MCP server: ${server.name}`);
135
+ removedCount++;
136
+ } else {
137
+ console.log(`ā„¹ļø MCP server not found: ${server.name}`);
138
+ }
139
+ }
140
+
141
+ // Remove MCP section if it's empty
142
+ if (Object.keys(mcpSection).length === 0) {
143
+ deleteNestedProperty(config, mcpConfigPath);
144
+ } else {
145
+ setNestedProperty(config, mcpConfigPath, mcpSection);
146
+ }
147
+
148
+ // Write the updated configuration
149
+ await target.writeConfig(cwd, config);
150
+ console.log(`āœ“ Updated ${target.config.configFile} (removed ${removedCount} MCP server(s))`);
151
+ }
152
+
153
+ /**
154
+ * List currently configured MCP servers for a target
155
+ */
156
+ export async function listMCPServersForTarget(cwd: string, targetId: string): Promise<void> {
157
+ const targetOption = targetManager.getTarget(targetId);
158
+
159
+ if (targetOption._tag === 'None') {
160
+ throw new Error(`Target not found: ${targetId}`);
161
+ }
162
+
163
+ const target = targetOption.value;
164
+
165
+ // Read current configuration
166
+ const config = await target.readConfig(cwd);
167
+
168
+ // Get MCP section
169
+ const mcpConfigPath = target.config.mcpConfigPath;
170
+ const mcpSection = getNestedProperty(config, mcpConfigPath);
171
+
172
+ if (!mcpSection || Object.keys(mcpSection).length === 0) {
173
+ console.log('ā„¹ļø No MCP servers configured');
174
+ return;
175
+ }
176
+
177
+ console.log(`šŸ“‹ Currently configured MCP servers for ${target.name}:`);
178
+ console.log('');
179
+
180
+ for (const [name, serverConfig] of Object.entries(mcpSection)) {
181
+ let configInfo = '';
182
+ if (serverConfig && typeof serverConfig === 'object' && 'type' in serverConfig) {
183
+ if (serverConfig.type === 'local') {
184
+ configInfo = (serverConfig as any).command?.join(' ') || 'Unknown command';
185
+ } else if (serverConfig.type === 'remote') {
186
+ configInfo = `HTTP: ${(serverConfig as any).url}`;
187
+ }
188
+ }
189
+
190
+ console.log(` • ${name}: ${configInfo}`);
191
+
192
+ // Find the server type for additional info
193
+ const serverInfo = Object.values(MCP_SERVER_REGISTRY).find((s) => s.name === name);
194
+ if (serverInfo) {
195
+ console.log(` ${serverInfo.description}`);
196
+ }
197
+ console.log('');
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Configure API keys for a specific MCP server in a target
203
+ * @returns Promise<boolean> - true if API keys were provided, false otherwise
204
+ */
205
+ export async function configureMCPServerForTarget(
206
+ cwd: string,
207
+ targetId: string,
208
+ serverType: MCPServerID
209
+ ): Promise<boolean> {
210
+ const targetOption = targetManager.getTarget(targetId);
211
+
212
+ if (targetOption._tag === 'None') {
213
+ throw new Error(`Target not found: ${targetId}`);
214
+ }
215
+
216
+ const target = targetOption.value;
217
+
218
+ const server = MCP_SERVER_REGISTRY[serverType];
219
+ if (!server) {
220
+ console.error(`āœ— Unknown MCP server: ${serverType}`);
221
+ return false;
222
+ }
223
+
224
+ const requiredEnvVars = getRequiredEnvVars(serverType);
225
+ const optionalEnvVars = getOptionalEnvVars(serverType);
226
+
227
+ if (requiredEnvVars.length === 0 && optionalEnvVars.length === 0) {
228
+ console.log(`ā„¹ļø ${server.name} does not require any API keys`);
229
+ return true; // No keys needed, so consider it successful
230
+ }
231
+
232
+ console.log(`šŸ”§ Configuring ${server.description} for ${target.name}...`);
233
+
234
+ // Check if server already exists
235
+ const config = await target.readConfig(cwd);
236
+ const mcpConfigPath = target.config.mcpConfigPath;
237
+ const mcpSection = getNestedProperty(config, mcpConfigPath);
238
+ const isServerInstalled = !!mcpSection?.[server.name];
239
+
240
+ // Check if existing server has valid keys (only required keys matter for validity)
241
+ let hasExistingValidKeys = false;
242
+ if (isServerInstalled && requiredEnvVars.length) {
243
+ const serverConfig = mcpSection[server.name];
244
+ hasExistingValidKeys = requiredEnvVars.every((envVar) => {
245
+ const envValue = serverConfig.env?.[envVar] || serverConfig.environment?.[envVar];
246
+ return envValue && envValue.trim() !== '';
247
+ });
248
+ } else if (isServerInstalled && requiredEnvVars.length === 0) {
249
+ // Server has no required keys, so it's always valid
250
+ hasExistingValidKeys = true;
251
+ }
252
+
253
+ // First, check environment variables for existing API keys
254
+ const envApiKeys: Record<string, string> = {};
255
+ const allEnvVars = getAllEnvVars(serverType);
256
+
257
+ for (const envVar of allEnvVars) {
258
+ const envValue = process.env[envVar];
259
+ if (envValue && envValue.trim() !== '') {
260
+ envApiKeys[envVar] = envValue.trim();
261
+ }
262
+ }
263
+
264
+ // Also extract existing keys from MCP config
265
+ const existingConfigKeys: Record<string, string> = {};
266
+ if (isServerInstalled && mcpSection[server.name]) {
267
+ const serverConfig = mcpSection[server.name];
268
+ for (const envVar of allEnvVars) {
269
+ const configValue = serverConfig.env?.[envVar] || serverConfig.environment?.[envVar];
270
+ if (configValue && configValue.trim() !== '') {
271
+ existingConfigKeys[envVar] = configValue.trim();
272
+ }
273
+ }
274
+ }
275
+
276
+ // Merge existing keys: env vars take precedence over config keys
277
+ const existingKeys = { ...existingConfigKeys, ...envApiKeys };
278
+
279
+ // If we have all required keys from environment, use them
280
+ const _hasAllRequiredEnvKeys = requiredEnvVars.every(
281
+ (envVar) => envApiKeys[envVar] && envApiKeys[envVar].trim() !== ''
282
+ );
283
+
284
+ // mcp config is always for configuring - show UI with existing values
285
+ console.log(
286
+ 'āœ“ Found existing API keys, you can update them or press Enter to keep current values'
287
+ );
288
+ const apiKeys = await promptForAPIKeys([serverType], existingKeys);
289
+
290
+ // Check if all required environment variables are provided
291
+ const hasAllRequiredKeys = requiredEnvVars.every(
292
+ (envVar) => apiKeys[envVar] && apiKeys[envVar].trim() !== ''
293
+ );
294
+
295
+ // Check if user made any changes to existing keys
296
+ const userMadeChanges = allEnvVars.some((envVar) => {
297
+ const newValue = apiKeys[envVar];
298
+ const existingValue = existingKeys[envVar];
299
+ return newValue !== existingValue;
300
+ });
301
+
302
+ // If server is installed with valid keys and user didn't change anything, keep it
303
+ if (isServerInstalled && hasExistingValidKeys && !userMadeChanges) {
304
+ console.log(`āœ“ Keeping ${server.name} (existing API keys are valid)`);
305
+ return true;
306
+ }
307
+
308
+ // For servers with required keys, validate them
309
+ if (requiredEnvVars.length > 0 && !hasAllRequiredKeys) {
310
+ // User didn't provide all required keys
311
+ if (isServerInstalled && !hasExistingValidKeys) {
312
+ // Case 1: Already installed + no keys + user doesn't provide → DELETE
313
+ console.log(`šŸ—‘ļø Removing ${server.name} (no API keys provided)`);
314
+ delete mcpSection[server.name];
315
+
316
+ // Remove MCP section if it's empty
317
+ if (Object.keys(mcpSection).length === 0) {
318
+ deleteNestedProperty(config, mcpConfigPath);
319
+ } else {
320
+ setNestedProperty(config, mcpConfigPath, mcpSection);
321
+ }
322
+
323
+ await target.writeConfig(cwd, config);
324
+ return false;
325
+ }
326
+ if (isServerInstalled && hasExistingValidKeys) {
327
+ // Case 2: Already installed + has keys + user doesn't provide → KEEP
328
+ console.log(`āœ“ Keeping ${server.name} (existing API keys are valid)`);
329
+ return true;
330
+ }
331
+ // Case 4: Not installed + required keys + user doesn't provide → SKIP
332
+ console.log(`āš ļø Skipping ${server.name} (required API keys not provided)`);
333
+ return false;
334
+ }
335
+
336
+ // For servers with only optional envVars (like sylphx-flow), always proceed to save
337
+ // if the user provided any values or if the server is being configured
338
+ if (requiredEnvVars.length === 0 && optionalEnvVars.length > 0) {
339
+ const hasUserInput = Object.keys(apiKeys).some((key) => {
340
+ const envValue = apiKeys[key];
341
+ const existingValue = envApiKeys[key];
342
+ return envValue && envValue !== existingValue;
343
+ });
344
+
345
+ if (hasUserInput || !isServerInstalled) {
346
+ console.log(`šŸ”§ Updating ${server.name} configuration...`);
347
+ // Proceed to save/update the configuration
348
+ } else {
349
+ console.log(`āœ“ No changes needed for ${server.name}`);
350
+ return true;
351
+ }
352
+ }
353
+
354
+ // Get MCP section for update (ensure it exists)
355
+ const mcpSectionForUpdate = mcpSection || {};
356
+
357
+ // Separate secret and non-secret API keys based on server configuration
358
+ const secretEnvVars = getSecretEnvVars(server.id);
359
+ const secretApiKeys: Record<string, string> = {};
360
+ const nonSecretApiKeys: Record<string, string> = {};
361
+
362
+ for (const [key, value] of Object.entries(apiKeys)) {
363
+ if (secretEnvVars.includes(key)) {
364
+ secretApiKeys[key] = value;
365
+ } else {
366
+ nonSecretApiKeys[key] = value;
367
+ }
368
+ }
369
+
370
+ // Convert secret API keys to file references if target supports it
371
+ let processedSecretApiKeys = secretApiKeys;
372
+ const targetConfigOption = targetManager.getTarget(targetId);
373
+
374
+ if (
375
+ targetConfigOption._tag === 'Some' &&
376
+ targetConfigOption.value.setupMCP &&
377
+ targetConfigOption.value.config.installation.useSecretFiles !== false &&
378
+ Object.keys(secretApiKeys).length > 0
379
+ ) {
380
+ processedSecretApiKeys = await secretUtils.convertSecretsToFileReferences(cwd, secretApiKeys);
381
+ await secretUtils.addToGitignore(cwd);
382
+ }
383
+
384
+ // Combine processed secret keys with non-secret keys
385
+ const processedApiKeys = { ...nonSecretApiKeys, ...processedSecretApiKeys };
386
+
387
+ // Update server config with API keys (only for local servers)
388
+ const currentConfig = mcpSectionForUpdate[server.name];
389
+ if (currentConfig && currentConfig.type === 'local') {
390
+ // Update existing local config
391
+ mcpSectionForUpdate[server.name] = {
392
+ ...currentConfig,
393
+ environment: {
394
+ ...(currentConfig.environment || {}),
395
+ ...processedApiKeys,
396
+ },
397
+ };
398
+ } else {
399
+ // Create new config with API keys
400
+ const baseConfig = server.config;
401
+ if (baseConfig.type === 'local') {
402
+ const transformedConfig = target.transformMCPConfig(baseConfig, server.id);
403
+ mcpSectionForUpdate[server.name] = {
404
+ ...transformedConfig,
405
+ environment: {
406
+ ...(baseConfig.environment || {}),
407
+ ...processedApiKeys,
408
+ },
409
+ };
410
+ } else {
411
+ // HTTP server - just add the base config
412
+ const transformedConfig = target.transformMCPConfig(baseConfig, server.id);
413
+ mcpSectionForUpdate[server.name] = transformedConfig;
414
+ }
415
+ }
416
+
417
+ // Update config with new MCP section
418
+ setNestedProperty(config, mcpConfigPath, mcpSectionForUpdate);
419
+
420
+ // Write updated configuration
421
+ await target.writeConfig(cwd, config);
422
+ console.log(`āœ“ Updated ${server.name} with API keys for ${target.name}`);
423
+ return true;
424
+ }
425
+
426
+ /**
427
+ * Get target-specific help text
428
+ */
429
+ export function getTargetHelpText(targetId: string): string {
430
+ const targetOption = targetManager.getTarget(targetId);
431
+ if (targetOption._tag === 'Some') {
432
+ return targetOption.value.getHelpText();
433
+ }
434
+ return '';
435
+ }
436
+
437
+ /**
438
+ * Get all available targets help text
439
+ */
440
+ export function getAllTargetsHelpText(): string {
441
+ const targets = targetManager.getImplementedTargets();
442
+ return targets.map((target) => target.getHelpText()).join('\n\n');
443
+ }
444
+
445
+ /**
446
+ * Validate target and return target ID
447
+ */
448
+ export function validateTarget(targetId: string): string {
449
+ const targetOption = targetManager.getTarget(targetId);
450
+
451
+ if (targetOption._tag === 'None') {
452
+ throw new Error(
453
+ `Unknown target: ${targetId}. Available targets: ${targetManager.getImplementedTargetIDs().join(', ')}`
454
+ );
455
+ }
456
+
457
+ const target = targetOption.value;
458
+ if (!target.isImplemented) {
459
+ throw new Error(
460
+ `Target '${targetId}' is not implemented. Available targets: ${targetManager.getImplementedTargetIDs().join(', ')}`
461
+ );
462
+ }
463
+ return targetId;
464
+ }
465
+
466
+ /**
467
+ * Check if target supports MCP servers
468
+ */
469
+ export function targetSupportsMCPServers(targetId: string): boolean {
470
+ const targetOption = targetManager.getTarget(targetId);
471
+ if (targetOption._tag === 'None') {
472
+ return false;
473
+ }
474
+ return !!targetOption.value.setupMCP;
475
+ }
476
+
477
+ /**
478
+ * Get targets that support MCP servers
479
+ */
480
+ export function getTargetsWithMCPSupport(): string[] {
481
+ const targets = targetManager.getTargetsWithMCPSupport();
482
+ return targets.map((target) => target.id);
483
+ }
484
+
485
+ /**
486
+ * Prompt user for API keys interactively
487
+ */
488
+ async function promptForAPIKeys(
489
+ serverTypes: MCPServerID[],
490
+ existingKeys: Record<string, string> = {}
491
+ ): Promise<Record<string, string>> {
492
+ const rl = createInterface({
493
+ input: process.stdin,
494
+ output: process.stdout,
495
+ });
496
+
497
+ const apiKeys: Record<string, string> = { ...existingKeys };
498
+
499
+ for (const serverType of serverTypes) {
500
+ const server = MCP_SERVER_REGISTRY[serverType];
501
+ const allEnvVars = getAllEnvVars(serverType);
502
+
503
+ if (!allEnvVars.length) {
504
+ continue;
505
+ }
506
+
507
+ console.log(`\nšŸ”‘ Configuring API keys for ${server.description}:`);
508
+
509
+ for (const envVar of allEnvVars) {
510
+ const envConfig = server.envVars?.[envVar];
511
+ if (!envConfig) {
512
+ continue;
513
+ }
514
+
515
+ const isRequired = envConfig.required;
516
+ const hasDefault = envConfig.default !== undefined;
517
+ const existingValue = existingKeys[envVar];
518
+ const displayExisting = existingValue
519
+ ? ` (current: ${existingValue.substring(0, 9)}${existingValue.length > 9 ? '...' : ''})`
520
+ : '';
521
+
522
+ let promptText: string;
523
+ if (isRequired) {
524
+ if (hasDefault) {
525
+ promptText = `Enter ${envVar} (${envConfig.description}) (required, default: ${envConfig.default}${displayExisting}): `;
526
+ } else {
527
+ promptText = `Enter ${envVar} (${envConfig.description}) (required${displayExisting}): `;
528
+ }
529
+ } else if (hasDefault) {
530
+ promptText = `Enter ${envVar} (${envConfig.description}) (optional, default: ${envConfig.default}${displayExisting}, press Enter to keep current): `;
531
+ } else {
532
+ promptText = `Enter ${envVar} (${envConfig.description}) (optional${displayExisting}, press Enter to keep current): `;
533
+ }
534
+
535
+ const answer = await new Promise<string>((resolve) => {
536
+ rl.question(promptText, (input) => {
537
+ resolve(input.trim());
538
+ });
539
+ });
540
+
541
+ let finalValue: string | undefined;
542
+ let actionText: string;
543
+
544
+ if (answer) {
545
+ finalValue = answer;
546
+ actionText = `Updated ${envVar}`;
547
+ } else if (existingValue) {
548
+ finalValue = existingValue;
549
+ actionText = `Kept existing ${envVar}`;
550
+ } else if (hasDefault) {
551
+ finalValue = envConfig.default;
552
+ actionText = `Using default for ${envVar}`;
553
+ } else if (isRequired) {
554
+ console.log(`āš ļø Skipped required ${envVar} - server may not function properly`);
555
+ continue;
556
+ } else {
557
+ console.log(`ā„¹ļø Skipped optional ${envVar}`);
558
+ continue;
559
+ }
560
+
561
+ if (finalValue) {
562
+ apiKeys[envVar] = finalValue;
563
+ console.log(`āœ“ ${actionText}`);
564
+ }
565
+ }
566
+ }
567
+
568
+ rl.close();
569
+ return apiKeys;
570
+ }