@sylphx/flow 1.8.2 → 2.1.0

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 (37) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/UPGRADE.md +151 -0
  3. package/package.json +11 -6
  4. package/src/commands/flow/execute-v2.ts +372 -0
  5. package/src/commands/flow/execute.ts +1 -18
  6. package/src/commands/flow/types.ts +3 -2
  7. package/src/commands/flow-command.ts +32 -69
  8. package/src/commands/flow-orchestrator.ts +18 -55
  9. package/src/commands/run-command.ts +12 -6
  10. package/src/commands/settings-command.ts +536 -0
  11. package/src/config/ai-config.ts +2 -69
  12. package/src/config/targets.ts +0 -11
  13. package/src/core/attach-manager.ts +495 -0
  14. package/src/core/backup-manager.ts +308 -0
  15. package/src/core/cleanup-handler.ts +166 -0
  16. package/src/core/flow-executor.ts +323 -0
  17. package/src/core/git-stash-manager.ts +133 -0
  18. package/src/core/installers/file-installer.ts +0 -57
  19. package/src/core/installers/mcp-installer.ts +0 -33
  20. package/src/core/project-manager.ts +274 -0
  21. package/src/core/secrets-manager.ts +229 -0
  22. package/src/core/session-manager.ts +268 -0
  23. package/src/core/template-loader.ts +189 -0
  24. package/src/core/upgrade-manager.ts +79 -47
  25. package/src/index.ts +15 -29
  26. package/src/services/auto-upgrade.ts +248 -0
  27. package/src/services/first-run-setup.ts +220 -0
  28. package/src/services/global-config.ts +337 -0
  29. package/src/services/target-installer.ts +254 -0
  30. package/src/targets/claude-code.ts +5 -7
  31. package/src/targets/opencode.ts +6 -26
  32. package/src/utils/__tests__/package-manager-detector.test.ts +163 -0
  33. package/src/utils/agent-enhancer.ts +40 -22
  34. package/src/utils/errors.ts +9 -0
  35. package/src/utils/package-manager-detector.ts +139 -0
  36. package/src/utils/prompt-helpers.ts +48 -0
  37. package/src/utils/target-selection.ts +169 -0
@@ -8,6 +8,7 @@ import type { FlowOptions } from './flow/types.js';
8
8
  import { StateDetector, type ProjectState } from '../core/state-detector.js';
9
9
  import { UpgradeManager } from '../core/upgrade-manager.js';
10
10
  import { targetManager } from '../core/target-manager.js';
11
+ import { detectPackageManager, getUpgradeCommand } from '../utils/package-manager-detector.js';
11
12
 
12
13
  /**
13
14
  * Step 1: Check for available upgrades
@@ -18,71 +19,33 @@ export async function checkUpgrades(
18
19
  ): Promise<void> {
19
20
  if (options.initOnly || options.runOnly) return;
20
21
 
22
+ const upgradeManager = new UpgradeManager();
23
+ const updates = await upgradeManager.checkUpdates();
24
+ const packageManager = detectPackageManager();
25
+
21
26
  // Check Flow upgrade
22
- if (await UpgradeManager.isUpgradeAvailable()) {
27
+ if (updates.flowUpdate && updates.flowVersion) {
28
+ const upgradeCmd = getUpgradeCommand('@sylphx/flow', packageManager);
23
29
  console.log(
24
30
  chalk.yellow(
25
- `šŸ“¦ Sylphx Flow update available: ${state.version} → ${state.latestVersion}\n`
31
+ `šŸ“¦ Sylphx Flow update available: ${updates.flowVersion.current} → ${updates.flowVersion.latest}`
26
32
  )
27
33
  );
28
- const { default: inquirer } = await import('inquirer');
29
- const { upgrade } = await inquirer.prompt([
30
- {
31
- type: 'confirm',
32
- name: 'upgrade',
33
- message: 'Upgrade Sylphx Flow now?',
34
- default: true,
35
- },
36
- ]);
37
- if (upgrade) {
38
- options.upgrade = true;
39
- }
34
+ console.log(chalk.dim(` Quick upgrade: ${chalk.cyan('sylphx-flow upgrade --auto')}`));
35
+ console.log(chalk.dim(` Or run: ${chalk.cyan(upgradeCmd)}\n`));
40
36
  }
41
37
 
42
- // Check target upgrade (if target exists and outdated)
43
- if (state.target && state.targetVersion && state.targetLatestVersion &&
44
- state.targetVersion !== state.targetLatestVersion) {
45
- // Simple version comparison
46
- const isOutdated = compareVersions(state.targetVersion, state.targetLatestVersion) < 0;
47
-
48
- if (isOutdated) {
49
- console.log(
50
- chalk.yellow(
51
- `šŸ“¦ ${state.target} update available: ${state.targetVersion} → ${state.targetLatestVersion}\n`
52
- )
53
- );
54
- const { default: inquirer } = await import('inquirer');
55
- const { upgradeTarget } = await inquirer.prompt([
56
- {
57
- type: 'confirm',
58
- name: 'upgradeTarget',
59
- message: `Upgrade ${state.target} now?`,
60
- default: true,
61
- },
62
- ]);
63
- if (upgradeTarget) {
64
- options.upgradeTarget = true;
65
- }
66
- }
38
+ // Check target upgrade
39
+ if (updates.targetUpdate && updates.targetVersion) {
40
+ console.log(
41
+ chalk.yellow(
42
+ `šŸ“¦ Target update available: ${updates.targetVersion.current} → ${updates.targetVersion.latest}`
43
+ )
44
+ );
45
+ console.log(chalk.dim(` Run: ${chalk.cyan('sylphx-flow upgrade --target --auto')}\n`));
67
46
  }
68
47
  }
69
48
 
70
- /**
71
- * Compare two version strings
72
- */
73
- function compareVersions(v1: string, v2: string): number {
74
- const parts1 = v1.split('.').map(Number);
75
- const parts2 = v2.split('.').map(Number);
76
-
77
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
78
- const p1 = parts1[i] || 0;
79
- const p2 = parts2[i] || 0;
80
- if (p1 !== p2) {
81
- return p1 - p2;
82
- }
83
- }
84
- return 0;
85
- }
86
49
 
87
50
  /**
88
51
  * Step 2: Check component integrity and prompt for repair
@@ -5,14 +5,20 @@ import { targetManager } from '../core/target-manager.js';
5
5
  import { CLIError } from '../utils/error-handler.js';
6
6
  import { getAgentsDir } from '../utils/config/paths.js';
7
7
 
8
- export async function loadAgentContent(agentName: string, agentFilePath?: string): Promise<string> {
8
+ export async function loadAgentContent(
9
+ agentName: string,
10
+ agentFilePath?: string,
11
+ enabledRules?: string[],
12
+ enabledOutputStyles?: string[]
13
+ ): Promise<string> {
9
14
  const { enhanceAgentContent } = await import('../utils/agent-enhancer.js');
10
15
 
11
16
  try {
12
17
  // If specific file path provided, load from there
13
18
  if (agentFilePath) {
14
19
  const content = await fs.readFile(path.resolve(agentFilePath), 'utf-8');
15
- return content;
20
+ // Enhance with enabled rules and styles
21
+ return await enhanceAgentContent(content, enabledRules, enabledOutputStyles);
16
22
  }
17
23
 
18
24
  // First try to load from .claude/agents/ directory (processed agents with rules and styles)
@@ -27,16 +33,16 @@ export async function loadAgentContent(agentName: string, agentFilePath?: string
27
33
 
28
34
  try {
29
35
  const content = await fs.readFile(localAgentPath, 'utf-8');
30
- // Enhance user-defined agents with rules and styles
31
- return await enhanceAgentContent(content);
36
+ // Enhance user-defined agents with enabled rules and styles
37
+ return await enhanceAgentContent(content, enabledRules, enabledOutputStyles);
32
38
  } catch (_error2) {
33
39
  // Try to load from the package's agents directory
34
40
  const packageAgentsDir = getAgentsDir();
35
41
  const packageAgentPath = path.join(packageAgentsDir, `${agentName}.md`);
36
42
 
37
43
  const content = await fs.readFile(packageAgentPath, 'utf-8');
38
- // Enhance package agents with rules and styles
39
- return await enhanceAgentContent(content);
44
+ // Enhance package agents with enabled rules and styles
45
+ return await enhanceAgentContent(content, enabledRules, enabledOutputStyles);
40
46
  }
41
47
  }
42
48
  } catch (_error) {
@@ -0,0 +1,536 @@
1
+ /**
2
+ * Settings Command
3
+ * Interactive configuration for Sylphx Flow
4
+ */
5
+
6
+ import { Command } from 'commander';
7
+ import chalk from 'chalk';
8
+ import inquirer from 'inquirer';
9
+ import { GlobalConfigService } from '../services/global-config.js';
10
+ import { UserCancelledError } from '../utils/errors.js';
11
+ import { TargetInstaller } from '../services/target-installer.js';
12
+ import { promptForDefaultTarget, buildAvailableTargets } from '../utils/target-selection.js';
13
+
14
+ export const settingsCommand = new Command('settings')
15
+ .description('Configure Sylphx Flow settings')
16
+ .option('--section <section>', 'Directly open a section (mcp, provider, target, general)')
17
+ .action(async (options) => {
18
+ try {
19
+ const configService = new GlobalConfigService();
20
+ await configService.initialize();
21
+
22
+ console.log(chalk.cyan.bold('\n╭─ Sylphx Flow Settings ─────────────────────────╮'));
23
+ console.log(chalk.cyan.bold('│ │'));
24
+ console.log(chalk.cyan.bold('│ Configure your Flow environment │'));
25
+ console.log(chalk.cyan.bold('│ │'));
26
+ console.log(chalk.cyan.bold('╰─────────────────────────────────────────────────╯\n'));
27
+
28
+ if (options.section) {
29
+ await openSection(options.section, configService);
30
+ } else {
31
+ await showMainMenu(configService);
32
+ }
33
+ } catch (error: any) {
34
+ // Handle user cancellation (Ctrl+C)
35
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
36
+ throw new UserCancelledError('Settings cancelled by user');
37
+ }
38
+ throw error;
39
+ }
40
+ });
41
+
42
+ /**
43
+ * Show main settings menu
44
+ */
45
+ async function showMainMenu(configService: GlobalConfigService): Promise<void> {
46
+ while (true) {
47
+ const { choice } = await inquirer.prompt([
48
+ {
49
+ type: 'list',
50
+ name: 'choice',
51
+ message: 'What would you like to configure?',
52
+ choices: [
53
+ { name: 'šŸ¤– Agents & Default Agent', value: 'agents' },
54
+ { name: 'šŸ“‹ Rules', value: 'rules' },
55
+ { name: 'šŸŽØ Output Styles', value: 'outputStyles' },
56
+ new inquirer.Separator(),
57
+ { name: 'šŸ“” MCP Servers', value: 'mcp' },
58
+ { name: 'šŸ”‘ Provider & API Keys (Claude Code)', value: 'provider' },
59
+ { name: 'šŸŽÆ Target Platform', value: 'target' },
60
+ { name: 'āš™ļø General Settings', value: 'general' },
61
+ new inquirer.Separator(),
62
+ { name: '← Back / Exit', value: 'exit' },
63
+ ],
64
+ },
65
+ ]);
66
+
67
+ if (choice === 'exit') {
68
+ console.log(chalk.green('\nāœ“ Settings saved\n'));
69
+ break;
70
+ }
71
+
72
+ await openSection(choice, configService);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Open specific settings section
78
+ */
79
+ async function openSection(section: string, configService: GlobalConfigService): Promise<void> {
80
+ switch (section) {
81
+ case 'agents':
82
+ await configureAgents(configService);
83
+ break;
84
+ case 'rules':
85
+ await configureRules(configService);
86
+ break;
87
+ case 'outputStyles':
88
+ await configureOutputStyles(configService);
89
+ break;
90
+ case 'mcp':
91
+ await configureMCP(configService);
92
+ break;
93
+ case 'provider':
94
+ await configureProvider(configService);
95
+ break;
96
+ case 'target':
97
+ await configureTarget(configService);
98
+ break;
99
+ case 'general':
100
+ await configureGeneral(configService);
101
+ break;
102
+ default:
103
+ console.log(chalk.red(`Unknown section: ${section}`));
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Configure Agents
109
+ */
110
+ async function configureAgents(configService: GlobalConfigService): Promise<void> {
111
+ console.log(chalk.cyan.bold('\n━━━ šŸ¤– Agent Configuration\n'));
112
+
113
+ const flowConfig = await configService.loadFlowConfig();
114
+ const settings = await configService.loadSettings();
115
+ const currentAgents = flowConfig.agents || {};
116
+
117
+ // Available agents
118
+ const availableAgents = {
119
+ coder: 'Coder - Write and modify code',
120
+ writer: 'Writer - Documentation and explanation',
121
+ reviewer: 'Reviewer - Code review and critique',
122
+ orchestrator: 'Orchestrator - Task coordination',
123
+ };
124
+
125
+ // Get current enabled agents
126
+ const currentEnabled = Object.keys(currentAgents).filter(
127
+ (key) => currentAgents[key].enabled
128
+ );
129
+
130
+ const { selectedAgents } = await inquirer.prompt([
131
+ {
132
+ type: 'checkbox',
133
+ name: 'selectedAgents',
134
+ message: 'Select agents to enable:',
135
+ choices: Object.entries(availableAgents).map(([key, name]) => ({
136
+ name,
137
+ value: key,
138
+ checked: currentEnabled.includes(key),
139
+ })),
140
+ },
141
+ ]);
142
+
143
+ // Update agents
144
+ for (const key of Object.keys(availableAgents)) {
145
+ if (selectedAgents.includes(key)) {
146
+ currentAgents[key] = { enabled: true };
147
+ } else {
148
+ currentAgents[key] = { enabled: false };
149
+ }
150
+ }
151
+
152
+ // Select default agent
153
+ const { defaultAgent } = await inquirer.prompt([
154
+ {
155
+ type: 'list',
156
+ name: 'defaultAgent',
157
+ message: 'Select default agent:',
158
+ choices: selectedAgents.map((key: string) => ({
159
+ name: availableAgents[key as keyof typeof availableAgents],
160
+ value: key,
161
+ })),
162
+ default: settings.defaultAgent || 'coder',
163
+ },
164
+ ]);
165
+
166
+ flowConfig.agents = currentAgents;
167
+ await configService.saveFlowConfig(flowConfig);
168
+
169
+ settings.defaultAgent = defaultAgent;
170
+ await configService.saveSettings(settings);
171
+
172
+ console.log(chalk.green(`\nāœ“ Agent configuration saved`));
173
+ console.log(chalk.dim(` Enabled agents: ${selectedAgents.length}`));
174
+ console.log(chalk.dim(` Default agent: ${defaultAgent}`));
175
+ }
176
+
177
+ /**
178
+ * Configure Rules
179
+ */
180
+ async function configureRules(configService: GlobalConfigService): Promise<void> {
181
+ console.log(chalk.cyan.bold('\n━━━ šŸ“‹ Rules Configuration\n'));
182
+
183
+ const flowConfig = await configService.loadFlowConfig();
184
+ const currentRules = flowConfig.rules || {};
185
+
186
+ // Available rules
187
+ const availableRules = {
188
+ core: 'Core - Identity, personality, execution',
189
+ 'code-standards': 'Code Standards - Quality, patterns, anti-patterns',
190
+ workspace: 'Workspace - Documentation management',
191
+ };
192
+
193
+ // Get current enabled rules
194
+ const currentEnabled = Object.keys(currentRules).filter(
195
+ (key) => currentRules[key].enabled
196
+ );
197
+
198
+ const { selectedRules } = await inquirer.prompt([
199
+ {
200
+ type: 'checkbox',
201
+ name: 'selectedRules',
202
+ message: 'Select rules to enable:',
203
+ choices: Object.entries(availableRules).map(([key, name]) => ({
204
+ name,
205
+ value: key,
206
+ checked: currentEnabled.includes(key),
207
+ })),
208
+ },
209
+ ]);
210
+
211
+ // Update rules
212
+ for (const key of Object.keys(availableRules)) {
213
+ if (selectedRules.includes(key)) {
214
+ currentRules[key] = { enabled: true };
215
+ } else {
216
+ currentRules[key] = { enabled: false };
217
+ }
218
+ }
219
+
220
+ flowConfig.rules = currentRules;
221
+ await configService.saveFlowConfig(flowConfig);
222
+
223
+ console.log(chalk.green(`\nāœ“ Rules configuration saved`));
224
+ console.log(chalk.dim(` Enabled rules: ${selectedRules.length}`));
225
+ }
226
+
227
+ /**
228
+ * Configure Output Styles
229
+ */
230
+ async function configureOutputStyles(configService: GlobalConfigService): Promise<void> {
231
+ console.log(chalk.cyan.bold('\n━━━ šŸŽØ Output Styles Configuration\n'));
232
+
233
+ const flowConfig = await configService.loadFlowConfig();
234
+ const currentStyles = flowConfig.outputStyles || {};
235
+
236
+ // Available output styles
237
+ const availableStyles = {
238
+ silent: 'Silent - Execution without narration',
239
+ };
240
+
241
+ // Get current enabled styles
242
+ const currentEnabled = Object.keys(currentStyles).filter(
243
+ (key) => currentStyles[key].enabled
244
+ );
245
+
246
+ const { selectedStyles } = await inquirer.prompt([
247
+ {
248
+ type: 'checkbox',
249
+ name: 'selectedStyles',
250
+ message: 'Select output styles to enable:',
251
+ choices: Object.entries(availableStyles).map(([key, name]) => ({
252
+ name,
253
+ value: key,
254
+ checked: currentEnabled.includes(key),
255
+ })),
256
+ },
257
+ ]);
258
+
259
+ // Update styles
260
+ for (const key of Object.keys(availableStyles)) {
261
+ if (selectedStyles.includes(key)) {
262
+ currentStyles[key] = { enabled: true };
263
+ } else {
264
+ currentStyles[key] = { enabled: false };
265
+ }
266
+ }
267
+
268
+ flowConfig.outputStyles = currentStyles;
269
+ await configService.saveFlowConfig(flowConfig);
270
+
271
+ console.log(chalk.green(`\nāœ“ Output styles configuration saved`));
272
+ console.log(chalk.dim(` Enabled styles: ${selectedStyles.length}`));
273
+ }
274
+
275
+ /**
276
+ * Configure MCP servers
277
+ */
278
+ async function configureMCP(configService: GlobalConfigService): Promise<void> {
279
+ console.log(chalk.cyan.bold('\n━━━ šŸ“” MCP Server Configuration\n'));
280
+
281
+ const mcpConfig = await configService.loadMCPConfig();
282
+ const currentServers = mcpConfig.servers || {};
283
+
284
+ // Available MCP servers (from MCP_SERVER_REGISTRY)
285
+ 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'] },
291
+ };
292
+
293
+ // Get current enabled servers
294
+ const currentEnabled = Object.keys(currentServers).filter(
295
+ (key) => currentServers[key].enabled
296
+ );
297
+
298
+ const { selectedServers } = await inquirer.prompt([
299
+ {
300
+ type: 'checkbox',
301
+ name: 'selectedServers',
302
+ message: 'Select MCP servers to enable:',
303
+ choices: Object.entries(availableServers).map(([key, info]) => {
304
+ const requiresText = info.requiresEnv.length > 0
305
+ ? chalk.dim(` (requires ${info.requiresEnv.join(', ')})`)
306
+ : '';
307
+ return {
308
+ name: `${info.name}${requiresText}`,
309
+ value: key,
310
+ checked: currentEnabled.includes(key),
311
+ };
312
+ }),
313
+ },
314
+ ]);
315
+
316
+ // Update servers
317
+ for (const key of Object.keys(availableServers)) {
318
+ if (selectedServers.includes(key)) {
319
+ if (!currentServers[key]) {
320
+ currentServers[key] = { enabled: true, env: {} };
321
+ } else {
322
+ currentServers[key].enabled = true;
323
+ }
324
+ } else if (currentServers[key]) {
325
+ currentServers[key].enabled = false;
326
+ }
327
+ }
328
+
329
+ // Ask for API keys for newly enabled servers
330
+ for (const serverKey of selectedServers) {
331
+ const serverInfo = availableServers[serverKey as keyof typeof availableServers];
332
+ if (serverInfo.requiresEnv.length > 0) {
333
+ const server = currentServers[serverKey];
334
+
335
+ for (const envKey of serverInfo.requiresEnv) {
336
+ const hasKey = server.env && server.env[envKey];
337
+
338
+ const { shouldConfigure } = await inquirer.prompt([
339
+ {
340
+ type: 'confirm',
341
+ name: 'shouldConfigure',
342
+ message: hasKey
343
+ ? `Update ${envKey} for ${serverInfo.name}?`
344
+ : `Configure ${envKey} for ${serverInfo.name}?`,
345
+ default: !hasKey,
346
+ },
347
+ ]);
348
+
349
+ if (shouldConfigure) {
350
+ const { apiKey } = await inquirer.prompt([
351
+ {
352
+ type: 'password',
353
+ name: 'apiKey',
354
+ message: `Enter ${envKey}:`,
355
+ mask: '*',
356
+ },
357
+ ]);
358
+
359
+ if (!server.env) {
360
+ server.env = {};
361
+ }
362
+ server.env[envKey] = apiKey;
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ mcpConfig.servers = currentServers;
369
+ await configService.saveMCPConfig(mcpConfig);
370
+
371
+ console.log(chalk.green(`\nāœ“ MCP configuration saved`));
372
+ console.log(chalk.dim(` Enabled servers: ${selectedServers.length}`));
373
+ }
374
+
375
+ /**
376
+ * Configure provider settings (Claude Code)
377
+ */
378
+ async function configureProvider(configService: GlobalConfigService): Promise<void> {
379
+ console.log(chalk.cyan.bold('\n━━━ šŸ”‘ Provider Configuration (Claude Code)\n'));
380
+
381
+ const providerConfig = await configService.loadProviderConfig();
382
+
383
+ const { defaultProvider } = await inquirer.prompt([
384
+ {
385
+ type: 'list',
386
+ name: 'defaultProvider',
387
+ message: 'Select default provider:',
388
+ choices: [
389
+ { name: 'Default (Claude Code built-in)', value: 'default' },
390
+ { name: 'Kimi', value: 'kimi' },
391
+ { name: 'Z.ai', value: 'zai' },
392
+ new inquirer.Separator(),
393
+ { name: 'Ask me every time', value: 'ask-every-time' },
394
+ ],
395
+ default: providerConfig.claudeCode.defaultProvider,
396
+ },
397
+ ]);
398
+
399
+ providerConfig.claudeCode.defaultProvider = defaultProvider;
400
+
401
+ // Configure API keys if provider selected
402
+ if (defaultProvider === 'kimi' || defaultProvider === 'zai') {
403
+ const currentKey = providerConfig.claudeCode.providers[defaultProvider]?.apiKey;
404
+
405
+ const { shouldConfigure } = await inquirer.prompt([
406
+ {
407
+ type: 'confirm',
408
+ name: 'shouldConfigure',
409
+ message: currentKey ? `Update ${defaultProvider} API key?` : `Configure ${defaultProvider} API key?`,
410
+ default: !currentKey,
411
+ },
412
+ ]);
413
+
414
+ if (shouldConfigure) {
415
+ const { apiKey } = await inquirer.prompt([
416
+ {
417
+ type: 'password',
418
+ name: 'apiKey',
419
+ message: 'Enter API key:',
420
+ mask: '*',
421
+ },
422
+ ]);
423
+
424
+ if (!providerConfig.claudeCode.providers[defaultProvider]) {
425
+ providerConfig.claudeCode.providers[defaultProvider] = { enabled: true };
426
+ }
427
+ providerConfig.claudeCode.providers[defaultProvider]!.apiKey = apiKey;
428
+ providerConfig.claudeCode.providers[defaultProvider]!.enabled = true;
429
+ }
430
+ }
431
+
432
+ await configService.saveProviderConfig(providerConfig);
433
+
434
+ console.log(chalk.green('\nāœ“ Provider configuration saved'));
435
+ console.log(chalk.dim(` Default provider: ${defaultProvider}`));
436
+ }
437
+
438
+ /**
439
+ * Configure target platform
440
+ */
441
+ async function configureTarget(configService: GlobalConfigService): Promise<void> {
442
+ console.log(chalk.cyan.bold('\n━━━ šŸŽÆ Target Platform\n'));
443
+
444
+ const settings = await configService.loadSettings();
445
+ const targetInstaller = new TargetInstaller();
446
+
447
+ // Detect which targets are installed
448
+ console.log(chalk.dim('Detecting installed AI CLIs...\n'));
449
+ const installedTargets = await targetInstaller.detectInstalledTargets();
450
+
451
+ const defaultTarget = await promptForDefaultTarget(installedTargets, settings.defaultTarget);
452
+
453
+ settings.defaultTarget = defaultTarget as 'claude-code' | 'opencode' | 'cursor' | 'ask-every-time';
454
+ await configService.saveSettings(settings);
455
+
456
+ if (defaultTarget === 'ask-every-time') {
457
+ console.log(chalk.green('\nāœ“ Target platform saved'));
458
+ console.log(chalk.dim(' Default: Ask every time (auto-detect or prompt)'));
459
+ } else {
460
+ const availableTargets = buildAvailableTargets(installedTargets);
461
+ const selectedTarget = availableTargets.find((t) => t.value === defaultTarget);
462
+ const installStatus = selectedTarget?.installed
463
+ ? chalk.green('(installed)')
464
+ : chalk.yellow('(will be installed on first use)');
465
+
466
+ console.log(chalk.green('\nāœ“ Target platform saved'));
467
+ console.log(chalk.dim(` Default: ${defaultTarget} ${installStatus}`));
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Configure general settings
473
+ */
474
+ async function configureGeneral(configService: GlobalConfigService): Promise<void> {
475
+ console.log(chalk.cyan.bold('\n━━━ āš™ļø General Settings\n'));
476
+
477
+ const settings = await configService.loadSettings();
478
+
479
+ console.log(chalk.dim('Flow Home Directory:'), configService.getFlowHomeDir());
480
+ console.log(chalk.dim('Version:'), settings.version);
481
+ console.log(chalk.dim('Last Updated:'), new Date(settings.lastUpdated).toLocaleString());
482
+
483
+ const { action } = await inquirer.prompt([
484
+ {
485
+ type: 'list',
486
+ name: 'action',
487
+ message: 'What would you like to do?',
488
+ choices: [
489
+ { name: 'Reset to defaults', value: 'reset' },
490
+ { name: 'Show current configuration', value: 'show' },
491
+ new inquirer.Separator(),
492
+ { name: '← Back', value: 'back' },
493
+ ],
494
+ },
495
+ ]);
496
+
497
+ if (action === 'reset') {
498
+ const { confirm } = await inquirer.prompt([
499
+ {
500
+ type: 'confirm',
501
+ name: 'confirm',
502
+ message: 'Are you sure you want to reset all settings to defaults?',
503
+ default: false,
504
+ },
505
+ ]);
506
+
507
+ if (confirm) {
508
+ await configService.saveSettings({
509
+ version: '1.0.0',
510
+ firstRun: false,
511
+ lastUpdated: new Date().toISOString(),
512
+ });
513
+ await configService.saveProviderConfig({
514
+ claudeCode: {
515
+ defaultProvider: 'ask-every-time',
516
+ providers: {
517
+ kimi: { enabled: false },
518
+ zai: { enabled: false },
519
+ },
520
+ },
521
+ });
522
+ await configService.saveMCPConfig({
523
+ version: '1.0.0',
524
+ servers: {},
525
+ });
526
+
527
+ console.log(chalk.green('\nāœ“ Settings reset to defaults'));
528
+ }
529
+ } else if (action === 'show') {
530
+ const providerConfig = await configService.loadProviderConfig();
531
+ const mcpConfig = await configService.loadMCPConfig();
532
+
533
+ console.log(chalk.cyan('\nCurrent Configuration:'));
534
+ console.log(JSON.stringify({ settings, providerConfig, mcpConfig }, null, 2));
535
+ }
536
+ }