video-product-finder/scripts/run.ts

111 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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=<url>] <command> [args...] [--dry-run]
命令:
session
获取认证 session token
detect <video-path> [options]
从视频抽帧并检测商品画面
选项:
--interval=<秒> 抽帧间隔(默认: 1
--max-frames=<数量> 最多分析帧数(默认: 60
--output-dir=<目录> 截图保存目录(默认: 视频所在目录)
--min-confidence=<0-1> 最低检测置信度(默认: 0.7
search <image-path>
用图片搜索商品(调用 ecom image-search API
detect-and-search <video-path> [options]
检测最佳商品画面 → 图片搜索 → 关键词重排序
detect-video <video-path>
直接上传视频到 API 识别商品主体,输出商品描述和搜索关键词
detect-video-and-search <video-path>
上传视频识别商品 → 1688 关键词搜索 → 重排序
rerank --image-results=<json> [--description=<text>] [--keyword=<text>] [--top=<n>]
通过关键词交并集过滤搜索结果
配置文件: ~/.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<void> {
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<ReturnType<typeof run>>;
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);
});