@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.
- package/CHANGELOG.md +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- 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
|
+
});
|