@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.
- package/CHANGELOG.md +31 -0
- package/package.json +1 -1
- package/src/commands/settings/checkbox-config.ts +128 -0
- package/src/commands/settings/index.ts +6 -0
- package/src/commands/settings-command.ts +76 -156
- package/src/config/servers.ts +67 -0
- package/src/core/attach/file-attacher.ts +170 -0
- package/src/core/attach/index.ts +5 -0
- package/src/core/attach-manager.ts +42 -101
- package/src/services/global-config.ts +24 -6
- package/src/services/target-installer.ts +2 -19
- package/src/targets/claude-code.ts +12 -70
- package/src/targets/opencode.ts +12 -63
- package/src/targets/shared/index.ts +7 -0
- package/src/targets/shared/mcp-transforms.ts +146 -0
- package/src/targets/shared/target-operations.ts +128 -0
- package/src/utils/target-selection.ts +1 -6
package/src/targets/opencode.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
89
|
+
* Uses shared pure function for bidirectional conversion
|
|
84
90
|
*/
|
|
85
91
|
transformMCPConfig(config: MCPServerConfigUnion, _serverId?: string): Record<string, unknown> {
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,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'
|
|
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
|
|