From fd018ccd6a47d700af030da6552b68ffa28d4397 Mon Sep 17 00:00:00 2001 From: ywkj Date: Mon, 20 Apr 2026 07:40:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5=20Hook=20=E9=81=A5?= =?UTF-8?q?=E6=B5=8B=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 与 template-skill 保持一致,每次执行后自动 POST hookUrl 上报执行结果。 Co-Authored-By: Claude Sonnet 4.6 --- scripts/run.ts | 74 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 12 deletions(-) diff --git a/scripts/run.ts b/scripts/run.ts index a5d98b8..56d99ad 100644 --- a/scripts/run.ts +++ b/scripts/run.ts @@ -2,6 +2,9 @@ import { resolve } from 'path'; import type { Command } from '../src/types.ts'; import { run } from '../src/index.ts'; +import { createSkillClient } from '../src/auth-cli.ts'; + +const SKILL_NAME = 'video-product-snapshot'; // Load .env from skill root (does not override existing env vars) loadDotenv(resolve(import.meta.dir, '../.env')); @@ -31,29 +34,34 @@ Commands: detect [options] Extract frames and detect ecommerce product snapshots Options: - --interval= Frame sampling interval (default: 3) - --max-frames= Max frames to analyze (default: 30) + --interval= Frame sampling interval (default: 1) + --max-frames= Max frames to analyze (default: 60) --output-dir= Where to save snapshots (default: next to video) --min-confidence=<0-1> Minimum detection confidence (default: 0.7) - --concurrency= Parallel Vision API calls per chunk (default: 5) search Search for products using an image via the ecom image-search API detect-and-search [options] - Detect best product snapshot from video then run image search + Detect best product snapshot from video then run image search + rerank -Examples: - bun scripts/run.ts detect ./demo.mp4 - bun scripts/run.ts detect ./demo.mp4 --interval=5 --max-frames=20 - bun scripts/run.ts search ./snapshot.jpg - bun scripts/run.ts detect-and-search ./demo.mp4 --min-confidence=0.8 + rerank --image-results= [--description=] [--keyword=] [--top=] + Filter image search results using keyword intersection -Config: ANTHROPIC_API_KEY env var required for detection. - auth-rt in PATH required for search commands. +Config: ~/.openclaw/.env (CLIENT_KEY), skill .env (VISION_API_KEY) `); } +async function reportHook( + hookUrl: string, + hookToken: string | undefined, + payload: object, +): Promise { + const headers: Record = { 'Content-Type': 'application/json' }; + if (hookToken) headers['Authorization'] = `Bearer ${hookToken}`; + await fetch(hookUrl, { method: 'POST', headers, body: JSON.stringify(payload) }); +} + async function main(): Promise { const positionals: string[] = []; let dryRun = false; @@ -72,8 +80,50 @@ async function main(): Promise { if (positionals.length < 1) { printUsage(); process.exit(1); } - const result = await run(positionals[0] as Command, positionals.slice(1), dryRun); + const command = positionals[0] as Command; + + // Exchange CLIENT_KEY for session — gives us hookUrl for telemetry + let hookUrl: string | undefined; + let hookToken: string | undefined; + try { + const client = createSkillClient({ dryRun }); + const session = await client.session(); + hookUrl = session.hookUrl; + hookToken = session.hookToken; + } catch { + // Auth failure is non-fatal for telemetry; skill still runs + } + + const startMs = Date.now(); + let result: Awaited>; + + try { + result = await run(command, positionals.slice(1), dryRun); + } catch (err) { + const error = err instanceof Error ? err.message : String(err); + const failed = { status: 'failed' as const, command, dryRun, error }; + console.log(JSON.stringify(failed, null, 2)); + + if (hookUrl && !dryRun) { + reportHook(hookUrl, hookToken, { + skill: SKILL_NAME, command, status: 'failed', + durationMs: Date.now() - startMs, error, + }).catch(() => {}); + } + process.exit(1); + } + console.log(JSON.stringify(result, null, 2)); + + if (hookUrl && !dryRun) { + reportHook(hookUrl, hookToken, { + skill: SKILL_NAME, + command, + status: result.status, + durationMs: Date.now() - startMs, + error: (result as any).error, + }).catch(() => {}); + } } main().catch((err) => {