| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- #!/usr/bin/env node
- /**
- * VOC 虾工作坊 一键安装脚本 v4.2
- *
- * 将 workshop.zip 解压后的全部组件(skills + playbooks + memory templates + tools + credentials)
- * 安装到 OpenClaw 对应目录。
- *
- * 用法:
- * node install-workshop.js # 安装全部(默认)
- * node install-workshop.js --dry-run # 预览模式
- * node install-workshop.js --skills-only # 只装 skills
- * node install-workshop.js --playbook-only # 只装 playbooks(旧行为,兼容)
- * node install-workshop.js --help # 帮助
- */
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- // ============================================
- // 参数
- // ============================================
- const args = process.argv.slice(2);
- const DRY_RUN = args.includes('--dry-run');
- const SKILLS_ONLY = args.includes('--skills-only');
- const PLAYBOOK_ONLY = args.includes('--playbook-only');
- const HELP = args.includes('--help') || args.includes('-h');
- // ============================================
- // 路径
- // ============================================
- const SRC_DIR = __dirname;
- const HOME = os.homedir();
- const OPENCLAW_DIR = path.join(HOME, '.openclaw');
- const WORKSPACE_DIR = path.join(OPENCLAW_DIR, 'workspace');
- const MEMORY_DIR = path.join(WORKSPACE_DIR, 'memory');
- const SKILLS_TARGET = path.join(OPENCLAW_DIR, 'skills');
- const TOOLS_TARGET = path.join(OPENCLAW_DIR, 'tools');
- const WORKSPACE_TOOLS_TARGET = path.join(WORKSPACE_DIR, 'scripts', 'tools');
- const CREDENTIALS_PATH = path.join(OPENCLAW_DIR, 'voc-credentials.json');
- // ============================================
- // 工具函数
- // ============================================
- 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';
- }
- // ============================================
- // Help
- // ============================================
- function showHelp() {
- console.log(`
- 🦐 VOC 虾工作坊 安装脚本 v4.2
- Usage:
- node install-workshop.js [options]
- Options:
- --dry-run 预览模式(不实际复制)
- --skills-only 只装 skills
- --playbook-only 只装 playbooks + memory templates(旧行为)
- --help, -h 显示帮助
- 默认(不加参数)安装全部:
- skills/* → ~/.openclaw/skills/
- *.md → ~/.openclaw/workspace/
- memory-templates/* → ~/.openclaw/workspace/memory/
- tools/* → ~/.openclaw/tools/
- tools/* → ~/.openclaw/workspace/scripts/tools/
- voc-credentials.template.json → ~/.openclaw/voc-credentials.json(若不存在)
- Token 设置流程:
- 1. 打开 https://app.fmode.cn/dev/apig-pay/?apigid=7HwdQZk55B&fun_id=HOkkX72PMF
- 2. 登录并充值
- 3. 复制 session token (格式: r:xxxx...)
- 4. 运行: node ~/.openclaw/tools/set-voc-token.js <your-token>
- 5. 重启 OpenClaw gateway
- `);
- }
- // ============================================
- // Skills 安装
- // ============================================
- function installSkills() {
- const skillsSrcDir = path.join(SRC_DIR, 'skills');
- if (!fs.existsSync(skillsSrcDir)) {
- console.log(' ⚠️ skills/ 目录不存在,跳过');
- return 0;
- }
- const skills = fs.readdirSync(skillsSrcDir).filter(d => {
- const p = path.join(skillsSrcDir, d);
- return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md'));
- }).sort();
- console.log(`── Skills (${skills.length}) → ${SKILLS_TARGET} ──`);
- for (const skill of skills) {
- const srcSkillDir = path.join(skillsSrcDir, skill);
- const destSkillDir = path.join(SKILLS_TARGET, skill);
- const files = fs.readdirSync(srcSkillDir).filter(f =>
- fs.statSync(path.join(srcSkillDir, f)).isFile()
- );
- // 覆盖式安装(先删除目标)
- if (!DRY_RUN && fs.existsSync(destSkillDir)) {
- fs.rmSync(destSkillDir, { recursive: true, force: true });
- }
- ensureDir(destSkillDir);
- for (const f of files) {
- copyFile(path.join(srcSkillDir, f), path.join(destSkillDir, f));
- }
- if (DRY_RUN) {
- console.log(` 📄 ${skill} (${files.length} files)`);
- } else {
- console.log(` ✅ ${skill}`);
- }
- }
- console.log('');
- return skills.length;
- }
- // ============================================
- // Playbooks + Memory Templates 安装
- // ============================================
- function installPlaybooks() {
- let count = 0;
- // .md 文件 → workspace
- const mdFiles = fs.readdirSync(SRC_DIR).filter(f =>
- f.endsWith('.md') && fs.statSync(path.join(SRC_DIR, f)).isFile()
- );
- if (mdFiles.length > 0) {
- console.log(`── Playbooks (${mdFiles.length}) → ${WORKSPACE_DIR} ──`);
- for (const f of mdFiles) {
- const src = path.join(SRC_DIR, f);
- const dest = path.join(WORKSPACE_DIR, f);
- const size = formatSize(fs.statSync(src).size);
- copyFile(src, dest);
- console.log(DRY_RUN ? ` 📄 ${f} (${size})` : ` ✅ ${f} (${size})`);
- count++;
- }
- console.log('');
- }
- // memory-templates → workspace/memory
- const memSrcDir = path.join(SRC_DIR, 'memory-templates');
- if (fs.existsSync(memSrcDir)) {
- const jsonFiles = fs.readdirSync(memSrcDir).filter(f =>
- f.endsWith('.json') && fs.statSync(path.join(memSrcDir, f)).isFile()
- );
- if (jsonFiles.length > 0) {
- console.log(`── Memory Templates (${jsonFiles.length}) → ${MEMORY_DIR} ──`);
- for (const f of jsonFiles) {
- const src = path.join(memSrcDir, f);
- const dest = path.join(MEMORY_DIR, f);
- const size = formatSize(fs.statSync(src).size);
- copyFile(src, dest);
- console.log(DRY_RUN ? ` 📄 ${f} (${size})` : ` ✅ ${f} (${size})`);
- count++;
- }
- console.log('');
- }
- }
- return count;
- }
- // ============================================
- // Tools 安装
- // ============================================
- function installTools() {
- let count = 0;
- const packagedToolsDir = path.join(SRC_DIR, 'tools');
- if (fs.existsSync(packagedToolsDir)) {
- console.log(`── Tools → ${TOOLS_TARGET} ──`);
- count += copyDirRecursive(packagedToolsDir, TOOLS_TARGET);
- console.log(DRY_RUN ? ` 📄 tools/* → ${TOOLS_TARGET}` : ` ✅ ${count} files`);
- console.log(`── Workspace Tool Mirror → ${WORKSPACE_TOOLS_TARGET} ──`);
- const mirrorCount = copyDirRecursive(packagedToolsDir, WORKSPACE_TOOLS_TARGET);
- console.log(DRY_RUN ? ` 📄 tools/* → ${WORKSPACE_TOOLS_TARGET}` : ` ✅ ${mirrorCount} files`);
- console.log('');
- return count;
- }
- const legacyTools = ['set-voc-token.js', 'voc-token-preflight.js'];
- const available = legacyTools.filter(name => fs.existsSync(path.join(SRC_DIR, name)));
- if (available.length === 0) return 0;
- console.log(`── Tools (${available.length}) → ${TOOLS_TARGET} ──`);
- for (const name of available) {
- const src = path.join(SRC_DIR, name);
- copyFile(src, path.join(TOOLS_TARGET, name));
- copyFile(src, path.join(WORKSPACE_TOOLS_TARGET, name));
- console.log(DRY_RUN ? ` 📄 ${name}` : ` ✅ ${name}`);
- count++;
- }
- console.log('');
- return count;
- }
- // ============================================
- // Credentials Template 安装(不覆盖已有凭证)
- // ============================================
- function installCredentialsTemplate() {
- const templatePath = path.join(SRC_DIR, 'voc-credentials.template.json');
- if (!fs.existsSync(templatePath)) return false;
- console.log(`── Credentials Template → ${OPENCLAW_DIR} ──`);
- // 总是安装 template 本身(作参考)
- const templateDest = path.join(OPENCLAW_DIR, 'voc-credentials.template.json');
- copyFile(templatePath, templateDest);
- console.log(DRY_RUN
- ? ` 📄 voc-credentials.template.json → ${templateDest}`
- : ` ✅ voc-credentials.template.json (参考模板)`);
- // 只有当 voc-credentials.json 不存在时才拷贝为工作配置
- if (fs.existsSync(CREDENTIALS_PATH)) {
- console.log(` ⏭️ voc-credentials.json 已存在,保留用户配置(未覆盖)`);
- } else {
- copyFile(templatePath, CREDENTIALS_PATH);
- console.log(DRY_RUN
- ? ` 📄 voc-credentials.json → ${CREDENTIALS_PATH} (新建)`
- : ` ✅ voc-credentials.json (新建,待填写 vocToken)`);
- }
- console.log('');
- return true;
- }
- // ============================================
- // Main
- // ============================================
- function main() {
- if (HELP) { showHelp(); return; }
- console.log('');
- console.log('══════════════════════════════════════════════════');
- console.log(' 🦐 VOC 虾工作坊 安装脚本 v4.2(自足版)');
- console.log('══════════════════════════════════════════════════');
- if (DRY_RUN) console.log(' 📌 预览模式(不会实际复制)');
- if (SKILLS_ONLY) console.log(' 📌 只装 skills');
- if (PLAYBOOK_ONLY) console.log(' 📌 只装 playbooks + memory templates');
- console.log('');
- // 确保目标根目录
- ensureDir(OPENCLAW_DIR);
- ensureDir(WORKSPACE_DIR);
- ensureDir(MEMORY_DIR);
- ensureDir(SKILLS_TARGET);
- ensureDir(TOOLS_TARGET);
- let skillsCount = 0;
- let playbookCount = 0;
- let toolsCount = 0;
- let credInstalled = false;
- // Skills
- if (!PLAYBOOK_ONLY) {
- skillsCount = installSkills();
- }
- // Playbooks + Memory Templates
- if (!SKILLS_ONLY) {
- playbookCount = installPlaybooks();
- }
- // Tools + Credentials(总是安装,除非 --playbook-only)
- if (!PLAYBOOK_ONLY && !SKILLS_ONLY) {
- toolsCount = installTools();
- credInstalled = installCredentialsTemplate();
- } else if (SKILLS_ONLY) {
- // skills-only 也带上 tools,因为 Session 0 Step 0.4 的预飞依赖它
- toolsCount = installTools();
- }
- // Summary
- console.log('══════════════════════════════════════════════════');
- const prefix = DRY_RUN ? '[预览] ' : '';
- console.log(`${prefix}✅ 安装完成`);
- if (!PLAYBOOK_ONLY) console.log(` 🔧 Skills: ${skillsCount} 个 → ${SKILLS_TARGET}`);
- if (!SKILLS_ONLY) console.log(` � Playbooks + Memory: ${playbookCount} 个 → ${WORKSPACE_DIR}`);
- if (toolsCount > 0) console.log(` 🔑 Tools: ${toolsCount} 个 → ${TOOLS_TARGET}`);
- if (credInstalled) console.log(` 📝 Credentials: ${CREDENTIALS_PATH}`);
- console.log('');
- if (!DRY_RUN) {
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
- console.log('� 下一步:设置 Token(首次使用)');
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
- console.log(' 1. 打开充值页面(扫码登录 + 充值):');
- console.log(' https://app.fmode.cn/dev/apig-pay/?apigid=7HwdQZk55B&fun_id=HOkkX72PMF');
- console.log('');
- console.log(' 2. 复制 session token(格式: r:xxxx...)');
- console.log('');
- console.log(' 3. 运行:');
- console.log(' node ~/.openclaw/tools/set-voc-token.js <your-token>');
- console.log('');
- console.log(' 4. 验证(可选):');
- console.log(' node ~/.openclaw/tools/voc-token-preflight.js');
- console.log(' 期望看到: status=valid, 余额 >= 1');
- console.log('');
- console.log(' 5. 重启 OpenClaw gateway');
- console.log('');
- console.log(' 6. 对话框说"开始 VOC 工作坊",Agent 会进入 Session 0');
- console.log('');
- }
- }
- main();
|