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