@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,303 @@
1
+ /**
2
+ * Composable file installer - shared installation logic
3
+ * Used by targets through composition
4
+ */
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import type { CommonOptions, ProcessResult } from '../../shared/index.js';
9
+ import { clearObsoleteFiles, collectFiles, getLocalFileInfo } from '../../shared/index.js';
10
+
11
+ export interface InstallOptions extends CommonOptions {
12
+ /** Custom file extension */
13
+ extension?: string;
14
+ /** Whether to flatten directory structure */
15
+ flatten?: boolean;
16
+ /** Show progress messages */
17
+ showProgress?: boolean;
18
+ }
19
+
20
+ export type FileTransformFn = (content: string, sourcePath?: string) => Promise<string>;
21
+
22
+ /**
23
+ * Collect files from source directory
24
+ */
25
+ async function collectSourceFiles(sourceDir: string, extension: string): Promise<string[]> {
26
+ if (!fs.existsSync(sourceDir)) {
27
+ return [];
28
+ }
29
+
30
+ const allFiles: string[] = [];
31
+
32
+ // Collect files directly in root
33
+ const rootFiles = fs
34
+ .readdirSync(sourceDir, { withFileTypes: true })
35
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith(extension))
36
+ .map((dirent) => dirent.name);
37
+
38
+ allFiles.push(...rootFiles);
39
+
40
+ // Collect files from subdirectories (excluding 'archived')
41
+ const subdirs = fs
42
+ .readdirSync(sourceDir, { withFileTypes: true })
43
+ .filter((dirent) => dirent.isDirectory() && dirent.name !== 'archived')
44
+ .map((dirent) => dirent.name);
45
+
46
+ for (const subdir of subdirs) {
47
+ const subdirPath = path.join(sourceDir, subdir);
48
+ const files = await collectFiles(subdirPath, [extension]);
49
+ allFiles.push(...files.map((file) => path.join(subdir, file)));
50
+ }
51
+
52
+ return allFiles;
53
+ }
54
+
55
+ /**
56
+ * Install files from source directory to target directory
57
+ */
58
+ export async function installToDirectory(
59
+ sourceDir: string,
60
+ targetDir: string,
61
+ transform: FileTransformFn,
62
+ options: InstallOptions = {}
63
+ ): Promise<ProcessResult[]> {
64
+ const results: ProcessResult[] = [];
65
+
66
+ // Collect source files
67
+ const files = await collectSourceFiles(sourceDir, options.extension || '.md');
68
+
69
+ if (files.length === 0) {
70
+ return results;
71
+ }
72
+
73
+ // Clear obsolete files if requested
74
+ if (options.clear && fs.existsSync(targetDir)) {
75
+ const expectedFiles = new Set(
76
+ files.map((file) => {
77
+ if (options.flatten) {
78
+ const parsed = path.parse(file);
79
+ const baseName = parsed.name;
80
+ const dir = parsed.dir;
81
+ const flatName = dir ? `${dir.replace(/[/\\]/g, '-')}-${baseName}` : baseName;
82
+ return `${flatName}${options.extension || '.md'}`;
83
+ }
84
+ return file;
85
+ })
86
+ );
87
+
88
+ clearObsoleteFiles(targetDir, expectedFiles, [options.extension || '.md'], results);
89
+ }
90
+
91
+ // Create target directory
92
+ fs.mkdirSync(targetDir, { recursive: true });
93
+
94
+ if (options.showProgress && !options.quiet) {
95
+ console.log(
96
+ `Installing ${files.length} file${files.length > 1 ? 's' : ''} to ${targetDir.replace(`${process.cwd()}/`, '')}`
97
+ );
98
+ console.log('');
99
+ }
100
+
101
+ if (options.dryRun) {
102
+ if (!options.quiet) {
103
+ console.log('✓ Dry run completed - no files were modified');
104
+ }
105
+ return results;
106
+ }
107
+
108
+ // Process files in parallel
109
+ const processPromises = files.map(async (file) => {
110
+ const sourcePath = path.join(sourceDir, file);
111
+ const targetPath = path.join(targetDir, file);
112
+
113
+ // Ensure target directory exists
114
+ const targetFileDir = path.dirname(targetPath);
115
+ if (!fs.existsSync(targetFileDir)) {
116
+ fs.mkdirSync(targetFileDir, { recursive: true });
117
+ }
118
+
119
+ const localInfo = await getLocalFileInfo(targetPath);
120
+
121
+ // Read and transform content
122
+ let content = fs.readFileSync(sourcePath, 'utf8');
123
+ content = await transform(content, file);
124
+
125
+ const localProcessed = localInfo ? await transform(localInfo.content, file) : '';
126
+ const contentChanged = !localInfo || localProcessed !== content;
127
+
128
+ if (contentChanged) {
129
+ fs.writeFileSync(targetPath, content, 'utf8');
130
+ results.push({
131
+ file,
132
+ status: localInfo ? 'updated' : 'added',
133
+ action: localInfo ? 'Updated' : 'Created',
134
+ });
135
+ } else {
136
+ results.push({
137
+ file,
138
+ status: 'current',
139
+ action: 'Already current',
140
+ });
141
+ }
142
+ });
143
+
144
+ await Promise.all(processPromises);
145
+
146
+ return results;
147
+ }
148
+
149
+ /**
150
+ * Append files from source directory to a single target file
151
+ */
152
+ export async function appendToFile(
153
+ sourceDir: string,
154
+ targetFile: string,
155
+ transform: FileTransformFn,
156
+ options: InstallOptions = {}
157
+ ): Promise<void> {
158
+ // Collect source files
159
+ const files = await collectSourceFiles(sourceDir, options.extension || '.md');
160
+
161
+ if (files.length === 0) {
162
+ return;
163
+ }
164
+
165
+ if (options.dryRun) {
166
+ if (!options.quiet) {
167
+ console.log(
168
+ `Dry run: Would append ${files.length} file${files.length > 1 ? 's' : ''} to ${targetFile.replace(`${process.cwd()}/`, '')}`
169
+ );
170
+ }
171
+ return;
172
+ }
173
+
174
+ // Read existing file content
175
+ let existingContent = '';
176
+ if (fs.existsSync(targetFile)) {
177
+ existingContent = fs.readFileSync(targetFile, 'utf8');
178
+ }
179
+
180
+ // Build appended content
181
+ let appendContent = '';
182
+ for (const file of files) {
183
+ const sourcePath = path.join(sourceDir, file);
184
+ let content = fs.readFileSync(sourcePath, 'utf8');
185
+ content = await transform(content, file);
186
+ appendContent += `${content}\n\n`;
187
+ }
188
+
189
+ // Write combined content
190
+ fs.writeFileSync(targetFile, existingContent + appendContent, 'utf8');
191
+
192
+ if (options.showProgress && !options.quiet) {
193
+ console.log(
194
+ `Appended ${files.length} file${files.length > 1 ? 's' : ''} to ${targetFile.replace(`${process.cwd()}/`, '')}`
195
+ );
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Install a single file from source to target
201
+ */
202
+ export async function installFile(
203
+ sourceFile: string,
204
+ targetFile: string,
205
+ transform: FileTransformFn,
206
+ options: InstallOptions = {}
207
+ ): Promise<void> {
208
+ if (options.dryRun) {
209
+ if (!options.quiet) {
210
+ console.log(`Dry run: Would install file to ${targetFile.replace(`${process.cwd()}/`, '')}`);
211
+ }
212
+ return;
213
+ }
214
+
215
+ if (!fs.existsSync(sourceFile)) {
216
+ if (!options.quiet) {
217
+ console.warn(`Source file not found: ${sourceFile}`);
218
+ }
219
+ return;
220
+ }
221
+
222
+ // Ensure target directory exists
223
+ const targetDir = path.dirname(targetFile);
224
+ fs.mkdirSync(targetDir, { recursive: true });
225
+
226
+ // Check if file already exists and is up to date
227
+ const localInfo = await getLocalFileInfo(targetFile);
228
+ let content = fs.readFileSync(sourceFile, 'utf8');
229
+ content = await transform(content);
230
+
231
+ if (localInfo && localInfo.content === content) {
232
+ if (!options.quiet) {
233
+ console.log(`File already current: ${targetFile.replace(`${process.cwd()}/`, '')}`);
234
+ }
235
+ return;
236
+ }
237
+
238
+ // Write file
239
+ fs.writeFileSync(targetFile, content, 'utf8');
240
+
241
+ if (options.showProgress && !options.quiet) {
242
+ const action = localInfo ? 'Updated' : 'Created';
243
+ console.log(`${action} file: ${targetFile.replace(`${process.cwd()}/`, '')}`);
244
+ }
245
+ }
246
+
247
+ /**
248
+ * File installer interface for backward compatibility
249
+ */
250
+ export interface FileInstaller {
251
+ installToDirectory(
252
+ sourceDir: string,
253
+ targetDir: string,
254
+ transform: FileTransformFn,
255
+ options?: InstallOptions
256
+ ): Promise<ProcessResult[]>;
257
+ appendToFile(
258
+ sourceDir: string,
259
+ targetFile: string,
260
+ transform: FileTransformFn,
261
+ options?: InstallOptions
262
+ ): Promise<void>;
263
+ installFile(
264
+ sourceFile: string,
265
+ targetFile: string,
266
+ transform: FileTransformFn,
267
+ options?: InstallOptions
268
+ ): Promise<void>;
269
+ }
270
+
271
+ /**
272
+ * Composable file installer
273
+ * Handles copying files from source to destination with optional transformation
274
+ * @deprecated Use standalone functions (installToDirectory, appendToFile, installFile) for new code
275
+ */
276
+ export class FileInstaller {
277
+ async installToDirectory(
278
+ sourceDir: string,
279
+ targetDir: string,
280
+ transform: FileTransformFn,
281
+ options: InstallOptions = {}
282
+ ): Promise<ProcessResult[]> {
283
+ return installToDirectory(sourceDir, targetDir, transform, options);
284
+ }
285
+
286
+ async appendToFile(
287
+ sourceDir: string,
288
+ targetFile: string,
289
+ transform: FileTransformFn,
290
+ options: InstallOptions = {}
291
+ ): Promise<void> {
292
+ return appendToFile(sourceDir, targetFile, transform, options);
293
+ }
294
+
295
+ async installFile(
296
+ sourceFile: string,
297
+ targetFile: string,
298
+ transform: FileTransformFn,
299
+ options: InstallOptions = {}
300
+ ): Promise<void> {
301
+ return installFile(sourceFile, targetFile, transform, options);
302
+ }
303
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Composable MCP installer - shared MCP installation logic
3
+ * Used by targets through composition
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import ora from 'ora';
9
+ import { MCP_SERVER_REGISTRY, type MCPServerID } from '../../config/servers.js';
10
+ import { createMCPService } from '../../services/mcp-service.js';
11
+ import type { Target } from '../../types.js';
12
+
13
+ export interface MCPInstallResult {
14
+ selectedServers: MCPServerID[];
15
+ serverConfigsMap: Record<MCPServerID, Record<string, string>>;
16
+ }
17
+
18
+ /**
19
+ * MCP Installer interface
20
+ */
21
+ export interface MCPInstaller {
22
+ selectServers(options?: { quiet?: boolean }): Promise<MCPServerID[]>;
23
+ configureServers(
24
+ selectedServers: MCPServerID[],
25
+ options?: { quiet?: boolean }
26
+ ): Promise<Record<MCPServerID, Record<string, string>>>;
27
+ installServers(
28
+ selectedServers: MCPServerID[],
29
+ serverConfigsMap: Record<MCPServerID, Record<string, string>>,
30
+ options?: { quiet?: boolean }
31
+ ): Promise<void>;
32
+ setupMCP(options?: { quiet?: boolean; dryRun?: boolean }): Promise<MCPInstallResult>;
33
+ }
34
+
35
+ /**
36
+ * Create an MCP installer instance
37
+ * Handles server selection, configuration, and installation
38
+ *
39
+ * @param target - Target configuration object (dependency injection)
40
+ */
41
+ export function createMCPInstaller(target: Target): MCPInstaller {
42
+ const mcpService = createMCPService({ target });
43
+
44
+ /**
45
+ * Prompt user to select MCP servers
46
+ */
47
+ const selectServers = async (options: { quiet?: boolean } = {}): Promise<MCPServerID[]> => {
48
+ const allServers = mcpService.getAllServerIds();
49
+ const installedServers = await mcpService.getInstalledServerIds();
50
+
51
+ if (!options.quiet) {
52
+ console.log(chalk.cyan.bold('━━━ Configure MCP Tools ━━━\n'));
53
+ }
54
+
55
+ // Show server selection
56
+ const serverSelectionAnswer = await inquirer.prompt([
57
+ {
58
+ type: 'checkbox',
59
+ name: 'selectedServers',
60
+ message: 'Select MCP tools to install:',
61
+ choices: allServers.map((id) => {
62
+ const server = MCP_SERVER_REGISTRY[id];
63
+ const isInstalled = installedServers.includes(id);
64
+ return {
65
+ name: `${server.name} - ${server.description}`,
66
+ value: id,
67
+ checked: server.required || isInstalled || server.defaultInInit || false,
68
+ disabled: server.required ? '(required)' : false,
69
+ };
70
+ }),
71
+ },
72
+ ]);
73
+
74
+ let selectedServers = serverSelectionAnswer.selectedServers as MCPServerID[];
75
+
76
+ // Ensure all required servers are included
77
+ const requiredServers = allServers.filter((id) => MCP_SERVER_REGISTRY[id].required);
78
+ selectedServers = [...new Set([...requiredServers, ...selectedServers])];
79
+
80
+ return selectedServers;
81
+ };
82
+
83
+ /**
84
+ * Configure selected servers
85
+ */
86
+ const configureServers = async (
87
+ selectedServers: MCPServerID[],
88
+ options: { quiet?: boolean } = {}
89
+ ): Promise<Record<MCPServerID, Record<string, string>>> => {
90
+ const serversNeedingConfig = selectedServers.filter((id) => {
91
+ const server = MCP_SERVER_REGISTRY[id];
92
+ return server.envVars && Object.keys(server.envVars).length > 0;
93
+ });
94
+
95
+ const serverConfigsMap: Record<MCPServerID, Record<string, string>> = {};
96
+
97
+ if (serversNeedingConfig.length > 0) {
98
+ if (!options.quiet) {
99
+ console.log(chalk.cyan.bold('\n━━━ Server Configuration ━━━\n'));
100
+ }
101
+
102
+ const collectedEnv: Record<string, string> = {};
103
+ for (const serverId of serversNeedingConfig) {
104
+ const configValues = await mcpService.configureServer(serverId, collectedEnv);
105
+ serverConfigsMap[serverId] = configValues;
106
+ }
107
+ }
108
+
109
+ return serverConfigsMap;
110
+ };
111
+
112
+ /**
113
+ * Install servers with configuration
114
+ */
115
+ const installServers = async (
116
+ selectedServers: MCPServerID[],
117
+ serverConfigsMap: Record<MCPServerID, Record<string, string>>,
118
+ options: { quiet?: boolean } = {}
119
+ ): Promise<void> => {
120
+ if (selectedServers.length === 0) {
121
+ return;
122
+ }
123
+
124
+ // Only show spinner if not in quiet mode
125
+ const spinner = options.quiet
126
+ ? null
127
+ : ora({
128
+ text: `Installing ${selectedServers.length} MCP server${selectedServers.length > 1 ? 's' : ''}`,
129
+ color: 'cyan',
130
+ }).start();
131
+
132
+ try {
133
+ await mcpService.installServers(selectedServers, serverConfigsMap);
134
+ if (spinner) {
135
+ spinner.succeed(
136
+ chalk.green(
137
+ `Installed ${chalk.cyan(selectedServers.length)} MCP server${selectedServers.length > 1 ? 's' : ''}`
138
+ )
139
+ );
140
+ }
141
+ } catch (error) {
142
+ if (spinner) {
143
+ spinner.fail(chalk.red('Failed to install MCP servers'));
144
+ }
145
+ throw error;
146
+ }
147
+ };
148
+
149
+ /**
150
+ * Full MCP setup workflow: select, configure, and install
151
+ */
152
+ const setupMCP = async (
153
+ options: { quiet?: boolean; dryRun?: boolean } = {}
154
+ ): Promise<MCPInstallResult> => {
155
+ // Select servers
156
+ const selectedServers = await selectServers(options);
157
+
158
+ if (selectedServers.length === 0) {
159
+ return { selectedServers: [], serverConfigsMap: {} };
160
+ }
161
+
162
+ // Configure servers
163
+ const serverConfigsMap = await configureServers(selectedServers, options);
164
+
165
+ // Install servers
166
+ if (!options.dryRun) {
167
+ await installServers(selectedServers, serverConfigsMap, options);
168
+ }
169
+
170
+ return { selectedServers, serverConfigsMap };
171
+ };
172
+
173
+ return {
174
+ selectServers,
175
+ configureServers,
176
+ installServers,
177
+ setupMCP,
178
+ };
179
+ }
180
+
181
+ /**
182
+ * @deprecated Use createMCPInstaller() for new code
183
+ */
184
+ export class MCPInstaller {
185
+ private installer: ReturnType<typeof createMCPInstaller>;
186
+
187
+ constructor(target: Target) {
188
+ this.installer = createMCPInstaller(target);
189
+ }
190
+
191
+ async selectServers(options: { quiet?: boolean } = {}): Promise<MCPServerID[]> {
192
+ return this.installer.selectServers(options);
193
+ }
194
+
195
+ async configureServers(
196
+ selectedServers: MCPServerID[],
197
+ options: { quiet?: boolean } = {}
198
+ ): Promise<Record<MCPServerID, Record<string, string>>> {
199
+ return this.installer.configureServers(selectedServers, options);
200
+ }
201
+
202
+ async installServers(
203
+ selectedServers: MCPServerID[],
204
+ serverConfigsMap: Record<MCPServerID, Record<string, string>>,
205
+ options: { quiet?: boolean } = {}
206
+ ): Promise<void> {
207
+ return this.installer.installServers(selectedServers, serverConfigsMap, options);
208
+ }
209
+
210
+ async setupMCP(options: { quiet?: boolean; dryRun?: boolean } = {}): Promise<MCPInstallResult> {
211
+ return this.installer.setupMCP(options);
212
+ }
213
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Core interfaces for dependency inversion
3
+ * All abstractions in one place
4
+ *
5
+ * DESIGN RATIONALE:
6
+ * - Single source of truth for contracts
7
+ * - Enables dependency injection
8
+ * - Facilitates testing
9
+ * - Clear separation of interface and implementation
10
+ */
11
+
12
+ // Re-export commonly used types from existing interfaces
13
+ export type {
14
+ CoreService,
15
+ FileProcessor,
16
+ IDatabaseConnection,
17
+ ILogger,
18
+ InitializationOptions,
19
+ TargetManager,
20
+ } from '../interfaces.js';
21
+ export * from './repository.interface.js';
22
+ export * from './service.interface.js';
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Repository interface for dependency inversion
3
+ * Abstracts data access implementation
4
+ *
5
+ * DESIGN RATIONALE:
6
+ * - Depend on abstractions, not concrete implementations
7
+ * - Enables testing with in-memory implementations
8
+ * - Separates interface from implementation
9
+ * - Clear contract for data access
10
+ */
11
+
12
+ import type { DatabaseError } from '../functional/error-types.js';
13
+ import type { Result } from '../functional/result.js';
14
+
15
+ /**
16
+ * Query options for filtering, sorting, pagination
17
+ */
18
+ export interface QueryOptions {
19
+ limit?: number;
20
+ offset?: number;
21
+ orderBy?: string;
22
+ orderDirection?: 'ASC' | 'DESC';
23
+ where?: Record<string, any>;
24
+ }
25
+
26
+ /**
27
+ * Paginated result wrapper
28
+ */
29
+ export interface PaginatedResult<T> {
30
+ items: T[];
31
+ total: number;
32
+ page: number;
33
+ pageSize: number;
34
+ hasNext: boolean;
35
+ hasPrevious: boolean;
36
+ }
37
+
38
+ /**
39
+ * Generic repository interface
40
+ * All operations return Result for explicit error handling
41
+ */
42
+ export interface IRepository<T, ID = string | number> {
43
+ /**
44
+ * Find entity by ID
45
+ */
46
+ findById(id: ID): Promise<Result<T | null, DatabaseError>>;
47
+
48
+ /**
49
+ * Find multiple entities with filtering and sorting
50
+ */
51
+ findMany(options?: QueryOptions): Promise<Result<T[], DatabaseError>>;
52
+
53
+ /**
54
+ * Find entities with pagination
55
+ */
56
+ findPaginated(
57
+ page: number,
58
+ pageSize: number,
59
+ options?: Omit<QueryOptions, 'limit' | 'offset'>
60
+ ): Promise<Result<PaginatedResult<T>, DatabaseError>>;
61
+
62
+ /**
63
+ * Create a new entity
64
+ */
65
+ create(data: Partial<T>): Promise<Result<T, DatabaseError>>;
66
+
67
+ /**
68
+ * Update an existing entity
69
+ */
70
+ update(id: ID, data: Partial<T>): Promise<Result<T | null, DatabaseError>>;
71
+
72
+ /**
73
+ * Delete an entity
74
+ */
75
+ delete(id: ID): Promise<Result<boolean, DatabaseError>>;
76
+
77
+ /**
78
+ * Count entities matching criteria
79
+ */
80
+ count(options?: { where?: Record<string, any> }): Promise<Result<number, DatabaseError>>;
81
+
82
+ /**
83
+ * Check if entity exists
84
+ */
85
+ exists(id: ID): Promise<Result<boolean, DatabaseError>>;
86
+ }
87
+
88
+ /**
89
+ * Factory function type for creating repositories
90
+ */
91
+ export type RepositoryFactory<T, ID = string | number> = (tableName: string) => IRepository<T, ID>;