#!/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();