@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +44 -0
  3. package/package.json +79 -73
  4. package/src/commands/flow/execute-v2.ts +37 -29
  5. package/src/commands/flow/prompt.ts +5 -3
  6. package/src/commands/flow/types.ts +0 -2
  7. package/src/commands/flow-command.ts +20 -13
  8. package/src/commands/hook-command.ts +1 -3
  9. package/src/commands/settings/checkbox-config.ts +128 -0
  10. package/src/commands/settings/index.ts +6 -0
  11. package/src/commands/settings-command.ts +84 -156
  12. package/src/config/ai-config.ts +60 -41
  13. package/src/core/agent-loader.ts +11 -6
  14. package/src/core/attach/file-attacher.ts +172 -0
  15. package/src/core/attach/index.ts +5 -0
  16. package/src/core/attach-manager.ts +117 -171
  17. package/src/core/backup-manager.ts +35 -29
  18. package/src/core/cleanup-handler.ts +11 -8
  19. package/src/core/error-handling.ts +23 -30
  20. package/src/core/flow-executor.ts +58 -76
  21. package/src/core/formatting/bytes.ts +2 -4
  22. package/src/core/functional/async.ts +5 -4
  23. package/src/core/functional/error-handler.ts +2 -2
  24. package/src/core/git-stash-manager.ts +21 -10
  25. package/src/core/installers/file-installer.ts +0 -1
  26. package/src/core/installers/mcp-installer.ts +0 -1
  27. package/src/core/project-manager.ts +24 -18
  28. package/src/core/secrets-manager.ts +54 -73
  29. package/src/core/session-manager.ts +20 -22
  30. package/src/core/state-detector.ts +139 -80
  31. package/src/core/template-loader.ts +13 -31
  32. package/src/core/upgrade-manager.ts +122 -69
  33. package/src/index.ts +8 -5
  34. package/src/services/auto-upgrade.ts +1 -1
  35. package/src/services/config-service.ts +41 -29
  36. package/src/services/global-config.ts +3 -3
  37. package/src/services/target-installer.ts +11 -26
  38. package/src/targets/claude-code.ts +35 -81
  39. package/src/targets/opencode.ts +28 -68
  40. package/src/targets/shared/index.ts +7 -0
  41. package/src/targets/shared/mcp-transforms.ts +132 -0
  42. package/src/targets/shared/target-operations.ts +135 -0
  43. package/src/types/cli.types.ts +2 -2
  44. package/src/types/provider.types.ts +1 -7
  45. package/src/types/session.types.ts +11 -11
  46. package/src/types/target.types.ts +3 -1
  47. package/src/types/todo.types.ts +1 -1
  48. package/src/types.ts +1 -1
  49. package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
  50. package/src/utils/agent-enhancer.ts +4 -4
  51. package/src/utils/config/paths.ts +3 -1
  52. package/src/utils/config/target-utils.ts +2 -2
  53. package/src/utils/display/banner.ts +2 -2
  54. package/src/utils/display/notifications.ts +58 -45
  55. package/src/utils/display/status.ts +29 -12
  56. package/src/utils/files/file-operations.ts +1 -1
  57. package/src/utils/files/sync-utils.ts +38 -41
  58. package/src/utils/index.ts +19 -27
  59. package/src/utils/package-manager-detector.ts +15 -5
  60. package/src/utils/security/security.ts +8 -4
  61. package/src/utils/target-selection.ts +6 -8
  62. package/src/utils/version.ts +4 -2
  63. package/src/commands/flow-orchestrator.ts +0 -328
  64. package/src/commands/init-command.ts +0 -92
  65. package/src/commands/init-core.ts +0 -331
  66. package/src/core/agent-manager.ts +0 -174
  67. package/src/core/loop-controller.ts +0 -200
  68. package/src/core/rule-loader.ts +0 -147
  69. package/src/core/rule-manager.ts +0 -240
  70. package/src/services/claude-config-service.ts +0 -252
  71. package/src/services/first-run-setup.ts +0 -220
  72. package/src/services/smart-config-service.ts +0 -269
  73. 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 { fileUtils, generateHelpText, pathUtils, yamlUtils } from '../utils/config/target-utils.js';
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
- * Convert from various formats to Claude Code's optimal format
76
+ * Uses shared pure function for bidirectional conversion
71
77
  */
72
78
  transformMCPConfig(config: MCPServerConfigUnion, _serverId?: string): Record<string, unknown> {
73
- // Handle legacy OpenCode 'local' type
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) dryRunArgs.push('-p');
222
- if (options.continue) dryRunArgs.push('-c');
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: any) {
301
- if (error.code === 'ENOENT') {
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 (error.code) {
305
- throw new CLIError(`Claude Code exited with code ${error.code}`, 'CLAUDE_ERROR');
275
+ if (err.code) {
276
+ throw new CLIError(`Claude Code exited with code ${err.code}`, 'CLAUDE_ERROR');
306
277
  }
307
- throw new CLIError(`Failed to execute Claude Code: ${error.message}`, 'CLAUDE_ERROR');
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
- try {
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: any) {
337
- if (error.code !== 'ENOENT') {
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
- async transformRulesContent(content: string): Promise<string> {
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('./functional/claude-code-logic.js');
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
 
@@ -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 { installToDirectory, installFile } from '../core/installers/file-installer.js';
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 { name: additionalName, mode: additionalMode, rules: additionalRules, ...additionalCleanMetadata } = metadata;
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
- * Convert from Claude Code's optimal format to OpenCode's format
89
+ * Uses shared pure function for bidirectional conversion
78
90
  */
79
91
  transformMCPConfig(config: MCPServerConfigUnion, _serverId?: string): Record<string, unknown> {
80
- // Handle new Claude Code stdio format
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
- try {
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
- async transformRulesContent(content: string): Promise<string> {
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
- systemPrompt: string,
355
+ _systemPrompt: string,
401
356
  userPrompt: string,
402
- options: { verbose?: boolean; dryRun?: boolean; print?: boolean; continue?: boolean; agent?: string } = {}
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,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,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
+ });
@@ -82,6 +82,6 @@ export interface RunCommandOptions {
82
82
  agent?: string;
83
83
  agentFile?: string;
84
84
  prompt?: string;
85
- print?: boolean; // Headless print mode
86
- continue?: boolean; // Continue previous conversation
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