Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

166 lines
4.6 KiB
TypeScript

import type { XuluxTemplate } from "@/components/xulux/templates/types";
import { getXuluxHostedTemplatesCatalog } from "@/lib/xulux/templates-catalog";
import {
CONFIG_ROOTS_SCHEMAS,
RULES,
TEMPLATE_LIST_META,
TOOLS_META,
fixedDemoListMeta,
type TemplateToolsMeta,
} from "@/lib/xulux/template-knowledge";
// ---------------------------------------------------------------------------
// MCP catalog serialization
//
// This module shapes the Xulux template catalog and authoring knowledge into
// the MCP-safe payload served by GET /api/xulux/mcp-catalog. It is the single
// source of truth boundary for external MCP agents. Frontend-only fields
// (gradients, category display copy, canvas hints) are intentionally omitted.
// ---------------------------------------------------------------------------
export const XULUX_MCP_CATALOG_VERSION = 1;
export type XuluxMcpCatalogVersion = {
id: string;
entryId: string;
name: string;
description: string;
previewUrl: string;
downloadUrl: string;
};
export type XuluxMcpCatalogTemplate = {
id: string;
templateId: string;
versionId: string | null;
kind: "template" | "example";
name: string;
summary: string;
assistantPlacement: string;
features: string[];
customizable: string[];
versions: XuluxMcpCatalogVersion[];
previewUrl?: string;
downloadUrl?: string;
sandboxBaseUrl?: string;
configRoots?: Record<string, unknown>;
rules?: {
required: string[];
unsupported?: string[];
};
tools?: TemplateToolsMeta;
};
export type XuluxMcpCatalog = {
version: typeof XULUX_MCP_CATALOG_VERSION;
generatedAt: string;
docsOrigin: string;
templates: XuluxMcpCatalogTemplate[];
};
function templateId(t: XuluxTemplate): string {
return t.templateId ?? t.id;
}
function toAbsolute(origin: string, url: string): string {
if (/^https?:\/\//.test(url)) return url;
return `${origin}${url.startsWith("/") ? "" : "/"}${url}`;
}
function serializeFixedDemo(
origin: string,
entry: XuluxTemplate,
): XuluxMcpCatalogTemplate | null {
const meta = fixedDemoListMeta(entry);
if (!meta) return null;
return {
id: entry.id,
templateId: entry.id,
versionId: null,
kind: "example",
name: meta.name,
summary: meta.summary,
assistantPlacement: meta.assistantPlacement,
features: meta.features,
customizable: [],
versions: [],
...(entry.previewUrl
? { previewUrl: toAbsolute(origin, entry.previewUrl) }
: {}),
...(entry.downloadUrl
? { downloadUrl: toAbsolute(origin, entry.downloadUrl) }
: {}),
rules: {
required: [
"This entry is a fixed demo and is not schema-customizable.",
"Preview and download it as-is. Do not pass a config for this entry.",
],
},
};
}
function serializeConfigurableTemplate(
entry: XuluxTemplate,
): XuluxMcpCatalogTemplate | null {
const tid = templateId(entry);
const meta = TEMPLATE_LIST_META[tid];
const configRoots = CONFIG_ROOTS_SCHEMAS[tid];
const toolsMeta = TOOLS_META[tid];
if (!meta || !configRoots || !toolsMeta) return null;
return {
id: tid,
templateId: tid,
versionId: entry.versionId ?? null,
kind: "template",
name: meta.name,
summary: meta.summary,
assistantPlacement: meta.assistantPlacement,
features: meta.features,
customizable: meta.customizable,
versions: (entry.versions ?? []).map((v) => ({
id: v.id,
entryId: `${tid}-${v.id}`,
name: v.title,
description: v.description,
previewUrl: v.previewUrl,
downloadUrl: v.downloadUrl,
})),
...(entry.previewUrl ? { previewUrl: entry.previewUrl } : {}),
...(entry.downloadUrl ? { downloadUrl: entry.downloadUrl } : {}),
...(entry.sandboxBaseUrl ? { sandboxBaseUrl: entry.sandboxBaseUrl } : {}),
configRoots,
rules: { required: RULES[tid] ?? [] },
tools: toolsMeta,
};
}
export function buildXuluxMcpCatalog(origin: string): XuluxMcpCatalog {
const normalizedOrigin = origin.replace(/\/+$/, "");
const { templates } = getXuluxHostedTemplatesCatalog();
const seen = new Set<string>();
const serialized: XuluxMcpCatalogTemplate[] = [];
for (const entry of templates) {
const tid = templateId(entry);
if (seen.has(tid)) continue;
const result =
entry.kind === "example"
? serializeFixedDemo(normalizedOrigin, entry)
: serializeConfigurableTemplate(entry);
if (!result) continue;
seen.add(tid);
serialized.push(result);
}
return {
version: XULUX_MCP_CATALOG_VERSION,
generatedAt: new Date().toISOString(),
docsOrigin: normalizedOrigin,
templates: serialized,
};
}