From b49063879c4baf3a033fba1f15d3efb1103e4f82 Mon Sep 17 00:00:00 2001 From: ivanberry Date: Mon, 16 Mar 2026 20:06:12 +0800 Subject: [PATCH] feat: hooks-transforms module with skill-install transform --- SKILL.md | 39 ++++++++++----------- bun.lock | 15 ++++++++ package.json | 2 +- scripts/run.ts | 46 +++++------------------- src/index.ts | 94 +++++++++++++++++++++++++++++++++----------------- 5 files changed, 106 insertions(+), 90 deletions(-) create mode 100644 bun.lock diff --git a/SKILL.md b/SKILL.md index e895188..a2170ae 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,26 +1,25 @@ --- -name: my-skill -description: "TODO: describe what this skill does and when to use it." +name: hooks-transforms +description: "OpenClaw hooks transform modules for webhook payload processing" --- - -# my-skill - -TODO: one-line description. - > Auth (CLIENT_KEY) is loaded automatically from `~/.openclaw/.env`. -## Run +## Transforms -```bash -bun scripts/run.ts [args] [--dry-run] +### skill-install + +Transform for `skill-update` webhook events. Processes payload containing skill git repos and generates installation instructions. + +**Expected payload:** +```json +{ + "skills": [ + { + "repo_url": "http://...", + "skill_slug": "my-skill", + "git_ref": "v1.0.0", + "repo_subpath": "optional/subpath" + } + ] +} ``` - -## Commands - -| Command | Description | -|---------|-------------| -| `run ` | TODO: describe | - -## Output - -Returns JSON: `{ "status": "success" | "failed", "data": ... }` diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..b0cc2e6 --- /dev/null +++ b/bun.lock @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "hooks-transforms", + "dependencies": { + "@clawd/auth-runtime": "git+http://192.168.0.108:3030/agent-skills/auth-runtime.git", + }, + }, + }, + "packages": { + "@clawd/auth-runtime": ["@clawd/auth-runtime@git+http://192.168.0.108:3030/agent-skills/auth-runtime.git#70cf86889eecbe9c4649bb072cd971c3a560e889", {}, "70cf86889eecbe9c4649bb072cd971c3a560e889"], + } +} diff --git a/package.json b/package.json index 5fd4e73..eb74894 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "my-skill", + "name": "hooks-transforms", "version": "0.1.0", "type": "module", "scripts": { diff --git a/scripts/run.ts b/scripts/run.ts index f2dfab8..224f10f 100644 --- a/scripts/run.ts +++ b/scripts/run.ts @@ -1,41 +1,13 @@ #!/usr/bin/env bun -import type { Command } from '../src/index.ts'; -import { run } from '../src/index.ts'; +import { run } from "../src/index.ts"; -function printUsage(): void { - console.error(`Usage: - bun scripts/run.ts [--api-base=] [args...] [--dry-run] +const args = process.argv.slice(2); +const command = args[0] as "skill-install"; +const commandArgs = args.slice(1); -Commands: - run +/* +Usage: + bun run scripts/run.ts skill-install '{"skills":[{"repo_url":"http://...","skill_slug":"my-skill"}]}' +*/ -Config: ~/.openclaw/.env (CLIENT_KEY, API_BASE) -`); -} - -async function main(): Promise { - const positionals: string[] = []; - let dryRun = false; - - for (const arg of process.argv.slice(2)) { - if (arg === '--dry-run') { - dryRun = true; - } else if (arg.startsWith('--api-base=')) { - process.env.API_BASE = arg.slice('--api-base='.length).trim(); - } else if (arg === '-h' || arg === '--help') { - printUsage(); process.exit(0); - } else { - positionals.push(arg); - } - } - - if (positionals.length < 1) { printUsage(); process.exit(1); } - - const result = await run(positionals[0] as Command, positionals.slice(1), dryRun); - console.log(JSON.stringify(result, null, 2)); -} - -main().catch((err) => { - console.error(JSON.stringify({ status: 'failed', error: err instanceof Error ? err.message : String(err) }, null, 2)); - process.exit(1); -}); +await run(command, commandArgs, false); diff --git a/src/index.ts b/src/index.ts index 086be5f..e08a1a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,42 +1,72 @@ -import { - createEnvConfig, - requestApiWithAutoRefresh, - type ApiResponse, -} from '@clawd/auth-runtime'; +import { createEnvConfig, requestApiWithAutoRefresh } from '@clawd/auth-runtime'; -export type Command = 'run'; // TODO: add your commands +export type Command = 'skill-install'; -export interface RunResult { - status: 'success' | 'failed'; - command: Command; - dryRun: boolean; - data?: unknown; - error?: string; +interface SkillEntry { + skill_id?: string; + skill_slug?: string; + skill_name?: string; + repo_url?: string; + repo_subpath?: string; + git_ref?: string; + commit_sha?: string; } -export async function run( - command: Command, - args: string[], - dryRun: boolean, -): Promise { +export async function run(command: Command, args: string[], dryRun: boolean) { const config = createEnvConfig(); - const apiBase = (process.env.API_BASE ?? 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''); + + if (command === 'skill-install') { + const payload = args[0] ? JSON.parse(args[0]) : {}; + const result = transformSkillInstall(payload); + console.log(result.message); + return result; + } +} - if (command === 'run') { - const response: ApiResponse = await requestApiWithAutoRefresh( - 'POST', - `${apiBase}/your/endpoint`, - dryRun, - config, - JSON.stringify({ param: args[0] }), - ); +function transformSkillInstall(payload: Record) { + const body = (payload.payload ?? payload) as Record; + const skills = body.skills as SkillEntry[] | undefined; - if (response.status < 200 || response.status >= 300) { - return { status: 'failed', command, dryRun, error: `HTTP ${response.status}: ${response.body}` }; - } - - return { status: 'success', command, dryRun, data: JSON.parse(response.body) }; + if (!skills || skills.length === 0) { + return { + message: "skill-update webhook received but no `skills` array in payload. Nothing to install.", + }; } - return { status: 'failed', command, dryRun, error: `unknown command: ${command}` }; + const lines: string[] = [ + `Install/update ${skills.length} skill(s) from git. Execute all steps directly — do not ask questions.`, + ``, + ]; + + for (const skill of skills) { + const { repo_url, skill_slug, skill_name, git_ref, repo_subpath } = skill; + + if (!repo_url) { + lines.push(`⚠️ Skipping skill "${skill_slug ?? "unknown"}" — missing repo_url.`, ``); + continue; + } + + const dirName = skill_slug ?? repo_url.split("/").pop()?.replace(/\.git$/, "") ?? "unknown-skill"; + const installPath = `~/clawd/skills/${dirName}`; + const refFlag = git_ref ? ` --branch ${git_ref}` : ""; + const subpath = repo_subpath ? `/${repo_subpath}` : ""; + const skillDir = repo_subpath ? `${installPath}/${repo_subpath}` : installPath; + + lines.push( + `── Skill: ${skill_name ?? dirName} (${git_ref ?? "default branch"})`, + ` Repo: ${repo_url}`, + ` Target: ${skillDir}`, + ``, + ` Steps:`, + ` 1. If ${installPath} exists: cd ${installPath} && git fetch --tags && git checkout ${git_ref ?? "HEAD"}`, + ` Otherwise: git clone ${repo_url} ${installPath} && cd ${installPath} && git checkout ${git_ref ?? "HEAD"}`, + ` 2. cd ${skillDir}`, + ` 3. If install.sh exists: bash install.sh`, + ` Otherwise: bun install`, + ` 4. Reply with one line: installed/updated + skill name + version.`, + ``, + ); + } + + return { message: lines.join("\n") }; }