@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.
- package/CHANGELOG.md +159 -0
- package/UPGRADE.md +151 -0
- package/package.json +11 -6
- package/src/commands/flow/execute-v2.ts +372 -0
- package/src/commands/flow/execute.ts +1 -18
- package/src/commands/flow/types.ts +3 -2
- package/src/commands/flow-command.ts +32 -69
- package/src/commands/flow-orchestrator.ts +18 -55
- package/src/commands/run-command.ts +12 -6
- package/src/commands/settings-command.ts +536 -0
- package/src/config/ai-config.ts +2 -69
- package/src/config/targets.ts +0 -11
- package/src/core/attach-manager.ts +495 -0
- package/src/core/backup-manager.ts +308 -0
- package/src/core/cleanup-handler.ts +166 -0
- package/src/core/flow-executor.ts +323 -0
- package/src/core/git-stash-manager.ts +133 -0
- package/src/core/installers/file-installer.ts +0 -57
- package/src/core/installers/mcp-installer.ts +0 -33
- package/src/core/project-manager.ts +274 -0
- package/src/core/secrets-manager.ts +229 -0
- package/src/core/session-manager.ts +268 -0
- package/src/core/template-loader.ts +189 -0
- package/src/core/upgrade-manager.ts +79 -47
- package/src/index.ts +15 -29
- package/src/services/auto-upgrade.ts +248 -0
- package/src/services/first-run-setup.ts +220 -0
- package/src/services/global-config.ts +337 -0
- package/src/services/target-installer.ts +254 -0
- package/src/targets/claude-code.ts +5 -7
- package/src/targets/opencode.ts +6 -26
- package/src/utils/__tests__/package-manager-detector.test.ts +163 -0
- package/src/utils/agent-enhancer.ts +40 -22
- package/src/utils/errors.ts +9 -0
- package/src/utils/package-manager-detector.ts +139 -0
- package/src/utils/prompt-helpers.ts +48 -0
- 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 (
|
|
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: ${
|
|
31
|
+
`š¦ Sylphx Flow update available: ${updates.flowVersion.current} ā ${updates.flowVersion.latest}`
|
|
26
32
|
)
|
|
27
33
|
);
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
+
}
|