Enginecy · Internal engineering guide

The Build Agent turns a site request into a live subdomain.

Open this repo in Claude Code on any machine and you're talking to the agent. It creates the repo, art-directs and builds the site, deploys to Cloudflare, and wires the subdomain — end to end, no dashboard clicks. The same folder is the headless runner's brain, so interactive and automated work share one source of truth.

The pipeline

// 01 · the stack

What it runs on

Deterministic Node scripts do the mechanical work; Claude does the judgment (design, copy, decisions). GitHub and Cloudflare are reached through org-owned credentials — no personal tokens.

LayerTechnologyNotes
Agent runtimeClaude Code (CLI / app)The folder is the agent; CLAUDE.md is its constitution
AutomationNode 20+ ESM (.mjs)Scripts in scripts/ — no framework
Source controlGitHub · org EnginecyEvery site is its own private repo Enginecy/<slug>
GitHub authGitHub App"Enginecy Site Pipeline" — org-owned, auto-rotating tokens. No PATs
Hosting · Lane ACloudflare PagesStatic / Jamstack / 3D (wrangler via npx)
Hosting · Lane BDokploydokploy2.enginecy.cloud — apps needing a server / DB
Design DBui-ux-pro-max67 styles · 161 palettes · 57 font pairings · 21 stacks
Pre-deploy gatesite-auditor subagentRenders the build in a headless browser; blocks deploy on AA-contrast / a11y / slop failures
AI imageryPollinationsReal photos + scroll-world video, generated at build time (POLLINATIONS_API_KEY)
Registry / alertsNocoDB · Telegram · DiscordOptional — skipped if unconfigured

Templates

Quick · Lane A

astro-marketing, astro-content

Bespoke · Lane A

bespoke-astro, three-experience, scroll-world

Apps · Lane B

next-app, webapp-fullstack

// 02 · setup

One time, per machine

Clone, run setup, then supply the two secret pieces from the team vault. Everything else travels in the repo.

git clone https://github.com/Enginecy/build-agent && cd build-agent
./setup.sh                 # deps + pinned design DB; creates .env
  1. Fill .env

    Copy values from the vault: GitHub App IDs + key path, Cloudflare tokens/account IDs (mo & client), optional NocoDB / Telegram / Discord.

  2. Add the App key

    Drop the GitHub App .pem into ./secrets/ and point GITHUB_APP_PRIVATE_KEY_FILE at it (exact filename, incl. .pem).

  3. Set your git identity

    Once — this is what puts your name on commits (see §10).

git config --global user.name  "Your Name"
git config --global user.email "<id>+<user>@users.noreply.github.com"
Run scripts from the repo root. Paths like ./secrets/*.pem and work/<slug> resolve against the current directory. .env auto-loads — no --env-file flag needed.

// 03 · mental model

Three repos, one working copy

Keeping these straight removes nearly every point of confusion.

Enginecy/build‑agent

This folder — the agent's brain (scripts, skills, commands). Clone once; git pull for updates.

Enginecy/website‑template

The template catalog. Never edited directly — scaffold copies a template out of it.

Enginecy/<slug>

The actual website — the source of truth for a site. Shared by the whole team.

A site's code does not live in this folder. To work on a site you bring it local into work/<slug> (a git-ignored scratch area). That checkout is your working copy — edit it, build it, push it back.

Enginecy/<slug>   ──pull──▶   work/<slug>   ──edit·build·push──▶   Enginecy/<slug>   ──deploy──▶   live
   remote,                  your local                        remote,
   shared truth             working copy                      updated

// 04 · scripts & commands

Your two toolbars

Slash commands (in Claude Code)

/new-siteFull net-new build, from repo creation to a live subdomain
/edit-sitePull → edit → build → push → deploy an existing site
/art-directRun only the design flow on work/<slug>
/deployBuild + push + deploy an already-prepared checkout
/scroll-worldBuild & launch an immersive 3D scroll-world site
/dashboardLaunch the local control panel

Scripts (from repo root — .env auto-loads)

create-repoCreate private Enginecy/<slug> (idempotent)
scaffoldCopy a template into work/<slug> + write config & brief
pullClone or fast-forward work/<slug> from the remote
pushCommit + push to main — stale-push guard, your attribution
deploy-pagesDeploy dist/, attach subdomain + DNS, poll until live
registerRecord the deploy in the NocoDB registry + notify
generate-mediaGenerate AI imagery (Pollinations) into public/img/
build-scroll-worldScroll-world scenes: stills → dive/connector videos → config.json (--mode kenburns|aivideo)
audit-scroll-worldDeterministic AA check of the scroll-world engine's runtime UI (nav, CTAs, labels)
list-sitesList registered sites (JSON) — powers the command site-pickers
add-domainCheck availability / register a new domain via Cloudflare Registrar (§09)
dashboardLocal control panel at localhost:4545
deploy-dashboardDeploy the hosted control-panel Worker (admin)
publish-guideRebuild this guide from docs/team-guide.html and publish it to baguide.enginecy.cloud (--deploy)
Commands are interactive. Type a command with no arguments (e.g. /new-site) and you get clickable menus — kind of site (incl. scroll-world), account, a site-picker for edit/deploy. Pass full key=value args to skip the menus (and for headless claude -p).

AI imagery & scroll-world

Any site's imagery step generates real photos via Pollinations: enginecy-image-direction writes prompts → brief/media.jsongenerate-media.mjspublic/img/. The scroll-world template is an immersive "scroll to fly through it" 3D scroll (pre-rendered video scrubbed by scroll); /scroll-world drives build-scroll-world.mjs to generate scene stills + dive/connector videos. Prereqs: POLLINATIONS_API_KEY + ffmpeg.

Two motion modes. --mode kenburns (default) is a deterministic ffmpeg push-in over each still — cheap, but a moving photo. --mode aivideo generates a real Pollinations video per scene, so the camera genuinely flies through the space (true parallax); if a scene's video generation fails (e.g. a Pollinations 402 balance limit) that scene falls back to ken-burns automatically, and cached scenes are reused so you can re-run just the failed one later.

Control panel & site registry

Every deploy is recorded in a NocoDB registryregister.mjs runs at the end of /new-site and /edit-site and upserts by slug (one row per site), auto-filling Type, Account, Lane, Template, Category, Vertical, Domain, Status and DeployedAt from site.config.json. See status across everything two ways:

Hosted · no install

dashboard.enginecy.cloud — behind Cloudflare Access; sign in with your @enginecy.com email + one-time PIN.

Local

node scripts/dashboard.mjs (or /dashboard) → localhost:4545. Same panel + your local work/ checkouts.

Both read .env server-side — tokens never reach the browser. One shared UI in shared/dashboard-view.mjs.

// 05 · workflow a

Build a brand-new site

One command — or plain English. The agent parses the details and runs the whole pipeline, asking only for anything required and missing.

/new-site slug=pharmacy-wellness template=bespoke-astro \
          lane=A account=mo subdomain=pharmacy.enginecy.cloud \
          brief='Trust-first pharmacy & wellness landing'

…or just say: "Build a trust-first pharmacy landing at pharmacy.enginecy.cloud."

Under the hood: create-reposcaffold → the design flownpm run build (must pass) → the audit gate (site-auditor — blocks on any critical finding) → pushdeploy-pages (polled to 200) → register → reports the live URL & repo URL.

Picking inputs. account=mo for Enginecy-owned sites, account=client for client sites. Template by intent (marketing → bespoke-astro, content → astro-content, 3D → three-experience, app → Lane B).

// 06 · workflow b

Edit an existing site

The easy way is one command; the manual equivalent is four steps.

/edit-site <slug> <mo|client> <subdomain>
node scripts/pull.mjs <slug>            # get latest into work/<slug>
# …edit work/<slug>…  (optionally: /art-direct <slug>)
cd work/<slug> && npm install && npm run build
# …audit gate: run the site-auditor; fix any critical finding, re-audit…
cd ../.. && node scripts/push.mjs <slug>
node scripts/deploy-pages.mjs <slug> <mo|client> <subdomain>
scaffold is create-only — it clones the template and refuses if work/<slug> exists. For an existing site always start with pull, never scaffold.

// 07 · across machines

Collaborating — different people, different machines

The shared truth is the GitHub repo. Git handles multi-machine, sequential collaboration natively; each person's work/<slug> is just their own checkout.

The golden rule: pull before you edit

node scripts/pull.mjs <slug>

push never force-pushes. If your checkout is behind the remote, the push is refused — clearly — instead of clobbering someone's work:

refusing to push: remote is 1 commit(s) ahead of your checkout (stale).
  Pull first:  node scripts/pull.mjs <slug>   then rebuild & re-run.

pull also refuses if you have uncommitted changes (commit or stash first) or if history has diverged (resolve manually). For genuinely concurrent work, use feature branches + PRs — the scripts operate on main.

// 08 · the design flow

What makes each site distinct

Never ship a template's placeholder look. Order: direction → data → copy → execution → imagery → polish → gate. Run just this with /art-direct <slug>.

StepSkillOutput
Directionenginecy-art-directionConcept, color world, type, motion, per-section direction
Dataui-ux-pro-maxConcrete palette / type / pattern → tokens.css
Copyart-direction (copy)Headline formulas, benefits over features — banned-phrase list
Executionfrontend-design + enginecy-motionAesthetic + scroll/motion (Lenis, GSAP) with exact easing
Imageryenginecy-image-directionPhotographic prompts + anti-plastic-skin negative prompt
Polishenginecy-visual-qualityAppend polish.css, walk the prohibitions list
Gatesite-auditor + accessibility-reviewRender the build, block on contrast / a11y / slop · Lighthouse ≥ 90

The audit gate — site-auditor

Contrast bugs kept slipping through — light text on light buttons, dark headings on dark sections — so an automated auditor now runs after every build and before every deploy. /new-site, /edit-site, /deploy and /scroll-world all invoke it as a gate; a CRITICAL finding means do not deploy until it's fixed and re-audited. It's a read-only Claude subagent (.claude/agents/site-auditor.md) that:

  • Renders the actual build in a headless browser and reads computed styles — so Tailwind utilities and runtime-injected CSS are covered, which static linters miss.
  • Sweeps every text element for AA contrast (lib/contrast-audit.js): normalises every colour through a canvas (handles oklch/oklab/color(srgb)), composites alpha over the real background, flags anything under 4.5:1 (3:1 for large text), and hands text-over-image to a visual screenshot pass.
  • Runs accessibility-review (focus, landmarks, alt, touch targets, reduced-motion) and the enginecy-visual-quality prohibitions. For scroll-world it also runs audit-scroll-world.mjs against the engine's runtime UI.
Tailwind v4 gotcha it catches: an unlayered base rule like h1{color:var(--ink)} beats a layered utility like text-white, so <h3 class="text-white"> on a dark section silently renders dark-on-dark. Fix: wrap base element styles in @layer base { … } so utilities win — all four Tailwind templates already have this fix.

// 09 · lanes & routing

Where a site ships — and to whose account

Lanes — decide by: does it need a server runtime you control?

  • No → Lane A (Cloudflare Pages): astro-marketing, astro-content, bespoke-astro, three-experience, scroll-world.
  • Yes → Lane B (Dokploy): next-app, webapp-fullstack.

Cloudflare account routing — a hard rule

Pages, R2, and DNS all follow ownership. Pass the right account flag:

Site ownerFlagAccount · env
Enginecyaccount=momo@enginecy.com · CLOUDFLARE_*
Clientaccount=clientbackend@enginecy.com · CLOUDFLARE_CLIENT_*

deploy-pages picks the token from this flag, creates the project, deploys dist/, attaches the subdomain + proxied CNAME, and polls https://<subdomain> until it returns 200. First-provision SSL can take a few minutes.

Custom domains — not just subdomains

The parameter is named subdomain, but the deploy step attaches whatever hostname you pass — a subdomain, www, or an apex/root domain (handled via CNAME flattening at @). The real requirement isn't "subdomain vs. domain" — it's that the domain's DNS zone lives in the correct Cloudflare account. For *.enginecy.cloud that's already true; a brand-new domain needs one-time onboarding.

Path A — register via Cloudflare Registrar (at-cost): the domain becomes a managed zone in the same account automatically, so deploy-pages then just works. One-time per account, in the dashboard: add a payment method + default registrant contact (Domains → Registrations), and add "Registrar" (read + edit) to that account's API token in .env.

# check availability + at-cost price (free, read-only)
node scripts/add-domain.mjs <mo|client> clientbrand.com

# register into the account (CHARGES it — deliberate two-step)
node scripts/add-domain.mjs <mo|client> clientbrand.com --register --yes [--auto-renew]

# then deploy onto it (apex or any host)
node scripts/deploy-pages.mjs <slug> <mo|client> clientbrand.com
add-domain never buys without both --register and --yes; it polls to succeeded and confirms the zone is deploy-ready. Buying elsewhere and pointing nameservers at Cloudflare (Path B) works too — Path A just keeps everything in one account.

// 10 · attribution

Getting your name on commits

Org access authorizes the push; it does not decide attribution. GitHub attributes a commit by matching the author email to a verified email on an account.

push authors each commit with your own git identity and clears any stale per-repo override. It falls back to the Enginecy Build Agent bot only when no identity is set (e.g. the headless runner). The push itself is authenticated by the App.

For your avatar to appear, both must be true:

  1. Your git config --global user.email is an email verified on your GitHub account — the <id>+<user>@users.noreply.github.com address is ideal (private + still links to you).
  2. You're a member of the Enginecy org.
Trade-off of this model: the commit is attributed to you, but the push event / audit log shows the App — so no per-dev GitHub push credentials are needed.

// 11 · troubleshooting

When something errors

SymptomCause & fix
Set GITHUB_APP_ID…Run scripts from the repo root (.env auto-loads there); confirm .env is filled
Invalid API TokenRegenerate the Cloudflare token with Pages › Edit + DNS › Edit, paste into .env
installation token / .pem errorGITHUB_APP_PRIVATE_KEY_FILE path wrong or key missing from secrets/ (check exact filename)
refusing to push… staleYour checkout is behind → pull, rebuild, re-push
not a clean fast-forwardDiverged → resolve manually in work/<slug> (git rebase origin/main)
Commit shows as the botYou didn't set git config --global user.email — set it (§10)
Subdomain not 200 yetFirst-provision SSL delay — wait a few minutes; the deploy script polls
Auditor flags dark-on-dark / light-on-light headingsA Tailwind text-* utility is being beaten by an unlayered base rule — wrap base heading styles in @layer base (§08)
scroll-world aivideo scene fell back to ken-burnsThe video API failed for that scene (often a Pollinations 402 balance limit) — top up the key and re-run; cached scenes are reused
token lacks Registrar permissionAdd "Registrar" (read + edit) to that account's API token in .env
Registration: no default registrant contactSet a default contact + payment method in the dashboard → Domains → Registrations

// 12 · quick reference

Copy-paste

# ── Setup (once per machine) ──
git clone https://github.com/Enginecy/build-agent && cd build-agent
./setup.sh                                    # then fill .env, add secrets/*.pem
git config --global user.email "<id>+<user>@users.noreply.github.com"

# ── New site (in Claude Code) ──
/new-site slug=<slug> template=<template> lane=A account=<mo|client> subdomain=<host> brief='<one line>'

# ── Edit an existing site ──
/edit-site <slug> <mo|client> <subdomain>
#   …or manually:
node scripts/pull.mjs <slug>
# …edit work/<slug> …
cd work/<slug> && npm install && npm run build && cd ../..
node scripts/push.mjs <slug>
node scripts/deploy-pages.mjs <slug> <mo|client> <subdomain>

# ── Design flow only ──
/art-direct <slug>

# ── Register a new domain (Path A · Cloudflare Registrar) ──
node scripts/add-domain.mjs <mo|client> <domain>                 # check price — free
node scripts/add-domain.mjs <mo|client> <domain> --register --yes  # buy it

# ── See status of everything ──
open https://dashboard.enginecy.cloud     # hosted, Access-gated, no install
node scripts/dashboard.mjs                # local panel → localhost:4545
Remember: pull before you edit · run scripts from the repo root · mo=Enginecy / client=client sites · never commit secrets · a site isn't done until it serves 200.