@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
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Execution Logic for Flow Command (V2 - Attach Mode)
3
+ * New execution flow with attach-mode lifecycle
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import { FlowExecutor } from '../../core/flow-executor.js';
9
+ import { targetManager } from '../../core/target-manager.js';
10
+ import { UpgradeManager } from '../../core/upgrade-manager.js';
11
+ import { showWelcome } from '../../utils/display/banner.js';
12
+ import { loadAgentContent, extractAgentInstructions } from '../run-command.js';
13
+ import { CLIError } from '../../utils/error-handler.js';
14
+ import type { RunCommandOptions } from '../../types.js';
15
+ import type { FlowOptions } from './types.js';
16
+ import { resolvePrompt } from './prompt.js';
17
+ import { GlobalConfigService } from '../../services/global-config.js';
18
+ import { UserCancelledError } from '../../utils/errors.js';
19
+ import { TargetInstaller } from '../../services/target-installer.js';
20
+ import { AutoUpgrade } from '../../services/auto-upgrade.js';
21
+ import { promptForTargetSelection, ensureTargetInstalled } from '../../utils/target-selection.js';
22
+
23
+ /**
24
+ * Configure provider environment variables
25
+ */
26
+ function configureProviderEnv(
27
+ provider: 'kimi' | 'zai',
28
+ apiKey: string
29
+ ): void {
30
+ const providerConfig = {
31
+ kimi: {
32
+ baseUrl: 'https://api.moonshot.cn/v1',
33
+ name: 'Kimi',
34
+ },
35
+ zai: {
36
+ baseUrl: 'https://api.z.ai/v1',
37
+ name: 'Z.ai',
38
+ },
39
+ };
40
+
41
+ const config = providerConfig[provider];
42
+ process.env.ANTHROPIC_BASE_URL = config.baseUrl;
43
+ process.env.ANTHROPIC_API_KEY = apiKey;
44
+ }
45
+
46
+ /**
47
+ * Select and configure provider for Claude Code
48
+ */
49
+ async function selectProvider(
50
+ configService: GlobalConfigService
51
+ ): Promise<void> {
52
+ try {
53
+ const providerConfig = await configService.loadProviderConfig();
54
+ const defaultProvider = providerConfig.claudeCode.defaultProvider;
55
+
56
+ // If not "ask-every-time", use the default provider
57
+ if (defaultProvider !== 'ask-every-time') {
58
+ if (defaultProvider === 'kimi' || defaultProvider === 'zai') {
59
+ const provider = providerConfig.claudeCode.providers[defaultProvider];
60
+ if (provider?.apiKey) {
61
+ configureProviderEnv(defaultProvider, provider.apiKey);
62
+ }
63
+ }
64
+ return;
65
+ }
66
+
67
+ // Ask user which provider to use for this session
68
+ const { selectedProvider } = await inquirer.prompt([
69
+ {
70
+ type: 'list',
71
+ name: 'selectedProvider',
72
+ message: 'Select provider for this session:',
73
+ choices: [
74
+ { name: 'Default (Claude Code built-in)', value: 'default' },
75
+ { name: 'Kimi', value: 'kimi' },
76
+ { name: 'Z.ai', value: 'zai' },
77
+ ],
78
+ default: 'default',
79
+ },
80
+ ]);
81
+
82
+ // Configure environment variables based on selection
83
+ if (selectedProvider === 'kimi' || selectedProvider === 'zai') {
84
+ const provider = providerConfig.claudeCode.providers[selectedProvider];
85
+
86
+ if (!provider?.apiKey) {
87
+ console.log(chalk.yellow('⚠ API key not configured. Use: sylphx-flow settings\n'));
88
+ return;
89
+ }
90
+
91
+ configureProviderEnv(selectedProvider, provider.apiKey);
92
+
93
+ const providerName = selectedProvider === 'kimi' ? 'Kimi' : 'Z.ai';
94
+ console.log(chalk.green(`✓ Using ${providerName} provider\n`));
95
+ } else {
96
+ console.log(chalk.green('✓ Using default Claude Code provider\n'));
97
+ }
98
+ } catch (error: any) {
99
+ // Handle user cancellation (Ctrl+C)
100
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
101
+ throw new UserCancelledError('Provider selection cancelled');
102
+ }
103
+ throw error;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Execute command using target's executeCommand method
109
+ */
110
+ async function executeTargetCommand(
111
+ targetId: string,
112
+ systemPrompt: string,
113
+ userPrompt: string,
114
+ options: RunCommandOptions
115
+ ): Promise<void> {
116
+ const targetOption = targetManager.getTarget(targetId);
117
+
118
+ if (targetOption._tag === 'None') {
119
+ throw new CLIError(`Target not found: ${targetId}`, 'TARGET_NOT_FOUND');
120
+ }
121
+
122
+ const target = targetOption.value;
123
+
124
+ if (!target.isImplemented || !target.executeCommand) {
125
+ throw new CLIError(
126
+ `Target '${targetId}' does not support command execution`,
127
+ 'EXECUTION_NOT_SUPPORTED'
128
+ );
129
+ }
130
+
131
+ return target.executeCommand(systemPrompt, userPrompt, options);
132
+ }
133
+
134
+ /**
135
+ * Main flow execution with attach mode (V2)
136
+ */
137
+ export async function executeFlowV2(
138
+ prompt: string | undefined,
139
+ options: FlowOptions
140
+ ): Promise<void> {
141
+ const projectPath = process.cwd();
142
+
143
+ // Show welcome banner
144
+ showWelcome();
145
+
146
+ // Initialize config service early to check for saved preferences
147
+ const configService = new GlobalConfigService();
148
+ await configService.initialize();
149
+
150
+ // Step 1: Determine target
151
+ const targetInstaller = new TargetInstaller(projectPath);
152
+ const installedTargets = await targetInstaller.detectInstalledTargets();
153
+ const settings = await configService.loadSettings();
154
+
155
+ let selectedTargetId: string | null = null;
156
+
157
+ // Distinguish between three cases:
158
+ // 1. User explicitly set "ask-every-time" → always prompt
159
+ // 2. User has no setting (undefined/null) → allow auto-detect
160
+ // 3. User has specific target → use that target
161
+ const isAskEveryTime = settings.defaultTarget === 'ask-every-time';
162
+ const hasNoSetting = !settings.defaultTarget;
163
+ const hasSpecificTarget = settings.defaultTarget && settings.defaultTarget !== 'ask-every-time';
164
+
165
+ if (isAskEveryTime) {
166
+ // User explicitly wants to be asked every time - ALWAYS prompt, never auto-detect
167
+ console.log(chalk.cyan('🔍 Detecting installed AI CLIs...\n'));
168
+
169
+ selectedTargetId = await promptForTargetSelection(
170
+ installedTargets,
171
+ 'Select AI CLI to use:',
172
+ 'execution'
173
+ );
174
+
175
+ const installation = targetInstaller.getInstallationInfo(selectedTargetId);
176
+ const installed = await ensureTargetInstalled(selectedTargetId, targetInstaller, installedTargets);
177
+
178
+ if (!installed) {
179
+ process.exit(1);
180
+ }
181
+
182
+ if (installedTargets.includes(selectedTargetId)) {
183
+ console.log(chalk.green(`✓ Using ${installation?.name}\n`));
184
+ }
185
+ } else if (hasNoSetting) {
186
+ // No setting - use auto-detection (smart default behavior)
187
+ if (installedTargets.length === 1) {
188
+ // Exactly 1 target found - use it automatically
189
+ selectedTargetId = installedTargets[0];
190
+ const installation = targetInstaller.getInstallationInfo(selectedTargetId);
191
+ console.log(chalk.green(`✓ Using ${installation?.name} (auto-detected)\n`));
192
+ } else {
193
+ // 0 or multiple targets - prompt for selection
194
+ console.log(chalk.cyan('🔍 Detecting installed AI CLIs...\n'));
195
+
196
+ selectedTargetId = await promptForTargetSelection(
197
+ installedTargets,
198
+ 'Select AI CLI to use:',
199
+ 'execution'
200
+ );
201
+
202
+ const installation = targetInstaller.getInstallationInfo(selectedTargetId);
203
+ const installed = await ensureTargetInstalled(selectedTargetId, targetInstaller, installedTargets);
204
+
205
+ if (!installed) {
206
+ process.exit(1);
207
+ }
208
+
209
+ if (installedTargets.includes(selectedTargetId)) {
210
+ console.log(chalk.green(`✓ Using ${installation?.name}\n`));
211
+ }
212
+ }
213
+ } else if (hasSpecificTarget) {
214
+ // User has a specific target preference - ALWAYS use it
215
+ selectedTargetId = settings.defaultTarget;
216
+ const installation = targetInstaller.getInstallationInfo(selectedTargetId);
217
+
218
+ // Check if the preferred target is installed
219
+ if (installedTargets.includes(selectedTargetId)) {
220
+ console.log(chalk.green(`✓ Using ${installation?.name} (from settings)\n`));
221
+ } else {
222
+ // Preferred target not installed - try to install it
223
+ console.log(chalk.yellow(`⚠️ ${installation?.name} is set as default but not installed\n`));
224
+ const installed = await targetInstaller.install(selectedTargetId, true);
225
+
226
+ if (!installed) {
227
+ // Installation failed - show error and exit
228
+ console.log(chalk.red(`\n✗ Cannot proceed: ${installation?.name} is not installed and auto-install failed`));
229
+ console.log(chalk.yellow(' Please either:'));
230
+ console.log(chalk.cyan(' 1. Install manually (see instructions above)'));
231
+ console.log(chalk.cyan(' 2. Change default target: sylphx-flow settings\n'));
232
+ process.exit(1);
233
+ }
234
+
235
+ console.log();
236
+ }
237
+ }
238
+
239
+ // Step 2: Auto-upgrade Flow and target CLI
240
+ const autoUpgrade = new AutoUpgrade(projectPath);
241
+ await autoUpgrade.runAutoUpgrade(selectedTargetId);
242
+
243
+ // Mode info
244
+ if (options.merge) {
245
+ console.log(chalk.cyan('🔗 Merge mode: Flow settings will be merged with your existing settings'));
246
+ console.log(chalk.dim(' Settings will be restored after execution\n'));
247
+ } else {
248
+ console.log(chalk.yellow('🔄 Replace mode (default): All settings will use Flow configuration'));
249
+ console.log(chalk.dim(' Use --merge to keep your existing settings\n'));
250
+ }
251
+
252
+ // Create executor
253
+ const executor = new FlowExecutor();
254
+ const projectManager = executor.getProjectManager();
255
+
256
+ // Step 2: Execute attach mode lifecycle
257
+ try {
258
+ // Attach Flow environment (backup → attach → register cleanup)
259
+ await executor.execute(projectPath, {
260
+ verbose: options.verbose,
261
+ skipBackup: false,
262
+ skipSecrets: false,
263
+ merge: options.merge || false,
264
+ });
265
+
266
+ // Step 3: Use the target we already selected (don't re-detect)
267
+ // selectedTargetId was determined earlier based on settings/auto-detect/prompt
268
+ const targetId = selectedTargetId;
269
+
270
+ // Step 3.5: Provider selection (Claude Code only)
271
+ if (targetId === 'claude-code') {
272
+ await selectProvider(configService);
273
+ }
274
+
275
+ // Step 3.6: Load Flow settings and determine agent to use
276
+ const settings = await configService.loadSettings();
277
+ const flowConfig = await configService.loadFlowConfig();
278
+
279
+ // Determine which agent to use (CLI option > settings default > 'coder')
280
+ const agent = options.agent || settings.defaultAgent || 'coder';
281
+
282
+ // Check if agent is enabled
283
+ if (!flowConfig.agents[agent]?.enabled) {
284
+ console.log(chalk.yellow(`⚠️ Agent '${agent}' is not enabled in settings`));
285
+ console.log(chalk.yellow(` Enable it with: sylphx-flow settings`));
286
+ console.log(chalk.yellow(` Using 'coder' agent instead\n`));
287
+ // Fallback to first enabled agent or coder
288
+ const enabledAgents = await configService.getEnabledAgents();
289
+ const fallbackAgent = enabledAgents.length > 0 ? enabledAgents[0] : 'coder';
290
+ options.agent = fallbackAgent;
291
+ }
292
+
293
+ console.log(chalk.cyan(`🤖 Running agent: ${agent}\n`));
294
+
295
+ // Load enabled rules and output styles from config
296
+ const enabledRules = await configService.getEnabledRules();
297
+ const enabledOutputStyles = await configService.getEnabledOutputStyles();
298
+
299
+ console.log(chalk.dim(` Enabled rules: ${enabledRules.join(', ')}`));
300
+ console.log(chalk.dim(` Enabled output styles: ${enabledOutputStyles.join(', ')}\n`));
301
+
302
+ // Load agent content with only enabled rules and styles
303
+ const agentContent = await loadAgentContent(
304
+ agent,
305
+ options.agentFile,
306
+ enabledRules,
307
+ enabledOutputStyles
308
+ );
309
+ const agentInstructions = extractAgentInstructions(agentContent);
310
+
311
+ const systemPrompt = `AGENT INSTRUCTIONS:\n${agentInstructions}`;
312
+
313
+ const userPrompt = prompt?.trim() || '';
314
+
315
+ // Prepare run options
316
+ const runOptions: RunCommandOptions = {
317
+ target: targetId,
318
+ verbose: options.verbose || false,
319
+ dryRun: options.dryRun || false,
320
+ agent,
321
+ agentFile: options.agentFile,
322
+ prompt,
323
+ print: options.print,
324
+ continue: options.continue,
325
+ };
326
+
327
+ // Step 4: Execute command
328
+ await executeTargetCommand(targetId, systemPrompt, userPrompt, runOptions);
329
+
330
+ // Step 5: Cleanup (restore environment)
331
+ await executor.cleanup(projectPath);
332
+
333
+ console.log(chalk.green('✓ Session complete\n'));
334
+ } catch (error) {
335
+ // Handle user cancellation gracefully
336
+ if (error instanceof UserCancelledError) {
337
+ console.log(chalk.yellow('\n⚠️ Operation cancelled by user'));
338
+ try {
339
+ await executor.cleanup(projectPath);
340
+ console.log(chalk.green(' ✓ Settings restored\n'));
341
+ } catch (cleanupError) {
342
+ console.error(chalk.red(' ✗ Cleanup failed:'), cleanupError);
343
+ }
344
+ process.exit(0);
345
+ }
346
+
347
+ console.error(chalk.red.bold('\n✗ Execution failed:'), error);
348
+
349
+ // Ensure cleanup even on error
350
+ try {
351
+ await executor.cleanup(projectPath);
352
+ } catch (cleanupError) {
353
+ console.error(chalk.red('✗ Cleanup failed:'), cleanupError);
354
+ }
355
+
356
+ throw error;
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Main flow execution entry point
362
+ */
363
+ export async function executeFlow(
364
+ prompt: string | undefined,
365
+ options: FlowOptions
366
+ ): Promise<void> {
367
+ // Resolve prompt (handle file input)
368
+ const resolvedPrompt = await resolvePrompt(prompt);
369
+
370
+ // Use V2 attach mode execution
371
+ await executeFlowV2(resolvedPrompt, options);
372
+ }
@@ -124,28 +124,11 @@ export async function executeFlowOnce(prompt: string | undefined, options: FlowO
124
124
  await showStatus(state);
125
125
  }
126
126
 
127
- // Step 1: Check for upgrades
127
+ // Step 1: Check for upgrades (non-intrusive notification)
128
128
  if (!options.quick) {
129
129
  await checkUpgrades(state, options);
130
130
  }
131
131
 
132
- // Step 1: Upgrade (if requested)
133
- if (options.upgrade && state.outdated && state.latestVersion) {
134
- console.log(chalk.cyan.bold('━━━ 📦 Upgrading Flow\n'));
135
- await upgradeManager.upgradeFlow(state);
136
- console.log(chalk.green('✓ Upgrade complete\n'));
137
- // Re-detect after upgrade
138
- state.version = state.latestVersion;
139
- state.outdated = false;
140
- }
141
-
142
- // Step 2: Upgrade target (if requested)
143
- if (options.upgradeTarget && state.target) {
144
- console.log(chalk.cyan.bold(`━━━ 🎯 Upgrading ${state.target}\n`));
145
- await upgradeManager.upgradeTarget(state);
146
- console.log(chalk.green('✓ Target upgrade complete\n'));
147
- }
148
-
149
132
  // Step 2.5: Check component integrity (only if we have valid state)
150
133
  await checkComponentIntegrity(state, options);
151
134
 
@@ -26,14 +26,15 @@ export interface FlowOptions {
26
26
  // Smart configuration options
27
27
  selectProvider?: boolean;
28
28
  selectAgent?: boolean;
29
- useDefaults?: boolean;
30
29
  provider?: string;
31
- quick?: boolean;
32
30
 
33
31
  // Execution modes
34
32
  print?: boolean;
35
33
  continue?: boolean;
36
34
 
35
+ // Attach strategy
36
+ merge?: boolean; // Merge with user settings instead of replacing (default: replace)
37
+
37
38
  // Loop mode
38
39
  loop?: number;
39
40
  maxRuns?: number;
@@ -11,49 +11,23 @@ import { StateDetector } from '../core/state-detector.js';
11
11
  import { UpgradeManager } from '../core/upgrade-manager.js';
12
12
  import { showWelcome } from '../utils/display/banner.js';
13
13
  import { showStatus } from '../utils/display/status.js';
14
- import { executeFlow } from './flow/execute.js';
14
+ import { executeFlow } from './flow/execute-v2.js';
15
15
  import type { FlowOptions } from './flow/types.js';
16
16
 
17
17
  /**
18
- * Smart flow command
18
+ * Flow command (simplified for attach mode)
19
19
  */
20
20
  export const flowCommand = new Command('flow')
21
- .description('Intelligent development flow (auto-detect state and act accordingly)')
22
-
23
- // Smart options
24
- .option('--init-only', 'Only initialize, do not run')
25
- .option('--run-only', 'Only run, skip initialization')
26
- .option('--sync', 'Synchronize with Flow templates (delete and re-install template files)')
27
- .option('--upgrade', 'Upgrade Sylphx Flow to latest version')
28
- .option('--upgrade-target', 'Upgrade target platform (Claude Code/OpenCode)')
29
-
30
- // Smart configuration options
31
- .option('--quick', 'Quick mode: use saved defaults and skip all prompts')
32
- .option('--select-provider', 'Prompt to select provider each run')
33
- .option('--select-agent', 'Prompt to select agent each run')
34
- .option('--use-defaults', 'Skip prompts, use saved defaults')
35
- .option('--provider <provider>', 'Override provider for this run (anthropic|z.ai|kimi)')
36
-
37
- // Init options
38
- .option('--target <type>', 'Target platform (opencode, claude-code, auto-detect)')
39
- .option('--verbose', 'Show detailed output')
40
- .option('--dry-run', 'Show what would be done without making changes')
41
- .option('--no-mcp', 'Skip MCP installation')
42
- .option('--no-agents', 'Skip agents installation')
43
- .option('--no-rules', 'Skip rules installation')
44
- .option('--no-output-styles', 'Skip output styles installation')
45
- .option('--no-slash-commands', 'Skip slash commands installation')
46
- .option('--no-hooks', 'Skip hooks setup')
47
-
48
- // Run options
21
+ .description('Run Flow with automatic environment attach')
22
+
23
+ // Core options
49
24
  .option('--agent <name>', 'Agent to use (default: coder)', 'coder')
50
25
  .option('--agent-file <path>', 'Load agent from specific file')
26
+ .option('--verbose', 'Show detailed output')
27
+ .option('--dry-run', 'Show what would be done without making changes')
51
28
  .option('-p, --print', 'Headless print mode (output only, no interactive)')
52
29
  .option('-c, --continue', 'Continue previous conversation (requires print mode)')
53
-
54
- // Loop options
55
- .option('--loop [interval]', 'Loop mode: run repeatedly (optional cooldown in seconds)')
56
- .option('--max-runs <number>', 'Maximum loop iterations (default: infinite)', parseInt)
30
+ .option('--merge', 'Merge Flow settings with existing settings (default: replace all)')
57
31
 
58
32
  // Prompt argument
59
33
  .argument('[prompt]', 'Prompt to execute with agent (optional, supports @file.txt for file input)')
@@ -63,34 +37,17 @@ export const flowCommand = new Command('flow')
63
37
  });
64
38
 
65
39
  /**
66
- * Setup command - alias for `flow --init-only`
67
- * Kept for backward compatibility
40
+ * Setup command - deprecated (attach mode is automatic)
68
41
  */
69
42
  export const setupCommand = new Command('setup')
70
- .description('Initialize project configuration (alias for: flow --init-only)')
43
+ .description('[DEPRECATED] No longer needed - Flow uses automatic attach mode')
71
44
  .action(async () => {
72
- console.log(chalk.yellow(' The "setup" command is deprecated.'));
73
- console.log(chalk.yellow(' Please use: flow --init-only\n'));
74
-
75
- showWelcome();
76
-
77
- const { runInit } = await import('./init-command.js');
78
- await runInit({
79
- target: undefined,
80
- verbose: false,
81
- dryRun: false,
82
- clear: false,
83
- mcp: true,
84
- agents: true,
85
- rules: true,
86
- outputStyles: true,
87
- slashCommands: true,
88
- hooks: true,
89
- helpOption: () => {},
90
- });
91
-
92
- console.log(chalk.green('\n✅ Setup complete!'));
93
- console.log(chalk.dim('\nNext time, use: flow --init-only'));
45
+ console.log(chalk.yellow('⚠️ The "setup" command is deprecated.\n'));
46
+ console.log(chalk.cyan('Flow now uses automatic attach mode:'));
47
+ console.log(chalk.dim(' • No installation needed'));
48
+ console.log(chalk.dim(' • Environment attached automatically'));
49
+ console.log(chalk.dim(' • Restored on exit\n'));
50
+ console.log(chalk.green('✨ Just run: sylphx-flow "your prompt"\n'));
94
51
  });
95
52
 
96
53
  /**
@@ -193,11 +150,12 @@ export const doctorCommand = new Command('doctor')
193
150
  export const upgradeCommand = new Command('upgrade')
194
151
  .description('Upgrade Sylphx Flow and components')
195
152
  .option('--check', 'Only check for updates, do not upgrade')
153
+ .option('--auto', 'Automatically install updates via npm')
196
154
  .option('--components', 'Upgrade components (agents, rules, etc)', true)
197
155
  .option('--target', 'Upgrade target platform (Claude Code/OpenCode)')
198
156
  .option('--verbose', 'Show detailed output')
199
157
  .action(async (options) => {
200
- console.log(chalk.cyan.bold('📦 检查更新\n'));
158
+ console.log(chalk.cyan.bold('📦 Checking for updates\n'));
201
159
 
202
160
  const detector = new StateDetector();
203
161
  const upgradeManager = new UpgradeManager();
@@ -205,7 +163,7 @@ export const upgradeCommand = new Command('upgrade')
205
163
  const updates = await upgradeManager.checkUpdates();
206
164
 
207
165
  if (!updates.flowUpdate && !updates.targetUpdate) {
208
- console.log(chalk.green('✓ 所有组件已是最新版本\n'));
166
+ console.log(chalk.green('✓ All components are up to date\n'));
209
167
  return;
210
168
  }
211
169
 
@@ -219,7 +177,7 @@ export const upgradeCommand = new Command('upgrade')
219
177
 
220
178
  // Check only
221
179
  if (options.check) {
222
- console.log('\n' + chalk.dim('使用 --no-check 或省略参数进行升级'));
180
+ console.log('\n' + chalk.dim('Run without --check to upgrade'));
223
181
  return;
224
182
  }
225
183
 
@@ -229,13 +187,13 @@ export const upgradeCommand = new Command('upgrade')
229
187
  {
230
188
  type: 'confirm',
231
189
  name: 'confirm',
232
- message: '确认升级到最新版本?',
190
+ message: 'Upgrade to latest version?',
233
191
  default: true,
234
192
  },
235
193
  ]);
236
194
 
237
195
  if (!confirm) {
238
- console.log(chalk.dim('\n升级已取消'));
196
+ console.log(chalk.dim('\nUpgrade cancelled'));
239
197
  return;
240
198
  }
241
199
 
@@ -243,16 +201,21 @@ export const upgradeCommand = new Command('upgrade')
243
201
  console.log('');
244
202
 
245
203
  const state = await detector.detect();
204
+ const autoInstall = options.auto || false;
246
205
 
247
206
  if (updates.flowUpdate) {
248
- console.log(chalk.cyan.bold('\n━ 升级 Sylphx Flow\n'));
249
- await upgradeManager.upgradeFlow(state);
207
+ console.log(chalk.cyan.bold('\n━ Upgrading Sylphx Flow\n'));
208
+ await upgradeManager.upgradeFlow(state, autoInstall);
250
209
  }
251
210
 
252
211
  if (updates.targetUpdate && options.target) {
253
- console.log(chalk.cyan.bold('\n━ 升级 Target\n'));
254
- await upgradeManager.upgradeTarget(state);
212
+ console.log(chalk.cyan.bold('\n━ Upgrading Target\n'));
213
+ await upgradeManager.upgradeTarget(state, autoInstall);
255
214
  }
256
215
 
257
- console.log(chalk.green('\n✓ 升级完成\n'));
216
+ console.log(chalk.green('\n✓ Upgrade complete\n'));
217
+
218
+ if (!autoInstall) {
219
+ console.log(chalk.dim('💡 Tip: Use --auto flag to automatically install updates via npm\n'));
220
+ }
258
221
  });