@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,371 @@
1
+ import type { MCPServerConfigUnion } from '../types.js';
2
+ import { envSecurity, securitySchemas } from '../utils/security.js';
3
+
4
+ /**
5
+ * Central MCP server registry for Sylphx Flow
6
+ * This is the single source of truth for all available MCP servers
7
+ */
8
+ export interface EnvVarConfig {
9
+ /** Description of what this environment variable does */
10
+ description: string;
11
+ /** Whether this environment variable is required or optional */
12
+ required: boolean;
13
+ /** Default value (if any) */
14
+ default?: string;
15
+ /** Whether this environment variable contains sensitive data and should be stored as secret */
16
+ secret?: boolean;
17
+ /** Async function to fetch choices for list inputs */
18
+ fetchChoices?: () => Promise<string[]>;
19
+ /** Environment variables this field depends on */
20
+ dependsOn?: string[];
21
+ }
22
+
23
+ export interface MCPServerDefinition {
24
+ /** Internal identifier used in CLI commands */
25
+ id: string;
26
+ /** Display name for the server */
27
+ name: string;
28
+ /** Human-readable description */
29
+ description: string;
30
+ /** MCP server configuration */
31
+ config: MCPServerConfigUnion;
32
+ /** Environment variables configuration */
33
+ envVars?: Record<string, EnvVarConfig>;
34
+ /** Server category for grouping */
35
+ category: 'core' | 'external' | 'ai';
36
+ /** Whether this server is included by default in init */
37
+ defaultInInit?: boolean;
38
+ /** Whether this server is required and cannot be uninstalled */
39
+ required?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Central registry of all available MCP servers
44
+ * This replaces all hardcoded server lists throughout the codebase
45
+ */
46
+ export const MCP_SERVER_REGISTRY: Record<string, MCPServerDefinition> = {
47
+ 'sylphx-flow': {
48
+ id: 'sylphx-flow',
49
+ name: 'sylphx-flow',
50
+ description: 'Sylphx Flow MCP server for agent coordination and memory management',
51
+ config: {
52
+ type: 'stdio' as const,
53
+ command: 'sylphx-flow',
54
+ args: ['mcp', 'start'],
55
+ env: {
56
+ OPENAI_API_KEY: '',
57
+ OPENAI_BASE_URL: 'https://api.openai.com/v1',
58
+ EMBEDDING_MODEL: 'text-embedding-3-small',
59
+ },
60
+ },
61
+ envVars: {
62
+ OPENAI_API_KEY: {
63
+ description: 'OpenAI API key for vector search embeddings',
64
+ required: false,
65
+ secret: true,
66
+ },
67
+ OPENAI_BASE_URL: {
68
+ description: 'Base URL for OpenAI-compatible embedding API',
69
+ required: false,
70
+ default: 'https://api.openai.com/v1',
71
+ },
72
+ EMBEDDING_MODEL: {
73
+ description: 'Embedding model to use for vector search',
74
+ required: false,
75
+ default: 'text-embedding-3-small',
76
+ dependsOn: ['OPENAI_API_KEY', 'OPENAI_BASE_URL'],
77
+ fetchChoices: async () => {
78
+ // Validate environment variables
79
+ const validatedBaseUrl = envSecurity.getEnvVar(
80
+ 'OPENAI_BASE_URL',
81
+ 'https://api.openai.com/v1'
82
+ );
83
+ const validatedApiKey = envSecurity.getEnvVar('OPENAI_API_KEY');
84
+
85
+ if (!validatedApiKey) {
86
+ throw new Error('OPENAI_API_KEY is required to fetch embedding models');
87
+ }
88
+
89
+ // Additional validation for API key format
90
+ try {
91
+ securitySchemas.apiKey.parse(validatedApiKey);
92
+ } catch (_error) {
93
+ throw new Error('Invalid OPENAI_API_KEY format');
94
+ }
95
+
96
+ const response = await fetch(`${validatedBaseUrl}/models`, {
97
+ headers: { Authorization: `Bearer ${validatedApiKey}` },
98
+ timeout: 10000, // Add timeout to prevent hanging
99
+ });
100
+
101
+ if (!response.ok) {
102
+ throw new Error(`Failed to fetch models: ${response.statusText}`);
103
+ }
104
+
105
+ const data = await response.json();
106
+ const embeddingModels = data.data
107
+ .filter((m: { id: string }) => m.id.includes('embedding'))
108
+ .map((m: { id: string }) => m.id)
109
+ .sort();
110
+
111
+ if (embeddingModels.length === 0) {
112
+ throw new Error('No embedding models found');
113
+ }
114
+
115
+ return embeddingModels;
116
+ },
117
+ },
118
+ },
119
+ category: 'core',
120
+ defaultInInit: true,
121
+ required: true,
122
+ },
123
+
124
+ 'gpt-image': {
125
+ id: 'gpt-image',
126
+ name: 'gpt-image-1-mcp',
127
+ description: 'GPT Image generation MCP server',
128
+ config: {
129
+ type: 'stdio' as const,
130
+ command: 'npx',
131
+ args: ['@napolab/gpt-image-1-mcp'],
132
+ env: { OPENAI_API_KEY: '' },
133
+ },
134
+ envVars: {
135
+ OPENAI_API_KEY: {
136
+ description: 'OpenAI API key for image generation',
137
+ required: true,
138
+ secret: true,
139
+ },
140
+ },
141
+ category: 'ai',
142
+ defaultInInit: false,
143
+ },
144
+
145
+ perplexity: {
146
+ id: 'perplexity',
147
+ name: 'perplexity-ask',
148
+ description: 'Perplexity Ask MCP server for search and queries',
149
+ config: {
150
+ type: 'stdio' as const,
151
+ command: 'npx',
152
+ args: ['-y', 'server-perplexity-ask'],
153
+ env: { PERPLEXITY_API_KEY: '' },
154
+ },
155
+ envVars: {
156
+ PERPLEXITY_API_KEY: {
157
+ description: 'Perplexity API key for search and queries',
158
+ required: true,
159
+ secret: true,
160
+ },
161
+ },
162
+ category: 'ai',
163
+ defaultInInit: false,
164
+ },
165
+
166
+ context7: {
167
+ id: 'context7',
168
+ name: 'context7',
169
+ description: 'Context7 HTTP MCP server for documentation retrieval',
170
+ config: {
171
+ type: 'http' as const,
172
+ url: 'https://mcp.context7.com/mcp',
173
+ },
174
+ envVars: {
175
+ CONTEXT7_API_KEY: {
176
+ description: 'Context7 API key for enhanced documentation access',
177
+ required: false,
178
+ secret: true,
179
+ },
180
+ },
181
+ category: 'external',
182
+ defaultInInit: true,
183
+ },
184
+
185
+ 'gemini-search': {
186
+ id: 'gemini-search',
187
+ name: 'gemini-google-search',
188
+ description: 'Gemini Google Search MCP server',
189
+ config: {
190
+ type: 'stdio' as const,
191
+ command: 'npx',
192
+ args: ['-y', 'mcp-gemini-google-search'],
193
+ env: { GEMINI_API_KEY: '', GEMINI_MODEL: 'gemini-2.5-flash' },
194
+ },
195
+ envVars: {
196
+ GEMINI_API_KEY: {
197
+ description: 'Google Gemini API key for search functionality',
198
+ required: true,
199
+ secret: true,
200
+ },
201
+ GEMINI_MODEL: {
202
+ description: 'Gemini model to use for search',
203
+ required: false,
204
+ default: 'gemini-2.5-flash',
205
+ },
206
+ },
207
+ category: 'ai',
208
+ defaultInInit: false,
209
+ },
210
+
211
+ grep: {
212
+ id: 'grep',
213
+ name: 'grep',
214
+ description: 'GitHub grep MCP server for searching GitHub repositories',
215
+ config: {
216
+ type: 'http' as const,
217
+ url: 'https://mcp.grep.app',
218
+ },
219
+ category: 'external',
220
+ defaultInInit: true,
221
+ },
222
+ };
223
+
224
+ /**
225
+ * Type for valid MCP server IDs
226
+ */
227
+ export type MCPServerID = keyof typeof MCP_SERVER_REGISTRY;
228
+
229
+ /**
230
+ * Get all available server IDs
231
+ */
232
+ export function getAllServerIDs(): MCPServerID[] {
233
+ return Object.keys(MCP_SERVER_REGISTRY) as MCPServerID[];
234
+ }
235
+
236
+ /**
237
+ * Get servers by category
238
+ */
239
+ export function getServersByCategory(category: MCPServerDefinition['category']): MCPServerID[] {
240
+ return Object.entries(MCP_SERVER_REGISTRY)
241
+ .filter(([, server]) => server.category === category)
242
+ .map(([id]) => id as MCPServerID);
243
+ }
244
+
245
+ /**
246
+ * Get servers that are included by default in init
247
+ */
248
+ export function getDefaultServers(): MCPServerID[] {
249
+ return Object.entries(MCP_SERVER_REGISTRY)
250
+ .filter(([, server]) => server.defaultInInit)
251
+ .map(([id]) => id as MCPServerID);
252
+ }
253
+
254
+ /**
255
+ * Get servers that require API keys
256
+ */
257
+ export function getServersRequiringAPIKeys(): MCPServerID[] {
258
+ return Object.entries(MCP_SERVER_REGISTRY)
259
+ .filter(
260
+ ([, server]) =>
261
+ server.envVars && Object.values(server.envVars).some((envVar) => envVar.required)
262
+ )
263
+ .map(([id]) => id as MCPServerID);
264
+ }
265
+
266
+ /**
267
+ * Get servers that have optional API keys
268
+ */
269
+ export function getServersWithOptionalAPIKeys(): MCPServerID[] {
270
+ return Object.entries(MCP_SERVER_REGISTRY)
271
+ .filter(
272
+ ([, server]) =>
273
+ server.envVars && Object.values(server.envVars).some((envVar) => !envVar.required)
274
+ )
275
+ .map(([id]) => id as MCPServerID);
276
+ }
277
+
278
+ /**
279
+ * Get all servers that have any API keys (required or optional)
280
+ */
281
+ export function getServersWithAnyAPIKeys(): MCPServerID[] {
282
+ return Object.entries(MCP_SERVER_REGISTRY)
283
+ .filter(([, server]) => server.envVars && Object.keys(server.envVars).length > 0)
284
+ .map(([id]) => id as MCPServerID);
285
+ }
286
+
287
+ /**
288
+ * Get required environment variables for a server
289
+ */
290
+ export function getRequiredEnvVars(serverId: MCPServerID): string[] {
291
+ const server = MCP_SERVER_REGISTRY[serverId];
292
+ if (!server?.envVars) {
293
+ return [];
294
+ }
295
+
296
+ return Object.entries(server.envVars)
297
+ .filter(([, config]) => config.required)
298
+ .map(([name]) => name);
299
+ }
300
+
301
+ /**
302
+ * Get optional environment variables for a server
303
+ */
304
+ export function getOptionalEnvVars(serverId: MCPServerID): string[] {
305
+ const server = MCP_SERVER_REGISTRY[serverId];
306
+ if (!server?.envVars) {
307
+ return [];
308
+ }
309
+
310
+ return Object.entries(server.envVars)
311
+ .filter(([, config]) => !config.required)
312
+ .map(([name]) => name);
313
+ }
314
+
315
+ /**
316
+ * Get all environment variables for a server
317
+ */
318
+ export function getAllEnvVars(serverId: MCPServerID): string[] {
319
+ const server = MCP_SERVER_REGISTRY[serverId];
320
+ if (!server?.envVars) {
321
+ return [];
322
+ }
323
+
324
+ return Object.keys(server.envVars);
325
+ }
326
+
327
+ /**
328
+ * Get secret environment variables for a server
329
+ */
330
+ export function getSecretEnvVars(serverId: MCPServerID): string[] {
331
+ const server = MCP_SERVER_REGISTRY[serverId];
332
+ if (!server?.envVars) {
333
+ return [];
334
+ }
335
+
336
+ return Object.entries(server.envVars)
337
+ .filter(([, config]) => config.secret)
338
+ .map(([name]) => name);
339
+ }
340
+
341
+ /**
342
+ * Get non-secret environment variables for a server
343
+ */
344
+ export function getNonSecretEnvVars(serverId: MCPServerID): string[] {
345
+ const server = MCP_SERVER_REGISTRY[serverId];
346
+ if (!server?.envVars) {
347
+ return [];
348
+ }
349
+
350
+ return Object.entries(server.envVars)
351
+ .filter(([, config]) => !config.secret)
352
+ .map(([name]) => name);
353
+ }
354
+
355
+ /**
356
+ * Validate server ID
357
+ */
358
+ export function isValidServerID(id: string): id is MCPServerID {
359
+ return id in MCP_SERVER_REGISTRY;
360
+ }
361
+
362
+ /**
363
+ * Get server definition
364
+ */
365
+ export function getServerDefinition(id: MCPServerID): MCPServerDefinition {
366
+ const server = MCP_SERVER_REGISTRY[id];
367
+ if (!server) {
368
+ throw new Error(`Unknown MCP server: ${id}`);
369
+ }
370
+ return server;
371
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Target registry - functional implementation with composition
3
+ * Pure functions operating on immutable data
4
+ */
5
+
6
+ import type { Option } from '../core/functional/option.js';
7
+ import { none, some } from '../core/functional/option.js';
8
+ import { claudeCodeTarget } from '../targets/claude-code.js';
9
+ import { opencodeTarget } from '../targets/opencode.js';
10
+ import type { Target } from '../types.js';
11
+
12
+ /**
13
+ * All available targets
14
+ * Lazy-initialized to avoid circular dependencies
15
+ */
16
+ let cachedTargets: readonly Target[] | null = null;
17
+
18
+ const initializeTargets = (): readonly Target[] => {
19
+ if (cachedTargets) {
20
+ return cachedTargets;
21
+ }
22
+
23
+ cachedTargets = Object.freeze([opencodeTarget, claudeCodeTarget]);
24
+
25
+ return cachedTargets;
26
+ };
27
+
28
+ /**
29
+ * Get all targets
30
+ */
31
+ export const getAllTargets = (): readonly Target[] => initializeTargets();
32
+
33
+ /**
34
+ * Get implemented targets only
35
+ */
36
+ export const getImplementedTargets = (): readonly Target[] =>
37
+ getAllTargets().filter((target) => target.isImplemented);
38
+
39
+ /**
40
+ * Get all target IDs
41
+ */
42
+ export const getAllTargetIDs = (): readonly string[] => getAllTargets().map((target) => target.id);
43
+
44
+ /**
45
+ * Get implemented target IDs
46
+ */
47
+ export const getImplementedTargetIDs = (): readonly string[] =>
48
+ getImplementedTargets().map((target) => target.id);
49
+
50
+ /**
51
+ * Get target by ID
52
+ * Returns Option type for explicit null handling
53
+ */
54
+ export const getTarget = (id: string): Option<Target> => {
55
+ const target = getAllTargets().find((t) => t.id === id);
56
+ return target ? some(target) : none;
57
+ };
58
+
59
+ /**
60
+ * Get target by ID (unsafe - throws if not found)
61
+ * Use getTarget() for safer alternative with Option type
62
+ */
63
+ export const getTargetUnsafe = (id: string): Target => {
64
+ const target = getAllTargets().find((t) => t.id === id);
65
+ if (!target) {
66
+ throw new Error(`Target not found: ${id}`);
67
+ }
68
+ return target;
69
+ };
70
+
71
+ /**
72
+ * Get default target
73
+ * Returns Option type for explicit null handling
74
+ */
75
+ export const getDefaultTarget = (): Option<Target> => {
76
+ const target = getAllTargets().find((t) => t.isDefault);
77
+ return target ? some(target) : none;
78
+ };
79
+
80
+ /**
81
+ * Get default target (unsafe - throws if not found)
82
+ * Use getDefaultTarget() for safer alternative with Option type
83
+ */
84
+ export const getDefaultTargetUnsafe = (): Target => {
85
+ const target = getAllTargets().find((t) => t.isDefault);
86
+ if (!target) {
87
+ throw new Error('No default target configured');
88
+ }
89
+ return target;
90
+ };
91
+
92
+ /**
93
+ * Get targets that support MCP servers
94
+ */
95
+ export const getTargetsWithMCPSupport = (): readonly Target[] =>
96
+ getImplementedTargets().filter((target) => !!target.setupMCP);
97
+
98
+ /**
99
+ * Get targets that support command execution (agent running)
100
+ */
101
+ export const getTargetsWithCommandSupport = (): readonly Target[] =>
102
+ getImplementedTargets().filter((target) => !!target.executeCommand);
103
+
104
+ /**
105
+ * Check if target is implemented
106
+ */
107
+ export const isTargetImplemented = (id: string): boolean => {
108
+ const target = getAllTargets().find((t) => t.id === id);
109
+ return target?.isImplemented ?? false;
110
+ };
111
+
112
+ /**
113
+ * Utility type for target IDs
114
+ */
115
+ export type TargetID = ReturnType<typeof getAllTargetIDs>[number];
116
+
117
+ /**
118
+ * Legacy aliases for backward compatibility
119
+ * @deprecated Use getAllTargets() instead
120
+ */
121
+ export const ALL_TARGETS = getAllTargets;
122
+
123
+ /**
124
+ * @deprecated Use getImplementedTargets() instead
125
+ */
126
+ export const IMPLEMENTED_TARGETS = getImplementedTargets;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Agent Loader
3
+ * Loads agent definitions from markdown files with front matter
4
+ */
5
+
6
+ import { readFile, readdir, access } from 'node:fs/promises';
7
+ import { join, parse, relative, dirname } from 'node:path';
8
+ import { homedir } from 'node:os';
9
+ import { fileURLToPath } from 'node:url';
10
+ import matter from 'gray-matter';
11
+ import type { Agent, AgentMetadata } from '../types/agent.types.js';
12
+
13
+ /**
14
+ * Load a single agent from a markdown file
15
+ */
16
+ export async function loadAgentFromFile(
17
+ filePath: string,
18
+ isBuiltin: boolean = false,
19
+ agentId?: string
20
+ ): Promise<Agent | null> {
21
+ try {
22
+ const content = await readFile(filePath, 'utf-8');
23
+ const { data, content: systemPrompt } = matter(content);
24
+
25
+ // Validate front matter
26
+ if (!data.name || typeof data.name !== 'string') {
27
+ console.error(`Agent file ${filePath} missing required 'name' field`);
28
+ return null;
29
+ }
30
+
31
+ const metadata: AgentMetadata = {
32
+ name: data.name,
33
+ description: data.description || '',
34
+ };
35
+
36
+ // Get agent ID from parameter or filename
37
+ const id = agentId || parse(filePath).name;
38
+
39
+ return {
40
+ id,
41
+ metadata,
42
+ systemPrompt: systemPrompt.trim(),
43
+ isBuiltin,
44
+ filePath,
45
+ };
46
+ } catch (error) {
47
+ console.error(`Failed to load agent from ${filePath}:`, error);
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Load all agents from a directory (recursively)
54
+ */
55
+ export async function loadAgentsFromDirectory(dirPath: string, isBuiltin: boolean = false): Promise<Agent[]> {
56
+ try {
57
+ // Read directory recursively to support subdirectories
58
+ const files = await readdir(dirPath, { recursive: true, withFileTypes: true });
59
+
60
+ // Filter for .md files and calculate agent IDs from relative paths
61
+ const agentFiles = files
62
+ .filter((f) => f.isFile() && f.name.endsWith('.md'))
63
+ .map((f) => {
64
+ const fullPath = join(f.parentPath || f.path, f.name);
65
+ // Calculate relative path from dirPath and remove .md extension
66
+ const relativePath = relative(dirPath, fullPath).replace(/\.md$/, '');
67
+ return { fullPath, agentId: relativePath };
68
+ });
69
+
70
+ const agents = await Promise.all(
71
+ agentFiles.map(({ fullPath, agentId }) => loadAgentFromFile(fullPath, isBuiltin, agentId))
72
+ );
73
+
74
+ return agents.filter((agent): agent is Agent => agent !== null);
75
+ } catch (error) {
76
+ // Directory doesn't exist or can't be read
77
+ return [];
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get system agents path (bundled with the app)
83
+ */
84
+ export async function getSystemAgentsPath(): Promise<string> {
85
+ // Get the directory of the current module (cross-platform)
86
+ const currentFile = fileURLToPath(import.meta.url);
87
+ const currentDir = dirname(currentFile);
88
+
89
+ // In production (dist), assets are at dist/assets/agents
90
+ // In development (src), go up to project root: src/core -> project root
91
+ const distPath = join(currentDir, '..', 'assets', 'agents');
92
+ const devPath = join(currentDir, '..', '..', 'assets', 'agents');
93
+
94
+ // Check which one exists (try dist first, then dev)
95
+ try {
96
+ await access(distPath);
97
+ return distPath;
98
+ } catch {
99
+ return devPath;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Get all agent search paths
105
+ */
106
+ export function getAgentSearchPaths(cwd: string): string[] {
107
+ const globalPath = join(homedir(), '.sylphx-flow', 'agents');
108
+ const projectPath = join(cwd, '.sylphx-flow', 'agents');
109
+
110
+ return [globalPath, projectPath];
111
+ }
112
+
113
+ /**
114
+ * Load all available agents from all sources
115
+ */
116
+ export async function loadAllAgents(cwd: string, targetAgentDir?: string): Promise<Agent[]> {
117
+ const systemPath = await getSystemAgentsPath();
118
+ const [globalPath, projectPath] = getAgentSearchPaths(cwd);
119
+
120
+ let allAgentPaths = [systemPath, globalPath, projectPath];
121
+
122
+ // If a target-specific agent directory is provided, add it with highest priority
123
+ if (targetAgentDir) {
124
+ const targetPath = join(cwd, targetAgentDir); // Assuming targetAgentDir is relative to cwd
125
+ allAgentPaths.push(targetPath);
126
+ }
127
+
128
+ // Load agents from all paths
129
+ const loadedAgentsPromises = allAgentPaths.map(path => loadAgentsFromDirectory(path, path === systemPath));
130
+ const loadedAgentsArrays = await Promise.all(loadedAgentsPromises);
131
+
132
+ // Flatten and deduplicate
133
+ const agentMap = new Map<string, Agent>();
134
+ for (const agentArray of loadedAgentsArrays) {
135
+ for (const agent of agentArray) {
136
+ agentMap.set(agent.id, agent);
137
+ }
138
+ }
139
+
140
+ return Array.from(agentMap.values());
141
+ }