|
|
@@ -1,96 +1,320 @@
|
|
|
#!/usr/bin/env node
|
|
|
/**
|
|
|
- * Workshop 安装脚本
|
|
|
- * 将 workshop.zip 解压后的文件复制到 ~/.openclaw/workspace/
|
|
|
- *
|
|
|
+ * 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 # 安装全部(默认)
|
|
|
+ * 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 WORKSPACE_DIR = path.join(os.homedir(), '.openclaw', 'workspace');
|
|
|
+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 CREDENTIALS_PATH = path.join(OPENCLAW_DIR, 'voc-credentials.json');
|
|
|
|
|
|
-console.log('');
|
|
|
-console.log('🦐 VOC虾工作坊 安装脚本');
|
|
|
-console.log('==================================================');
|
|
|
-if (DRY_RUN) console.log(' 📌 预览模式(不会实际复制)');
|
|
|
-console.log('');
|
|
|
+// ============================================
|
|
|
+// 工具函数
|
|
|
+// ============================================
|
|
|
+function ensureDir(dir) {
|
|
|
+ if (!DRY_RUN && !fs.existsSync(dir)) {
|
|
|
+ fs.mkdirSync(dir, { recursive: true });
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
-// 确保目标目录存在
|
|
|
-if (!DRY_RUN) {
|
|
|
- fs.mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
|
- fs.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
|
+function copyFile(src, dest) {
|
|
|
+ if (DRY_RUN) return;
|
|
|
+ ensureDir(path.dirname(dest));
|
|
|
+ fs.copyFileSync(src, dest);
|
|
|
}
|
|
|
|
|
|
-let copied = 0;
|
|
|
-let skipped = 0;
|
|
|
+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';
|
|
|
+}
|
|
|
|
|
|
-// 复制 .md 文件到 workspace
|
|
|
-const mdFiles = fs.readdirSync(SRC_DIR).filter(f =>
|
|
|
- f.endsWith('.md') && fs.statSync(path.join(SRC_DIR, f)).isFile()
|
|
|
-);
|
|
|
+// ============================================
|
|
|
+// Help
|
|
|
+// ============================================
|
|
|
+function showHelp() {
|
|
|
+ console.log(`
|
|
|
+🦐 VOC 虾工作坊 安装脚本 v4.2
|
|
|
|
|
|
-console.log('── Playbooks + Supporting Docs ──');
|
|
|
-for (const f of mdFiles) {
|
|
|
- const src = path.join(SRC_DIR, f);
|
|
|
- const dest = path.join(WORKSPACE_DIR, f);
|
|
|
- const size = fs.statSync(src).size;
|
|
|
- const sizeKB = (size / 1024).toFixed(1);
|
|
|
+Usage:
|
|
|
+ node install-workshop.js [options]
|
|
|
|
|
|
- if (DRY_RUN) {
|
|
|
- console.log(` 📄 ${f} (${sizeKB} KB) → ${dest}`);
|
|
|
- } else {
|
|
|
- fs.copyFileSync(src, dest);
|
|
|
- console.log(` ✅ ${f} (${sizeKB} KB)`);
|
|
|
- }
|
|
|
- copied++;
|
|
|
+Options:
|
|
|
+ --dry-run 预览模式(不实际复制)
|
|
|
+ --skills-only 只装 skills
|
|
|
+ --playbook-only 只装 playbooks + memory templates(旧行为)
|
|
|
+ --help, -h 显示帮助
|
|
|
+
|
|
|
+默认(不加参数)安装全部:
|
|
|
+ skills/* → ~/.openclaw/skills/
|
|
|
+ *.md → ~/.openclaw/workspace/
|
|
|
+ memory-templates/* → ~/.openclaw/workspace/memory/
|
|
|
+ set-voc-token.js → ~/.openclaw/tools/
|
|
|
+ voc-token-preflight.js → ~/.openclaw/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
|
|
|
+`);
|
|
|
}
|
|
|
-console.log('');
|
|
|
|
|
|
-// 复制 memory-templates
|
|
|
-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()
|
|
|
- );
|
|
|
+// ============================================
|
|
|
+// 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);
|
|
|
|
|
|
- console.log('── Memory Templates ──');
|
|
|
- for (const f of jsonFiles) {
|
|
|
- const src = path.join(memSrcDir, f);
|
|
|
- const dest = path.join(MEMORY_DIR, f);
|
|
|
- const size = fs.statSync(src).size;
|
|
|
- const sizeKB = (size / 1024).toFixed(1);
|
|
|
+ for (const f of files) {
|
|
|
+ copyFile(path.join(srcSkillDir, f), path.join(destSkillDir, f));
|
|
|
+ }
|
|
|
|
|
|
if (DRY_RUN) {
|
|
|
- console.log(` 📄 ${f} (${sizeKB} KB) → ${dest}`);
|
|
|
+ console.log(` 📄 ${skill} (${files.length} files)`);
|
|
|
} else {
|
|
|
- fs.copyFileSync(src, dest);
|
|
|
- console.log(` ✅ ${f} (${sizeKB} KB)`);
|
|
|
+ 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++;
|
|
|
}
|
|
|
- copied++;
|
|
|
+ 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() {
|
|
|
+ const tools = [
|
|
|
+ { name: 'set-voc-token.js', desc: 'Token 写入工具' },
|
|
|
+ { name: 'voc-token-preflight.js', desc: 'Token 预飞检测工具(Workshop 前置检查)' }
|
|
|
+ ];
|
|
|
+
|
|
|
+ const available = tools.filter(t => fs.existsSync(path.join(SRC_DIR, t.name)));
|
|
|
+ if (available.length === 0) return 0;
|
|
|
+
|
|
|
+ console.log(`── Tools (${available.length}) → ${TOOLS_TARGET} ──`);
|
|
|
+ for (const { name, desc } of available) {
|
|
|
+ const src = path.join(SRC_DIR, name);
|
|
|
+ const dest = path.join(TOOLS_TARGET, name);
|
|
|
+ copyFile(src, dest);
|
|
|
+ console.log(DRY_RUN
|
|
|
+ ? ` 📄 ${name} → ${dest} (${desc})`
|
|
|
+ : ` ✅ ${name} (${desc})`);
|
|
|
}
|
|
|
console.log('');
|
|
|
-} else {
|
|
|
- console.log(' ⚠️ memory-templates/ 目录不存在,跳过');
|
|
|
+ return available.length;
|
|
|
+}
|
|
|
+
|
|
|
+// ============================================
|
|
|
+// 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;
|
|
|
}
|
|
|
|
|
|
-console.log('==================================================');
|
|
|
-if (DRY_RUN) {
|
|
|
- console.log(` 📌 预览完成:${copied} 个文件待安装`);
|
|
|
- console.log(' 去掉 --dry-run 执行实际安装');
|
|
|
-} else {
|
|
|
- console.log(` ✅ 安装完成!共 ${copied} 个文件`);
|
|
|
- console.log(` 📂 ${WORKSPACE_DIR}`);
|
|
|
- console.log(' 🔄 请重启 OpenClaw gateway 加载新文件');
|
|
|
+// ============================================
|
|
|
+// 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('');
|
|
|
+ }
|
|
|
}
|
|
|
-console.log('');
|
|
|
+
|
|
|
+main();
|