| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- #!/usr/bin/env node
- /**
- * OpenClaw WeChat Skill Deploy v1.2.0 (Node.js cross-platform)
- *
- * Usage:
- * node deploy-to-openclaw.js # normal deploy
- * node deploy-to-openclaw.js --dry-run # preview only
- * node deploy-to-openclaw.js --skills-root=/custom/path
- * node deploy-to-openclaw.js --source-root=/path/to/source
- */
- 'use strict';
- const fs = require('fs');
- const path = require('path');
- const os = require('os');
- // ---- ANSI color helpers (fallback to plain text if not TTY) ----
- const useColor = process.stdout.isTTY;
- const c = (code, s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : s);
- const cyan = (s) => c('36', s);
- const green = (s) => c('32', s);
- const yellow = (s) => c('33', s);
- const red = (s) => c('31', s);
- // ---- Parse CLI args ----
- function parseArgs(argv) {
- const args = { dryRun: false, skillsRoot: null, sourceRoot: null };
- for (const a of argv) {
- if (a === '--dry-run' || a === '-DryRun') args.dryRun = true;
- else if (a.startsWith('--skills-root=')) args.skillsRoot = a.slice('--skills-root='.length);
- else if (a.startsWith('--source-root=')) args.sourceRoot = a.slice('--source-root='.length);
- }
- return args;
- }
- const opts = parseArgs(process.argv.slice(2));
- const sourceRoot = path.resolve(opts.sourceRoot || __dirname);
- const skillsRoot = path.resolve(
- opts.skillsRoot || path.join(os.homedir(), '.openclaw', 'skills')
- );
- const dryRun = opts.dryRun;
- const categories = ['wechat'];
- // ---- FS helpers ----
- function exists(p) {
- try { fs.accessSync(p); return true; } catch { return false; }
- }
- function mkdirp(p) {
- fs.mkdirSync(p, { recursive: true });
- }
- function rmrf(p) {
- fs.rmSync(p, { recursive: true, force: true });
- }
- function copyFile(src, dst) {
- fs.copyFileSync(src, dst);
- }
- function listDirs(p) {
- return fs.readdirSync(p, { withFileTypes: true })
- .filter(d => d.isDirectory())
- .map(d => ({ name: d.name, fullPath: path.join(p, d.name) }));
- }
- function listFiles(p) {
- return fs.readdirSync(p, { withFileTypes: true })
- .filter(d => d.isFile())
- .map(d => ({ name: d.name, fullPath: path.join(p, d.name) }));
- }
- // ---- Main ----
- console.log('=== OpenClaw WeChat Skill Deploy v1.2.0 ===');
- console.log('Source: ' + sourceRoot);
- console.log('Target: ' + skillsRoot);
- if (dryRun) console.log(yellow('[DRY RUN]'));
- if (!dryRun && !exists(skillsRoot)) mkdirp(skillsRoot);
- let deployed = 0, skipped = 0, errors = 0;
- for (const cat of categories) {
- const catPath = path.join(sourceRoot, cat);
- if (!exists(catPath)) continue;
- console.log(cyan('--- ' + cat + ' ---'));
- for (const skillDir of listDirs(catPath)) {
- const sm = path.join(skillDir.fullPath, 'SKILL.md');
- if (!exists(sm)) { skipped++; continue; }
- // api-config.json is optional reference material in v1.1.0+
- const ac = path.join(skillDir.fullPath, 'api-config.json');
- if (exists(ac)) {
- try { JSON.parse(fs.readFileSync(ac, 'utf8')); }
- catch (e) {
- console.log(red(' [ERR] ' + skillDir.name + ': invalid api-config.json'));
- errors++; continue;
- }
- }
- const dest = path.join(skillsRoot, skillDir.name);
- if (dryRun) {
- console.log(' [' + (exists(dest) ? 'UPDATE' : 'NEW') + '] ' + skillDir.name);
- } else {
- if (exists(dest)) rmrf(dest);
- mkdirp(dest);
- for (const f of listFiles(skillDir.fullPath)) {
- copyFile(f.fullPath, path.join(dest, f.name));
- }
- console.log(green(' [OK] ' + skillDir.name));
- }
- deployed++;
- }
- }
- console.log(`Result: ${deployed} deployed, ${skipped} skipped, ${errors} errors`);
- // ---- Deploy workflows ----
- const wfSource = path.join(sourceRoot, 'workflows');
- const wfTarget = path.join(path.dirname(skillsRoot), 'workflows');
- if (exists(wfSource)) {
- console.log(cyan('--- workflows ---'));
- if (!dryRun) {
- if (!exists(wfTarget)) mkdirp(wfTarget);
- for (const f of listFiles(wfSource)) {
- copyFile(f.fullPath, path.join(wfTarget, f.name));
- console.log(green(' [OK] ' + f.name));
- }
- } else {
- for (const f of listFiles(wfSource)) {
- console.log(' [WORKFLOW] ' + f.name);
- }
- }
- }
- // ---- Credentials template & default file ----
- const openclawDir = path.dirname(skillsRoot);
- if (!dryRun) {
- const tpl = path.join(sourceRoot, '__config', 'wechat-credentials.template.json');
- const dst = path.join(openclawDir, 'wechat-credentials.template.json');
- if (exists(tpl)) {
- copyFile(tpl, dst);
- console.log('Template: ' + dst);
- const credFile = path.join(openclawDir, 'wechat-credentials.json');
- if (!exists(credFile)) {
- copyFile(tpl, credFile);
- console.log(yellow('[!] Created ' + credFile + ' - please edit wechatApiBase to your server address'));
- }
- }
- }
- // ---- Auto-reply daemon + config ----
- const daemonSrc = path.join(sourceRoot, 'daemon', 'auto-reply-daemon.js');
- const daemonDst = path.join(openclawDir, 'auto-reply-daemon.js');
- const cfgTplSrc = path.join(sourceRoot, 'daemon', 'wechat-auto-reply-config.template.json');
- const cfgTplDst = path.join(openclawDir, 'wechat-auto-reply-config.template.json');
- const cfgDst = path.join(openclawDir, 'wechat-auto-reply-config.json');
- if (exists(daemonSrc)) {
- console.log(cyan('--- auto-reply daemon ---'));
- if (dryRun) {
- console.log(' [DAEMON] ' + daemonDst);
- console.log(' [CONFIG-TEMPLATE] ' + cfgTplDst);
- if (!exists(cfgDst)) console.log(' [CONFIG-DEFAULT] ' + cfgDst);
- } else {
- copyFile(daemonSrc, daemonDst);
- try { fs.chmodSync(daemonDst, 0o755); } catch {}
- console.log(green(' [OK] ' + daemonDst));
- if (exists(cfgTplSrc)) {
- copyFile(cfgTplSrc, cfgTplDst);
- console.log(green(' [OK] ' + cfgTplDst));
- if (!exists(cfgDst)) {
- copyFile(cfgTplSrc, cfgDst);
- console.log(yellow(' [!] Created default config: ' + cfgDst + ' (edit to customize reply rules)'));
- }
- }
- }
- }
- if (!dryRun) console.log(green('Done!'));
- process.exit(errors > 0 ? 1 : 0);
|