@sylphx/flow 1.2.0 → 1.3.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.
- package/CHANGELOG.md +30 -0
- package/package.json +1 -1
- package/src/commands/flow-command.ts +34 -6
- package/src/utils/sync-utils.ts +133 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @sylphx/flow
|
|
2
2
|
|
|
3
|
+
## 1.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Redesign sync flow for better clarity:
|
|
8
|
+
- Remove duplicate config files in preserved list
|
|
9
|
+
- Show MCP check in preview upfront (not after confirmation)
|
|
10
|
+
- Combined preview: templates + MCP servers + preserved files
|
|
11
|
+
- Clear sections with emojis for easy scanning
|
|
12
|
+
|
|
13
|
+
## 1.3.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- Enhanced --sync with MCP registry checking:
|
|
18
|
+
- Detect servers not in Flow registry (removed or custom)
|
|
19
|
+
- Interactive selection for removal
|
|
20
|
+
- Clean removal from .mcp.json
|
|
21
|
+
- Flow: sync templates → check MCP → remove selected
|
|
22
|
+
|
|
23
|
+
## 1.2.1
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Apply MEP principles to workspace documentation rule:
|
|
28
|
+
- Condensed from verbose instructions to condition→action format
|
|
29
|
+
- Removed step-by-step teaching and command examples
|
|
30
|
+
- Embedded verification in directives
|
|
31
|
+
- 31% reduction while maintaining clarity
|
|
32
|
+
|
|
3
33
|
## 1.2.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -380,7 +380,7 @@ async function executeSetupPhase(prompt: string | undefined, options: FlowOption
|
|
|
380
380
|
|
|
381
381
|
// Handle sync mode - delete template files first
|
|
382
382
|
if (options.sync && !options.dryRun) {
|
|
383
|
-
const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete } = await import('../utils/sync-utils.js');
|
|
383
|
+
const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete, checkMCPServers, selectServersToRemove, removeMCPServers } = await import('../utils/sync-utils.js');
|
|
384
384
|
|
|
385
385
|
// Need target to build manifest
|
|
386
386
|
const targetId = await selectAndValidateTarget(initOptions);
|
|
@@ -394,8 +394,11 @@ async function executeSetupPhase(prompt: string | undefined, options: FlowOption
|
|
|
394
394
|
const target = targetOption.value;
|
|
395
395
|
const manifest = await buildSyncManifest(process.cwd(), target);
|
|
396
396
|
|
|
397
|
+
// Check MCP servers before showing preview
|
|
398
|
+
const nonRegistryServers = await checkMCPServers(process.cwd());
|
|
399
|
+
|
|
397
400
|
console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
|
|
398
|
-
showSyncPreview(manifest, process.cwd());
|
|
401
|
+
showSyncPreview(manifest, process.cwd(), nonRegistryServers);
|
|
399
402
|
|
|
400
403
|
const confirmed = await confirmSync();
|
|
401
404
|
if (!confirmed) {
|
|
@@ -403,8 +406,19 @@ async function executeSetupPhase(prompt: string | undefined, options: FlowOption
|
|
|
403
406
|
process.exit(0);
|
|
404
407
|
}
|
|
405
408
|
|
|
409
|
+
// Delete templates
|
|
406
410
|
const deletedCount = await executeSyncDelete(manifest);
|
|
407
|
-
console.log(chalk.green(`\n✓ Deleted ${deletedCount} files\n`));
|
|
411
|
+
console.log(chalk.green(`\n✓ Deleted ${deletedCount} template files\n`));
|
|
412
|
+
|
|
413
|
+
// Handle MCP servers if any found
|
|
414
|
+
if (nonRegistryServers.length > 0) {
|
|
415
|
+
const serversToRemove = await selectServersToRemove(nonRegistryServers);
|
|
416
|
+
|
|
417
|
+
if (serversToRemove.length > 0) {
|
|
418
|
+
const removedCount = await removeMCPServers(process.cwd(), serversToRemove);
|
|
419
|
+
console.log(chalk.green(`✓ Removed ${removedCount} MCP server(s)\n`));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
408
422
|
} else if (!options.sync) {
|
|
409
423
|
const targetId = await selectAndValidateTarget(initOptions);
|
|
410
424
|
selectedTarget = targetId;
|
|
@@ -695,7 +709,7 @@ async function executeFlowOnce(prompt: string | undefined, options: FlowOptions)
|
|
|
695
709
|
|
|
696
710
|
// Handle sync mode - delete template files first
|
|
697
711
|
if (options.sync && !options.dryRun) {
|
|
698
|
-
const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete } = await import('../utils/sync-utils.js');
|
|
712
|
+
const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete, checkMCPServers, selectServersToRemove, removeMCPServers } = await import('../utils/sync-utils.js');
|
|
699
713
|
|
|
700
714
|
// Need target to build manifest
|
|
701
715
|
const targetId = await selectAndValidateTarget(initOptions);
|
|
@@ -709,8 +723,11 @@ async function executeFlowOnce(prompt: string | undefined, options: FlowOptions)
|
|
|
709
723
|
const target = targetOption.value;
|
|
710
724
|
const manifest = await buildSyncManifest(process.cwd(), target);
|
|
711
725
|
|
|
726
|
+
// Check MCP servers before showing preview
|
|
727
|
+
const nonRegistryServers = await checkMCPServers(process.cwd());
|
|
728
|
+
|
|
712
729
|
console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
|
|
713
|
-
showSyncPreview(manifest, process.cwd());
|
|
730
|
+
showSyncPreview(manifest, process.cwd(), nonRegistryServers);
|
|
714
731
|
|
|
715
732
|
const confirmed = await confirmSync();
|
|
716
733
|
if (!confirmed) {
|
|
@@ -718,8 +735,19 @@ async function executeFlowOnce(prompt: string | undefined, options: FlowOptions)
|
|
|
718
735
|
process.exit(0);
|
|
719
736
|
}
|
|
720
737
|
|
|
738
|
+
// Delete templates
|
|
721
739
|
const deletedCount = await executeSyncDelete(manifest);
|
|
722
|
-
console.log(chalk.green(`\n✓ Deleted ${deletedCount} files\n`));
|
|
740
|
+
console.log(chalk.green(`\n✓ Deleted ${deletedCount} template files\n`));
|
|
741
|
+
|
|
742
|
+
// Handle MCP servers if any found
|
|
743
|
+
if (nonRegistryServers.length > 0) {
|
|
744
|
+
const serversToRemove = await selectServersToRemove(nonRegistryServers);
|
|
745
|
+
|
|
746
|
+
if (serversToRemove.length > 0) {
|
|
747
|
+
const removedCount = await removeMCPServers(process.cwd(), serversToRemove);
|
|
748
|
+
console.log(chalk.green(`✓ Removed ${removedCount} MCP server(s)\n`));
|
|
749
|
+
}
|
|
750
|
+
}
|
|
723
751
|
} else {
|
|
724
752
|
// Select and validate target (will use existing in repair mode, or prompt if needed)
|
|
725
753
|
const targetId = await selectAndValidateTarget(initOptions);
|
package/src/utils/sync-utils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import type { Target } from '../types.js';
|
|
5
|
+
import { MCP_SERVER_REGISTRY } from '../config/servers.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Files to delete during sync for each target
|
|
@@ -59,8 +60,6 @@ export async function buildSyncManifest(cwd: string, target: Target): Promise<Sy
|
|
|
59
60
|
'.sylphx-flow/',
|
|
60
61
|
'.secrets/',
|
|
61
62
|
target.config.configFile || '',
|
|
62
|
-
'.mcp.json',
|
|
63
|
-
'opencode.jsonc',
|
|
64
63
|
]
|
|
65
64
|
.filter(Boolean)
|
|
66
65
|
.map((p) => path.join(cwd, p));
|
|
@@ -71,46 +70,64 @@ export async function buildSyncManifest(cwd: string, target: Target): Promise<Sy
|
|
|
71
70
|
/**
|
|
72
71
|
* Show sync preview - what will be deleted
|
|
73
72
|
*/
|
|
74
|
-
export function showSyncPreview(
|
|
73
|
+
export function showSyncPreview(
|
|
74
|
+
manifest: SyncManifest,
|
|
75
|
+
cwd: string,
|
|
76
|
+
nonRegistryServers: string[]
|
|
77
|
+
): void {
|
|
75
78
|
console.log(chalk.cyan.bold('📋 Sync Preview\n'));
|
|
76
|
-
console.log(chalk.dim('The following files will be deleted and re-installed:\n'));
|
|
77
79
|
|
|
80
|
+
// Template files section
|
|
78
81
|
const allFiles = [...manifest.agents, ...manifest.slashCommands, ...manifest.rules];
|
|
79
82
|
|
|
80
|
-
if (allFiles.length
|
|
81
|
-
console.log(chalk.yellow('
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
83
|
+
if (allFiles.length > 0) {
|
|
84
|
+
console.log(chalk.yellow('🔄 Templates (delete + reinstall):\n'));
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
86
|
+
if (manifest.agents.length > 0) {
|
|
87
|
+
console.log(chalk.dim(' Agents:'));
|
|
88
|
+
manifest.agents.forEach((file) => {
|
|
89
|
+
const relative = path.relative(cwd, file);
|
|
90
|
+
console.log(chalk.dim(` - ${relative}`));
|
|
91
|
+
});
|
|
92
|
+
console.log('');
|
|
93
|
+
}
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
if (manifest.slashCommands.length > 0) {
|
|
96
|
+
console.log(chalk.dim(' Slash Commands:'));
|
|
97
|
+
manifest.slashCommands.forEach((file) => {
|
|
98
|
+
const relative = path.relative(cwd, file);
|
|
99
|
+
console.log(chalk.dim(` - ${relative}`));
|
|
100
|
+
});
|
|
101
|
+
console.log('');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (manifest.rules.length > 0) {
|
|
105
|
+
console.log(chalk.dim(' Rules:'));
|
|
106
|
+
manifest.rules.forEach((file) => {
|
|
107
|
+
const relative = path.relative(cwd, file);
|
|
108
|
+
console.log(chalk.dim(` - ${relative}`));
|
|
109
|
+
});
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.yellow('🔄 Templates: None found\n'));
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.log(chalk.dim(` - ${
|
|
116
|
+
// MCP servers section
|
|
117
|
+
if (nonRegistryServers.length > 0) {
|
|
118
|
+
console.log(chalk.yellow('🔍 MCP Servers (not in registry):\n'));
|
|
119
|
+
nonRegistryServers.forEach((server) => {
|
|
120
|
+
console.log(chalk.dim(` - ${server}`));
|
|
109
121
|
});
|
|
110
|
-
console.log('');
|
|
122
|
+
console.log(chalk.dim('\n Possible reasons:'));
|
|
123
|
+
console.log(chalk.dim(' 1. Removed from Flow registry'));
|
|
124
|
+
console.log(chalk.dim(' 2. Custom installation\n'));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.green('✓ MCP Servers: All in registry\n'));
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
|
|
129
|
+
// Preserved files section
|
|
130
|
+
console.log(chalk.green('✓ Preserved:\n'));
|
|
114
131
|
manifest.preserve.forEach((file) => {
|
|
115
132
|
const relative = path.relative(cwd, file);
|
|
116
133
|
if (fs.existsSync(file)) {
|
|
@@ -157,3 +174,88 @@ export async function confirmSync(): Promise<boolean> {
|
|
|
157
174
|
]);
|
|
158
175
|
return confirm;
|
|
159
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check MCP servers - find servers not in Flow registry
|
|
180
|
+
*/
|
|
181
|
+
export async function checkMCPServers(cwd: string): Promise<string[]> {
|
|
182
|
+
const mcpPath = path.join(cwd, '.mcp.json');
|
|
183
|
+
|
|
184
|
+
if (!fs.existsSync(mcpPath)) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const content = await fs.promises.readFile(mcpPath, 'utf-8');
|
|
190
|
+
const mcpConfig = JSON.parse(content);
|
|
191
|
+
|
|
192
|
+
if (!mcpConfig.mcpServers) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const installedServers = Object.keys(mcpConfig.mcpServers);
|
|
197
|
+
const registryServers = Object.keys(MCP_SERVER_REGISTRY);
|
|
198
|
+
|
|
199
|
+
// Find servers not in registry
|
|
200
|
+
return installedServers.filter(id => !registryServers.includes(id));
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.warn(chalk.yellow('⚠ Failed to read .mcp.json'));
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Select servers to remove
|
|
209
|
+
*/
|
|
210
|
+
export async function selectServersToRemove(servers: string[]): Promise<string[]> {
|
|
211
|
+
const { default: inquirer } = await import('inquirer');
|
|
212
|
+
const { selected } = await inquirer.prompt([
|
|
213
|
+
{
|
|
214
|
+
type: 'checkbox',
|
|
215
|
+
name: 'selected',
|
|
216
|
+
message: '選擇要刪除既 servers:',
|
|
217
|
+
choices: servers.map(s => ({ name: s, value: s })),
|
|
218
|
+
},
|
|
219
|
+
]);
|
|
220
|
+
return selected;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Remove MCP servers from .mcp.json
|
|
225
|
+
*/
|
|
226
|
+
export async function removeMCPServers(cwd: string, serversToRemove: string[]): Promise<number> {
|
|
227
|
+
if (serversToRemove.length === 0) {
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const mcpPath = path.join(cwd, '.mcp.json');
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const content = await fs.promises.readFile(mcpPath, 'utf-8');
|
|
235
|
+
const mcpConfig = JSON.parse(content);
|
|
236
|
+
|
|
237
|
+
if (!mcpConfig.mcpServers) {
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let removedCount = 0;
|
|
242
|
+
for (const server of serversToRemove) {
|
|
243
|
+
if (mcpConfig.mcpServers[server]) {
|
|
244
|
+
delete mcpConfig.mcpServers[server];
|
|
245
|
+
removedCount++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Write back
|
|
250
|
+
await fs.promises.writeFile(
|
|
251
|
+
mcpPath,
|
|
252
|
+
JSON.stringify(mcpConfig, null, 2) + '\n',
|
|
253
|
+
'utf-8'
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return removedCount;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.warn(chalk.yellow('⚠ Failed to update .mcp.json'));
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
}
|