@sylphx/flow 1.8.0 → 1.8.1

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 (126) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/assets/output-styles/silent.md +141 -8
  3. package/assets/rules/core.md +19 -2
  4. package/package.json +2 -12
  5. package/src/commands/flow/execute.ts +470 -0
  6. package/src/commands/flow/index.ts +11 -0
  7. package/src/commands/flow/prompt.ts +35 -0
  8. package/src/commands/flow/setup.ts +312 -0
  9. package/src/commands/flow/targets.ts +18 -0
  10. package/src/commands/flow/types.ts +47 -0
  11. package/src/commands/flow-command.ts +18 -967
  12. package/src/commands/flow-orchestrator.ts +14 -5
  13. package/src/commands/hook-command.ts +1 -1
  14. package/src/commands/init-core.ts +12 -3
  15. package/src/commands/run-command.ts +1 -1
  16. package/src/config/rules.ts +1 -1
  17. package/src/core/error-handling.ts +1 -1
  18. package/src/core/loop-controller.ts +1 -1
  19. package/src/core/state-detector.ts +1 -1
  20. package/src/core/target-manager.ts +1 -1
  21. package/src/index.ts +1 -1
  22. package/src/shared/files/index.ts +1 -1
  23. package/src/shared/processing/index.ts +1 -1
  24. package/src/targets/claude-code.ts +3 -3
  25. package/src/targets/opencode.ts +3 -3
  26. package/src/utils/agent-enhancer.ts +2 -2
  27. package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
  28. package/src/utils/{paths.ts → config/paths.ts} +1 -1
  29. package/src/utils/{settings.ts → config/settings.ts} +1 -1
  30. package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
  31. package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
  32. package/src/utils/display/banner.ts +25 -0
  33. package/src/utils/display/status.ts +55 -0
  34. package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
  35. package/src/utils/files/jsonc.ts +36 -0
  36. package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
  37. package/src/utils/index.ts +42 -61
  38. package/src/utils/version.ts +47 -0
  39. package/src/components/benchmark-monitor.tsx +0 -331
  40. package/src/components/reindex-progress.tsx +0 -261
  41. package/src/composables/functional/index.ts +0 -14
  42. package/src/composables/functional/useEnvironment.ts +0 -171
  43. package/src/composables/functional/useFileSystem.ts +0 -139
  44. package/src/composables/index.ts +0 -4
  45. package/src/composables/useEnv.ts +0 -13
  46. package/src/composables/useRuntimeConfig.ts +0 -27
  47. package/src/core/ai-sdk.ts +0 -603
  48. package/src/core/app-factory.ts +0 -381
  49. package/src/core/builtin-agents.ts +0 -9
  50. package/src/core/command-system.ts +0 -550
  51. package/src/core/config-system.ts +0 -550
  52. package/src/core/connection-pool.ts +0 -390
  53. package/src/core/di-container.ts +0 -155
  54. package/src/core/headless-display.ts +0 -96
  55. package/src/core/interfaces/index.ts +0 -22
  56. package/src/core/interfaces/repository.interface.ts +0 -91
  57. package/src/core/interfaces/service.interface.ts +0 -133
  58. package/src/core/interfaces.ts +0 -96
  59. package/src/core/result.ts +0 -351
  60. package/src/core/service-config.ts +0 -252
  61. package/src/core/session-service.ts +0 -121
  62. package/src/core/storage-factory.ts +0 -115
  63. package/src/core/stream-handler.ts +0 -288
  64. package/src/core/type-utils.ts +0 -427
  65. package/src/core/unified-storage.ts +0 -456
  66. package/src/core/validation/limit.ts +0 -46
  67. package/src/core/validation/query.ts +0 -20
  68. package/src/db/auto-migrate.ts +0 -322
  69. package/src/db/base-database-client.ts +0 -144
  70. package/src/db/cache-db.ts +0 -218
  71. package/src/db/cache-schema.ts +0 -75
  72. package/src/db/database.ts +0 -70
  73. package/src/db/index.ts +0 -252
  74. package/src/db/memory-db.ts +0 -153
  75. package/src/db/memory-schema.ts +0 -29
  76. package/src/db/schema.ts +0 -289
  77. package/src/db/session-repository.ts +0 -733
  78. package/src/domains/index.ts +0 -6
  79. package/src/domains/utilities/index.ts +0 -6
  80. package/src/domains/utilities/time/index.ts +0 -5
  81. package/src/domains/utilities/time/tools.ts +0 -291
  82. package/src/services/agent-service.ts +0 -273
  83. package/src/services/evaluation-service.ts +0 -271
  84. package/src/services/functional/evaluation-logic.ts +0 -296
  85. package/src/services/functional/file-processor.ts +0 -273
  86. package/src/services/functional/index.ts +0 -12
  87. package/src/services/memory.service.ts +0 -476
  88. package/src/types/api/batch.ts +0 -108
  89. package/src/types/api/errors.ts +0 -118
  90. package/src/types/api/index.ts +0 -55
  91. package/src/types/api/requests.ts +0 -76
  92. package/src/types/api/responses.ts +0 -180
  93. package/src/types/api/websockets.ts +0 -85
  94. package/src/types/benchmark.ts +0 -49
  95. package/src/types/database.types.ts +0 -510
  96. package/src/types/memory-types.ts +0 -63
  97. package/src/utils/advanced-tokenizer.ts +0 -191
  98. package/src/utils/ai-model-fetcher.ts +0 -19
  99. package/src/utils/async-file-operations.ts +0 -516
  100. package/src/utils/audio-player.ts +0 -345
  101. package/src/utils/codebase-helpers.ts +0 -211
  102. package/src/utils/console-ui.ts +0 -79
  103. package/src/utils/database-errors.ts +0 -140
  104. package/src/utils/debug-logger.ts +0 -49
  105. package/src/utils/file-scanner.ts +0 -259
  106. package/src/utils/help.ts +0 -20
  107. package/src/utils/immutable-cache.ts +0 -106
  108. package/src/utils/jsonc.ts +0 -158
  109. package/src/utils/memory-tui.ts +0 -414
  110. package/src/utils/models-dev.ts +0 -91
  111. package/src/utils/parallel-operations.ts +0 -487
  112. package/src/utils/process-manager.ts +0 -155
  113. package/src/utils/prompts.ts +0 -120
  114. package/src/utils/search-tool-builder.ts +0 -214
  115. package/src/utils/session-manager.ts +0 -168
  116. package/src/utils/session-title.ts +0 -87
  117. package/src/utils/simplified-errors.ts +0 -410
  118. package/src/utils/template-engine.ts +0 -94
  119. package/src/utils/test-audio.ts +0 -71
  120. package/src/utils/todo-context.ts +0 -46
  121. package/src/utils/token-counter.ts +0 -288
  122. /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
  123. /package/src/utils/{logger.ts → display/logger.ts} +0 -0
  124. /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
  125. /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
  126. /package/src/utils/{security.ts → security/security.ts} +0 -0
@@ -1,967 +1,18 @@
1
+ /**
2
+ * Flow Commands
3
+ * Entry point for all flow-related CLI commands
4
+ */
5
+
1
6
  import { Command } from 'commander';
2
7
  import chalk from 'chalk';
3
- import boxen from 'boxen';
4
- import ora from 'ora';
5
8
  import path from 'node:path';
6
9
  import fs from 'node:fs/promises';
7
- import { targetManager } from '../core/target-manager.js';
8
- import { CLIError } from '../utils/error-handler.js';
9
- import type { RunCommandOptions } from '../types.js';
10
- import { StateDetector, type ProjectState } from '../core/state-detector.js';
10
+ import { StateDetector } from '../core/state-detector.js';
11
11
  import { UpgradeManager } from '../core/upgrade-manager.js';
12
- import { loadAgentContent, extractAgentInstructions } from './run-command.js';
13
- import { ClaudeConfigService } from '../services/claude-config-service.js';
14
- import { ConfigService } from '../services/config-service.js';
15
- import { projectSettings } from '../utils/settings.js';
16
-
17
- export interface FlowOptions {
18
- target?: string;
19
- verbose?: boolean;
20
- dryRun?: boolean;
21
- sync?: boolean; // Sync mode - delete and re-install template files
22
- initOnly?: boolean;
23
- runOnly?: boolean;
24
- repair?: boolean; // Repair mode - install missing components
25
- upgrade?: boolean;
26
- upgradeTarget?: boolean;
27
- mcp?: boolean;
28
- agents?: boolean;
29
- rules?: boolean;
30
- outputStyles?: boolean;
31
- slashCommands?: boolean;
32
- hooks?: boolean;
33
- agent?: string;
34
- agentFile?: string;
35
-
36
- // Smart configuration options
37
- selectProvider?: boolean;
38
- selectAgent?: boolean;
39
- useDefaults?: boolean;
40
- provider?: string;
41
- quick?: boolean;
42
-
43
- // Execution modes
44
- print?: boolean; // Headless print mode
45
- continue?: boolean; // Continue previous conversation
46
-
47
- // Loop mode (continuous execution)
48
- loop?: number; // Loop every N seconds (--loop 60)
49
- maxRuns?: number; // Optional max iterations (default: infinite)
50
- }
51
-
52
- /**
53
- * Display welcome banner
54
- */
55
- function showWelcome(): void {
56
- console.log(
57
- boxen(
58
- `${chalk.cyan.bold('Sylphx Flow')} ${chalk.dim('- AI-Powered Development Framework')}\n` +
59
- `${chalk.dim('Auto-initialization • Smart upgrades • One-click launch')}`,
60
- {
61
- padding: 1,
62
- margin: { bottom: 1 },
63
- borderStyle: 'round',
64
- borderColor: 'cyan',
65
- }
66
- )
67
- );
68
- }
69
-
70
- /**
71
- * Compare versions to check if one is outdated
72
- */
73
- function isVersionOutdated(current: string, latest: string): boolean {
74
- try {
75
- return compareVersions(current, latest) < 0;
76
- } catch {
77
- return false;
78
- }
79
- }
80
-
81
- /**
82
- * Compare two version strings
83
- */
84
- function compareVersions(v1: string, v2: string): number {
85
- const parts1 = v1.split('.').map(Number);
86
- const parts2 = v2.split('.').map(Number);
87
-
88
- for (let i = 0; i < Math.min(parts1.length, parts2.length); i++) {
89
- if (parts1[i] !== parts2[i]) {
90
- return parts1[i] - parts2[i];
91
- }
92
- }
93
-
94
- return parts1.length - parts2.length;
95
- }
96
-
97
- async function showStatus(state: ProjectState): Promise<void> {
98
- console.log(chalk.cyan.bold('📊 Project Status\n'));
99
-
100
- if (!state.initialized) {
101
- console.log(' ' + chalk.yellow('⚠ Not initialized'));
102
- } else {
103
- console.log(` ${chalk.green('✓')} Initialized (Flow v${state.version || 'unknown'})`);
104
-
105
- if (state.target) {
106
- const versionStr = state.targetVersion ? ` (v${state.targetVersion})` : '';
107
- console.log(` ${chalk.green('✓')} Target platform: ${state.target}${versionStr}`);
108
- }
109
-
110
- // Component status
111
- const components = state.components;
112
- console.log(`\n ${chalk.cyan('Components:')}`);
113
- console.log(` Agents: ${components.agents.installed ? chalk.green(`✓ ${components.agents.count}`) : chalk.red('✗')}`);
114
- console.log(` Rules: ${components.rules.installed ? chalk.green(`✓ ${components.rules.count}`) : chalk.red('✗')}`);
115
- console.log(` Hooks: ${components.hooks.installed ? chalk.green('✓') : chalk.red('✗')}`);
116
- console.log(` MCP: ${components.mcp.installed ? chalk.green(`✓ ${components.mcp.serverCount} servers`) : chalk.red('✗')}`);
117
- console.log(` Output styles: ${components.outputStyles.installed ? chalk.green('✓') : chalk.red('✗')}`);
118
- console.log(` Slash commands: ${components.slashCommands.installed ? chalk.green(`✓ ${components.slashCommands.count}`) : chalk.red('✗')}`);
119
-
120
- // Outdated warnings
121
- if (state.outdated) {
122
- console.log(`\n ${chalk.yellow('⚠')} Flow version outdated: ${state.version} → ${state.latestVersion}`);
123
- }
124
-
125
- if (state.targetVersion && state.targetLatestVersion &&
126
- isVersionOutdated(state.targetVersion, state.targetLatestVersion)) {
127
- console.log(` ${chalk.yellow('⚠')} ${state.target} update available: v${state.targetVersion} → v${state.targetLatestVersion}`);
128
- }
129
-
130
- if (state.lastUpdated) {
131
- const days = Math.floor((Date.now() - state.lastUpdated.getTime()) / (1000 * 60 * 60 * 24));
132
- if (days > 7) {
133
- console.log(`\n ${chalk.yellow('⚠')} Last updated: ${days} days ago`);
134
- }
135
- }
136
- }
137
-
138
- console.log('');
139
- }
140
-
141
- /**
142
- * Get executable targets
143
- */
144
- function getExecutableTargets(): string[] {
145
- return targetManager.getImplementedTargetIDs().filter((targetId) => {
146
- const targetOption = targetManager.getTarget(targetId);
147
- if (targetOption._tag === 'None') {
148
- return false;
149
- }
150
- return targetOption.value.executeCommand !== undefined;
151
- });
152
- }
153
-
154
- /**
155
- * Execute command using target's executeCommand method
156
- */
157
- async function executeTargetCommand(
158
- targetId: string,
159
- systemPrompt: string,
160
- userPrompt: string,
161
- options: RunCommandOptions
162
- ): Promise<void> {
163
- const targetOption = targetManager.getTarget(targetId);
164
-
165
- if (targetOption._tag === 'None') {
166
- throw new CLIError(`Target not found: ${targetId}`, 'TARGET_NOT_FOUND');
167
- }
168
-
169
- const target = targetOption.value;
170
-
171
- if (!target.isImplemented) {
172
- throw new CLIError(
173
- `Target '${targetId}' is not implemented. Supported targets: ${getExecutableTargets().join(', ')}`,
174
- 'TARGET_NOT_IMPLEMENTED'
175
- );
176
- }
177
-
178
- if (!target.executeCommand) {
179
- throw new CLIError(
180
- `Target '${targetId}' does not support command execution. Supported targets: ${getExecutableTargets().join(', ')}`,
181
- 'EXECUTION_NOT_SUPPORTED'
182
- );
183
- }
184
-
185
- return target.executeCommand(systemPrompt, userPrompt, options);
186
- }
187
-
188
- /**
189
- * Compare versions
190
- */
191
- function isVersionOutdated(current: string, latest: string): boolean {
192
- try {
193
- return compareVersions(current, latest) < 0;
194
- } catch {
195
- return false;
196
- }
197
- }
198
-
199
- function compareVersions(v1: string, v2: string): number {
200
- const parts1 = v1.split('.').map(Number);
201
- const parts2 = v2.split('.').map(Number);
202
-
203
- for (let i = 0; i < Math.min(parts1.length, parts2.length); i++) {
204
- if (parts1[i] !== parts2[i]) {
205
- return parts1[i] - parts2[i];
206
- }
207
- }
208
-
209
- return parts1.length - parts2.length;
210
- }
211
-
212
- /**
213
- * Resolve prompt - handle file input if needed
214
- * Supports @filename syntax: @prompt.txt or @/path/to/prompt.txt
215
- */
216
- async function resolvePrompt(prompt: string | undefined): Promise<string | undefined> {
217
- if (!prompt) return prompt;
218
-
219
- // Check for file input syntax: @filename
220
- if (prompt.startsWith('@')) {
221
- const filePath = prompt.slice(1); // Remove @ prefix
222
-
223
- try {
224
- const resolvedPath = path.isAbsolute(filePath)
225
- ? filePath
226
- : path.resolve(process.cwd(), filePath);
227
-
228
- const content = await fs.readFile(resolvedPath, 'utf-8');
229
- console.log(chalk.dim(` ✓ Loaded prompt from: ${filePath}\n`));
230
- return content.trim();
231
- } catch (error) {
232
- throw new Error(`Failed to read prompt file: ${filePath}`);
233
- }
234
- }
235
-
236
- return prompt;
237
- }
238
-
239
- /**
240
- * Main flow execution logic - simplified with orchestrator
241
- */
242
- export async function executeFlow(prompt: string | undefined, options: FlowOptions): Promise<void> {
243
- // Resolve prompt (handle file input)
244
- const resolvedPrompt = await resolvePrompt(prompt);
245
-
246
- // Loop mode: Setup once, then loop only execution
247
- if (options.loop !== undefined) {
248
- const { LoopController } = await import('../core/loop-controller.js');
249
- const controller = new LoopController();
250
-
251
- // Default to 0s (no cooldown) if just --loop with no value
252
- const interval = typeof options.loop === 'number' ? options.loop : 0;
253
-
254
- // Auto-enable headless mode for loop
255
- options.print = true;
256
-
257
- // ONE-TIME SETUP: Do all initialization once before loop starts
258
- const setupContext = await executeSetupPhase(resolvedPrompt, options);
259
-
260
- // Save original continue flag
261
- const originalContinue = options.continue || false;
262
-
263
- // LOOP: Only execute the command repeatedly
264
- await controller.run(
265
- async () => {
266
- const isFirstIteration = controller['state'].iteration === 1;
267
-
268
- // Continue logic:
269
- // - If user specified --continue, always use it (all iterations)
270
- // - If user didn't specify, only use from 2nd iteration onwards
271
- options.continue = originalContinue || !isFirstIteration;
272
-
273
- try {
274
- await executeCommandOnly(setupContext, resolvedPrompt, options);
275
- return { exitCode: 0 };
276
- } catch (error) {
277
- return { exitCode: 1, error: error as Error };
278
- }
279
- },
280
- {
281
- enabled: true,
282
- interval,
283
- maxRuns: options.maxRuns,
284
- }
285
- );
286
-
287
- return;
288
- }
289
-
290
- // Normal execution (non-loop)
291
- await executeFlowOnce(resolvedPrompt, options);
292
- }
293
-
294
- /**
295
- * Setup context for command execution
296
- * Returns everything needed to execute the command repeatedly
297
- */
298
- interface SetupContext {
299
- resolvedTarget: string;
300
- agent: string;
301
- systemPrompt: string;
302
- runOptions: RunCommandOptions;
303
- }
304
-
305
- /**
306
- * Execute setup phase once (for loop mode)
307
- * Returns context needed for repeated command execution
308
- */
309
- async function executeSetupPhase(prompt: string | undefined, options: FlowOptions): Promise<SetupContext> {
310
- // Quick mode: enable useDefaults and skip prompts
311
- if (options.quick) {
312
- options.useDefaults = true;
313
- console.log(chalk.cyan('⚡ Quick mode enabled - using saved defaults\n'));
314
- }
315
-
316
- // Import orchestrator functions
317
- const {
318
- checkUpgrades,
319
- checkComponentIntegrity,
320
- selectTarget,
321
- initializeProject,
322
- } = await import('./flow-orchestrator.js');
323
-
324
- // Show welcome banner (only once)
325
- showWelcome();
326
-
327
- let selectedTarget: string | undefined;
328
- let state: ProjectState | undefined;
329
-
330
- // Determine target
331
- const initialTarget = options.target || (await projectSettings.getDefaultTarget());
332
-
333
- // Detect state if we have a target
334
- if (initialTarget && !options.sync) {
335
- const detector = new StateDetector();
336
-
337
- if (options.verbose) {
338
- console.log(chalk.dim('🤔 Checking project status...\n'));
339
- }
340
-
341
- state = await detector.detect();
342
-
343
- if (options.verbose) {
344
- await showStatus(state);
345
- }
346
-
347
- // Check for upgrades
348
- if (!options.quick) {
349
- await checkUpgrades(state, options);
350
- }
351
-
352
- // Check component integrity
353
- await checkComponentIntegrity(state, options);
354
- }
355
-
356
- // Initialize if needed
357
- const shouldInitialize =
358
- !state?.initialized ||
359
- options.sync ||
360
- options.repair ||
361
- options.initOnly;
362
-
363
- if (shouldInitialize) {
364
- try {
365
- const { selectAndValidateTarget, previewDryRun, installComponents } =
366
- await import('./init-core.js');
367
-
368
- const initOptions = {
369
- target: options.target,
370
- verbose: options.verbose || false,
371
- dryRun: options.dryRun || false,
372
- clear: options.sync || false,
373
- mcp: options.mcp !== false,
374
- agents: options.agents !== false,
375
- rules: options.rules !== false,
376
- outputStyles: options.outputStyles !== false,
377
- slashCommands: options.slashCommands !== false,
378
- hooks: options.hooks !== false,
379
- };
380
-
381
- // Handle sync mode - delete template files first
382
- if (options.sync && !options.dryRun) {
383
- const { buildSyncManifest, showSyncPreview, selectUnknownFilesToRemove, showFinalSummary, confirmSync, executeSyncDelete, removeMCPServers, removeHooks } = await import('../utils/sync-utils.js');
384
-
385
- // Need target to build manifest
386
- const targetId = await selectAndValidateTarget(initOptions);
387
- selectedTarget = targetId;
388
-
389
- const targetOption = targetManager.getTarget(targetId);
390
- if (targetOption._tag === 'None') {
391
- throw new Error(`Target not found: ${targetId}`);
392
- }
393
-
394
- const target = targetOption.value;
395
- const manifest = await buildSyncManifest(process.cwd(), target);
396
-
397
- // Show preview
398
- console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
399
- showSyncPreview(manifest, process.cwd(), target);
400
-
401
- // Select unknown files to remove
402
- const selectedUnknowns = await selectUnknownFilesToRemove(manifest);
403
-
404
- // Show final summary
405
- showFinalSummary(manifest, selectedUnknowns);
406
-
407
- // Confirm
408
- const confirmed = await confirmSync();
409
- if (!confirmed) {
410
- console.log(chalk.yellow('\n✗ Sync cancelled\n'));
411
- process.exit(0);
412
- }
413
-
414
- // Execute deletion
415
- const { templates, unknowns } = await executeSyncDelete(manifest, selectedUnknowns);
416
-
417
- // Remove MCP servers
418
- let mcpRemoved = 0;
419
- if (selectedUnknowns.mcpServers.length > 0) {
420
- mcpRemoved = await removeMCPServers(process.cwd(), selectedUnknowns.mcpServers);
421
- }
422
-
423
- // Remove hooks
424
- let hooksRemoved = 0;
425
- if (selectedUnknowns.hooks.length > 0) {
426
- hooksRemoved = await removeHooks(process.cwd(), selectedUnknowns.hooks);
427
- }
428
-
429
- // Summary
430
- console.log(chalk.green(`\n✓ Synced ${templates} templates`));
431
- const totalRemoved = unknowns + mcpRemoved + hooksRemoved;
432
- if (totalRemoved > 0) {
433
- console.log(chalk.green(`✓ Removed ${totalRemoved} items`));
434
- }
435
- const totalSelected = selectedUnknowns.files.length + selectedUnknowns.mcpServers.length + selectedUnknowns.hooks.length;
436
- const preserved = manifest.agents.unknown.length + manifest.slashCommands.unknown.length + manifest.rules.unknown.length + manifest.mcpServers.notInRegistry.length + manifest.hooks.orphaned.length - totalSelected;
437
- if (preserved > 0) {
438
- console.log(chalk.green(`✓ Preserved ${preserved} custom items`));
439
- }
440
- console.log('');
441
- } else if (!options.sync) {
442
- const targetId = await selectAndValidateTarget(initOptions);
443
- selectedTarget = targetId;
444
- }
445
-
446
- if (options.dryRun) {
447
- // Ensure we have a target ID for dry run
448
- if (!selectedTarget) {
449
- const targetId = await selectAndValidateTarget(initOptions);
450
- selectedTarget = targetId;
451
- }
452
-
453
- console.log(
454
- boxen(
455
- chalk.yellow('⚠ Dry Run Mode') + chalk.dim('\nNo changes will be made to your project'),
456
- {
457
- padding: 1,
458
- margin: { top: 0, bottom: 1, left: 0, right: 0 },
459
- borderStyle: 'round',
460
- borderColor: 'yellow',
461
- }
462
- )
463
- );
464
-
465
- await previewDryRun(selectedTarget, initOptions);
466
-
467
- console.log(
468
- '\n' +
469
- boxen(chalk.green.bold('✓ Dry run complete'), {
470
- padding: { top: 0, bottom: 0, left: 2, right: 2 },
471
- margin: 0,
472
- borderStyle: 'round',
473
- borderColor: 'green',
474
- }) +
475
- '\n'
476
- );
477
-
478
- console.log(chalk.dim('✓ Initialization dry run complete\n'));
479
- } else {
480
- // Ensure we have a target ID for installation
481
- if (!selectedTarget) {
482
- const targetId = await selectAndValidateTarget(initOptions);
483
- selectedTarget = targetId;
484
- }
485
-
486
- await installComponents(selectedTarget, initOptions);
487
- console.log(chalk.green.bold('✓ Initialization complete\n'));
488
- }
489
- } catch (error) {
490
- console.error(chalk.red.bold('✗ Initialization failed:'), error);
491
- process.exit(1);
492
- }
493
- }
494
-
495
- // Resolve target
496
- let targetForResolution = options.target || state?.target || selectedTarget;
497
- if (selectedTarget) {
498
- targetForResolution = selectedTarget;
499
- }
500
-
501
- if (!targetForResolution) {
502
- console.error(chalk.red.bold('✗ No target selected. Use --target or run init first.'));
503
- process.exit(1);
504
- }
505
-
506
- const resolvedTarget = await targetManager.resolveTarget({
507
- target: targetForResolution,
508
- allowSelection: false,
509
- });
510
-
511
- console.log(chalk.cyan.bold(`━━━ 🎯 Launching ${resolvedTarget}\n`));
512
-
513
- // Check if target supports command execution
514
- const { getTargetsWithCommandSupport } = await import('../config/targets.js');
515
- const supportedTargets = getTargetsWithCommandSupport().map(t => t.id);
516
-
517
- if (!supportedTargets.includes(resolvedTarget)) {
518
- console.log(chalk.red.bold('✗ Unsupported target platform\n'));
519
- console.log(chalk.yellow(`Target '${resolvedTarget}' does not support agent execution.`));
520
- console.log(chalk.cyan(`Supported platforms: ${supportedTargets.join(', ')}\n`));
521
- console.log(chalk.dim('Tip: Use --target claude-code to specify Claude Code platform'));
522
- console.log(chalk.dim('Example: bun dev:flow --target claude-code\n'));
523
- process.exit(1);
524
- }
525
-
526
- // Claude Code handling
527
- if (resolvedTarget === 'claude-code') {
528
- const { SmartConfigService } = await import('../services/smart-config-service.js');
529
- const { ConfigService } = await import('../services/config-service.js');
530
-
531
- if (!(await ConfigService.hasInitialSetup())) {
532
- console.log(chalk.cyan('🔑 First-time setup for Claude Code\n'));
533
- await SmartConfigService.initialSetup();
534
- console.log(chalk.green('✓ Setup complete!\n'));
535
- }
536
-
537
- const runtimeChoices = await SmartConfigService.selectRuntimeChoices({
538
- selectProvider: options.selectProvider,
539
- selectAgent: options.selectAgent,
540
- useDefaults: options.useDefaults,
541
- provider: options.provider,
542
- agent: options.agent,
543
- });
544
-
545
- await SmartConfigService.setupEnvironment(runtimeChoices.provider!);
546
- options.agent = runtimeChoices.agent;
547
- }
548
-
549
- const agent = options.agent || 'coder';
550
- const verbose = options.verbose || false;
551
-
552
- if (verbose || options.runOnly || !options.quick) {
553
- console.log(` 🤖 Agent: ${chalk.cyan(agent)}`);
554
- console.log(` 🎯 Target: ${chalk.cyan(resolvedTarget)}`);
555
- if (prompt) {
556
- console.log(` 💬 Prompt: ${chalk.dim(prompt)}\n`);
557
- } else {
558
- console.log(` 💬 Mode: ${chalk.dim('Interactive')}\n`);
559
- }
560
- }
561
-
562
- // Load agent and prepare prompts
563
- const agentContent = await loadAgentContent(agent, options.agentFile);
564
- const agentInstructions = extractAgentInstructions(agentContent);
565
- const systemPrompt = `AGENT INSTRUCTIONS:\n${agentInstructions}`;
566
-
567
- // Prepare run options
568
- const runOptions: RunCommandOptions = {
569
- target: resolvedTarget,
570
- verbose,
571
- dryRun: options.dryRun,
572
- agent,
573
- agentFile: options.agentFile,
574
- prompt,
575
- print: options.print,
576
- continue: options.continue,
577
- };
578
-
579
- return {
580
- resolvedTarget,
581
- agent,
582
- systemPrompt,
583
- runOptions,
584
- };
585
- }
586
-
587
- /**
588
- * Execute command only (for loop mode iterations)
589
- * Uses pre-setup context to execute command without re-doing setup
590
- */
591
- async function executeCommandOnly(
592
- context: SetupContext,
593
- prompt: string | undefined,
594
- options: FlowOptions
595
- ): Promise<void> {
596
- const userPrompt = prompt?.trim() || '';
597
-
598
- // Update continue flag in runOptions
599
- const runOptions = {
600
- ...context.runOptions,
601
- continue: options.continue,
602
- };
603
-
604
- try {
605
- await executeTargetCommand(context.resolvedTarget, context.systemPrompt, userPrompt, runOptions);
606
- } catch (error) {
607
- console.error(chalk.red.bold('\n✗ Launch failed:'), error);
608
- throw error;
609
- }
610
- }
611
-
612
- /**
613
- * Single flow execution (used by both normal and loop mode)
614
- */
615
- async function executeFlowOnce(prompt: string | undefined, options: FlowOptions): Promise<void> {
616
- // Quick mode: enable useDefaults and skip prompts
617
- if (options.quick) {
618
- options.useDefaults = true;
619
- console.log(chalk.cyan('⚡ Quick mode enabled - using saved defaults\n'));
620
- }
621
-
622
- // Continue mode always requires print mode
623
- if (options.continue && !options.print) {
624
- options.print = true;
625
- }
626
-
627
- // Import orchestrator functions
628
- const {
629
- checkUpgrades,
630
- checkComponentIntegrity,
631
- checkSyncStatus,
632
- selectTarget,
633
- initializeProject,
634
- launchTarget,
635
- } = await import('./flow-orchestrator.js');
636
-
637
- // Show welcome banner
638
- showWelcome();
639
-
640
- // Declare at function level to persist across steps
641
- let selectedTarget: string | undefined;
642
- let state: ProjectState | undefined;
643
-
644
- // First: determine target (from options, saved settings, or init will prompt)
645
- const initialTarget = options.target || (await projectSettings.getDefaultTarget());
646
-
647
- // Only detect state if we have a target (can't check components without knowing target structure)
648
- if (initialTarget && !options.sync) {
649
- const detector = new StateDetector();
650
- const upgradeManager = new UpgradeManager();
651
-
652
- if (options.verbose) {
653
- console.log(chalk.dim('🤔 Checking project status...\n'));
654
- }
655
-
656
- state = await detector.detect();
657
-
658
- if (options.verbose) {
659
- await showStatus(state);
660
- }
661
-
662
- // Step 1: Check for upgrades
663
- if (!options.quick) {
664
- await checkUpgrades(state, options);
665
- }
666
-
667
- // Step 1: Upgrade (if requested)
668
- if (options.upgrade && state.outdated && state.latestVersion) {
669
- console.log(chalk.cyan.bold('━━━ 📦 Upgrading Flow\n'));
670
- await upgradeManager.upgradeFlow(state);
671
- console.log(chalk.green('✓ Upgrade complete\n'));
672
- // Re-detect after upgrade
673
- state.version = state.latestVersion;
674
- state.outdated = false;
675
- }
676
-
677
- // Step 2: Upgrade target (if requested)
678
- if (options.upgradeTarget && state.target) {
679
- console.log(chalk.cyan.bold(`━━━ 🎯 Upgrading ${state.target}\n`));
680
- await upgradeManager.upgradeTarget(state);
681
- console.log(chalk.green('✓ Target upgrade complete\n'));
682
- }
683
-
684
- // Step 2.5: Check component integrity (only if we have valid state)
685
- await checkComponentIntegrity(state, options);
686
-
687
- // Step 2.6: Check sync status (new templates available)
688
- await checkSyncStatus(state, options);
689
- }
690
-
691
- // Step 3: Initialize (only if actually needed)
692
- // Positive logic: should initialize when:
693
- // - Not initialized yet (state?.initialized === false)
694
- // - Sync mode (wipe and reinstall)
695
- // - Repair mode (install missing components)
696
- // - Init-only mode (user explicitly wants init)
697
- const shouldInitialize =
698
- !state?.initialized || // Not initialized yet
699
- options.sync || // Sync reinstall
700
- options.repair || // Repair missing components
701
- options.initOnly; // Explicit init request
702
-
703
- if (shouldInitialize) {
704
- console.log(chalk.cyan.bold('━━━ 🚀 Initializing Project\n'));
705
-
706
- // Import core init functions
707
- const {
708
- selectAndValidateTarget,
709
- previewDryRun,
710
- installComponents,
711
- } = await import('./init-core.js');
712
-
713
- try {
714
- // In repair mode, use existing target from state
715
- const targetForInit = options.repair && state?.target
716
- ? state.target
717
- : options.target;
718
-
719
- // Prepare init options
720
- const initOptions = {
721
- target: targetForInit, // Use existing target in repair mode
722
- verbose: options.verbose,
723
- dryRun: options.dryRun,
724
- clear: options.sync || false,
725
- mcp: options.mcp !== false,
726
- agents: options.agents !== false,
727
- rules: options.rules !== false,
728
- outputStyles: options.outputStyles !== false,
729
- slashCommands: options.slashCommands !== false,
730
- hooks: options.hooks !== false,
731
- };
732
-
733
- // Handle sync mode - delete template files first
734
- if (options.sync && !options.dryRun) {
735
- const { buildSyncManifest, showSyncPreview, selectUnknownFilesToRemove, showFinalSummary, confirmSync, executeSyncDelete, removeMCPServers, removeHooks } = await import('../utils/sync-utils.js');
736
-
737
- // Need target to build manifest
738
- const targetId = await selectAndValidateTarget(initOptions);
739
- selectedTarget = targetId;
740
-
741
- const targetOption = targetManager.getTarget(targetId);
742
- if (targetOption._tag === 'None') {
743
- throw new Error(`Target not found: ${targetId}`);
744
- }
745
-
746
- const target = targetOption.value;
747
- const manifest = await buildSyncManifest(process.cwd(), target);
748
-
749
- // Show preview
750
- console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
751
- showSyncPreview(manifest, process.cwd(), target);
752
-
753
- // Select unknown files to remove
754
- const selectedUnknowns = await selectUnknownFilesToRemove(manifest);
755
-
756
- // Show final summary
757
- showFinalSummary(manifest, selectedUnknowns);
758
-
759
- // Confirm
760
- const confirmed = await confirmSync();
761
- if (!confirmed) {
762
- console.log(chalk.yellow('\n✗ Sync cancelled\n'));
763
- process.exit(0);
764
- }
765
-
766
- // Execute deletion
767
- const { templates, unknowns } = await executeSyncDelete(manifest, selectedUnknowns);
768
-
769
- // Remove MCP servers
770
- let mcpRemoved = 0;
771
- if (selectedUnknowns.mcpServers.length > 0) {
772
- mcpRemoved = await removeMCPServers(process.cwd(), selectedUnknowns.mcpServers);
773
- }
774
-
775
- // Remove hooks
776
- let hooksRemoved = 0;
777
- if (selectedUnknowns.hooks.length > 0) {
778
- hooksRemoved = await removeHooks(process.cwd(), selectedUnknowns.hooks);
779
- }
780
-
781
- // Summary
782
- console.log(chalk.green(`\n✓ Synced ${templates} templates`));
783
- const totalRemoved = unknowns + mcpRemoved + hooksRemoved;
784
- if (totalRemoved > 0) {
785
- console.log(chalk.green(`✓ Removed ${totalRemoved} items`));
786
- }
787
- const totalSelected = selectedUnknowns.files.length + selectedUnknowns.mcpServers.length + selectedUnknowns.hooks.length;
788
- const preserved = manifest.agents.unknown.length + manifest.slashCommands.unknown.length + manifest.rules.unknown.length + manifest.mcpServers.notInRegistry.length + manifest.hooks.orphaned.length - totalSelected;
789
- if (preserved > 0) {
790
- console.log(chalk.green(`✓ Preserved ${preserved} custom items`));
791
- }
792
- console.log('');
793
- } else {
794
- // Select and validate target (will use existing in repair mode, or prompt if needed)
795
- const targetId = await selectAndValidateTarget(initOptions);
796
- selectedTarget = targetId; // Save for later use
797
- }
798
-
799
- // Dry run preview
800
- if (options.dryRun) {
801
- // Ensure we have a target ID for dry run
802
- if (!selectedTarget) {
803
- const targetId = await selectAndValidateTarget(initOptions);
804
- selectedTarget = targetId;
805
- }
806
-
807
- console.log(
808
- boxen(
809
- chalk.yellow('⚠ Dry Run Mode') + chalk.dim('\nNo changes will be made to your project'),
810
- {
811
- padding: 1,
812
- margin: { top: 0, bottom: 1, left: 0, right: 0 },
813
- borderStyle: 'round',
814
- borderColor: 'yellow',
815
- }
816
- )
817
- );
818
-
819
- await previewDryRun(selectedTarget, initOptions);
820
-
821
- console.log(
822
- '\n' +
823
- boxen(chalk.green.bold('✓ Dry run complete'), {
824
- padding: { top: 0, bottom: 0, left: 2, right: 2 },
825
- margin: 0,
826
- borderStyle: 'round',
827
- borderColor: 'green',
828
- }) +
829
- '\n'
830
- );
831
-
832
- console.log(chalk.dim('✓ Initialization dry run complete\n'));
833
- // Don't return - continue to show execution command
834
- } else {
835
- // Actually install components
836
- // Ensure we have a target ID for installation
837
- if (!selectedTarget) {
838
- const targetId = await selectAndValidateTarget(initOptions);
839
- selectedTarget = targetId;
840
- }
841
-
842
- const result = await installComponents(selectedTarget, initOptions);
843
-
844
- console.log(chalk.green.bold('✓ Initialization complete\n'));
845
- }
846
- } catch (error) {
847
- console.error(chalk.red.bold('✗ Initialization failed:'), error);
848
- process.exit(1);
849
- }
850
- }
851
-
852
- // Step 4: Launch target (if not init-only)
853
- if (!options.initOnly) {
854
- // Resolve target - use the target we just selected
855
- let targetForResolution = options.target || state?.target || selectedTarget;
856
-
857
- // If we just selected a target during init, use that
858
- if (selectedTarget) {
859
- targetForResolution = selectedTarget;
860
- }
861
-
862
- if (!targetForResolution) {
863
- console.error(chalk.red.bold('✗ No target selected. Use --target or run init first.'));
864
- process.exit(1);
865
- }
866
-
867
- const resolvedTarget = await targetManager.resolveTarget({
868
- target: targetForResolution,
869
- allowSelection: false, // Target should already be selected during init
870
- });
871
-
872
- console.log(chalk.cyan.bold(`━━━ 🎯 Launching ${resolvedTarget}\n`));
873
-
874
- // Check if target supports command execution
875
- const { getTargetsWithCommandSupport } = await import('../config/targets.js');
876
- const supportedTargets = getTargetsWithCommandSupport().map(t => t.id);
877
-
878
- if (!supportedTargets.includes(resolvedTarget)) {
879
- console.log(chalk.red.bold('✗ Unsupported target platform\n'));
880
- console.log(chalk.yellow(`Target '${resolvedTarget}' does not support agent execution.`));
881
- console.log(chalk.cyan(`Supported platforms: ${supportedTargets.join(', ')}\n`));
882
- console.log(chalk.dim('Tip: Use --target claude-code to specify Claude Code platform'));
883
- console.log(chalk.dim('Example: bun dev:flow --target claude-code\n'));
884
- process.exit(1);
885
- }
886
-
887
- // Claude Code handling - needs provider/agent setup
888
- if (resolvedTarget === 'claude-code') {
889
- // Handle provider and agent selection for Claude Code
890
- const { SmartConfigService } = await import('../services/smart-config-service.js');
891
-
892
- // Check if API keys are configured, if not, run initial setup
893
- const { ConfigService } = await import('../services/config-service.js');
894
- if (!(await ConfigService.hasInitialSetup())) {
895
- console.log(chalk.cyan('🔑 First-time setup for Claude Code\n'));
896
- await SmartConfigService.initialSetup();
897
- console.log(chalk.green('✓ Setup complete!\n'));
898
- }
899
-
900
- const runtimeChoices = await SmartConfigService.selectRuntimeChoices({
901
- selectProvider: options.selectProvider,
902
- selectAgent: options.selectAgent,
903
- useDefaults: options.useDefaults,
904
- provider: options.provider,
905
- agent: options.agent,
906
- });
907
-
908
- // Setup environment with selected provider
909
- await SmartConfigService.setupEnvironment(runtimeChoices.provider!);
910
-
911
- // Use selected agent
912
- options.agent = runtimeChoices.agent;
913
- }
914
-
915
- const agent = options.agent || 'coder';
916
- const verbose = options.verbose || false;
917
-
918
- if (verbose || options.runOnly || !options.quick) {
919
- console.log(` 🤖 Agent: ${chalk.cyan(agent)}`);
920
- console.log(` 🎯 Target: ${chalk.cyan(resolvedTarget)}`);
921
- if (prompt) {
922
- console.log(` 💬 Prompt: ${chalk.dim(prompt)}\n`);
923
- } else {
924
- console.log(` 💬 Mode: ${chalk.dim('Interactive')}\n`);
925
- }
926
- }
927
-
928
- // Load agent and prepare prompts
929
- const agentContent = await loadAgentContent(agent, options.agentFile);
930
- const agentInstructions = extractAgentInstructions(agentContent);
931
- const systemPrompt = `AGENT INSTRUCTIONS:\n${agentInstructions}`;
932
-
933
- const userPrompt = prompt?.trim() || '';
934
-
935
- // Environment should already be set up by SmartConfigService in main flow
936
- // No need to setup again here
937
-
938
- // Run options
939
- const runOptions: RunCommandOptions = {
940
- target: resolvedTarget,
941
- verbose,
942
- dryRun: options.dryRun,
943
- agent,
944
- agentFile: options.agentFile,
945
- prompt,
946
- print: options.print,
947
- continue: options.continue,
948
- };
949
-
950
- try {
951
- await executeTargetCommand(resolvedTarget, systemPrompt, userPrompt, runOptions);
952
- } catch (error) {
953
- console.error(chalk.red.bold('\n✗ Launch failed:'), error);
954
- process.exit(1);
955
- }
956
-
957
- if (!options.dryRun) {
958
- console.log(chalk.dim('━━━\n'));
959
- console.log(chalk.green('✓ Session complete\n'));
960
- }
961
- } else {
962
- console.log(chalk.dim('✓ Init-only mode, skipping execution\n'));
963
- }
964
- }
12
+ import { showWelcome } from '../utils/display/banner.js';
13
+ import { showStatus } from '../utils/display/status.js';
14
+ import { executeFlow } from './flow/execute.js';
15
+ import type { FlowOptions } from './flow/types.js';
965
16
 
966
17
  /**
967
18
  * Smart flow command
@@ -1000,16 +51,20 @@ export const flowCommand = new Command('flow')
1000
51
  .option('-p, --print', 'Headless print mode (output only, no interactive)')
1001
52
  .option('-c, --continue', 'Continue previous conversation (requires print mode)')
1002
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)
57
+
1003
58
  // Prompt argument
1004
59
  .argument('[prompt]', 'Prompt to execute with agent (optional, supports @file.txt for file input)')
1005
60
 
1006
- .action(async (prompt, options) => {
61
+ .action(async (prompt, options: FlowOptions) => {
1007
62
  await executeFlow(prompt, options);
1008
63
  });
1009
64
 
1010
65
  /**
1011
66
  * Setup command - alias for `flow --init-only`
1012
- * Kept for backward compatibility, but users should prefer `flow --init-only`
67
+ * Kept for backward compatibility
1013
68
  */
1014
69
  export const setupCommand = new Command('setup')
1015
70
  .description('Initialize project configuration (alias for: flow --init-only)')
@@ -1019,10 +74,9 @@ export const setupCommand = new Command('setup')
1019
74
 
1020
75
  showWelcome();
1021
76
 
1022
- // Initialize project with default target
1023
77
  const { runInit } = await import('./init-command.js');
1024
78
  await runInit({
1025
- target: undefined, // Let user choose
79
+ target: undefined,
1026
80
  verbose: false,
1027
81
  dryRun: false,
1028
82
  clear: false,
@@ -1056,7 +110,6 @@ export const statusCommand = new Command('status')
1056
110
  if (options.verbose) {
1057
111
  console.log(chalk.cyan.bold('\n📋 详细信息\n'));
1058
112
 
1059
- // 配置文件内容
1060
113
  try {
1061
114
  const { getProjectSettingsFile } = await import('../config/constants.js');
1062
115
  const configPath = path.join(process.cwd(), getProjectSettingsFile());
@@ -1105,9 +158,7 @@ export const doctorCommand = new Command('doctor')
1105
158
 
1106
159
  if (options.fix) {
1107
160
  console.log(chalk.yellow(' 🔄 正在修复...'));
1108
- // Run flow with clean flag
1109
- const { executeFlow } = await import('./flow-command.js');
1110
- await executeFlow(undefined, { clean: true });
161
+ await executeFlow(undefined, { sync: true } as FlowOptions);
1111
162
  console.log(chalk.green(' ✓ 已修复'));
1112
163
  }
1113
164
  } else if (!state.initialized) {