install-all.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #!/usr/bin/env node
  2. /**
  3. * OpenClaw 全量安装脚本 v4.1
  4. *
  5. * 一键安装 Skills + Workshop + Token 工具 + 凭证模板
  6. *
  7. * 用法:
  8. * node install-all.js # 安装全部
  9. * node install-all.js --dry-run # 预览模式
  10. * node install-all.js --skills-only # 只装 Skills
  11. * node install-all.js --workshop-only # 只装 Workshop
  12. * node install-all.js --list # 列出内容
  13. */
  14. const fs = require('fs');
  15. const path = require('path');
  16. const os = require('os');
  17. // ============================================
  18. // 配置
  19. // ============================================
  20. const VERSION = '4.1.0';
  21. const SRC_DIR = __dirname;
  22. const HOME = os.homedir();
  23. const OPENCLAW_DIR = path.join(HOME, '.openclaw');
  24. const SKILLS_TARGET = path.join(OPENCLAW_DIR, 'skills');
  25. const WORKSPACE_TARGET = path.join(OPENCLAW_DIR, 'workspace');
  26. const MEMORY_TARGET = path.join(WORKSPACE_TARGET, 'memory');
  27. const TOOLS_TARGET = path.join(OPENCLAW_DIR, 'tools');
  28. const WORKSPACE_TOOLS_TARGET = path.join(WORKSPACE_TARGET, 'scripts', 'tools');
  29. const CREDENTIALS_PATH = path.join(OPENCLAW_DIR, 'voc-credentials.json');
  30. // ============================================
  31. // 参数
  32. // ============================================
  33. const args = process.argv.slice(2);
  34. const DRY_RUN = args.includes('--dry-run');
  35. const SKILLS_ONLY = args.includes('--skills-only');
  36. const WORKSHOP_ONLY = args.includes('--workshop-only');
  37. const LIST_ONLY = args.includes('--list') || args.includes('-l');
  38. const HELP = args.includes('--help') || args.includes('-h');
  39. // ============================================
  40. // 工具函数
  41. // ============================================
  42. function ensureDir(dir) {
  43. if (!DRY_RUN && !fs.existsSync(dir)) {
  44. fs.mkdirSync(dir, { recursive: true });
  45. }
  46. }
  47. function copyFile(src, dest) {
  48. if (DRY_RUN) return;
  49. ensureDir(path.dirname(dest));
  50. fs.copyFileSync(src, dest);
  51. }
  52. function copyDirRecursive(srcDir, destDir) {
  53. let count = 0;
  54. if (!fs.existsSync(srcDir)) return count;
  55. for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
  56. const src = path.join(srcDir, entry.name);
  57. const dest = path.join(destDir, entry.name);
  58. if (entry.isDirectory()) {
  59. count += copyDirRecursive(src, dest);
  60. } else if (entry.isFile()) {
  61. copyFile(src, dest);
  62. count++;
  63. }
  64. }
  65. return count;
  66. }
  67. function formatSize(bytes) {
  68. if (bytes < 1024) return bytes + ' B';
  69. if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
  70. return (bytes / 1024 / 1024).toFixed(1) + ' MB';
  71. }
  72. // ============================================
  73. // Skills 安装
  74. // ============================================
  75. function installSkills() {
  76. const skillsDir = path.join(SRC_DIR, 'skills');
  77. if (!fs.existsSync(skillsDir)) {
  78. console.log(' ⚠️ skills/ 目录不存在,跳过');
  79. return { installed: 0, files: 0 };
  80. }
  81. const skills = fs.readdirSync(skillsDir).filter(d => {
  82. const p = path.join(skillsDir, d);
  83. return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md'));
  84. }).sort();
  85. console.log(` 共 ${skills.length} 个技能 → ${SKILLS_TARGET}`);
  86. console.log('');
  87. let fileCount = 0;
  88. for (const skill of skills) {
  89. const srcSkillDir = path.join(skillsDir, skill);
  90. const destSkillDir = path.join(SKILLS_TARGET, skill);
  91. const files = fs.readdirSync(srcSkillDir).filter(f =>
  92. fs.statSync(path.join(srcSkillDir, f)).isFile()
  93. );
  94. for (const f of files) {
  95. copyFile(path.join(srcSkillDir, f), path.join(destSkillDir, f));
  96. fileCount++;
  97. }
  98. process.stdout.write(` ✅ ${skill}\n`);
  99. }
  100. return { installed: skills.length, files: fileCount };
  101. }
  102. function listSkills() {
  103. const skillsDir = path.join(SRC_DIR, 'skills');
  104. if (!fs.existsSync(skillsDir)) return;
  105. const skills = fs.readdirSync(skillsDir).filter(d => {
  106. const p = path.join(skillsDir, d);
  107. return fs.statSync(p).isDirectory() && fs.existsSync(path.join(p, 'SKILL.md'));
  108. }).sort();
  109. console.log(` Skills (${skills.length}):`);
  110. for (const s of skills) {
  111. const files = fs.readdirSync(path.join(skillsDir, s)).filter(f =>
  112. fs.statSync(path.join(skillsDir, s, f)).isFile()
  113. );
  114. const totalSize = files.reduce((sum, f) =>
  115. sum + fs.statSync(path.join(skillsDir, s, f)).size, 0
  116. );
  117. console.log(` ${s.padEnd(38)} ${formatSize(totalSize).padStart(8)} [${files.join(', ')}]`);
  118. }
  119. console.log('');
  120. }
  121. // ============================================
  122. // Workshop 安装
  123. // ============================================
  124. function installWorkshop() {
  125. const workshopDir = path.join(SRC_DIR, 'workshop');
  126. if (!fs.existsSync(workshopDir)) {
  127. console.log(' ⚠️ workshop/ 目录不存在,跳过');
  128. return { installed: 0 };
  129. }
  130. let count = 0;
  131. // Playbooks + supporting docs
  132. const mdFiles = fs.readdirSync(workshopDir).filter(f =>
  133. f.endsWith('.md') && fs.statSync(path.join(workshopDir, f)).isFile()
  134. );
  135. console.log(` Playbooks → ${WORKSPACE_TARGET}`);
  136. for (const f of mdFiles) {
  137. copyFile(path.join(workshopDir, f), path.join(WORKSPACE_TARGET, f));
  138. const size = formatSize(fs.statSync(path.join(workshopDir, f)).size);
  139. console.log(` ✅ ${f} (${size})`);
  140. count++;
  141. }
  142. console.log('');
  143. // Memory templates
  144. const memDir = path.join(workshopDir, 'memory-templates');
  145. if (fs.existsSync(memDir)) {
  146. const jsonFiles = fs.readdirSync(memDir).filter(f =>
  147. f.endsWith('.json') && fs.statSync(path.join(memDir, f)).isFile()
  148. );
  149. console.log(` Memory Templates → ${MEMORY_TARGET}`);
  150. for (const f of jsonFiles) {
  151. copyFile(path.join(memDir, f), path.join(MEMORY_TARGET, f));
  152. const size = formatSize(fs.statSync(path.join(memDir, f)).size);
  153. console.log(` ✅ ${f} (${size})`);
  154. count++;
  155. }
  156. console.log('');
  157. }
  158. return { installed: count };
  159. }
  160. function listWorkshop() {
  161. const workshopDir = path.join(SRC_DIR, 'workshop');
  162. if (!fs.existsSync(workshopDir)) return;
  163. const mdFiles = fs.readdirSync(workshopDir).filter(f =>
  164. f.endsWith('.md') && fs.statSync(path.join(workshopDir, f)).isFile()
  165. );
  166. console.log(` Workshop Playbooks (${mdFiles.length}):`);
  167. for (const f of mdFiles) {
  168. const size = formatSize(fs.statSync(path.join(workshopDir, f)).size);
  169. console.log(` ${f.padEnd(42)} ${size}`);
  170. }
  171. const memDir = path.join(workshopDir, 'memory-templates');
  172. if (fs.existsSync(memDir)) {
  173. const jsonFiles = fs.readdirSync(memDir).filter(f =>
  174. f.endsWith('.json') && fs.statSync(path.join(memDir, f)).isFile()
  175. );
  176. console.log(` Memory Templates (${jsonFiles.length}):`);
  177. for (const f of jsonFiles) {
  178. const size = formatSize(fs.statSync(path.join(memDir, f)).size);
  179. console.log(` ${f.padEnd(42)} ${size}`);
  180. }
  181. }
  182. console.log('');
  183. }
  184. // ============================================
  185. // 工具安装
  186. // ============================================
  187. function installTools() {
  188. let count = 0;
  189. const packagedToolsDir = path.join(SRC_DIR, 'tools');
  190. if (fs.existsSync(packagedToolsDir)) {
  191. count += copyDirRecursive(packagedToolsDir, TOOLS_TARGET);
  192. const mirrorCount = copyDirRecursive(packagedToolsDir, WORKSPACE_TOOLS_TARGET);
  193. console.log(` ✅ tools/* → ${TOOLS_TARGET} (${count} files)`);
  194. console.log(` ✅ tools/* → ${WORKSPACE_TOOLS_TARGET} (${mirrorCount} files)`);
  195. return count;
  196. }
  197. const legacyTools = ['set-voc-token.js', 'voc-token-preflight.js'];
  198. for (const name of legacyTools) {
  199. const src = path.join(SRC_DIR, name);
  200. if (!fs.existsSync(src)) {
  201. console.log(` ⚠️ ${name} 不存在,跳过`);
  202. continue;
  203. }
  204. copyFile(src, path.join(TOOLS_TARGET, name));
  205. copyFile(src, path.join(WORKSPACE_TOOLS_TARGET, name));
  206. console.log(` ✅ ${name}`);
  207. count++;
  208. }
  209. return count;
  210. }
  211. function installCredentialsTemplate() {
  212. if (fs.existsSync(CREDENTIALS_PATH)) {
  213. // 不覆盖已有的凭证文件
  214. console.log(` ⏭️ ${CREDENTIALS_PATH} 已存在,跳过`);
  215. return;
  216. }
  217. const template = {
  218. _comment: "VOC-AI Token 配置文件。充值登录后将获得的 session token 填入 vocToken 字段。",
  219. _format: "Token 格式为 r:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(32位十六进制)",
  220. _setup: "运行 node ~/.openclaw/tools/set-voc-token.js <session-token> 自动写入",
  221. _payment: "充值页面: https://app.fmode.cn/dev/apig-pay/?apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF",
  222. vocToken: ""
  223. };
  224. if (!DRY_RUN) {
  225. ensureDir(OPENCLAW_DIR);
  226. fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(template, null, 2) + '\n', 'utf-8');
  227. }
  228. console.log(` ✅ 凭证模板 → ${CREDENTIALS_PATH}`);
  229. }
  230. // ============================================
  231. // Help
  232. // ============================================
  233. function showHelp() {
  234. console.log(`
  235. OpenClaw 全量安装脚本 v${VERSION}
  236. Usage:
  237. node install-all.js [options]
  238. Options:
  239. --list, -l 列出包含内容
  240. --dry-run 预览模式
  241. --skills-only 只安装 Skills
  242. --workshop-only 只安装 Workshop
  243. --help, -h 显示帮助
  244. 安装内容:
  245. Skills → ~/.openclaw/skills/ (技能文件)
  246. Workshop → ~/.openclaw/workspace/ (工作坊 playbooks)
  247. Memory → ~/.openclaw/workspace/memory/(记忆模板)
  248. Tools → ~/.openclaw/tools/ (本地脚本工具)
  249. Tool mirror → ~/.openclaw/workspace/scripts/tools/ (Skill 相对路径执行)
  250. Credentials → ~/.openclaw/voc-credentials.json (Token 配置模板)
  251. Token 设置流程:
  252. 1. 打开充值页面: https://app.fmode.cn/dev/apig-pay/?apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF
  253. 2. 登录并充值
  254. 3. 获取 session token (格式: r:xxxx...)
  255. 4. 运行: node ~/.openclaw/tools/set-voc-token.js <your-token>
  256. 5. 重启 OpenClaw gateway
  257. `);
  258. }
  259. // ============================================
  260. // Main
  261. // ============================================
  262. function main() {
  263. if (HELP) { showHelp(); return; }
  264. console.log('');
  265. console.log('╔══════════════════════════════════════════════════╗');
  266. console.log(`║ OpenClaw 全量安装脚本 v${VERSION} ║`);
  267. console.log('╚══════════════════════════════════════════════════╝');
  268. if (DRY_RUN) console.log(' 📌 预览模式');
  269. console.log('');
  270. // ── List mode ──
  271. if (LIST_ONLY) {
  272. if (!WORKSHOP_ONLY) listSkills();
  273. if (!SKILLS_ONLY) listWorkshop();
  274. return;
  275. }
  276. // ── Install ──
  277. let totalSkills = 0;
  278. let totalFiles = 0;
  279. let totalWorkshop = 0;
  280. let totalTools = 0;
  281. // Tools + Credentials(总是安装)
  282. console.log('── Token 工具 + 凭证 ──');
  283. totalTools = installTools();
  284. installCredentialsTemplate();
  285. console.log('');
  286. // Skills
  287. if (!WORKSHOP_ONLY) {
  288. console.log('── Skills ──');
  289. const sr = installSkills();
  290. totalSkills = sr.installed;
  291. totalFiles = sr.files;
  292. console.log('');
  293. }
  294. // Workshop
  295. if (!SKILLS_ONLY) {
  296. console.log('── Workshop ──');
  297. const wr = installWorkshop();
  298. totalWorkshop = wr.installed;
  299. }
  300. // Summary
  301. console.log('══════════════════════════════════════════════════');
  302. const prefix = DRY_RUN ? '[预览] ' : '';
  303. console.log(`${prefix}安装完成!`);
  304. if (!WORKSHOP_ONLY) console.log(` 🔧 Skills: ${totalSkills} 个技能 (${totalFiles} 个文件) → ${SKILLS_TARGET}`);
  305. if (!SKILLS_ONLY) console.log(` 🦐 Workshop: ${totalWorkshop} 个文件 → ${WORKSPACE_TARGET}`);
  306. console.log(` 🔑 Tools: ${totalTools} 个文件 → ${TOOLS_TARGET}`);
  307. console.log(` 🧰 Tool mirror → ${WORKSPACE_TOOLS_TARGET}`);
  308. console.log(` 📄 凭证配置 → ${CREDENTIALS_PATH}`);
  309. console.log('');
  310. if (!DRY_RUN) {
  311. console.log('📋 下一步:');
  312. console.log(' 1. 设置 Token(如果还没有):');
  313. console.log(' 打开 https://app.fmode.cn/dev/apig-pay/?apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF');
  314. console.log(' 登录充值后获取 session token,运行:');
  315. console.log(' node ~/.openclaw/tools/set-voc-token.js <your-session-token>');
  316. console.log('');
  317. console.log(' 2. 重启 OpenClaw gateway 加载新文件');
  318. console.log('');
  319. }
  320. }
  321. main();