#!/usr/bin/env node /** * fmode-skills installer.mjs * 检测宿主 Agent,把仓库内 skills/ 下所有子技能分发到宿主技能目录。 * Copyright (c) 未来飞马 MPL-2.0 * * 用法: * node installer.mjs --all 分发全部子技能到检测到的宿主目录 * node installer.mjs --check 检查现状(只读,不写文件) * node installer.mjs --host wb 指定宿主 wb|cc|hermes(跳过自动检测) */ import { existsSync, readdirSync, mkdirSync, cpSync, writeFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { homedir } from 'node:os'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); const SKILLS_DIR = join(ROOT, 'skills'); // 宿主 -> 技能目录 映射(ESM 技能放在各宿主全局技能位置) const HOSTS = { wb: { name: 'WorkBuddy', dir: () => join(homedir(), '.workbuddy', 'skills') }, cc: { name: 'Claude Code', dir: () => join(homedir(), '.claude', 'skills') }, hermes: { name: 'Hermes Agent', dir: () => join(homedir(), '.hermes', 'skills') }, }; function detectHost() { // 探测哪个宿主目录存在 if (existsSync(join(homedir(), '.workbuddy', 'skills')) || existsSync(join(homedir(), '.workbuddy'))) return 'wb'; if (existsSync(join(homedir(), '.claude', 'skills')) || existsSync(join(homedir(), '.claude')) || process.env.CLAUDE_CODE_ENTRYPOINT) return 'cc'; if (existsSync(join(homedir(), '.hermes', 'skills')) || process.env.HERMES_HOME) return 'hermes'; return 'cc'; // 默认 Claude-compatible } function listSkills() { if (!existsSync(SKILLS_DIR)) return []; return readdirSync(SKILLS_DIR, { withFileTypes: true }) .filter(e => e.isDirectory()) .map(e => e.name) .filter(n => n.startsWith('fmode-')); } function main() { const args = process.argv.slice(2); const check = args.includes('--check'); const hostArg = args.find(a => a.startsWith('--host')); const hostKey = (hostArg ? hostArg.split('=')[1] || process.argv[process.argv.indexOf(hostArg)+1] : detectHost()) || detectHost(); const host = HOSTS[hostKey]; if (!host) { console.error(`未知宿主: ${hostKey}`); process.exit(1); } const skills = listSkills(); console.log(`[fmode-skills] 检测到宿主: ${host.name}`); console.log(`[fmode-skills] 发现子技能: ${skills.length} 个 -> ${skills.join(', ')}`); if (skills.length === 0) { console.error('未找到子技能(打开 /skills 目录检查)'); process.exit(1); } if (check) { const target = host.dir(); console.log(`[check] 目标目录: ${target}`); console.log(`[check] 已存在: ${Object.keys(HOSTS).map(k => `${k}:${existsSync(join(homedir(), k === 'wb' ? '.workbuddy' : k === 'cc' ? '.claude' : '.hermes'))}`).join(' ')}`); process.exit(0); } const target = host.dir(); console.log(`[fmode-skills] 安装到: ${target}`); const installed = []; for (const skill of skills) { const src = join(SKILLS_DIR, skill); const dest = join(target, skill); if (!existsSync(src)) continue; mkdirSync(target, { recursive: true }); cpSync(src, dest, { recursive: true, force: true }); installed.push(skill); } writeFileSync(join(target, 'fmode-skills.installed.json'), JSON.stringify({ installedAt: new Date().toISOString(), host: hostKey, count: installed.length, skills: installed, }, null, 2)); console.log(`✅ 安装完成: ${installed.length} 个子技能已落盘到 ${target}`); console.log(`清单: fmode-skills.installed.json`); } main();