@sylphx/flow 2.16.2 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/commands/flow/execute-v2.ts +4 -1
- package/src/core/attach-manager.ts +54 -5
- package/src/core/backup-manager.ts +5 -0
- package/src/core/flow-executor.ts +21 -6
- package/src/core/template-loader.ts +35 -0
- package/src/targets/claude-code.ts +1 -0
- package/src/types/target.types.ts +5 -0
- package/src/utils/display/banner.ts +34 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sylphx/flow
|
|
2
2
|
|
|
3
|
+
## 2.18.0 (2025-12-18)
|
|
4
|
+
|
|
5
|
+
### ✨ Features
|
|
6
|
+
|
|
7
|
+
- **cli:** show skills count in attach summary ([a02c422](https://github.com/SylphxAI/flow/commit/a02c42239e79af1cfc891bb4554e9fac7c2a7f9b))
|
|
8
|
+
|
|
9
|
+
## 2.17.0 (2025-12-18)
|
|
10
|
+
|
|
11
|
+
### ✨ Features
|
|
12
|
+
|
|
13
|
+
- **skills:** implement skills loading and attach pipeline ([8d720b8](https://github.com/SylphxAI/flow/commit/8d720b8118ffe19e14d50aaacd9282bd5d42e702))
|
|
14
|
+
|
|
3
15
|
## 2.16.2 (2025-12-17)
|
|
4
16
|
|
|
5
17
|
### 🐛 Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/flow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "One CLI to rule them all. Unified orchestration layer for Claude Code, OpenCode, Cursor and all AI development tools. Auto-detection, auto-installation, auto-upgrade.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@ import { GlobalConfigService } from '../../services/global-config.js';
|
|
|
15
15
|
import { TargetInstaller } from '../../services/target-installer.js';
|
|
16
16
|
import type { RunCommandOptions } from '../../types.js';
|
|
17
17
|
import { extractAgentInstructions, loadAgentContent } from '../../utils/agent-enhancer.js';
|
|
18
|
-
import { showHeader } from '../../utils/display/banner.js';
|
|
18
|
+
import { showAttachSummary, showHeader } from '../../utils/display/banner.js';
|
|
19
19
|
import { CLIError } from '../../utils/error-handler.js';
|
|
20
20
|
import { UserCancelledError } from '../../utils/errors.js';
|
|
21
21
|
import { ensureTargetInstalled, promptForTargetSelection } from '../../utils/target-selection.js';
|
|
@@ -263,6 +263,9 @@ export async function executeFlowV2(
|
|
|
263
263
|
merge: options.merge || false,
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
// Show attach summary
|
|
267
|
+
showAttachSummary(attachResult);
|
|
268
|
+
|
|
266
269
|
const targetId = selectedTargetId;
|
|
267
270
|
|
|
268
271
|
// Provider selection (Claude Code only, silent unless prompting)
|
|
@@ -21,6 +21,8 @@ export interface AttachResult {
|
|
|
21
21
|
agentsOverridden: string[];
|
|
22
22
|
commandsAdded: string[];
|
|
23
23
|
commandsOverridden: string[];
|
|
24
|
+
skillsAdded: string[];
|
|
25
|
+
skillsOverridden: string[];
|
|
24
26
|
rulesAppended: boolean;
|
|
25
27
|
mcpServersAdded: string[];
|
|
26
28
|
mcpServersOverridden: string[];
|
|
@@ -31,7 +33,7 @@ export interface AttachResult {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export interface ConflictInfo {
|
|
34
|
-
type: 'agent' | 'command' | 'mcp' | 'hook';
|
|
36
|
+
type: 'agent' | 'command' | 'skill' | 'mcp' | 'hook';
|
|
35
37
|
name: string;
|
|
36
38
|
action: 'overridden' | 'merged';
|
|
37
39
|
message: string;
|
|
@@ -40,6 +42,7 @@ export interface ConflictInfo {
|
|
|
40
42
|
export interface FlowTemplates {
|
|
41
43
|
agents: Array<{ name: string; content: string }>;
|
|
42
44
|
commands: Array<{ name: string; content: string }>;
|
|
45
|
+
skills: Array<{ name: string; content: string }>;
|
|
43
46
|
rules?: string;
|
|
44
47
|
mcpServers: Array<{ name: string; config: Record<string, unknown> }>;
|
|
45
48
|
hooks: Array<{ name: string; content: string }>;
|
|
@@ -143,6 +146,8 @@ export class AttachManager {
|
|
|
143
146
|
agentsOverridden: [],
|
|
144
147
|
commandsAdded: [],
|
|
145
148
|
commandsOverridden: [],
|
|
149
|
+
skillsAdded: [],
|
|
150
|
+
skillsOverridden: [],
|
|
146
151
|
rulesAppended: false,
|
|
147
152
|
mcpServersAdded: [],
|
|
148
153
|
mcpServersOverridden: [],
|
|
@@ -160,12 +165,17 @@ export class AttachManager {
|
|
|
160
165
|
// 2. Attach commands
|
|
161
166
|
await this.attachCommands(projectPath, target, templates.commands, result, manifest);
|
|
162
167
|
|
|
163
|
-
// 3. Attach
|
|
168
|
+
// 3. Attach skills (if target supports them)
|
|
169
|
+
if (target.config.skillsDir && templates.skills.length > 0) {
|
|
170
|
+
await this.attachSkills(projectPath, target, templates.skills, result, manifest);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 4. Attach rules (if applicable)
|
|
164
174
|
if (templates.rules) {
|
|
165
175
|
await this.attachRules(projectPath, target, templates.rules, result, manifest);
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
//
|
|
178
|
+
// 5. Attach MCP servers (merge global + template servers)
|
|
169
179
|
const globalMCPServers = await this.loadGlobalMCPServers(target);
|
|
170
180
|
const allMCPServers = [...globalMCPServers, ...templates.mcpServers];
|
|
171
181
|
|
|
@@ -173,12 +183,12 @@ export class AttachManager {
|
|
|
173
183
|
await this.attachMCPServers(projectPath, target, allMCPServers, result, manifest);
|
|
174
184
|
}
|
|
175
185
|
|
|
176
|
-
//
|
|
186
|
+
// 6. Attach hooks
|
|
177
187
|
if (templates.hooks.length > 0) {
|
|
178
188
|
await this.attachHooks(projectPath, target, templates.hooks, result, manifest);
|
|
179
189
|
}
|
|
180
190
|
|
|
181
|
-
//
|
|
191
|
+
// 7. Attach single files
|
|
182
192
|
if (templates.singleFiles.length > 0) {
|
|
183
193
|
await this.attachSingleFiles(projectPath, templates.singleFiles, result, manifest);
|
|
184
194
|
}
|
|
@@ -238,6 +248,45 @@ export class AttachManager {
|
|
|
238
248
|
manifest.backup.commands.flow.push(...itemManifest.flow);
|
|
239
249
|
}
|
|
240
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Attach skills (override strategy)
|
|
253
|
+
* Skills are stored as <domain>/SKILL.md subdirectories
|
|
254
|
+
*/
|
|
255
|
+
private async attachSkills(
|
|
256
|
+
projectPath: string,
|
|
257
|
+
target: Target,
|
|
258
|
+
skills: Array<{ name: string; content: string }>,
|
|
259
|
+
result: AttachResult,
|
|
260
|
+
manifest: BackupManifest
|
|
261
|
+
): Promise<void> {
|
|
262
|
+
const skillsDir = path.join(projectPath, target.config.skillsDir!);
|
|
263
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
264
|
+
|
|
265
|
+
for (const skill of skills) {
|
|
266
|
+
// skill.name is like "auth/SKILL.md" - create subdirectory
|
|
267
|
+
const skillPath = path.join(skillsDir, skill.name);
|
|
268
|
+
const skillSubDir = path.dirname(skillPath);
|
|
269
|
+
await fs.mkdir(skillSubDir, { recursive: true });
|
|
270
|
+
|
|
271
|
+
const existed = existsSync(skillPath);
|
|
272
|
+
if (existed) {
|
|
273
|
+
result.skillsOverridden.push(skill.name);
|
|
274
|
+
result.conflicts.push({
|
|
275
|
+
type: 'skill',
|
|
276
|
+
name: skill.name,
|
|
277
|
+
action: 'overridden',
|
|
278
|
+
message: `Skill '${skill.name}' overridden (will be restored on exit)`,
|
|
279
|
+
});
|
|
280
|
+
manifest.backup.skills.user.push(skillPath);
|
|
281
|
+
} else {
|
|
282
|
+
result.skillsAdded.push(skill.name);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
await fs.writeFile(skillPath, skill.content);
|
|
286
|
+
manifest.backup.skills.flow.push(skillPath);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
241
290
|
/**
|
|
242
291
|
* Attach rules (append strategy for AGENTS.md)
|
|
243
292
|
* Uses shared attachRulesFile function
|
|
@@ -38,6 +38,10 @@ export interface BackupManifest {
|
|
|
38
38
|
user: string[];
|
|
39
39
|
flow: string[];
|
|
40
40
|
};
|
|
41
|
+
skills: {
|
|
42
|
+
user: string[];
|
|
43
|
+
flow: string[];
|
|
44
|
+
};
|
|
41
45
|
rules?: {
|
|
42
46
|
path: string;
|
|
43
47
|
originalSize: number;
|
|
@@ -114,6 +118,7 @@ export class BackupManager {
|
|
|
114
118
|
backup: {
|
|
115
119
|
agents: { user: [], flow: [] },
|
|
116
120
|
commands: { user: [], flow: [] },
|
|
121
|
+
skills: { user: [], flow: [] },
|
|
117
122
|
singleFiles: {},
|
|
118
123
|
},
|
|
119
124
|
secrets: {
|
|
@@ -55,7 +55,13 @@ export class FlowExecutor {
|
|
|
55
55
|
async execute(
|
|
56
56
|
projectPath: string,
|
|
57
57
|
options: FlowExecutorOptions = {}
|
|
58
|
-
): Promise<{
|
|
58
|
+
): Promise<{
|
|
59
|
+
joined: boolean;
|
|
60
|
+
agents?: number;
|
|
61
|
+
commands?: number;
|
|
62
|
+
skills?: number;
|
|
63
|
+
mcp?: number;
|
|
64
|
+
}> {
|
|
59
65
|
// Initialize Flow directories
|
|
60
66
|
await this.projectManager.initialize();
|
|
61
67
|
|
|
@@ -137,6 +143,7 @@ export class FlowExecutor {
|
|
|
137
143
|
joined: false,
|
|
138
144
|
agents: attachResult.agentsAdded.length,
|
|
139
145
|
commands: attachResult.commandsAdded.length,
|
|
146
|
+
skills: attachResult.skillsAdded.length,
|
|
140
147
|
mcp: attachResult.mcpServersAdded.length,
|
|
141
148
|
};
|
|
142
149
|
}
|
|
@@ -184,7 +191,15 @@ export class FlowExecutor {
|
|
|
184
191
|
}
|
|
185
192
|
}
|
|
186
193
|
|
|
187
|
-
// 3. Clear
|
|
194
|
+
// 3. Clear skills directory (if target supports skills)
|
|
195
|
+
if (target.config.skillsDir) {
|
|
196
|
+
const skillsDir = path.join(projectPath, target.config.skillsDir);
|
|
197
|
+
if (existsSync(skillsDir)) {
|
|
198
|
+
await fs.rm(skillsDir, { recursive: true, force: true });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 4. Clear hooks directory (in configDir)
|
|
188
203
|
const hooksDir = path.join(projectPath, target.config.configDir, 'hooks');
|
|
189
204
|
if (existsSync(hooksDir)) {
|
|
190
205
|
const files = await fs.readdir(hooksDir);
|
|
@@ -193,7 +208,7 @@ export class FlowExecutor {
|
|
|
193
208
|
}
|
|
194
209
|
}
|
|
195
210
|
|
|
196
|
-
//
|
|
211
|
+
// 5. Clear MCP configuration using target config
|
|
197
212
|
const configPath = path.join(projectPath, target.config.configFile);
|
|
198
213
|
const mcpPath = target.config.mcpConfigPath;
|
|
199
214
|
|
|
@@ -206,7 +221,7 @@ export class FlowExecutor {
|
|
|
206
221
|
}
|
|
207
222
|
}
|
|
208
223
|
|
|
209
|
-
//
|
|
224
|
+
// 6. Clear rules file if target has one defined (for targets like OpenCode)
|
|
210
225
|
// Claude Code puts AGENTS.md in agents directory, handled above
|
|
211
226
|
if (target.config.rulesFile) {
|
|
212
227
|
const rulesPath = path.join(projectPath, target.config.rulesFile);
|
|
@@ -215,7 +230,7 @@ export class FlowExecutor {
|
|
|
215
230
|
}
|
|
216
231
|
}
|
|
217
232
|
|
|
218
|
-
//
|
|
233
|
+
// 7. Clear single files (output styles) - currently none
|
|
219
234
|
// These would be in the configDir if we had any
|
|
220
235
|
const singleFiles: string[] = [];
|
|
221
236
|
for (const fileName of singleFiles) {
|
|
@@ -225,7 +240,7 @@ export class FlowExecutor {
|
|
|
225
240
|
}
|
|
226
241
|
}
|
|
227
242
|
|
|
228
|
-
//
|
|
243
|
+
// 8. Clean up any Flow-created files in project root (legacy bug cleanup)
|
|
229
244
|
// This handles files that were incorrectly created in project root
|
|
230
245
|
const legacySingleFiles = ['silent.md']; // Keep for cleanup of legacy installations
|
|
231
246
|
for (const fileName of legacySingleFiles) {
|
|
@@ -29,6 +29,7 @@ export class TemplateLoader {
|
|
|
29
29
|
const templates: FlowTemplates = {
|
|
30
30
|
agents: [],
|
|
31
31
|
commands: [],
|
|
32
|
+
skills: [],
|
|
32
33
|
rules: undefined,
|
|
33
34
|
mcpServers: [],
|
|
34
35
|
hooks: [],
|
|
@@ -47,6 +48,12 @@ export class TemplateLoader {
|
|
|
47
48
|
templates.commands = await this.loadCommands(commandsDir);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// Load skills (skills/<domain>/SKILL.md structure)
|
|
52
|
+
const skillsDir = path.join(this.assetsDir, 'skills');
|
|
53
|
+
if (existsSync(skillsDir)) {
|
|
54
|
+
templates.skills = await this.loadSkills(skillsDir);
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
// Load rules (check multiple possible locations)
|
|
51
58
|
const rulesLocations = [
|
|
52
59
|
path.join(this.assetsDir, 'rules', 'AGENTS.md'),
|
|
@@ -115,6 +122,34 @@ export class TemplateLoader {
|
|
|
115
122
|
return commands;
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Load skills from directory
|
|
127
|
+
* Skills are stored as <domain>/SKILL.md subdirectories
|
|
128
|
+
*/
|
|
129
|
+
private async loadSkills(skillsDir: string): Promise<Array<{ name: string; content: string }>> {
|
|
130
|
+
const skills = [];
|
|
131
|
+
const domains = await fs.readdir(skillsDir);
|
|
132
|
+
|
|
133
|
+
for (const domain of domains) {
|
|
134
|
+
const domainPath = path.join(skillsDir, domain);
|
|
135
|
+
const stat = await fs.stat(domainPath);
|
|
136
|
+
|
|
137
|
+
if (!stat.isDirectory()) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Look for SKILL.md in each domain directory
|
|
142
|
+
const skillFile = path.join(domainPath, 'SKILL.md');
|
|
143
|
+
if (existsSync(skillFile)) {
|
|
144
|
+
const content = await fs.readFile(skillFile, 'utf-8');
|
|
145
|
+
// Name includes subdirectory: "auth/SKILL.md"
|
|
146
|
+
skills.push({ name: `${domain}/SKILL.md`, content });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return skills;
|
|
151
|
+
}
|
|
152
|
+
|
|
118
153
|
/**
|
|
119
154
|
* Load MCP servers configuration
|
|
120
155
|
*/
|
|
@@ -41,6 +41,7 @@ export const claudeCodeTarget: Target = {
|
|
|
41
41
|
rulesFile: undefined, // Rules are included in agent files
|
|
42
42
|
outputStylesDir: undefined, // Output styles are included in agent files
|
|
43
43
|
slashCommandsDir: '.claude/commands',
|
|
44
|
+
skillsDir: '.claude/skills',
|
|
44
45
|
installation: {
|
|
45
46
|
createAgentDir: true,
|
|
46
47
|
createConfigFile: true,
|
|
@@ -35,6 +35,8 @@ export interface TargetConfig {
|
|
|
35
35
|
outputStylesDir?: string;
|
|
36
36
|
/** Slash commands directory (optional, relative to project root) */
|
|
37
37
|
slashCommandsDir?: string;
|
|
38
|
+
/** Skills directory (optional, relative to project root) */
|
|
39
|
+
skillsDir?: string;
|
|
38
40
|
/** Installation-specific configuration */
|
|
39
41
|
installation: {
|
|
40
42
|
/** Whether to create the agent directory */
|
|
@@ -137,4 +139,7 @@ export interface Target {
|
|
|
137
139
|
|
|
138
140
|
/** Setup slash commands for this target (optional - implement if target supports slash commands) */
|
|
139
141
|
setupSlashCommands?(cwd: string, options: CommonOptions): Promise<SetupResult>;
|
|
142
|
+
|
|
143
|
+
/** Setup skills for this target (optional - implement if target supports skills) */
|
|
144
|
+
setupSkills?(cwd: string, options: CommonOptions): Promise<SetupResult>;
|
|
140
145
|
}
|
|
@@ -12,6 +12,40 @@ export function showHeader(version: string, target: string): void {
|
|
|
12
12
|
console.log(`\n${chalk.cyan('flow')} ${chalk.dim(version)} ${chalk.dim('→')} ${target}\n`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Show attach summary: ✓ Attached {n} agents, {n} commands, {n} skills, {n} MCP
|
|
17
|
+
*/
|
|
18
|
+
export function showAttachSummary(result: {
|
|
19
|
+
joined: boolean;
|
|
20
|
+
agents?: number;
|
|
21
|
+
commands?: number;
|
|
22
|
+
skills?: number;
|
|
23
|
+
mcp?: number;
|
|
24
|
+
}): void {
|
|
25
|
+
if (result.joined) {
|
|
26
|
+
// Joining existing session - no summary needed
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const parts: string[] = [];
|
|
31
|
+
if (result.agents && result.agents > 0) {
|
|
32
|
+
parts.push(`${result.agents} agents`);
|
|
33
|
+
}
|
|
34
|
+
if (result.commands && result.commands > 0) {
|
|
35
|
+
parts.push(`${result.commands} commands`);
|
|
36
|
+
}
|
|
37
|
+
if (result.skills && result.skills > 0) {
|
|
38
|
+
parts.push(`${result.skills} skills`);
|
|
39
|
+
}
|
|
40
|
+
if (result.mcp && result.mcp > 0) {
|
|
41
|
+
parts.push(`${result.mcp} MCP`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (parts.length > 0) {
|
|
45
|
+
console.log(`${chalk.green('✓')} Attached ${parts.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
15
49
|
/**
|
|
16
50
|
* @deprecated Use showHeader instead
|
|
17
51
|
*/
|