@hex-core/payload

Pure-function builders for the canonical LLM context markdown.

Install

Version 0.2.1Runtime dependency
pnpm
pnpm add @hex-core/payload

What it does

`@hex-core/payload` is the pure-function builder for the canonical LLM context blob. Pass it a theme + components (+ optional recipes, density), get markdown back.

It exists so non-MCP-client surfaces — Next.js Server Components, generator scripts, CI fixtures, CLI tools — can render the canonical payload without spawning a subprocess and speaking JSON-RPC over stdio. The MCP server (`@hex-core/mcp@0.4.0+`) is now a thin transport shell that wraps this package.

The package also bundles the registry data (~59 items at last release) inside its tarball, so loaders work without filesystem coupling to the consumer's repo. This is what unblocked the studio's `/copy` route from re-implementing the markdown format locally.

Public API

buildAppContext

ts
function buildAppContext(input: {
	theme: { requested: string; resolved: Theme };
	components: { slug: string; item: RegistryItem | null }[];
	recipes?: { slug: string; recipe: Recipe }[];
	density?: "compact" | "comfortable" | "spacious";
	overrides?: Record<string, string>;
}): string;

Build the full canonical markdown blob: install commands, `globals.css`, `tailwind.config.ts`, components, recipes, and the LLM context prompt. The `theme.requested` field surfaces in the markdown so the LLM knows whether the user picked the theme or fell back to default.

tsx
import { buildAppContext, loadRegistryItem } from "@hex-core/payload";
import { defaultTheme } from "@hex-core/tokens";

const components = ["button", "card", "input"].map((slug) => ({
	slug,
	item: loadRegistryItem(slug),
}));

const markdown = buildAppContext({
	theme: { requested: "default", resolved: defaultTheme },
	components,
	density: "comfortable",
});

// Render markdown in a Server Component, write to disk, etc.

buildFigmaTokens

ts
function buildFigmaTokens(theme: Theme): {
	$schema: string;
	[collection: string]: unknown;
};

Build a Figma Variables JSON document from a theme. Drop the result into the Figma plugin's import slot and the design system's tokens land in the file as variables. Round-trips with the same `Theme` shape that powers `themeToCss`.

tsx
import { buildFigmaTokens } from "@hex-core/payload";
import { defaultTheme } from "@hex-core/tokens";
import { writeFileSync } from "node:fs";

writeFileSync(
	"./design-tokens.figma.json",
	JSON.stringify(buildFigmaTokens(defaultTheme), null, 2),
);

loadRegistryItem / loadRegistry

ts
function loadRegistryItem(slug: string): RegistryItem | null;
function loadRegistry(): readonly RegistryItem[];

Read from the bundled registry snapshot. `loadRegistryItem` returns `null` for an unknown slug; `loadRegistry` enumerates the whole catalog. Server-only — both functions perform synchronous filesystem reads, so don't import them from a Client Component.

tsx
import { loadRegistry, loadRegistryItem } from "@hex-core/payload";

const all = loadRegistry();
console.log(all.length); // 59 (at @0.2.0)

const button = loadRegistryItem("button");
if (button) {
	console.log(button.ai.whenToUse);
}

loadRecipes / loadRecipe

ts
function loadRecipes(): readonly Recipe[];
function loadRecipe(slug: string): Recipe | null;

Same shape as the component loaders, but for the multi-component recipes (auth-form, settings-page, command-palette, etc.). Pass results into `buildAppContext`'s optional `recipes` field to bundle them into the LLM payload.

tsx
import { loadRecipe } from "@hex-core/payload";

const auth = loadRecipe("auth-form");
console.log(auth?.components); // ["button", "input", "label", ...]

getTheme / listThemes

ts
function getTheme(name: string): Theme | undefined;
function listThemes(): readonly { name: string; theme: Theme }[];

Re-exports of the theme catalog from `@hex-core/tokens`. Convenient when you already have `@hex-core/payload` installed and don't want to add a second dependency just to look up a theme by name.

tsx
import { getTheme, listThemes } from "@hex-core/payload";

const theme = getTheme("midnight") ?? getTheme("default");
console.log(listThemes().map((t) => t.name));

resolveSpec

ts
function resolveSpec(brief: string): {
	components: string[];
	recipes: string[];
	rationale: string;
};

Deterministic brief-to-shortlist resolver. Pass a natural-language description ("settings page with profile, billing, notifications"); get back a ranked list of registry slugs + rationale. Same logic the MCP `resolve_spec` tool wraps.

tsx
import { resolveSpec } from "@hex-core/payload";

const result = resolveSpec("confirm-destructive dialog with checkbox and dual-action footer");
console.log(result.components); // ["dialog", "checkbox", "button"]
console.log(result.recipes);    // ["confirm-destructive"]

Workflows

Render the canonical payload from a Next.js Server Component

This is what the studio's `/copy` page does. No subprocess, no JSON-RPC, no MCP transport — direct function call.

tsx
// app/llm-context/page.tsx
import { buildAppContext, loadRegistryItem } from "@hex-core/payload";
import { defaultTheme } from "@hex-core/tokens";

export default function LlmContextPage() {
	const components = ["button", "card", "input", "dialog"].map((slug) => ({
		slug,
		item: loadRegistryItem(slug),
	}));

	const markdown = buildAppContext({
		theme: { requested: "default", resolved: defaultTheme },
		components,
	});

	return <pre>{markdown}</pre>;
}

Generate a Figma tokens file in a CI script

Run on every theme change to keep design and code in sync.

ts
// scripts/sync-figma-tokens.ts
import { buildFigmaTokens, getTheme } from "@hex-core/payload";
import { writeFileSync } from "node:fs";

const theme = getTheme(process.env.THEME ?? "default");
if (!theme) throw new Error("Unknown theme");

writeFileSync(
	"./design/figma-tokens.json",
	JSON.stringify(buildFigmaTokens(theme), null, 2),
);
console.log("Wrote figma-tokens.json");

Compatibility

  • Pure ESM. Server-only — `loadRegistry*` and `loadRecipe*` perform synchronous `fs` reads. Do not import from a Client Component.
  • Bundles `registry/` data into the published tarball. Works in fresh `npm install` outside the hex-core monorepo.
  • Depends on `@hex-core/registry@^0.2.1` and `@hex-core/tokens@^1.2.0`. Re-exports `Theme`, `RegistryItem`, `Recipe` types so consumers don't need a second dep.

See also