Skill: yuchangxu1989-openclaw/openclaw-skill-distill (version 0.2.0)

--- name: skill-distill description: Strip any local project into a clean, publishable agent skill. Scan for hardcoded paths and secrets, scaffold standard structure, validate before publish. Use when you need to distill, extract, generalize, package, or publish a skill from an existing project or codebase. --- # skill-distill Turn any local project into a clean, publishable ClawHub skill. Complementary to `skill-creator` (build from scratch) — `skill-distill` extracts from existing code. ## Tools | Tool | What it does | Command | |------|-------------|---------| | `scan` | Detect hardcoded paths, secrets, local config references | `bash skills/skill-distill/scripts/scan.sh <project-dir>` | | `scaffold` | Generate standard skill directory structure from a project | `bash skills/skill-distill/scripts/scaffold.sh <project-dir> <skill-name>` | | `validate` | Pre-publish check: structure, frontmatter, no local traces | `bash skills/skill-distill/scripts/validate.sh <skill-dir>` | ## Typical Flow ``` scan → fix issues → scaffold → validate → clawhub publish ``` ## Prerequisites - bash, grep (no external dependencies) - `clawhub` CLI for the final publish step --- ## Agent Workflow (below this line is for agent consumption) ### 1. Scan — 扫描本地痕迹 ```bash bash skills/skill-distill/scripts/scan.sh <project-dir> ``` 扫描硬编码路径、敏感信息、本地配置引用。输出问题列表。 ### 2. Fix — 修复发现的问题 根据 scan 输出，逐项修复： - 硬编码路径 → 参数化 / 环境变量 - 敏感信息 → 移除或占位符替换 - 本地配置引用 → 通用化 ### 3. Scaffold — 生成标准骨架 ```bash bash skills/skill-distill/scripts/scaffold.sh <project-dir> <skill-name> ``` 从项目目录生成标准 skill 目录结构。 ### 4. Validate — 发布前校验 ```bash bash skills/skill-distill/scripts/validate.sh <skill-dir> ``` 检查目录结构、frontmatter、无残留本地痕迹。还会检测 frontmatter 的 name/description 是否包含中文字符（ClawHub 要求全英文）。PASS 才可发布。 ### 5. Publish ```bash clawhub publish <skill-dir> ``` ## 检查清单 详见 `references/checklist.md`。

path: references/checklist.md
content: # Skill Distill — 通用化检查清单

发布前逐项确认。全部 PASS 才可 `clawhub publish`。

## 1. 路径参数化
- [ ] 无硬编码绝对路径（系统用户目录、临时目录等）
- [ ] 无硬编码 IP 地址
- [ ] 路径通过参数或环境变量传入

## 2. 敏感信息清除
- [ ] 无 API key / token（sk-、ghp_、xoxb-、AKIA 等）
- [ ] 无 password/secret 赋值
- [ ] 无 .env 文件含真实值
- [ ] 无本地用户名引用

## 3. 依赖声明完整性
- [ ] 外部命令依赖已在 SKILL.md 或 README 中声明
- [ ] 无隐式依赖（脚本中调用但未声明的工具）
- [ ] 如有 package.json / requirements.txt，已包含且干净

## 4. 目录结构规范性
- [ ] SKILL.md 在根目录
- [ ] 脚本在 scripts/
- [ ] 参考资料在 references/
- [ ] 无 .git 目录
- [ ] 无 node_modules 目录
- [ ] 无超大文件（>1MB）

## 5. SKILL.md 质量
- [ ] frontmatter 包含 name 和 description
- [ ] Frontmatter (name, description) must be English-only — no CJK characters
- [ ] description 写清触发场景
- [ ] 工作流步骤完整可执行

## 6. 版本与元数据
- [ ] 如有版本号，已设置合理初始值
- [ ] 无调试/开发阶段残留（console.log、print debug 等）


path: scripts/scaffold.sh
content: #!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'EOF'
Usage: scaffold.sh <source-project-dir> <skill-name>

Generate a standard skill directory from an existing project:
  - Creates skill-name/ with standard structure
  - Copies core files (excludes .git, node_modules, .env, logs/, etc.)
  - Generates SKILL.md skeleton from source README
  - Generates references/checklist.md

Output: path to generated skill directory.
EOF
  exit 1
}

[[ $# -lt 2 ]] && usage
SRC_DIR="$1"
SKILL_NAME="$2"
[[ ! -d "$SRC_DIR" ]] && echo "ERROR: '$SRC_DIR' is not a directory" && exit 1

DEST_DIR="./${SKILL_NAME}"
if [[ -d "$DEST_DIR" ]]; then
  echo "ERROR: '$DEST_DIR' already exists" && exit 1
fi

echo "Scaffolding skill '$SKILL_NAME' from $SRC_DIR ..."

mkdir -p "$DEST_DIR"/{scripts,references}

# Copy core files, excluding noise
rsync -a --exclude='.git' --exclude='node_modules' \
  --exclude='.env' --exclude='.env.*' \
  --exclude='logs/' --exclude='*.log' \
  --exclude='__pycache__' --exclude='.venv' \
  --exclude='.DS_Store' --exclude='Thumbs.db' \
  --exclude='dist/' --exclude='build/' \
  --exclude='.towow/' --exclude='.wow-harness/' \
  "$SRC_DIR/" "$DEST_DIR/"

# Extract description from README if available
DESC=""
for readme in "$SRC_DIR/README.md" "$SRC_DIR/readme.md" "$SRC_DIR/README"; do
  if [[ -f "$readme" ]]; then
    DESC=$(awk 'NR>1 && /^[^#]/ && NF>0 {print; exit}' "$readme")
    break
  fi
done
[[ -z "$DESC" ]] && DESC="TODO: Write a clear description with trigger keywords (e.g. verbs/nouns that match user intent) for when this skill should be activated"

# Generate SKILL.md if not already copied from source
if [[ ! -f "$DEST_DIR/SKILL.md" ]]; then
  cat > "$DEST_DIR/SKILL.md" <<SKILLEOF
---
name: ${SKILL_NAME}
description: ${DESC}
---

# ${SKILL_NAME}

${DESC}

## Usage

TODO: document usage steps.
SKILLEOF
  echo "  Created SKILL.md"
fi

# Generate checklist
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CHECKLIST_SRC="$SCRIPT_DIR/../references/checklist.md"
if [[ -f "$CHECKLIST_SRC" ]]; then
  mkdir -p "$DEST_DIR/references"
  cp "$CHECKLIST_SRC" "$DEST_DIR/references/checklist.md"
  echo "  Copied references/checklist.md"
fi

# Remove .git if rsync somehow included it
rm -rf "$DEST_DIR/.git"

echo ""
echo "Done. Skill scaffolded at: $DEST_DIR"
echo "Next: run scan.sh and validate.sh before publishing."


path: scripts/scan.sh
content: #!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'EOF'
Usage: scan.sh <directory>

Scan a directory for local traces that prevent clean publishing:
  - Hardcoded absolute paths (/root/, /home/, /tmp/)
  - Hardcoded IP addresses
  - API keys / tokens (sk-, ghp_, xoxb-, etc.)
  - Local username references
  - openclaw.json config references
  - Sensitive values in .env files

Output: file:line:match:risk_level
EOF
  exit 1
}

[[ $# -lt 1 ]] && usage
TARGET_DIR="$1"
[[ ! -d "$TARGET_DIR" ]] && echo "ERROR: '$TARGET_DIR' is not a directory" && exit 1

FOUND=0

SELF_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

scan_pattern() {
  local label="$1" pattern="$2" risk="$3"
  while IFS= read -r line; do
    local file="${line%%:*}"
    [[ "$(realpath "$file")" == "$SELF_PATH" ]] && continue
    echo "${line}:${risk}  # ${label}"
    FOUND=1
  done < <(grep -rnI --include='*' -E "$pattern" "$TARGET_DIR" \
    --exclude-dir='.git' --exclude-dir='node_modules' \
    --exclude-dir='__pycache__' --exclude-dir='.venv' \
    --exclude='*.png' --exclude='*.jpg' --exclude='*.ico' \
    2>/dev/null || true)
}

# 1. Hardcoded absolute paths
scan_pattern "hardcoded-path" \
  '(/root/|/home/[a-zA-Z]|/Users/[a-zA-Z]|/tmp/[a-zA-Z])' \
  "HIGH"

# 2. Hardcoded IP addresses (skip 0.0.0.0, 127.0.0.1, common docs)
scan_pattern "hardcoded-ip" \
  '\b(10\.[0-9]+\.[0-9]+\.[0-9]+|172\.(1[6-9]|2[0-9]|3[01])\.[0-9]+\.[0-9]+|192\.168\.[0-9]+\.[0-9]+)\b' \
  "MEDIUM"

# 3. API keys / tokens
scan_pattern "api-key" \
  '(sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|xoxb-[0-9]|xoxp-[0-9]|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35})' \
  "CRITICAL"

# 4. Generic secret patterns (password=, token=, secret= with values)
scan_pattern "secret-assignment" \
  '(password|token|secret|api_key|apikey|access_key)\s*[=:]\s*["\x27]?[a-zA-Z0-9/+]{8,}' \
  "HIGH"

# 5. Local username references
if [[ -n "${USER:-}" ]]; then
  scan_pattern "local-username" \
    "\\b${USER}\\b" \
    "MEDIUM"
fi

# 6. openclaw.json references
scan_pattern "openclaw-config" \
  'openclaw\.json' \
  "LOW"

# 7. .env file sensitive values (scan .env* files specifically)
while IFS= read -r envfile; do
  while IFS= read -r line; do
    echo "${line}:HIGH  # env-secret"
    FOUND=1
  done < <(grep -nE '^[A-Z_]+\s*=\s*.+' "$envfile" \
    | grep -ivE '=(true|false|[0-9]+|""|'\'''\''|\$\{)' 2>/dev/null || true)
done < <(find "$TARGET_DIR" -maxdepth 3 -name '.env*' -not -name '.env.example' \
  -not -path '*/.git/*' 2>/dev/null || true)

# Summary
if [[ "$FOUND" -eq 0 ]]; then
  echo "CLEAN: No local traces found in $TARGET_DIR"
  exit 0
else
  echo ""
  echo "--- scan complete: issues found above ---"
  exit 1
fi


path: scripts/validate.sh
content: #!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

usage() {
  cat <<'EOF'
Usage: validate.sh <skill-directory>

Pre-publish validation for a skill directory. Checks:
  - SKILL.md exists with valid frontmatter (name + description)
  - Directory structure is compliant
  - No hardcoded paths or secrets (via scan.sh)
  - No .git or node_modules directories
  - No oversized files (>1MB)

Output: PASS or FAIL with details.
EOF
  exit 1
}

[[ $# -lt 1 ]] && usage
SKILL_DIR="$1"
[[ ! -d "$SKILL_DIR" ]] && echo "ERROR: '$SKILL_DIR' is not a directory" && exit 1

ERRORS=0

fail() {
  echo "FAIL: $1"
  ERRORS=$((ERRORS + 1))
}

pass() {
  echo "  OK: $1"
}

echo "=== Validating skill: $SKILL_DIR ==="
echo ""

# 1. SKILL.md exists
SKILL_MD="$SKILL_DIR/SKILL.md"
if [[ ! -f "$SKILL_MD" ]]; then
  fail "SKILL.md not found"
else
  pass "SKILL.md exists"

  # Check frontmatter
  if ! head -1 "$SKILL_MD" | grep -q '^---'; then
    fail "SKILL.md missing frontmatter (no opening ---)"
  else
    FRONTMATTER=$(awk 'NR==1{next} /^---$/{exit} {print}' "$SKILL_MD")

    if echo "$FRONTMATTER" | grep -q '^name:'; then
      pass "SKILL.md has 'name' field"
    else
      fail "SKILL.md frontmatter missing 'name'"
    fi

    if echo "$FRONTMATTER" | grep -q '^description:'; then
      pass "SKILL.md has 'description' field"
    else
      fail "SKILL.md frontmatter missing 'description'"
    fi
    # Check frontmatter fields are English-only (no CJK characters)
    NAME_VAL=$(echo "$FRONTMATTER" | grep '^name:' | sed 's/^name:[[:space:]]*//')
    DESC_VAL=$(echo "$FRONTMATTER" | grep '^description:' | sed 's/^description:[[:space:]]*//')
    if echo "${NAME_VAL}${DESC_VAL}" | grep -Pq '[\x{4e00}-\x{9fff}]'; then
      fail "SKILL.md frontmatter contains Chinese characters. Use English only for ClawHub publishing."
    else
      pass "SKILL.md frontmatter is English-only"
    fi
  fi
fi

# 2. No .git directory
if [[ -d "$SKILL_DIR/.git" ]]; then
  fail ".git directory found — must be removed before publish"
else
  pass "No .git directory"
fi

# 3. No node_modules
if [[ -d "$SKILL_DIR/node_modules" ]]; then
  fail "node_modules directory found — must be removed before publish"
else
  pass "No node_modules directory"
fi

# 4. Directory structure compliance (known dirs only)
while IFS= read -r dir; do
  dirname=$(basename "$dir")
  case "$dirname" in
    scripts|references|assets|templates|examples) ;;
    .*) ;; # hidden dirs already checked
    *) fail "Non-standard directory: $dir (allowed: scripts/ references/ assets/ templates/ examples/)" ;;
  esac
done < <(find "$SKILL_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)

# 5. File size check (<1MB each)
while IFS= read -r bigfile; do
  size=$(stat -c%s "$bigfile" 2>/dev/null || echo 0)
  if [[ "$size" -gt 1048576 ]]; then
    fail "Oversized file (>1MB): $bigfile ($(( size / 1024 ))KB)"
  fi
done < <(find "$SKILL_DIR" -type f -not -path '*/.git/*' 2>/dev/null)

# 6. No hardcoded paths / secrets (via scan.sh)
echo ""
echo "--- Running scan.sh ---"
if bash "$SCRIPT_DIR/scan.sh" "$SKILL_DIR" >/dev/null 2>&1; then
  pass "No local traces found (scan.sh clean)"
else
  fail "scan.sh found local traces — run scan.sh manually for details"
fi

# Result
echo ""
echo "========================"
if [[ "$ERRORS" -eq 0 ]]; then
  echo "RESULT: PASS"
  exit 0
else
  echo "RESULT: FAIL ($ERRORS issues)"
  exit 1
fi


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)