gangvy 5 месяцев назад
Родитель
Сommit
25af61b835
5 измененных файлов с 299 добавлено и 6 удалено
  1. 33 3
      README.md
  2. 143 0
      deploy-to-openclaw.js
  3. 8 3
      deploy-to-openclaw.ps1
  4. 115 0
      deploy-to-openclaw.sh
  5. BIN
      dist/openclaw-wechat-skill-v1.0.0.zip

+ 33 - 3
README.md

@@ -15,7 +15,9 @@ openclaw-wechat-skill/
 │   ├── wechat-get-conversations/  # 获取会话列表
 │   ├── wechat-get-contact-list/   # 获取通讯录列表
 │   └── wechat-get-contact-detail/ # 获取联系人详情
-├── deploy-to-openclaw.ps1
+├── deploy-to-openclaw.ps1          # Windows PowerShell (也支持 pwsh 7+)
+├── deploy-to-openclaw.js           # Node.js 跨平台(推荐)
+├── deploy-to-openclaw.sh           # Linux / macOS Bash
 └── README.md
 ```
 
@@ -54,14 +56,42 @@ workflows/
 
 ## 客户部署(一步到位)
 
-```powershell
+提供三种部署方式,**按你的系统任选其一**:
+
+### 方式 1:Node.js(推荐,跨平台)
+
+任何装了 Node.js 的系统(Windows / Linux / macOS)都能直接运行:
+
+```bash
 # 预览(不实际部署)
+node deploy-to-openclaw.js --dry-run
+
+# 正式部署
+node deploy-to-openclaw.js
+```
+
+### 方式 2:Windows PowerShell
+
+```powershell
+# 预览
 .\deploy-to-openclaw.ps1 -DryRun
 
-# 正式部署技能 + workflow + 凭证
+# 正式部署
 .\deploy-to-openclaw.ps1
 ```
 
+> 注意:不要用 `node deploy-to-openclaw.ps1`,`.ps1` 是 PowerShell 脚本不是 JS。
+
+### 方式 3:Linux / macOS Bash
+
+```bash
+chmod +x deploy-to-openclaw.sh
+./deploy-to-openclaw.sh --dry-run   # 预览
+./deploy-to-openclaw.sh              # 正式部署
+```
+
+### 部署产物
+
 运行后自动完成:
 - 6个技能 → `~/.openclaw/skills/`
 - 自动应答编排 → `~/.openclaw/workflows/`

+ 143 - 0
deploy-to-openclaw.js

@@ -0,0 +1,143 @@
+#!/usr/bin/env node
+/**
+ * OpenClaw WeChat Skill Deploy v1.0.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.0.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');
+        const ac = path.join(skillDir.fullPath, 'api-config.json');
+        if (!exists(sm) || !exists(ac)) { skipped++; continue; }
+        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 ----
+if (!dryRun) {
+    const tpl = path.join(sourceRoot, '__config', 'wechat-credentials.template.json');
+    const dst = path.join(path.dirname(skillsRoot), 'wechat-credentials.template.json');
+    if (exists(tpl)) {
+        copyFile(tpl, dst);
+        console.log('Template: ' + dst);
+
+        const credFile = path.join(path.dirname(skillsRoot), 'wechat-credentials.json');
+        if (!exists(credFile)) {
+            copyFile(tpl, credFile);
+            console.log(yellow('[!] Created ' + credFile + ' - please edit wechatApiBase to your server address'));
+        }
+    }
+    console.log(green('Done!'));
+}
+
+process.exit(errors > 0 ? 1 : 0);

+ 8 - 3
deploy-to-openclaw.ps1

@@ -1,9 +1,13 @@
 param(
-    [string]$SkillsRoot = "$env:USERPROFILE\.openclaw\skills",
+    [string]$SkillsRoot,
     [string]$SourceRoot = $PSScriptRoot,
     [switch]$DryRun
 )
 $ErrorActionPreference = "Stop"
+# Cross-platform home dir: $HOME works on Windows PowerShell 5+ and PowerShell 7 (Linux/Mac)
+if (-not $SkillsRoot) {
+    $SkillsRoot = Join-Path (Join-Path $HOME ".openclaw") "skills"
+}
 $categories = @("wechat")
 Write-Host "=== OpenClaw WeChat Skill Deploy v1.0.0 ==="
 Write-Host "Source: $SourceRoot"
@@ -25,7 +29,8 @@ foreach ($cat in $categories) {
         try { $null = Get-Content $ac -Raw -Encoding UTF8 | ConvertFrom-Json } catch { $errors++; continue }
         $dest = Join-Path $SkillsRoot $n
         if ($DryRun) {
-            Write-Host "  [$( if (Test-Path $dest){'UPDATE'}else{'NEW'} )] $n"
+            if (Test-Path $dest) { $tag = 'UPDATE' } else { $tag = 'NEW' }
+            Write-Host "  [$tag] $n"
         } else {
             if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
             New-Item -ItemType Directory -Path $dest -Force | Out-Null
@@ -55,7 +60,7 @@ if (Test-Path $wfSource) {
 
 if (-not $DryRun) {
     # Copy credentials template
-    $tpl = Join-Path $SourceRoot "__config\wechat-credentials.template.json"
+    $tpl = Join-Path (Join-Path $SourceRoot "__config") "wechat-credentials.template.json"
     $dst = Join-Path (Split-Path $SkillsRoot -Parent) "wechat-credentials.template.json"
     if (Test-Path $tpl) { Copy-Item $tpl -Destination $dst -Force; Write-Host "Template: $dst" }
 

+ 115 - 0
deploy-to-openclaw.sh

@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+# OpenClaw WeChat Skill Deploy v1.0.0 (Bash, Linux/Mac)
+#
+# Usage:
+#   ./deploy-to-openclaw.sh              # normal deploy
+#   ./deploy-to-openclaw.sh --dry-run    # preview
+#   SKILLS_ROOT=/custom/path ./deploy-to-openclaw.sh
+
+set -euo pipefail
+
+DRY_RUN=0
+for arg in "$@"; do
+    case "$arg" in
+        --dry-run|-DryRun) DRY_RUN=1 ;;
+    esac
+done
+
+SOURCE_ROOT="${SOURCE_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
+SKILLS_ROOT="${SKILLS_ROOT:-$HOME/.openclaw/skills}"
+CATEGORIES=("wechat")
+
+# Colors (disabled if not TTY)
+if [ -t 1 ]; then
+    CYAN=$'\e[36m'; GREEN=$'\e[32m'; YELLOW=$'\e[33m'; RED=$'\e[31m'; NC=$'\e[0m'
+else
+    CYAN=""; GREEN=""; YELLOW=""; RED=""; NC=""
+fi
+
+echo "=== OpenClaw WeChat Skill Deploy v1.0.0 ==="
+echo "Source: $SOURCE_ROOT"
+echo "Target: $SKILLS_ROOT"
+[ "$DRY_RUN" -eq 1 ] && echo "${YELLOW}[DRY RUN]${NC}"
+
+[ "$DRY_RUN" -eq 0 ] && mkdir -p "$SKILLS_ROOT"
+
+deployed=0; skipped=0; errors=0
+
+# JSON validator: prefer python3, fallback to node, else skip validation
+validate_json() {
+    local file="$1"
+    if command -v python3 >/dev/null 2>&1; then
+        python3 -c "import json,sys; json.load(open(sys.argv[1],encoding='utf-8'))" "$file" >/dev/null 2>&1
+    elif command -v node >/dev/null 2>&1; then
+        node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" "$file" >/dev/null 2>&1
+    else
+        return 0
+    fi
+}
+
+for cat in "${CATEGORIES[@]}"; do
+    cat_path="$SOURCE_ROOT/$cat"
+    [ -d "$cat_path" ] || continue
+    echo "${CYAN}--- $cat ---${NC}"
+    for skill_dir in "$cat_path"/*/; do
+        [ -d "$skill_dir" ] || continue
+        name=$(basename "$skill_dir")
+        sm="$skill_dir/SKILL.md"
+        ac="$skill_dir/api-config.json"
+        if [ ! -f "$sm" ] || [ ! -f "$ac" ]; then
+            skipped=$((skipped+1)); continue
+        fi
+        if ! validate_json "$ac"; then
+            echo "  ${RED}[ERR] $name: invalid api-config.json${NC}"
+            errors=$((errors+1)); continue
+        fi
+        dest="$SKILLS_ROOT/$name"
+        if [ "$DRY_RUN" -eq 1 ]; then
+            if [ -d "$dest" ]; then echo "  [UPDATE] $name"; else echo "  [NEW] $name"; fi
+        else
+            rm -rf "$dest"
+            mkdir -p "$dest"
+            # copy files (non-recursive, skill files only)
+            find "$skill_dir" -maxdepth 1 -type f -exec cp {} "$dest/" \;
+            echo "  ${GREEN}[OK] $name${NC}"
+        fi
+        deployed=$((deployed+1))
+    done
+done
+echo "Result: $deployed deployed, $skipped skipped, $errors errors"
+
+# ---- Workflows ----
+wf_source="$SOURCE_ROOT/workflows"
+wf_target="$(dirname "$SKILLS_ROOT")/workflows"
+if [ -d "$wf_source" ]; then
+    echo "${CYAN}--- workflows ---${NC}"
+    if [ "$DRY_RUN" -eq 0 ]; then
+        mkdir -p "$wf_target"
+        find "$wf_source" -maxdepth 1 -type f | while read -r f; do
+            cp "$f" "$wf_target/"
+            echo "  ${GREEN}[OK] $(basename "$f")${NC}"
+        done
+    else
+        find "$wf_source" -maxdepth 1 -type f | while read -r f; do
+            echo "  [WORKFLOW] $(basename "$f")"
+        done
+    fi
+fi
+
+# ---- Credentials ----
+if [ "$DRY_RUN" -eq 0 ]; then
+    tpl="$SOURCE_ROOT/__config/wechat-credentials.template.json"
+    parent="$(dirname "$SKILLS_ROOT")"
+    if [ -f "$tpl" ]; then
+        cp "$tpl" "$parent/wechat-credentials.template.json"
+        echo "Template: $parent/wechat-credentials.template.json"
+        cred_file="$parent/wechat-credentials.json"
+        if [ ! -f "$cred_file" ]; then
+            cp "$tpl" "$cred_file"
+            echo "${YELLOW}[!] Created $cred_file - please edit wechatApiBase to your server address${NC}"
+        fi
+    fi
+    echo "${GREEN}Done!${NC}"
+fi
+
+exit $([ "$errors" -gt 0 ] && echo 1 || echo 0)

BIN
dist/openclaw-wechat-skill-v1.0.0.zip