@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,529 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { getRulesPath, ruleFileExists } from '../config/rules.js';
5
+ import { MCP_SERVER_REGISTRY } from '../config/servers.js';
6
+ import { FileInstaller } from '../core/installers/file-installer.js';
7
+ import { MCPInstaller } from '../core/installers/mcp-installer.js';
8
+ import type { AgentMetadata } from '../types/target-config.types.js';
9
+ import type { CommonOptions, MCPServerConfigUnion, SetupResult, Target } from '../types.js';
10
+ import { getAgentsDir, getOutputStylesDir, getSlashCommandsDir } from '../utils/paths.js';
11
+ import { secretUtils } from '../utils/secret-utils.js';
12
+ import { fileUtils, generateHelpText, yamlUtils } from '../utils/target-utils.js';
13
+ import { CLIError } from '../utils/error-handler.js';
14
+
15
+ /**
16
+ * OpenCode target - composition approach with all original functionality
17
+ */
18
+ export const opencodeTarget: Target = {
19
+ id: 'opencode',
20
+ name: 'OpenCode',
21
+ description: 'OpenCode IDE with YAML front matter agents (.opencode/agent/*.md)',
22
+ category: 'ide',
23
+ isImplemented: true,
24
+ isDefault: true,
25
+
26
+ mcpServerConfig: {
27
+ disableTime: false,
28
+ disableKnowledge: false,
29
+ disableCodebase: false,
30
+ },
31
+
32
+ config: {
33
+ agentDir: '.opencode/agent',
34
+ agentExtension: '.md',
35
+ agentFormat: 'yaml-frontmatter',
36
+ stripYaml: false,
37
+ flatten: false,
38
+ configFile: 'opencode.jsonc',
39
+ configSchema: 'https://opencode.ai/config.json',
40
+ mcpConfigPath: 'mcp',
41
+ rulesFile: 'AGENTS.md',
42
+ outputStylesDir: undefined, // OpenCode doesn't support output styles as separate files
43
+ slashCommandsDir: '.opencode/command', // OpenCode uses singular 'command', not 'commands'
44
+ installation: {
45
+ createAgentDir: true,
46
+ createConfigFile: true,
47
+ useSecretFiles: true,
48
+ },
49
+ },
50
+
51
+ /**
52
+ * Transform agent content for OpenCode
53
+ * OpenCode uses YAML front matter, but removes name field as it doesn't use it
54
+ */
55
+ async transformAgentContent(
56
+ content: string,
57
+ metadata?: AgentMetadata,
58
+ _sourcePath?: string
59
+ ): Promise<string> {
60
+ // For OpenCode, we preserve YAML front matter but remove unsupported fields
61
+ const { metadata: existingMetadata, content: baseContent } =
62
+ await yamlUtils.extractFrontMatter(content);
63
+
64
+ // Remove fields that OpenCode doesn't support:
65
+ // - name: not used by OpenCode
66
+ // - mode: OpenCode doesn't support 'both' mode (only 'primary')
67
+ // - rules: OpenCode doesn't use rule references
68
+ const { name, mode, rules, ...cleanMetadata } = existingMetadata;
69
+
70
+ // If additional metadata is provided, merge it (but exclude unsupported fields)
71
+ if (metadata) {
72
+ const { name: additionalName, mode: additionalMode, rules: additionalRules, ...additionalCleanMetadata } = metadata;
73
+ const mergedMetadata = { ...cleanMetadata, ...additionalCleanMetadata };
74
+ return yamlUtils.addFrontMatter(baseContent, mergedMetadata);
75
+ }
76
+
77
+ // Return content with only OpenCode-supported fields
78
+ return yamlUtils.addFrontMatter(baseContent, cleanMetadata);
79
+ },
80
+
81
+ /**
82
+ * Transform MCP server configuration for OpenCode
83
+ * Convert from Claude Code's optimal format to OpenCode's format
84
+ */
85
+ transformMCPConfig(config: MCPServerConfigUnion, _serverId?: string): Record<string, unknown> {
86
+ // Handle new Claude Code stdio format
87
+ if (config.type === 'stdio') {
88
+ // Convert Claude Code format to OpenCode format
89
+ const openCodeConfig: Record<string, unknown> = {
90
+ type: 'local',
91
+ command: [config.command],
92
+ };
93
+
94
+ if (config.args && config.args.length > 0) {
95
+ openCodeConfig.command.push(...config.args);
96
+ }
97
+
98
+ if (config.env) {
99
+ openCodeConfig.environment = config.env;
100
+ }
101
+
102
+ return openCodeConfig;
103
+ }
104
+
105
+ // Handle new Claude Code http format
106
+ if (config.type === 'http') {
107
+ // Claude Code http format is compatible with OpenCode remote format
108
+ return {
109
+ type: 'remote',
110
+ url: config.url,
111
+ ...(config.headers && { headers: config.headers }),
112
+ };
113
+ }
114
+
115
+ // Handle legacy OpenCode formats (pass through)
116
+ if (config.type === 'local' || config.type === 'remote') {
117
+ return config;
118
+ }
119
+
120
+ return config;
121
+ },
122
+
123
+ getConfigPath: (cwd: string) =>
124
+ Promise.resolve(fileUtils.getConfigPath(opencodeTarget.config, cwd)),
125
+
126
+ /**
127
+ * Read OpenCode configuration with structure normalization
128
+ */
129
+ async readConfig(cwd: string): Promise<any> {
130
+ const config = await fileUtils.readConfig(opencodeTarget.config, cwd);
131
+
132
+ // Resolve any file references in the configuration
133
+ const resolvedConfig = await secretUtils.resolveFileReferences(cwd, config);
134
+
135
+ // Ensure the config has the expected structure
136
+ if (!resolvedConfig.mcp) {
137
+ resolvedConfig.mcp = {};
138
+ }
139
+
140
+ return resolvedConfig;
141
+ },
142
+
143
+ /**
144
+ * Write OpenCode configuration with structure normalization
145
+ */
146
+ async writeConfig(cwd: string, config: Record<string, unknown>): Promise<void> {
147
+ // Ensure the config has the expected structure for OpenCode
148
+ if (!config.mcp) {
149
+ config.mcp = {};
150
+ }
151
+
152
+ // Convert secrets to file references if secret files are enabled
153
+ if (opencodeTarget.config.installation?.useSecretFiles) {
154
+ // Process each MCP server's environment variables
155
+ for (const [serverId, serverConfig] of Object.entries(config.mcp || {})) {
156
+ if (serverConfig && typeof serverConfig === 'object' && 'environment' in serverConfig) {
157
+ const envVars = serverConfig.environment as Record<string, string>;
158
+ if (envVars && typeof envVars === 'object') {
159
+ // Find the corresponding server definition to get secret env vars
160
+ const serverDef = Object.values(MCP_SERVER_REGISTRY).find((s) => s.name === serverId);
161
+ if (serverDef?.envVars) {
162
+ // Separate secret and non-secret variables
163
+ const secretEnvVars: Record<string, string> = {};
164
+ const nonSecretEnvVars: Record<string, string> = {};
165
+
166
+ for (const [key, value] of Object.entries(envVars)) {
167
+ const envConfig = serverDef.envVars[key];
168
+ if (envConfig?.secret && value && !secretUtils.isFileReference(value)) {
169
+ secretEnvVars[key] = value;
170
+ } else {
171
+ nonSecretEnvVars[key] = value;
172
+ }
173
+ }
174
+
175
+ // Convert only secret variables
176
+ const convertedSecrets = await secretUtils.convertSecretsToFileReferences(
177
+ cwd,
178
+ secretEnvVars
179
+ );
180
+
181
+ // Merge back
182
+ serverConfig.environment = { ...nonSecretEnvVars, ...convertedSecrets };
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ await fileUtils.writeConfig(opencodeTarget.config, cwd, config);
190
+ },
191
+
192
+ validateRequirements: (cwd: string) => fileUtils.validateRequirements(opencodeTarget.config, cwd),
193
+
194
+ /**
195
+ * Get detailed OpenCode-specific help text
196
+ */
197
+ getHelpText(): string {
198
+ let help = generateHelpText(opencodeTarget.config);
199
+
200
+ help += 'OpenCode-Specific Information:\n';
201
+ help += ' Configuration File: opencode.jsonc\n';
202
+ help += ' Schema: https://opencode.ai/config.json\n';
203
+ help += ' Agent Format: Markdown with YAML front matter\n';
204
+ help += ' MCP Integration: Automatic server discovery\n\n';
205
+
206
+ help += 'Example Agent Structure:\n';
207
+ help += ' ---\n';
208
+ help += ` name: "My Agent"\n`;
209
+ help += ` description: "Agent description"\n`;
210
+ help += ' ---\n\n';
211
+ help += ' Agent content here...\n\n';
212
+
213
+ return help;
214
+ },
215
+
216
+ /**
217
+ * Detect if this target is being used in the current environment
218
+ */
219
+ detectFromEnvironment(): boolean {
220
+ try {
221
+ const cwd = process.cwd();
222
+ return fs.existsSync(path.join(cwd, 'opencode.jsonc'));
223
+ } catch {
224
+ return false;
225
+ }
226
+ },
227
+
228
+ /**
229
+ * Transform rules content for OpenCode
230
+ * OpenCode doesn't need front matter in rules files (AGENTS.md)
231
+ */
232
+ async transformRulesContent(content: string): Promise<string> {
233
+ return yamlUtils.stripFrontMatter(content);
234
+ },
235
+
236
+ /**
237
+ * Setup agents for OpenCode
238
+ * Install agents to .opencode/agent/ directory
239
+ */
240
+ async setupAgents(cwd: string, options: CommonOptions): Promise<SetupResult> {
241
+ // Clean up old 'commands' directory if it exists (migration from old structure)
242
+ // This ensures OpenCode won't crash with ConfigDirectoryTypoError
243
+ const oldCommandsDir = path.join(cwd, '.opencode/commands');
244
+ try {
245
+ await fs.rm(oldCommandsDir, { recursive: true, force: true });
246
+ } catch {
247
+ // Ignore if doesn't exist
248
+ }
249
+
250
+ const installer = new FileInstaller();
251
+ const agentsDir = path.join(cwd, this.config.agentDir);
252
+
253
+ const results = await installer.installToDirectory(
254
+ getAgentsDir(),
255
+ agentsDir,
256
+ async (content, sourcePath) => {
257
+ return await this.transformAgentContent(content, undefined, sourcePath);
258
+ },
259
+ {
260
+ ...options,
261
+ showProgress: false, // UI handled by init-command
262
+ }
263
+ );
264
+
265
+ return { count: results.length };
266
+ },
267
+
268
+ /**
269
+ * Setup output styles for OpenCode
270
+ * Append output styles to AGENTS.md (OpenCode doesn't support separate output style files)
271
+ */
272
+ async setupOutputStyles(cwd: string, _options: CommonOptions): Promise<SetupResult> {
273
+ if (!this.config.rulesFile) {
274
+ return { count: 0 };
275
+ }
276
+
277
+ const rulesFilePath = path.join(cwd, this.config.rulesFile);
278
+
279
+ // Read existing rules file content if it exists
280
+ let existingContent = '';
281
+ if (fs.existsSync(rulesFilePath)) {
282
+ existingContent = fs.readFileSync(rulesFilePath, 'utf8');
283
+ }
284
+
285
+ // Build output styles section
286
+ const outputStylesSourceDir = getOutputStylesDir();
287
+ if (!fs.existsSync(outputStylesSourceDir)) {
288
+ return { count: 0 }; // No output styles available
289
+ }
290
+
291
+ let outputStylesContent = '\n\n---\n\n# Output Styles\n\n';
292
+
293
+ // Collect output style files
294
+ const files = fs
295
+ .readdirSync(outputStylesSourceDir, { withFileTypes: true })
296
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
297
+ .map((dirent) => dirent.name);
298
+
299
+ if (files.length === 0) {
300
+ return { count: 0 };
301
+ }
302
+
303
+ for (const styleFile of files) {
304
+ const sourcePath = path.join(outputStylesSourceDir, styleFile);
305
+ let content = fs.readFileSync(sourcePath, 'utf8');
306
+
307
+ // Strip YAML front matter for system prompt
308
+ if (this.transformRulesContent) {
309
+ content = await this.transformRulesContent(content);
310
+ }
311
+
312
+ outputStylesContent += `${content}\n\n`;
313
+ }
314
+
315
+ // Check if output styles section already exists
316
+ const outputStylesMarker = '# Output Styles';
317
+ if (existingContent.includes(outputStylesMarker)) {
318
+ // Replace existing output styles section
319
+ const startIndex = existingContent.indexOf('---\n\n# Output Styles');
320
+ if (startIndex !== -1) {
321
+ existingContent = existingContent.substring(0, startIndex);
322
+ }
323
+ }
324
+
325
+ // Append output styles section
326
+ fs.writeFileSync(rulesFilePath, existingContent + outputStylesContent, 'utf8');
327
+
328
+ return { count: files.length };
329
+ },
330
+
331
+ /**
332
+ * Setup rules for OpenCode
333
+ * Install rules to AGENTS.md in project root
334
+ */
335
+ async setupRules(cwd: string, options: CommonOptions): Promise<SetupResult> {
336
+ if (!this.config.rulesFile) {
337
+ return { count: 0 };
338
+ }
339
+
340
+ // Check if core rules file exists
341
+ if (!ruleFileExists('core')) {
342
+ throw new Error('Core rules file not found');
343
+ }
344
+
345
+ const installer = new FileInstaller();
346
+ const rulesDestPath = path.join(cwd, this.config.rulesFile);
347
+ const rulePath = getRulesPath('core');
348
+
349
+ await installer.installFile(
350
+ rulePath,
351
+ rulesDestPath,
352
+ async (content) => {
353
+ // Transform rules content if transformation is available
354
+ if (this.transformRulesContent) {
355
+ return await this.transformRulesContent(content);
356
+ }
357
+ return content;
358
+ },
359
+ {
360
+ ...options,
361
+ showProgress: false, // UI handled by init-command
362
+ }
363
+ );
364
+
365
+ return { count: 1 };
366
+ },
367
+
368
+ /**
369
+ * Setup MCP servers for OpenCode
370
+ * Select, configure, install, and setup secrets directory
371
+ */
372
+ async setupMCP(cwd: string, options: CommonOptions): Promise<SetupResult> {
373
+ // Setup secrets directory first (OpenCode-specific)
374
+ if (!options.dryRun) {
375
+ await secretUtils.ensureSecretsDir(cwd);
376
+ await secretUtils.addToGitignore(cwd);
377
+ }
378
+
379
+ // Install MCP servers
380
+ const installer = new MCPInstaller(this);
381
+ const result = await installer.setupMCP({ ...options, quiet: true });
382
+
383
+ return { count: result.selectedServers.length };
384
+ },
385
+
386
+ /**
387
+ * Setup slash commands for OpenCode
388
+ * Install slash command templates to .opencode/command/ directory
389
+ */
390
+ async setupSlashCommands(cwd: string, options: CommonOptions): Promise<SetupResult> {
391
+ if (!this.config.slashCommandsDir) {
392
+ return { count: 0 };
393
+ }
394
+
395
+ // Clean up old 'commands' directory if it exists (migration from old structure)
396
+ const oldCommandsDir = path.join(cwd, '.opencode/commands');
397
+ try {
398
+ await fs.rm(oldCommandsDir, { recursive: true, force: true });
399
+ } catch {
400
+ // Ignore if doesn't exist
401
+ }
402
+
403
+ const installer = new FileInstaller();
404
+ const slashCommandsDir = path.join(cwd, this.config.slashCommandsDir);
405
+
406
+ const results = await installer.installToDirectory(
407
+ getSlashCommandsDir(),
408
+ slashCommandsDir,
409
+ async (content) => {
410
+ // Slash commands are plain markdown with front matter - no transformation needed
411
+ return content;
412
+ },
413
+ {
414
+ ...options,
415
+ showProgress: false, // UI handled by init-command
416
+ }
417
+ );
418
+
419
+ return { count: results.length };
420
+ },
421
+
422
+ /**
423
+ * Execute OpenCode CLI
424
+ */
425
+ async executeCommand(
426
+ systemPrompt: string,
427
+ userPrompt: string,
428
+ options: { verbose?: boolean; dryRun?: boolean; print?: boolean; continue?: boolean; agent?: string } = {}
429
+ ): Promise<void> {
430
+ if (options.dryRun) {
431
+ // Build the command for display
432
+ const dryRunArgs = ['opencode'];
433
+ if (options.print) {
434
+ // Don't use --agent with 'run' (OpenCode bug)
435
+ dryRunArgs.push('run');
436
+ if (options.continue) {
437
+ dryRunArgs.push('-c');
438
+ }
439
+ if (userPrompt && userPrompt.trim() !== '') {
440
+ dryRunArgs.push(`"${userPrompt}"`);
441
+ }
442
+ } else {
443
+ if (options.agent) {
444
+ dryRunArgs.push('--agent', options.agent);
445
+ }
446
+ if (userPrompt && userPrompt.trim() !== '') {
447
+ dryRunArgs.push('-p', `"${userPrompt}"`);
448
+ }
449
+ }
450
+
451
+ console.log(chalk.cyan('Dry run - Would execute:'));
452
+ console.log(chalk.bold(dryRunArgs.join(' ')));
453
+ console.log(chalk.dim(`Agent: ${options.agent || 'coder'}`));
454
+ console.log(chalk.dim(`User prompt: ${userPrompt.length} characters`));
455
+ console.log('✓ Dry run completed successfully');
456
+ return;
457
+ }
458
+
459
+ try {
460
+ const { spawn } = await import('node:child_process');
461
+
462
+ const args = [];
463
+
464
+ // Handle print mode (headless)
465
+ if (options.print) {
466
+ // Note: Don't use --agent flag with 'run' command - OpenCode has a bug
467
+ // TypeError: undefined is not an object (evaluating 'input.agent.model')
468
+ // Default agent is 'coder' anyway
469
+ args.push('run');
470
+ // Use -c flag to continue last session (stdin is closed to prevent hanging)
471
+ if (options.continue) {
472
+ args.push('-c');
473
+ }
474
+ if (userPrompt && userPrompt.trim() !== '') {
475
+ args.push(userPrompt);
476
+ }
477
+ } else {
478
+ // Add agent flag for normal mode (TUI)
479
+ if (options.agent) {
480
+ args.push('--agent', options.agent);
481
+ }
482
+ // Normal mode with -p flag for prompt
483
+ if (userPrompt && userPrompt.trim() !== '') {
484
+ args.push('-p', userPrompt);
485
+ }
486
+ }
487
+
488
+ // Always print command for debugging in loop/headless mode
489
+ if (options.verbose || options.print) {
490
+ console.log(chalk.dim(`$ opencode ${args.join(' ')}`));
491
+ }
492
+
493
+ await new Promise<void>((resolve, reject) => {
494
+ const child = spawn('opencode', args, {
495
+ // In print mode (headless), close stdin to prevent waiting for input
496
+ // In normal mode, inherit all stdio for interactive TUI
497
+ stdio: options.print ? ['ignore', 'inherit', 'inherit'] : 'inherit',
498
+ shell: true,
499
+ env: process.env, // Pass environment variables
500
+ });
501
+
502
+ child.on('spawn', () => {
503
+ if (options.verbose) {
504
+ console.log('✓ OpenCode process started');
505
+ }
506
+ });
507
+
508
+ child.on('error', (error) => {
509
+ console.error('✗ Error spawning OpenCode:', error);
510
+ reject(error);
511
+ });
512
+
513
+ child.on('close', (code) => {
514
+ if (code !== 0) {
515
+ reject(new Error(`OpenCode exited with code ${code}`));
516
+ } else {
517
+ resolve();
518
+ }
519
+ });
520
+ });
521
+
522
+ } catch (error) {
523
+ if (error instanceof Error) {
524
+ throw new CLIError(`Failed to execute OpenCode: ${error.message}`, 'OPENCODE_ERROR');
525
+ }
526
+ throw error;
527
+ }
528
+ },
529
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Agent Types
3
+ * Defines agents with custom system prompts and metadata
4
+ */
5
+
6
+ /**
7
+ * Agent metadata from front matter
8
+ */
9
+ export interface AgentMetadata {
10
+ name: string;
11
+ description: string;
12
+ rules?: string[]; // Optional list of rule files to include (e.g., ['core', 'code-standards'])
13
+ }
14
+
15
+ /**
16
+ * Agent definition
17
+ */
18
+ export interface Agent {
19
+ id: string; // File name without extension (e.g., 'coder', 'planner')
20
+ metadata: AgentMetadata;
21
+ systemPrompt: string;
22
+ isBuiltin: boolean; // Whether this is a system-provided agent
23
+ filePath?: string; // Path to the agent file (for user-defined agents)
24
+ }
25
+
26
+ /**
27
+ * Agent source location
28
+ */
29
+ export interface AgentSource {
30
+ type: 'global' | 'project' | 'builtin';
31
+ path: string;
32
+ }
@@ -0,0 +1,108 @@
1
+ import { z } from 'zod';
2
+ import type { ApiError } from './errors.js';
3
+
4
+ // ============================================================================
5
+ // BATCH API INTERFACES
6
+ // ============================================================================
7
+
8
+ /**
9
+ * Batch API result for processing multiple operations
10
+ */
11
+ export interface BatchApiResult<T = unknown> {
12
+ /** Batch operation ID */
13
+ batchId: string;
14
+ /** Total number of operations */
15
+ total: number;
16
+ /** Number of successful operations */
17
+ successful: number;
18
+ /** Number of failed operations */
19
+ failed: number;
20
+ /** Results for individual operations */
21
+ results: BatchOperationResult<T>[];
22
+ /** Batch operation metadata */
23
+ metadata?: {
24
+ /** Batch start timestamp */
25
+ startedAt: string;
26
+ /** Batch completion timestamp */
27
+ completedAt?: string;
28
+ /** Total processing time in milliseconds */
29
+ duration?: number;
30
+ /** Processing batch size */
31
+ batchSize?: number;
32
+ /** Number of concurrent operations */
33
+ concurrency?: number;
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Individual batch operation result
39
+ */
40
+ export interface BatchOperationResult<T = unknown> {
41
+ /** Operation index in the batch */
42
+ index: number;
43
+ /** Operation ID */
44
+ id: string;
45
+ /** Operation success status */
46
+ success: boolean;
47
+ /** Operation result data */
48
+ data?: T;
49
+ /** Operation error (if failed) */
50
+ error?: ApiError;
51
+ /** Operation processing time in milliseconds */
52
+ processingTime?: number;
53
+ /** Operation retry attempts */
54
+ retryCount?: number;
55
+ }
56
+
57
+ // ============================================================================
58
+ // ZOD SCHEMAS
59
+ // ============================================================================
60
+
61
+ export const BatchOperationResultSchema = z.object({
62
+ index: z.number().min(0),
63
+ id: z.string(),
64
+ success: z.boolean(),
65
+ data: z.unknown().optional(),
66
+ error: z
67
+ .object({
68
+ code: z.string(),
69
+ message: z.string(),
70
+ statusCode: z.number().optional(),
71
+ type: z.enum([
72
+ 'validation',
73
+ 'authentication',
74
+ 'authorization',
75
+ 'not_found',
76
+ 'conflict',
77
+ 'rate_limit',
78
+ 'server_error',
79
+ 'network',
80
+ 'timeout',
81
+ ]),
82
+ description: z.string().optional(),
83
+ fieldErrors: z.record(z.array(z.string())).optional(),
84
+ stack: z.string().optional(),
85
+ context: z.record(z.unknown()).optional(),
86
+ suggestions: z.array(z.string()).optional(),
87
+ })
88
+ .optional(),
89
+ processingTime: z.number().min(0).optional(),
90
+ retryCount: z.number().min(0).optional(),
91
+ });
92
+
93
+ export const BatchApiResultSchema = z.object({
94
+ batchId: z.string(),
95
+ total: z.number().min(0),
96
+ successful: z.number().min(0),
97
+ failed: z.number().min(0),
98
+ results: z.array(BatchOperationResultSchema),
99
+ metadata: z
100
+ .object({
101
+ startedAt: z.string(),
102
+ completedAt: z.string().optional(),
103
+ duration: z.number().min(0).optional(),
104
+ batchSize: z.number().positive().optional(),
105
+ concurrency: z.number().positive().optional(),
106
+ })
107
+ .optional(),
108
+ });