@taghost/site-engine
v0.6.4
Published
Shared AI website-generation engine: brief -> research -> architect -> copywriter -> critic -> render -> QA. Consumed by TagHost Studio and SMEBundle GoLive.
Readme
@taghost/site-engine
Shared AI website-generation engine: brief → research → architect → copywriter → critic → design → render → QA. Pure, dependency-free, environment-agnostic; runs on Cloudflare Workers. Consumed by TagHost (Studio "Build from scratch") and SMEBundle (GoLive), so both products generate un-sloppy sites from one core.
The page is authored by a frontier gateway model when one is configured:
authorSite() hands the model the brief, copy, research dossier and generated
image URLs, and it writes the complete HTML/CSS page itself. Every authored page
passes sanitizePageHtml() (no scripts, frames, event handlers, unsafe URLs or
dead forms) and the static QA gate before shipping. When no gateway is
configured, or authoring fails, the deterministic composition engine renders a
fallback page instead, so a build never fails on a model outage. Workers AI is
never in the authoring chain by default.
The engine never touches a database or a billing system. The host owns persistence, auth, quotas and the publish path, and passes:
env— Worker bindings (env.AIfor Workers AI, or an OpenAI-compatible key).store— aSiteEngineStoreadapter (below).
Usage
import { runDeliberation, renderSite, runStaticQa } from '@taghost/site-engine';
const { generated } = await runDeliberation(env, { project, brief, spec, store });
const { files, templateId } = renderSite({ content: generated.content, brief, domain, spec });
const qa = runStaticQa({ files, content: generated.content, brief, domain });
// host: publish `files` (R2/KV) and open the editorgenerateContentModel(env, brief, { provider }) does just the copy step;
chatComplete / pickModelChain / providerInfo expose the provider+router
layer directly (e.g. for the brainstorm tool).
SiteEngineStore (host-implemented)
interface SiteEngineStore {
getHouseStyle?(): Promise<string>;
latestSteering?(projectId: string): Promise<string>;
saveDeliberation?(turn: {
id: string; projectId: string; buildId: string | null; round: number;
role: string; message: string; metadata: unknown; createdAt: string;
}): Promise<void>;
getDeliberations?(args: { projectId: string; buildId?: string | null; limit?: number }): Promise<unknown[]>;
getExemplars?(sector: string): Promise<unknown[]>;
getFeedbackHints?(): Promise<string[]>;
}All methods are optional: with no store, generation still runs (it just does not persist a transcript or read exemplars/house style).
Providers
- Workers AI (default) —
env.AI, edge, no external key. - OpenCode Go / Zen or any OpenAI-compatible gateway — set
SITE_BUILDER_PROVIDER(opencode-go|zen) and the key. The router picks a fast, low-reasoning model per task and blocks free/stealth models (which may train on inputs) from customer data.
Motion layer
Every authored page must move. The model writes only declarative hooks
(data-motion="kinetic|reveal|stagger|parallax|pin|horizontal|marquee|counter|magnetic|cursor|stack|progress|glass"),
picks 3–5 per page with at least one signature move (kinetic|pin|horizontal),
and sets an intensity level (premium or restrained, sector-adaptive).
injectMotion() appends the engine-owned layer: baseline CSS, a tiny
visibility guard and four asset files (GSAP core + ScrollTrigger + SplitText +
motion.js). These are the only scripts a generated bundle may contain —
QA matches external script src exactly and inline script content
byte-for-byte. Content stays visible without JavaScript, the guard failsafe
reveals everything after 3s, and prefers-reduced-motion disables motion
entirely. GSAP is bundled under its free-for-commercial-use license
(https://gsap.com/standard-license; regenerate with
node scripts/embed-motion.mjs).
Composition axes
content.design is validated against catalog.js; anything unknown falls back
to the family preset, about + services are always present and contact is
always last:
hero:centered | split | bleed | statement | panelservices:cards | rows | bento | numberedabout:plain | split | panelcontact:center | split | bandsections: ordered subset oftrust | about | services | gallery | faq
Families (golive-a…golive-e) stay as the aesthetic skin (surface, radius,
header tone); a host spec still works as a per-axis fallback and for backward
compatibility.
What is NOT here
Build state machine (site_builds claim/lease), auth, billing, quotas and the
publish path. Those are host-specific and live in each app. Imagery generation
is here (generateSubjectImages); storing the bytes and referencing their URLs
is the host's job.
Consumers
- TagHost apps (control-plane
site-engine-routes.ts, StudioBuildFromScratchModal) import it via the pnpm workspace ("@taghost/site-engine": "workspace:*"). - SMEBundle (separate repo) consumes the published package. The pure
modules in
functions/_shared/site/*areexport * from '@taghost/site-engine're-exports; host code (state machine, publish, learning/exemplar DB) stays there. While developing, SMEBundle may use a localfile:../TagHost/packages/site-enginelink — but a deploy guard blocksfile:dependencies from shipping, so publish before deploying.
Publishing
publish-site-engine.yml publishes to public npm on a site-engine-v* tag
(needs the NPM_TOKEN secret). Then SMEBundle pins "^<version>".
Status
Shipped: the portable core, TagHost "Build from scratch" (schema, routes, tier
quotas, preview mapping, Studio entry), SMEBundle consumption via re-exports,
the composition engine (block library + design spec, v0.4), host-wired sector
imagery (Flux → deployment R2 → content.images), model-authored full pages
through OpenCode Go with sanitizer + QA and a composition fallback (v0.5), and
the engine-owned GSAP motion layer with declarative hooks and a vision gate
(v0.6).
