{
  "markdown": "# builder2\n\nGenerates a complete multi-page WordPress site from a one-line prompt: a block\ntheme (theme.json + templates + header/footer parts + the preview screenshot\nWordPress shows on the theme card) plus a companion content plugin that seeds\nevery generated page on activation and removes them on deactivation. The site\nspec carries a page tree (home, menu, about, …); every page gets its own\nplanned and generated sections. Optionally turns the\n`AI_IMAGE` placeholders it emits into real assets via Google Gemini (through\nthe WPCOM AI proxy).\n\nThe split is deliberate: design lives in the theme, content lives in\n`projects/<slug>/plugin/` (static seeder code + `pages.json` manifest +\n`pages/<slug>.html` block markup + `images/` content images, which the\nseeder imports into the media library on activation). The homepage is a seeded page too —\n`page_on_front` points at it; there is no front-page.html template.\n\n## Setup\n\n```bash\ncp .env.example .env\n# Text/code LLM (default Anthropic): ANTHROPIC_API_KEY\n# Or xAI Grok: LLM_PROVIDER=xai, XAI_API_KEY, LLM_MODEL=grok-4.6 (and per-step models)\n# Or OpenRouter: LLM_PROVIDER=openrouter, OPENROUTER_API_KEY (models come from config/models.json)\n# Or Baseten (Kimi/GLM/DeepSeek via the wpcom AI proxy): LLM_PROVIDER=baseten, BASETEN_API_KEY\n# Images (optional): GOOGLE_VERTEX_API_TOKEN\n\ncomposer install\nnpm ci   # optional; installs Playground, screenshot helpers, and block-fixer oracle tooling\n```\n\nStandalone theme generation and block fixing require PHP 8.1+ and the Composer\ndependencies installed in `vendor/`. Embedding hosts may provide those\ndependencies through their own autoloader instead.\n\nThe block fixer is implemented entirely in PHP and needs neither Node nor\n`node_modules`. WordPress Playground previews and screenshot tooling still use\nNode. Use `php bin/build.php \"…\" --no-serve` for a PHP-only build.\n\n## Install as a plugin\n\nUse [`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json) as the\nmarketplace entry point and install the `site-builder` plugin. The plugin ships\nthe [`site-build` Skill](skills/site-build/SKILL.md) for driving this repository\nfrom a supported coding-agent harness.\n\n> **Breaking change for downstream consumers:** the Node block fixer is gone —\n> `NodeBlockFixer` and `Package::blockFixerScript()` no longer exist. Any host\n> that vendors this package and wrapped the Node script in a sandbox or adapter\n> must delete or rewire that adapter in the same change as the re-vendor.\n> `PhpBlockFixer` runs in-process with zero runtime dependencies; the frozen\n> compatibility artifacts and their regeneration path are documented in\n> `docs/block-fixer-oracle.md`.\n\n## Build a site\n\n```bash\nphp bin/build.php \"A cozy neighborhood bakery\"\nphp bin/build.php \"A cozy neighborhood bakery\" --with-images   # also generate images\nphp bin/build.php \"A cozy neighborhood bakery\" --provider=openai   # build on GPT-5.x instead of Claude\nphp bin/build.php \"A cozy neighborhood bakery\" --html-first     # author an HTML+CSS design, then convert it to blocks\nphp bin/build.php \"A cozy neighborhood bakery\" --blocks-first   # author block markup directly (the default)\nphp bin/build.php \"A cozy neighborhood bakery\" --multi-page    # let the site plan inner pages beyond the homepage\nphp bin/build.php \"A cozy neighborhood bakery\" --multi-page --pages=\"Home, Menu, About, Visit\"   # fix the page list yourself (first = homepage)\nphp bin/build.php \"A Persian poetry archive\" --writing-direction=rtl --hero-canvas=framed --hero-media-modes=none,foreground-image --max-hero-images=1\n```\n\nHero composition is selected from a reviewed code-owned catalog after filtering\nthe optional caller constraints `--hero-canvas`, `--hero-media-modes`,\n`--max-hero-images`, and `--hero-copy-capacity`.\n`--use-jetpack-placeholders` is for hosts that own a form backend: a section\nthat needs a form reserves its place with a `JP_FORM` placeholder block the\nhost substitutes after the build, instead of the default of emitting no form\nmarkup at all. `--writing-direction=ltr|rtl`\nis an explicit caller override; otherwise the site language determines logical\ndirection. The selected recipe and normalized blueprint are persisted in\n`designDirection.json`, while `aboveFold.json` records the two-phase shared\nheader/hero/page-opening contract.\n\n### Embedding with an existing site spec\n\nAn embedding host that already owns the factual site specification can pass\nthe package-canonical decoded object to `SiteBuilder::createProject()` instead\nof paying for the `site-spec` LLM call:\n\n```php\n$project = $builder->createProject(\n    prompt: $userPrompt,\n    slug: $projectSlug,\n    siteSpec: $canonicalSiteSpec,\n);\n$builder->pipeline()->runThrough($project);\n```\n\nThe consumer contract ships with the package in two forms:\n\n- [`schemas/site-spec.schema.json`](schemas/site-spec.schema.json) — JSON\n  Schema Draft 2020-12 for the complete canonical object.\n- [`examples/site-spec.json`](examples/site-spec.json) — a complete payload,\n  including nested pages and host-defined factual fields.\n\nVendored consumers can resolve those files without assuming an installation\npath through `Package::siteSpecSchemaPath()` and\n`Package::siteSpecExamplePath()`. The schema describes the recommended input\nand normalized `siteSpec.json` artifact. Runtime intake remains deliberately\nforgiving: missing or malformed candidate fields are normalized, repaired, or\nwarned about rather than becoming a new build-stopping validation gate.\n\nThe value crosses the portable project boundary as `meta.json.site_spec`; the\nnormal `site-spec` step still canonicalizes it and writes `siteSpec.json`, but\nmakes no LLM request. With `multiPage` omitted, a supplied spec keeps its page\ntree. Pass `multiPage: false` to deliberately force one homepage. A non-empty\n`pages:` list implies multi-page scope and replaces the supplied tree with an\nexact caller-owned list. The user prompt is still required because the design\nand content steps consume both inputs.\n\nThe fixed properties use this package's canonical snake-case fields. Additional\ntop-level properties may carry grounded facts such as hours, location, or\nservices; page objects have the exact recursive `title` / `slug` / `purpose` /\n`children` shape shown in the schema. A host with its own metadata shape\n(including WordPress.com) maps that payload in its adapter rather than adding\nhost-specific aliases to this package.\n\n### Choosing the model / provider\n\n`--provider=<anthropic|openai|xai|openrouter|baseten>` (or the `LLM_PROVIDER` env var) picks a whole\nmodel set at once. Each provider defines a **large** (quality-critical steps) and\n**small** (fast/cheap structural steps) model in\n[`config/models.json`](config/models.json), and each pipeline step is mapped to a\ntier there — so switching providers needs no per-step configuration. Defaults:\n\n| Provider | large | small |\n|----------|-------|-------|\n| `anthropic` (default) | `claude-opus-5` | `claude-haiku-4-5` |\n| `openai` | `gpt-5.5` | `gpt-5.4-mini` |\n| `xai` | `grok-4.6` | `grok-4.6` |\n| `openrouter` | `moonshotai/kimi-k3` | `moonshotai/kimi-k2.5:nitro` |\n| `baseten` | `deepseek-ai/DeepSeek-V4-Pro` | `zai-org/GLM-5.3-Flash` |\n\n`baseten` reaches Baseten's open-weight models through the WordPress.com AI\nproxy (`https://public-api.wordpress.com/wpcom/v2/ai-api-proxy/v1`, feature slug\n`site-builder`) — the route Studio's \"hosted\" models use. Set\n`BASETEN_BASE_URL=https://inference.baseten.co/v1` to go direct with a real\nBaseten key. Besides the two tier defaults, `LLM_MODEL` / `LLM_MODEL_<STEP>`\naccept `moonshotai/Kimi-K3`, `deepseek-ai/DeepSeek-V4-Flash-0731`,\n`zai-org/GLM-5.2`, `zai-org/GLM-5.2-Fast` and `zai-org/GLM-5.3`. Model ids are\ncase-sensitive.\n\n### Running one step on a different provider\n\n`LLM_MODEL_<STEP>` has always overridden a single step's model. It also accepts\nan optional `transport:` prefix, which moves that one step to another provider:\n\n```bash\nLLM_PROVIDER=anthropic\nLLM_MODEL_THEME_JSON=claude-opus-5                    # model only, as always\nLLM_MODEL_SECTIONS=baseten:zai-org/GLM-5.3-Flash      # this step runs on Baseten\n```\n\nThe build then uses one client per transport, dispatching each request on its\nmodel id. No step knows more than one provider is in play, and a run with no\nprefixed override builds exactly the single client it always did.\n\nTransports are `anthropic`, `xai`, `openai`, `openrouter` and `baseten`. The\nprefix is recognised only when the text before the first colon **is** one of\nthem, because model ids contain colons too: `moonshotai/kimi-k2.5:nitro` is an\nordinary OpenRouter id and is read as a model, not as a transport. Model ids are\npassed through verbatim — Baseten's are case-sensitive.\n\n`design-preview`, `inner-pages-design` and `transform-site` have no tier, so an\n`LLM_MODEL_<STEP>` override is the only thing that ever gives them a model;\nwithout one they use the client's default.\n\nMost of these models reason by default, and those tokens come out of the same\ncompletion budget as the answer — asked for 24 tokens, Kimi K3 and both DeepSeek\nV4 models spend every one on hidden thinking and return an empty answer with\n`finish_reason: length`. The client therefore sends `reasoning_effort: none` for\nevery Baseten model it may quiet, listed in `BASETEN_REASONING_EFFORT` in\n`src/OpenAiCompatibleClient.php`. This differs from the `openrouter` profile,\nwhich keeps K3's max effort and leans on a large token floor — a floor any\ncaller pinning its own smaller budget defeats. To reason on a step instead,\nremove that model from the table and give the step a budget that fits the\nthinking.\n\nGLM 5.3 Flash is the exception: it carries its publisher's recommended settings\nin `BASETEN_MODEL_PROFILE` — `temperature: 1`, `top_p: 0.95`, `thinking` left\nenabled, and `tool_stream` — as defaults a request can override. Its effort\ndefaults to `low` rather than the recommended `max`, because it serves the small\ntier, where `max` measured 2.6× the completion tokens and 3.3× the wall time and\ncould not answer the structural steps' pinned budgets at all. A caller that\nwants the publisher's number passes `reasoning_effort: max` on the request,\nwhich also raises that request's token ceiling to give the thinking room. Note\nthat the wpcom proxy accepts the `thinking` object and ignores it; it is sent\nfor the direct `BASETEN_BASE_URL` route, where `reasoning_effort` is not the\nonly lever.\n\nEdit `config/models.json` to change those model ids. To override just one run or\none step (any model id, wins over the config):\n\n- `LLM_MODEL` / `LLM_MODEL_SMALL` — the run-wide large / small tier\n- `LLM_MODEL_<STEP>` — a single step, e.g. `LLM_MODEL_SITE_SPEC=gpt-5.5`\n\nThe OpenRouter profile uses K3 for every quality-critical large-tier step:\n`design-direction-judge`, `design-direction`, `theme-json`, `sections`,\n`page-styles`, and `custom-motion`. Fast K2.5 `:nitro`, with optional reasoning disabled, is\nreserved for the small structural steps. K3's maximum-effort reasoning shares\nits completion budget with the visible answer, so the transport gives it a\nlarger token budget and timeout. OpenRouter demo batches run up to three sites\nin parallel and bound each site's internal request fan-out at four; pass\n`--parallel=<n>` to override the site cap.\n\nOutput lands in `projects/<slug>/`. Each build also writes a run overview —\nper-step times and token spend, totals, and the image tally — to\n`projects/<slug>/logs/project.log` (the same summary printed to the terminal).\nA successful build can also contain `projects/<slug>/warnings.json`. This\nmachine-readable artifact groups non-fatal defects by step id for output the\nbuild still delivered:\n\n```json\n{\n  \"fix-blocks\": [\n    \"parts/example.html block 0: core/paragraph style \\\"opacity\\\" could not be preserved\"\n  ],\n  \"validate-theme\": [\n    \"plugin/pages/home.html: a button link has no href\"\n  ]\n}\n```\n\nWarnings do not make the build fail; inspect the corresponding file under\n`logs/` for full evidence. Mutating repair/serialization steps only warn through\nan exact reviewed, deterministic safe degradation; malformed or unsupported\ninput, unreviewed content loss, and non-convergence remain fatal there. An\nadvisory final validator may warn about residual problems without rewriting the\nalready usable artifact. Operational failures such as unreadable inputs or\nfailed writes remain fatal everywhere.\nRun the unit tests with `php tests/run.php`.\n\n## Preview a built site\n\n`php bin/serve.php <slug>` boots a built project. Studio is the default when\nthe WordPress Studio desktop app is available (macOS and Windows only).\nPlayground is the failover for Linux and CI. Generated Studio sites live\nunder `~/Studio` and stay running after the command returns.\n\n```bash\nphp bin/serve.php bakery\nphp bin/serve.php bakery --runner=studio       # force Studio\nphp bin/serve.php bakery --runner=playground   # force Playground\nphp bin/serve.php bakery --stop                # stop one persistent site\nphp bin/serve.php --stop-all                   # stop every site this checkout created\nphp bin/serve.php --prune                      # remove those sites from ~/Studio\n```\n\n`--runner=studio|playground` (or `SITE_BUILD_RUNNER`) picks the runner: flag,\nthen env, then Studio if available, else Playground with a warning. A Studio\nwe picked ourselves also falls back when it fails to boot, so a finished build\nstill gets a preview; the downgrade is recorded under `site-runner` in\n`warnings.json` and in `build-stats.json`. Naming a runner turns both cases\ninto errors: `--runner=studio` never silently serves something else. `--port`\nand `--workers` apply to Playground only; on Studio one note is printed. Override the Studio workspace with `SITE_BUILD_STUDIO_ROOT`.\n`--prune` removes sites this checkout created (the ones whose marker records\nthis repo path), not hand-made directories under `~/Studio`.\n\n## Build the demo set\n\n`eval/theme-prompts.json` holds a persisted set of demo prompts. Build them all\nin one command — useful as testing evidence for pipeline/theme changes:\n\n```bash\nphp bin/build-demos.php --with-images   # build every demo, with generated images\n```\n\nAn entry may carry a canonical `site_spec` object (the `hearth` demo does): it\nis pre-seeded into the project's `meta.json`, so the site-spec step normalizes\nit deterministically instead of generating one via LLM — a fixed, reproducible\nprobe of the host-supplied-spec path described above.\n\nThe demos build **in parallel** by default (up to three at once for OpenRouter) —\none `bin/build.php` child process per entry, output streamed with a `[slug]`\nprefix. After the builds, each home page is\nbooted headless in WordPress Playground and a full-page screenshot is saved to\n`projects/<slug>/logs/home.png`. Re-runs never overwrite prior output — each\nbuild goes to the next free slug (`tbilisi` → `tbilisi2` → …).\n\nNeeds a text LLM key (`ANTHROPIC_API_KEY`, `XAI_API_KEY`, `OPENAI_API_KEY`, or\n`OPENROUTER_API_KEY`, or `BASETEN_API_KEY`, with the matching `LLM_PROVIDER`)\nand `GOOGLE_VERTEX_API_TOKEN` in `.env`, plus Node.js (for Playground) and a\nChrome/Chromium binary (for the screenshot).\n\nUseful variants:\n\n```bash\nphp bin/build-demos.php --with-images --only=tbilisi     # just one demo\nSITE_BUILD_IMAGE_QA=0 php bin/build-demos.php --with-images  # skip the vision check on delivered heroes\nphp bin/build-demos.php --with-images --provider=openai  # build the set on GPT-5.x\nphp bin/build-demos.php --with-images --parallel=2       # cap concurrent builds\nphp bin/build-demos.php --with-images --no-screenshot    # skip the screenshots\nphp bin/build-demos.php --with-images --serve            # serve all sites afterward\n```\n\n`--serve` boots every built site after the batch and prints the URLs, so the\nwhole demo set can be inspected side by side. Studio (the default when\navailable) creates persistent sites under `~/Studio` and the command returns;\nstop them with `php bin/serve.php --stop-all`. Playground (Linux/CI failover)\nstill binds each site on its own port, and a single Ctrl-C stops all the servers.\n\nEach build normally fires up to ~10 concurrent LLM requests. The OpenRouter\nprofile caps that at four per site, so its default three-site batch reaches at\nmost 12; use `--parallel=<n>` to tune the outer site concurrency.\n\n## Publish a shareable Playground link\n\nUpload a built project to the Playground artifact branch and print a URL that\nopens it directly in WordPress Playground:\n\n```bash\nphp bin/publish-playground.php <slug>\nphp bin/publish-playground.php <slug> --dry-run   # build the ZIP, don't upload\nphp bin/publish-playground.php --list             # list uploaded artifacts\n```\n\nThe uploaded ZIP is a Playground Blueprint bundle. It contains the runnable\nBlueprint plus a complete archive of the project folder for debugging:\n\n```text\nblueprint.json\nproject.zip\n```\n\n`project.zip` contains `project/<slug>/...`, including logs, screenshots, JSON\nartifacts, and the generated theme. By default assets are pushed to a\n`playground-artifacts` branch in the current GitHub repo and served from\n`raw.githubusercontent.com`, which WordPress Playground can fetch in the\nbrowser. Override with `--repo=OWNER/REPO` or `--branch=<branch-name>`.\nUploaded ZIPs are browsable online at\n<https://github.com/matiasbenedetto/minimalistic-site-builder/tree/playground-artifacts>.\n\n## Image prompt debugger\n\nA standalone page for iterating on `AI_IMAGE` prompts **without building a whole\ntheme**. It drives the real `GenerateImagesStep` against a throwaway temp\nproject, so what you see is exactly what the pipeline would produce: the same\nprompt composition, the same site-context grounding, the same Gemini call.\n\nIt comes pre-filled with a site context and 10 example prompts, each with an\neditable subject / page-context / style / aspect-ratio. Use **Generate** on a\ncard to render that one image, or **Generate all** to render every card in one\nconcurrent batch. Each card shows the result, its status, and the exact composed\nprompt sent to the endpoint.\n\n### Run it\n\n```bash\nphp -S localhost:8080 bin/image-debug.php\n```\n\nThen open <http://localhost:8080/>.\n\nRequires `GOOGLE_VERTEX_API_TOKEN` in `.env` (the same token the build uses for\nimages) — without it, the cards report a generation error.\n\n**Notes**\n\n- The page must be served by PHP (not opened as a `file://` page): image\n  generation needs the server-side image client and the secret Vertex token.\n- If the port is already in use (e.g. an SSH tunnel is holding it), pick another:\n  `php -S localhost:8090 bin/image-debug.php`, and forward that port to your\n  browser if you're on a remote host.\n\n## Full-page screenshots\n\n`bin/screenshot/screenshot.js` captures a full-page screenshot of any URL (e.g.\na generated theme served via `bin/playground.php`). It scrolls the page\ntop-to-bottom before capturing so lazy-loaded images far down a tall page are\nactually fetched and rendered — a plain `fullPage` capture leaves them as empty\nboxes (see [issue #31](docs/evidence/issue-31/README.md)).\n\n```bash\nnpm ci   # once, at the repo root; uses your system Chrome, no download\nnode bin/screenshot/screenshot.js http://localhost:9400/ shot.png\n```\n\nPass `--width=<px>` (or set `SHOT_WIDTH`), `--chrome=<path>` (or set\n`CHROME`/`CHROME_BIN`), and `--no-scroll` to reproduce the old un-scrolled\nbehaviour.\n",
  "bytes": 19351,
  "sha": "f2a7a8aff502dec760a273ddb4cdc2632b6a1f20fdaa5b6e41b7edcb2e1027d3",
  "repo_slug": "automattic/minimalistic-site-builder",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_automattic_minimalistic_site_builder_fea15113/readme"
}