install-all.js 12 KB

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