@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
@@ -1,1320 +0,0 @@
1
- import {
2
- getAgentsDir,
3
- getOutputStylesDir,
4
- getRuleFile,
5
- getSlashCommandsDir
6
- } from "./chunk-t16rfxh0.js";
7
- import {
8
- envSecurity,
9
- fileUtils,
10
- generateHelpText,
11
- pathSecurity,
12
- securitySchemas,
13
- yamlUtils
14
- } from "./chunk-hpkhykhq.js";
15
- import {
16
- __require,
17
- __toESM
18
- } from "./chunk-5j4w74t6.js";
19
-
20
- // src/targets/opencode.ts
21
- import fs4 from "node:fs";
22
- import path6 from "node:path";
23
- import chalk3 from "chalk";
24
-
25
- // src/config/rules.ts
26
- var CORE_RULES = {
27
- core: "core.md"
28
- };
29
- function getRulesPath(ruleType = "core") {
30
- return getRuleFile(CORE_RULES[ruleType]);
31
- }
32
- function ruleFileExists(ruleType) {
33
- try {
34
- getRulesPath(ruleType);
35
- return true;
36
- } catch {
37
- return false;
38
- }
39
- }
40
-
41
- // src/config/servers.ts
42
- var MCP_SERVER_REGISTRY = {
43
- "sylphx-flow": {
44
- id: "sylphx-flow",
45
- name: "sylphx-flow",
46
- description: "Sylphx Flow MCP server for agent coordination and memory management",
47
- config: {
48
- type: "stdio",
49
- command: "sylphx-flow",
50
- args: ["mcp", "start"],
51
- env: {
52
- OPENAI_API_KEY: "",
53
- OPENAI_BASE_URL: "https://api.openai.com/v1",
54
- EMBEDDING_MODEL: "text-embedding-3-small"
55
- }
56
- },
57
- envVars: {
58
- OPENAI_API_KEY: {
59
- description: "OpenAI API key for vector search embeddings",
60
- required: false,
61
- secret: true
62
- },
63
- OPENAI_BASE_URL: {
64
- description: "Base URL for OpenAI-compatible embedding API",
65
- required: false,
66
- default: "https://api.openai.com/v1"
67
- },
68
- EMBEDDING_MODEL: {
69
- description: "Embedding model to use for vector search",
70
- required: false,
71
- default: "text-embedding-3-small",
72
- dependsOn: ["OPENAI_API_KEY", "OPENAI_BASE_URL"],
73
- fetchChoices: async () => {
74
- const validatedBaseUrl = envSecurity.getEnvVar("OPENAI_BASE_URL", "https://api.openai.com/v1");
75
- const validatedApiKey = envSecurity.getEnvVar("OPENAI_API_KEY");
76
- if (!validatedApiKey) {
77
- throw new Error("OPENAI_API_KEY is required to fetch embedding models");
78
- }
79
- try {
80
- securitySchemas.apiKey.parse(validatedApiKey);
81
- } catch (_error) {
82
- throw new Error("Invalid OPENAI_API_KEY format");
83
- }
84
- const response = await fetch(`${validatedBaseUrl}/models`, {
85
- headers: { Authorization: `Bearer ${validatedApiKey}` },
86
- timeout: 1e4
87
- });
88
- if (!response.ok) {
89
- throw new Error(`Failed to fetch models: ${response.statusText}`);
90
- }
91
- const data = await response.json();
92
- const embeddingModels = data.data.filter((m) => m.id.includes("embedding")).map((m) => m.id).sort();
93
- if (embeddingModels.length === 0) {
94
- throw new Error("No embedding models found");
95
- }
96
- return embeddingModels;
97
- }
98
- }
99
- },
100
- category: "core",
101
- defaultInInit: true,
102
- required: true
103
- },
104
- "gpt-image": {
105
- id: "gpt-image",
106
- name: "gpt-image-1-mcp",
107
- description: "GPT Image generation MCP server",
108
- config: {
109
- type: "stdio",
110
- command: "npx",
111
- args: ["@napolab/gpt-image-1-mcp"],
112
- env: { OPENAI_API_KEY: "" }
113
- },
114
- envVars: {
115
- OPENAI_API_KEY: {
116
- description: "OpenAI API key for image generation",
117
- required: true,
118
- secret: true
119
- }
120
- },
121
- category: "ai",
122
- defaultInInit: false
123
- },
124
- perplexity: {
125
- id: "perplexity",
126
- name: "perplexity-ask",
127
- description: "Perplexity Ask MCP server for search and queries",
128
- config: {
129
- type: "stdio",
130
- command: "npx",
131
- args: ["-y", "server-perplexity-ask"],
132
- env: { PERPLEXITY_API_KEY: "" }
133
- },
134
- envVars: {
135
- PERPLEXITY_API_KEY: {
136
- description: "Perplexity API key for search and queries",
137
- required: true,
138
- secret: true
139
- }
140
- },
141
- category: "ai",
142
- defaultInInit: false
143
- },
144
- context7: {
145
- id: "context7",
146
- name: "context7",
147
- description: "Context7 HTTP MCP server for documentation retrieval",
148
- config: {
149
- type: "http",
150
- url: "https://mcp.context7.com/mcp"
151
- },
152
- envVars: {
153
- CONTEXT7_API_KEY: {
154
- description: "Context7 API key for enhanced documentation access",
155
- required: false,
156
- secret: true
157
- }
158
- },
159
- category: "external",
160
- defaultInInit: true
161
- },
162
- "gemini-search": {
163
- id: "gemini-search",
164
- name: "gemini-google-search",
165
- description: "Gemini Google Search MCP server",
166
- config: {
167
- type: "stdio",
168
- command: "npx",
169
- args: ["-y", "mcp-gemini-google-search"],
170
- env: { GEMINI_API_KEY: "", GEMINI_MODEL: "gemini-2.5-flash" }
171
- },
172
- envVars: {
173
- GEMINI_API_KEY: {
174
- description: "Google Gemini API key for search functionality",
175
- required: true,
176
- secret: true
177
- },
178
- GEMINI_MODEL: {
179
- description: "Gemini model to use for search",
180
- required: false,
181
- default: "gemini-2.5-flash"
182
- }
183
- },
184
- category: "ai",
185
- defaultInInit: false
186
- },
187
- grep: {
188
- id: "grep",
189
- name: "grep",
190
- description: "GitHub grep MCP server for searching GitHub repositories",
191
- config: {
192
- type: "http",
193
- url: "https://mcp.grep.app"
194
- },
195
- category: "external",
196
- defaultInInit: true
197
- }
198
- };
199
-
200
- // src/core/installers/file-installer.ts
201
- import fs2 from "node:fs";
202
- import path4 from "node:path";
203
- // src/shared/files/index.ts
204
- import path2 from "node:path";
205
-
206
- // src/utils/file-operations.ts
207
- import fs from "node:fs/promises";
208
- import path from "node:path";
209
- async function readFileSafe(filePath, options = {}) {
210
- const { encoding = "utf8", fallback } = options;
211
- try {
212
- const content = await fs.readFile(filePath, encoding);
213
- return content;
214
- } catch (error) {
215
- if (error.code === "ENOENT" && fallback !== undefined) {
216
- return fallback;
217
- }
218
- throw error;
219
- }
220
- }
221
- async function fileExists(filePath) {
222
- try {
223
- await fs.access(filePath);
224
- return true;
225
- } catch {
226
- return false;
227
- }
228
- }
229
- async function getFileInfo(filePath) {
230
- try {
231
- const stats = await fs.stat(filePath);
232
- return {
233
- exists: true,
234
- isFile: stats.isFile(),
235
- isDirectory: stats.isDirectory(),
236
- size: stats.size,
237
- mtime: stats.mtime,
238
- atime: stats.atime,
239
- ctime: stats.ctime
240
- };
241
- } catch {
242
- return {
243
- exists: false,
244
- isFile: false,
245
- isDirectory: false
246
- };
247
- }
248
- }
249
- async function deletePathSafe(targetPath) {
250
- pathSecurity.validatePath(targetPath);
251
- if (!await fileExists(targetPath)) {
252
- return;
253
- }
254
- const info = await getFileInfo(targetPath);
255
- if (info.isDirectory) {
256
- await fs.rm(targetPath, { recursive: true, force: true });
257
- } else {
258
- await fs.unlink(targetPath);
259
- }
260
- }
261
- async function readDirectorySafe(dirPath, options = {}) {
262
- const { recursive = false, includeFiles = true, includeDirectories = true } = options;
263
- pathSecurity.validatePath(dirPath);
264
- if (!await fileExists(dirPath)) {
265
- throw new Error(`Directory does not exist: ${dirPath}`);
266
- }
267
- const info = await getFileInfo(dirPath);
268
- if (!info.isDirectory) {
269
- throw new Error(`Path is not a directory: ${dirPath}`);
270
- }
271
- if (recursive) {
272
- const results = [];
273
- const items2 = await fs.readdir(dirPath, { withFileTypes: true });
274
- for (const item of items2) {
275
- const fullPath = path.join(dirPath, item.name);
276
- if (item.isDirectory() && includeDirectories) {
277
- results.push(fullPath);
278
- const subResults = await readDirectorySafe(fullPath, options);
279
- results.push(...subResults);
280
- } else if (item.isFile() && includeFiles) {
281
- results.push(fullPath);
282
- }
283
- }
284
- return results;
285
- }
286
- const items = await fs.readdir(dirPath, { withFileTypes: true });
287
- return items.filter((item) => {
288
- if (item.isFile() && includeFiles) {
289
- return true;
290
- }
291
- if (item.isDirectory() && includeDirectories) {
292
- return true;
293
- }
294
- return false;
295
- }).map((item) => path.join(dirPath, item.name));
296
- }
297
-
298
- // src/shared/files/index.ts
299
- async function collectFiles(dir, extensions) {
300
- try {
301
- const allFiles = await readDirectorySafe(dir, {
302
- recursive: true,
303
- includeFiles: true,
304
- includeDirectories: false
305
- });
306
- return allFiles.filter((filePath) => extensions.some((ext) => filePath.endsWith(ext))).map((filePath) => path2.relative(dir, filePath)).sort();
307
- } catch {
308
- return [];
309
- }
310
- }
311
- async function getLocalFileInfo(filePath) {
312
- const info = await getFileInfo(filePath);
313
- if (!info.exists || !info.isFile) {
314
- return null;
315
- }
316
- const content = await readFileSafe(filePath);
317
- if (content === null) {
318
- return null;
319
- }
320
- return {
321
- content,
322
- mtime: info.mtime
323
- };
324
- }
325
- async function clearObsoleteFiles(targetDir, expectedFiles, extensions, results) {
326
- try {
327
- const items = await readDirectorySafe(targetDir, {
328
- recursive: false,
329
- includeFiles: true,
330
- includeDirectories: false
331
- });
332
- for (const itemPath of items) {
333
- const fileName = path2.basename(itemPath);
334
- const hasValidExtension = extensions.some((ext) => fileName.endsWith(ext));
335
- if (hasValidExtension && !expectedFiles.has(fileName)) {
336
- await deletePathSafe(itemPath);
337
- results.push({
338
- file: fileName,
339
- status: "skipped",
340
- action: "Removed obsolete file"
341
- });
342
- }
343
- }
344
- } catch {}
345
- }
346
- // src/shared/processing/index.ts
347
- import path3 from "node:path";
348
- // src/core/installers/file-installer.ts
349
- async function collectSourceFiles(sourceDir, extension) {
350
- if (!fs2.existsSync(sourceDir)) {
351
- return [];
352
- }
353
- const allFiles = [];
354
- const rootFiles = fs2.readdirSync(sourceDir, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(extension)).map((dirent) => dirent.name);
355
- allFiles.push(...rootFiles);
356
- const subdirs = fs2.readdirSync(sourceDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory() && dirent.name !== "archived").map((dirent) => dirent.name);
357
- for (const subdir of subdirs) {
358
- const subdirPath = path4.join(sourceDir, subdir);
359
- const files = await collectFiles(subdirPath, [extension]);
360
- allFiles.push(...files.map((file) => path4.join(subdir, file)));
361
- }
362
- return allFiles;
363
- }
364
- async function installToDirectory(sourceDir, targetDir, transform, options = {}) {
365
- const results = [];
366
- const files = await collectSourceFiles(sourceDir, options.extension || ".md");
367
- if (files.length === 0) {
368
- return results;
369
- }
370
- if (options.clear && fs2.existsSync(targetDir)) {
371
- const expectedFiles = new Set(files.map((file) => {
372
- if (options.flatten) {
373
- const parsed = path4.parse(file);
374
- const baseName = parsed.name;
375
- const dir = parsed.dir;
376
- const flatName = dir ? `${dir.replace(/[/\\]/g, "-")}-${baseName}` : baseName;
377
- return `${flatName}${options.extension || ".md"}`;
378
- }
379
- return file;
380
- }));
381
- clearObsoleteFiles(targetDir, expectedFiles, [options.extension || ".md"], results);
382
- }
383
- fs2.mkdirSync(targetDir, { recursive: true });
384
- if (options.showProgress && !options.quiet) {
385
- console.log(`Installing ${files.length} file${files.length > 1 ? "s" : ""} to ${targetDir.replace(`${process.cwd()}/`, "")}`);
386
- console.log("");
387
- }
388
- if (options.dryRun) {
389
- if (!options.quiet) {
390
- console.log("✓ Dry run completed - no files were modified");
391
- }
392
- return results;
393
- }
394
- const processPromises = files.map(async (file) => {
395
- const sourcePath = path4.join(sourceDir, file);
396
- const targetPath = path4.join(targetDir, file);
397
- const targetFileDir = path4.dirname(targetPath);
398
- if (!fs2.existsSync(targetFileDir)) {
399
- fs2.mkdirSync(targetFileDir, { recursive: true });
400
- }
401
- const localInfo = await getLocalFileInfo(targetPath);
402
- let content = fs2.readFileSync(sourcePath, "utf8");
403
- content = await transform(content, file);
404
- const localProcessed = localInfo ? await transform(localInfo.content, file) : "";
405
- const contentChanged = !localInfo || localProcessed !== content;
406
- if (contentChanged) {
407
- fs2.writeFileSync(targetPath, content, "utf8");
408
- results.push({
409
- file,
410
- status: localInfo ? "updated" : "added",
411
- action: localInfo ? "Updated" : "Created"
412
- });
413
- } else {
414
- results.push({
415
- file,
416
- status: "current",
417
- action: "Already current"
418
- });
419
- }
420
- });
421
- await Promise.all(processPromises);
422
- return results;
423
- }
424
- async function appendToFile(sourceDir, targetFile, transform, options = {}) {
425
- const files = await collectSourceFiles(sourceDir, options.extension || ".md");
426
- if (files.length === 0) {
427
- return;
428
- }
429
- if (options.dryRun) {
430
- if (!options.quiet) {
431
- console.log(`Dry run: Would append ${files.length} file${files.length > 1 ? "s" : ""} to ${targetFile.replace(`${process.cwd()}/`, "")}`);
432
- }
433
- return;
434
- }
435
- let existingContent = "";
436
- if (fs2.existsSync(targetFile)) {
437
- existingContent = fs2.readFileSync(targetFile, "utf8");
438
- }
439
- let appendContent = "";
440
- for (const file of files) {
441
- const sourcePath = path4.join(sourceDir, file);
442
- let content = fs2.readFileSync(sourcePath, "utf8");
443
- content = await transform(content, file);
444
- appendContent += `${content}
445
-
446
- `;
447
- }
448
- fs2.writeFileSync(targetFile, existingContent + appendContent, "utf8");
449
- if (options.showProgress && !options.quiet) {
450
- console.log(`Appended ${files.length} file${files.length > 1 ? "s" : ""} to ${targetFile.replace(`${process.cwd()}/`, "")}`);
451
- }
452
- }
453
- async function installFile(sourceFile, targetFile, transform, options = {}) {
454
- if (options.dryRun) {
455
- if (!options.quiet) {
456
- console.log(`Dry run: Would install file to ${targetFile.replace(`${process.cwd()}/`, "")}`);
457
- }
458
- return;
459
- }
460
- if (!fs2.existsSync(sourceFile)) {
461
- if (!options.quiet) {
462
- console.warn(`Source file not found: ${sourceFile}`);
463
- }
464
- return;
465
- }
466
- const targetDir = path4.dirname(targetFile);
467
- fs2.mkdirSync(targetDir, { recursive: true });
468
- const localInfo = await getLocalFileInfo(targetFile);
469
- let content = fs2.readFileSync(sourceFile, "utf8");
470
- content = await transform(content);
471
- if (localInfo && localInfo.content === content) {
472
- if (!options.quiet) {
473
- console.log(`File already current: ${targetFile.replace(`${process.cwd()}/`, "")}`);
474
- }
475
- return;
476
- }
477
- fs2.writeFileSync(targetFile, content, "utf8");
478
- if (options.showProgress && !options.quiet) {
479
- const action = localInfo ? "Updated" : "Created";
480
- console.log(`${action} file: ${targetFile.replace(`${process.cwd()}/`, "")}`);
481
- }
482
- }
483
-
484
- class FileInstaller {
485
- async installToDirectory(sourceDir, targetDir, transform, options = {}) {
486
- return installToDirectory(sourceDir, targetDir, transform, options);
487
- }
488
- async appendToFile(sourceDir, targetFile, transform, options = {}) {
489
- return appendToFile(sourceDir, targetFile, transform, options);
490
- }
491
- async installFile(sourceFile, targetFile, transform, options = {}) {
492
- return installFile(sourceFile, targetFile, transform, options);
493
- }
494
- }
495
-
496
- // src/core/installers/mcp-installer.ts
497
- import chalk2 from "chalk";
498
- import inquirer2 from "inquirer";
499
- import ora2 from "ora";
500
-
501
- // src/services/mcp-service.ts
502
- import chalk from "chalk";
503
- import inquirer from "inquirer";
504
- import ora from "ora";
505
-
506
- // src/types/mcp.types.ts
507
- var isStdioConfig = (config) => config.type === "stdio";
508
- var isCLICommandConfig = (config) => isStdioConfig(config);
509
- // src/services/mcp-service.ts
510
- function resolveConfig(config) {
511
- if (typeof config === "function") {
512
- const result = config();
513
- return Promise.resolve(result);
514
- }
515
- return Promise.resolve(config);
516
- }
517
- var createMCPService = (deps) => {
518
- const getAllServerIds = () => {
519
- return Object.keys(MCP_SERVER_REGISTRY);
520
- };
521
- const getAvailableServers = async () => {
522
- const existingServerIds = await getInstalledServerIds();
523
- return getAllServerIds().filter((id) => !existingServerIds.includes(id));
524
- };
525
- const getInstalledServerIds = async () => {
526
- try {
527
- const configData = await deps.target.readConfig(process.cwd());
528
- const mcpConfigPath = deps.target.config.mcpConfigPath;
529
- const existingMcpSection = getNestedProperty(configData, mcpConfigPath) || {};
530
- const existingServerNames = Object.keys(existingMcpSection);
531
- return Object.values(MCP_SERVER_REGISTRY).filter((server) => existingServerNames.includes(server.name)).map((server) => server.id);
532
- } catch (_error) {
533
- return [];
534
- }
535
- };
536
- const getRequiringConfiguration = (serverIds) => {
537
- return serverIds.filter((id) => {
538
- const server = MCP_SERVER_REGISTRY[id];
539
- return !!server.envVars;
540
- });
541
- };
542
- const validateServer = (serverId, envValues) => {
543
- const server = MCP_SERVER_REGISTRY[serverId];
544
- if (!server?.envVars) {
545
- return { isValid: true, missingRequired: [], invalidValues: [] };
546
- }
547
- const missingRequired = [];
548
- const invalidValues = [];
549
- for (const [key, config] of Object.entries(server.envVars)) {
550
- const value = envValues[key] || process.env[key] || "";
551
- if (config.required && (!value || value === "")) {
552
- missingRequired.push(key);
553
- }
554
- }
555
- return {
556
- isValid: missingRequired.length === 0 && invalidValues.length === 0,
557
- missingRequired,
558
- invalidValues
559
- };
560
- };
561
- const configureServer = async (serverId, collectedEnv = {}) => {
562
- const server = MCP_SERVER_REGISTRY[serverId];
563
- const values = {};
564
- if (!server.envVars) {
565
- return values;
566
- }
567
- console.log("");
568
- console.log(chalk.cyan(`▸ ${server.name}`));
569
- console.log(chalk.gray(` ${server.description}`));
570
- console.log("");
571
- for (const [key, config] of Object.entries(server.envVars)) {
572
- if (config.dependsOn) {
573
- const missingDeps = config.dependsOn.filter((dep) => !(collectedEnv[dep] || process.env[dep]));
574
- if (missingDeps.length > 0) {
575
- continue;
576
- }
577
- }
578
- const existingValue = collectedEnv[key] || process.env[key];
579
- let value;
580
- if (existingValue && existingValue.trim() !== "") {
581
- value = existingValue;
582
- } else if (config.fetchChoices) {
583
- const spinner = ora("Fetching options...").start();
584
- for (const [envKey, envValue] of Object.entries(collectedEnv)) {
585
- if (envValue) {
586
- process.env[envKey] = envValue;
587
- }
588
- }
589
- try {
590
- const choices = await config.fetchChoices();
591
- spinner.stop();
592
- const answer = await inquirer.prompt({
593
- type: "list",
594
- name: "value",
595
- message: `${key}${config.required ? " *" : ""}`,
596
- choices,
597
- default: config.default || choices[0]
598
- });
599
- value = answer.value;
600
- } catch (error) {
601
- const errorMsg = error instanceof Error ? error.message : String(error);
602
- spinner.fail(chalk.red(`Failed to fetch options: ${errorMsg}`));
603
- const answer = await inquirer.prompt({
604
- type: "input",
605
- name: "value",
606
- message: `${key}${config.required ? " *" : ""}`,
607
- default: config.default
608
- });
609
- value = answer.value;
610
- }
611
- } else if (key === "GEMINI_MODEL") {
612
- const answer = await inquirer.prompt({
613
- type: "list",
614
- name: "model",
615
- message: `${key}${config.required ? " *" : ""}`,
616
- choices: ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-1.5-flash", "gemini-1.5-pro"],
617
- default: config.default || "gemini-2.5-flash"
618
- });
619
- value = answer.model;
620
- } else {
621
- const answer = await inquirer.prompt({
622
- type: config.secret ? "password" : "input",
623
- name: "value",
624
- message: `${key}${config.required ? " *" : ""}`,
625
- default: config.default,
626
- mask: config.secret ? "•" : undefined
627
- });
628
- value = answer.value;
629
- }
630
- if (value) {
631
- values[key] = value;
632
- collectedEnv[key] = value;
633
- }
634
- }
635
- console.log(chalk.green("✓ Configured"));
636
- console.log("");
637
- return values;
638
- };
639
- const installServers = async (serverIds, serverConfigs = {}) => {
640
- if (serverIds.length === 0) {
641
- return;
642
- }
643
- try {
644
- const configData = await deps.target.readConfig(process.cwd());
645
- const mcpConfigPath = deps.target.config.mcpConfigPath;
646
- const existingMcpSection = getNestedProperty(configData, mcpConfigPath) || {};
647
- const mcpSection = { ...existingMcpSection };
648
- for (const serverId of serverIds) {
649
- const server = MCP_SERVER_REGISTRY[serverId];
650
- const configuredValues = serverConfigs[serverId] || {};
651
- let configToTransform = { ...server.config };
652
- let resolvedCommand;
653
- let resolvedArgs;
654
- if (isCLICommandConfig(server.config)) {
655
- resolvedCommand = server.config.command ? await resolveConfig(server.config.command) : undefined;
656
- resolvedArgs = server.config.args ? await resolveConfig(server.config.args) : [];
657
- if (serverId === "sylphx-flow" && Array.isArray(resolvedArgs)) {
658
- const targetConfig = deps.target.mcpServerConfig;
659
- if (targetConfig) {
660
- if (targetConfig.disableTime) {
661
- resolvedArgs.push("--disable-time");
662
- }
663
- if (targetConfig.disableKnowledge) {
664
- resolvedArgs.push("--disable-knowledge");
665
- }
666
- if (targetConfig.disableCodebase) {
667
- resolvedArgs.push("--disable-codebase");
668
- }
669
- }
670
- }
671
- }
672
- if (isCLICommandConfig(server.config)) {
673
- configToTransform = {
674
- ...server.config,
675
- command: resolvedCommand,
676
- args: resolvedArgs
677
- };
678
- }
679
- if (Object.keys(configuredValues).length > 0) {
680
- const serverConfigEnv = server.config.type === "local" ? server.config.environment : server.config.type === "stdio" ? server.config.env : {};
681
- const updatedEnv = { ...serverConfigEnv };
682
- for (const [key, value] of Object.entries(configuredValues)) {
683
- if (value && value.trim() !== "") {
684
- updatedEnv[key] = value;
685
- }
686
- }
687
- if (configToTransform.type === "local") {
688
- configToTransform.environment = updatedEnv;
689
- } else if (configToTransform.type === "stdio") {
690
- configToTransform.env = updatedEnv;
691
- }
692
- }
693
- const transformedConfig = deps.target.transformMCPConfig(configToTransform, serverId);
694
- mcpSection[server.name] = transformedConfig;
695
- }
696
- setNestedProperty(configData, mcpConfigPath, mcpSection);
697
- await deps.target.writeConfig(process.cwd(), configData);
698
- if (deps.target.approveMCPServers) {
699
- const serverNames = serverIds.map((id) => MCP_SERVER_REGISTRY[id].name);
700
- await deps.target.approveMCPServers(process.cwd(), serverNames);
701
- }
702
- } catch (error) {
703
- throw new Error(`Failed to install MCP servers: ${error instanceof Error ? error.message : String(error)}`);
704
- }
705
- };
706
- const readConfig = async () => {
707
- try {
708
- return await deps.target.readConfig(process.cwd());
709
- } catch (_error) {
710
- return { settings: {} };
711
- }
712
- };
713
- const writeConfig = async (configData) => {
714
- await deps.target.writeConfig(process.cwd(), configData);
715
- };
716
- const getNestedProperty = (obj, path5) => {
717
- return path5.split(".").reduce((current, key) => {
718
- if (typeof current === "object" && current !== null) {
719
- return current[key];
720
- }
721
- return;
722
- }, obj);
723
- };
724
- const setNestedProperty = (obj, path5, value) => {
725
- const keys = path5.split(".");
726
- const lastKey = keys.pop();
727
- if (!lastKey) {
728
- return;
729
- }
730
- const target = keys.reduce((current, key) => {
731
- if (!current[key]) {
732
- current[key] = {};
733
- }
734
- return current[key];
735
- }, obj);
736
- target[lastKey] = value;
737
- };
738
- return {
739
- getAllServerIds,
740
- getAvailableServers,
741
- getInstalledServerIds,
742
- getRequiringConfiguration,
743
- validateServer,
744
- configureServer,
745
- installServers,
746
- readConfig,
747
- writeConfig
748
- };
749
- };
750
-
751
- // src/core/installers/mcp-installer.ts
752
- function createMCPInstaller(target) {
753
- const mcpService = createMCPService({ target });
754
- const selectServers = async (options = {}) => {
755
- const allServers = mcpService.getAllServerIds();
756
- const installedServers = await mcpService.getInstalledServerIds();
757
- if (!options.quiet) {
758
- console.log(chalk2.cyan.bold(`━━━ Configure MCP Tools ━━━
759
- `));
760
- }
761
- const serverSelectionAnswer = await inquirer2.prompt([
762
- {
763
- type: "checkbox",
764
- name: "selectedServers",
765
- message: "Select MCP tools to install:",
766
- choices: allServers.map((id) => {
767
- const server = MCP_SERVER_REGISTRY[id];
768
- const isInstalled = installedServers.includes(id);
769
- return {
770
- name: `${server.name} - ${server.description}`,
771
- value: id,
772
- checked: server.required || isInstalled || server.defaultInInit || false,
773
- disabled: server.required ? "(required)" : false
774
- };
775
- })
776
- }
777
- ]);
778
- let selectedServers = serverSelectionAnswer.selectedServers;
779
- const requiredServers = allServers.filter((id) => MCP_SERVER_REGISTRY[id].required);
780
- selectedServers = [...new Set([...requiredServers, ...selectedServers])];
781
- return selectedServers;
782
- };
783
- const configureServers = async (selectedServers, options = {}) => {
784
- const serversNeedingConfig = selectedServers.filter((id) => {
785
- const server = MCP_SERVER_REGISTRY[id];
786
- return server.envVars && Object.keys(server.envVars).length > 0;
787
- });
788
- const serverConfigsMap = {};
789
- if (serversNeedingConfig.length > 0) {
790
- if (!options.quiet) {
791
- console.log(chalk2.cyan.bold(`
792
- ━━━ Server Configuration ━━━
793
- `));
794
- }
795
- const collectedEnv = {};
796
- for (const serverId of serversNeedingConfig) {
797
- const configValues = await mcpService.configureServer(serverId, collectedEnv);
798
- serverConfigsMap[serverId] = configValues;
799
- }
800
- }
801
- return serverConfigsMap;
802
- };
803
- const installServers = async (selectedServers, serverConfigsMap, options = {}) => {
804
- if (selectedServers.length === 0) {
805
- return;
806
- }
807
- const spinner = options.quiet ? null : ora2({
808
- text: `Installing ${selectedServers.length} MCP server${selectedServers.length > 1 ? "s" : ""}`,
809
- color: "cyan"
810
- }).start();
811
- try {
812
- await mcpService.installServers(selectedServers, serverConfigsMap);
813
- if (spinner) {
814
- spinner.succeed(chalk2.green(`Installed ${chalk2.cyan(selectedServers.length)} MCP server${selectedServers.length > 1 ? "s" : ""}`));
815
- }
816
- } catch (error) {
817
- if (spinner) {
818
- spinner.fail(chalk2.red("Failed to install MCP servers"));
819
- }
820
- throw error;
821
- }
822
- };
823
- const setupMCP = async (options = {}) => {
824
- const selectedServers = await selectServers(options);
825
- if (selectedServers.length === 0) {
826
- return { selectedServers: [], serverConfigsMap: {} };
827
- }
828
- const serverConfigsMap = await configureServers(selectedServers, options);
829
- if (!options.dryRun) {
830
- await installServers(selectedServers, serverConfigsMap, options);
831
- }
832
- return { selectedServers, serverConfigsMap };
833
- };
834
- return {
835
- selectServers,
836
- configureServers,
837
- installServers,
838
- setupMCP
839
- };
840
- }
841
-
842
- class MCPInstaller {
843
- installer;
844
- constructor(target) {
845
- this.installer = createMCPInstaller(target);
846
- }
847
- async selectServers(options = {}) {
848
- return this.installer.selectServers(options);
849
- }
850
- async configureServers(selectedServers, options = {}) {
851
- return this.installer.configureServers(selectedServers, options);
852
- }
853
- async installServers(selectedServers, serverConfigsMap, options = {}) {
854
- return this.installer.installServers(selectedServers, serverConfigsMap, options);
855
- }
856
- async setupMCP(options = {}) {
857
- return this.installer.setupMCP(options);
858
- }
859
- }
860
-
861
- // src/utils/secret-utils.ts
862
- import fs3 from "node:fs/promises";
863
- import path5 from "node:path";
864
- var secretUtils = {
865
- getSecretsDir(cwd) {
866
- return path5.join(cwd, ".secrets");
867
- },
868
- async ensureSecretsDir(cwd) {
869
- const secretsDir = secretUtils.getSecretsDir(cwd);
870
- await fs3.mkdir(secretsDir, { recursive: true });
871
- },
872
- async writeSecret(cwd, key, value) {
873
- await secretUtils.ensureSecretsDir(cwd);
874
- const secretFile = path5.join(".secrets", key);
875
- const secretPath = path5.join(cwd, secretFile);
876
- await fs3.writeFile(secretPath, value.trim(), "utf8");
877
- return secretFile;
878
- },
879
- async readSecret(cwd, secretFile) {
880
- const secretPath = path5.resolve(cwd, secretFile);
881
- try {
882
- return await fs3.readFile(secretPath, "utf8");
883
- } catch (error) {
884
- throw new Error(`Failed to read secret file ${secretFile}: ${error}`);
885
- }
886
- },
887
- toFileReference(key) {
888
- return `{file:.secrets/${key}}`;
889
- },
890
- isFileReference(value) {
891
- return value.startsWith("{file:") && value.endsWith("}");
892
- },
893
- extractFilePath(reference) {
894
- if (!secretUtils.isFileReference(reference)) {
895
- throw new Error(`Invalid file reference: ${reference}`);
896
- }
897
- return reference.slice(6, -1);
898
- },
899
- async resolveFileReferences(cwd, obj) {
900
- if (typeof obj === "string" && secretUtils.isFileReference(obj)) {
901
- const filePath = secretUtils.extractFilePath(obj);
902
- return await secretUtils.readSecret(cwd, filePath);
903
- }
904
- if (Array.isArray(obj)) {
905
- return Promise.all(obj.map((item) => secretUtils.resolveFileReferences(cwd, item)));
906
- }
907
- if (obj && typeof obj === "object") {
908
- const resolved = {};
909
- for (const [key, value] of Object.entries(obj)) {
910
- resolved[key] = await secretUtils.resolveFileReferences(cwd, value);
911
- }
912
- return resolved;
913
- }
914
- return obj;
915
- },
916
- async convertSecretsToFileReferences(cwd, envVars) {
917
- const result = {};
918
- for (const [key, value] of Object.entries(envVars)) {
919
- if (value && !secretUtils.isFileReference(value)) {
920
- const _secretFile = await secretUtils.writeSecret(cwd, key, value);
921
- result[key] = secretUtils.toFileReference(key);
922
- } else {
923
- result[key] = value;
924
- }
925
- }
926
- return result;
927
- },
928
- async saveSecrets(cwd, secrets) {
929
- for (const [key, value] of Object.entries(secrets)) {
930
- await secretUtils.writeSecret(cwd, key, value);
931
- }
932
- },
933
- async loadSecrets(cwd) {
934
- const secretsDir = secretUtils.getSecretsDir(cwd);
935
- const secrets = {};
936
- try {
937
- const files = await fs3.readdir(secretsDir);
938
- for (const file of files) {
939
- const filePath = path5.join(secretsDir, file);
940
- const stat = await fs3.stat(filePath);
941
- if (stat.isFile()) {
942
- const content = await fs3.readFile(filePath, "utf8");
943
- secrets[file] = content.trim();
944
- }
945
- }
946
- } catch (_error) {}
947
- return secrets;
948
- },
949
- async addToGitignore(cwd) {
950
- const gitignorePath = path5.join(cwd, ".gitignore");
951
- try {
952
- let gitignoreContent = "";
953
- try {
954
- gitignoreContent = await fs3.readFile(gitignorePath, "utf8");
955
- } catch {}
956
- const lines = gitignoreContent.split(`
957
- `).map((line) => line.trim());
958
- if (!lines.includes(".secrets") && !lines.includes(".secrets/")) {
959
- gitignoreContent += `${gitignoreContent && !gitignoreContent.endsWith(`
960
- `) ? `
961
- ` : ""}.secrets/
962
- `;
963
- await fs3.writeFile(gitignorePath, gitignoreContent, "utf8");
964
- }
965
- } catch (error) {
966
- console.warn("Warning: Could not update .gitignore:", error);
967
- }
968
- }
969
- };
970
-
971
- // src/utils/error-handler.ts
972
- class CLIError extends Error {
973
- code;
974
- constructor(message, code) {
975
- super(message);
976
- this.code = code;
977
- this.name = "CLIError";
978
- }
979
- }
980
-
981
- // src/targets/opencode.ts
982
- var opencodeTarget = {
983
- id: "opencode",
984
- name: "OpenCode",
985
- description: "OpenCode IDE with YAML front matter agents (.opencode/agent/*.md)",
986
- category: "ide",
987
- isImplemented: true,
988
- isDefault: true,
989
- mcpServerConfig: {
990
- disableTime: false,
991
- disableKnowledge: false,
992
- disableCodebase: false
993
- },
994
- config: {
995
- agentDir: ".opencode/agent",
996
- agentExtension: ".md",
997
- agentFormat: "yaml-frontmatter",
998
- stripYaml: false,
999
- flatten: false,
1000
- configFile: "opencode.jsonc",
1001
- configSchema: "https://opencode.ai/config.json",
1002
- mcpConfigPath: "mcp",
1003
- rulesFile: "AGENTS.md",
1004
- outputStylesDir: undefined,
1005
- slashCommandsDir: ".opencode/command",
1006
- installation: {
1007
- createAgentDir: true,
1008
- createConfigFile: true,
1009
- useSecretFiles: true
1010
- }
1011
- },
1012
- async transformAgentContent(content, metadata, _sourcePath) {
1013
- const { metadata: existingMetadata, content: baseContent } = await yamlUtils.extractFrontMatter(content);
1014
- const { name, mode, rules, ...cleanMetadata } = existingMetadata;
1015
- if (metadata) {
1016
- const { name: additionalName, mode: additionalMode, rules: additionalRules, ...additionalCleanMetadata } = metadata;
1017
- const mergedMetadata = { ...cleanMetadata, ...additionalCleanMetadata };
1018
- return yamlUtils.addFrontMatter(baseContent, mergedMetadata);
1019
- }
1020
- return yamlUtils.addFrontMatter(baseContent, cleanMetadata);
1021
- },
1022
- transformMCPConfig(config, _serverId) {
1023
- if (config.type === "stdio") {
1024
- const openCodeConfig = {
1025
- type: "local",
1026
- command: [config.command]
1027
- };
1028
- if (config.args && config.args.length > 0) {
1029
- openCodeConfig.command.push(...config.args);
1030
- }
1031
- if (config.env) {
1032
- openCodeConfig.environment = config.env;
1033
- }
1034
- return openCodeConfig;
1035
- }
1036
- if (config.type === "http") {
1037
- return {
1038
- type: "remote",
1039
- url: config.url,
1040
- ...config.headers && { headers: config.headers }
1041
- };
1042
- }
1043
- if (config.type === "local" || config.type === "remote") {
1044
- return config;
1045
- }
1046
- return config;
1047
- },
1048
- getConfigPath: (cwd) => Promise.resolve(fileUtils.getConfigPath(opencodeTarget.config, cwd)),
1049
- async readConfig(cwd) {
1050
- const config = await fileUtils.readConfig(opencodeTarget.config, cwd);
1051
- const resolvedConfig = await secretUtils.resolveFileReferences(cwd, config);
1052
- if (!resolvedConfig.mcp) {
1053
- resolvedConfig.mcp = {};
1054
- }
1055
- return resolvedConfig;
1056
- },
1057
- async writeConfig(cwd, config) {
1058
- if (!config.mcp) {
1059
- config.mcp = {};
1060
- }
1061
- if (opencodeTarget.config.installation?.useSecretFiles) {
1062
- for (const [serverId, serverConfig] of Object.entries(config.mcp || {})) {
1063
- if (serverConfig && typeof serverConfig === "object" && "environment" in serverConfig) {
1064
- const envVars = serverConfig.environment;
1065
- if (envVars && typeof envVars === "object") {
1066
- const serverDef = Object.values(MCP_SERVER_REGISTRY).find((s) => s.name === serverId);
1067
- if (serverDef?.envVars) {
1068
- const secretEnvVars = {};
1069
- const nonSecretEnvVars = {};
1070
- for (const [key, value] of Object.entries(envVars)) {
1071
- const envConfig = serverDef.envVars[key];
1072
- if (envConfig?.secret && value && !secretUtils.isFileReference(value)) {
1073
- secretEnvVars[key] = value;
1074
- } else {
1075
- nonSecretEnvVars[key] = value;
1076
- }
1077
- }
1078
- const convertedSecrets = await secretUtils.convertSecretsToFileReferences(cwd, secretEnvVars);
1079
- serverConfig.environment = { ...nonSecretEnvVars, ...convertedSecrets };
1080
- }
1081
- }
1082
- }
1083
- }
1084
- }
1085
- await fileUtils.writeConfig(opencodeTarget.config, cwd, config);
1086
- },
1087
- validateRequirements: (cwd) => fileUtils.validateRequirements(opencodeTarget.config, cwd),
1088
- getHelpText() {
1089
- let help = generateHelpText(opencodeTarget.config);
1090
- help += `OpenCode-Specific Information:
1091
- `;
1092
- help += ` Configuration File: opencode.jsonc
1093
- `;
1094
- help += ` Schema: https://opencode.ai/config.json
1095
- `;
1096
- help += ` Agent Format: Markdown with YAML front matter
1097
- `;
1098
- help += ` MCP Integration: Automatic server discovery
1099
-
1100
- `;
1101
- help += `Example Agent Structure:
1102
- `;
1103
- help += ` ---
1104
- `;
1105
- help += ` name: "My Agent"
1106
- `;
1107
- help += ` description: "Agent description"
1108
- `;
1109
- help += ` ---
1110
-
1111
- `;
1112
- help += ` Agent content here...
1113
-
1114
- `;
1115
- return help;
1116
- },
1117
- detectFromEnvironment() {
1118
- try {
1119
- const cwd = process.cwd();
1120
- return fs4.existsSync(path6.join(cwd, "opencode.jsonc"));
1121
- } catch {
1122
- return false;
1123
- }
1124
- },
1125
- async transformRulesContent(content) {
1126
- return yamlUtils.stripFrontMatter(content);
1127
- },
1128
- async setupAgents(cwd, options) {
1129
- const oldCommandsDir = path6.join(cwd, ".opencode/commands");
1130
- try {
1131
- await fs4.rm(oldCommandsDir, { recursive: true, force: true });
1132
- } catch {}
1133
- const installer = new FileInstaller;
1134
- const agentsDir = path6.join(cwd, this.config.agentDir);
1135
- const results = await installer.installToDirectory(getAgentsDir(), agentsDir, async (content, sourcePath) => {
1136
- return await this.transformAgentContent(content, undefined, sourcePath);
1137
- }, {
1138
- ...options,
1139
- showProgress: false
1140
- });
1141
- return { count: results.length };
1142
- },
1143
- async setupOutputStyles(cwd, _options) {
1144
- if (!this.config.rulesFile) {
1145
- return { count: 0 };
1146
- }
1147
- const rulesFilePath = path6.join(cwd, this.config.rulesFile);
1148
- let existingContent = "";
1149
- if (fs4.existsSync(rulesFilePath)) {
1150
- existingContent = fs4.readFileSync(rulesFilePath, "utf8");
1151
- }
1152
- const outputStylesSourceDir = getOutputStylesDir();
1153
- if (!fs4.existsSync(outputStylesSourceDir)) {
1154
- return { count: 0 };
1155
- }
1156
- let outputStylesContent = `
1157
-
1158
- ---
1159
-
1160
- # Output Styles
1161
-
1162
- `;
1163
- const files = fs4.readdirSync(outputStylesSourceDir, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name);
1164
- if (files.length === 0) {
1165
- return { count: 0 };
1166
- }
1167
- for (const styleFile of files) {
1168
- const sourcePath = path6.join(outputStylesSourceDir, styleFile);
1169
- let content = fs4.readFileSync(sourcePath, "utf8");
1170
- if (this.transformRulesContent) {
1171
- content = await this.transformRulesContent(content);
1172
- }
1173
- outputStylesContent += `${content}
1174
-
1175
- `;
1176
- }
1177
- const outputStylesMarker = "# Output Styles";
1178
- if (existingContent.includes(outputStylesMarker)) {
1179
- const startIndex = existingContent.indexOf(`---
1180
-
1181
- # Output Styles`);
1182
- if (startIndex !== -1) {
1183
- existingContent = existingContent.substring(0, startIndex);
1184
- }
1185
- }
1186
- fs4.writeFileSync(rulesFilePath, existingContent + outputStylesContent, "utf8");
1187
- return { count: files.length };
1188
- },
1189
- async setupRules(cwd, options) {
1190
- if (!this.config.rulesFile) {
1191
- return { count: 0 };
1192
- }
1193
- if (!ruleFileExists("core")) {
1194
- throw new Error("Core rules file not found");
1195
- }
1196
- const installer = new FileInstaller;
1197
- const rulesDestPath = path6.join(cwd, this.config.rulesFile);
1198
- const rulePath = getRulesPath("core");
1199
- await installer.installFile(rulePath, rulesDestPath, async (content) => {
1200
- if (this.transformRulesContent) {
1201
- return await this.transformRulesContent(content);
1202
- }
1203
- return content;
1204
- }, {
1205
- ...options,
1206
- showProgress: false
1207
- });
1208
- return { count: 1 };
1209
- },
1210
- async setupMCP(cwd, options) {
1211
- if (!options.dryRun) {
1212
- await secretUtils.ensureSecretsDir(cwd);
1213
- await secretUtils.addToGitignore(cwd);
1214
- }
1215
- const installer = new MCPInstaller(this);
1216
- const result = await installer.setupMCP({ ...options, quiet: true });
1217
- return { count: result.selectedServers.length };
1218
- },
1219
- async setupSlashCommands(cwd, options) {
1220
- if (!this.config.slashCommandsDir) {
1221
- return { count: 0 };
1222
- }
1223
- const oldCommandsDir = path6.join(cwd, ".opencode/commands");
1224
- try {
1225
- await fs4.rm(oldCommandsDir, { recursive: true, force: true });
1226
- } catch {}
1227
- const installer = new FileInstaller;
1228
- const slashCommandsDir = path6.join(cwd, this.config.slashCommandsDir);
1229
- const results = await installer.installToDirectory(getSlashCommandsDir(), slashCommandsDir, async (content) => {
1230
- return content;
1231
- }, {
1232
- ...options,
1233
- showProgress: false
1234
- });
1235
- return { count: results.length };
1236
- },
1237
- async executeCommand(systemPrompt, userPrompt, options = {}) {
1238
- if (options.dryRun) {
1239
- const dryRunArgs = ["opencode"];
1240
- if (options.print) {
1241
- dryRunArgs.push("run");
1242
- if (options.continue) {
1243
- dryRunArgs.push("-c");
1244
- }
1245
- if (userPrompt && userPrompt.trim() !== "") {
1246
- dryRunArgs.push(`"${userPrompt}"`);
1247
- }
1248
- } else {
1249
- if (options.agent) {
1250
- dryRunArgs.push("--agent", options.agent);
1251
- }
1252
- if (userPrompt && userPrompt.trim() !== "") {
1253
- dryRunArgs.push("-p", `"${userPrompt}"`);
1254
- }
1255
- }
1256
- console.log(chalk3.cyan("Dry run - Would execute:"));
1257
- console.log(chalk3.bold(dryRunArgs.join(" ")));
1258
- console.log(chalk3.dim(`Agent: ${options.agent || "coder"}`));
1259
- console.log(chalk3.dim(`User prompt: ${userPrompt.length} characters`));
1260
- console.log("✓ Dry run completed successfully");
1261
- return;
1262
- }
1263
- try {
1264
- const { spawn } = await import("node:child_process");
1265
- const args = [];
1266
- if (options.print) {
1267
- args.push("run");
1268
- if (options.continue) {
1269
- args.push("-c");
1270
- }
1271
- if (userPrompt && userPrompt.trim() !== "") {
1272
- args.push(userPrompt);
1273
- }
1274
- } else {
1275
- if (options.agent) {
1276
- args.push("--agent", options.agent);
1277
- }
1278
- if (userPrompt && userPrompt.trim() !== "") {
1279
- args.push("-p", userPrompt);
1280
- }
1281
- }
1282
- if (options.verbose || options.print) {
1283
- console.log(chalk3.dim(`$ opencode ${args.join(" ")}`));
1284
- }
1285
- await new Promise((resolve, reject) => {
1286
- const child = spawn("opencode", args, {
1287
- stdio: options.print ? ["ignore", "inherit", "inherit"] : "inherit",
1288
- shell: true,
1289
- env: process.env
1290
- });
1291
- child.on("spawn", () => {
1292
- if (options.verbose) {
1293
- console.log("✓ OpenCode process started");
1294
- }
1295
- });
1296
- child.on("error", (error) => {
1297
- console.error("✗ Error spawning OpenCode:", error);
1298
- reject(error);
1299
- });
1300
- child.on("close", (code) => {
1301
- if (code !== 0) {
1302
- reject(new Error(`OpenCode exited with code ${code}`));
1303
- } else {
1304
- resolve();
1305
- }
1306
- });
1307
- });
1308
- } catch (error) {
1309
- if (error instanceof Error) {
1310
- throw new CLIError(`Failed to execute OpenCode: ${error.message}`, "OPENCODE_ERROR");
1311
- }
1312
- throw error;
1313
- }
1314
- }
1315
- };
1316
- export {
1317
- opencodeTarget
1318
- };
1319
-
1320
- export { secretUtils, CLIError, FileInstaller, MCPInstaller, opencodeTarget };