flowmind 1.0.0 → 1.0.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/README.md +81 -2
- package/README_CN.md +81 -2
- package/bin/flowmind.js +390 -6
- package/core/mcp-compatibility.js +4 -4
- package/core/providers/yapi/yapi-adapter.js +2 -2
- package/core/providers/yuque/yuque-adapter.js +2 -2
- package/core/skill-loader.js +31 -3
- package/package.json +1 -1
- package/scripts/migrate-config.js +2 -2
- package/skills/yapi-sync-interface/SKILL.md +1 -1
- package/skills/yuque-sync-design/SKILL.md +6 -6
package/README.md
CHANGED
|
@@ -317,6 +317,85 @@ flowmind "这个功能应该用 Redis 还是 MongoDB?"
|
|
|
317
317
|
|
|
318
318
|
---
|
|
319
319
|
|
|
320
|
+
## 📖 CLI Reference
|
|
321
|
+
|
|
322
|
+
### Skill Management
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
# List all available skills
|
|
326
|
+
flowmind skills
|
|
327
|
+
flowmind skills --verbose # Show detailed info
|
|
328
|
+
flowmind skills --json # JSON output (for tool integration)
|
|
329
|
+
flowmind skills --category quality # Filter by category
|
|
330
|
+
|
|
331
|
+
# View single skill info
|
|
332
|
+
flowmind skill log-audit
|
|
333
|
+
flowmind skill log-audit --json # JSON output
|
|
334
|
+
flowmind skill log-audit --read # Read SKILL.md content
|
|
335
|
+
flowmind skill log-audit --config # Show configuration
|
|
336
|
+
|
|
337
|
+
# Modify skill configuration
|
|
338
|
+
flowmind skill log-audit --set defaultFormat sequential-list
|
|
339
|
+
flowmind skill code-review --set security.enabled true
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Resource Management
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# List resource files
|
|
346
|
+
flowmind resource --list
|
|
347
|
+
flowmind resource --list learning
|
|
348
|
+
flowmind resource --list --json # JSON output
|
|
349
|
+
|
|
350
|
+
# View/edit files
|
|
351
|
+
flowmind resource --show config.json
|
|
352
|
+
flowmind resource --edit config.json
|
|
353
|
+
|
|
354
|
+
# Show resource configuration
|
|
355
|
+
flowmind resource --config
|
|
356
|
+
flowmind resource --config --json
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Other Commands
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# Process request
|
|
363
|
+
flowmind process "your request"
|
|
364
|
+
flowmind process --skill log-audit "query logs"
|
|
365
|
+
|
|
366
|
+
# Manage learning
|
|
367
|
+
flowmind learn --list
|
|
368
|
+
flowmind learn --export learnings.json
|
|
369
|
+
|
|
370
|
+
# Scene management
|
|
371
|
+
flowmind scenes --list
|
|
372
|
+
flowmind scenes --add
|
|
373
|
+
|
|
374
|
+
# Show statistics
|
|
375
|
+
flowmind stats
|
|
376
|
+
|
|
377
|
+
# Configuration
|
|
378
|
+
flowmind config --list
|
|
379
|
+
flowmind config --set learning.enabled true
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Tool Integration (Codex/Claude)
|
|
383
|
+
|
|
384
|
+
All commands support `--json` flag for programmatic access:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Get skills list as JSON
|
|
388
|
+
flowmind skills --json
|
|
389
|
+
|
|
390
|
+
# Get skill info as JSON
|
|
391
|
+
flowmind skill log-audit --json
|
|
392
|
+
|
|
393
|
+
# Get resource list as JSON
|
|
394
|
+
flowmind resource --list --json
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
320
399
|
## 🏗️ Architecture
|
|
321
400
|
|
|
322
401
|
### System Architecture
|
|
@@ -614,8 +693,8 @@ flowmind init → Load component-config.json → Register adapters → Activate
|
|
|
614
693
|
| `aliyun-dms-mcp-server` | `databaseManager` | `aliyun-dms` |
|
|
615
694
|
| `friday-rds-redis-query` | `databaseQuery` | `aliyun-rds-query` |
|
|
616
695
|
| `friday-aliyun-sz-rds-redis` | `redisMonitor` | `aliyun-redis` |
|
|
617
|
-
| `
|
|
618
|
-
| `
|
|
696
|
+
| `yapi-mcp` | `apiDoc` | `yapi` |
|
|
697
|
+
| `yuque-mcp` | `knowledgeBase` | `yuque` |
|
|
619
698
|
| `friday-auto-flow` | `workflow` | `friday-flow` |
|
|
620
699
|
| `friday-auto-report` | `report` | `friday-report` |
|
|
621
700
|
|
package/README_CN.md
CHANGED
|
@@ -317,6 +317,85 @@ flowmind "这个功能应该用 Redis 还是 MongoDB?"
|
|
|
317
317
|
|
|
318
318
|
---
|
|
319
319
|
|
|
320
|
+
## 📖 CLI 命令参考
|
|
321
|
+
|
|
322
|
+
### 技能管理
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
# 列出所有可用技能
|
|
326
|
+
flowmind skills
|
|
327
|
+
flowmind skills --verbose # 显示详细信息
|
|
328
|
+
flowmind skills --json # JSON 输出(用于工具集成)
|
|
329
|
+
flowmind skills --category quality # 按分类过滤
|
|
330
|
+
|
|
331
|
+
# 查看单个技能信息
|
|
332
|
+
flowmind skill log-audit
|
|
333
|
+
flowmind skill log-audit --json # JSON 输出
|
|
334
|
+
flowmind skill log-audit --read # 读取 SKILL.md 内容
|
|
335
|
+
flowmind skill log-audit --config # 显示配置
|
|
336
|
+
|
|
337
|
+
# 修改技能配置
|
|
338
|
+
flowmind skill log-audit --set defaultFormat sequential-list
|
|
339
|
+
flowmind skill code-review --set security.enabled true
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 资源管理
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# 列出资源文件
|
|
346
|
+
flowmind resource --list
|
|
347
|
+
flowmind resource --list learning
|
|
348
|
+
flowmind resource --list --json # JSON 输出
|
|
349
|
+
|
|
350
|
+
# 查看/编辑文件
|
|
351
|
+
flowmind resource --show config.json
|
|
352
|
+
flowmind resource --edit config.json
|
|
353
|
+
|
|
354
|
+
# 显示资源配置
|
|
355
|
+
flowmind resource --config
|
|
356
|
+
flowmind resource --config --json
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 其他命令
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# 处理请求
|
|
363
|
+
flowmind process "your request"
|
|
364
|
+
flowmind process --skill log-audit "query logs"
|
|
365
|
+
|
|
366
|
+
# 管理学习记录
|
|
367
|
+
flowmind learn --list
|
|
368
|
+
flowmind learn --export learnings.json
|
|
369
|
+
|
|
370
|
+
# 场景管理
|
|
371
|
+
flowmind scenes --list
|
|
372
|
+
flowmind scenes --add
|
|
373
|
+
|
|
374
|
+
# 显示统计信息
|
|
375
|
+
flowmind stats
|
|
376
|
+
|
|
377
|
+
# 配置管理
|
|
378
|
+
flowmind config --list
|
|
379
|
+
flowmind config --set learning.enabled true
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 工具集成(Codex/Claude)
|
|
383
|
+
|
|
384
|
+
所有命令支持 `--json` 参数用于程序化访问:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# 获取技能列表 JSON
|
|
388
|
+
flowmind skills --json
|
|
389
|
+
|
|
390
|
+
# 获取技能信息 JSON
|
|
391
|
+
flowmind skill log-audit --json
|
|
392
|
+
|
|
393
|
+
# 获取资源列表 JSON
|
|
394
|
+
flowmind resource --list --json
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
320
399
|
## 🏗️ 架构
|
|
321
400
|
|
|
322
401
|
### 系统架构
|
|
@@ -613,8 +692,8 @@ flowmind init → 加载 component-config.json → 注册适配器 → 激活默
|
|
|
613
692
|
| `aliyun-dms-mcp-server` | `databaseManager` | `aliyun-dms` |
|
|
614
693
|
| `friday-rds-redis-query` | `databaseQuery` | `aliyun-rds-query` |
|
|
615
694
|
| `friday-aliyun-sz-rds-redis` | `redisMonitor` | `aliyun-redis` |
|
|
616
|
-
| `
|
|
617
|
-
| `
|
|
695
|
+
| `yapi-mcp` | `apiDoc` | `yapi` |
|
|
696
|
+
| `yuque-mcp` | `knowledgeBase` | `yuque` |
|
|
618
697
|
| `friday-auto-flow` | `workflow` | `friday-flow` |
|
|
619
698
|
| `friday-auto-report` | `report` | `friday-report` |
|
|
620
699
|
|
package/bin/flowmind.js
CHANGED
|
@@ -215,15 +215,145 @@ program
|
|
|
215
215
|
}
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
// Skills command
|
|
218
|
+
// Skills command (enhanced)
|
|
219
219
|
program
|
|
220
220
|
.command('skills')
|
|
221
221
|
.description('List available skills')
|
|
222
|
-
.
|
|
222
|
+
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
223
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
224
|
+
.option('-c, --category <category>', 'Filter by category')
|
|
225
|
+
.action(async (options) => {
|
|
223
226
|
try {
|
|
224
227
|
const fm = await initFlowMind();
|
|
225
228
|
const skills = fm.skills.list();
|
|
226
|
-
|
|
229
|
+
|
|
230
|
+
// Filter by category if specified
|
|
231
|
+
const filtered = options.category
|
|
232
|
+
? skills.filter(s => s.category === options.category)
|
|
233
|
+
: skills;
|
|
234
|
+
|
|
235
|
+
if (options.json) {
|
|
236
|
+
// JSON output for codex/claude integration
|
|
237
|
+
console.log(JSON.stringify({ skills: filtered }, null, 2));
|
|
238
|
+
} else {
|
|
239
|
+
displaySkills(filtered, options.verbose);
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(chalk.red('Error:'), error.message);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Skill command (view/modify single skill)
|
|
247
|
+
program
|
|
248
|
+
.command('skill <name>')
|
|
249
|
+
.description('View or modify skill configuration')
|
|
250
|
+
.option('-i, --info', 'Show skill info (default)')
|
|
251
|
+
.option('-c, --config', 'Show/edit skill configuration')
|
|
252
|
+
.option('-s, --set <key> <value>', 'Set config value')
|
|
253
|
+
.option('-r, --read', 'Read SKILL.md content')
|
|
254
|
+
.option('-e, --edit', 'Open SKILL.md in editor')
|
|
255
|
+
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
256
|
+
.action(async (name, options) => {
|
|
257
|
+
try {
|
|
258
|
+
const fm = await initFlowMind();
|
|
259
|
+
const skill = fm.skills.get(name);
|
|
260
|
+
|
|
261
|
+
if (!skill) {
|
|
262
|
+
console.error(chalk.red(`Skill not found: ${name}`));
|
|
263
|
+
console.log(chalk.cyan('\nAvailable skills:'));
|
|
264
|
+
fm.skills.list().forEach(s => console.log(` - ${s.name}`));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Default to info if no option specified
|
|
269
|
+
if (!options.config && !options.read && !options.edit && !options.set) {
|
|
270
|
+
options.info = true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (options.info) {
|
|
274
|
+
await showSkillInfo(skill, options.json);
|
|
275
|
+
} else if (options.read) {
|
|
276
|
+
await readSkillMd(skill);
|
|
277
|
+
} else if (options.edit) {
|
|
278
|
+
await editSkillMd(skill);
|
|
279
|
+
} else if (options.config) {
|
|
280
|
+
await showSkillConfig(skill, fm, options.json);
|
|
281
|
+
} else if (options.set) {
|
|
282
|
+
// options.set is the key, need value from next arg
|
|
283
|
+
const value = options.set;
|
|
284
|
+
const key = options.set;
|
|
285
|
+
// Get key and value from command line
|
|
286
|
+
const args = process.argv.slice(3);
|
|
287
|
+
if (args.length >= 2) {
|
|
288
|
+
await setSkillConfig(skill, fm, args[0], args[1]);
|
|
289
|
+
} else {
|
|
290
|
+
console.error(chalk.red('Usage: flowmind skill <name> --set <key> <value>'));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error(chalk.red('Error:'), error.message);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Resource command (local resource files)
|
|
299
|
+
program
|
|
300
|
+
.command('resource')
|
|
301
|
+
.alias('res')
|
|
302
|
+
.description('Manage local resource files')
|
|
303
|
+
.option('-l, --list [dir]', 'List resource directory')
|
|
304
|
+
.option('-s, --show <file>', 'Show file content')
|
|
305
|
+
.option('-e, --edit <file>', 'Edit file')
|
|
306
|
+
.option('-c, --config', 'Show resource configuration')
|
|
307
|
+
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
308
|
+
.action(async (options) => {
|
|
309
|
+
try {
|
|
310
|
+
const fm = await initFlowMind();
|
|
311
|
+
const resourceConfig = fm.config.get('resources', {});
|
|
312
|
+
const configDir = path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind');
|
|
313
|
+
|
|
314
|
+
if (options.config) {
|
|
315
|
+
// Show resource configuration
|
|
316
|
+
if (options.json) {
|
|
317
|
+
console.log(JSON.stringify({ resources: resourceConfig }, null, 2));
|
|
318
|
+
} else {
|
|
319
|
+
displayResourceConfig(resourceConfig);
|
|
320
|
+
}
|
|
321
|
+
} else if (options.show) {
|
|
322
|
+
// Show file content
|
|
323
|
+
const filePath = resolveResourcePath(options.show, configDir);
|
|
324
|
+
if (await fs.pathExists(filePath)) {
|
|
325
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
326
|
+
if (options.json) {
|
|
327
|
+
console.log(JSON.stringify({ file: options.show, content }, null, 2));
|
|
328
|
+
} else {
|
|
329
|
+
console.log(chalk.cyan(`\n📄 ${options.show}`));
|
|
330
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
331
|
+
console.log(content);
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
console.error(chalk.red(`File not found: ${options.show}`));
|
|
335
|
+
}
|
|
336
|
+
} else if (options.edit) {
|
|
337
|
+
// Edit file
|
|
338
|
+
const filePath = resolveResourcePath(options.edit, configDir);
|
|
339
|
+
await openInEditor(filePath);
|
|
340
|
+
} else {
|
|
341
|
+
// List directory (default)
|
|
342
|
+
const targetDir = options.list && typeof options.list === 'string'
|
|
343
|
+
? resolveResourcePath(options.list, configDir)
|
|
344
|
+
: configDir;
|
|
345
|
+
|
|
346
|
+
if (await fs.pathExists(targetDir)) {
|
|
347
|
+
const files = await listResourceFiles(targetDir, configDir);
|
|
348
|
+
if (options.json) {
|
|
349
|
+
console.log(JSON.stringify({ directory: targetDir, files }, null, 2));
|
|
350
|
+
} else {
|
|
351
|
+
displayResourceFiles(files, targetDir);
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
console.error(chalk.red(`Directory not found: ${targetDir}`));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
227
357
|
} catch (error) {
|
|
228
358
|
console.error(chalk.red('Error:'), error.message);
|
|
229
359
|
}
|
|
@@ -432,15 +562,19 @@ function displayScenes(scenes) {
|
|
|
432
562
|
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
433
563
|
}
|
|
434
564
|
|
|
435
|
-
function displaySkills(skills) {
|
|
565
|
+
function displaySkills(skills, verbose = false) {
|
|
436
566
|
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
437
567
|
console.log(chalk.cyan('│ Available Skills │'));
|
|
438
568
|
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
439
569
|
|
|
440
570
|
for (const skill of skills) {
|
|
441
|
-
console.log(chalk.cyan(`│ ${skill.name}`));
|
|
571
|
+
console.log(chalk.cyan(`│ ${chalk.bold(skill.name)}`));
|
|
442
572
|
if (skill.description) {
|
|
443
|
-
|
|
573
|
+
const desc = verbose ? skill.description : skill.description.substring(0, 50) + '...';
|
|
574
|
+
console.log(chalk.cyan(`│ ${desc}`));
|
|
575
|
+
}
|
|
576
|
+
if (verbose && skill.category) {
|
|
577
|
+
console.log(chalk.cyan(`│ Category: ${skill.category}`));
|
|
444
578
|
}
|
|
445
579
|
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
446
580
|
}
|
|
@@ -448,6 +582,256 @@ function displaySkills(skills) {
|
|
|
448
582
|
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
449
583
|
}
|
|
450
584
|
|
|
585
|
+
// Skill info display
|
|
586
|
+
async function showSkillInfo(skill, asJson = false) {
|
|
587
|
+
const info = {
|
|
588
|
+
name: skill.name,
|
|
589
|
+
path: skill.path,
|
|
590
|
+
description: skill.definition?.description || 'No description',
|
|
591
|
+
version: skill.definition?.version || skill.definition?.metadata?.version || '1.0.0',
|
|
592
|
+
author: skill.definition?.author || skill.definition?.metadata?.author || 'unknown',
|
|
593
|
+
category: skill.definition?.category || skill.definition?.metadata?.category || 'general',
|
|
594
|
+
componentDependencies: skill.definition?.componentDependencies || [],
|
|
595
|
+
triggers: skill.definition?.triggers || []
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
if (asJson) {
|
|
599
|
+
console.log(JSON.stringify(info, null, 2));
|
|
600
|
+
} else {
|
|
601
|
+
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
602
|
+
console.log(chalk.cyan(`│ ${chalk.bold(info.name)}`));
|
|
603
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
604
|
+
console.log(chalk.cyan(`│ Description: ${info.description}`));
|
|
605
|
+
console.log(chalk.cyan(`│ Version: ${info.version}`));
|
|
606
|
+
console.log(chalk.cyan(`│ Author: ${info.author}`));
|
|
607
|
+
console.log(chalk.cyan(`│ Category: ${info.category}`));
|
|
608
|
+
console.log(chalk.cyan(`│ Path: ${info.path}`));
|
|
609
|
+
|
|
610
|
+
if (info.componentDependencies.length > 0) {
|
|
611
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
612
|
+
console.log(chalk.cyan('│ Component Dependencies:'));
|
|
613
|
+
for (const dep of info.componentDependencies) {
|
|
614
|
+
console.log(chalk.cyan(`│ - ${dep}`));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (info.triggers.length > 0) {
|
|
619
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
620
|
+
console.log(chalk.cyan('│ Trigger Patterns:'));
|
|
621
|
+
for (const trigger of info.triggers.slice(0, 10)) {
|
|
622
|
+
console.log(chalk.cyan(`│ - ${trigger}`));
|
|
623
|
+
}
|
|
624
|
+
if (info.triggers.length > 10) {
|
|
625
|
+
console.log(chalk.cyan(`│ ... and ${info.triggers.length - 10} more`));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Read SKILL.md content
|
|
634
|
+
async function readSkillMd(skill) {
|
|
635
|
+
const skillMdPath = path.join(skill.path, 'SKILL.md');
|
|
636
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
637
|
+
const content = await fs.readFile(skillMdPath, 'utf-8');
|
|
638
|
+
console.log(chalk.cyan(`\n📄 ${skill.name}/SKILL.md`));
|
|
639
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
640
|
+
console.log(content);
|
|
641
|
+
} else {
|
|
642
|
+
console.error(chalk.red('SKILL.md not found'));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Edit SKILL.md
|
|
647
|
+
async function editSkillMd(skill) {
|
|
648
|
+
const skillMdPath = path.join(skill.path, 'SKILL.md');
|
|
649
|
+
await openInEditor(skillMdPath);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Show skill configuration
|
|
653
|
+
async function showSkillConfig(skill, fm, asJson = false) {
|
|
654
|
+
const configKey = skill.name;
|
|
655
|
+
const config = fm.config.get(configKey, {});
|
|
656
|
+
|
|
657
|
+
if (asJson) {
|
|
658
|
+
console.log(JSON.stringify({ skill: skill.name, config }, null, 2));
|
|
659
|
+
} else {
|
|
660
|
+
console.log(chalk.cyan(`\n┌─────────────────────────────────────────────────────┐`));
|
|
661
|
+
console.log(chalk.cyan(`│ ${skill.name} Configuration`));
|
|
662
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
663
|
+
|
|
664
|
+
if (Object.keys(config).length === 0) {
|
|
665
|
+
console.log(chalk.cyan('│ No configuration set'));
|
|
666
|
+
console.log(chalk.cyan('│'));
|
|
667
|
+
console.log(chalk.cyan('│ Default configuration from SKILL.md:'));
|
|
668
|
+
|
|
669
|
+
// Extract default config from SKILL.md
|
|
670
|
+
const skillMdPath = path.join(skill.path, 'SKILL.md');
|
|
671
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
672
|
+
const content = await fs.readFile(skillMdPath, 'utf-8');
|
|
673
|
+
const configMatch = content.match(/## Configuration\n([\s\S]*?)(?=\n##|$)/);
|
|
674
|
+
if (configMatch) {
|
|
675
|
+
const configSection = configMatch[1].trim();
|
|
676
|
+
const lines = configSection.split('\n').slice(0, 15);
|
|
677
|
+
for (const line of lines) {
|
|
678
|
+
console.log(chalk.cyan(`│ ${line}`));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
} else {
|
|
683
|
+
const configJson = JSON.stringify(config, null, 2);
|
|
684
|
+
configJson.split('\n').forEach(line => {
|
|
685
|
+
console.log(chalk.cyan(`│ ${line}`));
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Set skill configuration
|
|
694
|
+
async function setSkillConfig(skill, fm, key, value) {
|
|
695
|
+
const configKey = skill.name;
|
|
696
|
+
const config = fm.config.get(configKey, {});
|
|
697
|
+
|
|
698
|
+
// Support nested keys like "security.enabled"
|
|
699
|
+
const keys = key.split('.');
|
|
700
|
+
let current = config;
|
|
701
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
702
|
+
if (!current[keys[i]]) current[keys[i]] = {};
|
|
703
|
+
current = current[keys[i]];
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Try to parse value as JSON, fallback to string
|
|
707
|
+
let parsedValue;
|
|
708
|
+
try {
|
|
709
|
+
parsedValue = JSON.parse(value);
|
|
710
|
+
} catch {
|
|
711
|
+
parsedValue = value;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
current[keys[keys.length - 1]] = parsedValue;
|
|
715
|
+
fm.config.set(configKey, config);
|
|
716
|
+
await fm.config.save();
|
|
717
|
+
|
|
718
|
+
console.log(chalk.green(`✓ Set ${skill.name}.${key} = ${value}`));
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Resource file helpers
|
|
722
|
+
function resolveResourcePath(filePath, configDir) {
|
|
723
|
+
// If absolute path, use as-is
|
|
724
|
+
if (path.isAbsolute(filePath)) {
|
|
725
|
+
return filePath;
|
|
726
|
+
}
|
|
727
|
+
// Otherwise resolve relative to config dir
|
|
728
|
+
return path.join(configDir, filePath);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
async function listResourceFiles(dir, configDir) {
|
|
732
|
+
const files = [];
|
|
733
|
+
const entries = await fs.readdir(dir);
|
|
734
|
+
|
|
735
|
+
for (const entry of entries) {
|
|
736
|
+
const fullPath = path.join(dir, entry);
|
|
737
|
+
const stat = await fs.stat(fullPath);
|
|
738
|
+
const relativePath = path.relative(configDir, fullPath);
|
|
739
|
+
|
|
740
|
+
if (stat.isDirectory()) {
|
|
741
|
+
files.push({
|
|
742
|
+
name: entry,
|
|
743
|
+
path: relativePath,
|
|
744
|
+
type: 'directory',
|
|
745
|
+
size: 0
|
|
746
|
+
});
|
|
747
|
+
} else {
|
|
748
|
+
files.push({
|
|
749
|
+
name: entry,
|
|
750
|
+
path: relativePath,
|
|
751
|
+
type: 'file',
|
|
752
|
+
size: stat.size,
|
|
753
|
+
modified: stat.mtime
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return files.sort((a, b) => {
|
|
759
|
+
if (a.type === b.type) return a.name.localeCompare(b.name);
|
|
760
|
+
return a.type === 'directory' ? -1 : 1;
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function displayResourceFiles(files, dir) {
|
|
765
|
+
console.log(chalk.cyan(`\n📁 ${dir}`));
|
|
766
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
767
|
+
|
|
768
|
+
if (files.length === 0) {
|
|
769
|
+
console.log(chalk.cyan(' (empty)'));
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
for (const file of files) {
|
|
774
|
+
if (file.type === 'directory') {
|
|
775
|
+
console.log(chalk.blue(` 📁 ${file.name}/`));
|
|
776
|
+
} else {
|
|
777
|
+
const size = formatFileSize(file.size);
|
|
778
|
+
console.log(chalk.white(` 📄 ${file.name}`) + chalk.gray(` (${size})`));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function displayResourceConfig(config) {
|
|
784
|
+
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
785
|
+
console.log(chalk.cyan('│ Resource Configuration │'));
|
|
786
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
787
|
+
|
|
788
|
+
const entries = Object.entries(config);
|
|
789
|
+
if (entries.length === 0) {
|
|
790
|
+
console.log(chalk.cyan('│ No resources configured'));
|
|
791
|
+
} else {
|
|
792
|
+
for (const [key, value] of entries) {
|
|
793
|
+
const enabled = value.enabled !== false;
|
|
794
|
+
const status = enabled ? chalk.green('✓') : chalk.red('✗');
|
|
795
|
+
console.log(chalk.cyan(`│ ${status} ${key}`));
|
|
796
|
+
if (value.path) {
|
|
797
|
+
console.log(chalk.cyan(`│ Path: ${value.path}`));
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function formatFileSize(bytes) {
|
|
806
|
+
if (bytes === 0) return '0 B';
|
|
807
|
+
const k = 1024;
|
|
808
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
809
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
810
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function openInEditor(filePath) {
|
|
814
|
+
const { spawn } = require('child_process');
|
|
815
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
816
|
+
|
|
817
|
+
console.log(chalk.cyan(`Opening ${filePath} in ${editor}...`));
|
|
818
|
+
|
|
819
|
+
return new Promise((resolve, reject) => {
|
|
820
|
+
const child = spawn(editor, [filePath], {
|
|
821
|
+
stdio: 'inherit',
|
|
822
|
+
shell: true
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
child.on('close', (code) => {
|
|
826
|
+
if (code === 0) {
|
|
827
|
+
resolve();
|
|
828
|
+
} else {
|
|
829
|
+
reject(new Error(`Editor exited with code ${code}`));
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
451
835
|
// Parse arguments
|
|
452
836
|
program.parse(process.argv);
|
|
453
837
|
|
|
@@ -41,14 +41,14 @@ const McpServerMapping = Object.freeze({
|
|
|
41
41
|
},
|
|
42
42
|
|
|
43
43
|
// API Documentation
|
|
44
|
-
'
|
|
44
|
+
'yapi-mcp': {
|
|
45
45
|
type: ComponentType.API_DOC,
|
|
46
46
|
provider: 'yapi',
|
|
47
47
|
description: 'YApi API documentation management'
|
|
48
48
|
},
|
|
49
49
|
|
|
50
50
|
// Knowledge Base
|
|
51
|
-
'
|
|
51
|
+
'yuque-mcp': {
|
|
52
52
|
type: ComponentType.KNOWLEDGE_BASE,
|
|
53
53
|
provider: 'yuque',
|
|
54
54
|
description: 'Yuque knowledge base management'
|
|
@@ -77,8 +77,8 @@ const ProviderToMcp = Object.freeze({
|
|
|
77
77
|
'databaseManager:aliyun-dms': 'aliyun-dms-mcp-server',
|
|
78
78
|
'databaseQuery:aliyun-rds-query': 'friday-rds-redis-query',
|
|
79
79
|
'redisMonitor:aliyun-redis': 'friday-aliyun-sz-rds-redis',
|
|
80
|
-
'apiDoc:yapi': '
|
|
81
|
-
'knowledgeBase:yuque': '
|
|
80
|
+
'apiDoc:yapi': 'yapi-mcp',
|
|
81
|
+
'knowledgeBase:yuque': 'yuque-mcp',
|
|
82
82
|
'workflow:friday-flow': 'friday-auto-flow',
|
|
83
83
|
'report:friday-report': 'friday-auto-report'
|
|
84
84
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* YApi API Documentation Adapter
|
|
3
|
-
* Wraps the
|
|
3
|
+
* Wraps the yapi-mcp MCP server for YApi platform management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const ApiDocAdapter = require('../../adapters/api-doc-adapter');
|
|
@@ -24,7 +24,7 @@ class YapiAdapter extends ApiDocAdapter {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
get mcpServer() {
|
|
27
|
-
return '
|
|
27
|
+
return 'yapi-mcp';
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
async searchApis(params) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Yuque Knowledge Base Adapter
|
|
3
|
-
* Wraps the
|
|
3
|
+
* Wraps the yuque-mcp MCP server for Yuque platform management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const KnowledgeBaseAdapter = require('../../adapters/knowledge-base-adapter');
|
|
@@ -23,7 +23,7 @@ class YuqueAdapter extends KnowledgeBaseAdapter {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
get mcpServer() {
|
|
26
|
-
return '
|
|
26
|
+
return 'yuque-mcp';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async getRepos(params) {
|
package/core/skill-loader.js
CHANGED
|
@@ -96,10 +96,35 @@ class SkillLoader {
|
|
|
96
96
|
|
|
97
97
|
// Parse YAML-like frontmatter
|
|
98
98
|
const lines = frontmatter.split('\n');
|
|
99
|
+
let currentKey = null;
|
|
100
|
+
let currentObj = null;
|
|
101
|
+
|
|
99
102
|
for (const line of lines) {
|
|
103
|
+
// Top-level key: value
|
|
100
104
|
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
101
105
|
if (match) {
|
|
102
|
-
|
|
106
|
+
const key = match[1];
|
|
107
|
+
const value = match[2].replace(/^["']|["']$/g, '');
|
|
108
|
+
definition[key] = value;
|
|
109
|
+
currentKey = key;
|
|
110
|
+
currentObj = null;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Nested object (e.g., metadata:)
|
|
115
|
+
const nestedMatch = line.match(/^(\w+):\s*$/);
|
|
116
|
+
if (nestedMatch) {
|
|
117
|
+
currentKey = nestedMatch[1];
|
|
118
|
+
definition[currentKey] = {};
|
|
119
|
+
currentObj = definition[currentKey];
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Nested key: value (indented)
|
|
124
|
+
const nestedValueMatch = line.match(/^\s+(\w+):\s*(.+)$/);
|
|
125
|
+
if (nestedValueMatch && currentObj) {
|
|
126
|
+
currentObj[nestedValueMatch[1]] = nestedValueMatch[2].replace(/^["']|["']$/g, '');
|
|
127
|
+
continue;
|
|
103
128
|
}
|
|
104
129
|
}
|
|
105
130
|
|
|
@@ -114,7 +139,7 @@ class SkillLoader {
|
|
|
114
139
|
}
|
|
115
140
|
|
|
116
141
|
// Extract trigger patterns
|
|
117
|
-
const triggerMatch = content.match(/## Trigger
|
|
142
|
+
const triggerMatch = content.match(/## Trigger Patterns\n([\s\S]*?)(?=\n##|$)/);
|
|
118
143
|
if (triggerMatch) {
|
|
119
144
|
definition.triggers = this.extractTriggers(triggerMatch[1]);
|
|
120
145
|
}
|
|
@@ -266,7 +291,10 @@ class SkillLoader {
|
|
|
266
291
|
return Array.from(this.skills.values()).map(s => ({
|
|
267
292
|
name: s.name,
|
|
268
293
|
description: s.definition?.description,
|
|
269
|
-
category: s.definition?.category
|
|
294
|
+
category: s.definition?.category || s.definition?.metadata?.category,
|
|
295
|
+
version: s.definition?.version || s.definition?.metadata?.version,
|
|
296
|
+
author: s.definition?.author || s.definition?.metadata?.author,
|
|
297
|
+
path: s.path
|
|
270
298
|
}));
|
|
271
299
|
}
|
|
272
300
|
|
package/package.json
CHANGED
|
@@ -19,8 +19,8 @@ const DEFAULT_MAPPING = {
|
|
|
19
19
|
'aliyun-dms-mcp-server': { type: 'databaseManager', provider: 'aliyun-dms' },
|
|
20
20
|
'friday-rds-redis-query': { type: 'databaseQuery', provider: 'aliyun-rds-query' },
|
|
21
21
|
'friday-aliyun-sz-rds-redis': { type: 'redisMonitor', provider: 'aliyun-redis' },
|
|
22
|
-
'
|
|
23
|
-
'
|
|
22
|
+
'yapi-mcp': { type: 'apiDoc', provider: 'yapi' },
|
|
23
|
+
'yuque-mcp': { type: 'knowledgeBase', provider: 'yuque' },
|
|
24
24
|
'friday-auto-flow': { type: 'workflow', provider: 'friday-flow' },
|
|
25
25
|
'friday-auto-report': { type: 'report', provider: 'friday-report' }
|
|
26
26
|
};
|
|
@@ -68,7 +68,7 @@ This skill uses the **apiDoc** component. The actual API documentation provider
|
|
|
68
68
|
|
|
69
69
|
| Provider | MCP Server | Description |
|
|
70
70
|
|----------|------------|-------------|
|
|
71
|
-
| yapi |
|
|
71
|
+
| yapi | yapi-mcp | YApi API documentation platform |
|
|
72
72
|
|
|
73
73
|
Configuration is managed in `flowmind.config.json` under `components.apiDoc`.
|
|
74
74
|
|
|
@@ -69,7 +69,7 @@ This skill uses the **knowledgeBase** component. The actual knowledge base provi
|
|
|
69
69
|
|
|
70
70
|
| Provider | MCP Server | Description |
|
|
71
71
|
|----------|------------|-------------|
|
|
72
|
-
| yuque |
|
|
72
|
+
| yuque | yuque-mcp | Yuque knowledge base |
|
|
73
73
|
| notion | notion-mcp | Notion workspace |
|
|
74
74
|
|
|
75
75
|
Configuration is managed in `flowmind.config.json` under `components.knowledgeBase`.
|
|
@@ -105,12 +105,12 @@ FlowMind:
|
|
|
105
105
|
┌─────────────────────────────────────────────────────┐
|
|
106
106
|
│ Yuque Design Sync Report │
|
|
107
107
|
├─────────────────────────────────────────────────────┤
|
|
108
|
-
│ Repository:
|
|
108
|
+
│ Repository: design-docs │
|
|
109
109
|
│ Document: 订单模块详细设计 │
|
|
110
110
|
├─────────────────────────────────────────────────────┤
|
|
111
111
|
│ Action: Update │
|
|
112
112
|
│ Status: ✅ Success │
|
|
113
|
-
│ URL: https://yuque.com/
|
|
113
|
+
│ URL: https://yuque.com/your-org/design/order-module │
|
|
114
114
|
├─────────────────────────────────────────────────────┤
|
|
115
115
|
│ Sections Synced: 6 │
|
|
116
116
|
│ • API Design: ✅ │
|
|
@@ -134,9 +134,9 @@ FlowMind:
|
|
|
134
134
|
│ Keyword: 订单 │
|
|
135
135
|
│ Results: 5 documents │
|
|
136
136
|
├─────────────────────────────────────────────────────┤
|
|
137
|
-
│ 1. 订单模块详细设计 (
|
|
137
|
+
│ 1. 订单模块详细设计 (design-docs) │
|
|
138
138
|
│ Updated: 2026-06-20 │
|
|
139
|
-
│ 2. 订单流程图 (
|
|
139
|
+
│ 2. 订单流程图 (design-docs) │
|
|
140
140
|
│ Updated: 2026-06-18 │
|
|
141
141
|
│ 3. 订单API文档 (api-docs) │
|
|
142
142
|
│ Updated: 2026-06-15 │
|
|
@@ -148,7 +148,7 @@ FlowMind:
|
|
|
148
148
|
```json
|
|
149
149
|
{
|
|
150
150
|
"yuque-sync-design": {
|
|
151
|
-
"defaultRepo": "
|
|
151
|
+
"defaultRepo": "design-docs",
|
|
152
152
|
"syncFormat": "markdown",
|
|
153
153
|
"autoCreateToc": true,
|
|
154
154
|
"archiveAfterSync": true
|