@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
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* Sylphx Flow
|
|
4
|
-
*
|
|
3
|
+
* Sylphx Flow CLI
|
|
4
|
+
* AI-powered development flow management
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
doctorCommand,
|
|
17
17
|
upgradeCommand,
|
|
18
18
|
} from './commands/flow-command.js';
|
|
19
|
-
import {
|
|
19
|
+
import { settingsCommand } from './commands/settings-command.js';
|
|
20
|
+
import { executeFlow } from './commands/flow/execute-v2.js';
|
|
21
|
+
import { UserCancelledError } from './utils/errors.js';
|
|
20
22
|
|
|
21
23
|
// Read version from package.json
|
|
22
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -52,36 +54,13 @@ export function createCLI(): Command {
|
|
|
52
54
|
// This allows `sylphx-flow "prompt"` instead of requiring `sylphx-flow flow "prompt"`
|
|
53
55
|
program
|
|
54
56
|
.argument('[prompt]', 'Prompt to execute with agent (optional, supports @file.txt for file input)')
|
|
55
|
-
.option('--init-only', 'Only initialize, do not run')
|
|
56
|
-
.option('--run-only', 'Only run, skip initialization')
|
|
57
|
-
.option('--sync', 'Synchronize with Flow templates (delete and re-install template files)')
|
|
58
|
-
.option('--upgrade', 'Upgrade Sylphx Flow to latest version')
|
|
59
|
-
.option('--upgrade-target', 'Upgrade target platform (Claude Code/OpenCode)')
|
|
60
|
-
.option('--quick', 'Quick mode: use saved defaults and skip all prompts')
|
|
61
|
-
.option('--select-provider', 'Prompt to select provider each run')
|
|
62
|
-
.option('--select-agent', 'Prompt to select agent each run')
|
|
63
|
-
.option('--use-defaults', 'Skip prompts, use saved defaults')
|
|
64
|
-
.option('--provider <provider>', 'Override provider for this run (anthropic|z.ai|kimi)')
|
|
65
|
-
.option('--target <type>', 'Target platform (opencode, claude-code, auto-detect)')
|
|
66
|
-
.option('--verbose', 'Show detailed output')
|
|
67
|
-
.option('--dry-run', 'Show what would be done without making changes')
|
|
68
|
-
.option('--no-mcp', 'Skip MCP installation')
|
|
69
|
-
.option('--no-agents', 'Skip agents installation')
|
|
70
|
-
.option('--no-rules', 'Skip rules installation')
|
|
71
|
-
.option('--no-output-styles', 'Skip output styles installation')
|
|
72
|
-
.option('--no-slash-commands', 'Skip slash commands installation')
|
|
73
|
-
.option('--no-hooks', 'Skip hooks setup')
|
|
74
57
|
.option('--agent <name>', 'Agent to use (default: coder)', 'coder')
|
|
75
58
|
.option('--agent-file <path>', 'Load agent from specific file')
|
|
59
|
+
.option('--verbose', 'Show detailed output')
|
|
60
|
+
.option('--dry-run', 'Show what would be done without making changes')
|
|
76
61
|
.option('-p, --print', 'Headless print mode (output only, no interactive)')
|
|
77
62
|
.option('-c, --continue', 'Continue previous conversation (requires print mode)')
|
|
78
|
-
|
|
79
|
-
// Loop mode options
|
|
80
|
-
.option('--loop [seconds]', 'Loop mode: wait N seconds between runs (default: 0 = immediate)', (value) => {
|
|
81
|
-
// If no value provided, default to 0 (no wait time)
|
|
82
|
-
return value ? parseInt(value) : 0;
|
|
83
|
-
})
|
|
84
|
-
.option('--max-runs <count>', 'Maximum iterations before stopping (default: infinite)', parseInt)
|
|
63
|
+
.option('--merge', 'Merge Flow settings with existing settings (default: replace all)')
|
|
85
64
|
|
|
86
65
|
.action(async (prompt, options) => {
|
|
87
66
|
await executeFlow(prompt, options);
|
|
@@ -94,6 +73,7 @@ export function createCLI(): Command {
|
|
|
94
73
|
program.addCommand(doctorCommand);
|
|
95
74
|
program.addCommand(upgradeCommand);
|
|
96
75
|
program.addCommand(hookCommand);
|
|
76
|
+
program.addCommand(settingsCommand);
|
|
97
77
|
|
|
98
78
|
return program;
|
|
99
79
|
}
|
|
@@ -168,6 +148,12 @@ function setupGlobalErrorHandling(): void {
|
|
|
168
148
|
*/
|
|
169
149
|
function handleCommandError(error: unknown): void {
|
|
170
150
|
if (error instanceof Error) {
|
|
151
|
+
// Handle user cancellation gracefully
|
|
152
|
+
if (error instanceof UserCancelledError) {
|
|
153
|
+
console.log('\n⚠️ Operation cancelled by user\n');
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
|
|
171
157
|
// Handle Commander.js specific errors
|
|
172
158
|
if (error.name === 'CommanderError') {
|
|
173
159
|
const commanderError = error as any;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Upgrade Service
|
|
3
|
+
* Automatically checks and upgrades Flow and target CLI before each execution
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import { detectPackageManager, getUpgradeCommand } from '../utils/package-manager-detector.js';
|
|
13
|
+
import { TargetInstaller } from './target-installer.js';
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
export interface UpgradeStatus {
|
|
18
|
+
flowNeedsUpgrade: boolean;
|
|
19
|
+
targetNeedsUpgrade: boolean;
|
|
20
|
+
flowVersion: { current: string; latest: string } | null;
|
|
21
|
+
targetVersion: { current: string; latest: string } | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AutoUpgradeOptions {
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
skipFlow?: boolean;
|
|
27
|
+
skipTarget?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class AutoUpgrade {
|
|
31
|
+
private projectPath: string;
|
|
32
|
+
private options: AutoUpgradeOptions;
|
|
33
|
+
private targetInstaller: TargetInstaller;
|
|
34
|
+
|
|
35
|
+
constructor(projectPath: string = process.cwd(), options: AutoUpgradeOptions = {}) {
|
|
36
|
+
this.projectPath = projectPath;
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.targetInstaller = new TargetInstaller(projectPath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check for available upgrades for Flow and target CLI
|
|
43
|
+
* @param targetId - Optional target CLI ID to check for upgrades
|
|
44
|
+
* @returns Upgrade status indicating what needs upgrading
|
|
45
|
+
*/
|
|
46
|
+
async checkForUpgrades(targetId?: string): Promise<UpgradeStatus> {
|
|
47
|
+
const [flowVersion, targetVersion] = await Promise.all([
|
|
48
|
+
this.options.skipFlow ? null : this.checkFlowVersion(),
|
|
49
|
+
this.options.skipTarget || !targetId ? null : this.checkTargetVersion(targetId),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
flowNeedsUpgrade: !!flowVersion,
|
|
54
|
+
targetNeedsUpgrade: !!targetVersion,
|
|
55
|
+
flowVersion,
|
|
56
|
+
targetVersion,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check Flow version
|
|
62
|
+
*/
|
|
63
|
+
private async checkFlowVersion(): Promise<{ current: string; latest: string } | null> {
|
|
64
|
+
try {
|
|
65
|
+
// Get current version from package.json
|
|
66
|
+
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
|
67
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
68
|
+
const currentVersion = packageJson.version;
|
|
69
|
+
|
|
70
|
+
// Get latest version from npm
|
|
71
|
+
const { stdout } = await execAsync('npm view @sylphx/flow version');
|
|
72
|
+
const latestVersion = stdout.trim();
|
|
73
|
+
|
|
74
|
+
if (currentVersion !== latestVersion) {
|
|
75
|
+
return { current: currentVersion, latest: latestVersion };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check target CLI version
|
|
86
|
+
*/
|
|
87
|
+
private async checkTargetVersion(
|
|
88
|
+
targetId: string
|
|
89
|
+
): Promise<{ current: string; latest: string } | null> {
|
|
90
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
91
|
+
if (!installation) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Get current version
|
|
97
|
+
const { stdout: currentOutput } = await execAsync(installation.checkCommand);
|
|
98
|
+
const currentMatch = currentOutput.match(/v?(\d+\.\d+\.\d+)/);
|
|
99
|
+
if (!currentMatch) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const currentVersion = currentMatch[1];
|
|
103
|
+
|
|
104
|
+
// Get latest version from npm
|
|
105
|
+
const { stdout: latestOutput } = await execAsync(`npm view ${installation.package} version`);
|
|
106
|
+
const latestVersion = latestOutput.trim();
|
|
107
|
+
|
|
108
|
+
if (currentVersion !== latestVersion) {
|
|
109
|
+
return { current: currentVersion, latest: latestVersion };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Upgrade Flow to latest version using detected package manager
|
|
120
|
+
* @returns True if upgrade successful, false otherwise
|
|
121
|
+
*/
|
|
122
|
+
async upgradeFlow(): Promise<boolean> {
|
|
123
|
+
const packageManager = detectPackageManager(this.projectPath);
|
|
124
|
+
const spinner = ora('Upgrading Flow...').start();
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const upgradeCmd = getUpgradeCommand('@sylphx/flow', packageManager);
|
|
128
|
+
await execAsync(upgradeCmd);
|
|
129
|
+
|
|
130
|
+
spinner.succeed(chalk.green('✓ Flow upgraded to latest version'));
|
|
131
|
+
return true;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
spinner.fail(chalk.red('✗ Flow upgrade failed'));
|
|
134
|
+
|
|
135
|
+
if (this.options.verbose) {
|
|
136
|
+
console.error(error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Upgrade target CLI to latest version
|
|
145
|
+
* Tries built-in upgrade command first, falls back to package manager
|
|
146
|
+
* @param targetId - Target CLI ID to upgrade
|
|
147
|
+
* @returns True if upgrade successful, false otherwise
|
|
148
|
+
*/
|
|
149
|
+
async upgradeTarget(targetId: string): Promise<boolean> {
|
|
150
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
151
|
+
if (!installation) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const packageManager = detectPackageManager(this.projectPath);
|
|
156
|
+
const spinner = ora(`Upgrading ${installation.name}...`).start();
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// For Claude Code, use built-in update command if available
|
|
160
|
+
if (targetId === 'claude-code') {
|
|
161
|
+
try {
|
|
162
|
+
await execAsync('claude update');
|
|
163
|
+
spinner.succeed(chalk.green(`✓ ${installation.name} upgraded`));
|
|
164
|
+
return true;
|
|
165
|
+
} catch {
|
|
166
|
+
// Fall back to npm upgrade
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// For OpenCode, use built-in upgrade command if available
|
|
171
|
+
if (targetId === 'opencode') {
|
|
172
|
+
try {
|
|
173
|
+
await execAsync('opencode upgrade');
|
|
174
|
+
spinner.succeed(chalk.green(`✓ ${installation.name} upgraded`));
|
|
175
|
+
return true;
|
|
176
|
+
} catch {
|
|
177
|
+
// Fall back to npm upgrade
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Fall back to npm/bun/pnpm/yarn upgrade
|
|
182
|
+
const upgradeCmd = getUpgradeCommand(installation.package, packageManager);
|
|
183
|
+
await execAsync(upgradeCmd);
|
|
184
|
+
|
|
185
|
+
spinner.succeed(chalk.green(`✓ ${installation.name} upgraded`));
|
|
186
|
+
return true;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
spinner.fail(chalk.red(`✗ ${installation.name} upgrade failed`));
|
|
189
|
+
|
|
190
|
+
if (this.options.verbose) {
|
|
191
|
+
console.error(error);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Run auto-upgrade check and upgrade if needed
|
|
200
|
+
* Shows upgrade status and performs upgrades automatically
|
|
201
|
+
* @param targetId - Optional target CLI ID to check and upgrade
|
|
202
|
+
*/
|
|
203
|
+
async runAutoUpgrade(targetId?: string): Promise<void> {
|
|
204
|
+
console.log(chalk.cyan('🔄 Checking for updates...\n'));
|
|
205
|
+
|
|
206
|
+
const status = await this.checkForUpgrades(targetId);
|
|
207
|
+
|
|
208
|
+
// Show upgrade status
|
|
209
|
+
if (status.flowNeedsUpgrade && status.flowVersion) {
|
|
210
|
+
console.log(
|
|
211
|
+
chalk.yellow(
|
|
212
|
+
`📦 Flow update available: ${status.flowVersion.current} → ${status.flowVersion.latest}`
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
} else if (!this.options.skipFlow) {
|
|
216
|
+
console.log(chalk.green('✓ Flow is up to date'));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (status.targetNeedsUpgrade && status.targetVersion && targetId) {
|
|
220
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
221
|
+
console.log(
|
|
222
|
+
chalk.yellow(
|
|
223
|
+
`📦 ${installation?.name} update available: ${status.targetVersion.current} → ${status.targetVersion.latest}`
|
|
224
|
+
)
|
|
225
|
+
);
|
|
226
|
+
} else if (!this.options.skipTarget && targetId) {
|
|
227
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
228
|
+
console.log(chalk.green(`✓ ${installation?.name} is up to date`));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Perform upgrades if needed
|
|
232
|
+
if (status.flowNeedsUpgrade || status.targetNeedsUpgrade) {
|
|
233
|
+
console.log(chalk.cyan('\n📦 Installing updates...\n'));
|
|
234
|
+
|
|
235
|
+
if (status.flowNeedsUpgrade) {
|
|
236
|
+
await this.upgradeFlow();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (status.targetNeedsUpgrade && targetId) {
|
|
240
|
+
await this.upgradeTarget(targetId);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log(chalk.green('\n✓ All tools upgraded\n'));
|
|
244
|
+
} else {
|
|
245
|
+
console.log();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First Run Setup
|
|
3
|
+
* Quick configuration wizard for new users
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import { GlobalConfigService } from './global-config.js';
|
|
9
|
+
import { UserCancelledError } from '../utils/errors.js';
|
|
10
|
+
|
|
11
|
+
export interface QuickSetupResult {
|
|
12
|
+
target: 'claude-code' | 'opencode';
|
|
13
|
+
provider?: 'default' | 'kimi' | 'zai' | 'ask-every-time';
|
|
14
|
+
mcpServers: string[];
|
|
15
|
+
apiKeys: Record<string, Record<string, string>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class FirstRunSetup {
|
|
19
|
+
private configService: GlobalConfigService;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this.configService = new GlobalConfigService();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Run setup wizard
|
|
27
|
+
*/
|
|
28
|
+
async run(): Promise<QuickSetupResult> {
|
|
29
|
+
try {
|
|
30
|
+
console.log(chalk.cyan.bold('\n╭─────────────────────────────────────────────────╮'));
|
|
31
|
+
console.log(chalk.cyan.bold('│ │'));
|
|
32
|
+
console.log(chalk.cyan.bold('│ Welcome to Sylphx Flow! │'));
|
|
33
|
+
console.log(chalk.cyan.bold('│ Let\'s configure your environment │'));
|
|
34
|
+
console.log(chalk.cyan.bold('│ │'));
|
|
35
|
+
console.log(chalk.cyan.bold('╰─────────────────────────────────────────────────╯\n'));
|
|
36
|
+
|
|
37
|
+
// Step 1: Select target platform
|
|
38
|
+
console.log(chalk.cyan('🔧 Setup (1/3) - Target Platform\n'));
|
|
39
|
+
const { target } = await inquirer.prompt([
|
|
40
|
+
{
|
|
41
|
+
type: 'list',
|
|
42
|
+
name: 'target',
|
|
43
|
+
message: 'Select your preferred platform:',
|
|
44
|
+
choices: [
|
|
45
|
+
{ name: 'Claude Code', value: 'claude-code' },
|
|
46
|
+
{ name: 'OpenCode', value: 'opencode' },
|
|
47
|
+
],
|
|
48
|
+
default: 'claude-code',
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
let provider: string | undefined = 'ask-every-time';
|
|
53
|
+
const apiKeys: Record<string, Record<string, string>> = {};
|
|
54
|
+
|
|
55
|
+
// Step 2: Provider setup (Claude Code only)
|
|
56
|
+
if (target === 'claude-code') {
|
|
57
|
+
console.log(chalk.cyan('\n🔧 Setup (2/3) - Provider\n'));
|
|
58
|
+
const { selectedProvider } = await inquirer.prompt([
|
|
59
|
+
{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'selectedProvider',
|
|
62
|
+
message: 'Select your preferred provider:',
|
|
63
|
+
choices: [
|
|
64
|
+
{ name: 'Ask me every time', value: 'ask-every-time' },
|
|
65
|
+
{ name: 'Default (Claude Code built-in)', value: 'default' },
|
|
66
|
+
{ name: 'Kimi (requires API key)', value: 'kimi' },
|
|
67
|
+
{ name: 'Z.ai (requires API key)', value: 'zai' },
|
|
68
|
+
],
|
|
69
|
+
default: 'ask-every-time',
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
provider = selectedProvider;
|
|
74
|
+
|
|
75
|
+
// Configure API key if needed
|
|
76
|
+
if (provider === 'kimi' || provider === 'zai') {
|
|
77
|
+
const { apiKey } = await inquirer.prompt([
|
|
78
|
+
{
|
|
79
|
+
type: 'password',
|
|
80
|
+
name: 'apiKey',
|
|
81
|
+
message: provider === 'kimi' ? 'Enter Kimi API key:' : 'Enter Z.ai API key:',
|
|
82
|
+
mask: '*',
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
apiKeys[provider] = { apiKey };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Step 3: MCP Servers
|
|
91
|
+
const stepNumber = target === 'claude-code' ? '3/3' : '2/2';
|
|
92
|
+
console.log(chalk.cyan('\n🔧 Setup (' + stepNumber + ') - MCP Servers\n'));
|
|
93
|
+
|
|
94
|
+
const { mcpServers } = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'checkbox',
|
|
97
|
+
name: 'mcpServers',
|
|
98
|
+
message: 'Select MCP servers to enable:',
|
|
99
|
+
choices: [
|
|
100
|
+
{ name: 'GitHub Code Search (grep.app)', value: 'grep', checked: true },
|
|
101
|
+
{ name: 'Context7 Docs', value: 'context7', checked: true },
|
|
102
|
+
{ name: 'Playwright Browser Control', value: 'playwright', checked: true },
|
|
103
|
+
{ name: 'GitHub (requires GITHUB_TOKEN)', value: 'github' },
|
|
104
|
+
{ name: 'Notion (requires NOTION_API_KEY)', value: 'notion' },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// Configure MCP API keys
|
|
110
|
+
const mcpServerRequirements: Record<string, string[]> = {
|
|
111
|
+
github: ['GITHUB_TOKEN'],
|
|
112
|
+
notion: ['NOTION_API_KEY'],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
for (const serverKey of mcpServers) {
|
|
116
|
+
const requirements = mcpServerRequirements[serverKey];
|
|
117
|
+
if (requirements) {
|
|
118
|
+
const configMessage = 'Configure ' + requirements[0] + ' for ' + serverKey + '?';
|
|
119
|
+
const { shouldConfigure } = await inquirer.prompt([
|
|
120
|
+
{
|
|
121
|
+
type: 'confirm',
|
|
122
|
+
name: 'shouldConfigure',
|
|
123
|
+
message: configMessage,
|
|
124
|
+
default: true,
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
if (shouldConfigure) {
|
|
129
|
+
const questions = requirements.map((key) => {
|
|
130
|
+
return {
|
|
131
|
+
type: 'password' as const,
|
|
132
|
+
name: key,
|
|
133
|
+
message: 'Enter ' + key + ':',
|
|
134
|
+
mask: '*',
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
const answers = await inquirer.prompt(questions);
|
|
138
|
+
|
|
139
|
+
apiKeys[serverKey] = answers;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Save configuration
|
|
145
|
+
await this.saveConfiguration({
|
|
146
|
+
target,
|
|
147
|
+
provider,
|
|
148
|
+
mcpServers,
|
|
149
|
+
apiKeys,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log(chalk.green('\n✓ Configuration saved to ~/.sylphx-flow/\n'));
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
target,
|
|
156
|
+
provider: provider as any,
|
|
157
|
+
mcpServers,
|
|
158
|
+
apiKeys,
|
|
159
|
+
};
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
// Handle user cancellation (Ctrl+C)
|
|
162
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
163
|
+
throw new UserCancelledError('Setup cancelled by user');
|
|
164
|
+
}
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Save configuration to global config files
|
|
171
|
+
*/
|
|
172
|
+
private async saveConfiguration(result: QuickSetupResult): Promise<void> {
|
|
173
|
+
// Save global settings
|
|
174
|
+
await this.configService.saveSettings({
|
|
175
|
+
version: '1.0.0',
|
|
176
|
+
defaultTarget: result.target,
|
|
177
|
+
firstRun: false,
|
|
178
|
+
lastUpdated: new Date().toISOString(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Save provider config (Claude Code)
|
|
182
|
+
if (result.target === 'claude-code' && result.provider) {
|
|
183
|
+
const providerConfig = await this.configService.loadProviderConfig();
|
|
184
|
+
providerConfig.claudeCode.defaultProvider = result.provider;
|
|
185
|
+
|
|
186
|
+
// Save API keys
|
|
187
|
+
if (result.provider === 'kimi' && result.apiKeys.kimi) {
|
|
188
|
+
providerConfig.claudeCode.providers.kimi = {
|
|
189
|
+
apiKey: result.apiKeys.kimi.apiKey,
|
|
190
|
+
enabled: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (result.provider === 'zai' && result.apiKeys.zai) {
|
|
194
|
+
providerConfig.claudeCode.providers.zai = {
|
|
195
|
+
apiKey: result.apiKeys.zai.apiKey,
|
|
196
|
+
enabled: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await this.configService.saveProviderConfig(providerConfig);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Save MCP config
|
|
204
|
+
const mcpConfig = await this.configService.loadMCPConfig();
|
|
205
|
+
for (const serverKey of result.mcpServers) {
|
|
206
|
+
mcpConfig.servers[serverKey] = {
|
|
207
|
+
enabled: true,
|
|
208
|
+
env: result.apiKeys[serverKey] || {},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
await this.configService.saveMCPConfig(mcpConfig);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if we should run quick setup
|
|
216
|
+
*/
|
|
217
|
+
async shouldRun(): Promise<boolean> {
|
|
218
|
+
return await this.configService.isFirstRun();
|
|
219
|
+
}
|
|
220
|
+
}
|