
liberate-local
★ 470by 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
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/liberatefront door, not a standalone entry point — it isuser-invocable: false(hidden from the user's autocomplete) anddisable-model-invocation: true(the Skill tool can't invoke it). Instead,/liberate <dir>reads & follows this SKILL.md inline (seeskills/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-dataskill 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, ornone. For JS arrays it reads the source JS; for static content cards the scaffold's records come fromdiscoverHtmlCards. 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-detailsStart this right after resolving inputs and dispatching the data-model fill. Wait for BOTH the background provisioning command and the
model-local-datasubagent 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/queryloops (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_sitewithcreateSite: 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 createonly when no WP install exists atstudioSitePath— a re-run reuses the site (and re-converts idempotently). Admin creds come from envWP_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
falsefor 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/htmlislands into editabledla/editable-htmlblocks (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_PASSin 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
studioCLI on PATH (studio site createprovisions WP + SQLite and starts the site on a Studio-assigned port; the convert auto-resolves the live URL viawp 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-iclasses (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 createwith 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.
npx skills add https://github.com/automattic/studio --skill liberate-localRun this in your project — your agent picks the skill up automatically.
No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.
Licensed under GPL-2.0— you can use, modify, and redistribute it under that license's terms.
View the full license file on GitHub →