@sylphx/flow 2.1.3 → 2.1.5
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 +28 -0
- package/README.md +44 -0
- package/package.json +79 -73
- package/src/commands/flow/execute-v2.ts +37 -29
- package/src/commands/flow/prompt.ts +5 -3
- package/src/commands/flow/types.ts +0 -2
- package/src/commands/flow-command.ts +20 -13
- package/src/commands/hook-command.ts +1 -3
- package/src/commands/settings/checkbox-config.ts +128 -0
- package/src/commands/settings/index.ts +6 -0
- package/src/commands/settings-command.ts +84 -156
- package/src/config/ai-config.ts +60 -41
- package/src/core/agent-loader.ts +11 -6
- package/src/core/attach/file-attacher.ts +172 -0
- package/src/core/attach/index.ts +5 -0
- package/src/core/attach-manager.ts +117 -171
- package/src/core/backup-manager.ts +35 -29
- package/src/core/cleanup-handler.ts +11 -8
- package/src/core/error-handling.ts +23 -30
- package/src/core/flow-executor.ts +58 -76
- package/src/core/formatting/bytes.ts +2 -4
- package/src/core/functional/async.ts +5 -4
- package/src/core/functional/error-handler.ts +2 -2
- package/src/core/git-stash-manager.ts +21 -10
- package/src/core/installers/file-installer.ts +0 -1
- package/src/core/installers/mcp-installer.ts +0 -1
- package/src/core/project-manager.ts +24 -18
- package/src/core/secrets-manager.ts +54 -73
- package/src/core/session-manager.ts +20 -22
- package/src/core/state-detector.ts +139 -80
- package/src/core/template-loader.ts +13 -31
- package/src/core/upgrade-manager.ts +122 -69
- package/src/index.ts +8 -5
- package/src/services/auto-upgrade.ts +1 -1
- package/src/services/config-service.ts +41 -29
- package/src/services/global-config.ts +3 -3
- package/src/services/target-installer.ts +11 -26
- package/src/targets/claude-code.ts +35 -81
- package/src/targets/opencode.ts +28 -68
- package/src/targets/shared/index.ts +7 -0
- package/src/targets/shared/mcp-transforms.ts +132 -0
- package/src/targets/shared/target-operations.ts +135 -0
- package/src/types/cli.types.ts +2 -2
- package/src/types/provider.types.ts +1 -7
- package/src/types/session.types.ts +11 -11
- package/src/types/target.types.ts +3 -1
- package/src/types/todo.types.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
- package/src/utils/agent-enhancer.ts +4 -4
- package/src/utils/config/paths.ts +3 -1
- package/src/utils/config/target-utils.ts +2 -2
- package/src/utils/display/banner.ts +2 -2
- package/src/utils/display/notifications.ts +58 -45
- package/src/utils/display/status.ts +29 -12
- package/src/utils/files/file-operations.ts +1 -1
- package/src/utils/files/sync-utils.ts +38 -41
- package/src/utils/index.ts +19 -27
- package/src/utils/package-manager-detector.ts +15 -5
- package/src/utils/security/security.ts +8 -4
- package/src/utils/target-selection.ts +6 -8
- package/src/utils/version.ts +4 -2
- package/src/commands/flow-orchestrator.ts +0 -328
- package/src/commands/init-command.ts +0 -92
- package/src/commands/init-core.ts +0 -331
- package/src/core/agent-manager.ts +0 -174
- package/src/core/loop-controller.ts +0 -200
- package/src/core/rule-loader.ts +0 -147
- package/src/core/rule-manager.ts +0 -240
- package/src/services/claude-config-service.ts +0 -252
- package/src/services/first-run-setup.ts +0 -220
- package/src/services/smart-config-service.ts +0 -269
- package/src/types/api.types.ts +0 -9
|
@@ -3,14 +3,19 @@ import fs from 'node:fs';
|
|
|
3
3
|
import fsPromises from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import { installToDirectory } from '../core/installers/file-installer.js';
|
|
7
6
|
import { createMCPInstaller } from '../core/installers/mcp-installer.js';
|
|
8
7
|
import type { AgentMetadata } from '../types/target-config.types.js';
|
|
9
8
|
import type { CommonOptions, MCPServerConfigUnion, SetupResult, Target } from '../types.js';
|
|
9
|
+
import { getAgentsDir } from '../utils/config/paths.js';
|
|
10
|
+
import { fileUtils, generateHelpText, pathUtils, yamlUtils } from '../utils/config/target-utils.js';
|
|
10
11
|
import { CLIError } from '../utils/error-handler.js';
|
|
11
|
-
import { getAgentsDir, getSlashCommandsDir } from '../utils/config/paths.js';
|
|
12
12
|
import { sanitize } from '../utils/security/security.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
transformMCPConfig as transformMCP,
|
|
15
|
+
detectTargetConfig,
|
|
16
|
+
stripFrontMatter,
|
|
17
|
+
setupSlashCommandsTo,
|
|
18
|
+
} from './shared/index.js';
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Claude Code target - composition approach with all original functionality
|
|
@@ -24,6 +29,7 @@ export const claudeCodeTarget: Target = {
|
|
|
24
29
|
isDefault: false,
|
|
25
30
|
|
|
26
31
|
config: {
|
|
32
|
+
configDir: '.claude',
|
|
27
33
|
agentDir: '.claude/agents',
|
|
28
34
|
agentExtension: '.md',
|
|
29
35
|
agentFormat: 'yaml-frontmatter',
|
|
@@ -67,50 +73,10 @@ export const claudeCodeTarget: Target = {
|
|
|
67
73
|
|
|
68
74
|
/**
|
|
69
75
|
* Transform MCP server configuration for Claude Code
|
|
70
|
-
*
|
|
76
|
+
* Uses shared pure function for bidirectional conversion
|
|
71
77
|
*/
|
|
72
78
|
transformMCPConfig(config: MCPServerConfigUnion, _serverId?: string): Record<string, unknown> {
|
|
73
|
-
|
|
74
|
-
if (config.type === 'local') {
|
|
75
|
-
// Convert OpenCode 'local' array command to Claude Code format
|
|
76
|
-
const [command, ...args] = config.command;
|
|
77
|
-
return {
|
|
78
|
-
type: 'stdio',
|
|
79
|
-
command,
|
|
80
|
-
...(args && args.length > 0 && { args }),
|
|
81
|
-
...(config.environment && { env: config.environment }),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Handle new stdio format (already optimized for Claude Code)
|
|
86
|
-
if (config.type === 'stdio') {
|
|
87
|
-
return {
|
|
88
|
-
type: 'stdio',
|
|
89
|
-
command: config.command,
|
|
90
|
-
...(config.args && config.args.length > 0 && { args: config.args }),
|
|
91
|
-
...(config.env && { env: config.env }),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Handle legacy OpenCode 'remote' type
|
|
96
|
-
if (config.type === 'remote') {
|
|
97
|
-
return {
|
|
98
|
-
type: 'http',
|
|
99
|
-
url: config.url,
|
|
100
|
-
...(config.headers && { headers: config.headers }),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Handle new http format (already optimized for Claude Code)
|
|
105
|
-
if (config.type === 'http') {
|
|
106
|
-
return {
|
|
107
|
-
type: 'http',
|
|
108
|
-
url: config.url,
|
|
109
|
-
...(config.headers && { headers: config.headers }),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return config;
|
|
79
|
+
return transformMCP(config, 'claude-code');
|
|
114
80
|
},
|
|
115
81
|
|
|
116
82
|
getConfigPath: (cwd: string) =>
|
|
@@ -218,8 +184,12 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
218
184
|
if (options.dryRun) {
|
|
219
185
|
// Build the command for display
|
|
220
186
|
const dryRunArgs = ['claude', '--dangerously-skip-permissions'];
|
|
221
|
-
if (options.print)
|
|
222
|
-
|
|
187
|
+
if (options.print) {
|
|
188
|
+
dryRunArgs.push('-p');
|
|
189
|
+
}
|
|
190
|
+
if (options.continue) {
|
|
191
|
+
dryRunArgs.push('-c');
|
|
192
|
+
}
|
|
223
193
|
dryRunArgs.push('--system-prompt', '"<agent content>"');
|
|
224
194
|
if (sanitizedUserPrompt.trim() !== '') {
|
|
225
195
|
dryRunArgs.push(`"${sanitizedUserPrompt}"`);
|
|
@@ -297,14 +267,18 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
297
267
|
reject(error);
|
|
298
268
|
});
|
|
299
269
|
});
|
|
300
|
-
} catch (error:
|
|
301
|
-
|
|
270
|
+
} catch (error: unknown) {
|
|
271
|
+
const err = error as NodeJS.ErrnoException & { code?: string | number };
|
|
272
|
+
if (err.code === 'ENOENT') {
|
|
302
273
|
throw new CLIError('Claude Code not found. Please install it first.', 'CLAUDE_NOT_FOUND');
|
|
303
274
|
}
|
|
304
|
-
if (
|
|
305
|
-
throw new CLIError(`Claude Code exited with code ${
|
|
275
|
+
if (err.code) {
|
|
276
|
+
throw new CLIError(`Claude Code exited with code ${err.code}`, 'CLAUDE_ERROR');
|
|
306
277
|
}
|
|
307
|
-
throw new CLIError(
|
|
278
|
+
throw new CLIError(
|
|
279
|
+
`Failed to execute Claude Code: ${(error as Error).message}`,
|
|
280
|
+
'CLAUDE_ERROR'
|
|
281
|
+
);
|
|
308
282
|
}
|
|
309
283
|
},
|
|
310
284
|
|
|
@@ -312,12 +286,7 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
312
286
|
* Detect if this target is being used in the current environment
|
|
313
287
|
*/
|
|
314
288
|
detectFromEnvironment(): boolean {
|
|
315
|
-
|
|
316
|
-
const cwd = process.cwd();
|
|
317
|
-
return fs.existsSync(path.join(cwd, '.mcp.json'));
|
|
318
|
-
} catch {
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
289
|
+
return detectTargetConfig(process.cwd(), '.mcp.json');
|
|
321
290
|
},
|
|
322
291
|
|
|
323
292
|
/**
|
|
@@ -333,8 +302,9 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
333
302
|
try {
|
|
334
303
|
const content = await fsPromises.readFile(settingsPath, 'utf8');
|
|
335
304
|
settings = JSON.parse(content);
|
|
336
|
-
} catch (error:
|
|
337
|
-
|
|
305
|
+
} catch (error: unknown) {
|
|
306
|
+
const err = error as NodeJS.ErrnoException;
|
|
307
|
+
if (err.code !== 'ENOENT') {
|
|
338
308
|
throw error;
|
|
339
309
|
}
|
|
340
310
|
// File doesn't exist, will create new
|
|
@@ -367,16 +337,16 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
367
337
|
* Transform rules content for Claude Code
|
|
368
338
|
* Claude Code doesn't need front matter in rules files (CLAUDE.md)
|
|
369
339
|
*/
|
|
370
|
-
|
|
371
|
-
return yamlUtils.stripFrontMatter(content);
|
|
372
|
-
},
|
|
340
|
+
transformRulesContent: stripFrontMatter,
|
|
373
341
|
|
|
374
342
|
/**
|
|
375
343
|
* Setup hooks for Claude Code
|
|
376
344
|
* Configure session and prompt hooks for system information display
|
|
377
345
|
*/
|
|
378
346
|
async setupHooks(cwd: string, _options: CommonOptions): Promise<SetupResult> {
|
|
379
|
-
const { processSettings, generateHookCommands } = await import(
|
|
347
|
+
const { processSettings, generateHookCommands } = await import(
|
|
348
|
+
'./functional/claude-code-logic.js'
|
|
349
|
+
);
|
|
380
350
|
const { pathExists, createDirectory, readFile, writeFile } = await import(
|
|
381
351
|
'../composables/functional/useFileSystem.js'
|
|
382
352
|
);
|
|
@@ -512,23 +482,7 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
512
482
|
if (!this.config.slashCommandsDir) {
|
|
513
483
|
return { count: 0 };
|
|
514
484
|
}
|
|
515
|
-
|
|
516
|
-
const slashCommandsDir = path.join(cwd, this.config.slashCommandsDir);
|
|
517
|
-
|
|
518
|
-
const results = await installToDirectory(
|
|
519
|
-
getSlashCommandsDir(),
|
|
520
|
-
slashCommandsDir,
|
|
521
|
-
async (content) => {
|
|
522
|
-
// Slash commands are plain markdown with front matter - no transformation needed
|
|
523
|
-
return content;
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
...options,
|
|
527
|
-
showProgress: false, // UI handled by init-command
|
|
528
|
-
}
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
return { count: results.length };
|
|
485
|
+
return setupSlashCommandsTo(path.join(cwd, this.config.slashCommandsDir), undefined, options);
|
|
532
486
|
},
|
|
533
487
|
};
|
|
534
488
|
|
package/src/targets/opencode.ts
CHANGED
|
@@ -3,14 +3,20 @@ import path from 'node:path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { getRulesPath, ruleFileExists } from '../config/rules.js';
|
|
5
5
|
import { MCP_SERVER_REGISTRY } from '../config/servers.js';
|
|
6
|
-
import {
|
|
6
|
+
import { installFile, installToDirectory } from '../core/installers/file-installer.js';
|
|
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
10
|
import { getAgentsDir, getOutputStylesDir, getSlashCommandsDir } from '../utils/config/paths.js';
|
|
11
|
-
import { secretUtils } from '../utils/security/secret-utils.js';
|
|
12
11
|
import { fileUtils, generateHelpText, yamlUtils } from '../utils/config/target-utils.js';
|
|
13
12
|
import { CLIError } from '../utils/error-handler.js';
|
|
13
|
+
import { secretUtils } from '../utils/security/secret-utils.js';
|
|
14
|
+
import {
|
|
15
|
+
transformMCPConfig as transformMCP,
|
|
16
|
+
detectTargetConfig,
|
|
17
|
+
stripFrontMatter,
|
|
18
|
+
setupSlashCommandsTo,
|
|
19
|
+
} from './shared/index.js';
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
22
|
* OpenCode target - composition approach with all original functionality
|
|
@@ -24,6 +30,7 @@ export const opencodeTarget: Target = {
|
|
|
24
30
|
isDefault: true,
|
|
25
31
|
|
|
26
32
|
config: {
|
|
33
|
+
configDir: '.opencode',
|
|
27
34
|
agentDir: '.opencode/agent',
|
|
28
35
|
agentExtension: '.md',
|
|
29
36
|
agentFormat: 'yaml-frontmatter',
|
|
@@ -63,7 +70,12 @@ export const opencodeTarget: Target = {
|
|
|
63
70
|
|
|
64
71
|
// If additional metadata is provided, merge it (but exclude unsupported fields)
|
|
65
72
|
if (metadata) {
|
|
66
|
-
const {
|
|
73
|
+
const {
|
|
74
|
+
name: additionalName,
|
|
75
|
+
mode: additionalMode,
|
|
76
|
+
rules: additionalRules,
|
|
77
|
+
...additionalCleanMetadata
|
|
78
|
+
} = metadata;
|
|
67
79
|
const mergedMetadata = { ...cleanMetadata, ...additionalCleanMetadata };
|
|
68
80
|
return yamlUtils.addFrontMatter(baseContent, mergedMetadata);
|
|
69
81
|
}
|
|
@@ -74,44 +86,10 @@ export const opencodeTarget: Target = {
|
|
|
74
86
|
|
|
75
87
|
/**
|
|
76
88
|
* Transform MCP server configuration for OpenCode
|
|
77
|
-
*
|
|
89
|
+
* Uses shared pure function for bidirectional conversion
|
|
78
90
|
*/
|
|
79
91
|
transformMCPConfig(config: MCPServerConfigUnion, _serverId?: string): Record<string, unknown> {
|
|
80
|
-
|
|
81
|
-
if (config.type === 'stdio') {
|
|
82
|
-
// Convert Claude Code format to OpenCode format
|
|
83
|
-
const openCodeConfig: Record<string, unknown> = {
|
|
84
|
-
type: 'local',
|
|
85
|
-
command: [config.command],
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
if (config.args && config.args.length > 0) {
|
|
89
|
-
openCodeConfig.command.push(...config.args);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (config.env) {
|
|
93
|
-
openCodeConfig.environment = config.env;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return openCodeConfig;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Handle new Claude Code http format
|
|
100
|
-
if (config.type === 'http') {
|
|
101
|
-
// Claude Code http format is compatible with OpenCode remote format
|
|
102
|
-
return {
|
|
103
|
-
type: 'remote',
|
|
104
|
-
url: config.url,
|
|
105
|
-
...(config.headers && { headers: config.headers }),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Handle legacy OpenCode formats (pass through)
|
|
110
|
-
if (config.type === 'local' || config.type === 'remote') {
|
|
111
|
-
return config;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return config;
|
|
92
|
+
return transformMCP(config, 'opencode');
|
|
115
93
|
},
|
|
116
94
|
|
|
117
95
|
getConfigPath: (cwd: string) =>
|
|
@@ -211,21 +189,14 @@ export const opencodeTarget: Target = {
|
|
|
211
189
|
* Detect if this target is being used in the current environment
|
|
212
190
|
*/
|
|
213
191
|
detectFromEnvironment(): boolean {
|
|
214
|
-
|
|
215
|
-
const cwd = process.cwd();
|
|
216
|
-
return fs.existsSync(path.join(cwd, 'opencode.jsonc'));
|
|
217
|
-
} catch {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
192
|
+
return detectTargetConfig(process.cwd(), 'opencode.jsonc');
|
|
220
193
|
},
|
|
221
194
|
|
|
222
195
|
/**
|
|
223
196
|
* Transform rules content for OpenCode
|
|
224
197
|
* OpenCode doesn't need front matter in rules files (AGENTS.md)
|
|
225
198
|
*/
|
|
226
|
-
|
|
227
|
-
return yamlUtils.stripFrontMatter(content);
|
|
228
|
-
},
|
|
199
|
+
transformRulesContent: stripFrontMatter,
|
|
229
200
|
|
|
230
201
|
/**
|
|
231
202
|
* Setup agents for OpenCode
|
|
@@ -374,32 +345,22 @@ export const opencodeTarget: Target = {
|
|
|
374
345
|
if (!this.config.slashCommandsDir) {
|
|
375
346
|
return { count: 0 };
|
|
376
347
|
}
|
|
377
|
-
|
|
378
|
-
const slashCommandsDir = path.join(cwd, this.config.slashCommandsDir);
|
|
379
|
-
|
|
380
|
-
const results = await installToDirectory(
|
|
381
|
-
getSlashCommandsDir(),
|
|
382
|
-
slashCommandsDir,
|
|
383
|
-
async (content) => {
|
|
384
|
-
// Slash commands are plain markdown with front matter - no transformation needed
|
|
385
|
-
return content;
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
...options,
|
|
389
|
-
showProgress: false, // UI handled by init-command
|
|
390
|
-
}
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
return { count: results.length };
|
|
348
|
+
return setupSlashCommandsTo(path.join(cwd, this.config.slashCommandsDir), undefined, options);
|
|
394
349
|
},
|
|
395
350
|
|
|
396
351
|
/**
|
|
397
352
|
* Execute OpenCode CLI
|
|
398
353
|
*/
|
|
399
354
|
async executeCommand(
|
|
400
|
-
|
|
355
|
+
_systemPrompt: string,
|
|
401
356
|
userPrompt: string,
|
|
402
|
-
options: {
|
|
357
|
+
options: {
|
|
358
|
+
verbose?: boolean;
|
|
359
|
+
dryRun?: boolean;
|
|
360
|
+
print?: boolean;
|
|
361
|
+
continue?: boolean;
|
|
362
|
+
agent?: string;
|
|
363
|
+
} = {}
|
|
403
364
|
): Promise<void> {
|
|
404
365
|
if (options.dryRun) {
|
|
405
366
|
// Build the command for display
|
|
@@ -492,7 +453,6 @@ export const opencodeTarget: Target = {
|
|
|
492
453
|
}
|
|
493
454
|
});
|
|
494
455
|
});
|
|
495
|
-
|
|
496
456
|
} catch (error) {
|
|
497
457
|
if (error instanceof Error) {
|
|
498
458
|
throw new CLIError(`Failed to execute OpenCode: ${error.message}`, 'OPENCODE_ERROR');
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
if (config.type === 'local') return localToStdio(config as LocalConfig);
|
|
117
|
+
if (config.type === 'remote') return remoteToHttp(config as RemoteConfig);
|
|
118
|
+
if (config.type === 'stdio') return normalizeStdio(config as StdioConfig);
|
|
119
|
+
if (config.type === 'http') return normalizeHttp(config as HttpConfig);
|
|
120
|
+
return config;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// OpenCode format (local/remote)
|
|
124
|
+
if (targetFormat === 'opencode') {
|
|
125
|
+
if (config.type === 'stdio') return stdioToLocal(config as StdioConfig);
|
|
126
|
+
if (config.type === 'http') return httpToRemote(config as HttpConfig);
|
|
127
|
+
if (config.type === 'local' || config.type === 'remote') return config;
|
|
128
|
+
return config;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return config;
|
|
132
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
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 type { CommonOptions, SetupResult, TargetConfig } from '../../types.js';
|
|
9
|
+
import { installToDirectory } from '../../core/installers/file-installer.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) =>
|
|
53
|
+
Promise.resolve(content);
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Pure Functions - Setup Operations
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Setup agents to target directory
|
|
61
|
+
* Generic function used by both targets
|
|
62
|
+
*/
|
|
63
|
+
export const setupAgentsTo = async (
|
|
64
|
+
targetDir: string,
|
|
65
|
+
transformer: ContentTransformer,
|
|
66
|
+
options: SetupOptions = {}
|
|
67
|
+
): Promise<SetupResult> => {
|
|
68
|
+
const results = await installToDirectory(
|
|
69
|
+
getAgentsDir(),
|
|
70
|
+
targetDir,
|
|
71
|
+
transformer,
|
|
72
|
+
{ ...options, showProgress: false }
|
|
73
|
+
);
|
|
74
|
+
return { count: results.length };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Setup slash commands to target directory
|
|
79
|
+
* Generic function used by both targets
|
|
80
|
+
*/
|
|
81
|
+
export const setupSlashCommandsTo = async (
|
|
82
|
+
targetDir: string,
|
|
83
|
+
transformer: ContentTransformer = identityTransform,
|
|
84
|
+
options: SetupOptions = {}
|
|
85
|
+
): Promise<SetupResult> => {
|
|
86
|
+
const results = await installToDirectory(
|
|
87
|
+
getSlashCommandsDir(),
|
|
88
|
+
targetDir,
|
|
89
|
+
transformer,
|
|
90
|
+
{ ...options, showProgress: false }
|
|
91
|
+
);
|
|
92
|
+
return { count: results.length };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Pure Functions - Config Operations
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Ensure config has required structure
|
|
101
|
+
* Returns new object, doesn't mutate input
|
|
102
|
+
*/
|
|
103
|
+
export const ensureConfigStructure = <T extends Record<string, unknown>>(
|
|
104
|
+
config: T,
|
|
105
|
+
key: string,
|
|
106
|
+
defaultValue: unknown = {}
|
|
107
|
+
): T => {
|
|
108
|
+
if (config[key] !== undefined) return config;
|
|
109
|
+
return { ...config, [key]: defaultValue };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get MCP config key for target
|
|
114
|
+
*/
|
|
115
|
+
export const getMCPKey = (targetId: string): string =>
|
|
116
|
+
targetId === 'claude-code' ? 'mcpServers' : 'mcp';
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Pure Functions - Path Resolution
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Resolve target directory paths
|
|
124
|
+
*/
|
|
125
|
+
export const resolveTargetPaths = (cwd: string, config: TargetConfig) => ({
|
|
126
|
+
configDir: path.join(cwd, config.configDir),
|
|
127
|
+
agentDir: path.join(cwd, config.agentDir),
|
|
128
|
+
configFile: path.join(cwd, config.configFile),
|
|
129
|
+
slashCommandsDir: config.slashCommandsDir
|
|
130
|
+
? path.join(cwd, config.slashCommandsDir)
|
|
131
|
+
: undefined,
|
|
132
|
+
rulesFile: config.rulesFile
|
|
133
|
+
? path.join(cwd, config.rulesFile)
|
|
134
|
+
: undefined,
|
|
135
|
+
});
|
package/src/types/cli.types.ts
CHANGED
|
@@ -82,6 +82,6 @@ export interface RunCommandOptions {
|
|
|
82
82
|
agent?: string;
|
|
83
83
|
agentFile?: string;
|
|
84
84
|
prompt?: string;
|
|
85
|
-
print?: boolean;
|
|
86
|
-
continue?: boolean;
|
|
85
|
+
print?: boolean; // Headless print mode
|
|
86
|
+
continue?: boolean; // Continue previous conversation
|
|
87
87
|
}
|
|
@@ -10,13 +10,7 @@
|
|
|
10
10
|
* Provider IDs
|
|
11
11
|
* All supported AI providers
|
|
12
12
|
*/
|
|
13
|
-
export type ProviderId =
|
|
14
|
-
| 'anthropic'
|
|
15
|
-
| 'openai'
|
|
16
|
-
| 'google'
|
|
17
|
-
| 'openrouter'
|
|
18
|
-
| 'claude-code'
|
|
19
|
-
| 'zai';
|
|
13
|
+
export type ProviderId = 'anthropic' | 'openai' | 'google' | 'openrouter' | 'claude-code' | 'zai';
|
|
20
14
|
|
|
21
15
|
/**
|
|
22
16
|
* Provider configuration value
|