Labsco
Automattic logo

liberate-local

470

by automattic · part of automattic/studio

The OWNED LOCAL static-site path of /liberate — dispatched inline by `/liberate` when its input is a local directory (not a URL). Point it at a directory of hand-authored / Claude-generated HTML+CSS+JS and it stands up a fresh WordPress Studio site and converts the source into a native block theme + live editable pages, carrying the source's own CSS/JS for identical parity. The local-source analog of the URL path: no extraction (the source is already on disk), no platform detection — it provisio

🧰 Not standalone. This skill ships with automattic/studio and only works together with that tool — install the tool first, then add this skill.

This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.

Liberate a local static site

Dispatched by /liberate. This is the local-directory branch of the /liberate front door, not a standalone entry point — it is user-invocable: false (hidden from the user's autocomplete) and disable-model-invocation: true (the Skill tool can't invoke it). Instead, /liberate <dir> reads & follows this SKILL.md inline (see skills/liberate/SKILL.md → "Step R — Route on input type"). The steps below run in that shared context.

The owned local-source path: a directory of HTML/CSS/JS → a live WordPress Studio site running a native block theme that carries the source's own design. Unlike the URL path of /liberate (which detects a remote platform and extracts over the network), this path has the source on disk already, so it skips detect/discover/extract entirely and goes straight to: provision the Studio site → process the source → build the theme + pages → capture → parity compare → deterministic repair.

liberate_convert_local_site is the one command that drives all of it (with createSite: true it provisions the Studio target too). This skill is the thin front door that resolves inputs, runs that command, and reports.


Pipeline overview

/liberate-local <source-dir>
│
├─ resolve inputs: source dir · site name · theme slug · Studio site path · output dir
│
├─ if JS-data OR static content cards: start these together, then wait for both:
│     ├─ background provision: studio site create --path <studioSitePath> --start --skip-browser --skip-log-details
│     └─ model-local-data subagent → <outputDir>/data-model.json
│
└─ liberate_convert_local_site({ dir, studioSitePath, createSite:true, … })  ── one command, drives everything:
      1 createSite      studio site create (fresh WP+SQLite, started)  — skipped if the site already exists or was already provisioned (idempotent)
      2 ingest          source HTML → native block sidecars (roundtrip-validated) + per-instance lib-i styles
      3 block-fixer     canonicalize each page through @wordpress/blocks (editor-valid markup)
      4 design capture  serve source over HTTP → palette/typography/breakpoints + self-host Google fonts
      5 theme assemble  block theme: nav from the graph, footer, no-title page templates, carried source.css/js + instance-styles.css (+ add_editor_style)
      6 install         write + activate the theme; create WP Pages from sidecars (idempotent via _source_url); front page; page template
      7 compare         capture the WP replica (desktop+mobile) and score parity vs the source
      8 repair          deterministic parity loop: diff → computed-style probe → parity-patch.css → re-compare (bounded, no AI)

There is no path checkpoint (unlike /liberate): the owned-local path is always carry-to-block-theme — the source IS the design authority. The only decision is the optional editor-fidelity surface (below).


Step-by-step

Step 0 — Resolve inputs

Ask for the source directory if not given. Then derive (let the operator override any):

  • dir — the absolute source directory (HTML/CSS/JS).
  • themeSlug — kebab-case, e.g. basename(dir)maison-clouet.
  • siteTitle — the home page <title>, or a name the operator gives.
  • studioSitePath~/Studio/<slug> for a fresh site (must NOT collide with an existing unrelated site unless you intend to reuse it). Confirm the chosen path with the operator before creating.
  • outputDir — liberation artifacts (sidecars, reports, screenshots). Default ~/Studio/_liberations/<slug> or a run-local dir.

Step 0b — WordPress-driven data (conditional)

If the source renders its main content from JavaScript data (an empty <div id="…"> filled at runtime by a mount call over an in-file array, e.g. a catalog/listing/gallery) or from repeated static-HTML content cards (post-preview cards on index/archive pages), those grids would be empty or non-native in the block editor on a straight carry. In that case, first produce the data model:

  • Dispatch the model-local-data skill via a subagent (so the nested skill runs to completion without interrupting this workflow), pointing it at the source dir + output dir. It runs the scaffold to decide the source: js-array, html-cards, or none. For JS arrays it reads the source JS; for static content cards the scaffold's records come from discoverHtmlCards. It writes <outputDir>/data-model.json (posts/taxonomy + native query loops + a faithful card render).

  • At the same time, start Studio provisioning in the background:

    studio site create --path <studioSitePath> --start --skip-browser --skip-log-details

    Start this right after resolving inputs and dispatching the data-model fill. Wait for BOTH the background provisioning command and the model-local-data subagent to finish before Step 1.

  • When that file exists, the convert step below auto-activates the data path: it inserts the items, registers any needed custom type/taxonomy, replaces the mounts/card containers with core/query loops (editable/analyzable in the editor), and neutralizes the JS data-mounts or static card container + rebinds any modal to per-card DOM islands — while keeping styling/animation/filter JS.

  • Step 1 still calls liberate_convert_local_site with createSite: true. The convert handler is idempotent, so it reuses the already-provisioned site and skips re-provisioning; behavior is unchanged, only the orchestration order and wall-clock time change.

Run the scaffold to decide. If discovered.source is none, fall through to the pure carry path (today's behavior): do not use a data model, pass dataModel: false to the convert call to force the data path off even if a model file is present, and let Step 1 provision or reuse the site idempotently.

Step 1 — Convert (one command)

Call liberate_convert_local_site with createSite: true:

liberate_convert_local_site({
  dir:            "<source-dir>",
  studioSitePath: "~/Studio/<slug>",
  outputDir:      "<output-dir>",
  themeSlug:      "<slug>",
  siteTitle:      "<title>",
  createSite:     true,     // provision the Studio site if it doesn't exist (idempotent)
})
  • createSite runs studio site create only when no WP install exists at studioSitePath — a re-run reuses the site (and re-converts idempotently). Admin creds come from env WP_ADMIN_USER / WP_ADMIN_PASS; omit them and Studio auto-generates (fine for convert-only).
  • carryCss / carryJs default ON — the source's own stylesheet + scripts are carried into the theme (the parity mechanism). Pass false for a tokens-only theme.
  • nativeBehaviors (opt-in) swaps carried JS for native Interactivity blocks (reveal/sticky/tabs/slider/modal); unmapped behaviors land in behavior-gaps.json. Default OFF (maintain the source JS).
  • editableIslands (opt-in) converts carried core/html islands into editable dla/editable-html blocks (text+image bindable, static-save, render-anywhere). Default OFF.
  • editorSurface (opt-in) also scores each page in the live block editor canvas — needs WP_ADMIN_PASS in env; warn-only for carry (it does not flip the verdict).

Narrate progress. On isError, surface the message (a missing source dir, a Studio create failure, a roundtrip/compose failure per page) and stop.

Step 2 — Report

From the handler summary + parity-report.json in the output dir, report:

  • the Studio site (created vs reused) + its live URL,
  • pages composed / failed / empty + low-confidence count,
  • parity: per-page desktop/mobile scores, the averages, allPass, and repair rounds/converged,
  • any warnings (carried-asset notes, sticky-not-carried, behavior gaps).

Honest visual assessment: itemize real differences; do not oversell a match (see the match-section skill's anti-gaming rules if doing a polish pass).


Idempotency & resume

Re-running /liberate-local <dir> on the same studioSitePath:

  • skips site creation (the site exists),
  • re-ingests + re-converts deterministically,
  • re-installs pages by _source_url (no duplicates),
  • re-runs the parity loop.

To start completely fresh, point at a new studioSitePath (or delete the old site via studio site delete).


Notes

  • This path needs Studio installed and the studio CLI on PATH (studio site create provisions WP + SQLite and starts the site on a Studio-assigned port; the convert auto-resolves the live URL via wp option get siteurl).
  • The source is trusted (owned). There is no platform adapter, no network extraction, no products path — it's structure + design carry.
  • The theme is a real block theme: pages are block-editable, the carried CSS is the design authority, and per-instance source styles ride fixer-safe lib-i classes (so editor saves don't drop them). Carried CSS is also loaded into the editor canvas (add_editor_style).
  • Measured timing: a fresh convert with provisioning took about 3m36s, while reusing an already-provisioned site took about 2m. On the JS-data path, overlapping the background studio site create with the model fill hides roughly 1.5m of fixed provisioning cost.
  • Deeper overlap of ingest or design capture would require splitting the convert handler. Keep this workflow to the provisioning overlap unless that handler split exists.