@sylphx/flow 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +44 -0
- package/package.json +79 -73
- package/src/commands/flow/execute-v2.ts +39 -30
- package/src/commands/flow/index.ts +2 -4
- package/src/commands/flow/prompt.ts +5 -3
- package/src/commands/flow/types.ts +0 -9
- package/src/commands/flow-command.ts +20 -13
- package/src/commands/hook-command.ts +1 -3
- package/src/commands/settings-command.ts +36 -33
- package/src/config/ai-config.ts +60 -41
- package/src/core/agent-loader.ts +11 -6
- package/src/core/attach-manager.ts +92 -84
- package/src/core/backup-manager.ts +35 -29
- package/src/core/cleanup-handler.ts +11 -8
- package/src/core/error-handling.ts +23 -30
- package/src/core/flow-executor.ts +58 -76
- package/src/core/formatting/bytes.ts +2 -4
- package/src/core/functional/async.ts +5 -4
- package/src/core/functional/error-handler.ts +2 -2
- package/src/core/git-stash-manager.ts +21 -10
- package/src/core/installers/file-installer.ts +0 -1
- package/src/core/installers/mcp-installer.ts +0 -1
- package/src/core/project-manager.ts +24 -18
- package/src/core/secrets-manager.ts +54 -73
- package/src/core/session-manager.ts +20 -22
- package/src/core/state-detector.ts +139 -80
- package/src/core/template-loader.ts +13 -31
- package/src/core/upgrade-manager.ts +122 -69
- package/src/index.ts +8 -5
- package/src/services/auto-upgrade.ts +1 -1
- package/src/services/config-service.ts +41 -29
- package/src/services/global-config.ts +2 -2
- package/src/services/target-installer.ts +9 -7
- package/src/targets/claude-code.ts +28 -15
- package/src/targets/opencode.ts +17 -6
- package/src/types/cli.types.ts +2 -2
- package/src/types/provider.types.ts +1 -7
- package/src/types/session.types.ts +11 -11
- package/src/types/target.types.ts +3 -1
- package/src/types/todo.types.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
- package/src/utils/agent-enhancer.ts +111 -3
- package/src/utils/config/paths.ts +3 -1
- package/src/utils/config/target-utils.ts +2 -2
- package/src/utils/display/banner.ts +2 -2
- package/src/utils/display/notifications.ts +58 -45
- package/src/utils/display/status.ts +29 -12
- package/src/utils/files/file-operations.ts +1 -1
- package/src/utils/files/sync-utils.ts +38 -41
- package/src/utils/index.ts +19 -27
- package/src/utils/package-manager-detector.ts +15 -5
- package/src/utils/security/security.ts +8 -4
- package/src/utils/target-selection.ts +5 -2
- package/src/utils/version.ts +4 -2
- package/src/commands/flow/execute.ts +0 -453
- package/src/commands/flow/setup.ts +0 -312
- package/src/commands/flow-orchestrator.ts +0 -328
- package/src/commands/init-command.ts +0 -92
- package/src/commands/init-core.ts +0 -331
- package/src/commands/run-command.ts +0 -126
- package/src/core/agent-manager.ts +0 -174
- package/src/core/loop-controller.ts +0 -200
- package/src/core/rule-loader.ts +0 -147
- package/src/core/rule-manager.ts +0 -240
- package/src/services/claude-config-service.ts +0 -252
- package/src/services/first-run-setup.ts +0 -220
- package/src/services/smart-config-service.ts +0 -269
- package/src/types/api.types.ts +0 -9
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Setup Phase for Flow Command
|
|
3
|
-
* One-time initialization and preparation for command execution
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import { targetManager } from '../../core/target-manager.js';
|
|
8
|
-
import { StateDetector, type ProjectState } from '../../core/state-detector.js';
|
|
9
|
-
import { projectSettings } from '../../utils/config/settings.js';
|
|
10
|
-
import { showWelcome } from '../../utils/display/banner.js';
|
|
11
|
-
import { showStatus } from '../../utils/display/status.js';
|
|
12
|
-
import { loadAgentContent, extractAgentInstructions } from '../run-command.js';
|
|
13
|
-
import type { RunCommandOptions } from '../../types.js';
|
|
14
|
-
import type { FlowOptions, SetupContext } from './types.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Execute setup phase once (for loop mode)
|
|
18
|
-
* Returns context needed for repeated command execution
|
|
19
|
-
*/
|
|
20
|
-
export async function executeSetupPhase(prompt: string | undefined, options: FlowOptions): Promise<SetupContext> {
|
|
21
|
-
// Quick mode: enable useDefaults and skip prompts
|
|
22
|
-
if (options.quick) {
|
|
23
|
-
options.useDefaults = true;
|
|
24
|
-
console.log(chalk.cyan('⚡ Quick mode enabled - using saved defaults\n'));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Import orchestrator functions
|
|
28
|
-
const {
|
|
29
|
-
checkUpgrades,
|
|
30
|
-
checkComponentIntegrity,
|
|
31
|
-
} = await import('../flow-orchestrator.js');
|
|
32
|
-
|
|
33
|
-
// Show welcome banner (only once)
|
|
34
|
-
showWelcome();
|
|
35
|
-
|
|
36
|
-
let selectedTarget: string | undefined;
|
|
37
|
-
let state: ProjectState | undefined;
|
|
38
|
-
|
|
39
|
-
// Determine target
|
|
40
|
-
const initialTarget = options.target || (await projectSettings.getDefaultTarget());
|
|
41
|
-
|
|
42
|
-
// Detect state if we have a target
|
|
43
|
-
if (initialTarget && !options.sync) {
|
|
44
|
-
const detector = new StateDetector();
|
|
45
|
-
|
|
46
|
-
if (options.verbose) {
|
|
47
|
-
console.log(chalk.dim('🤔 Checking project status...\n'));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
state = await detector.detect();
|
|
51
|
-
|
|
52
|
-
if (options.verbose) {
|
|
53
|
-
await showStatus(state);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Check for upgrades
|
|
57
|
-
if (!options.quick) {
|
|
58
|
-
await checkUpgrades(state, options);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check component integrity
|
|
62
|
-
await checkComponentIntegrity(state, options);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Initialize if needed
|
|
66
|
-
const shouldInitialize =
|
|
67
|
-
!state?.initialized ||
|
|
68
|
-
options.sync ||
|
|
69
|
-
options.repair ||
|
|
70
|
-
options.initOnly;
|
|
71
|
-
|
|
72
|
-
if (shouldInitialize) {
|
|
73
|
-
selectedTarget = await initializeWithTarget(options);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Resolve target
|
|
77
|
-
const targetForResolution = resolveTargetPriority(options.target, state?.target, selectedTarget);
|
|
78
|
-
|
|
79
|
-
if (!targetForResolution) {
|
|
80
|
-
console.error(chalk.red.bold('✗ No target selected. Use --target or run init first.'));
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const resolvedTarget = await targetManager.resolveTarget({
|
|
85
|
-
target: targetForResolution,
|
|
86
|
-
allowSelection: false,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
console.log(chalk.cyan.bold(`━━━ 🎯 Launching ${resolvedTarget}\n`));
|
|
90
|
-
|
|
91
|
-
// Validate target support
|
|
92
|
-
await validateTargetSupport(resolvedTarget);
|
|
93
|
-
|
|
94
|
-
// Handle Claude Code specific setup
|
|
95
|
-
if (resolvedTarget === 'claude-code') {
|
|
96
|
-
await setupClaudeCode(options);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const agent = options.agent || 'coder';
|
|
100
|
-
const verbose = options.verbose || false;
|
|
101
|
-
|
|
102
|
-
if (verbose || options.runOnly || !options.quick) {
|
|
103
|
-
console.log(` 🤖 Agent: ${chalk.cyan(agent)}`);
|
|
104
|
-
console.log(` 🎯 Target: ${chalk.cyan(resolvedTarget)}`);
|
|
105
|
-
if (prompt) {
|
|
106
|
-
console.log(` 💬 Prompt: ${chalk.dim(prompt)}\n`);
|
|
107
|
-
} else {
|
|
108
|
-
console.log(` 💬 Mode: ${chalk.dim('Interactive')}\n`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Load agent and prepare prompts
|
|
113
|
-
const agentContent = await loadAgentContent(agent, options.agentFile);
|
|
114
|
-
const agentInstructions = extractAgentInstructions(agentContent);
|
|
115
|
-
const systemPrompt = `AGENT INSTRUCTIONS:\n${agentInstructions}`;
|
|
116
|
-
|
|
117
|
-
// Prepare run options
|
|
118
|
-
const runOptions: RunCommandOptions = {
|
|
119
|
-
target: resolvedTarget,
|
|
120
|
-
verbose,
|
|
121
|
-
dryRun: options.dryRun,
|
|
122
|
-
agent,
|
|
123
|
-
agentFile: options.agentFile,
|
|
124
|
-
prompt,
|
|
125
|
-
print: options.print,
|
|
126
|
-
continue: options.continue,
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
resolvedTarget,
|
|
131
|
-
initializedSuccessfully: true,
|
|
132
|
-
systemPrompt,
|
|
133
|
-
runOptions,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Initialize project with target selection and component installation
|
|
139
|
-
*/
|
|
140
|
-
async function initializeWithTarget(options: FlowOptions): Promise<string | undefined> {
|
|
141
|
-
let selectedTarget: string | undefined;
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const { selectAndValidateTarget, previewDryRun, installComponents } =
|
|
145
|
-
await import('../init-core.js');
|
|
146
|
-
|
|
147
|
-
const initOptions = {
|
|
148
|
-
target: options.target,
|
|
149
|
-
verbose: options.verbose || false,
|
|
150
|
-
dryRun: options.dryRun || false,
|
|
151
|
-
clear: options.sync || false,
|
|
152
|
-
mcp: options.mcp !== false,
|
|
153
|
-
agents: options.agents !== false,
|
|
154
|
-
rules: options.rules !== false,
|
|
155
|
-
outputStyles: options.outputStyles !== false,
|
|
156
|
-
slashCommands: options.slashCommands !== false,
|
|
157
|
-
hooks: options.hooks !== false,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Handle sync mode - delete template files first
|
|
161
|
-
if (options.sync && !options.dryRun) {
|
|
162
|
-
selectedTarget = await handleSyncMode(initOptions);
|
|
163
|
-
} else if (!options.sync) {
|
|
164
|
-
const targetId = await selectAndValidateTarget(initOptions);
|
|
165
|
-
selectedTarget = targetId;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (options.dryRun) {
|
|
169
|
-
// Ensure we have a target ID for dry run
|
|
170
|
-
if (!selectedTarget) {
|
|
171
|
-
const targetId = await selectAndValidateTarget(initOptions);
|
|
172
|
-
selectedTarget = targetId;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
await previewDryRun(selectedTarget, initOptions);
|
|
176
|
-
console.log(chalk.dim('✓ Initialization dry run complete\n'));
|
|
177
|
-
} else {
|
|
178
|
-
// Ensure we have a target ID for installation
|
|
179
|
-
if (!selectedTarget) {
|
|
180
|
-
const targetId = await selectAndValidateTarget(initOptions);
|
|
181
|
-
selectedTarget = targetId;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
await installComponents(selectedTarget, initOptions);
|
|
185
|
-
console.log(chalk.green.bold('✓ Initialization complete\n'));
|
|
186
|
-
}
|
|
187
|
-
} catch (error) {
|
|
188
|
-
console.error(chalk.red.bold('✗ Initialization failed:'), error);
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return selectedTarget;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Handle sync mode: delete templates then reinstall
|
|
197
|
-
*/
|
|
198
|
-
async function handleSyncMode(initOptions: any): Promise<string> {
|
|
199
|
-
const { buildSyncManifest, showSyncPreview, selectUnknownFilesToRemove, showFinalSummary, confirmSync, executeSyncDelete, removeMCPServers, removeHooks } = await import('../../utils/files/sync-utils.js');
|
|
200
|
-
const { selectAndValidateTarget } = await import('../init-core.js');
|
|
201
|
-
|
|
202
|
-
// Need target to build manifest
|
|
203
|
-
const targetId = await selectAndValidateTarget(initOptions);
|
|
204
|
-
|
|
205
|
-
const targetOption = targetManager.getTarget(targetId);
|
|
206
|
-
if (targetOption._tag === 'None') {
|
|
207
|
-
throw new Error(`Target not found: ${targetId}`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const target = targetOption.value;
|
|
211
|
-
const manifest = await buildSyncManifest(process.cwd(), target);
|
|
212
|
-
|
|
213
|
-
// Show preview
|
|
214
|
-
console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
|
|
215
|
-
showSyncPreview(manifest, process.cwd(), target);
|
|
216
|
-
|
|
217
|
-
// Select unknown files to remove
|
|
218
|
-
const selectedUnknowns = await selectUnknownFilesToRemove(manifest);
|
|
219
|
-
|
|
220
|
-
// Show final summary
|
|
221
|
-
showFinalSummary(manifest, selectedUnknowns);
|
|
222
|
-
|
|
223
|
-
// Confirm
|
|
224
|
-
const confirmed = await confirmSync();
|
|
225
|
-
if (!confirmed) {
|
|
226
|
-
console.log(chalk.yellow('\n✗ Sync cancelled\n'));
|
|
227
|
-
process.exit(0);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Execute deletion
|
|
231
|
-
const { templates, unknowns } = await executeSyncDelete(manifest, selectedUnknowns);
|
|
232
|
-
|
|
233
|
-
// Remove MCP servers
|
|
234
|
-
let mcpRemoved = 0;
|
|
235
|
-
if (selectedUnknowns.mcpServers.length > 0) {
|
|
236
|
-
mcpRemoved = await removeMCPServers(process.cwd(), selectedUnknowns.mcpServers);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Remove hooks
|
|
240
|
-
let hooksRemoved = 0;
|
|
241
|
-
if (selectedUnknowns.hooks.length > 0) {
|
|
242
|
-
hooksRemoved = await removeHooks(process.cwd(), selectedUnknowns.hooks);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Summary
|
|
246
|
-
console.log(chalk.green(`\n✓ Synced ${templates} templates`));
|
|
247
|
-
const totalRemoved = unknowns + mcpRemoved + hooksRemoved;
|
|
248
|
-
if (totalRemoved > 0) {
|
|
249
|
-
console.log(chalk.green(`✓ Removed ${totalRemoved} items`));
|
|
250
|
-
}
|
|
251
|
-
const totalSelected = selectedUnknowns.files.length + selectedUnknowns.mcpServers.length + selectedUnknowns.hooks.length;
|
|
252
|
-
const preserved = manifest.agents.unknown.length + manifest.slashCommands.unknown.length + manifest.rules.unknown.length + manifest.mcpServers.notInRegistry.length + manifest.hooks.orphaned.length - totalSelected;
|
|
253
|
-
if (preserved > 0) {
|
|
254
|
-
console.log(chalk.green(`✓ Preserved ${preserved} custom items`));
|
|
255
|
-
}
|
|
256
|
-
console.log('');
|
|
257
|
-
|
|
258
|
-
return targetId;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Resolve target priority: options > state > selected
|
|
263
|
-
*/
|
|
264
|
-
function resolveTargetPriority(
|
|
265
|
-
optionsTarget: string | undefined,
|
|
266
|
-
stateTarget: string | undefined,
|
|
267
|
-
selectedTarget: string | undefined
|
|
268
|
-
): string | undefined {
|
|
269
|
-
return selectedTarget || optionsTarget || stateTarget;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Validate that target supports command execution
|
|
274
|
-
*/
|
|
275
|
-
async function validateTargetSupport(resolvedTarget: string): Promise<void> {
|
|
276
|
-
const { getTargetsWithCommandSupport } = await import('../../config/targets.js');
|
|
277
|
-
const supportedTargets = getTargetsWithCommandSupport().map(t => t.id);
|
|
278
|
-
|
|
279
|
-
if (!supportedTargets.includes(resolvedTarget)) {
|
|
280
|
-
console.log(chalk.red.bold('✗ Unsupported target platform\n'));
|
|
281
|
-
console.log(chalk.yellow(`Target '${resolvedTarget}' does not support agent execution.`));
|
|
282
|
-
console.log(chalk.cyan(`Supported platforms: ${supportedTargets.join(', ')}\n`));
|
|
283
|
-
console.log(chalk.dim('Tip: Use --target claude-code to specify Claude Code platform'));
|
|
284
|
-
console.log(chalk.dim('Example: bun dev:flow --target claude-code\n'));
|
|
285
|
-
process.exit(1);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Setup Claude Code provider and agent
|
|
291
|
-
*/
|
|
292
|
-
async function setupClaudeCode(options: FlowOptions): Promise<void> {
|
|
293
|
-
const { SmartConfigService } = await import('../../services/smart-config-service.js');
|
|
294
|
-
const { ConfigService } = await import('../../services/config-service.js');
|
|
295
|
-
|
|
296
|
-
if (!(await ConfigService.hasInitialSetup())) {
|
|
297
|
-
console.log(chalk.cyan('🔑 First-time setup for Claude Code\n'));
|
|
298
|
-
await SmartConfigService.initialSetup();
|
|
299
|
-
console.log(chalk.green('✓ Setup complete!\n'));
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const runtimeChoices = await SmartConfigService.selectRuntimeChoices({
|
|
303
|
-
selectProvider: options.selectProvider,
|
|
304
|
-
selectAgent: options.selectAgent,
|
|
305
|
-
useDefaults: options.useDefaults,
|
|
306
|
-
provider: options.provider,
|
|
307
|
-
agent: options.agent,
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
await SmartConfigService.setupEnvironment(runtimeChoices.provider!);
|
|
311
|
-
options.agent = runtimeChoices.agent;
|
|
312
|
-
}
|
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flow Orchestrator - Simplified flow management
|
|
3
|
-
* Separates concerns and reduces complexity
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import type { FlowOptions } from './flow/types.js';
|
|
8
|
-
import { StateDetector, type ProjectState } from '../core/state-detector.js';
|
|
9
|
-
import { UpgradeManager } from '../core/upgrade-manager.js';
|
|
10
|
-
import { targetManager } from '../core/target-manager.js';
|
|
11
|
-
import { detectPackageManager, getUpgradeCommand } from '../utils/package-manager-detector.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Step 1: Check for available upgrades
|
|
15
|
-
*/
|
|
16
|
-
export async function checkUpgrades(
|
|
17
|
-
state: ProjectState,
|
|
18
|
-
options: FlowOptions
|
|
19
|
-
): Promise<void> {
|
|
20
|
-
if (options.initOnly || options.runOnly) return;
|
|
21
|
-
|
|
22
|
-
const upgradeManager = new UpgradeManager();
|
|
23
|
-
const updates = await upgradeManager.checkUpdates();
|
|
24
|
-
const packageManager = detectPackageManager();
|
|
25
|
-
|
|
26
|
-
// Check Flow upgrade
|
|
27
|
-
if (updates.flowUpdate && updates.flowVersion) {
|
|
28
|
-
const upgradeCmd = getUpgradeCommand('@sylphx/flow', packageManager);
|
|
29
|
-
console.log(
|
|
30
|
-
chalk.yellow(
|
|
31
|
-
`📦 Sylphx Flow update available: ${updates.flowVersion.current} → ${updates.flowVersion.latest}`
|
|
32
|
-
)
|
|
33
|
-
);
|
|
34
|
-
console.log(chalk.dim(` Quick upgrade: ${chalk.cyan('sylphx-flow upgrade --auto')}`));
|
|
35
|
-
console.log(chalk.dim(` Or run: ${chalk.cyan(upgradeCmd)}\n`));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Check target upgrade
|
|
39
|
-
if (updates.targetUpdate && updates.targetVersion) {
|
|
40
|
-
console.log(
|
|
41
|
-
chalk.yellow(
|
|
42
|
-
`📦 Target update available: ${updates.targetVersion.current} → ${updates.targetVersion.latest}`
|
|
43
|
-
)
|
|
44
|
-
);
|
|
45
|
-
console.log(chalk.dim(` Run: ${chalk.cyan('sylphx-flow upgrade --target --auto')}\n`));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Step 2: Check component integrity and prompt for repair
|
|
52
|
-
*/
|
|
53
|
-
export async function checkComponentIntegrity(
|
|
54
|
-
state: ProjectState,
|
|
55
|
-
options: FlowOptions
|
|
56
|
-
): Promise<void> {
|
|
57
|
-
// Skip if not initialized or cleaning or init-only
|
|
58
|
-
if (!state.initialized || options.clean || options.initOnly) return;
|
|
59
|
-
|
|
60
|
-
// Skip in quick mode
|
|
61
|
-
if (options.quick) return;
|
|
62
|
-
|
|
63
|
-
// Find missing components (target-aware)
|
|
64
|
-
const missing: string[] = [];
|
|
65
|
-
|
|
66
|
-
// Agents are always required
|
|
67
|
-
if (!state.components.agents.installed) missing.push('agents');
|
|
68
|
-
|
|
69
|
-
// For OpenCode: check rules (separate AGENTS.md file)
|
|
70
|
-
// For Claude Code: rules are included in agent files, so skip this check
|
|
71
|
-
if (state.target === 'opencode' && !state.components.rules.installed) {
|
|
72
|
-
missing.push('rules');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Hooks are optional - don't check
|
|
76
|
-
// Claude Code can have hooks in .claude/hooks/*.js but they're optional
|
|
77
|
-
// OpenCode doesn't have separate hooks
|
|
78
|
-
|
|
79
|
-
// MCP is optional now - many users don't use MCP
|
|
80
|
-
// if (!state.components.mcp.installed) missing.push('mcp');
|
|
81
|
-
|
|
82
|
-
// Output styles:
|
|
83
|
-
// - Claude Code: included in agent files, so skip check
|
|
84
|
-
// - OpenCode: included in AGENTS.md, so skip check
|
|
85
|
-
|
|
86
|
-
// Slash commands are optional
|
|
87
|
-
// if (!state.components.slashCommands.installed) missing.push('slash commands');
|
|
88
|
-
|
|
89
|
-
// If no missing components, we're good
|
|
90
|
-
if (missing.length === 0) return;
|
|
91
|
-
|
|
92
|
-
// Prompt user to repair
|
|
93
|
-
console.log(chalk.yellow(`\n⚠️ Missing components detected: ${missing.join(', ')}\n`));
|
|
94
|
-
const { default: inquirer } = await import('inquirer');
|
|
95
|
-
const { repair } = await inquirer.prompt([
|
|
96
|
-
{
|
|
97
|
-
type: 'confirm',
|
|
98
|
-
name: 'repair',
|
|
99
|
-
message: 'Install missing components now?',
|
|
100
|
-
default: true,
|
|
101
|
-
},
|
|
102
|
-
]);
|
|
103
|
-
|
|
104
|
-
if (repair) {
|
|
105
|
-
// Set repair mode - will trigger component installation without full re-init
|
|
106
|
-
options.repair = true;
|
|
107
|
-
console.log(chalk.cyan('\n🔧 Repairing components...\n'));
|
|
108
|
-
} else {
|
|
109
|
-
console.log(chalk.dim('Skipping repair. Components may not work correctly.\n'));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Step 2.5: Check sync status (new templates available)
|
|
115
|
-
* Only checks for missing templates, ignores unknown files
|
|
116
|
-
*/
|
|
117
|
-
export async function checkSyncStatus(
|
|
118
|
-
state: ProjectState,
|
|
119
|
-
options: FlowOptions
|
|
120
|
-
): Promise<void> {
|
|
121
|
-
// Skip if not initialized, syncing, or init-only
|
|
122
|
-
if (!state.initialized || options.sync || options.initOnly) return;
|
|
123
|
-
|
|
124
|
-
// Skip in quick mode
|
|
125
|
-
if (options.quick) return;
|
|
126
|
-
|
|
127
|
-
// Need target to check sync status
|
|
128
|
-
if (!state.target) return;
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const { buildSyncManifest } = await import('../utils/files/sync-utils.js');
|
|
132
|
-
const target = targetManager.getTarget(state.target);
|
|
133
|
-
|
|
134
|
-
if (target._tag === 'None') return;
|
|
135
|
-
|
|
136
|
-
const manifest = await buildSyncManifest(process.cwd(), target.value);
|
|
137
|
-
|
|
138
|
-
// Count missing templates (new templates not installed locally)
|
|
139
|
-
const missingCount =
|
|
140
|
-
manifest.agents.missing.length +
|
|
141
|
-
manifest.slashCommands.missing.length +
|
|
142
|
-
manifest.rules.missing.length;
|
|
143
|
-
|
|
144
|
-
// Only prompt if there are missing templates
|
|
145
|
-
if (missingCount > 0) {
|
|
146
|
-
const missing: string[] = [];
|
|
147
|
-
|
|
148
|
-
if (manifest.agents.missing.length > 0) {
|
|
149
|
-
missing.push(`${manifest.agents.missing.length} agent${manifest.agents.missing.length > 1 ? 's' : ''}`);
|
|
150
|
-
}
|
|
151
|
-
if (manifest.slashCommands.missing.length > 0) {
|
|
152
|
-
missing.push(`${manifest.slashCommands.missing.length} command${manifest.slashCommands.missing.length > 1 ? 's' : ''}`);
|
|
153
|
-
}
|
|
154
|
-
if (manifest.rules.missing.length > 0) {
|
|
155
|
-
missing.push(`${manifest.rules.missing.length} rule${manifest.rules.missing.length > 1 ? 's' : ''}`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
console.log(chalk.yellow(`\n📦 New templates available: ${missing.join(', ')}\n`));
|
|
159
|
-
console.log(chalk.dim(` Run ${chalk.cyan('sylphx-flow --sync')} to install new templates\n`));
|
|
160
|
-
}
|
|
161
|
-
} catch (error) {
|
|
162
|
-
// Silently ignore sync check errors - don't block execution
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Step 3: Handle target selection
|
|
168
|
-
* Returns the selected target ID
|
|
169
|
-
*/
|
|
170
|
-
export async function selectTarget(
|
|
171
|
-
state: ProjectState,
|
|
172
|
-
options: FlowOptions
|
|
173
|
-
): Promise<string | undefined> {
|
|
174
|
-
// Force target selection when cleaning
|
|
175
|
-
if (options.clean) {
|
|
176
|
-
try {
|
|
177
|
-
const targetId = await targetManager.promptForTargetSelection();
|
|
178
|
-
console.log(chalk.green(`✅ Selected target: ${targetId}`));
|
|
179
|
-
return targetId;
|
|
180
|
-
} catch (error) {
|
|
181
|
-
// User cancelled with Ctrl+C - exit gracefully
|
|
182
|
-
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
183
|
-
console.log('\n');
|
|
184
|
-
process.exit(0);
|
|
185
|
-
}
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Use existing target or option
|
|
191
|
-
return options.target || state.target;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Step 3: Initialize project
|
|
196
|
-
*/
|
|
197
|
-
export async function initializeProject(
|
|
198
|
-
targetId: string | undefined,
|
|
199
|
-
options: FlowOptions
|
|
200
|
-
): Promise<void> {
|
|
201
|
-
if (options.runOnly && !options.clean) return;
|
|
202
|
-
|
|
203
|
-
console.log(chalk.cyan.bold('━ Initializing Project\n'));
|
|
204
|
-
|
|
205
|
-
const { runInit } = await import('./init-command.js');
|
|
206
|
-
|
|
207
|
-
const initOptions = {
|
|
208
|
-
target: targetId,
|
|
209
|
-
verbose: options.verbose,
|
|
210
|
-
dryRun: options.dryRun,
|
|
211
|
-
clear: options.clean || false,
|
|
212
|
-
mcp: options.mcp !== false,
|
|
213
|
-
agents: options.agents !== false,
|
|
214
|
-
rules: options.rules !== false,
|
|
215
|
-
outputStyles: options.outputStyles !== false,
|
|
216
|
-
slashCommands: options.slashCommands !== false,
|
|
217
|
-
hooks: options.hooks !== false,
|
|
218
|
-
helpOption: () => {},
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
await runInit(initOptions);
|
|
223
|
-
|
|
224
|
-
if (!options.dryRun) {
|
|
225
|
-
console.log(chalk.green.bold('\n✓ Initialization complete\n'));
|
|
226
|
-
} else {
|
|
227
|
-
console.log(chalk.dim('\n✓ Dry run complete - skipping execution\n'));
|
|
228
|
-
}
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error(chalk.red.bold('\n✗ Initialization failed:'), error);
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Step 4: Launch target
|
|
237
|
-
*/
|
|
238
|
-
export async function launchTarget(
|
|
239
|
-
targetId: string | undefined,
|
|
240
|
-
prompt: string | undefined,
|
|
241
|
-
options: FlowOptions,
|
|
242
|
-
state: ProjectState
|
|
243
|
-
): Promise<void> {
|
|
244
|
-
if (options.initOnly) return;
|
|
245
|
-
|
|
246
|
-
// Resolve target
|
|
247
|
-
const resolvedTarget = await targetManager.resolveTarget({
|
|
248
|
-
target: targetId || state.target,
|
|
249
|
-
allowSelection: false,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
console.log(chalk.cyan.bold(`━ Launching ${resolvedTarget}\n`));
|
|
253
|
-
|
|
254
|
-
// Check if target supports command execution
|
|
255
|
-
const { getTargetsWithCommandSupport } = await import('../config/targets.js');
|
|
256
|
-
const supportedTargets = getTargetsWithCommandSupport().map(t => t.id);
|
|
257
|
-
|
|
258
|
-
if (!supportedTargets.includes(resolvedTarget)) {
|
|
259
|
-
console.log(chalk.red.bold('✗ Unsupported target platform\n'));
|
|
260
|
-
console.log(
|
|
261
|
-
chalk.yellow(`Target '${resolvedTarget}' does not support agent execution.`)
|
|
262
|
-
);
|
|
263
|
-
console.log(chalk.cyan(`Supported platforms: ${supportedTargets.join(', ')}\n`));
|
|
264
|
-
throw new Error(`Unsupported target: ${resolvedTarget}`);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Handle Claude Code specific setup
|
|
268
|
-
if (resolvedTarget === 'claude-code') {
|
|
269
|
-
await setupClaudeCode(options);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Execute command
|
|
273
|
-
await executeCommand(resolvedTarget, prompt, options);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Setup Claude Code (provider + agent selection)
|
|
278
|
-
*/
|
|
279
|
-
async function setupClaudeCode(options: FlowOptions): Promise<void> {
|
|
280
|
-
const { SmartConfigService } = await import('../services/smart-config-service.js');
|
|
281
|
-
const { ConfigService } = await import('../services/config-service.js');
|
|
282
|
-
|
|
283
|
-
// Check if API keys are configured
|
|
284
|
-
if (!(await ConfigService.hasInitialSetup())) {
|
|
285
|
-
console.log(chalk.cyan('\n🔑 First-time setup for Claude Code:\n'));
|
|
286
|
-
await SmartConfigService.initialSetup();
|
|
287
|
-
console.log(chalk.green('\n✅ Claude Code setup complete!\n'));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Select provider and agent
|
|
291
|
-
const runtimeChoices = await SmartConfigService.selectRuntimeChoices({
|
|
292
|
-
selectProvider: options.selectProvider,
|
|
293
|
-
selectAgent: options.selectAgent,
|
|
294
|
-
useDefaults: options.useDefaults,
|
|
295
|
-
provider: options.provider,
|
|
296
|
-
agent: options.agent,
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Setup environment
|
|
300
|
-
await SmartConfigService.setupEnvironment(runtimeChoices.provider!);
|
|
301
|
-
|
|
302
|
-
// Store selected agent
|
|
303
|
-
options.agent = runtimeChoices.agent;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Execute the target command
|
|
308
|
-
*/
|
|
309
|
-
async function executeCommand(
|
|
310
|
-
targetId: string,
|
|
311
|
-
prompt: string | undefined,
|
|
312
|
-
options: FlowOptions
|
|
313
|
-
): Promise<void> {
|
|
314
|
-
const agent = options.agent || 'coder';
|
|
315
|
-
const verbose = options.verbose || false;
|
|
316
|
-
|
|
317
|
-
if (verbose || options.runOnly) {
|
|
318
|
-
console.log(`🤖 Agent: ${agent}`);
|
|
319
|
-
console.log(`🎯 Target: ${targetId}`);
|
|
320
|
-
if (prompt) {
|
|
321
|
-
console.log(`💬 Prompt: ${prompt}\n`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Run the command
|
|
326
|
-
const { runCommand } = await import('./run-command.js');
|
|
327
|
-
await runCommand({ target: targetId, agent, prompt, verbose });
|
|
328
|
-
}
|