Files
openclaw-skill/create-skill.md
Selig 4c966a3ad2 Initial commit: OpenClaw Skill Collection
6 custom skills (assign-task, dispatch-webhook, daily-briefing,
task-capture, qmd-brain, tts-voice) with technical documentation.
Compatible with Claude Code, OpenClaw, Codex CLI, and OpenCode.
2026-03-13 10:58:30 +08:00

13 KiB
Raw Permalink Blame History

OpenClaw Skill 開發指南

從零開始建立一個 OpenClaw workspace skill 的完整步驟與慣例。 基於 x550v 主機上的實作經驗整理2026-03-02


目錄

  1. 檔案結構
  2. SKILL.md 格式
  3. handler.ts 格式
  4. 觸發機制Triggers
  5. 可用工具Tools
  6. Context 物件
  7. 回傳格式
  8. Skill 間呼叫callSkill
  9. Internal Skill僅供內部呼叫
  10. 安裝與驗證
  11. 實戰範例
  12. 常見踩坑

1. 檔案結構

每個 skill 是一個資料夾,包含兩個檔案:

skill-name/
├── SKILL.md      # 元資料frontmatter+ Markdown 說明
└── handler.ts    # TypeScript 實作
路徑 用途
/home/selig/openclaw-skill/skills/{name}/ 原始碼(版本控管)
~/.openclaw/workspace/skills/{name}/ 安裝位置Gateway 讀取)

資料夾名稱必須與 SKILL.md frontmatter 的 name 欄位一致。


2. SKILL.md 格式

---
name: my-skill                          # 必填和資料夾同名kebab-case
description: 一句話說明功能               # 必填,顯示在 skills list
triggers:                                # 必填,陣列;空陣列 = 使用者不可觸發
  - "關鍵字1"
  - "keyword2"
tools:                                   # 必填,宣告此 skill 可使用的工具
  - exec
  - web_fetch
internal: false                          # 選填true = 隱藏(不顯示、不可由使用者觸發)
---

# My Skill 標題

## 功能說明
詳細描述 skill 做什麼、怎麼做。

## 觸發範例
使用者怎麼說會觸發這個 skill、agent 會怎麼回應。

## 設定(如果需要)
需要哪些環境變數、外部服務、CLI 工具。

Frontmatter 欄位一覽

欄位 必填 型別 說明
name string Skill 識別名kebab-case等同資料夾名
description string 一行描述,顯示在 openclaw skills list
triggers string[] 觸發關鍵字陣列,空陣列 [] 表示僅供內部呼叫
tools string[] 可用工具宣告(exec, web_fetch, web_search, memory
internal boolean 預設 falsetrue 時對使用者隱藏

3. handler.ts 格式

/**
 * my-skill handler
 * 功能簡述
 */

import { execSync } from 'child_process';
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';

export async function handler(ctx: any) {
  const message = ctx.message?.text || ctx.message?.content || '';

  if (!message.trim()) {
    return { reply: '請提供輸入。' };
  }

  // ... 業務邏輯 ...

  return {
    reply: '回覆文字(支援 Markdown',
    metadata: { key: 'value' },
  };
}

重點

  • 必須 export async function handler(ctx: any)
  • 函式名必須是 handler,必須 async必須 export
  • 可使用 Node.js 內建模組(fs, path, child_process, util 等)
  • 不能 import 第三方 npm 套件skill 環境沒有 node_modules
  • 外部依賴用 execSync / exec 呼叫 CLI 或 curl

4. 觸發機制Triggers

OpenClaw 收到使用者訊息時,對 triggers 做 case-insensitive substring match

使用者訊息:「幫我搜尋 nginx 設定」
                     ^^^^
triggers: ["搜尋", "查找", "recall"]
               ↑ 命中 → 啟動 qmd-brain skill

設計觸發詞的原則

原則 說明
中英文兼備 使用者可能用中文或英文,如 ["tts", "文字轉語音"]
避免太短 一兩個字容易誤觸發(如「找」可能在任何對話出現)
避免太泛 「幫我」會攔截大量無關訊息
口語化 使用者自然語言,如「唸出來」、「記一下」
順序無關 只要訊息中包含任一觸發詞即命中

多 skill 觸發衝突

若多個 skill 的 trigger 同時命中agent 會根據上下文選擇最適合的 skill。盡量讓 trigger 夠獨特以避免衝突。


5. 可用工具Tools

在 SKILL.md 的 tools 陣列中宣告OpenClaw 會依此限制 skill 的能力。

工具 用途 handler 中的使用方式
exec 執行 shell 指令 execSync() / exec() from child_process
web_fetch HTTP 請求 fetch(url)execSync('curl ...')
web_search 搜尋引擎 由 agent 呼叫,非 handler 直接使用
memory 對話記憶 ctx.memory.*(需 memory plugin

實務上,大部分 skill 只需要 exec。需要打 API 或抓網頁時加 web_fetch


6. Context 物件

handler 收到的 ctx 結構:

interface SkillContext {
  message: {
    text: string;        // 使用者原始訊息
    content: string;     // 同 text備用欄位
  };
  env: {
    OPENCLAW_WORKSPACE: string;  // workspace 路徑(預設 ~/.openclaw/workspace
    HOME: string;                // 使用者家目錄
    [key: string]: string;       // 其他環境變數
  };
  agent: {
    id: string;          // 當前 agent 名稱(如 "main", "kaiwu"
  };
  callSkill: (name: string, params: any) => Promise<any>;  // 呼叫其他 skill
  memory?: any;          // memory plugin 提供的介面(若有)
}

常用存取模式

// 取得使用者訊息
const message = ctx.message?.text || ctx.message?.content || '';

// 取得 workspace 路徑
const workspace = ctx.env?.OPENCLAW_WORKSPACE || process.env.HOME + '/.openclaw/workspace';

// 讀寫 workspace 檔案
const filePath = join(workspace, 'TODO.md');
if (existsSync(filePath)) {
  const content = readFileSync(filePath, 'utf-8');
}
writeFileSync(filePath, newContent, 'utf-8');

7. 回傳格式

return {
  reply: string;           // 必填:回覆使用者的文字(支援 Markdown
  metadata?: {             // 選填:結構化資料(記錄 / 追蹤用,不顯示給使用者)
    [key: string]: any;
  };
  files?: string[];        // 選填:附件檔案路徑陣列(如產出的音訊、圖片)
};

範例

// 簡單文字回覆
return { reply: '✅ 完成!' };

// 附帶 metadata
return {
  reply: '🧠 搜尋完成',
  metadata: { query: 'nginx', results: 5 },
};

// 附帶檔案
return {
  reply: '🔊 語音合成完成',
  files: ['/tmp/output.wav'],
  metadata: { text: '你好' },
};

8. Skill 間呼叫callSkill

一個 skill 可以呼叫另一個 skill

export async function handler(ctx: any) {
  // 呼叫 dispatch-webhook skill
  const result = await ctx.callSkill('dispatch-webhook', {
    target: 'vps-a',
    payload: { task: '部署新版本' },
    webhookUrl: 'https://vps-a.example.com/webhook',
  });

  return { reply: `派發結果:${result.reply}` };
}

被呼叫的 skill 會收到 ctx.message 為傳入的參數物件。


9. Internal Skill僅供內部呼叫

設定 internal: true + 空 triggers使 skill 對使用者不可見:

---
name: dispatch-webhook
description: 發送 Webhook 到 VPS
triggers: []
tools:
  - web_fetch
  - exec
internal: true
---

用途:底層工具 skill由其他 skill 透過 callSkill 呼叫。


10. 安裝與驗證

從原始碼安裝

# 1. 複製到 workspace
cp -r /home/selig/openclaw-skill/skills/my-skill \
      ~/.openclaw/workspace/skills/

# 2. 確認檔案結構
ls ~/.openclaw/workspace/skills/my-skill/
# 應該看到SKILL.md  handler.ts

# 3. 重啟 Gateway載入新 skill
systemctl --user restart openclaw-gateway

# 4. 確認載入成功
openclaw skills list
# 應該看到 my-skill 狀態為 ✓ ready

更新已安裝的 skill

# 修改原始碼後,重新複製 + 重啟
cp -r /home/selig/openclaw-skill/skills/my-skill \
      ~/.openclaw/workspace/skills/
systemctl --user restart openclaw-gateway

除錯

# 查看 Gateway 日誌skill 載入錯誤會在這裡)
journalctl --user -u openclaw-gateway -f

# 常見問題
# - SKILL.md frontmatter 格式錯誤YAML 語法)
# - handler.ts 語法錯誤TypeScript 編譯失敗)
# - name 和資料夾名不一致
# - tools 未宣告就使用 → 權限被擋

11. 實戰範例

範例 A最簡 Skill純文字處理

task-capture:使用者說「記一下…」→ 解析優先級/截止日/標籤 → 寫入 TODO.md

skills/task-capture/
├── SKILL.md       triggers: ["記住", "記一下", "待辦", "todo", ...]
└── handler.ts     讀寫 workspace/TODO.md

重點技巧:

  • cleanTaskText() 移除觸發詞,提取純任務文字
  • detectPriority() / detectDueDate() / detectTags() 自動分類
  • 直接用 readFileSync / writeFileSync 操作 workspace 檔案

範例 B呼叫外部 API 的 Skill

tts-voice使用者說「tts 你好」→ 呼叫本機 LuxTTS API → 回傳音訊檔

skills/tts-voice/
├── SKILL.md       triggers: ["tts", "文字轉語音", "語音合成", ...]
└── handler.ts     curl → localhost:7860 API

重點技巧:

  • execSync('curl ...') 呼叫 HTTP API無法直接 import axios
  • 認證:先取 cookie 再帶 cookie 呼叫 API
  • 帳密:readFileSync('.env') + regex 解析
  • 長時間操作(合成 ~20 秒):設定 timeout: 120000
  • 回傳 files: ['/tmp/output.wav'] 讓 agent 附送檔案

範例 C外部 CLI 工具 + 多重搜尋引擎

qmd-brain:使用者說「搜尋 nginx」→ 並行 BM25 + pgvector → 整合結果

skills/qmd-brain/
├── SKILL.md       triggers: ["搜尋", "查找", "recall", "知識庫", ...]
└── handler.ts     execAsync → qmd CLI + embed_to_pg.py

重點技巧:

  • promisify(exec) 做非同步 shell 呼叫
  • Promise.all([qmdSearch(), pgSearch()]) 並行多搜尋
  • detectIntent() 判斷使用者意圖(搜尋 / 更新索引 / 統計)
  • extractQuery() 移除觸發詞,提取搜尋關鍵字
  • 結果截斷防止 Telegram 訊息過長

範例 DInternal Skill底層工具

dispatch-webhook:由 assign-task 呼叫,發送 webhook + 重試

skills/dispatch-webhook/
├── SKILL.md       triggers: [], internal: true
└── handler.ts     fetch → VPS webhook endpoint

重點技巧:

  • triggers: [] + internal: true → 使用者看不到
  • 由其他 skill 用 ctx.callSkill('dispatch-webhook', payload) 呼叫
  • 實作重試邏輯、錯誤碼分類、超時處理

12. 常見踩坑

問題 原因 解決
openclaw skills list 看不到新 skill 未重啟 Gateway systemctl --user restart openclaw-gateway
handler.ts 中 import axios 失敗 skill 環境無 node_modules 改用 execSync('curl ...') 或內建 fetch
SKILL.md parse 失敗 frontmatter YAML 語法錯(如缺引號、縮排錯) 用 YAML lint 檢查,字串值加引號
skill name 和資料夾名不一致 Gateway 比對 name ↔ 資料夾名 確保兩者完全相同kebab-case
sudo openclaw skills list 看不到 workspace skills sudo 以 root 跑PATH 不同 改用 openclaw skills list(不加 sudo
trigger 誤觸發無關對話 觸發詞太短/太泛 用更具體的詞,避免單字觸發
ctx.env.OPENCLAW_WORKSPACE undefined 舊版或特殊環境 fallbackprocess.env.HOME + '/.openclaw/workspace'
呼叫 nvm 安裝的 CLI 找不到 Gateway PATH 不含 nvm 路徑 symlink 到 ~/.local/bin/
handler 回傳後 Telegram 沒顯示 reply 欄位為空字串 確保 reply 有內容
.env 讀不到Permission denied 檔案權限 600 但 Gateway 是 selig user selig user service 可以讀自己的 600 檔案,正常不會有問題;確認檔案 owner

快速模板

建立新 skill 時,複製此模板:

SKILL_NAME="my-new-skill"
mkdir -p ~/openclaw-skill/skills/$SKILL_NAME

SKILL.md

---
name: my-new-skill
description: 一句話描述功能
triggers:
  - "觸發詞1"
  - "trigger2"
tools:
  - exec
---

# My New Skill

## 功能說明
做什麼、怎麼觸發。

## 觸發範例
使用者「觸發詞1 某些參數」→ skill 做某事 → 回覆結果

handler.ts

import { execSync } from 'child_process';

const TRIGGER_WORDS = ['觸發詞1', 'trigger2'];

function cleanInput(message: string): string {
  let cleaned = message;
  for (const t of TRIGGER_WORDS) {
    cleaned = cleaned.replace(new RegExp(t, 'gi'), '');
  }
  return cleaned.replace(/^[\s:,]+/, '').trim();
}

export async function handler(ctx: any) {
  const message = ctx.message?.text || ctx.message?.content || '';
  const input = cleanInput(message);

  if (!input) {
    return { reply: '請提供輸入內容。' };
  }

  // TODO: 業務邏輯

  return {
    reply: `✅ 完成:${input}`,
    metadata: { input },
  };
}

安裝

cp -r ~/openclaw-skill/skills/$SKILL_NAME ~/.openclaw/workspace/skills/
systemctl --user restart openclaw-gateway
openclaw skills list  # 確認 ✓ ready