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.
This commit is contained in:
2026-03-13 10:58:30 +08:00
commit 4c966a3ad2
884 changed files with 140761 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
/**
* dispatch-webhook skill
* 發送 Webhook 到 VPS處理重試與錯誤
*/
interface DispatchInput {
target: 'vps-a' | 'vps-b';
payload: Record<string, unknown>;
webhookUrl: string;
webhookToken: string;
timeoutMs?: number;
retries?: number;
}
async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs: number): Promise<Response> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, { ...options, signal: controller.signal });
} finally {
clearTimeout(timer);
}
}
export async function handler(ctx: any) {
const input: DispatchInput = ctx.input || ctx.params;
if (!input.webhookUrl) {
throw new Error(`${input.target.toUpperCase()} Webhook URL 未設定。請在環境變數設定 VPS_A_WEBHOOK_URL 或 VPS_B_WEBHOOK_URL`);
}
if (!input.webhookToken) {
throw new Error(`${input.target.toUpperCase()} Webhook Token 未設定`);
}
const timeoutMs = input.timeoutMs ?? 30000;
const maxRetries = input.retries ?? 3;
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetchWithTimeout(
input.webhookUrl,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${input.webhookToken}`,
'X-OpenClaw-Version': '1.0',
'X-OpenClaw-Task-Id': String(input.payload.task_id || ''),
},
body: JSON.stringify(input.payload),
},
timeoutMs
);
if (response.status === 401) {
throw new Error('Webhook Token 驗證失敗401請確認 VPS_WEBHOOK_TOKEN 設定正確');
}
if (!response.ok && response.status !== 202) {
const body = await response.text().catch(() => '');
throw new Error(`VPS 回應錯誤 ${response.status}${body.slice(0, 200)}`);
}
const result = await response.json().catch(() => ({ status: 'accepted' }));
return {
success: true,
status: result.status || 'accepted',
task_id: input.payload.task_id,
target: input.target,
attempt,
};
} catch (err: any) {
lastError = err;
if (err.message?.includes('401') || err.message?.includes('Token')) {
break; // 認證錯誤不重試
}
if (attempt < maxRetries) {
await new Promise(r => setTimeout(r, 5000 * attempt)); // 指數退避
}
}
}
throw lastError || new Error('Webhook 發送失敗');
}