sync-skills.js
3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/usr/bin/env node
/**
* sync-skills.js — 将 skills/ 目录下的共享 Skill 同步到各 CLI 配置目录
*
* 源目录: skills/
* 目标目录:
* - .claude/skills/ (SKILL.md,原样复制)
*
* 用法:
* node scripts/sync-skills.js # 同步所有 skill
* node scripts/sync-skills.js innovator # 只同步指定 skill
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const SOURCE_DIR = path.join(ROOT, 'skills');
// 同步目标配置
const TARGETS = [
{
name: 'claude',
dir: path.join(ROOT, '.claude', 'skills'),
// Claude Code: SKILL.md 原样保留
renameMap: {},
},
];
/**
* 递归复制目录,支持文件名重命名映射
* @param {Set} [skipDirs] - 要跳过的子目录名集合
*/
function copyDirSync(src, dest, renameMap = {}, skipDirs = new Set()) {
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destName = renameMap[entry.name] || entry.name;
const destPath = path.join(dest, destName);
if (entry.isDirectory()) {
if (skipDirs.has(entry.name)) continue; // 跳过内部目录
copyDirSync(srcPath, destPath, renameMap, skipDirs);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
/**
* 递归删除目录
*/
function rmDirSync(dir) {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
// Innovator 内部目录,不同步到 CLI
const INTERNAL_DIRS = new Set(['hunter', 'digest', 'evolve']);
/**
* 同步指定的 skill 到所有目标
*/
function syncSkill(skillName) {
const srcDir = path.join(SOURCE_DIR, skillName);
if (!fs.existsSync(srcDir)) {
console.error(` ✗ 源目录不存在: skills/${skillName}/`);
return false;
}
for (const target of TARGETS) {
const destDir = path.join(target.dir, skillName);
// 清空目标目录(确保删除旧文件)
rmDirSync(destDir);
// 复制(跳过 hunter/digest/evolve 内部目录,带重命名映射)
copyDirSync(srcDir, destDir, target.renameMap, INTERNAL_DIRS);
const renames = Object.entries(target.renameMap)
.map(([from, to]) => `${from}→${to}`)
.join(', ');
const renameNote = renames ? ` (${renames})` : '';
console.log(` ✓ .${target.name}/skills/${skillName}/${renameNote}`);
}
return true;
}
// ─── Main ───
function main() {
console.log('[sync-skills] 同步共享 Skill 到 CLI 配置目录\n');
// 检查源目录
if (!fs.existsSync(SOURCE_DIR)) {
console.error(`源目录不存在: ${SOURCE_DIR}`);
process.exit(1);
}
// 确定要同步的 skill 列表
const filterSkills = process.argv.slice(2);
const allSkills = fs.readdirSync(SOURCE_DIR, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
const skillsToSync = filterSkills.length > 0
? filterSkills.filter(s => allSkills.includes(s))
: allSkills;
if (skillsToSync.length === 0) {
console.log('没有需要同步的 Skill。');
return;
}
console.log(`源: skills/ (${allSkills.length} 个 skill)`);
console.log(`目标: .claude/skills/`);
console.log(`同步: ${skillsToSync.join(', ')}\n`);
let synced = 0;
for (const skillName of skillsToSync) {
console.log(`[${skillName}]`);
if (syncSkill(skillName)) synced++;
}
console.log(`\n✅ 同步完成: ${synced} 个 skill → ${TARGETS.length} 个目标`);
}
main();