@sylphx/flow 1.8.0 → 1.8.2

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 +72 -0
  2. package/assets/output-styles/silent.md +145 -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
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Execution Logic for Flow Command
3
+ * Command execution for single-run and loop modes
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import { targetManager } from '../../core/target-manager.js';
8
+ import { StateDetector } from '../../core/state-detector.js';
9
+ import { UpgradeManager } from '../../core/upgrade-manager.js';
10
+ import { projectSettings } from '../../utils/config/settings.js';
11
+ import { showWelcome } from '../../utils/display/banner.js';
12
+ import { showStatus } from '../../utils/display/status.js';
13
+ import { loadAgentContent, extractAgentInstructions } from '../run-command.js';
14
+ import { CLIError } from '../../utils/error-handler.js';
15
+ import type { RunCommandOptions } from '../../types.js';
16
+ import type { FlowOptions, SetupContext } from './types.js';
17
+ import { resolvePrompt } from './prompt.js';
18
+ import { executeSetupPhase } from './setup.js';
19
+ import { getExecutableTargets } from './targets.js';
20
+
21
+ /**
22
+ * Execute command using target's executeCommand method
23
+ */
24
+ export async function executeTargetCommand(
25
+ targetId: string,
26
+ systemPrompt: string,
27
+ userPrompt: string,
28
+ options: RunCommandOptions
29
+ ): Promise<void> {
30
+ const targetOption = targetManager.getTarget(targetId);
31
+
32
+ if (targetOption._tag === 'None') {
33
+ throw new CLIError(`Target not found: ${targetId}`, 'TARGET_NOT_FOUND');
34
+ }
35
+
36
+ const target = targetOption.value;
37
+
38
+ if (!target.isImplemented) {
39
+ throw new CLIError(
40
+ `Target '${targetId}' is not implemented. Supported targets: ${getExecutableTargets().join(', ')}`,
41
+ 'TARGET_NOT_IMPLEMENTED'
42
+ );
43
+ }
44
+
45
+ if (!target.executeCommand) {
46
+ throw new CLIError(
47
+ `Target '${targetId}' does not support command execution. Supported targets: ${getExecutableTargets().join(', ')}`,
48
+ 'EXECUTION_NOT_SUPPORTED'
49
+ );
50
+ }
51
+
52
+ return target.executeCommand(systemPrompt, userPrompt, options);
53
+ }
54
+
55
+ /**
56
+ * Execute command only (for loop mode iterations)
57
+ * Uses pre-setup context to execute command without re-doing setup
58
+ */
59
+ export async function executeCommandOnly(
60
+ context: SetupContext,
61
+ prompt: string | undefined,
62
+ options: FlowOptions
63
+ ): Promise<void> {
64
+ const userPrompt = prompt?.trim() || '';
65
+
66
+ // Update continue flag in runOptions
67
+ const runOptions = {
68
+ ...context.runOptions,
69
+ continue: options.continue,
70
+ };
71
+
72
+ try {
73
+ await executeTargetCommand(context.resolvedTarget, context.systemPrompt!, userPrompt, runOptions);
74
+ } catch (error) {
75
+ console.error(chalk.red.bold('\n✗ Launch failed:'), error);
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Single flow execution (used by both normal and loop mode)
82
+ */
83
+ export async function executeFlowOnce(prompt: string | undefined, options: FlowOptions): Promise<void> {
84
+ // Quick mode: enable useDefaults and skip prompts
85
+ if (options.quick) {
86
+ options.useDefaults = true;
87
+ console.log(chalk.cyan('⚡ Quick mode enabled - using saved defaults\n'));
88
+ }
89
+
90
+ // Continue mode always requires print mode
91
+ if (options.continue && !options.print) {
92
+ options.print = true;
93
+ }
94
+
95
+ // Import orchestrator functions
96
+ const {
97
+ checkUpgrades,
98
+ checkComponentIntegrity,
99
+ checkSyncStatus,
100
+ } = await import('../flow-orchestrator.js');
101
+
102
+ // Show welcome banner
103
+ showWelcome();
104
+
105
+ // Declare at function level to persist across steps
106
+ let selectedTarget: string | undefined;
107
+ let state = undefined;
108
+
109
+ // First: determine target (from options, saved settings, or init will prompt)
110
+ const initialTarget = options.target || (await projectSettings.getDefaultTarget());
111
+
112
+ // Only detect state if we have a target (can't check components without knowing target structure)
113
+ if (initialTarget && !options.sync) {
114
+ const detector = new StateDetector();
115
+ const upgradeManager = new UpgradeManager();
116
+
117
+ if (options.verbose) {
118
+ console.log(chalk.dim('🤔 Checking project status...\n'));
119
+ }
120
+
121
+ state = await detector.detect();
122
+
123
+ if (options.verbose) {
124
+ await showStatus(state);
125
+ }
126
+
127
+ // Step 1: Check for upgrades
128
+ if (!options.quick) {
129
+ await checkUpgrades(state, options);
130
+ }
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
+ // Step 2.5: Check component integrity (only if we have valid state)
150
+ await checkComponentIntegrity(state, options);
151
+
152
+ // Step 2.6: Check sync status (new templates available)
153
+ await checkSyncStatus(state, options);
154
+ }
155
+
156
+ // Step 3: Initialize (only if actually needed)
157
+ const shouldInitialize =
158
+ !state?.initialized ||
159
+ options.sync ||
160
+ options.repair ||
161
+ options.initOnly;
162
+
163
+ if (shouldInitialize) {
164
+ console.log(chalk.cyan.bold('━━━ 🚀 Initializing Project\n'));
165
+ selectedTarget = await initializeProject(options, state);
166
+ }
167
+
168
+ // Step 4: Launch target (if not init-only)
169
+ if (!options.initOnly) {
170
+ await launchTarget(prompt, options, state, selectedTarget);
171
+ } else {
172
+ console.log(chalk.dim('✓ Init-only mode, skipping execution\n'));
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Initialize project with components
178
+ */
179
+ async function initializeProject(options: FlowOptions, state: any): Promise<string | undefined> {
180
+ const {
181
+ selectAndValidateTarget,
182
+ previewDryRun,
183
+ installComponents,
184
+ } = await import('../init-core.js');
185
+
186
+ let selectedTarget: string | undefined;
187
+
188
+ try {
189
+ // In repair mode, use existing target from state
190
+ const targetForInit = options.repair && state?.target
191
+ ? state.target
192
+ : options.target;
193
+
194
+ // Prepare init options
195
+ const initOptions = {
196
+ target: targetForInit,
197
+ verbose: options.verbose,
198
+ dryRun: options.dryRun,
199
+ clear: options.sync || false,
200
+ mcp: options.mcp !== false,
201
+ agents: options.agents !== false,
202
+ rules: options.rules !== false,
203
+ outputStyles: options.outputStyles !== false,
204
+ slashCommands: options.slashCommands !== false,
205
+ hooks: options.hooks !== false,
206
+ };
207
+
208
+ // Handle sync mode - delete template files first
209
+ if (options.sync && !options.dryRun) {
210
+ selectedTarget = await handleSyncMode(initOptions);
211
+ } else {
212
+ // Select and validate target
213
+ const targetId = await selectAndValidateTarget(initOptions);
214
+ selectedTarget = targetId;
215
+ }
216
+
217
+ // Dry run preview
218
+ if (options.dryRun) {
219
+ if (!selectedTarget) {
220
+ const targetId = await selectAndValidateTarget(initOptions);
221
+ selectedTarget = targetId;
222
+ }
223
+
224
+ await previewDryRun(selectedTarget, initOptions);
225
+ console.log(chalk.dim('✓ Initialization dry run complete\n'));
226
+ } else {
227
+ // Actually install components
228
+ if (!selectedTarget) {
229
+ const targetId = await selectAndValidateTarget(initOptions);
230
+ selectedTarget = targetId;
231
+ }
232
+
233
+ await installComponents(selectedTarget, initOptions);
234
+ console.log(chalk.green.bold('✓ Initialization complete\n'));
235
+ }
236
+ } catch (error) {
237
+ console.error(chalk.red.bold('✗ Initialization failed:'), error);
238
+ process.exit(1);
239
+ }
240
+
241
+ return selectedTarget;
242
+ }
243
+
244
+ /**
245
+ * Handle sync mode
246
+ */
247
+ async function handleSyncMode(initOptions: any): Promise<string> {
248
+ const { buildSyncManifest, showSyncPreview, selectUnknownFilesToRemove, showFinalSummary, confirmSync, executeSyncDelete, removeMCPServers, removeHooks } = await import('../../utils/files/sync-utils.js');
249
+ const { selectAndValidateTarget } = await import('../init-core.js');
250
+
251
+ const targetId = await selectAndValidateTarget(initOptions);
252
+
253
+ const targetOption = targetManager.getTarget(targetId);
254
+ if (targetOption._tag === 'None') {
255
+ throw new Error(`Target not found: ${targetId}`);
256
+ }
257
+
258
+ const target = targetOption.value;
259
+ const manifest = await buildSyncManifest(process.cwd(), target);
260
+
261
+ console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
262
+ showSyncPreview(manifest, process.cwd(), target);
263
+
264
+ const selectedUnknowns = await selectUnknownFilesToRemove(manifest);
265
+ showFinalSummary(manifest, selectedUnknowns);
266
+
267
+ const confirmed = await confirmSync();
268
+ if (!confirmed) {
269
+ console.log(chalk.yellow('\n✗ Sync cancelled\n'));
270
+ process.exit(0);
271
+ }
272
+
273
+ const { templates, unknowns } = await executeSyncDelete(manifest, selectedUnknowns);
274
+
275
+ let mcpRemoved = 0;
276
+ if (selectedUnknowns.mcpServers.length > 0) {
277
+ mcpRemoved = await removeMCPServers(process.cwd(), selectedUnknowns.mcpServers);
278
+ }
279
+
280
+ let hooksRemoved = 0;
281
+ if (selectedUnknowns.hooks.length > 0) {
282
+ hooksRemoved = await removeHooks(process.cwd(), selectedUnknowns.hooks);
283
+ }
284
+
285
+ console.log(chalk.green(`\n✓ Synced ${templates} templates`));
286
+ const totalRemoved = unknowns + mcpRemoved + hooksRemoved;
287
+ if (totalRemoved > 0) {
288
+ console.log(chalk.green(`✓ Removed ${totalRemoved} items`));
289
+ }
290
+ const totalSelected = selectedUnknowns.files.length + selectedUnknowns.mcpServers.length + selectedUnknowns.hooks.length;
291
+ const preserved = manifest.agents.unknown.length + manifest.slashCommands.unknown.length + manifest.rules.unknown.length + manifest.mcpServers.notInRegistry.length + manifest.hooks.orphaned.length - totalSelected;
292
+ if (preserved > 0) {
293
+ console.log(chalk.green(`✓ Preserved ${preserved} custom items`));
294
+ }
295
+ console.log('');
296
+
297
+ return targetId;
298
+ }
299
+
300
+ /**
301
+ * Launch target with agent
302
+ */
303
+ async function launchTarget(
304
+ prompt: string | undefined,
305
+ options: FlowOptions,
306
+ state: any,
307
+ selectedTarget: string | undefined
308
+ ): Promise<void> {
309
+ // Resolve target - use the target we just selected
310
+ let targetForResolution = options.target || state?.target || selectedTarget;
311
+
312
+ // If we just selected a target during init, use that
313
+ if (selectedTarget) {
314
+ targetForResolution = selectedTarget;
315
+ }
316
+
317
+ if (!targetForResolution) {
318
+ console.error(chalk.red.bold('✗ No target selected. Use --target or run init first.'));
319
+ process.exit(1);
320
+ }
321
+
322
+ const resolvedTarget = await targetManager.resolveTarget({
323
+ target: targetForResolution,
324
+ allowSelection: false,
325
+ });
326
+
327
+ console.log(chalk.cyan.bold(`━━━ 🎯 Launching ${resolvedTarget}\n`));
328
+
329
+ // Check if target supports command execution
330
+ const { getTargetsWithCommandSupport } = await import('../../config/targets.js');
331
+ const supportedTargets = getTargetsWithCommandSupport().map(t => t.id);
332
+
333
+ if (!supportedTargets.includes(resolvedTarget)) {
334
+ console.log(chalk.red.bold('✗ Unsupported target platform\n'));
335
+ console.log(chalk.yellow(`Target '${resolvedTarget}' does not support agent execution.`));
336
+ console.log(chalk.cyan(`Supported platforms: ${supportedTargets.join(', ')}\n`));
337
+ console.log(chalk.dim('Tip: Use --target claude-code to specify Claude Code platform'));
338
+ console.log(chalk.dim('Example: bun dev:flow --target claude-code\n'));
339
+ process.exit(1);
340
+ }
341
+
342
+ // Claude Code handling - needs provider/agent setup
343
+ if (resolvedTarget === 'claude-code') {
344
+ await setupClaudeCode(options);
345
+ }
346
+
347
+ const agent = options.agent || 'coder';
348
+ const verbose = options.verbose || false;
349
+
350
+ if (verbose || options.runOnly || !options.quick) {
351
+ console.log(` 🤖 Agent: ${chalk.cyan(agent)}`);
352
+ console.log(` 🎯 Target: ${chalk.cyan(resolvedTarget)}`);
353
+ if (prompt) {
354
+ console.log(` 💬 Prompt: ${chalk.dim(prompt)}\n`);
355
+ } else {
356
+ console.log(` 💬 Mode: ${chalk.dim('Interactive')}\n`);
357
+ }
358
+ }
359
+
360
+ // Load agent and prepare prompts
361
+ const agentContent = await loadAgentContent(agent, options.agentFile);
362
+ const agentInstructions = extractAgentInstructions(agentContent);
363
+ const systemPrompt = `AGENT INSTRUCTIONS:\n${agentInstructions}`;
364
+
365
+ const userPrompt = prompt?.trim() || '';
366
+
367
+ // Run options
368
+ const runOptions: RunCommandOptions = {
369
+ target: resolvedTarget,
370
+ verbose,
371
+ dryRun: options.dryRun,
372
+ agent,
373
+ agentFile: options.agentFile,
374
+ prompt,
375
+ print: options.print,
376
+ continue: options.continue,
377
+ };
378
+
379
+ try {
380
+ await executeTargetCommand(resolvedTarget, systemPrompt, userPrompt, runOptions);
381
+ } catch (error) {
382
+ console.error(chalk.red.bold('\n✗ Launch failed:'), error);
383
+ process.exit(1);
384
+ }
385
+
386
+ if (!options.dryRun) {
387
+ console.log(chalk.dim('━━━\n'));
388
+ console.log(chalk.green('✓ Session complete\n'));
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Setup Claude Code provider and agent
394
+ */
395
+ async function setupClaudeCode(options: FlowOptions): Promise<void> {
396
+ const { SmartConfigService } = await import('../../services/smart-config-service.js');
397
+ const { ConfigService } = await import('../../services/config-service.js');
398
+
399
+ if (!(await ConfigService.hasInitialSetup())) {
400
+ console.log(chalk.cyan('🔑 First-time setup for Claude Code\n'));
401
+ await SmartConfigService.initialSetup();
402
+ console.log(chalk.green('✓ Setup complete!\n'));
403
+ }
404
+
405
+ const runtimeChoices = await SmartConfigService.selectRuntimeChoices({
406
+ selectProvider: options.selectProvider,
407
+ selectAgent: options.selectAgent,
408
+ useDefaults: options.useDefaults,
409
+ provider: options.provider,
410
+ agent: options.agent,
411
+ });
412
+
413
+ await SmartConfigService.setupEnvironment(runtimeChoices.provider!);
414
+ options.agent = runtimeChoices.agent;
415
+ }
416
+
417
+ /**
418
+ * Main flow execution logic - simplified with orchestrator
419
+ */
420
+ export async function executeFlow(prompt: string | undefined, options: FlowOptions): Promise<void> {
421
+ // Resolve prompt (handle file input)
422
+ const resolvedPrompt = await resolvePrompt(prompt);
423
+
424
+ // Loop mode: Setup once, then loop only execution
425
+ if (options.loop !== undefined) {
426
+ const { LoopController } = await import('../../core/loop-controller.js');
427
+ const controller = new LoopController();
428
+
429
+ // Default to 0s (no cooldown) if just --loop with no value
430
+ const interval = typeof options.loop === 'number' ? options.loop : 0;
431
+
432
+ // Auto-enable headless mode for loop
433
+ options.print = true;
434
+
435
+ // ONE-TIME SETUP: Do all initialization once before loop starts
436
+ const setupContext = await executeSetupPhase(resolvedPrompt, options);
437
+
438
+ // Save original continue flag
439
+ const originalContinue = options.continue || false;
440
+
441
+ // LOOP: Only execute the command repeatedly
442
+ await controller.run(
443
+ async () => {
444
+ const isFirstIteration = controller['state'].iteration === 1;
445
+
446
+ // Continue logic:
447
+ // - If user specified --continue, always use it (all iterations)
448
+ // - If user didn't specify, only use from 2nd iteration onwards
449
+ options.continue = originalContinue || !isFirstIteration;
450
+
451
+ try {
452
+ await executeCommandOnly(setupContext, resolvedPrompt, options);
453
+ return { exitCode: 0 };
454
+ } catch (error) {
455
+ return { exitCode: 1, error: error as Error };
456
+ }
457
+ },
458
+ {
459
+ enabled: true,
460
+ interval,
461
+ maxRuns: options.maxRuns,
462
+ }
463
+ );
464
+
465
+ return;
466
+ }
467
+
468
+ // Normal execution (non-loop)
469
+ await executeFlowOnce(resolvedPrompt, options);
470
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Flow Command Exports
3
+ * Centralized exports for all flow command components
4
+ */
5
+
6
+ export { executeFlow } from './execute.js';
7
+ export { executeSetupPhase } from './setup.js';
8
+ export { executeTargetCommand, executeCommandOnly, executeFlowOnce } from './execute.js';
9
+ export { resolvePrompt } from './prompt.js';
10
+ export { getExecutableTargets } from './targets.js';
11
+ export type { FlowOptions, SetupContext } from './types.js';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Prompt Resolution Utilities
3
+ * Handle file input (@file.txt) and prompt loading
4
+ */
5
+
6
+ import path from 'node:path';
7
+ import fs from 'node:fs/promises';
8
+ import chalk from 'chalk';
9
+
10
+ /**
11
+ * Resolve prompt - handle file input if needed
12
+ * Supports @filename syntax: @prompt.txt or @/path/to/prompt.txt
13
+ */
14
+ export async function resolvePrompt(prompt: string | undefined): Promise<string | undefined> {
15
+ if (!prompt) return prompt;
16
+
17
+ // Check for file input syntax: @filename
18
+ if (prompt.startsWith('@')) {
19
+ const filePath = prompt.slice(1); // Remove @ prefix
20
+
21
+ try {
22
+ const resolvedPath = path.isAbsolute(filePath)
23
+ ? filePath
24
+ : path.resolve(process.cwd(), filePath);
25
+
26
+ const content = await fs.readFile(resolvedPath, 'utf-8');
27
+ console.log(chalk.dim(` ✓ Loaded prompt from: ${filePath}\n`));
28
+ return content.trim();
29
+ } catch (error) {
30
+ throw new Error(`Failed to read prompt file: ${filePath}`);
31
+ }
32
+ }
33
+
34
+ return prompt;
35
+ }