#!/usr/bin/env bun import { resolve } from 'path'; import type { Command } from '../src/types.ts'; import { run } from '../src/index.ts'; const SKILL_NAME = 'video-product-snapshot'; // Load .env from skill root (does not override existing env vars) loadDotenv(resolve(import.meta.dir, '../.env')); function loadDotenv(path: string): void { let raw: string; try { raw = require('fs').readFileSync(path, 'utf-8'); } catch { return; } for (const line of raw.split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq < 0) continue; const key = trimmed.slice(0, eq).trim(); const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, ''); if (key && !(key in process.env)) process.env[key] = val; } } function printUsage(): void { console.error(`用法: bun scripts/run.ts [--api-base=] [args...] [--dry-run] 命令: session 获取认证 session token detect [options] 从视频抽帧并检测商品画面 选项: --interval=<秒> 抽帧间隔(默认: 1) --max-frames=<数量> 最多分析帧数(默认: 60) --output-dir=<目录> 截图保存目录(默认: 视频所在目录) --min-confidence=<0-1> 最低检测置信度(默认: 0.7) search 用图片搜索商品(调用 ecom image-search API) detect-and-search [options] 检测最佳商品画面 → 图片搜索 → 关键词重排序 detect-best [options] 从视频抽帧并选择最佳商品画面(更快更稳定) detect-best-and-search [options] 最佳画面 → 图片搜索 → 关键词重排序 detect-video 识别商品描述和搜索关键词(当前实现:从视频抽帧选最佳帧) detect-video-and-search 识别商品 → 图片搜索 → 1688 关键词重排序(当前实现:从视频抽帧选最佳帧) rerank --image-results= [--description=] [--keyword=] [--top=] 通过关键词交并集过滤搜索结果 配置文件: ~/.openclaw/.env (CLIENT_KEY), skill 目录 .env (VISION_API_KEY) `); } function reportTelemetry(payload: object): void { const endpoint = process.env.TELEMETRY_ENDPOINT; if (!endpoint) return; fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }).catch(() => {}); } 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 command = positionals[0] as Command; 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); console.log(JSON.stringify({ status: 'failed', command, dryRun, error }, null, 2)); if (!dryRun) reportTelemetry({ skill: SKILL_NAME, command, status: 'failed', durationMs: Date.now() - startMs, error }); process.exit(1); } console.log(JSON.stringify(result, null, 2)); if (!dryRun) reportTelemetry({ skill: SKILL_NAME, command, status: result.status, durationMs: Date.now() - startMs, error: (result as any).error }); } main().catch((err) => { console.error(JSON.stringify({ status: 'failed', error: err instanceof Error ? err.message : String(err), }, null, 2)); process.exit(1); });