@sylphx/flow 2.1.4 → 2.1.6

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.
@@ -7,10 +7,16 @@ import { installFile, installToDirectory } from '../core/installers/file-install
7
7
  import { createMCPInstaller } from '../core/installers/mcp-installer.js';
8
8
  import type { AgentMetadata } from '../types/target-config.types.js';
9
9
  import type { CommonOptions, MCPServerConfigUnion, SetupResult, Target } from '../types.js';
10
- import { getAgentsDir, getOutputStylesDir, getSlashCommandsDir } from '../utils/config/paths.js';
10
+ import { getAgentsDir, getOutputStylesDir } from '../utils/config/paths.js';
11
11
  import { fileUtils, generateHelpText, yamlUtils } from '../utils/config/target-utils.js';
12
12
  import { CLIError } from '../utils/error-handler.js';
13
13
  import { secretUtils } from '../utils/security/secret-utils.js';
14
+ import {
15
+ detectTargetConfig,
16
+ setupSlashCommandsTo,
17
+ stripFrontMatter,
18
+ transformMCPConfig as transformMCP,
19
+ } from './shared/index.js';
14
20
 
15
21
  /**
16
22
  * OpenCode target - composition approach with all original functionality
@@ -80,44 +86,10 @@ export const opencodeTarget: Target = {
80
86
 
81
87
  /**
82
88
  * Transform MCP server configuration for OpenCode
83
- * Convert from Claude Code's optimal format to OpenCode's format
89
+ * Uses shared pure function for bidirectional conversion
84
90
  */
85
91
  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;
92
+ return transformMCP(config, 'opencode');
121
93
  },
122
94
 
123
95
  getConfigPath: (cwd: string) =>
@@ -217,21 +189,14 @@ export const opencodeTarget: Target = {
217
189
  * Detect if this target is being used in the current environment
218
190
  */
219
191
  detectFromEnvironment(): boolean {
220
- try {
221
- const cwd = process.cwd();
222
- return fs.existsSync(path.join(cwd, 'opencode.jsonc'));
223
- } catch {
224
- return false;
225
- }
192
+ return detectTargetConfig(process.cwd(), 'opencode.jsonc');
226
193
  },
227
194
 
228
195
  /**
229
196
  * Transform rules content for OpenCode
230
197
  * OpenCode doesn't need front matter in rules files (AGENTS.md)
231
198
  */
232
- async transformRulesContent(content: string): Promise<string> {
233
- return yamlUtils.stripFrontMatter(content);
234
- },
199
+ transformRulesContent: stripFrontMatter,
235
200
 
236
201
  /**
237
202
  * Setup agents for OpenCode
@@ -380,23 +345,7 @@ export const opencodeTarget: Target = {
380
345
  if (!this.config.slashCommandsDir) {
381
346
  return { count: 0 };
382
347
  }
383
-
384
- const slashCommandsDir = path.join(cwd, this.config.slashCommandsDir);
385
-
386
- const results = await installToDirectory(
387
- getSlashCommandsDir(),
388
- slashCommandsDir,
389
- async (content) => {
390
- // Slash commands are plain markdown with front matter - no transformation needed
391
- return content;
392
- },
393
- {
394
- ...options,
395
- showProgress: false, // UI handled by init-command
396
- }
397
- );
398
-
399
- return { count: results.length };
348
+ return setupSlashCommandsTo(path.join(cwd, this.config.slashCommandsDir), undefined, options);
400
349
  },
401
350
 
402
351
  /**
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared target utilities
3
+ * Pure functions for common target operations
4
+ */
5
+
6
+ export * from './mcp-transforms.js';
7
+ export * from './target-operations.js';
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Pure functions for MCP configuration transformations
3
+ * Bidirectional conversion between Claude Code and OpenCode formats
4
+ */
5
+
6
+ import type { MCPServerConfigUnion } from '../../types.js';
7
+
8
+ // ============================================================================
9
+ // Types
10
+ // ============================================================================
11
+
12
+ export type MCPFormat = 'claude-code' | 'opencode';
13
+
14
+ export interface StdioConfig {
15
+ type: 'stdio';
16
+ command: string;
17
+ args?: string[];
18
+ env?: Record<string, string>;
19
+ }
20
+
21
+ export interface HttpConfig {
22
+ type: 'http';
23
+ url: string;
24
+ headers?: Record<string, string>;
25
+ }
26
+
27
+ export interface LocalConfig {
28
+ type: 'local';
29
+ command: string[];
30
+ environment?: Record<string, string>;
31
+ }
32
+
33
+ export interface RemoteConfig {
34
+ type: 'remote';
35
+ url: string;
36
+ headers?: Record<string, string>;
37
+ }
38
+
39
+ // ============================================================================
40
+ // Pure Transform Functions
41
+ // ============================================================================
42
+
43
+ /**
44
+ * Convert stdio format to local format (Claude Code → OpenCode)
45
+ */
46
+ export const stdioToLocal = (config: StdioConfig): LocalConfig => ({
47
+ type: 'local',
48
+ command: config.args ? [config.command, ...config.args] : [config.command],
49
+ ...(config.env && { environment: config.env }),
50
+ });
51
+
52
+ /**
53
+ * Convert local format to stdio format (OpenCode → Claude Code)
54
+ */
55
+ export const localToStdio = (config: LocalConfig): StdioConfig => {
56
+ const [command, ...args] = config.command;
57
+ return {
58
+ type: 'stdio',
59
+ command,
60
+ ...(args.length > 0 && { args }),
61
+ ...(config.environment && { env: config.environment }),
62
+ };
63
+ };
64
+
65
+ /**
66
+ * Convert http format to remote format (Claude Code → OpenCode)
67
+ */
68
+ export const httpToRemote = (config: HttpConfig): RemoteConfig => ({
69
+ type: 'remote',
70
+ url: config.url,
71
+ ...(config.headers && { headers: config.headers }),
72
+ });
73
+
74
+ /**
75
+ * Convert remote format to http format (OpenCode → Claude Code)
76
+ */
77
+ export const remoteToHttp = (config: RemoteConfig): HttpConfig => ({
78
+ type: 'http',
79
+ url: config.url,
80
+ ...(config.headers && { headers: config.headers }),
81
+ });
82
+
83
+ /**
84
+ * Normalize stdio config (ensure consistent structure)
85
+ */
86
+ export const normalizeStdio = (config: StdioConfig): StdioConfig => ({
87
+ type: 'stdio',
88
+ command: config.command,
89
+ ...(config.args && config.args.length > 0 && { args: config.args }),
90
+ ...(config.env && { env: config.env }),
91
+ });
92
+
93
+ /**
94
+ * Normalize http config (ensure consistent structure)
95
+ */
96
+ export const normalizeHttp = (config: HttpConfig): HttpConfig => ({
97
+ type: 'http',
98
+ url: config.url,
99
+ ...(config.headers && { headers: config.headers }),
100
+ });
101
+
102
+ // ============================================================================
103
+ // Main Transform Function
104
+ // ============================================================================
105
+
106
+ /**
107
+ * Transform MCP config to target format
108
+ * Pure function - no side effects
109
+ */
110
+ export const transformMCPConfig = (
111
+ config: MCPServerConfigUnion,
112
+ targetFormat: MCPFormat
113
+ ): Record<string, unknown> => {
114
+ // Claude Code format (stdio/http)
115
+ if (targetFormat === 'claude-code') {
116
+ switch (config.type) {
117
+ case 'local':
118
+ return localToStdio(config as LocalConfig);
119
+ case 'remote':
120
+ return remoteToHttp(config as RemoteConfig);
121
+ case 'stdio':
122
+ return normalizeStdio(config as StdioConfig);
123
+ case 'http':
124
+ return normalizeHttp(config as HttpConfig);
125
+ default:
126
+ return config;
127
+ }
128
+ }
129
+
130
+ // OpenCode format (local/remote)
131
+ if (targetFormat === 'opencode') {
132
+ switch (config.type) {
133
+ case 'stdio':
134
+ return stdioToLocal(config as StdioConfig);
135
+ case 'http':
136
+ return httpToRemote(config as HttpConfig);
137
+ case 'local':
138
+ case 'remote':
139
+ return config;
140
+ default:
141
+ return config;
142
+ }
143
+ }
144
+
145
+ return config;
146
+ };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Pure functions for common target operations
3
+ * Shared logic between claude-code and opencode targets
4
+ */
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { installToDirectory } from '../../core/installers/file-installer.js';
9
+ import type { CommonOptions, SetupResult, TargetConfig } from '../../types.js';
10
+ import { getAgentsDir, getSlashCommandsDir } from '../../utils/config/paths.js';
11
+ import { yamlUtils } from '../../utils/config/target-utils.js';
12
+
13
+ // ============================================================================
14
+ // Types
15
+ // ============================================================================
16
+
17
+ export type ContentTransformer = (content: string, sourcePath?: string) => Promise<string>;
18
+
19
+ export interface SetupOptions extends CommonOptions {
20
+ showProgress?: boolean;
21
+ }
22
+
23
+ // ============================================================================
24
+ // Pure Functions - Environment Detection
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Check if target config file exists in directory
29
+ */
30
+ export const detectTargetConfig = (cwd: string, configFile: string): boolean => {
31
+ try {
32
+ return fs.existsSync(path.join(cwd, configFile));
33
+ } catch {
34
+ return false;
35
+ }
36
+ };
37
+
38
+ // ============================================================================
39
+ // Pure Functions - Content Transformation
40
+ // ============================================================================
41
+
42
+ /**
43
+ * Strip YAML front matter from content
44
+ * Used for rules transformation in both targets
45
+ */
46
+ export const stripFrontMatter = (content: string): Promise<string> =>
47
+ Promise.resolve(yamlUtils.stripFrontMatter(content));
48
+
49
+ /**
50
+ * Identity transformer - returns content unchanged
51
+ */
52
+ export const identityTransform: ContentTransformer = (content: string) => Promise.resolve(content);
53
+
54
+ // ============================================================================
55
+ // Pure Functions - Setup Operations
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Setup agents to target directory
60
+ * Generic function used by both targets
61
+ */
62
+ export const setupAgentsTo = async (
63
+ targetDir: string,
64
+ transformer: ContentTransformer,
65
+ options: SetupOptions = {}
66
+ ): Promise<SetupResult> => {
67
+ const results = await installToDirectory(getAgentsDir(), targetDir, transformer, {
68
+ ...options,
69
+ showProgress: false,
70
+ });
71
+ return { count: results.length };
72
+ };
73
+
74
+ /**
75
+ * Setup slash commands to target directory
76
+ * Generic function used by both targets
77
+ */
78
+ export const setupSlashCommandsTo = async (
79
+ targetDir: string,
80
+ transformer: ContentTransformer = identityTransform,
81
+ options: SetupOptions = {}
82
+ ): Promise<SetupResult> => {
83
+ const results = await installToDirectory(getSlashCommandsDir(), targetDir, transformer, {
84
+ ...options,
85
+ showProgress: false,
86
+ });
87
+ return { count: results.length };
88
+ };
89
+
90
+ // ============================================================================
91
+ // Pure Functions - Config Operations
92
+ // ============================================================================
93
+
94
+ /**
95
+ * Ensure config has required structure
96
+ * Returns new object, doesn't mutate input
97
+ */
98
+ export const ensureConfigStructure = <T extends Record<string, unknown>>(
99
+ config: T,
100
+ key: string,
101
+ defaultValue: unknown = {}
102
+ ): T => {
103
+ if (config[key] !== undefined) {
104
+ return config;
105
+ }
106
+ return { ...config, [key]: defaultValue };
107
+ };
108
+
109
+ /**
110
+ * Get MCP config key for target
111
+ */
112
+ export const getMCPKey = (targetId: string): string =>
113
+ targetId === 'claude-code' ? 'mcpServers' : 'mcp';
114
+
115
+ // ============================================================================
116
+ // Pure Functions - Path Resolution
117
+ // ============================================================================
118
+
119
+ /**
120
+ * Resolve target directory paths
121
+ */
122
+ export const resolveTargetPaths = (cwd: string, config: TargetConfig) => ({
123
+ configDir: path.join(cwd, config.configDir),
124
+ agentDir: path.join(cwd, config.agentDir),
125
+ configFile: path.join(cwd, config.configFile),
126
+ slashCommandsDir: config.slashCommandsDir ? path.join(cwd, config.slashCommandsDir) : undefined,
127
+ rulesFile: config.rulesFile ? path.join(cwd, config.rulesFile) : undefined,
128
+ });
@@ -15,7 +15,7 @@ export interface TargetChoice {
15
15
  /** Display name of the target */
16
16
  name: string;
17
17
  /** Target identifier */
18
- value: 'claude-code' | 'opencode' | 'cursor';
18
+ value: 'claude-code' | 'opencode';
19
19
  /** Whether the target is currently installed */
20
20
  installed: boolean;
21
21
  }
@@ -37,11 +37,6 @@ export function buildAvailableTargets(installedTargets: string[]): TargetChoice[
37
37
  value: 'opencode',
38
38
  installed: installedTargets.includes('opencode'),
39
39
  },
40
- {
41
- name: 'Cursor',
42
- value: 'cursor',
43
- installed: installedTargets.includes('cursor'),
44
- },
45
40
  ];
46
41
  }
47
42