@sylphx/flow 1.8.1 → 2.0.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.
@@ -0,0 +1,529 @@
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
+
12
+ export const settingsCommand = new Command('settings')
13
+ .description('Configure Sylphx Flow settings')
14
+ .option('--section <section>', 'Directly open a section (mcp, provider, target, general)')
15
+ .action(async (options) => {
16
+ try {
17
+ const configService = new GlobalConfigService();
18
+ await configService.initialize();
19
+
20
+ console.log(chalk.cyan.bold('\n╭─ Sylphx Flow Settings ─────────────────────────╮'));
21
+ console.log(chalk.cyan.bold('│ │'));
22
+ console.log(chalk.cyan.bold('│ Configure your Flow environment │'));
23
+ console.log(chalk.cyan.bold('│ │'));
24
+ console.log(chalk.cyan.bold('╰─────────────────────────────────────────────────╯\n'));
25
+
26
+ if (options.section) {
27
+ await openSection(options.section, configService);
28
+ } else {
29
+ await showMainMenu(configService);
30
+ }
31
+ } catch (error: any) {
32
+ // Handle user cancellation (Ctrl+C)
33
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
34
+ throw new UserCancelledError('Settings cancelled by user');
35
+ }
36
+ throw error;
37
+ }
38
+ });
39
+
40
+ /**
41
+ * Show main settings menu
42
+ */
43
+ async function showMainMenu(configService: GlobalConfigService): Promise<void> {
44
+ while (true) {
45
+ const { choice } = await inquirer.prompt([
46
+ {
47
+ type: 'list',
48
+ name: 'choice',
49
+ message: 'What would you like to configure?',
50
+ choices: [
51
+ { name: '🤖 Agents & Default Agent', value: 'agents' },
52
+ { name: '📋 Rules', value: 'rules' },
53
+ { name: '🎨 Output Styles', value: 'outputStyles' },
54
+ new inquirer.Separator(),
55
+ { name: '📡 MCP Servers', value: 'mcp' },
56
+ { name: '🔑 Provider & API Keys (Claude Code)', value: 'provider' },
57
+ { name: '🎯 Target Platform', value: 'target' },
58
+ { name: '⚙️ General Settings', value: 'general' },
59
+ new inquirer.Separator(),
60
+ { name: '← Back / Exit', value: 'exit' },
61
+ ],
62
+ },
63
+ ]);
64
+
65
+ if (choice === 'exit') {
66
+ console.log(chalk.green('\n✓ Settings saved\n'));
67
+ break;
68
+ }
69
+
70
+ await openSection(choice, configService);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Open specific settings section
76
+ */
77
+ async function openSection(section: string, configService: GlobalConfigService): Promise<void> {
78
+ switch (section) {
79
+ case 'agents':
80
+ await configureAgents(configService);
81
+ break;
82
+ case 'rules':
83
+ await configureRules(configService);
84
+ break;
85
+ case 'outputStyles':
86
+ await configureOutputStyles(configService);
87
+ break;
88
+ case 'mcp':
89
+ await configureMCP(configService);
90
+ break;
91
+ case 'provider':
92
+ await configureProvider(configService);
93
+ break;
94
+ case 'target':
95
+ await configureTarget(configService);
96
+ break;
97
+ case 'general':
98
+ await configureGeneral(configService);
99
+ break;
100
+ default:
101
+ console.log(chalk.red(`Unknown section: ${section}`));
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Configure Agents
107
+ */
108
+ async function configureAgents(configService: GlobalConfigService): Promise<void> {
109
+ console.log(chalk.cyan.bold('\n━━━ 🤖 Agent Configuration\n'));
110
+
111
+ const flowConfig = await configService.loadFlowConfig();
112
+ const settings = await configService.loadSettings();
113
+ const currentAgents = flowConfig.agents || {};
114
+
115
+ // Available agents
116
+ const availableAgents = {
117
+ coder: 'Coder - Write and modify code',
118
+ writer: 'Writer - Documentation and explanation',
119
+ reviewer: 'Reviewer - Code review and critique',
120
+ orchestrator: 'Orchestrator - Task coordination',
121
+ };
122
+
123
+ // Get current enabled agents
124
+ const currentEnabled = Object.keys(currentAgents).filter(
125
+ (key) => currentAgents[key].enabled
126
+ );
127
+
128
+ const { selectedAgents } = await inquirer.prompt([
129
+ {
130
+ type: 'checkbox',
131
+ name: 'selectedAgents',
132
+ message: 'Select agents to enable:',
133
+ choices: Object.entries(availableAgents).map(([key, name]) => ({
134
+ name,
135
+ value: key,
136
+ checked: currentEnabled.includes(key),
137
+ })),
138
+ },
139
+ ]);
140
+
141
+ // Update agents
142
+ for (const key of Object.keys(availableAgents)) {
143
+ if (selectedAgents.includes(key)) {
144
+ currentAgents[key] = { enabled: true };
145
+ } else {
146
+ currentAgents[key] = { enabled: false };
147
+ }
148
+ }
149
+
150
+ // Select default agent
151
+ const { defaultAgent } = await inquirer.prompt([
152
+ {
153
+ type: 'list',
154
+ name: 'defaultAgent',
155
+ message: 'Select default agent:',
156
+ choices: selectedAgents.map((key: string) => ({
157
+ name: availableAgents[key as keyof typeof availableAgents],
158
+ value: key,
159
+ })),
160
+ default: settings.defaultAgent || 'coder',
161
+ },
162
+ ]);
163
+
164
+ flowConfig.agents = currentAgents;
165
+ await configService.saveFlowConfig(flowConfig);
166
+
167
+ settings.defaultAgent = defaultAgent;
168
+ await configService.saveSettings(settings);
169
+
170
+ console.log(chalk.green(`\n✓ Agent configuration saved`));
171
+ console.log(chalk.dim(` Enabled agents: ${selectedAgents.length}`));
172
+ console.log(chalk.dim(` Default agent: ${defaultAgent}`));
173
+ }
174
+
175
+ /**
176
+ * Configure Rules
177
+ */
178
+ async function configureRules(configService: GlobalConfigService): Promise<void> {
179
+ console.log(chalk.cyan.bold('\n━━━ 📋 Rules Configuration\n'));
180
+
181
+ const flowConfig = await configService.loadFlowConfig();
182
+ const currentRules = flowConfig.rules || {};
183
+
184
+ // Available rules
185
+ const availableRules = {
186
+ core: 'Core - Identity, personality, execution',
187
+ 'code-standards': 'Code Standards - Quality, patterns, anti-patterns',
188
+ workspace: 'Workspace - Documentation management',
189
+ };
190
+
191
+ // Get current enabled rules
192
+ const currentEnabled = Object.keys(currentRules).filter(
193
+ (key) => currentRules[key].enabled
194
+ );
195
+
196
+ const { selectedRules } = await inquirer.prompt([
197
+ {
198
+ type: 'checkbox',
199
+ name: 'selectedRules',
200
+ message: 'Select rules to enable:',
201
+ choices: Object.entries(availableRules).map(([key, name]) => ({
202
+ name,
203
+ value: key,
204
+ checked: currentEnabled.includes(key),
205
+ })),
206
+ },
207
+ ]);
208
+
209
+ // Update rules
210
+ for (const key of Object.keys(availableRules)) {
211
+ if (selectedRules.includes(key)) {
212
+ currentRules[key] = { enabled: true };
213
+ } else {
214
+ currentRules[key] = { enabled: false };
215
+ }
216
+ }
217
+
218
+ flowConfig.rules = currentRules;
219
+ await configService.saveFlowConfig(flowConfig);
220
+
221
+ console.log(chalk.green(`\n✓ Rules configuration saved`));
222
+ console.log(chalk.dim(` Enabled rules: ${selectedRules.length}`));
223
+ }
224
+
225
+ /**
226
+ * Configure Output Styles
227
+ */
228
+ async function configureOutputStyles(configService: GlobalConfigService): Promise<void> {
229
+ console.log(chalk.cyan.bold('\n━━━ 🎨 Output Styles Configuration\n'));
230
+
231
+ const flowConfig = await configService.loadFlowConfig();
232
+ const currentStyles = flowConfig.outputStyles || {};
233
+
234
+ // Available output styles
235
+ const availableStyles = {
236
+ silent: 'Silent - Execution without narration',
237
+ };
238
+
239
+ // Get current enabled styles
240
+ const currentEnabled = Object.keys(currentStyles).filter(
241
+ (key) => currentStyles[key].enabled
242
+ );
243
+
244
+ const { selectedStyles } = await inquirer.prompt([
245
+ {
246
+ type: 'checkbox',
247
+ name: 'selectedStyles',
248
+ message: 'Select output styles to enable:',
249
+ choices: Object.entries(availableStyles).map(([key, name]) => ({
250
+ name,
251
+ value: key,
252
+ checked: currentEnabled.includes(key),
253
+ })),
254
+ },
255
+ ]);
256
+
257
+ // Update styles
258
+ for (const key of Object.keys(availableStyles)) {
259
+ if (selectedStyles.includes(key)) {
260
+ currentStyles[key] = { enabled: true };
261
+ } else {
262
+ currentStyles[key] = { enabled: false };
263
+ }
264
+ }
265
+
266
+ flowConfig.outputStyles = currentStyles;
267
+ await configService.saveFlowConfig(flowConfig);
268
+
269
+ console.log(chalk.green(`\n✓ Output styles configuration saved`));
270
+ console.log(chalk.dim(` Enabled styles: ${selectedStyles.length}`));
271
+ }
272
+
273
+ /**
274
+ * Configure MCP servers
275
+ */
276
+ async function configureMCP(configService: GlobalConfigService): Promise<void> {
277
+ console.log(chalk.cyan.bold('\n━━━ 📡 MCP Server Configuration\n'));
278
+
279
+ const mcpConfig = await configService.loadMCPConfig();
280
+ const currentServers = mcpConfig.servers || {};
281
+
282
+ // Available MCP servers (from MCP_SERVER_REGISTRY)
283
+ const availableServers = {
284
+ 'grep': { name: 'GitHub Code Search (grep.app)', requiresEnv: [] },
285
+ 'context7': { name: 'Context7 Docs', requiresEnv: [] },
286
+ 'playwright': { name: 'Playwright Browser Control', requiresEnv: [] },
287
+ 'github': { name: 'GitHub', requiresEnv: ['GITHUB_TOKEN'] },
288
+ 'notion': { name: 'Notion', requiresEnv: ['NOTION_API_KEY'] },
289
+ };
290
+
291
+ // Get current enabled servers
292
+ const currentEnabled = Object.keys(currentServers).filter(
293
+ (key) => currentServers[key].enabled
294
+ );
295
+
296
+ const { selectedServers } = await inquirer.prompt([
297
+ {
298
+ type: 'checkbox',
299
+ name: 'selectedServers',
300
+ message: 'Select MCP servers to enable:',
301
+ choices: Object.entries(availableServers).map(([key, info]) => {
302
+ const requiresText = info.requiresEnv.length > 0
303
+ ? chalk.dim(` (requires ${info.requiresEnv.join(', ')})`)
304
+ : '';
305
+ return {
306
+ name: `${info.name}${requiresText}`,
307
+ value: key,
308
+ checked: currentEnabled.includes(key),
309
+ };
310
+ }),
311
+ },
312
+ ]);
313
+
314
+ // Update servers
315
+ for (const key of Object.keys(availableServers)) {
316
+ if (selectedServers.includes(key)) {
317
+ if (!currentServers[key]) {
318
+ currentServers[key] = { enabled: true, env: {} };
319
+ } else {
320
+ currentServers[key].enabled = true;
321
+ }
322
+ } else if (currentServers[key]) {
323
+ currentServers[key].enabled = false;
324
+ }
325
+ }
326
+
327
+ // Ask for API keys for newly enabled servers
328
+ for (const serverKey of selectedServers) {
329
+ const serverInfo = availableServers[serverKey as keyof typeof availableServers];
330
+ if (serverInfo.requiresEnv.length > 0) {
331
+ const server = currentServers[serverKey];
332
+
333
+ for (const envKey of serverInfo.requiresEnv) {
334
+ const hasKey = server.env && server.env[envKey];
335
+
336
+ const { shouldConfigure } = await inquirer.prompt([
337
+ {
338
+ type: 'confirm',
339
+ name: 'shouldConfigure',
340
+ message: hasKey
341
+ ? `Update ${envKey} for ${serverInfo.name}?`
342
+ : `Configure ${envKey} for ${serverInfo.name}?`,
343
+ default: !hasKey,
344
+ },
345
+ ]);
346
+
347
+ if (shouldConfigure) {
348
+ const { apiKey } = await inquirer.prompt([
349
+ {
350
+ type: 'password',
351
+ name: 'apiKey',
352
+ message: `Enter ${envKey}:`,
353
+ mask: '*',
354
+ },
355
+ ]);
356
+
357
+ if (!server.env) {
358
+ server.env = {};
359
+ }
360
+ server.env[envKey] = apiKey;
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ mcpConfig.servers = currentServers;
367
+ await configService.saveMCPConfig(mcpConfig);
368
+
369
+ console.log(chalk.green(`\n✓ MCP configuration saved`));
370
+ console.log(chalk.dim(` Enabled servers: ${selectedServers.length}`));
371
+ }
372
+
373
+ /**
374
+ * Configure provider settings (Claude Code)
375
+ */
376
+ async function configureProvider(configService: GlobalConfigService): Promise<void> {
377
+ console.log(chalk.cyan.bold('\n━━━ 🔑 Provider Configuration (Claude Code)\n'));
378
+
379
+ const providerConfig = await configService.loadProviderConfig();
380
+
381
+ const { defaultProvider } = await inquirer.prompt([
382
+ {
383
+ type: 'list',
384
+ name: 'defaultProvider',
385
+ message: 'Select default provider:',
386
+ choices: [
387
+ { name: 'Default (Claude Code built-in)', value: 'default' },
388
+ { name: 'Kimi', value: 'kimi' },
389
+ { name: 'Z.ai', value: 'zai' },
390
+ new inquirer.Separator(),
391
+ { name: 'Ask me every time', value: 'ask-every-time' },
392
+ ],
393
+ default: providerConfig.claudeCode.defaultProvider,
394
+ },
395
+ ]);
396
+
397
+ providerConfig.claudeCode.defaultProvider = defaultProvider;
398
+
399
+ // Configure API keys if provider selected
400
+ if (defaultProvider === 'kimi' || defaultProvider === 'zai') {
401
+ const currentKey = providerConfig.claudeCode.providers[defaultProvider]?.apiKey;
402
+
403
+ const { shouldConfigure } = await inquirer.prompt([
404
+ {
405
+ type: 'confirm',
406
+ name: 'shouldConfigure',
407
+ message: currentKey ? `Update ${defaultProvider} API key?` : `Configure ${defaultProvider} API key?`,
408
+ default: !currentKey,
409
+ },
410
+ ]);
411
+
412
+ if (shouldConfigure) {
413
+ const { apiKey } = await inquirer.prompt([
414
+ {
415
+ type: 'password',
416
+ name: 'apiKey',
417
+ message: 'Enter API key:',
418
+ mask: '*',
419
+ },
420
+ ]);
421
+
422
+ if (!providerConfig.claudeCode.providers[defaultProvider]) {
423
+ providerConfig.claudeCode.providers[defaultProvider] = { enabled: true };
424
+ }
425
+ providerConfig.claudeCode.providers[defaultProvider]!.apiKey = apiKey;
426
+ providerConfig.claudeCode.providers[defaultProvider]!.enabled = true;
427
+ }
428
+ }
429
+
430
+ await configService.saveProviderConfig(providerConfig);
431
+
432
+ console.log(chalk.green('\n✓ Provider configuration saved'));
433
+ console.log(chalk.dim(` Default provider: ${defaultProvider}`));
434
+ }
435
+
436
+ /**
437
+ * Configure target platform
438
+ */
439
+ async function configureTarget(configService: GlobalConfigService): Promise<void> {
440
+ console.log(chalk.cyan.bold('\n━━━ 🎯 Target Platform\n'));
441
+
442
+ const settings = await configService.loadSettings();
443
+
444
+ const { defaultTarget } = await inquirer.prompt([
445
+ {
446
+ type: 'list',
447
+ name: 'defaultTarget',
448
+ message: 'Select default target platform:',
449
+ choices: [
450
+ { name: 'Claude Code', value: 'claude-code' },
451
+ { name: 'OpenCode', value: 'opencode' },
452
+ ],
453
+ default: settings.defaultTarget || 'claude-code',
454
+ },
455
+ ]);
456
+
457
+ settings.defaultTarget = defaultTarget;
458
+ await configService.saveSettings(settings);
459
+
460
+ console.log(chalk.green('\n✓ Target platform saved'));
461
+ console.log(chalk.dim(` Default: ${defaultTarget}`));
462
+ }
463
+
464
+ /**
465
+ * Configure general settings
466
+ */
467
+ async function configureGeneral(configService: GlobalConfigService): Promise<void> {
468
+ console.log(chalk.cyan.bold('\n━━━ ⚙️ General Settings\n'));
469
+
470
+ const settings = await configService.loadSettings();
471
+
472
+ console.log(chalk.dim('Flow Home Directory:'), configService.getFlowHomeDir());
473
+ console.log(chalk.dim('Version:'), settings.version);
474
+ console.log(chalk.dim('Last Updated:'), new Date(settings.lastUpdated).toLocaleString());
475
+
476
+ const { action } = await inquirer.prompt([
477
+ {
478
+ type: 'list',
479
+ name: 'action',
480
+ message: 'What would you like to do?',
481
+ choices: [
482
+ { name: 'Reset to defaults', value: 'reset' },
483
+ { name: 'Show current configuration', value: 'show' },
484
+ new inquirer.Separator(),
485
+ { name: '← Back', value: 'back' },
486
+ ],
487
+ },
488
+ ]);
489
+
490
+ if (action === 'reset') {
491
+ const { confirm } = await inquirer.prompt([
492
+ {
493
+ type: 'confirm',
494
+ name: 'confirm',
495
+ message: 'Are you sure you want to reset all settings to defaults?',
496
+ default: false,
497
+ },
498
+ ]);
499
+
500
+ if (confirm) {
501
+ await configService.saveSettings({
502
+ version: '1.0.0',
503
+ firstRun: false,
504
+ lastUpdated: new Date().toISOString(),
505
+ });
506
+ await configService.saveProviderConfig({
507
+ claudeCode: {
508
+ defaultProvider: 'ask-every-time',
509
+ providers: {
510
+ kimi: { enabled: false },
511
+ zai: { enabled: false },
512
+ },
513
+ },
514
+ });
515
+ await configService.saveMCPConfig({
516
+ version: '1.0.0',
517
+ servers: {},
518
+ });
519
+
520
+ console.log(chalk.green('\n✓ Settings reset to defaults'));
521
+ }
522
+ } else if (action === 'show') {
523
+ const providerConfig = await configService.loadProviderConfig();
524
+ const mcpConfig = await configService.loadMCPConfig();
525
+
526
+ console.log(chalk.cyan('\nCurrent Configuration:'));
527
+ console.log(JSON.stringify({ settings, providerConfig, mcpConfig }, null, 2));
528
+ }
529
+ }