| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- #!/usr/bin/env node
- /**
- * OpenClaw WeChat Skill - Deployment Checker v1.2.0
- *
- * Cross-platform diagnostic script. Run this on the customer machine AFTER
- * running deploy-to-openclaw.*, and send the output back for troubleshooting.
- *
- * Usage:
- * node check-deployment.js
- * node check-deployment.js --openclaw-url=http://127.0.0.1:18789
- */
- 'use strict';
- const fs = require('fs');
- const path = require('path');
- const os = require('os');
- const http = require('http');
- const https = require('https');
- // ---- Args ----
- const args = process.argv.slice(2);
- const getArg = (k, d) => {
- const hit = args.find(a => a.startsWith('--' + k + '='));
- return hit ? hit.slice(('--' + k + '=').length) : d;
- };
- const OPENCLAW_URL = getArg('openclaw-url', 'http://127.0.0.1:18789');
- const SKILLS_ROOT = getArg('skills-root', path.join(os.homedir(), '.openclaw', 'skills'));
- // ---- Color helpers ----
- const useColor = process.stdout.isTTY;
- const c = (code, s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : s);
- const green = s => c('32', s);
- const red = s => c('31', s);
- const yellow = s => c('33', s);
- const cyan = s => c('36', s);
- const gray = s => c('90', s);
- const OK = green('[OK] ');
- const ERR = red('[FAIL] ');
- const WARN = yellow('[WARN] ');
- const INFO = cyan('[INFO] ');
- let passed = 0, failed = 0, warned = 0;
- function ok(msg) { console.log(OK + msg); passed++; }
- function fail(msg) { console.log(ERR + msg); failed++; }
- function warn(msg) { console.log(WARN + msg); warned++; }
- function info(msg) { console.log(INFO + msg); }
- function header(s) {
- console.log('\n' + cyan('=== ' + s + ' ==='));
- }
- function exists(p) { try { fs.accessSync(p); return true; } catch { return false; } }
- // ---- HTTP helpers ----
- function httpRequest(url, options = {}, body = null) {
- return new Promise((resolve) => {
- try {
- const u = new URL(url);
- const lib = u.protocol === 'https:' ? https : http;
- const req = lib.request({
- hostname: u.hostname,
- port: u.port || (u.protocol === 'https:' ? 443 : 80),
- path: u.pathname + u.search,
- method: options.method || 'GET',
- headers: options.headers || {},
- timeout: options.timeout || 5000,
- }, (res) => {
- const chunks = [];
- res.on('data', c => chunks.push(c));
- res.on('end', () => {
- resolve({ ok: true, status: res.statusCode, body: Buffer.concat(chunks).toString('utf8') });
- });
- });
- req.on('error', e => resolve({ ok: false, error: e.message }));
- req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'timeout' }); });
- if (body) req.write(body);
- req.end();
- } catch (e) {
- resolve({ ok: false, error: e.message });
- }
- });
- }
- // =================================================================
- // Start checks
- // =================================================================
- console.log(cyan('=== OpenClaw WeChat Skill Deployment Check v1.2.0 ==='));
- info('Platform: ' + process.platform + ' / Node ' + process.version);
- info('HOME: ' + os.homedir());
- info('Skills root: ' + SKILLS_ROOT);
- info('OpenClaw URL: ' + OPENCLAW_URL);
- // -----------------------------------------------------------------
- header('1. Skills directory layout');
- // -----------------------------------------------------------------
- const openclawDir = path.dirname(SKILLS_ROOT);
- if (exists(openclawDir)) ok('~/.openclaw exists: ' + openclawDir);
- else fail('~/.openclaw NOT FOUND — did you run the deploy script?');
- if (exists(SKILLS_ROOT)) ok('skills dir exists: ' + SKILLS_ROOT);
- else fail('skills dir NOT FOUND — deploy script did not create ' + SKILLS_ROOT);
- const expectedSkills = [
- 'wechat-check-online',
- 'wechat-send-text',
- 'wechat-get-messages',
- 'wechat-get-conversations',
- 'wechat-get-contact-list',
- 'wechat-get-contact-detail',
- 'wechat-auto-reply-start',
- 'wechat-auto-reply-stop',
- 'wechat-auto-reply-status',
- ];
- // -----------------------------------------------------------------
- header('2. Each WeChat skill');
- // -----------------------------------------------------------------
- let skillCount = 0;
- for (const name of expectedSkills) {
- const skillDir = path.join(SKILLS_ROOT, name);
- if (!exists(skillDir)) { fail(name + ': directory missing at ' + skillDir); continue; }
- const sm = path.join(skillDir, 'SKILL.md');
- if (!exists(sm)) { fail(name + ': SKILL.md missing'); continue; }
- // Validate SKILL.md format: YAML frontmatter + executable instructions
- const content = fs.readFileSync(sm, 'utf8');
- const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
- if (!fmMatch) { fail(name + ': SKILL.md missing YAML frontmatter'); continue; }
- const fm = fmMatch[1];
- if (!/^name:\s*\S+/m.test(fm)) { fail(name + ': SKILL.md frontmatter missing "name"'); continue; }
- if (!/^description:\s*\S+/m.test(fm)) { fail(name + ': SKILL.md frontmatter missing "description"'); continue; }
- // v1.1.0+: must contain runnable bash snippet so LLM knows how to execute
- const hasBash = /```bash/.test(content);
- if (!hasBash) {
- fail(name + ': SKILL.md has no bash code block — LLM will not know how to execute (v1.0.0 format)');
- continue;
- }
- // HTTP skills (not the daemon control ones) should use curl
- const isControlSkill = /^wechat-auto-reply-(start|stop|status)$/.test(name);
- if (!isControlSkill && !/curl\s/.test(content)) {
- warn(name + ': SKILL.md has bash block but no curl command — please verify');
- }
- // api-config.json is optional in v1.1.0 (reference only)
- const ac = path.join(skillDir, 'api-config.json');
- if (exists(ac)) {
- try { JSON.parse(fs.readFileSync(ac, 'utf8')); }
- catch (e) { warn(name + ': api-config.json is not valid JSON — ' + e.message); }
- }
- ok(name + ' → SKILL.md OK');
- skillCount++;
- }
- info('Valid skills: ' + skillCount + ' / ' + expectedSkills.length);
- // -----------------------------------------------------------------
- header('3. Credentials file');
- // -----------------------------------------------------------------
- const credFile = path.join(openclawDir, 'wechat-credentials.json');
- const tplFile = path.join(openclawDir, 'wechat-credentials.template.json');
- let credUrl = null;
- if (exists(tplFile)) ok('template present: ' + tplFile);
- else warn('template missing: ' + tplFile);
- if (!exists(credFile)) {
- fail('wechat-credentials.json NOT FOUND at ' + credFile);
- } else {
- try {
- const cred = JSON.parse(fs.readFileSync(credFile, 'utf8'));
- if (!cred.wechatApiBase) fail('wechat-credentials.json: missing wechatApiBase field');
- else { ok('wechatApiBase = ' + cred.wechatApiBase); credUrl = cred.wechatApiBase; }
- } catch (e) {
- fail('wechat-credentials.json is NOT valid JSON — ' + e.message);
- }
- }
- // -----------------------------------------------------------------
- header('4. Workflows deployment');
- // -----------------------------------------------------------------
- const wfDir = path.join(openclawDir, 'workflows');
- if (!exists(wfDir)) {
- warn('workflows dir NOT FOUND at ' + wfDir);
- } else {
- const expectedWf = ['wechat-auto-reply.workflow.json', 'pipeline.md'];
- for (const f of expectedWf) {
- if (exists(path.join(wfDir, f))) ok('workflow: ' + f);
- else warn('workflow missing: ' + f);
- }
- }
- // -----------------------------------------------------------------
- header('4b. Auto-reply daemon');
- // -----------------------------------------------------------------
- const daemonFile = path.join(openclawDir, 'auto-reply-daemon.js');
- const cfgFile = path.join(openclawDir, 'wechat-auto-reply-config.json');
- const cfgTpl = path.join(openclawDir, 'wechat-auto-reply-config.template.json');
- const pidFile = path.join(openclawDir, 'wechat-auto-reply.pid');
- const logFile = path.join(openclawDir, 'logs', 'auto-reply.log');
- if (!exists(daemonFile)) fail('auto-reply-daemon.js NOT FOUND at ' + daemonFile);
- else ok('daemon: ' + daemonFile);
- if (!exists(cfgTpl)) warn('config template missing: ' + cfgTpl);
- else ok('config template: ' + cfgTpl);
- if (!exists(cfgFile)) warn('user config missing: ' + cfgFile);
- else {
- try { JSON.parse(fs.readFileSync(cfgFile, 'utf8')); ok('user config valid JSON'); }
- catch (e) { fail('user config is NOT valid JSON — ' + e.message); }
- }
- // Daemon running status
- if (exists(pidFile)) {
- const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
- if (!isFinite(pid)) warn('PID file has non-numeric content');
- else {
- let alive = false;
- try { process.kill(pid, 0); alive = true; } catch {}
- if (alive) ok('daemon is running, PID=' + pid);
- else warn('PID file exists but process ' + pid + ' is dead — remove ' + pidFile + ' then restart via wechat-auto-reply-start');
- }
- } else {
- info('daemon not running (no PID file) — start via wechat-auto-reply-start skill');
- }
- if (exists(logFile)) {
- try {
- const data = fs.readFileSync(logFile, 'utf8');
- const lines = data.trim().split(/\n/);
- const tail = lines.slice(-5);
- info('last 5 log lines:');
- tail.forEach(l => console.log(' ' + gray(l)));
- } catch {}
- }
- // -----------------------------------------------------------------
- header('5. WeChat backend connectivity');
- // -----------------------------------------------------------------
- (async () => {
- if (!credUrl) {
- warn('skip — no wechatApiBase in credentials');
- } else {
- const probeUrl = credUrl.replace(/\/$/, '') + '/login/check-online';
- info('POST ' + probeUrl);
- const r = await httpRequest(probeUrl, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- timeout: 8000,
- }, '{}');
- if (!r.ok) fail('backend unreachable: ' + r.error);
- else if (r.status >= 200 && r.status < 500) {
- ok('backend responded HTTP ' + r.status);
- info('body: ' + (r.body || '').slice(0, 200));
- } else {
- warn('backend responded HTTP ' + r.status);
- info('body: ' + (r.body || '').slice(0, 200));
- }
- }
- // -----------------------------------------------------------------
- header('6. OpenClaw service probe');
- // -----------------------------------------------------------------
- info('probing ' + OPENCLAW_URL);
- const rRoot = await httpRequest(OPENCLAW_URL + '/', { timeout: 4000 });
- if (!rRoot.ok) {
- fail('OpenClaw service unreachable at ' + OPENCLAW_URL + ' — ' + rRoot.error);
- info('If OpenClaw runs on a different host/port, re-run:');
- info(' node check-deployment.js --openclaw-url=http://<host>:<port>');
- } else {
- ok('OpenClaw HTTP reachable (status=' + rRoot.status + ')');
- // Try common skill-listing endpoints (best-effort — unknown API)
- const probeEndpoints = [
- '/api/skills',
- '/api/skill/list',
- '/skills',
- '/api/v1/skills',
- ];
- let hitAny = false;
- for (const ep of probeEndpoints) {
- const r = await httpRequest(OPENCLAW_URL + ep, { timeout: 3000 });
- if (r.ok && r.status === 200 && r.body && r.body.length > 0) {
- const hasWechat = /wechat-/.test(r.body);
- if (hasWechat) {
- ok('skill API ' + ep + ' lists our wechat-* skills');
- } else {
- warn('skill API ' + ep + ' reachable but NO wechat-* found in response');
- info('body (first 300): ' + r.body.slice(0, 300));
- }
- hitAny = true;
- break;
- }
- }
- if (!hitAny) {
- warn('could not auto-detect skill list API — please check OpenClaw UI -> 技能 panel manually');
- }
- }
- // -----------------------------------------------------------------
- header('Summary');
- // -----------------------------------------------------------------
- console.log(`${green('passed')}: ${passed} ${yellow('warnings')}: ${warned} ${red('failed')}: ${failed}`);
- if (failed === 0 && skillCount === expectedSkills.length) {
- console.log('\n' + green('✓ Deployment files look correct.'));
- console.log(gray('If the main agent still does not see wechat-* skills:'));
- console.log(gray(' 1. Restart the OpenClaw service to force skill re-scan'));
- console.log(gray(' 2. In OpenClaw UI -> 技能 panel, confirm wechat-* skills are listed'));
- console.log(gray(' 3. In the agent config, make sure these skills are enabled for the main agent'));
- } else {
- console.log('\n' + red('✗ Deployment has issues — fix the FAIL items above and re-run.'));
- }
- process.exit(failed > 0 ? 1 : 0);
- })();
|