@sylphx/flow 2.1.3 → 2.1.4

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 (66) hide show
  1. package/CHANGELOG.md +12 -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-command.ts +36 -33
  10. package/src/config/ai-config.ts +60 -41
  11. package/src/core/agent-loader.ts +11 -6
  12. package/src/core/attach-manager.ts +92 -84
  13. package/src/core/backup-manager.ts +35 -29
  14. package/src/core/cleanup-handler.ts +11 -8
  15. package/src/core/error-handling.ts +23 -30
  16. package/src/core/flow-executor.ts +58 -76
  17. package/src/core/formatting/bytes.ts +2 -4
  18. package/src/core/functional/async.ts +5 -4
  19. package/src/core/functional/error-handler.ts +2 -2
  20. package/src/core/git-stash-manager.ts +21 -10
  21. package/src/core/installers/file-installer.ts +0 -1
  22. package/src/core/installers/mcp-installer.ts +0 -1
  23. package/src/core/project-manager.ts +24 -18
  24. package/src/core/secrets-manager.ts +54 -73
  25. package/src/core/session-manager.ts +20 -22
  26. package/src/core/state-detector.ts +139 -80
  27. package/src/core/template-loader.ts +13 -31
  28. package/src/core/upgrade-manager.ts +122 -69
  29. package/src/index.ts +8 -5
  30. package/src/services/auto-upgrade.ts +1 -1
  31. package/src/services/config-service.ts +41 -29
  32. package/src/services/global-config.ts +2 -2
  33. package/src/services/target-installer.ts +9 -7
  34. package/src/targets/claude-code.ts +24 -12
  35. package/src/targets/opencode.ts +17 -6
  36. package/src/types/cli.types.ts +2 -2
  37. package/src/types/provider.types.ts +1 -7
  38. package/src/types/session.types.ts +11 -11
  39. package/src/types/target.types.ts +3 -1
  40. package/src/types/todo.types.ts +1 -1
  41. package/src/types.ts +1 -1
  42. package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
  43. package/src/utils/agent-enhancer.ts +4 -4
  44. package/src/utils/config/paths.ts +3 -1
  45. package/src/utils/config/target-utils.ts +2 -2
  46. package/src/utils/display/banner.ts +2 -2
  47. package/src/utils/display/notifications.ts +58 -45
  48. package/src/utils/display/status.ts +29 -12
  49. package/src/utils/files/file-operations.ts +1 -1
  50. package/src/utils/files/sync-utils.ts +38 -41
  51. package/src/utils/index.ts +19 -27
  52. package/src/utils/package-manager-detector.ts +15 -5
  53. package/src/utils/security/security.ts +8 -4
  54. package/src/utils/target-selection.ts +5 -2
  55. package/src/utils/version.ts +4 -2
  56. package/src/commands/flow-orchestrator.ts +0 -328
  57. package/src/commands/init-command.ts +0 -92
  58. package/src/commands/init-core.ts +0 -331
  59. package/src/core/agent-manager.ts +0 -174
  60. package/src/core/loop-controller.ts +0 -200
  61. package/src/core/rule-loader.ts +0 -147
  62. package/src/core/rule-manager.ts +0 -240
  63. package/src/services/claude-config-service.ts +0 -252
  64. package/src/services/first-run-setup.ts +0 -220
  65. package/src/services/smart-config-service.ts +0 -269
  66. package/src/types/api.types.ts +0 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sylphx/flow
2
2
 
3
+ ## 2.1.4 (2025-11-28)
4
+
5
+ ### ♻️ Refactoring
6
+
7
+ - **flow:** eliminate hardcoded target checks with Target interface ([1dc75f9](https://github.com/SylphxAI/flow/commit/1dc75f9d4936b51554b1d09bf8576f832ce131e9))
8
+
9
+ ### 🔧 Chores
10
+
11
+ - apply @sylphx/doctor fixes for 100% health score ([ae55969](https://github.com/SylphxAI/flow/commit/ae5596924dab48675ff3100b40f67651e7ebe26f))
12
+ - remove unused api.types.ts re-export file ([ad8f6a6](https://github.com/SylphxAI/flow/commit/ad8f6a6b8dcad75d2c0201f2286e52adccb728c7))
13
+ - remove dead code and unused modules ([6eaa904](https://github.com/SylphxAI/flow/commit/6eaa90438dcb40f9508953e874bf8c04204ae017))
14
+
3
15
  ## 2.1.3
4
16
 
5
17
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @sylphx/flow
2
+
3
+ **One CLI to rule them all.**
4
+
5
+ Unified orchestration layer for Claude Code, OpenCode, Cursor and all AI development tools. Auto-detection, auto-installation, auto-upgrade.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g @sylphx/flow
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```bash
16
+ # Run with a prompt
17
+ sylphx-flow "your prompt here"
18
+
19
+ # Configure settings
20
+ sylphx-flow settings
21
+
22
+ # Check status
23
+ sylphx-flow status
24
+ ```
25
+
26
+ ## Features
27
+
28
+ - **Auto-detect** - Automatically detects installed AI CLIs
29
+ - **Auto-install** - Installs missing dependencies on demand
30
+ - **Auto-upgrade** - Upgrades before each session
31
+ - **Unified settings** - One configuration for all tools
32
+ - **MEP system** - Minimal Effective Prompts for optimal context
33
+
34
+ ## Documentation
35
+
36
+ See the [main repository](https://github.com/sylphxltd/flow) for full documentation.
37
+
38
+ ## License
39
+
40
+ MIT License - see [LICENSE](../../LICENSE) file for details.
41
+
42
+ ---
43
+
44
+ Powered by [@sylphx](https://github.com/SylphxAI)
package/package.json CHANGED
@@ -1,75 +1,81 @@
1
1
  {
2
- "name": "@sylphx/flow",
3
- "version": "2.1.3",
4
- "description": "One CLI to rule them all. Unified orchestration layer for Claude Code, OpenCode, Cursor and all AI development tools. Auto-detection, auto-installation, auto-upgrade.",
5
- "type": "module",
6
- "bin": {
7
- "sylphx-flow": "./src/index.ts"
8
- },
9
- "engines": {
10
- "node": ">=18.0.0"
11
- },
12
- "scripts": {
13
- "dev": "bun src/index.ts",
14
- "start": "bun src/index.ts",
15
- "test": "vitest run",
16
- "test:watch": "vitest",
17
- "type-check": "tsc --noEmit",
18
- "prepublishOnly": "echo 'Using assets from packages/flow/assets'"
19
- },
20
- "dependencies": {
21
- "commander": "^14.0.2",
22
- "chalk": "^5.6.2",
23
- "boxen": "^8.0.1",
24
- "gradient-string": "^3.0.0",
25
- "ora": "^9.0.0",
26
- "inquirer": "^12.10.0",
27
- "gray-matter": "^4.0.3",
28
- "yaml": "^2.8.1",
29
- "zod": "^4.1.12",
30
- "debug": "^4.4.3"
31
- },
32
- "devDependencies": {
33
- "@types/node": "^24.9.2",
34
- "typescript": "^5.9.3",
35
- "vitest": "^4.0.6"
36
- },
37
- "publishConfig": {
38
- "access": "public"
39
- },
40
- "files": [
41
- "src",
42
- "assets",
43
- "README.md",
44
- "CHANGELOG.md",
45
- "LOOP_MODE.md",
46
- "UPGRADE.md",
47
- "package.json"
48
- ],
49
- "keywords": [
50
- "ai",
51
- "automation",
52
- "workflow",
53
- "claude-code",
54
- "opencode",
55
- "cursor",
56
- "cli",
57
- "orchestration",
58
- "unified",
59
- "meta-layer",
60
- "developer-tools",
61
- "auto-install",
62
- "auto-upgrade"
63
- ],
64
- "repository": {
65
- "type": "git",
66
- "url": "https://github.com/sylphxltd/flow.git",
67
- "directory": "packages/flow"
68
- },
69
- "bugs": {
70
- "url": "https://github.com/sylphxltd/flow/issues"
71
- },
72
- "homepage": "https://github.com/sylphxltd/flow#readme",
73
- "license": "MIT",
74
- "author": "sylphxltd"
2
+ "name": "@sylphx/flow",
3
+ "version": "2.1.4",
4
+ "description": "One CLI to rule them all. Unified orchestration layer for Claude Code, OpenCode, Cursor and all AI development tools. Auto-detection, auto-installation, auto-upgrade.",
5
+ "type": "module",
6
+ "bin": {
7
+ "sylphx-flow": "./src/index.ts"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "import": "./src/index.ts",
12
+ "types": "./src/index.ts"
13
+ }
14
+ },
15
+ "engines": {
16
+ "node": ">=18.0.0"
17
+ },
18
+ "scripts": {
19
+ "dev": "bun src/index.ts",
20
+ "start": "bun src/index.ts",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "type-check": "tsc --noEmit",
24
+ "prepublishOnly": "echo 'Using assets from packages/flow/assets'"
25
+ },
26
+ "dependencies": {
27
+ "commander": "^14.0.2",
28
+ "chalk": "^5.6.2",
29
+ "boxen": "^8.0.1",
30
+ "gradient-string": "^3.0.0",
31
+ "ora": "^9.0.0",
32
+ "inquirer": "^12.10.0",
33
+ "gray-matter": "^4.0.3",
34
+ "yaml": "^2.8.1",
35
+ "zod": "^4.1.12",
36
+ "debug": "^4.4.3"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^24.9.2",
40
+ "typescript": "^5.9.3",
41
+ "vitest": "^4.0.6"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "files": [
47
+ "src",
48
+ "assets",
49
+ "README.md",
50
+ "CHANGELOG.md",
51
+ "LOOP_MODE.md",
52
+ "UPGRADE.md",
53
+ "package.json"
54
+ ],
55
+ "keywords": [
56
+ "ai",
57
+ "automation",
58
+ "workflow",
59
+ "claude-code",
60
+ "opencode",
61
+ "cursor",
62
+ "cli",
63
+ "orchestration",
64
+ "unified",
65
+ "meta-layer",
66
+ "developer-tools",
67
+ "auto-install",
68
+ "auto-upgrade"
69
+ ],
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "https://github.com/sylphxltd/flow.git",
73
+ "directory": "packages/flow"
74
+ },
75
+ "bugs": {
76
+ "url": "https://github.com/sylphxltd/flow/issues"
77
+ },
78
+ "homepage": "https://github.com/sylphxltd/flow#readme",
79
+ "license": "MIT",
80
+ "author": "sylphxltd"
75
81
  }
@@ -7,26 +7,22 @@ import chalk from 'chalk';
7
7
  import inquirer from 'inquirer';
8
8
  import { FlowExecutor } from '../../core/flow-executor.js';
9
9
  import { targetManager } from '../../core/target-manager.js';
10
- import { UpgradeManager } from '../../core/upgrade-manager.js';
10
+ import { AutoUpgrade } from '../../services/auto-upgrade.js';
11
+ import { GlobalConfigService } from '../../services/global-config.js';
12
+ import { TargetInstaller } from '../../services/target-installer.js';
13
+ import type { RunCommandOptions } from '../../types.js';
14
+ import { extractAgentInstructions, loadAgentContent } from '../../utils/agent-enhancer.js';
11
15
  import { showWelcome } from '../../utils/display/banner.js';
12
- import { loadAgentContent, extractAgentInstructions } from '../../utils/agent-enhancer.js';
13
16
  import { CLIError } from '../../utils/error-handler.js';
14
- import type { RunCommandOptions } from '../../types.js';
15
- import type { FlowOptions } from './types.js';
16
- import { resolvePrompt } from './prompt.js';
17
- import { GlobalConfigService } from '../../services/global-config.js';
18
17
  import { UserCancelledError } from '../../utils/errors.js';
19
- import { TargetInstaller } from '../../services/target-installer.js';
20
- import { AutoUpgrade } from '../../services/auto-upgrade.js';
21
- import { promptForTargetSelection, ensureTargetInstalled } from '../../utils/target-selection.js';
18
+ import { ensureTargetInstalled, promptForTargetSelection } from '../../utils/target-selection.js';
19
+ import { resolvePrompt } from './prompt.js';
20
+ import type { FlowOptions } from './types.js';
22
21
 
23
22
  /**
24
23
  * Configure provider environment variables
25
24
  */
26
- function configureProviderEnv(
27
- provider: 'kimi' | 'zai',
28
- apiKey: string
29
- ): void {
25
+ function configureProviderEnv(provider: 'kimi' | 'zai', apiKey: string): void {
30
26
  const providerConfig = {
31
27
  kimi: {
32
28
  baseUrl: 'https://api.moonshot.cn/v1',
@@ -46,9 +42,7 @@ function configureProviderEnv(
46
42
  /**
47
43
  * Select and configure provider for Claude Code
48
44
  */
49
- async function selectProvider(
50
- configService: GlobalConfigService
51
- ): Promise<void> {
45
+ async function selectProvider(configService: GlobalConfigService): Promise<void> {
52
46
  try {
53
47
  const providerConfig = await configService.loadProviderConfig();
54
48
  const defaultProvider = providerConfig.claudeCode.defaultProvider;
@@ -95,9 +89,10 @@ async function selectProvider(
95
89
  } else {
96
90
  console.log(chalk.green('✓ Using default Claude Code provider\n'));
97
91
  }
98
- } catch (error: any) {
92
+ } catch (error: unknown) {
99
93
  // Handle user cancellation (Ctrl+C)
100
- if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
94
+ const err = error as Error & { name?: string };
95
+ if (err.name === 'ExitPromptError' || err.message?.includes('force closed')) {
101
96
  throw new UserCancelledError('Provider selection cancelled');
102
97
  }
103
98
  throw error;
@@ -107,7 +102,7 @@ async function selectProvider(
107
102
  /**
108
103
  * Execute command using target's executeCommand method
109
104
  */
110
- async function executeTargetCommand(
105
+ function executeTargetCommand(
111
106
  targetId: string,
112
107
  systemPrompt: string,
113
108
  userPrompt: string,
@@ -173,7 +168,11 @@ export async function executeFlowV2(
173
168
  );
174
169
 
175
170
  const installation = targetInstaller.getInstallationInfo(selectedTargetId);
176
- const installed = await ensureTargetInstalled(selectedTargetId, targetInstaller, installedTargets);
171
+ const installed = await ensureTargetInstalled(
172
+ selectedTargetId,
173
+ targetInstaller,
174
+ installedTargets
175
+ );
177
176
 
178
177
  if (!installed) {
179
178
  process.exit(1);
@@ -200,7 +199,11 @@ export async function executeFlowV2(
200
199
  );
201
200
 
202
201
  const installation = targetInstaller.getInstallationInfo(selectedTargetId);
203
- const installed = await ensureTargetInstalled(selectedTargetId, targetInstaller, installedTargets);
202
+ const installed = await ensureTargetInstalled(
203
+ selectedTargetId,
204
+ targetInstaller,
205
+ installedTargets
206
+ );
204
207
 
205
208
  if (!installed) {
206
209
  process.exit(1);
@@ -225,7 +228,11 @@ export async function executeFlowV2(
225
228
 
226
229
  if (!installed) {
227
230
  // Installation failed - show error and exit
228
- console.log(chalk.red(`\n✗ Cannot proceed: ${installation?.name} is not installed and auto-install failed`));
231
+ console.log(
232
+ chalk.red(
233
+ `\n✗ Cannot proceed: ${installation?.name} is not installed and auto-install failed`
234
+ )
235
+ );
229
236
  console.log(chalk.yellow(' Please either:'));
230
237
  console.log(chalk.cyan(' 1. Install manually (see instructions above)'));
231
238
  console.log(chalk.cyan(' 2. Change default target: sylphx-flow settings\n'));
@@ -242,16 +249,20 @@ export async function executeFlowV2(
242
249
 
243
250
  // Mode info
244
251
  if (options.merge) {
245
- console.log(chalk.cyan('🔗 Merge mode: Flow settings will be merged with your existing settings'));
252
+ console.log(
253
+ chalk.cyan('🔗 Merge mode: Flow settings will be merged with your existing settings')
254
+ );
246
255
  console.log(chalk.dim(' Settings will be restored after execution\n'));
247
256
  } else {
248
- console.log(chalk.yellow('🔄 Replace mode (default): All settings will use Flow configuration'));
257
+ console.log(
258
+ chalk.yellow('🔄 Replace mode (default): All settings will use Flow configuration')
259
+ );
249
260
  console.log(chalk.dim(' Use --merge to keep your existing settings\n'));
250
261
  }
251
262
 
252
263
  // Create executor
253
264
  const executor = new FlowExecutor();
254
- const projectManager = executor.getProjectManager();
265
+ const _projectManager = executor.getProjectManager();
255
266
 
256
267
  // Step 2: Execute attach mode lifecycle
257
268
  try {
@@ -361,10 +372,7 @@ export async function executeFlowV2(
361
372
  /**
362
373
  * Main flow execution entry point
363
374
  */
364
- export async function executeFlow(
365
- prompt: string | undefined,
366
- options: FlowOptions
367
- ): Promise<void> {
375
+ export async function executeFlow(prompt: string | undefined, options: FlowOptions): Promise<void> {
368
376
  // Resolve prompt (handle file input)
369
377
  const resolvedPrompt = await resolvePrompt(prompt);
370
378
 
@@ -3,8 +3,8 @@
3
3
  * Handle file input (@file.txt) and prompt loading
4
4
  */
5
5
 
6
- import path from 'node:path';
7
6
  import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
8
  import chalk from 'chalk';
9
9
 
10
10
  /**
@@ -12,7 +12,9 @@ import chalk from 'chalk';
12
12
  * Supports @filename syntax: @prompt.txt or @/path/to/prompt.txt
13
13
  */
14
14
  export async function resolvePrompt(prompt: string | undefined): Promise<string | undefined> {
15
- if (!prompt) return prompt;
15
+ if (!prompt) {
16
+ return prompt;
17
+ }
16
18
 
17
19
  // Check for file input syntax: @filename
18
20
  if (prompt.startsWith('@')) {
@@ -26,7 +28,7 @@ export async function resolvePrompt(prompt: string | undefined): Promise<string
26
28
  const content = await fs.readFile(resolvedPath, 'utf-8');
27
29
  console.log(chalk.dim(` ✓ Loaded prompt from: ${filePath}\n`));
28
30
  return content.trim();
29
- } catch (error) {
31
+ } catch (_error) {
30
32
  throw new Error(`Failed to read prompt file: ${filePath}`);
31
33
  }
32
34
  }
@@ -2,8 +2,6 @@
2
2
  * Flow Command Types
3
3
  */
4
4
 
5
- import type { RunCommandOptions } from '../../types.js';
6
-
7
5
  export interface FlowOptions {
8
6
  target?: string;
9
7
  verbose?: boolean;
@@ -3,10 +3,10 @@
3
3
  * Entry point for all flow-related CLI commands
4
4
  */
5
5
 
6
- import { Command } from 'commander';
7
- import chalk from 'chalk';
8
- import path from 'node:path';
9
6
  import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import chalk from 'chalk';
9
+ import { Command } from 'commander';
10
10
  import { StateDetector } from '../core/state-detector.js';
11
11
  import { UpgradeManager } from '../core/upgrade-manager.js';
12
12
  import { showWelcome } from '../utils/display/banner.js';
@@ -30,7 +30,10 @@ export const flowCommand = new Command('flow')
30
30
  .option('--merge', 'Merge Flow settings with existing settings (default: replace all)')
31
31
 
32
32
  // Prompt argument
33
- .argument('[prompt]', 'Prompt to execute with agent (optional, supports @file.txt for file input)')
33
+ .argument(
34
+ '[prompt]',
35
+ 'Prompt to execute with agent (optional, supports @file.txt for file input)'
36
+ )
34
37
 
35
38
  .action(async (prompt, options: FlowOptions) => {
36
39
  await executeFlow(prompt, options);
@@ -41,7 +44,7 @@ export const flowCommand = new Command('flow')
41
44
  */
42
45
  export const setupCommand = new Command('setup')
43
46
  .description('[DEPRECATED] No longer needed - Flow uses automatic attach mode')
44
- .action(async () => {
47
+ .action(() => {
45
48
  console.log(chalk.yellow('⚠️ The "setup" command is deprecated.\n'));
46
49
  console.log(chalk.cyan('Flow now uses automatic attach mode:'));
47
50
  console.log(chalk.dim(' • No installation needed'));
@@ -118,23 +121,23 @@ export const doctorCommand = new Command('doctor')
118
121
  await executeFlow(undefined, { sync: true } as FlowOptions);
119
122
  console.log(chalk.green(' ✓ 已修复'));
120
123
  }
121
- } else if (!state.initialized) {
124
+ } else if (state.initialized) {
125
+ console.log(chalk.green(' ✓ 配置正常'));
126
+ } else {
122
127
  console.log(chalk.yellow(' ⚠ 项目未初始化'));
123
128
  issuesFound = true;
124
- } else {
125
- console.log(chalk.green(' ✓ 配置正常'));
126
129
  }
127
130
 
128
131
  // Check 3: Components
129
132
  console.log('\n检查组件...');
130
133
  Object.entries(state.components).forEach(([name, component]) => {
131
134
  const status = component.installed ? chalk.green('✓') : chalk.red('✗');
132
- const count = ('count' in component && component.count) ? ` (${component.count})` : '';
135
+ const count = 'count' in component && component.count ? ` (${component.count})` : '';
133
136
  console.log(` ${status} ${name}${count}`);
134
137
  });
135
138
 
136
139
  // Summary
137
- console.log('\n' + chalk.bold('结果:'));
140
+ console.log(`\n${chalk.bold('结果:')}`);
138
141
  if (!issuesFound) {
139
142
  console.log(chalk.green('✓ 所有检查通过'));
140
143
  } else if (options.fix) {
@@ -168,16 +171,20 @@ export const upgradeCommand = new Command('upgrade')
168
171
  }
169
172
 
170
173
  if (updates.flowVersion) {
171
- console.log(`Sylphx Flow: ${updates.flowVersion.current} → ${chalk.green(updates.flowVersion.latest)}`);
174
+ console.log(
175
+ `Sylphx Flow: ${updates.flowVersion.current} → ${chalk.green(updates.flowVersion.latest)}`
176
+ );
172
177
  }
173
178
 
174
179
  if (updates.targetVersion) {
175
- console.log(`${updates.targetVersion.current ? 'claude-code' : 'target'}: ${updates.targetVersion.current} → ${chalk.green(updates.targetVersion.latest)}`);
180
+ console.log(
181
+ `${updates.targetVersion.current ? 'claude-code' : 'target'}: ${updates.targetVersion.current} → ${chalk.green(updates.targetVersion.latest)}`
182
+ );
176
183
  }
177
184
 
178
185
  // Check only
179
186
  if (options.check) {
180
- console.log('\n' + chalk.dim('Run without --check to upgrade'));
187
+ console.log(`\n${chalk.dim('Run without --check to upgrade')}`);
181
188
  return;
182
189
  }
183
190
 
@@ -153,9 +153,7 @@ async function sendLinuxNotification(title: string, message: string): Promise<vo
153
153
  try {
154
154
  await execAsync('which notify-send');
155
155
  // Use Flow-themed spiral emoji as icon for Sylphx Flow
156
- await execAsync(
157
- `notify-send -i "🌀" "${escapeForShell(title)}" "${escapeForShell(message)}"`
158
- );
156
+ await execAsync(`notify-send -i "🌀" "${escapeForShell(title)}" "${escapeForShell(message)}"`);
159
157
  } catch {
160
158
  // notify-send not available, skip notification silently
161
159
  }
@@ -3,13 +3,13 @@
3
3
  * Interactive configuration for Sylphx Flow
4
4
  */
5
5
 
6
- import { Command } from 'commander';
7
6
  import chalk from 'chalk';
7
+ import { Command } from 'commander';
8
8
  import inquirer from 'inquirer';
9
9
  import { GlobalConfigService } from '../services/global-config.js';
10
- import { UserCancelledError } from '../utils/errors.js';
11
10
  import { TargetInstaller } from '../services/target-installer.js';
12
- import { promptForDefaultTarget, buildAvailableTargets } from '../utils/target-selection.js';
11
+ import { UserCancelledError } from '../utils/errors.js';
12
+ import { buildAvailableTargets, promptForDefaultTarget } from '../utils/target-selection.js';
13
13
 
14
14
  export const settingsCommand = new Command('settings')
15
15
  .description('Configure Sylphx Flow settings')
@@ -30,9 +30,10 @@ export const settingsCommand = new Command('settings')
30
30
  } else {
31
31
  await showMainMenu(configService);
32
32
  }
33
- } catch (error: any) {
33
+ } catch (error: unknown) {
34
34
  // Handle user cancellation (Ctrl+C)
35
- if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
35
+ const err = error as Error & { name?: string };
36
+ if (err.name === 'ExitPromptError' || err.message?.includes('force closed')) {
36
37
  throw new UserCancelledError('Settings cancelled by user');
37
38
  }
38
39
  throw error;
@@ -123,9 +124,7 @@ async function configureAgents(configService: GlobalConfigService): Promise<void
123
124
  };
124
125
 
125
126
  // Get current enabled agents
126
- const currentEnabled = Object.keys(currentAgents).filter(
127
- (key) => currentAgents[key].enabled
128
- );
127
+ const currentEnabled = Object.keys(currentAgents).filter((key) => currentAgents[key].enabled);
129
128
 
130
129
  const { selectedAgents } = await inquirer.prompt([
131
130
  {
@@ -191,9 +190,7 @@ async function configureRules(configService: GlobalConfigService): Promise<void>
191
190
  };
192
191
 
193
192
  // Get current enabled rules
194
- const currentEnabled = Object.keys(currentRules).filter(
195
- (key) => currentRules[key].enabled
196
- );
193
+ const currentEnabled = Object.keys(currentRules).filter((key) => currentRules[key].enabled);
197
194
 
198
195
  const { selectedRules } = await inquirer.prompt([
199
196
  {
@@ -239,9 +236,7 @@ async function configureOutputStyles(configService: GlobalConfigService): Promis
239
236
  };
240
237
 
241
238
  // Get current enabled styles
242
- const currentEnabled = Object.keys(currentStyles).filter(
243
- (key) => currentStyles[key].enabled
244
- );
239
+ const currentEnabled = Object.keys(currentStyles).filter((key) => currentStyles[key].enabled);
245
240
 
246
241
  const { selectedStyles } = await inquirer.prompt([
247
242
  {
@@ -283,17 +278,15 @@ async function configureMCP(configService: GlobalConfigService): Promise<void> {
283
278
 
284
279
  // Available MCP servers (from MCP_SERVER_REGISTRY)
285
280
  const availableServers = {
286
- 'grep': { name: 'GitHub Code Search (grep.app)', requiresEnv: [] },
287
- 'context7': { name: 'Context7 Docs', requiresEnv: [] },
288
- 'playwright': { name: 'Playwright Browser Control', requiresEnv: [] },
289
- 'github': { name: 'GitHub', requiresEnv: ['GITHUB_TOKEN'] },
290
- 'notion': { name: 'Notion', requiresEnv: ['NOTION_API_KEY'] },
281
+ grep: { name: 'GitHub Code Search (grep.app)', requiresEnv: [] },
282
+ context7: { name: 'Context7 Docs', requiresEnv: [] },
283
+ playwright: { name: 'Playwright Browser Control', requiresEnv: [] },
284
+ github: { name: 'GitHub', requiresEnv: ['GITHUB_TOKEN'] },
285
+ notion: { name: 'Notion', requiresEnv: ['NOTION_API_KEY'] },
291
286
  };
292
287
 
293
288
  // Get current enabled servers
294
- const currentEnabled = Object.keys(currentServers).filter(
295
- (key) => currentServers[key].enabled
296
- );
289
+ const currentEnabled = Object.keys(currentServers).filter((key) => currentServers[key].enabled);
297
290
 
298
291
  const { selectedServers } = await inquirer.prompt([
299
292
  {
@@ -301,9 +294,10 @@ async function configureMCP(configService: GlobalConfigService): Promise<void> {
301
294
  name: 'selectedServers',
302
295
  message: 'Select MCP servers to enable:',
303
296
  choices: Object.entries(availableServers).map(([key, info]) => {
304
- const requiresText = info.requiresEnv.length > 0
305
- ? chalk.dim(` (requires ${info.requiresEnv.join(', ')})`)
306
- : '';
297
+ const requiresText =
298
+ info.requiresEnv.length > 0
299
+ ? chalk.dim(` (requires ${info.requiresEnv.join(', ')})`)
300
+ : '';
307
301
  return {
308
302
  name: `${info.name}${requiresText}`,
309
303
  value: key,
@@ -316,10 +310,10 @@ async function configureMCP(configService: GlobalConfigService): Promise<void> {
316
310
  // Update servers
317
311
  for (const key of Object.keys(availableServers)) {
318
312
  if (selectedServers.includes(key)) {
319
- if (!currentServers[key]) {
320
- currentServers[key] = { enabled: true, env: {} };
321
- } else {
313
+ if (currentServers[key]) {
322
314
  currentServers[key].enabled = true;
315
+ } else {
316
+ currentServers[key] = { enabled: true, env: {} };
323
317
  }
324
318
  } else if (currentServers[key]) {
325
319
  currentServers[key].enabled = false;
@@ -333,7 +327,7 @@ async function configureMCP(configService: GlobalConfigService): Promise<void> {
333
327
  const server = currentServers[serverKey];
334
328
 
335
329
  for (const envKey of serverInfo.requiresEnv) {
336
- const hasKey = server.env && server.env[envKey];
330
+ const hasKey = server.env?.[envKey];
337
331
 
338
332
  const { shouldConfigure } = await inquirer.prompt([
339
333
  {
@@ -406,7 +400,9 @@ async function configureProvider(configService: GlobalConfigService): Promise<vo
406
400
  {
407
401
  type: 'confirm',
408
402
  name: 'shouldConfigure',
409
- message: currentKey ? `Update ${defaultProvider} API key?` : `Configure ${defaultProvider} API key?`,
403
+ message: currentKey
404
+ ? `Update ${defaultProvider} API key?`
405
+ : `Configure ${defaultProvider} API key?`,
410
406
  default: !currentKey,
411
407
  },
412
408
  ]);
@@ -424,8 +420,11 @@ async function configureProvider(configService: GlobalConfigService): Promise<vo
424
420
  if (!providerConfig.claudeCode.providers[defaultProvider]) {
425
421
  providerConfig.claudeCode.providers[defaultProvider] = { enabled: true };
426
422
  }
427
- providerConfig.claudeCode.providers[defaultProvider]!.apiKey = apiKey;
428
- providerConfig.claudeCode.providers[defaultProvider]!.enabled = true;
423
+ const provider = providerConfig.claudeCode.providers[defaultProvider];
424
+ if (provider) {
425
+ provider.apiKey = apiKey;
426
+ provider.enabled = true;
427
+ }
429
428
  }
430
429
  }
431
430
 
@@ -450,7 +449,11 @@ async function configureTarget(configService: GlobalConfigService): Promise<void
450
449
 
451
450
  const defaultTarget = await promptForDefaultTarget(installedTargets, settings.defaultTarget);
452
451
 
453
- settings.defaultTarget = defaultTarget as 'claude-code' | 'opencode' | 'cursor' | 'ask-every-time';
452
+ settings.defaultTarget = defaultTarget as
453
+ | 'claude-code'
454
+ | 'opencode'
455
+ | 'cursor'
456
+ | 'ask-every-time';
454
457
  await configService.saveSettings(settings);
455
458
 
456
459
  if (defaultTarget === 'ask-every-time') {