Files
openwork/services/openwork-share/server/_lib/share-utils.ts
ben 6516a8dbf2 feat: restyle share bundle pages and OG previews (#844)
* feat(share): restyle bundle pages and OG previews

* feat(share): refine OG preview layout and bundle page components
2026-03-11 18:30:03 -07:00

717 lines
23 KiB
TypeScript

import { parse as parseYaml } from "yaml";
import type { PreviewItem } from "../../components/share-home-types.ts";
import type {
BundleCounts,
BundleUrls,
Frontmatter,
NormalizedBundle,
NormalizedCommandItem,
NormalizedSkillItem,
OpenInAppUrls,
RequestLike,
ValidationResult,
} from "./types.ts";
export const OPENWORK_SITE_URL = "https://openwork.software";
export const OPENWORK_DOWNLOAD_URL = "https://openwork.software/download";
export const DEFAULT_PUBLIC_BASE_URL = "https://share.openwork.software";
export const DEFAULT_OPENWORK_APP_URL = "https://app.openwork.software";
export const SHARE_EASE = "cubic-bezier(0.31, 0.325, 0, 0.92)";
export function maybeString(value: unknown): string {
return typeof value === "string" ? value : "";
}
export function maybeObject(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
}
export function maybeArray(value: unknown): unknown[] {
return Array.isArray(value) ? value : [];
}
export function getEnv(name: string, fallback = ""): string {
const value = process.env[name];
return typeof value === "string" && value.trim() ? value.trim() : fallback;
}
export function normalizeBaseUrl(input: unknown): string {
return String(input ?? "").trim().replace(/\/+$/, "");
}
export function normalizeAppUrl(input: unknown): string {
return normalizeBaseUrl(input);
}
export function setCors(
res: { setHeader(name: string, value: string): void },
options: { methods?: string; headers?: string } = {},
): void {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", options.methods ?? "GET,POST,OPTIONS");
res.setHeader(
"Access-Control-Allow-Headers",
options.headers ??
"Content-Type,Accept,X-OpenWork-Bundle-Type,X-OpenWork-Schema-Version,X-OpenWork-Name",
);
}
export function readBody(req: NodeJS.ReadableStream): Promise<Buffer> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => resolve(Buffer.concat(chunks)));
req.on("error", reject);
});
}
export function escapeHtml(value: unknown): string {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
export function escapeJsonForScript(rawJson: string): string {
return String(rawJson)
.replaceAll("<", "\\u003c")
.replaceAll(">", "\\u003e")
.replaceAll("\u2028", "\\u2028")
.replaceAll("\u2029", "\\u2029");
}
export function humanizeType(type: unknown): string {
if (!type) return "Bundle";
return String(type)
.split("-")
.filter(Boolean)
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
.join(" ");
}
export function truncate(value: unknown, maxChars = 3200): string {
const text = String(value ?? "");
if (text.length <= maxChars) return text;
return `${text.slice(0, maxChars)}\n\n... (truncated for display)`;
}
function getOrigin(req: RequestLike): string {
const protocolHeader = String(req.headers?.["x-forwarded-proto"] ?? "https")
.split(",")[0]
.trim();
const hostHeader = String(req.headers?.["x-forwarded-host"] ?? req.headers?.host ?? "")
.split(",")[0]
.trim();
if (!hostHeader) {
return normalizeBaseUrl(getEnv("PUBLIC_BASE_URL", DEFAULT_PUBLIC_BASE_URL));
}
return `${protocolHeader || "https"}://${hostHeader}`;
}
export function buildRootUrl(req: RequestLike): string {
return normalizeBaseUrl(getOrigin(req)) || DEFAULT_PUBLIC_BASE_URL;
}
export function buildOgImageUrl(req: RequestLike, targetId = "root"): string {
const origin = buildRootUrl(req);
return `${origin}/og/${encodeURIComponent(targetId)}`;
}
export function buildBundleUrls(req: RequestLike, id: string): BundleUrls {
const encodedId = encodeURIComponent(id);
const origin = buildRootUrl(req);
const path = `/b/${encodedId}`;
return {
shareUrl: `${origin}${path}`,
jsonUrl: `${origin}${path}?format=json`,
downloadUrl: `${origin}${path}?format=json&download=1`,
};
}
export function buildOpenInAppUrls(shareUrl: string, options: { label?: string } = {}): OpenInAppUrls {
const query = new URLSearchParams();
query.set("ow_bundle", shareUrl);
query.set("ow_intent", "new_worker");
query.set("ow_source", "share_service");
const label = String(options.label ?? "").trim();
if (label) query.set("ow_label", label.slice(0, 120));
const openInAppDeepLink = `openwork://import-bundle?${query.toString()}`;
const appUrl = normalizeAppUrl(getEnv("PUBLIC_OPENWORK_APP_URL", DEFAULT_OPENWORK_APP_URL)) || DEFAULT_OPENWORK_APP_URL;
try {
const url = new URL(appUrl);
for (const [key, value] of query.entries()) {
url.searchParams.set(key, value);
}
return {
openInAppDeepLink,
openInWebAppUrl: url.toString(),
};
} catch {
return {
openInAppDeepLink,
openInWebAppUrl: `${DEFAULT_OPENWORK_APP_URL}?${query.toString()}`,
};
}
}
export function wantsJsonResponse(req: RequestLike): boolean {
const format = String(req.query?.format ?? "").trim().toLowerCase();
if (format === "json") return true;
if (format === "html") return false;
const accept = String(req.headers?.accept ?? "").toLowerCase();
if (!accept) return true;
if (accept.includes("application/json")) return true;
if (accept.includes("text/html") || accept.includes("application/xhtml+xml")) return false;
return true;
}
export function wantsDownload(req: RequestLike): boolean {
return String(req.query?.download ?? "").trim() === "1";
}
export function parseFrontmatter(content: unknown): Frontmatter {
const text = String(content ?? "");
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
if (!match) return { data: {}, body: text };
const raw = match[1] ?? "";
let data: Record<string, unknown> = {};
try {
data = maybeObject(parseYaml(raw)) ?? {};
} catch {
data = {};
}
return { data, body: text.slice(match[0].length) };
}
function normalizeSkillItem(value: unknown): NormalizedSkillItem | null {
const record = maybeObject(value);
if (!record) return null;
const name = maybeString(record.name).trim();
const content = maybeString(record.content);
if (!name || !content.trim()) return null;
return {
name,
description: maybeString(record.description).trim(),
trigger: maybeString(record.trigger).trim(),
content,
};
}
function normalizeCommandItem(value: unknown): NormalizedCommandItem | null {
const record = maybeObject(value);
if (!record) return null;
const name = maybeString(record.name).trim();
const template = maybeString(record.template);
const content = maybeString(record.content);
if (!name || (!template.trim() && !content.trim())) return null;
return {
name,
description: maybeString(record.description).trim(),
template,
content,
agent: maybeString(record.agent).trim(),
model: maybeString(record.model).trim(),
subtask: record.subtask === true,
};
}
export function parseBundle(rawJson: string): NormalizedBundle {
try {
return normalizeBundleRecord(JSON.parse(rawJson));
} catch {
return normalizeBundleRecord(null);
}
}
function normalizeBundleRecord(parsed: unknown): NormalizedBundle {
const record = maybeObject(parsed);
const workspace = maybeObject(record?.workspace);
return {
schemaVersion: typeof record?.schemaVersion === "number" ? record.schemaVersion : null,
type: maybeString(record?.type).trim(),
name: maybeString(record?.name).trim(),
description: maybeString(record?.description).trim(),
trigger: maybeString(record?.trigger).trim(),
content: maybeString(record?.content),
workspace,
skills: maybeArray(record?.skills).map(normalizeSkillItem).filter((s): s is NormalizedSkillItem => s !== null),
commands: maybeArray(record?.commands).map(normalizeCommandItem).filter((c): c is NormalizedCommandItem => c !== null),
};
}
export function validateBundlePayload(rawJson: string): ValidationResult {
let parsed: unknown;
try {
parsed = JSON.parse(rawJson);
} catch {
return { ok: false, message: "Invalid JSON" };
}
const bundle = normalizeBundleRecord(parsed);
if (bundle.schemaVersion !== 1) {
return { ok: false, message: "Unsupported bundle schema version" };
}
if (!["skill", "skills-set", "workspace-profile"].includes(bundle.type)) {
return { ok: false, message: "Unsupported bundle type" };
}
if (bundle.type === "skill") {
if (!bundle.name || !bundle.content.trim()) {
return { ok: false, message: "Skill bundles require name and content" };
}
}
if (bundle.type === "skills-set") {
if (!bundle.skills.length) {
return { ok: false, message: "Skills set bundle has no importable skills" };
}
}
if (bundle.type === "workspace-profile") {
if (!bundle.workspace) {
return { ok: false, message: "Workspace profile bundle is missing workspace payload" };
}
}
return { ok: true, bundle };
}
export function getBundleCounts(bundle: NormalizedBundle): BundleCounts {
const workspaceSkills = maybeArray(bundle.workspace?.skills).map(normalizeSkillItem).filter((s): s is NormalizedSkillItem => s !== null);
const opencode = maybeObject(bundle.workspace?.opencode);
const openwork = maybeObject(bundle.workspace?.openwork);
const genericConfig = maybeObject(bundle.workspace?.config);
const commands = maybeArray(bundle.workspace?.commands).map(normalizeCommandItem).filter((c): c is NormalizedCommandItem => c !== null);
const agentEntries = Object.entries(maybeObject(opencode?.agent) ?? {});
const mcpEntries = Object.entries(maybeObject(opencode?.mcp) ?? {});
const opencodeConfigKeys = Object.keys(opencode ?? {}).filter((key) => !["agent", "mcp"].includes(key));
return {
skillCount:
bundle.type === "skill"
? bundle.name
? 1
: 0
: bundle.type === "skills-set"
? bundle.skills.length
: workspaceSkills.length,
commandCount: commands.length,
agentCount: agentEntries.length,
mcpCount: mcpEntries.length,
configCount: (openwork ? 1 : 0) + (opencodeConfigKeys.length ? 1 : 0) + Object.keys(genericConfig ?? {}).length,
hasConfig: Boolean(openwork || opencodeConfigKeys.length || genericConfig),
};
}
function readVersionFromContent(content: string): string {
const { data } = parseFrontmatter(content);
const version = maybeString(data.version).trim();
return version || "";
}
export function collectBundleItems(bundle: NormalizedBundle, limit = 8): PreviewItem[] {
const items: PreviewItem[] = [];
if (bundle.type === "skill") {
items.push({
name: bundle.name || "Untitled skill",
kind: "Skill",
meta: bundle.trigger ? `Trigger · ${bundle.trigger}` : readVersionFromContent(bundle.content) || "Skill bundle",
tone: "skill",
});
}
if (bundle.type === "skills-set") {
for (const skill of bundle.skills) {
items.push({
name: skill.name,
kind: "Skill",
meta: skill.trigger ? `Trigger · ${skill.trigger}` : readVersionFromContent(skill.content) || "Skill",
tone: "skill",
});
}
}
if (bundle.type === "workspace-profile") {
const workspaceSkills = maybeArray(bundle.workspace?.skills).map(normalizeSkillItem).filter((s): s is NormalizedSkillItem => s !== null);
for (const skill of workspaceSkills) {
items.push({
name: skill.name,
kind: "Skill",
meta: skill.trigger ? `Trigger · ${skill.trigger}` : readVersionFromContent(skill.content) || "Skill",
tone: "skill",
});
}
const opencode = maybeObject(bundle.workspace?.opencode);
for (const [name, config] of Object.entries(maybeObject(opencode?.agent) ?? {})) {
const entry = maybeObject(config) ?? {};
const version = maybeString(entry.version).trim();
const model = maybeString(entry.model).trim();
items.push({
name,
kind: "Agent",
meta: version ? `v${version}` : model ? model : "Agent config",
tone: "agent",
});
}
for (const [name, config] of Object.entries(maybeObject(opencode?.mcp) ?? {})) {
const entry = maybeObject(config) ?? {};
const type = maybeString(entry.type).trim();
const url = maybeString(entry.url).trim();
items.push({
name,
kind: "MCP",
meta: type ? `${humanizeType(type)} MCP` : url ? "Remote MCP" : "MCP config",
tone: "mcp",
});
}
const commands = maybeArray(bundle.workspace?.commands).map(normalizeCommandItem).filter((c): c is NormalizedCommandItem => c !== null);
for (const command of commands) {
items.push({
name: command.name,
kind: "Command",
meta: command.agent ? `Agent · ${command.agent}` : "Command",
tone: "command",
});
}
const opencodeConfigKeys = Object.keys(maybeObject(opencode) ?? {}).filter((key) => !["agent", "mcp"].includes(key));
if (opencodeConfigKeys.length) {
items.push({
name: "opencode.json",
kind: "Config",
meta: "OpenCode config",
tone: "config",
});
}
if (maybeObject(bundle.workspace?.openwork)) {
items.push({
name: "openwork.json",
kind: "Config",
meta: "OpenWork config",
tone: "config",
});
}
for (const [name] of Object.entries(maybeObject(bundle.workspace?.config) ?? {})) {
items.push({
name,
kind: "Config",
meta: "Config file",
tone: "config",
});
}
}
return items.slice(0, limit);
}
const PREVIEW_MAX_CHARS = 2200;
function slugifyPreviewFilename(value: string, fallback: string, extension: string): string {
const stem = String(value ?? "")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return `${stem || fallback}.${extension}`;
}
function buildTextPreview(content: string, fallback: string): string {
const normalized = String(content ?? "").trim();
return truncate(normalized || fallback, PREVIEW_MAX_CHARS);
}
function buildJsonPreview(value: unknown, fallback: string): string {
try {
const serialized = JSON.stringify(value, null, 2);
return truncate(serialized || fallback, PREVIEW_MAX_CHARS);
} catch {
return fallback;
}
}
function buildBundlePreviewSelection(input: {
filename: string;
text: string;
tone: PreviewItem["tone"];
label: string;
}): {
filename: string;
text: string;
tone: PreviewItem["tone"];
label: string;
} {
return input;
}
export function buildBundlePreview(bundle: NormalizedBundle): {
filename: string;
text: string;
tone: PreviewItem["tone"];
label: string;
} {
if (bundle.type === "skill") {
return buildBundlePreviewSelection({
filename: slugifyPreviewFilename(bundle.name || "skill", "skill", "md"),
text: buildTextPreview(bundle.content, `# ${bundle.name || "OpenWork skill"}`),
tone: "skill",
label: bundle.trigger ? `Trigger: ${bundle.trigger}` : "Skill preview",
});
}
if (bundle.type === "skills-set" && bundle.skills.length) {
const firstSkill = bundle.skills[0]!;
return buildBundlePreviewSelection({
filename: slugifyPreviewFilename(firstSkill.name || "skill", "skill", "md"),
text: buildTextPreview(firstSkill.content, `# ${firstSkill.name || "Shared skill"}`),
tone: "skill",
label: bundle.skills.length > 1 ? `First of ${bundle.skills.length} skills` : "Skill preview",
});
}
const workspaceSkills = maybeArray(bundle.workspace?.skills).map(normalizeSkillItem).filter((skill): skill is NormalizedSkillItem => skill !== null);
if (workspaceSkills.length) {
const firstSkill = workspaceSkills[0]!;
return buildBundlePreviewSelection({
filename: slugifyPreviewFilename(firstSkill.name || "skill", "skill", "md"),
text: buildTextPreview(firstSkill.content, `# ${firstSkill.name || "Workspace skill"}`),
tone: "skill",
label: workspaceSkills.length > 1 ? `Lead skill of ${workspaceSkills.length}` : "Skill preview",
});
}
const commands = maybeArray(bundle.workspace?.commands).map(normalizeCommandItem).filter((command): command is NormalizedCommandItem => command !== null);
if (commands.length) {
const firstCommand = commands[0]!;
return buildBundlePreviewSelection({
filename: slugifyPreviewFilename(firstCommand.name || "command", "command", "md"),
text: buildTextPreview(firstCommand.template || firstCommand.content, `# ${firstCommand.name || "OpenWork command"}`),
tone: "command",
label: firstCommand.agent ? `Command for ${firstCommand.agent}` : "Command preview",
});
}
const opencode = maybeObject(bundle.workspace?.opencode);
const agentEntries = Object.entries(maybeObject(opencode?.agent) ?? {});
if (agentEntries.length) {
const [name, config] = agentEntries[0]!;
return buildBundlePreviewSelection({
filename: slugifyPreviewFilename(name, "agent", "json"),
text: buildJsonPreview({ agent: { [name]: config } }, '{\n "agent": {}\n}'),
tone: "agent",
label: "Agent config",
});
}
const mcpEntries = Object.entries(maybeObject(opencode?.mcp) ?? {});
if (mcpEntries.length) {
const [name, config] = mcpEntries[0]!;
return buildBundlePreviewSelection({
filename: slugifyPreviewFilename(name, "mcp", "json"),
text: buildJsonPreview({ mcp: { [name]: config } }, '{\n "mcp": {}\n}'),
tone: "mcp",
label: "MCP config",
});
}
if (maybeObject(bundle.workspace?.openwork)) {
return buildBundlePreviewSelection({
filename: "openwork.json",
text: buildJsonPreview(bundle.workspace?.openwork, '{\n "openwork": {}\n}'),
tone: "config",
label: "OpenWork config",
});
}
if (opencode) {
return buildBundlePreviewSelection({
filename: "opencode.json",
text: buildJsonPreview(opencode, '{\n "opencode": {}\n}'),
tone: "config",
label: "OpenCode config",
});
}
const configEntries = Object.entries(maybeObject(bundle.workspace?.config) ?? {});
if (configEntries.length) {
const [name, value] = configEntries[0]!;
const extension = name.includes(".") ? name.split(".").pop() || "json" : "json";
return buildBundlePreviewSelection({
filename: name,
text: buildJsonPreview(value, `{\n "${name}": {}\n}`),
tone: "config",
label: `Config preview · ${extension}`,
});
}
return buildBundlePreviewSelection({
filename: "bundle.json",
text: buildJsonPreview(bundle, '{\n "bundle": true\n}'),
tone: "config",
label: "Bundle JSON",
});
}
export function prettyJson(rawJson: string): string {
try {
return JSON.stringify(JSON.parse(rawJson), null, 2);
} catch {
return rawJson;
}
}
export function buildBundleNarrative(bundle: NormalizedBundle): string {
const counts = getBundleCounts(bundle);
if (bundle.type === "skill") {
return "One reusable skill, wrapped in a share link that opens directly into a new OpenWork worker.";
}
if (bundle.type === "skills-set") {
return `${counts.skillCount} skills packaged together so a new worker can start with the full set in one import.`;
}
const parts: string[] = [];
if (counts.skillCount) parts.push(`${counts.skillCount} skill${counts.skillCount === 1 ? "" : "s"}`);
if (counts.agentCount) parts.push(`${counts.agentCount} agent${counts.agentCount === 1 ? "" : "s"}`);
if (counts.mcpCount) parts.push(`${counts.mcpCount} MCP${counts.mcpCount === 1 ? "" : "s"}`);
if (counts.commandCount) parts.push(`${counts.commandCount} command${counts.commandCount === 1 ? "" : "s"}`);
if (counts.configCount) parts.push(`${counts.configCount} config${counts.configCount === 1 ? "" : "s"}`);
return parts.length
? `${parts.join(", ")} bundled into a worker package that imports through OpenWork with one step.`
: "Worker configuration bundle prepared for OpenWork import.";
}
export function buildStatusMarkup({
title,
description,
actionHref,
actionLabel,
}: {
title: string;
description: string;
actionHref?: string;
actionLabel?: string;
}): string {
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${escapeHtml(title)} - OpenWork Share</title>
<style>
@font-face {
font-family: "FK Raster Roman Compact Smooth";
src: url("https://openwork.software/fonts/FKRasterRomanCompact-Smooth.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
:root {
color-scheme: light;
--ow-bg: #f6f9fc;
--ow-ink: #011627;
--ow-card: rgba(255, 255, 255, 0.8);
--ow-border: rgba(255, 255, 255, 0.8);
--ow-shadow: 0 20px 60px -15px rgba(0, 0, 0, 0.1);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
font-family: Inter, "Segoe UI", sans-serif;
color: var(--ow-ink);
background:
radial-gradient(circle at top left, rgba(251, 191, 36, 0.34), transparent 36%),
radial-gradient(circle at right, rgba(96, 165, 250, 0.28), transparent 30%),
radial-gradient(circle at bottom right, rgba(244, 114, 182, 0.14), transparent 28%),
linear-gradient(180deg, #fbfdff 0%, var(--ow-bg) 100%);
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image:
radial-gradient(rgba(1, 22, 39, 0.055) 0.75px, transparent 0.75px),
radial-gradient(rgba(1, 22, 39, 0.03) 0.6px, transparent 0.6px);
background-position: 0 0, 18px 18px;
background-size: 36px 36px;
opacity: 0.34;
mix-blend-mode: multiply;
}
.card {
position: relative;
z-index: 1;
width: min(100%, 540px);
border-radius: 2rem;
padding: 32px;
border: 1px solid var(--ow-border);
background: var(--ow-card);
box-shadow: var(--ow-shadow);
backdrop-filter: blur(16px);
}
h1 {
margin: 0 0 12px;
font-size: clamp(2rem, 5vw, 2.8rem);
line-height: 0.96;
letter-spacing: -0.06em;
}
p {
margin: 0;
color: #5f6b7a;
line-height: 1.6;
}
a {
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 24px;
min-height: 48px;
padding: 0 20px;
border-radius: 999px;
text-decoration: none;
color: white;
background: #011627;
transition: transform 300ms ${SHARE_EASE}, background-color 300ms ${SHARE_EASE}, box-shadow 300ms ${SHARE_EASE};
box-shadow: 0 14px 32px -16px rgba(1, 22, 39, 0.55);
font-weight: 500;
}
a:hover {
background: rgb(110, 110, 110);
transform: translateY(-1px);
box-shadow:
rgba(0, 0, 0, 0.06) 0px 0px 0px 1px,
rgba(0, 0, 0, 0.04) 0px 1px 2px 0px,
rgba(0, 0, 0, 0.04) 0px 2px 4px 0px;
}
</style>
</head>
<body>
<main class="card">
<h1>${escapeHtml(title)}</h1>
<p>${escapeHtml(description)}</p>
${actionHref && actionLabel ? `<a href="${escapeHtml(actionHref)}">${escapeHtml(actionLabel)}</a>` : ""}
</main>
</body>
</html>`;
}