@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,376 @@
1
+ /**
2
+ * AI Configuration Management
3
+ *
4
+ * Three-tier configuration system:
5
+ * 1. Global: ~/.sylphx-flow/settings.json (user defaults, contains API keys)
6
+ * 2. Project: ./.sylphx-flow/settings.json (project preferences, no secrets)
7
+ * 3. Local: ./.sylphx-flow/settings.local.json (local overrides, gitignored)
8
+ *
9
+ * Priority: local > project > global
10
+ */
11
+
12
+ import fs from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import { z } from 'zod';
16
+ import { type Result, success, tryCatchAsync } from '../core/functional/result.js';
17
+ import { getAllProviders } from '../providers/index.js';
18
+ import type { ProviderId, ProviderConfigValue as ProviderConfigValueType } from '../types/provider.types.js';
19
+
20
+ // Re-export types for backward compatibility
21
+ export type { ProviderId } from '../types/provider.types.js';
22
+
23
+ /**
24
+ * AI_PROVIDERS - Provider metadata from registry
25
+ * Contains basic info (id, name) for UI components
26
+ * Config schemas are defined in each provider's getConfigSchema()
27
+ */
28
+ export const AI_PROVIDERS = getAllProviders();
29
+
30
+ /**
31
+ * Provider configuration
32
+ * Each provider can have different config fields (defined by provider.getConfigSchema())
33
+ * Common fields: apiKey, defaultModel, etc
34
+ */
35
+ export type ProviderConfigValue = ProviderConfigValueType;
36
+
37
+ /**
38
+ * AI configuration schema
39
+ * Uses generic Record for provider configs - validation happens at provider level
40
+ */
41
+ const aiConfigSchema = z.object({
42
+ defaultProvider: z.enum(['anthropic', 'openai', 'google', 'openrouter', 'claude-code', 'zai']).optional(),
43
+ defaultModel: z.string().optional(),
44
+ providers: z.record(
45
+ z.string(),
46
+ z.object({
47
+ defaultModel: z.string().optional(),
48
+ }).passthrough() // Allow additional fields defined by provider
49
+ ).optional(),
50
+ });
51
+
52
+ export type AIConfig = z.infer<typeof aiConfigSchema>;
53
+
54
+ /**
55
+ * Configuration file paths
56
+ */
57
+ const GLOBAL_CONFIG_FILE = path.join(os.homedir(), '.sylphx-flow', 'settings.json');
58
+ const PROJECT_CONFIG_FILE = '.sylphx-flow/settings.json';
59
+ const LOCAL_CONFIG_FILE = '.sylphx-flow/settings.local.json';
60
+
61
+ /**
62
+ * Deprecated config file (for migration)
63
+ */
64
+ const LEGACY_CONFIG_FILE = '.sylphx-flow/ai-config.json';
65
+
66
+ /**
67
+ * Get AI config file paths in priority order
68
+ */
69
+ export const getAIConfigPaths = (cwd: string = process.cwd()): {
70
+ global: string;
71
+ project: string;
72
+ local: string;
73
+ legacy: string;
74
+ } => ({
75
+ global: GLOBAL_CONFIG_FILE,
76
+ project: path.join(cwd, PROJECT_CONFIG_FILE),
77
+ local: path.join(cwd, LOCAL_CONFIG_FILE),
78
+ legacy: path.join(cwd, LEGACY_CONFIG_FILE),
79
+ });
80
+
81
+ /**
82
+ * Load config from a single file
83
+ */
84
+ const loadConfigFile = async (filePath: string): Promise<AIConfig | null> => {
85
+ try {
86
+ const content = await fs.readFile(filePath, 'utf8');
87
+ const parsed = JSON.parse(content);
88
+ return aiConfigSchema.parse(parsed);
89
+ } catch (error: any) {
90
+ if (error.code === 'ENOENT') {
91
+ return null; // File doesn't exist
92
+ }
93
+ throw error; // Re-throw other errors
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Deep merge two configs (b overwrites a)
99
+ */
100
+ const mergeConfigs = (a: AIConfig, b: AIConfig): AIConfig => {
101
+ // Merge provider configs dynamically
102
+ const allProviderIds = new Set([
103
+ ...Object.keys(a.providers || {}),
104
+ ...Object.keys(b.providers || {}),
105
+ ]);
106
+
107
+ const mergedProviders: Record<string, any> = {};
108
+ for (const providerId of allProviderIds) {
109
+ mergedProviders[providerId] = {
110
+ ...a.providers?.[providerId],
111
+ ...b.providers?.[providerId],
112
+ };
113
+ }
114
+
115
+ return {
116
+ defaultProvider: b.defaultProvider ?? a.defaultProvider,
117
+ defaultModel: b.defaultModel ?? a.defaultModel,
118
+ providers: mergedProviders,
119
+ };
120
+ };
121
+
122
+ /**
123
+ * Check if any AI config exists
124
+ */
125
+ export const aiConfigExists = async (cwd: string = process.cwd()): Promise<boolean> => {
126
+ const paths = getAIConfigPaths(cwd);
127
+ try {
128
+ // Check any of the config files
129
+ await fs.access(paths.global).catch(() => {});
130
+ return true;
131
+ } catch {}
132
+
133
+ try {
134
+ await fs.access(paths.project);
135
+ return true;
136
+ } catch {}
137
+
138
+ try {
139
+ await fs.access(paths.local);
140
+ return true;
141
+ } catch {}
142
+
143
+ try {
144
+ await fs.access(paths.legacy);
145
+ return true;
146
+ } catch {}
147
+
148
+ return false;
149
+ };
150
+
151
+ /**
152
+ * Load AI configuration
153
+ * Merges global, project, and local configs with priority: local > project > global
154
+ * Automatically migrates legacy config on first load
155
+ */
156
+ export const loadAIConfig = async (cwd: string = process.cwd()): Promise<Result<AIConfig, Error>> => {
157
+ return tryCatchAsync(
158
+ async () => {
159
+ const paths = getAIConfigPaths(cwd);
160
+
161
+ // Load all config files
162
+ const [globalConfig, projectConfig, localConfig, legacyConfig] = await Promise.all([
163
+ loadConfigFile(paths.global),
164
+ loadConfigFile(paths.project),
165
+ loadConfigFile(paths.local),
166
+ loadConfigFile(paths.legacy),
167
+ ]);
168
+
169
+ // Auto-migrate legacy config if it exists and global doesn't
170
+ if (legacyConfig && !globalConfig) {
171
+ await migrateLegacyConfig(cwd);
172
+ // Reload global config after migration
173
+ const migratedGlobal = await loadConfigFile(paths.global);
174
+ if (migratedGlobal) {
175
+ // Start with empty config
176
+ let merged: AIConfig = {};
177
+
178
+ // Merge in priority order: global < project < local
179
+ merged = mergeConfigs(merged, migratedGlobal);
180
+ if (projectConfig) merged = mergeConfigs(merged, projectConfig);
181
+ if (localConfig) merged = mergeConfigs(merged, localConfig);
182
+
183
+ return merged;
184
+ }
185
+ }
186
+
187
+ // Start with empty config
188
+ let merged: AIConfig = {};
189
+
190
+ // Merge in priority order: global < project < local < legacy (for backwards compat)
191
+ if (globalConfig) merged = mergeConfigs(merged, globalConfig);
192
+ if (projectConfig) merged = mergeConfigs(merged, projectConfig);
193
+ if (localConfig) merged = mergeConfigs(merged, localConfig);
194
+ if (legacyConfig) merged = mergeConfigs(merged, legacyConfig);
195
+
196
+ return merged;
197
+ },
198
+ (error: any) => new Error(`Failed to load AI config: ${error.message}`)
199
+ );
200
+ };
201
+
202
+ /**
203
+ * Save AI configuration to global settings
204
+ * By default, all configuration (including API keys) goes to ~/.sylphx-flow/settings.json
205
+ * Automatically sets default provider if not set
206
+ */
207
+ export const saveAIConfig = async (
208
+ config: AIConfig,
209
+ cwd: string = process.cwd()
210
+ ): Promise<Result<void, Error>> => {
211
+ const paths = getAIConfigPaths(cwd);
212
+ const configPath = paths.global; // Save to global by default
213
+
214
+ return tryCatchAsync(
215
+ async () => {
216
+ // Ensure directory exists
217
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
218
+
219
+ // Auto-set default provider if not set
220
+ const configToSave = { ...config };
221
+ if (!configToSave.defaultProvider && configToSave.providers) {
222
+ // Get configured providers (those that pass isConfigured check)
223
+ const { getProvider } = await import('../providers/index.js');
224
+ const configuredProviders: ProviderId[] = [];
225
+
226
+ for (const [providerId, providerConfig] of Object.entries(configToSave.providers)) {
227
+ try {
228
+ const provider = getProvider(providerId as ProviderId);
229
+ if (provider.isConfigured(providerConfig)) {
230
+ configuredProviders.push(providerId as ProviderId);
231
+ }
232
+ } catch {
233
+ // Skip unknown providers
234
+ }
235
+ }
236
+
237
+ // Use last configured provider as default
238
+ if (configuredProviders.length > 0) {
239
+ configToSave.defaultProvider = configuredProviders[configuredProviders.length - 1];
240
+ }
241
+ }
242
+
243
+ // Validate config
244
+ const validated = aiConfigSchema.parse(configToSave);
245
+
246
+ // Write config
247
+ await fs.writeFile(configPath, JSON.stringify(validated, null, 2) + '\n', 'utf8');
248
+ },
249
+ (error: any) => new Error(`Failed to save AI config: ${error.message}`)
250
+ );
251
+ };
252
+
253
+ /**
254
+ * Save AI configuration to a specific location
255
+ */
256
+ export const saveAIConfigTo = async (
257
+ config: AIConfig,
258
+ location: 'global' | 'project' | 'local',
259
+ cwd: string = process.cwd()
260
+ ): Promise<Result<void, Error>> => {
261
+ const paths = getAIConfigPaths(cwd);
262
+ const configPath = paths[location];
263
+
264
+ return tryCatchAsync(
265
+ async () => {
266
+ // Ensure directory exists
267
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
268
+
269
+ // Validate config
270
+ const validated = aiConfigSchema.parse(config);
271
+
272
+ // Write config
273
+ await fs.writeFile(configPath, JSON.stringify(validated, null, 2) + '\n', 'utf8');
274
+ },
275
+ (error: any) => new Error(`Failed to save AI config to ${location}: ${error.message}`)
276
+ );
277
+ };
278
+
279
+ /**
280
+ * Update AI configuration (merge with existing)
281
+ * Default provider is auto-set by saveAIConfig to last configured provider
282
+ */
283
+ export const updateAIConfig = async (
284
+ updates: Partial<AIConfig>,
285
+ cwd: string = process.cwd()
286
+ ): Promise<Result<void, Error>> => {
287
+ const currentResult = await loadAIConfig(cwd);
288
+
289
+ if (currentResult._tag === 'Failure') {
290
+ return currentResult;
291
+ }
292
+
293
+ const merged: AIConfig = {
294
+ ...currentResult.value,
295
+ ...updates,
296
+ providers: {
297
+ ...currentResult.value.providers,
298
+ ...updates.providers,
299
+ },
300
+ };
301
+
302
+ // saveAIConfig will auto-set default provider if not set
303
+ return saveAIConfig(merged, cwd);
304
+ };
305
+
306
+ /**
307
+ * Get configured providers
308
+ * Uses provider's isConfigured() method to check
309
+ */
310
+ export const getConfiguredProviders = async (
311
+ cwd: string = process.cwd()
312
+ ): Promise<ProviderId[]> => {
313
+ const result = await loadAIConfig(cwd);
314
+
315
+ if (result._tag === 'Failure') {
316
+ return [];
317
+ }
318
+
319
+ const providers: ProviderId[] = [];
320
+ const config = result.value;
321
+
322
+ if (!config.providers) {
323
+ return [];
324
+ }
325
+
326
+ // Dynamically import provider registry to avoid circular dependency
327
+ const { getProvider } = await import('../providers/index.js');
328
+
329
+ for (const [providerId, providerConfig] of Object.entries(config.providers)) {
330
+ try {
331
+ const provider = getProvider(providerId as ProviderId);
332
+ if (provider.isConfigured(providerConfig)) {
333
+ providers.push(providerId as ProviderId);
334
+ }
335
+ } catch {
336
+ // Skip unknown providers
337
+ }
338
+ }
339
+
340
+ return providers;
341
+ };
342
+
343
+ /**
344
+ * Migrate legacy ai-config.json to new settings system
345
+ * Automatically called on first load if legacy config exists
346
+ */
347
+ export const migrateLegacyConfig = async (cwd: string = process.cwd()): Promise<Result<void, Error>> => {
348
+ return tryCatchAsync(
349
+ async () => {
350
+ const paths = getAIConfigPaths(cwd);
351
+
352
+ // Check if legacy config exists
353
+ const legacyConfig = await loadConfigFile(paths.legacy);
354
+ if (!legacyConfig) {
355
+ return; // No legacy config to migrate
356
+ }
357
+
358
+ // Check if global config already exists
359
+ const globalConfig = await loadConfigFile(paths.global);
360
+ if (globalConfig) {
361
+ // Global config exists, don't overwrite it
362
+ console.log('Legacy config found but global config already exists. Skipping migration.');
363
+ console.log(`You can manually delete ${paths.legacy} if migration is complete.`);
364
+ return;
365
+ }
366
+
367
+ // Migrate to global config
368
+ await fs.mkdir(path.dirname(paths.global), { recursive: true });
369
+ await fs.writeFile(paths.global, JSON.stringify(legacyConfig, null, 2) + '\n', 'utf8');
370
+
371
+ console.log(`✓ Migrated configuration from ${paths.legacy} to ${paths.global}`);
372
+ console.log(` You can now safely delete the legacy file: ${paths.legacy}`);
373
+ },
374
+ (error: any) => new Error(`Failed to migrate legacy config: ${error.message}`)
375
+ );
376
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Central constants for configuration
3
+ */
4
+
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+
8
+ /**
9
+ * Configuration directory name
10
+ */
11
+ export const CONFIG_DIR = '.sylphx-flow';
12
+
13
+ /**
14
+ * User-global settings directory (in home directory)
15
+ */
16
+ export const USER_CONFIG_DIR = path.join(os.homedir(), CONFIG_DIR);
17
+
18
+ /**
19
+ * User-global settings file (for API keys)
20
+ */
21
+ export const USER_SETTINGS_FILE = path.join(USER_CONFIG_DIR, 'settings.json');
22
+
23
+ /**
24
+ * Project-level settings file (shareable)
25
+ */
26
+ export function getProjectSettingsFile(cwd: string = process.cwd()): string {
27
+ return path.join(cwd, CONFIG_DIR, 'settings.json');
28
+ }
29
+
30
+ /**
31
+ * Project local settings file (never commit, overrides project settings)
32
+ */
33
+ export function getProjectLocalSettingsFile(cwd: string = process.cwd()): string {
34
+ return path.join(cwd, CONFIG_DIR, 'settings.local.json');
35
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Configuration modules barrel export
3
+ * Centralized access to configuration-related functionality
4
+ */
5
+
6
+ // Rules configuration
7
+ export * from './rules.js';
8
+ export {
9
+ getDefaultRules,
10
+ loadRuleConfiguration,
11
+ validateRuleConfiguration,
12
+ } from './rules.js';
13
+ // MCP server configurations
14
+ export * from './servers.js';
15
+ export {
16
+ configureServer,
17
+ getServerConfigurations,
18
+ validateServerConfiguration,
19
+ } from './servers.js';
20
+ // Target configurations
21
+ export * from './targets.js';
22
+ // Re-export commonly used configuration functions with better naming
23
+ export {
24
+ configureTargetDefaults,
25
+ getTargetDefaults,
26
+ validateTargetConfiguration,
27
+ } from './targets.js';
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Rules configuration
3
+ */
4
+
5
+ import { getRuleFile } from '../utils/paths.js';
6
+
7
+ export const CORE_RULES = {
8
+ core: 'core.md',
9
+ } as const;
10
+
11
+ /**
12
+ * Get the path to a specific core rules file
13
+ */
14
+ export function getRulesPath(ruleType: keyof typeof CORE_RULES = 'core'): string {
15
+ return getRuleFile(CORE_RULES[ruleType]);
16
+ }
17
+
18
+ /**
19
+ * Get all available rule types
20
+ */
21
+ export function getAllRuleTypes(): string[] {
22
+ return Object.keys(CORE_RULES);
23
+ }
24
+
25
+ /**
26
+ * Check if a rule file exists
27
+ */
28
+ export function ruleFileExists(ruleType: keyof typeof CORE_RULES): boolean {
29
+ try {
30
+ getRulesPath(ruleType);
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Rules filename mapping by target
39
+ */
40
+ export const RULES_FILES = {
41
+ 'claude-code': 'CLAUDE.md',
42
+ opencode: 'AGENTS.md',
43
+ } as const;