import JSON5 from "json5"; import { LEGACY_MANIFEST_KEYS, MANIFEST_KEY } from "../compat/legacy-names.js"; import { parseBooleanValue } from "../utils/boolean.js"; export function normalizeStringList(input: unknown): string[] { if (!input) { return []; } if (Array.isArray(input)) { return input.map((value) => String(value).trim()).filter(Boolean); } if (typeof input === "string") { return input .split(",") .map((value) => value.trim()) .filter(Boolean); } return []; } export function getFrontmatterString( frontmatter: Record, key: string, ): string | undefined { const raw = frontmatter[key]; return typeof raw === "string" ? raw : undefined; } export function parseFrontmatterBool(value: string | undefined, fallback: boolean): boolean { const parsed = parseBooleanValue(value); return parsed === undefined ? fallback : parsed; } export function resolveOpenClawManifestBlock(params: { frontmatter: Record; key?: string; }): Record | undefined { const raw = getFrontmatterString(params.frontmatter, params.key ?? "metadata"); if (!raw) { return undefined; } try { const parsed = JSON5.parse(raw); if (!parsed || typeof parsed !== "object") { return undefined; } const manifestKeys = [MANIFEST_KEY, ...LEGACY_MANIFEST_KEYS]; for (const key of manifestKeys) { const candidate = (parsed as Record)[key]; if (candidate && typeof candidate === "object") { return candidate as Record; } } return undefined; } catch { return undefined; } } export type OpenClawManifestRequires = { bins: string[]; anyBins: string[]; env: string[]; config: string[]; }; export function resolveOpenClawManifestRequires( metadataObj: Record, ): OpenClawManifestRequires | undefined { const requiresRaw = typeof metadataObj.requires === "object" && metadataObj.requires !== null ? (metadataObj.requires as Record) : undefined; if (!requiresRaw) { return undefined; } return { bins: normalizeStringList(requiresRaw.bins), anyBins: normalizeStringList(requiresRaw.anyBins), env: normalizeStringList(requiresRaw.env), config: normalizeStringList(requiresRaw.config), }; } export function resolveOpenClawManifestInstall( metadataObj: Record, parseInstallSpec: (input: unknown) => T | undefined, ): T[] { const installRaw = Array.isArray(metadataObj.install) ? (metadataObj.install as unknown[]) : []; return installRaw .map((entry) => parseInstallSpec(entry)) .filter((entry): entry is T => Boolean(entry)); } export function resolveOpenClawManifestOs(metadataObj: Record): string[] { return normalizeStringList(metadataObj.os); } export type ParsedOpenClawManifestInstallBase = { raw: Record; kind: string; id?: string; label?: string; bins?: string[]; }; export function parseOpenClawManifestInstallBase( input: unknown, allowedKinds: readonly string[], ): ParsedOpenClawManifestInstallBase | undefined { if (!input || typeof input !== "object") { return undefined; } const raw = input as Record; const kindRaw = typeof raw.kind === "string" ? raw.kind : typeof raw.type === "string" ? raw.type : ""; const kind = kindRaw.trim().toLowerCase(); if (!allowedKinds.includes(kind)) { return undefined; } const spec: ParsedOpenClawManifestInstallBase = { raw, kind, }; if (typeof raw.id === "string") { spec.id = raw.id; } if (typeof raw.label === "string") { spec.label = raw.label; } const bins = normalizeStringList(raw.bins); if (bins.length > 0) { spec.bins = bins; } return spec; }