#!/usr/bin/env node /** * OpenClaw 全量安装脚本 v4.1 * * 一键安装 Skills + Workshop + Token 工具 + 凭证模板 * * 用法: * node install-all.js # 安装全部 * node install-all.js --dry-run # 预览模式 * node install-all.js --skills-only # 只装 Skills * node install-all.js --workshop-only # 只装 Workshop * node install-all.js --list # 列出内容 */ const fs = require('fs'); const path = require('path'); const os = require('os'); // ============================================ // 配置 // ============================================ const VERSION = '4.1.0'; const SRC_DIR = __dirname; const HOME = os.homedir(); const OPENCLAW_DIR = path.join(HOME, '.openclaw'); const SKILLS_TARGET = path.join(OPENCLAW_DIR, 'skills'); const WORKSPACE_TARGET = path.join(OPENCLAW_DIR, 'workspace'); const MEMORY_TARGET = path.join(WORKSPACE_TARGET, 'memory'); const TOOLS_TARGET = path.join(OPENCLAW_DIR, 'tools'); const WORKSPACE_TOOLS_TARGET = path.join(WORKSPACE_TARGET, 'scripts', 'tools'); const CREDENTIALS_PATH = path.join(OPENCLAW_DIR, 'voc-credentials.json'); // ============================================ // 参数 // ============================================ const args = process.argv.slice(2); const DRY_RUN = args.includes('--dry-run'); const SKILLS_ONLY = args.includes('--skills-only'); const WORKSHOP_ONLY = args.includes('--workshop-only'); const LIST_ONLY = args.includes('--list') || args.includes('-l'); const HELP = args.includes('--help') || args.includes('-h'); // ============================================ // 工具函数 // ============================================ function ensureDir(dir) { if (!DRY_RUN && !fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } function copyFile(src, dest) { if (DRY_RUN) return; ensureDir(path.dirname(dest)); fs.copyFileSync(src, dest); } function copyDirRecursive(srcDir, destDir) { let count = 0; if (!fs.existsSync(srcDir)) return count; for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) { const src = path.join(srcDir, entry.name); const dest = path.join(destDir, entry.name); if (entry.isDirectory()) { count += copyDirRecursive(src, dest); } else if (entry.isFile()) { copyFile(src, dest); count++; } } return count; } function formatSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1024 / 1024).toFixed(1) + ' MB'; } // ============================================ // Skills 安装 // ============================================ function installSkills() { const skillsDir = path.join(SRC_DIR, 'skills'); if (!fs.existsSync(skillsDir)) { console.log(' ⚠️ skills/ 目录不存在,跳过'); return { installed: 0, files: 0 }; } const skills = fs.readdirSync(skillsDir).filter(d => { const p = path.join(skillsDir, d); return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md')); }).sort(); console.log(` 共 ${skills.length} 个技能 → ${SKILLS_TARGET}`); console.log(''); let fileCount = 0; for (const skill of skills) { const srcSkillDir = path.join(skillsDir, skill); const destSkillDir = path.join(SKILLS_TARGET, skill); const files = fs.readdirSync(srcSkillDir).filter(f => fs.statSync(path.join(srcSkillDir, f)).isFile() ); for (const f of files) { copyFile(path.join(srcSkillDir, f), path.join(destSkillDir, f)); fileCount++; } process.stdout.write(` ✅ ${skill}\n`); } return { installed: skills.length, files: fileCount }; } function listSkills() { const skillsDir = path.join(SRC_DIR, 'skills'); if (!fs.existsSync(skillsDir)) return; const skills = fs.readdirSync(skillsDir).filter(d => { const p = path.join(skillsDir, d); return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md')); }).sort(); console.log(` Skills (${skills.length}):`); for (const s of skills) { const files = fs.readdirSync(path.join(skillsDir, s)).filter(f => fs.statSync(path.join(skillsDir, s, f)).isFile() ); const totalSize = files.reduce((sum, f) => sum + fs.statSync(path.join(skillsDir, s, f)).size, 0 ); console.log(` ${s.padEnd(38)} ${formatSize(totalSize).padStart(8)} [${files.join(', ')}]`); } console.log(''); } // ============================================ // Workshop 安装 // ============================================ function installWorkshop() { const workshopDir = path.join(SRC_DIR, 'workshop'); if (!fs.existsSync(workshopDir)) { console.log(' ⚠️ workshop/ 目录不存在,跳过'); return { installed: 0 }; } let count = 0; // Playbooks + supporting docs const mdFiles = fs.readdirSync(workshopDir).filter(f => f.endsWith('.md') && fs.statSync(path.join(workshopDir, f)).isFile() ); console.log(` Playbooks → ${WORKSPACE_TARGET}`); for (const f of mdFiles) { copyFile(path.join(workshopDir, f), path.join(WORKSPACE_TARGET, f)); const size = formatSize(fs.statSync(path.join(workshopDir, f)).size); console.log(` ✅ ${f} (${size})`); count++; } console.log(''); // Memory templates const memDir = path.join(workshopDir, 'memory-templates'); if (fs.existsSync(memDir)) { const jsonFiles = fs.readdirSync(memDir).filter(f => f.endsWith('.json') && fs.statSync(path.join(memDir, f)).isFile() ); console.log(` Memory Templates → ${MEMORY_TARGET}`); for (const f of jsonFiles) { copyFile(path.join(memDir, f), path.join(MEMORY_TARGET, f)); const size = formatSize(fs.statSync(path.join(memDir, f)).size); console.log(` ✅ ${f} (${size})`); count++; } console.log(''); } return { installed: count }; } function listWorkshop() { const workshopDir = path.join(SRC_DIR, 'workshop'); if (!fs.existsSync(workshopDir)) return; const mdFiles = fs.readdirSync(workshopDir).filter(f => f.endsWith('.md') && fs.statSync(path.join(workshopDir, f)).isFile() ); console.log(` Workshop Playbooks (${mdFiles.length}):`); for (const f of mdFiles) { const size = formatSize(fs.statSync(path.join(workshopDir, f)).size); console.log(` ${f.padEnd(42)} ${size}`); } const memDir = path.join(workshopDir, 'memory-templates'); if (fs.existsSync(memDir)) { const jsonFiles = fs.readdirSync(memDir).filter(f => f.endsWith('.json') && fs.statSync(path.join(memDir, f)).isFile() ); console.log(` Memory Templates (${jsonFiles.length}):`); for (const f of jsonFiles) { const size = formatSize(fs.statSync(path.join(memDir, f)).size); console.log(` ${f.padEnd(42)} ${size}`); } } console.log(''); } // ============================================ // 工具安装 // ============================================ function installTools() { let count = 0; const packagedToolsDir = path.join(SRC_DIR, 'tools'); if (fs.existsSync(packagedToolsDir)) { count += copyDirRecursive(packagedToolsDir, TOOLS_TARGET); const mirrorCount = copyDirRecursive(packagedToolsDir, WORKSPACE_TOOLS_TARGET); console.log(` ✅ tools/* → ${TOOLS_TARGET} (${count} files)`); console.log(` ✅ tools/* → ${WORKSPACE_TOOLS_TARGET} (${mirrorCount} files)`); return count; } const legacyTools = ['set-voc-token.js', 'voc-token-preflight.js']; for (const name of legacyTools) { const src = path.join(SRC_DIR, name); if (!fs.existsSync(src)) { console.log(` ⚠️ ${name} 不存在,跳过`); continue; } copyFile(src, path.join(TOOLS_TARGET, name)); copyFile(src, path.join(WORKSPACE_TOOLS_TARGET, name)); console.log(` ✅ ${name}`); count++; } return count; } function installCredentialsTemplate() { if (fs.existsSync(CREDENTIALS_PATH)) { // 不覆盖已有的凭证文件 console.log(` ⏭️ ${CREDENTIALS_PATH} 已存在,跳过`); return; } const template = { _comment: "VOC-AI Token 配置文件。充值登录后将获得的 session token 填入 vocToken 字段。", _format: "Token 格式为 r:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(32位十六进制)", _setup: "运行 node ~/.openclaw/tools/set-voc-token.js 自动写入", _payment: "充值页面: https://app.fmode.cn/dev/apig-pay/?apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF", vocToken: "" }; if (!DRY_RUN) { ensureDir(OPENCLAW_DIR); fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(template, null, 2) + '\n', 'utf-8'); } console.log(` ✅ 凭证模板 → ${CREDENTIALS_PATH}`); } // ============================================ // Help // ============================================ function showHelp() { console.log(` OpenClaw 全量安装脚本 v${VERSION} Usage: node install-all.js [options] Options: --list, -l 列出包含内容 --dry-run 预览模式 --skills-only 只安装 Skills --workshop-only 只安装 Workshop --help, -h 显示帮助 安装内容: Skills → ~/.openclaw/skills/ (技能文件) Workshop → ~/.openclaw/workspace/ (工作坊 playbooks) Memory → ~/.openclaw/workspace/memory/(记忆模板) Tools → ~/.openclaw/tools/ (本地脚本工具) Tool mirror → ~/.openclaw/workspace/scripts/tools/ (Skill 相对路径执行) Credentials → ~/.openclaw/voc-credentials.json (Token 配置模板) Token 设置流程: 1. 打开充值页面: https://app.fmode.cn/dev/apig-pay/?apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF 2. 登录并充值 3. 获取 session token (格式: r:xxxx...) 4. 运行: node ~/.openclaw/tools/set-voc-token.js 5. 重启 OpenClaw gateway `); } // ============================================ // Main // ============================================ function main() { if (HELP) { showHelp(); return; } console.log(''); console.log('╔══════════════════════════════════════════════════╗'); console.log(`║ OpenClaw 全量安装脚本 v${VERSION} ║`); console.log('╚══════════════════════════════════════════════════╝'); if (DRY_RUN) console.log(' 📌 预览模式'); console.log(''); // ── List mode ── if (LIST_ONLY) { if (!WORKSHOP_ONLY) listSkills(); if (!SKILLS_ONLY) listWorkshop(); return; } // ── Install ── let totalSkills = 0; let totalFiles = 0; let totalWorkshop = 0; let totalTools = 0; // Tools + Credentials(总是安装) console.log('── Token 工具 + 凭证 ──'); totalTools = installTools(); installCredentialsTemplate(); console.log(''); // Skills if (!WORKSHOP_ONLY) { console.log('── Skills ──'); const sr = installSkills(); totalSkills = sr.installed; totalFiles = sr.files; console.log(''); } // Workshop if (!SKILLS_ONLY) { console.log('── Workshop ──'); const wr = installWorkshop(); totalWorkshop = wr.installed; } // Summary console.log('══════════════════════════════════════════════════'); const prefix = DRY_RUN ? '[预览] ' : ''; console.log(`${prefix}安装完成!`); if (!WORKSHOP_ONLY) console.log(` 🔧 Skills: ${totalSkills} 个技能 (${totalFiles} 个文件) → ${SKILLS_TARGET}`); if (!SKILLS_ONLY) console.log(` 🦐 Workshop: ${totalWorkshop} 个文件 → ${WORKSPACE_TARGET}`); console.log(` 🔑 Tools: ${totalTools} 个文件 → ${TOOLS_TARGET}`); console.log(` 🧰 Tool mirror → ${WORKSPACE_TOOLS_TARGET}`); console.log(` 📄 凭证配置 → ${CREDENTIALS_PATH}`); console.log(''); if (!DRY_RUN) { console.log('📋 下一步:'); console.log(' 1. 设置 Token(如果还没有):'); console.log(' 打开 https://app.fmode.cn/dev/apig-pay/?apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF'); console.log(' 登录充值后获取 session token,运行:'); console.log(' node ~/.openclaw/tools/set-voc-token.js '); console.log(''); console.log(' 2. 重启 OpenClaw gateway 加载新文件'); console.log(''); } } main();