Files
market/server/routes/og/skill.png.ts
zeekay 661a6a28d5 rebrand: Bot Hub → Hanzo Market, white-label via @luxfi/brand schema
- Repo transferred hanzobot/hub → hanzoai/market (matches owned hanzo.market domain)
- npm package: bothub → @hanzoai/market, packages/bothub → packages/market
- bothub-schema → @hanzoai/market-schema
- CLI binary: bothub → market
- Brand: Bot Hub → Hanzo Market across ~95 user-facing surfaces (README, docs, k8s,
  Dockerfile, public/manifest.json, OG image, CLI banner, site config, theme)
- Brand-neutral white-label: src/brand/ owns BrandConfig + BrandTheme + loader,
  public/brands/<id>.json presets for hanzo, zoo, lux, pars, liquidity, generic
- BRAND_ID env at build picks default; ConfigMap-mounted brand.json overrides at runtime
- Legacy paths retained for compat: ~/.bothub, .bothubignore, /.well-known/bothub.json,
  bothub-theme localStorage key, LEGACY_HOSTS for hub.hanzo.bot/clawhub.ai
- Fixed broken sed-replace artifacts in packages/market/src/cli/botConfig.test.ts
- Drops Clawd/OpenClaw dead brand surface, keeps Clawdis* schema types and
  clawd skill directory paths (backward compat for installed bot products)
2026-05-29 13:46:09 -07:00

104 lines
2.9 KiB
TypeScript

import { initWasm, Resvg } from '@resvg/resvg-wasm'
import { defineEventHandler, getQuery, getRequestHost, setHeader } from 'h3'
import { fetchSkillOgMeta } from '../../og/fetchSkillOgMeta'
import {
FONT_MONO,
FONT_SANS,
getFontBuffers,
getMarkDataUrl,
getResvgWasm,
} from '../../og/ogAssets'
import { buildSkillOgSvg } from '../../og/skillOgSvg'
type OgQuery = {
slug?: string
owner?: string
version?: string
title?: string
description?: string
v?: string
}
let wasmInitPromise: Promise<void> | null = null
function cleanString(value: unknown) {
if (typeof value !== 'string') return ''
return value.trim()
}
function getApiBase(eventHost: string | null) {
const direct = (process.env.VITE_API_URL ?? '').trim()
if (direct) return direct
const site = process.env.SITE_URL?.trim() || process.env.VITE_SITE_URL?.trim()
if (site) return site
if (eventHost) return `https://${eventHost}`
return 'https://hanzo.market'
}
async function ensureWasm() {
if (!wasmInitPromise) {
wasmInitPromise = getResvgWasm().then((wasm) => initWasm(wasm))
}
await wasmInitPromise
}
export default defineEventHandler(async (event) => {
const query = getQuery(event) as OgQuery
const slug = cleanString(query.slug)
if (!slug) {
setHeader(event, 'Content-Type', 'text/plain; charset=utf-8')
return 'Missing `slug` query param.'
}
const ownerFromQuery = cleanString(query.owner)
const versionFromQuery = cleanString(query.version)
const titleFromQuery = cleanString(query.title)
const descriptionFromQuery = cleanString(query.description)
const needFetch = !titleFromQuery || !descriptionFromQuery || !ownerFromQuery || !versionFromQuery
const meta = needFetch ? await fetchSkillOgMeta(slug, getApiBase(getRequestHost(event))) : null
const owner = ownerFromQuery || meta?.owner || ''
const version = versionFromQuery || meta?.version || ''
const title = titleFromQuery || meta?.displayName || slug
const description = descriptionFromQuery || meta?.summary || ''
const ownerLabel = owner ? `@${owner}` : 'market'
const versionLabel = version ? `v${version}` : 'latest'
const footer = owner ? `hanzo.market/${owner}/${slug}` : `hanzo.market/skills/${slug}`
const cacheKey = version ? 'public, max-age=31536000, immutable' : 'public, max-age=3600'
setHeader(event, 'Cache-Control', cacheKey)
setHeader(event, 'Content-Type', 'image/png')
const [markDataUrl, fontBuffers] = await Promise.all([
getMarkDataUrl(),
ensureWasm().then(() => getFontBuffers()),
])
const svg = buildSkillOgSvg({
markDataUrl,
title,
description,
ownerLabel,
versionLabel,
footer,
})
const resvg = new Resvg(svg, {
fitTo: { mode: 'width', value: 1200 },
font: {
fontBuffers,
defaultFontFamily: FONT_SANS,
sansSerifFamily: FONT_SANS,
monospaceFamily: FONT_MONO,
},
})
const png = resvg.render().asPng()
resvg.free()
return png
})