diff --git a/packages/data-liberation-agent/.claude-plugin/marketplace.json b/packages/data-liberation-agent/.claude-plugin/marketplace.json new file mode 100644 index 0000000000..7b7f8c1751 --- /dev/null +++ b/packages/data-liberation-agent/.claude-plugin/marketplace.json @@ -0,0 +1,13 @@ +{ + "name": "data-liberation", + "owner": { + "name": "Automattic" + }, + "plugins": [ + { + "name": "data-liberation", + "source": "./", + "description": "Extract content from closed web platforms (GoDaddy Websites & Marketing, Hostinger, HubSpot, Shopify, Squarespace, Webflow, Weebly, Wix) into WordPress-compatible WXR files. Inspect, extract, QA, and import to WordPress." + } + ] +} diff --git a/packages/data-liberation-agent/.claude-plugin/plugin.json b/packages/data-liberation-agent/.claude-plugin/plugin.json new file mode 100644 index 0000000000..f754ebde89 --- /dev/null +++ b/packages/data-liberation-agent/.claude-plugin/plugin.json @@ -0,0 +1,30 @@ +{ + "name": "data-liberation", + "description": "Extract content from closed web platforms (GoDaddy Websites & Marketing, Hostinger, HubSpot, Shopify, Squarespace, Webflow, Weebly, Wix) into WordPress-compatible WXR files. Inspect, extract, QA, and import to WordPress.", + "version": "0.2.2", + "author": { + "name": "Automattic" + }, + "homepage": "https://github.com/Automattic/data-liberation-agent", + "repository": "https://github.com/Automattic/data-liberation-agent", + "license": "GPL-2.0-or-later", + "keywords": [ + "content-extraction", + "data-liberation", + "godaddy", + "hostinger", + "hubspot", + "migration", + "shopify", + "squarespace", + "webflow", + "weebly", + "wix", + "wordpress", + "wxr" + ], + "mcp": { + "command": "npx", + "args": ["tsx", "src/mcp-server.ts"] + } +} diff --git a/packages/data-liberation-agent/.claudeignore b/packages/data-liberation-agent/.claudeignore new file mode 100644 index 0000000000..967c76da9d --- /dev/null +++ b/packages/data-liberation-agent/.claudeignore @@ -0,0 +1,2 @@ +node_modules/ +*.lock diff --git a/packages/data-liberation-agent/.codex-plugin/plugin.json b/packages/data-liberation-agent/.codex-plugin/plugin.json new file mode 100644 index 0000000000..d769b11fc4 --- /dev/null +++ b/packages/data-liberation-agent/.codex-plugin/plugin.json @@ -0,0 +1,31 @@ +{ + "name": "data-liberation", + "description": "Extract content from closed web platforms (GoDaddy Websites & Marketing, Hostinger, HubSpot, Shopify, Squarespace, Webflow, Weebly, Wix) into WordPress-compatible WXR files. Inspect, extract, QA, and import to WordPress.", + "version": "0.2.2", + "author": { + "name": "Automattic" + }, + "homepage": "https://github.com/Automattic/data-liberation-agent", + "license": "GPL-2.0-or-later", + "skills": "./skills/", + "mcpServers": "./.mcp.json", + "interface": { + "displayName": "Data Liberation", + "shortDescription": "Extract closed-platform sites into WordPress-compatible exports", + "longDescription": "Inspect, extract, QA, and import content from closed website platforms into WordPress-compatible WXR files.", + "developerName": "Automattic", + "category": "Developer Tools", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "websiteURL": "https://github.com/Automattic/data-liberation-agent", + "defaultPrompt": [ + "Inspect a site for liberation", + "Extract this site into WordPress-compatible WXR", + "QA a liberated WordPress import" + ], + "screenshots": [] + } +} diff --git a/packages/data-liberation-agent/.github/pull_request_template.md b/packages/data-liberation-agent/.github/pull_request_template.md new file mode 100644 index 0000000000..be8bc15f92 --- /dev/null +++ b/packages/data-liberation-agent/.github/pull_request_template.md @@ -0,0 +1,21 @@ +## What this changes + + +## How I found it + + +## Type of change +- [ ] New Wix API endpoint discovered +- [ ] Better content type handling +- [ ] Bug fix (something broke) +- [ ] Performance improvement +- [ ] New Wix feature support +- [ ] Documentation + +## Tested against +- [ ] Real Wix site (describe briefly, no personal info needed) +- [ ] Scripts run without errors +- [ ] Output looks correct + +## Discovery log +- [ ] Entry added to DISCOVERIES.md diff --git a/packages/data-liberation-agent/.gitignore b/packages/data-liberation-agent/.gitignore new file mode 100644 index 0000000000..14c416ed8c --- /dev/null +++ b/packages/data-liberation-agent/.gitignore @@ -0,0 +1,26 @@ +graphify-out/ +output/ +skills/replicate-workspace/ +node_modules/ +.DS_Store +*.log +dist/ +docs/plans/ +docs/superpowers/ +qa-screenshots/ +.claude/ +.liberation-lock +.playwright-mcp/ +.superpowers/ +.worktrees/ +.tmp-test/ +TODOS.md + +# Streaming compose-then-install scratch dir +.tmp-compose/ + +# Scratch reference clones (e.g. source-site mirrors, try-wordpress) +clones/ + +# This repo uses npm (package-lock.json) — a stray pnpm lockfile shouldn't land +pnpm-lock.yaml diff --git a/packages/data-liberation-agent/.mcp.json b/packages/data-liberation-agent/.mcp.json new file mode 100644 index 0000000000..5b0c593718 --- /dev/null +++ b/packages/data-liberation-agent/.mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "data-liberation": { + "command": "npx", + "args": ["tsx", "${CLAUDE_PLUGIN_ROOT:-.}/src/mcp-server.ts"], + "cwd": "${CLAUDE_PLUGIN_ROOT:-.}" + } + } +} diff --git a/packages/data-liberation-agent/.nvmrc b/packages/data-liberation-agent/.nvmrc new file mode 100644 index 0000000000..2bd5a0a98a --- /dev/null +++ b/packages/data-liberation-agent/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/packages/data-liberation-agent/AGENTS.md b/packages/data-liberation-agent/AGENTS.md new file mode 100644 index 0000000000..f69a22ce81 --- /dev/null +++ b/packages/data-liberation-agent/AGENTS.md @@ -0,0 +1,78 @@ +# AGENTS.md — Instructions for AI Agents + +## Overview + +`data-liberation-agent` extracts content from closed web platforms (GoDaddy Websites & Marketing, Hostinger, HubSpot, Shopify, Squarespace, Webflow, Weebly, Wix) and produces WordPress-compatible WXR files. All eight platform adapters are implemented, plus a generic `default` fallback adapter for sites that match no known platform. + +Three entry points — MCP server (35 tools), CLI (`src/cli.ts`), and Claude Code plugin (`claude plugin add .`) — all share `src/lib/` and `src/adapters/`. The plugin just wraps the MCP server. + +The `scripts/` directory contains legacy standalone extraction scripts (Squarespace via CDP, Wix via Playwright). These predate the adapter system and are kept for reference. + +## Adding a New Platform + +1. Create `src/adapters//` — a directory whose `index.ts` assembles and exports the `PlatformAdapter` (inline `detect` + `discover`/`extract` imported from focused siblings: `discover.ts`, `extract.ts`, `content.ts`, `media.ts`, `products.ts`, `types.ts`, …). Keep `index.ts` a thin assembler and re-export the adapter's public API from it so external imports only reference `/index.js`. See `src/adapters/webflow/` (smallest) or `src/adapters/shopify/` (fuller split) as references. +2. Register it in `src/mcp-server.ts` (see the "Static adapter imports" comment) — import from `./adapters//index.js`. +3. Add platform-specific barriers and workarounds as inline comments in the adapter +4. Update the supported platforms table in `README.md` + +Adapters produce structured content and call into `WxrBuilder`, `ExtractionLog`, `ImportSession`, `MediaStubStore`, and `media` utilities for output. + +### Optional per-adapter capabilities + +Both are opt-in, declared on the adapter object, and consumed by later pipeline stages — the stage subsystems (screenshotter, reconstructor) stay adapter-agnostic; the handlers that already resolve the adapter do the wiring. Types live in `src/adapters/page-actions.ts`. + +- **`capture?: AdapterCapture`** (seam 1) — `removeSelectors: string[]` (plus an optional imperative `prepare(page, ctx)`) removed from the live page in `screenshotter.ts` after settle and BEFORE screenshots / carried HTML / mobile carry / `SectionSpec` are captured, so one removal cleans every artifact. Best-effort (never fails capture). Put it in `/capture.ts`; example: `src/adapters/shopify/capture.ts` (strips `#upCart` + Klaviyo teaser chrome). +- **`blocks?: AdapterBlocks`** (seam 2) — a content→Gutenberg-blocks recipe (a declarative `recipes[]` table and/or a whole-body `htmlToBlocks(html, ctx)` fn). Applied ONLY on the blocks reconstruct path; the theme/carry path never invokes it. Two firing points, both blocks-path-gated: (a) per visual section in `page-reconstruct.ts`, before the `core/html` fallback island; (b) in BULK over post/page `content:encoded` bodies via the `liberate_blockify_wxr` tool (`src/lib/extraction/blockify-wxr.ts`), run after extraction and before import. Put the recipe in `/blocks.ts`; example: `src/adapters/squarespace/blocks.ts`. + +## Resume State Files + +Four files cooperate to make runs resumable. Never write to them outside of the extraction-log lockfile (`mcp-server.ts` and `ui/discover.tsx` bracket the whole pipeline): + +- `extraction-log.jsonl` (`ExtractionLog`) — append-only per-URL dedupe. Source of truth for "did we process this URL." +- `session.json` (`ImportSession`) — stage, original opts, per-entity counts, adapter pagination cursors. Single-writer, atomic rename. Corrupt files become `session.json.corrupt.` (never silently deleted). +- `media-stubs.json` (`MediaStubStore`) — per-asset status (`success` / `error` / `ignored`) with retry cap (default 3). Successful writes are buffered via `flush()`; failures persist immediately. +- `products.jsonl` — streaming Woo product output. `openStream({resume: true})` appends instead of truncating, preserving GraphQL-emitted products across mid-run crashes. + +A fifth artifact, `sections/.json` (`SectionSpecsStore`), is a per-URL capture-once cache rather than shared run state, so it does NOT need the lockfile: each file is independent and written atomically (unique tmp + rename). The screenshot/capture phase runs `extractFull` on the settled desktop page (1440×900) — which returns `{ specs, landmarks }` — and persists the `SectionSpec[]` (each carrying a compact CSS `selector` that locates it in the source DOM) plus a top-level **landmark census** (`main`/`nav`/`header`/`footer`, consumed by the reconstruction region audit) here, keyed by `slugify(url)` and self-describing (records `sourceUrl` + a `SECTION_SPECS_SCHEMA` version). The reconstruction phase (`liberate_reconstruct_pages`) reads this instead of re-running Playwright, falling back to a live extract only on a cache miss — absent, corrupt, schema drift, or a slug collision (recorded `sourceUrl` ≠ requested) — and persisting the live result (specs + census) for the next run. Bump `SECTION_SPECS_SCHEMA` whenever `SectionSpec`'s shape OR the persisted envelope (e.g. the landmark census) changes so stale caches invalidate instead of silently degrading fidelity. The `selector` is built Node-side from browser-emitted `SelectorParts` via the pure `buildSelector` (`section-selector.ts`) — the `page.evaluate` closure only emits plain parts; both the section selectors and the census come from the SAME browser walk, so the region audit's census↔section join holds by construction. + +Adapters call `ImportSession.loadOrCreate(outputDir, id, opts, { resume })` and pass the session to `runExtractionLoop` via `ExtractionLoopOpts.session`. The shared loop updates stage + per-entity counts automatically. + +## Shopify GraphQL Path + +When `adminToken` is present, `shopifyAdapter.extract` fetches products via the Shopify Admin GraphQL API (pinned to `2025-04`) instead of the public JSON API. The GraphQL path: + +- Requires a `*.myshopify.com` hostname — throws if a custom storefront domain is derived and `shopDomain` wasn't passed explicitly. +- Paginates via `endCursor` stored in `session.cursors['shopify:products:endCursor']` (resumable). +- Tracks already-emitted product handles in `session.cursors['shopify:products:emittedHandles']` for idempotent CSV output across crashes. +- Falls back to the JSON API + URL loop on any GraphQL failure. +- Has `MAX_PAGES = 10000` and a non-advancing cursor guard to prevent infinite loops. + +## Non-obvious Details + +- **MCP server runs `tsx src/mcp-server.ts` as ONE long-lived process — editing `src/` does NOT hot-reload it.** ESM modules are cached per process, so a re-called `liberate_*` tool keeps running the code that was loaded at server start. After editing handler/lib source, either restart the MCP server (so all callers pick it up) or, for a quick one-off re-run, drive the handler from a fresh `tsx` process (import the handler, pass a minimal `HandlerContext` with `textResult`/`errorResult`). Unit tests (`vitest`) always use the on-disk source, so they reflect edits immediately — the staleness is only the running MCP server. +- DLA consumes `@automattic/blocks-engine` through the local pre-publish override `"file:../blocks-engine/packages/blocks-engine"`; standalone `npm install` therefore needs the sibling `blocks-engine` checkout until the package is published. On publish, flip the dependency to the authorized semver range (for example `"^0.x.y"`). Publishing itself remains gated on explicit user authorization. +- **`tsx scripts/*.ts` that call `page.evaluate` must create the page via `newShimmedPage` from `scripts/_pw.ts`** — tsx's esbuild keepNames rewrites named functions to `__name(...)`, which is undefined in the browser context Playwright serializes into (`ReferenceError: __name is not defined`). The shim installs a `__name` identity via `addInitScript`. vitest-run code is unaffected (its transform doesn't keepNames). +- WXR builder targets WXR 1.2 spec compliance +- `core/html` islands convert to editable `dla/editable-html` blocks (in-canvas render → visible + styled + text/image-editable in the block editor; static-save = byte-identical front-end output). This is the DEFAULT across all three reconstruct paths (carry, local, block — every `core/html` island converts except those wrapping nested `wp:` block delimiters); opt out with `editableIslands: false` (or `EDITABLE_ISLANDS=0` on the carry driver). Conversion runs before the block-fixer canonicalization and ships + activates the `dla-editable-html` plugin once when any island converts. +- Page-reconstruction full-width vs constrained DEFERS TO THE SOURCE: `SectionSpec.fullBleed` (a section carrying an IMAGE — foreground or background, height ≥ 100px — spanning ≥ 92% of the viewport) drives the page's `main`/`post-content` layout type. Any non-chrome (`footer`/`nav`) full-bleed section → `default` (full-width); otherwise `constrained`. This is independent of `heroIsCover`, which only controls the transparent overlay header. Specs without `fullBleed` default to constrained (back-compat). +- `classifyUrl` types: `homepage`, `post`, `product`, `gallery`, `event`, `page` (no `category`/`author`/`other`) +- Media filename collision handling uses numeric suffixes (`-2`, `-3`), not hashes +- `detect-platform` uses domain-level URL patterns and HTTP fingerprinting (headers + HTML markers) — no path-based detection +- **Fallback adapter:** sites that detect as `unknown` (or name an unregistered platform) resolve to the `default` adapter (`src/adapters/default/`) via `resolveAdapter` (`src/adapters/resolve-adapter.ts`), used by all three resolver sites (`mcp-server.ts`, `ui/discover.tsx`, `ui/inspect.tsx`). Detection is unchanged — it still reports `unknown`; the fallback is at *resolution* (so the "No adapter available" error is now unreachable). The adapter is platform-agnostic: Playwright-rendered HTML → cheerio main-content + media + JSON-LD products; its `detect()` returns `false` (nothing selects by `.detect()`). Products gotcha: the shared loop's `extractProductFromHtml` reads JSON-LD from `ExtractedPage.content` (not a re-fetch), so `default` re-attaches the Product `ld+json` script to `content` (content extraction strips ` + + +
+ Data Liberation · Part I — Scaffold + Lesson 1 / 17 +
+ +
+
Lesson 01 · The scaffold
+

The Two-Phase Spine

+

Everything in this codebase hangs off one sentence: extract a URL into artifacts, then reconstruct those artifacts into a WordPress site. Get this skeleton in muscle memory and every other lesson is just detail on a bone you already know.

+ +

When you make an architecture call in this repo, the first question is always which +phase, which stage. So before anything else, hold the whole shape in your head:

+ +
+
+ IN +
A URL (or an owned local directory)
+ e.g. https://someshop.example
+
+
+ P1 +
Extraction phase — turn the live site into on-disk artifacts.
+ → output.wxr · products.jsonl · screenshots/ · palette/typography/breakpoints.json
+
+
+ P2 +
Reconstruction phase — turn those artifacts into a real, styled WordPress site.
+ → a block theme + imported content, running in Studio
+
+
+ OUT +
A self-hosted WordPress site that looks and reads like the original.
+
+
+ +

Why two phases, not one

+

The split is the most important design decision in the system, and it is deliberate. +Extraction is expensive and fragile — it hits a live site, fights bot defenses, +downloads media, and can crash halfway. Reconstruction is cheap and deterministic — +it reads local files and rebuilds. Separating them means you can re-run reconstruction a +hundred times against one expensive extraction. Every architecture conversation about +"can we change how pages are built" lives entirely in phase two and never re-touches the +live site.

+ +
+ The load-bearing idea + Extraction is a capture-once, resumable investment. Reconstruction is a + repeatable, deterministic transform over that capture. The artifacts on + disk are the contract between them — that is why the resume-state files (Lesson 6) matter + so much. +
+ +

Extraction has named stages

+

Phase one is not a blob — it is a fixed sequence of stages, persisted so a crashed run +resumes where it left off. This exact union lives in the source:

+ +
initial → discovering → extracting → downloading-media
+        → screenshotting → finalizing → complete
+                                        (error from any stage)
+

src/lib/resume-state/import-session.ts:22

+ +

You will drill into each of these. For now, just notice: discover finds +the URLs, extract turns each URL into content + products, download-media +fetches the assets, screenshot captures the design. Reconstruction is a +separate phase driven by its own tools — it is not in this union.

+ +
+ Common mental-model error + Don't picture reconstruction as a stage after complete. The + ImportStage union ends at complete — that is the end of + extraction. Reconstruction is a different set of tools (liberate_reconstruct_*) + reading the artifacts. The default streaming run blurs them together in time, but + architecturally they are two systems. +
+ +
+
Why is the extraction / reconstruction split the central design decision?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Without scrolling up: name the seven extraction stages in order, and say which artifact + the extract stage and the screenshot stage each produce. +
Check yourself + initial → discovering → extracting → downloading-media → screenshotting → finalizing → + complete. Extract produces output.wxr + products.jsonl; + screenshot produces screenshots/ + palette/typography/breakpoints.json. +
+
+ +
+ Primary source
+ Read the stage diagram and union at the top of + src/lib/resume-state/import-session.ts (lines 5–30). It is the + most authoritative one-screen statement of the extraction spine in the whole repo. +
+ +

Anything unclear? I'm your teacher — ask me to expand any stage, draw the +flow differently, or connect it to a decision you're weighing.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0002-three-entry-points.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0002-three-entry-points.html new file mode 100644 index 0000000000..c9f6ae8656 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0002-three-entry-points.html @@ -0,0 +1,128 @@ + + + + + +2 · Three Entry Points, One Core + + + + +
+ Data Liberation · Part I — Scaffold + Lesson 2 / 17 +
+ +
+
Lesson 02 · The scaffold
+

Three Entry Points, One Core

+

Same machine, three front doors. Knowing which door a request came through tells you which thin shell you're in — and that all of them bottom out in the same shared core.

+ +

There is exactly one binary, data-liberation, defined by +src/cli.ts. It is a dispatcher that branches on the first +argument. Everything else is a shell over src/lib/ and src/adapters/.

+ +
+
+ 1 +
Streaming CLIdata-liberation <url>
+ The default. Boots the live "watch loop" (ui/watch.tsxwatch-runner.ts). + cli.ts:271 · --no-watch falls back to the legacy batch path
+
+
+ 2 +
CLI subcommandsinspect / import / qa / verify / setup / preview / screenshot / compare …
+ Each lazy-imports a ui/<cmd>.js runner. + cli.ts:105 · the manual, à-la-carte surface
+
+
+ 3 +
MCP stdio serverdata-liberation mcp
+ Exposes ~38 liberate_* tools to agents/skills. + cli.ts:16 → src/mcp-server.ts · the Claude Code plugin wraps this
+
+
+ +

The shape that matters

+

Picture an hourglass. Three entry points at the top, one shared core at the waist, the +platform adapters at the bottom:

+ +
  CLI watch     CLI subcommands     MCP tools          ← entry (thin shells)
+       \              |              /
+        \             |             /
+         ▼            ▼            ▼
+      src/lib/*   +   src/adapters/*                    ← the core
+        (wxr, resume-state, replicate, screenshot…)
+ +

The CLI UI runners (src/ui/*) and the MCP handlers +(src/mcp-server/handlers/*) are both thin. Neither contains real logic. +This is why you can drive the same behavior from a skill (MCP) or from a terminal +(CLI) — they call the same functions.

+ +
+ Why this matters for architecture calls + When you decide where a new capability lives, the answer is almost always + "in src/lib/, exposed through a thin handler." Putting logic in the handler + or the UI runner is the anti-pattern — it can only be reached from one door. By design, + agents are the primary runtime, so the MCP door is first-class, not an + afterthought. +
+ +

The agent door is the strategic one

+

Entry point 3 is not just "the API." The project's direction is agent-driven: the +data-liberation:* Skills are the product surface for a Claude Code +user. They orchestrate the liberate_* tools. So when you reason about UX, the +relevant "user" is often an agent following a skill, and the relevant "interface" is the +tool schemas in mcp-server.ts.

+ +
+ Live-process gotcha + The MCP server runs tsx src/mcp-server.ts as one long-lived + process. Editing src/ does not hot-reload it — ESM modules + are cached per process. After editing handler/lib source you must restart the server (or + drive the handler from a fresh tsx process) for callers to see the change. + Unit tests always use on-disk source, so they reflect edits immediately. +
+ +
+
A new extraction capability is being added. Where should the real logic live?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the three entry points and the single file that dispatches between them. Then say + which one the Claude Code plugin and the data-liberation:* skills use. +
Check yourself + Streaming watch CLI, CLI subcommands, MCP stdio server — all dispatched by + src/cli.ts. The plugin and skills use door 3 (MCP). +
+
+ +
+ Primary source
+ Skim the branch logic in src/cli.ts (the args[0] + switch), then the tool registry + handlers dispatch map in + src/mcp-server.ts (~line 741). Seeing the handler map drives + home how thin the shells are. +
+ +

Want me to trace one tool from schema → handler → lib function end to end? +Just ask.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0003-fork-in-the-road.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0003-fork-in-the-road.html new file mode 100644 index 0000000000..1924f528c5 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0003-fork-in-the-road.html @@ -0,0 +1,129 @@ + + + + + +3 · The Fork in the Road + + + + +
+ Data Liberation · Part I — Scaffold + Lesson 3 / 17 +
+ +
+
Lesson 03 · The scaffold
+

The Fork in the Road

+

Reconstruction is not one path — it is three, and choosing between them is the single most consequential decision an operator (or you) makes. This is the lesson you'll reach for most in architecture conversations.

+ +

Once extraction has produced artifacts, there are three different ways to turn them into a +WordPress site. They trade native-ness against pixel fidelity.

+ + + + + + + + + + + + + + + + + + + + + + + +
PathToolWhat it doesTradeoff
Blocksliberate_reconstruct_pagesRebuilds each page into native Gutenberg block markup from its captured section specs.Most editable & WordPress-native; fidelity depends on how well sections map to blocks.
Carryliberate_reconstruct_pages_carryCarries sanitized source HTML + scoped CSS verbatim into core/html islands; self-hosts media.Highest pixel fidelity; least native (it's the source HTML wearing a WordPress hat).
Theme / localliberate_convert_local_siteConverts an owned local static-site directory (no network extraction) into a live block theme.For source you already own; different front door entirely (no detect/discover).
+ +

The axis you're actually choosing on

+

Think of one slider: how much of the source's literal HTML/CSS survives into the +output.

+ +
  native blocks  ◄───────────────────────────►  verbatim source
+   (Blocks path)                                  (Carry path)
+   editable, clean markup            pixel-perfect, opaque core/html
+   risk: layout drift                 risk: not really "WordPress content"
+ +

The carry path wins on fidelity because it doesn't interpret the source — it +ships it. The blocks path wins on editability because it does interpret the source +into first-class blocks a user can edit in Gutenberg. Neither is "better"; they answer +different missions.

+ +
+ The bridge: editable islands + The two paths share a bridge. By default, core/html islands convert to + dla/editable-html blocks — visible, styled, and editable in the + block editor, with identical front-end output. So even on the carry path the islands aren't + purely "opaque HTML" — they're editable in-canvas. This editable island is a real + architectural achievement, not a detail. +
+ +

Why this is a hard, early gate

+

This path choice is a decision gate that fires early — right after +discovery, while the operator is present — and is never auto-selected. The +data-liberation:liberate skill is built so a URL run stops and asks +which reconstruct path to take before spending money on extraction. That gate exists because +the choice changes what you capture and how you spend the expensive phase.

+ +
+ How to reason about the choice + Ask: does the user need to edit this content as WordPress, or do they need it to look + exactly like the original? Editing → blocks. Fidelity-first (and the source is a + complex builder site like Wix) → carry. Owned static source on disk → local/theme path, + which skips detection entirely. +
+ +
+
A client's Wix site must look pixel-identical and they won't edit it much. Which path?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the three reconstruct tools and the one axis they trade on. Then state when + in the run the path question is asked, and why it's asked then. +
Check yourself + liberate_reconstruct_pages (blocks), liberate_reconstruct_pages_carry + (carry), liberate_convert_local_site (local/theme). Axis = native blocks ↔ + verbatim source. Asked right after discovery, before extraction, because it changes what + the expensive capture phase needs to collect. +
+
+ +
+ Primary source
+ Read the path-gate description in the data-liberation:liberate + skill, then compare the two handlers + src/mcp-server/handlers/reconstruct-pages.ts and + …/reconstruct-pages-carry.ts side by side to feel the difference. +
+ +

Lessons 8–10 drill into each path. Want a decision-tree cheat sheet for the +choice? Ask and I'll add one to the reference shelf.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0004-the-adapter-pattern.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0004-the-adapter-pattern.html new file mode 100644 index 0000000000..c0f7eb7daf --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0004-the-adapter-pattern.html @@ -0,0 +1,147 @@ + + + + + +4 · The Adapter Pattern + + + + +
+ Data Liberation · Part II — Extraction + Lesson 4 / 17 +
+ +
+
Lesson 04 · Extraction drill
+

The Adapter Pattern

+

One pipeline serves nine platforms because every platform-specific quirk is quarantined behind a uniform three-method interface. This is the seam that keeps the core from rotting into a pile of if (platform === …).

+ +

An adapter is a directory under src/adapters/<platform>/ +whose index.ts assembles and exports a PlatformAdapter. The nine:

+ +
default  godaddy-wm  hostinger  hubspot  shopify
+squarespace  webflow  weebly  wix
+ +

webflow/ is the smallest reference; shopify/ is the fullest +split (discover.ts, extract.ts, content.ts, +media.ts, products.ts…). index.ts stays a thin +assembler.

+ +

The three methods every adapter exposes

+
+
+ +
detect — "is this site mine?" Domain patterns + HTTP + fingerprinting (headers + HTML markers). No path-based detection.
+
+
+ +
discover — find the URLs: sitemap, nav, URL inventory, + archetype counts.
+
+
+ +
extract — turn each URL into structured content + + products, feeding WxrBuilder, MediaStubStore, products stream.
+
+
+ +

Detection and resolution are two different steps

+

This trips people up. Detection reports a platform id (possibly +unknown). ResolutionresolveAdapter in +src/adapters/resolve-adapter.ts — maps that id to an actual +adapter object:

+ +
exact id match  →  that adapter
+unknown / unregistered  →  the `default` adapter
+(null only if `default` itself is missing — unreachable in practice)
+ +
+ Why the fallback matters + Because resolution always lands on some adapter, a "No adapter available" + error is unreachable. The default adapter is platform-agnostic: + Playwright-rendered HTML → cheerio main-content + media + JSON-LD products. Its own + detect() returns false — nothing selects it; it is only + ever reached by fallback. Three call sites share this seam (MCP server, the discover UI, + the inspect UI), so behavior can't drift between front doors. +
+ +

The two opt-in seams an adapter can declare

+

Beyond the three methods, an adapter may declare two optional capabilities. Both +are consumed by later pipeline stages, which stay adapter-agnostic — the handler +that already resolved the adapter does the wiring. Types live in +src/adapters/page-actions.ts.

+ + + + + + + + + + + + + + + +
SeamFieldFires where
Capturecapture?: AdapterCaptureIn screenshotter.ts, before screenshots/HTML/specs are + captured. removeSelectors[] strips platform chrome (e.g. Shopify's + #upCart) so one removal cleans every artifact. Best-effort.
Blocksblocks?: AdapterBlocksOnly on the blocks reconstruct path. A content→Gutenberg recipe applied per + section and in bulk over post bodies. The theme/carry path never invokes it.
+ +
+ The architectural lesson + Platform knowledge is pushed to the edge (the adapter) and the core stages stay + generic. When you're tempted to special-case a platform deep in a shared stage, the right + move is almost always a new adapter capability + a generic consumer — exactly how + capture and blocks are built. That keeps the blast radius of + platform quirks to one directory. +
+ +
+
A site detects as unknown. What happens, and what does its content come from?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the three adapter methods, the two optional seams, and the difference between + detection and resolution. +
Check yourself + Methods: detect / discover / extract. Seams: capture (pre-screenshot chrome + removal) and blocks (blocks-path recipe). Detection = reports a platform id; + resolution = resolveAdapter maps id → adapter, with default as + fallback. +
+
+ +
+ Primary source
+ Read src/adapters/webflow/index.ts (smallest adapter) to see the + assembler shape, then src/adapters/resolve-adapter.ts for the + fallback, then src/adapters/page-actions.ts for the two seam types. +
+ +

Want to walk the Shopify adapter's GraphQL product path, or see exactly what a +capture recipe strips? Ask.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0005-capture-and-sectionspec.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0005-capture-and-sectionspec.html new file mode 100644 index 0000000000..0533678586 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0005-capture-and-sectionspec.html @@ -0,0 +1,119 @@ + + + + + +5 · Capture & the SectionSpec + + + + +
+ Data Liberation · Part II — Extraction + Lesson 5 / 17 +
+ +
+
Lesson 05 · Extraction drill
+

Capture & the SectionSpec

+

Screenshots are the obvious output of the capture stage. The quiet output — the SectionSpec — is the one that makes deterministic block reconstruction possible. This is where design becomes data.

+ +

The screenshotting stage does more than take pictures. On the settled desktop +page (1440×900) it runs extractFull, which returns +{ specs, landmarks } and persists them.

+ +

What gets produced

+ + + + + + + + + +
ArtifactWhat it is
screenshots/{desktop,mobile}/<slug>.pngFull-page + post-scroll captures.
html/<slug>.htmlThe rendered HTML cache.
screenshots/manifest.jsonURL → files join (the only link back to content; nothing is injected into the WXR).
sections/<slug>.jsonThe SectionSpec[] + landmark census. The star of this lesson.
palette / typography / breakpoints.jsonSite-wide design tokens aggregated at run end.
+ +

The SectionSpec, precisely

+

A SectionSpec is a computed-style description of one visual section of a +page, each carrying a compact CSS selector that re-locates it in the source DOM. +Reconstruction reads these instead of re-running Playwright. The store is +src/lib/replicate/section-specs-store.ts (schema v9).

+ +
+ Why a cache, and why it's special + sections/<slug>.json is a capture-once cache, not shared run + state — so unlike the other resume files it needs no lockfile. Each file is independent, + written atomically (unique tmp + rename), keyed by slugify(url), and + self-describing (records its sourceUrl + schema version). Reconstruction + falls back to a live extract only on a cache miss: absent, corrupt, schema drift, or a + slug collision (recorded sourceUrl ≠ requested). +
+ +

The landmark census

+

The same browser walk that emits section selectors also emits a top-level +landmark census: the tally of main / nav / +header / footer. Because the selectors and the census come from +one walk, the later region audit's "did this landmark survive?" join holds by +construction — they can't disagree about what was on the page.

+ +
+ Schema-version discipline + Bump SECTION_SPECS_SCHEMA whenever SectionSpec's shape or + the persisted envelope (e.g. the census) changes — stale caches must invalidate instead of + silently degrading fidelity. A known failure mode: a schema bump paired with a weak live + re-capture can corrupt every spec on a run. The cache-miss path must walk the saved HTML + robustly, not half-re-extract. +
+ +

One spec field worth knowing now: fullBleed

+

Page layout defers to the source. A section carrying an image (foreground or +background, height ≥ 100px) that spans ≥ 92% of the viewport is fullBleed. Any +non-chrome full-bleed section pushes the page's main layout to full-width; +otherwise it's constrained. This is independent of heroIsCover (which only +controls the transparent overlay header). It's a clean example of the design philosophy: +measure the source, don't guess the layout.

+ +
+
Why does the section cache not need the extraction-log lockfile that the other state files use?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + What two things does extractFull return, and why does pairing them in one + browser walk matter downstream? +
Check yourself + { specs, landmarks } — the SectionSpec[] and the landmark census. + One walk means the region audit's census↔section join is correct by construction; they + can't disagree about the page. +
+
+ +
+ Primary source
+ Read src/lib/replicate/section-specs-store.ts (the store + + schema), and find extractFull / buildSelector in + src/lib/replicate/ to see how browser-emitted + SelectorParts become a stable Node-side selector. +
+ +

The selector-building split (browser emits parts, Node builds the +string) is subtle and load-bearing — ask me to unpack the __name gotcha behind it.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0006-resume-state-contract.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0006-resume-state-contract.html new file mode 100644 index 0000000000..052b61b63b --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0006-resume-state-contract.html @@ -0,0 +1,133 @@ + + + + + +6 · The Resume-State Contract + + + + +
+ Data Liberation · Part II — Extraction + Lesson 6 / 17 +
+ +
+
Lesson 06 · Extraction drill
+

The Resume-State Contract

+

Extraction is expensive and crashes happen. The system survives because five on-disk stores make every stage idempotent. Knowing exactly what each one owns is how you reason about "what happens if this dies halfway."

+ +

Four files cooperate to make a run resumable, plus a fifth per-URL cache. The rule: +never write to the shared four outside the extraction-log lockfile +(mcp-server.ts and ui/discover.tsx bracket the whole pipeline).

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileOwnerOwns
extraction-log.jsonlExtractionLogAppend-only per-URL dedupe. Source of truth for "did we process this URL."
session.jsonImportSessionStage, original opts, per-entity counts, adapter pagination cursors. Single-writer, atomic rename.
media-stubs.jsonMediaStubStorePer-asset status (success/error/ignored) + retry cap (default 3) + installed wpPostId.
products.jsonlWoo CSV writerStreaming Woo product output, one product per line. openStream({resume:true}) appends.
sections/<slug>.jsonSectionSpecsStorePer-URL capture-once spec cache (Lesson 5). Independent files; no lockfile.
+ +

The pattern behind all of them

+

Each store implements the same idea in its own way: write so that a crash leaves +recoverable state, and a re-run skips what's already done.

+
    +
  • Append-only (extraction-log.jsonl, products.jsonl) + — a partial line is the worst case; resume appends.
  • +
  • Atomic rename (session.json, section files) — a write is + all-or-nothing; you never read a half-written file.
  • +
  • Status + retry cap (media-stubs.json) — failures persist + immediately and are retried up to a cap, so a flaky CDN doesn't wedge the run.
  • +
+ +
+ Corruption is never silent + A corrupt session.json becomes session.json.corrupt.<ts> — + it is preserved, never deleted. This is a recurring value in the codebase: degrade loudly, + keep the evidence. The same instinct shows up in the diagnostics artifacts (Lesson 12). +
+ +

How a stage uses it

+

Adapters call ImportSession.loadOrCreate(outputDir, id, opts, { resume }) and +pass the session into runExtractionLoop via ExtractionLoopOpts.session. +The shared loop updates stage + per-entity counts automatically. So an adapter author mostly +gets resumability for free — they emit content; the loop and the stores handle the +bookkeeping.

+ +
+ Why this is an architecture-grade concern + When you evaluate a change to extraction, the question "what does this do to resume?" is + load-bearing. A change that writes shared state outside the lockfile, or that makes a store + non-idempotent, can corrupt long runs in ways tests rarely catch. The faultpoint + crash-harness exists precisely to inject crashes and prove recovery — reach for it when a + change touches these stores. +
+ +
+
Which file is the authoritative answer to "have we already processed this URL?"
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + List the five stores and one word for what each owns. Then name the three crash-safety + techniques and which store uses each. +
Check yourself + extraction-log (URL-dedupe), session (stage/opts/cursors), media-stubs (asset-status), + products (product-stream), sections (spec-cache). Techniques: append-only (log, products); + atomic rename (session, sections); status+retry cap (media-stubs). +
+
+ +
+ Primary source
+ Read the headers of src/lib/resume-state/extraction-log.ts, + …/import-session.ts, and …/media-stubs.ts. + Then look at …/faultpoint.ts to see how crashes are injected for + testing. +
+ +

Want me to walk a concrete "crash during downloading-media, then resume" +trace through all five files? Ask.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0007-the-tick-loop.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0007-the-tick-loop.html new file mode 100644 index 0000000000..fed0bdf9f6 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0007-the-tick-loop.html @@ -0,0 +1,119 @@ + + + + + +7 · The Tick Loop & the Judgment Seam + + + + +
+ Data Liberation · Part III — Reconstruction + Lesson 7 / 17 +
+ +
+
Lesson 07 · Reconstruction drill
+

The Tick Loop & the Judgment Seam

+

This is the most distinctive thing about the architecture — and the easiest to miss from the file tree. The default run is not a batch script; it's a streaming loop that deliberately hands reasoning back to an agent at marked boundaries.

+ +

The design center of the system: AI agents are the primary runtime. The +default data-liberation <url> command doesn't just run code — it runs a +watch loop that interleaves deterministic work with agent judgment.

+ +

Two state machines, not one

+

Lesson 1 gave you the persisted ImportStage union. The streaming path has a +second, finer-grained phase enum that drives live orchestration — +WatchPhase in src/ui/watch-runner.ts:

+ +
resolving-agent → resetting → detecting → discovering
+  → extracting → starting-preview → tick-drain
+  → invoking-judgments → done            (+ error)
+ + + + + + + +
EnumRole
ImportStageThe persisted resume checkpoint (what's written to session.json).
WatchPhaseThe live orchestration state of the streaming run — finer-grained, not persisted.
+ +

Notice starting-preview runs in parallel with discovery: a Studio +WordPress site boots while URLs are still being found. The reconstruction has somewhere to +land before extraction even finishes.

+ +

The judgment seam

+

The heart of it: tick-scheduler (src/lib/streaming/) +emits a judgmentNeeded[] list. That is the exact line where deterministic +code stops and the agent/skill is asked to reason — to run the replicate / compose / +design-foundation skills, pick clusters, judge parity.

+ +
+ The seam, stated plainly + Everything a function can decide, a function decides (detection, selectors, + layout type, block canonicalization). Everything that needs judgment — + "is this section good enough?", "which representative page stands for this cluster?", + "does this match the source?" — is pushed across the judgmentNeeded boundary + to the model. The whole system is organized around drawing that line well. +
+ +

Why this is the architecture decision behind all the others

+

The governing rule: ship AI stages as skills, not SDK calls in src/. +That's why there are ~38 small deterministic MCP tools instead of a few big "do it all" +functions. The tools are the vocabulary; the skills are the sentences an +agent composes. When you design a new capability, the question is: which part is +deterministic (→ a tool in src/lib) and which part is judgment (→ a step in a +skill)?

+ +
+ A litmus test you can apply + If a proposed feature reads "have the model look at X and decide Y," it belongs in a + skill orchestrating tools — not as an LLM call buried in src/. If it + reads "given inputs, always produce the same output," it belongs in a deterministic tool. + Mixing the two is the smell to catch in review. +
+ +
+
What is the judgmentNeeded[] list emitted by the tick scheduler?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the two state machines and which one persists. Then state the litmus test for + "deterministic tool vs skill step." +
Check yourself + ImportStage (persisted resume checkpoint) and WatchPhase (live, + not persisted). Litmus: "model looks and decides" → skill orchestrating tools; "same + inputs → same output" → deterministic tool in src/lib. +
+
+ +
+ Primary source
+ Read the WatchPhase enum and loop in + src/ui/watch-runner.ts, then the + src/lib/streaming/tick-scheduler.ts to see where + judgmentNeeded is built and drained. +
+ +

This seam is the crux of your "what should be a skill vs a tool" calls — want +to pressure-test a specific feature idea against it together? Ask.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0008-blocks-reconstruction.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0008-blocks-reconstruction.html new file mode 100644 index 0000000000..63621813a4 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0008-blocks-reconstruction.html @@ -0,0 +1,133 @@ + + + + + +8 · Blocks Reconstruction + + + + +
+ Data Liberation · Part III — Reconstruction + Lesson 8 / 17 +
+ +
+
Lesson 08 · Reconstruction drill
+

Blocks Reconstruction

+

The blocks path turns captured section specs into native Gutenberg markup. Its real sophistication is in how it handles the sections it can't cleanly model — and how it tells you, in writing, every time it falls back.

+ +

Tool: liberate_reconstruct_pages → +src/mcp-server/handlers/reconstruct-pages.ts → +src/lib/replicate/{reconstruct-pages,page-reconstruct}.ts. It reads +the SectionSpec[] from the cache (Lesson 5) and emits a block document per page.

+ +

The fallback ladder

+

For each visual section the pipeline tries to produce clean native blocks. When it can't, +it falls back to a core/html island — but the fallback is gated and +recorded, never silent.

+ +
+
Adapter blocks recipe (if declared) — platform-specific content→blocks, per section and in bulk.
+
Native block emit — structured render into core blocks (columns, headings, images, lists…).
+
core/html coverage island — the fallback, when native coverage drops below floor. Marked lib-coverage-island.
+
editable-html conversion — islands become dla/editable-html by default: visible, styled, editable, byte-identical save.
+
+ +

It writes down why it fell back

+

This is the part to internalize. The blocks reconstruction emits three +warning-level diagnostic artifacts, all keyed off the section +selector, all atomic, all surfaced as tallies in the tool's result (they do +not flip the run verdict):

+ + + + + + + + + + + + + + + + + +
ArtifactAnswers
fallback-diagnostics.json"Why did this section become a core/html island?" One record per + coverage-gated island: reasonCode (dropped_images | + text_coverage_below_floor, media-first precedence), + suggestedRepairClass, dropped URLs, coverage, source/emitted previews.
region-audit.json"Did every source landmark survive?" Reconciles the landmark census vs what was + placed. An actionable landmark mapping to nothing is unassigned — the + dropped-nav/footer signal.
asset-triage.jsonVision-classified decorative-asset verdicts (keep/decoration); + decorations are skipped as images pre-build.
+ +
+ The design principle here + Fidelity loss is made legible. Instead of a black-box "best effort," every + fallback leaves a structured, selector-keyed record an agent can triage — what fell back, + why, and what repair class would fix it. This is what makes the blocks path safe to iterate + on: you can see the gaps. +
+ +

The coverage island marker (a subtle gate)

+

Coverage islands carry a deterministic marker on the opening delimiter — +<!-- wp:html {"metadata":{"name":"lib-coverage-island"}} -->. This lets the +install-time wp:html ban accept pipeline islands on theme reinstall +while still rejecting hand-authored Custom HTML. It's a quality gate, not a security +boundary — the marker is recognizable, not unforgeable. Knowing that distinction keeps +you from over-trusting it.

+ +
+ The coverage gate's blind spot + The coverage gate measures text/image coverage — it is blind to layout breakage. + A section can pass coverage while its structure is mangled — coverage looks fine, layout is + wrong. This is why coverage alone can't gate aggressive native conversion on complex builder + sites. When you reason about quality here, remember coverage ≠ correctness. +
+ +
+
A section becomes a core/html island. Where do you look to learn why?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the three diagnostic artifacts and the one question each answers. Then state why the + coverage gate isn't enough on its own. +
Check yourself + fallback-diagnostics (why this island), region-audit (did landmarks survive), asset-triage + (keep/decoration). Coverage measures text/image presence, not layout — it's blind to + structural breakage. +
+
+ +
+ Primary source
+ Read src/lib/replicate/page-reconstruct.ts for the per-section + emit + fallback, and …/fallback-diagnostic.ts + + …/region-audit.ts for the pure builders behind the artifacts. +
+ +

Want to walk a sample fallback-diagnostics.json and triage it +together? Ask — it's the fastest way to make this concrete.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0009-the-carry-path.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0009-the-carry-path.html new file mode 100644 index 0000000000..a72771ef1d --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0009-the-carry-path.html @@ -0,0 +1,122 @@ + + + + + +9 · The Carry Path + + + + +
+ Data Liberation · Part III — Reconstruction + Lesson 9 / 17 +
+ +
+
Lesson 09 · Reconstruction drill
+

The Carry Path

+

When fidelity is the whole game, you stop interpreting the source and start carrying it. The carry path's craft is in making "ship the original HTML" actually behave inside WordPress — self-hosted, scoped, and de-CDN'd.

+ +

Tool: liberate_reconstruct_pages_carry → +src/mcp-server/handlers/reconstruct-pages-carry.ts → +src/lib/replicate/{page-reconstruct-carry,html-carry}.ts. It writes a +<slug>-carry FSE block theme whose page bodies are the sanitized source +HTML in core/html islands, with the source CSS scoped to avoid bleed.

+ +

The three problems carry has to solve

+ + + + + + + + + + + + + + + + +
ProblemSolution
CSS bleed — source CSS would clobber the WP theme and admin.Scope every rule (e.g. :where(body.lib-carry-site) …) — :where() keeps specificity at zero so it layers cleanly.
CDN dependence — the replica must not phone home to Wix/Shopify CDNs.Full self-hosting: repoint image variants, pre-pass download extraction-missed images, strip-unused + localize fonts, drop dead preload/builder <link>s.
Dead interactivity — source JS reveal/animation leaves content hidden.Un-freeze JS reveals (e.g. :has() nav-reveal), denylist display/visibility in carried CSS so display:none doesn't survive.
+ +
+ The mental model + Carry is "the source HTML wearing a WordPress costume." The hard work isn't producing the + markup — it's severing every tie to the origin platform (CDN, fonts, builder + chrome) and neutralizing the source's own hiding mechanisms so the carried content + is actually visible and self-contained. +
+ +

Chrome fidelity is its own subsystem

+

Headers, footers, and nav are the hardest part of carry, because builder platforms ride +chrome inline or behind custom elements. Chrome fidelity is a layered strategy: +prevent (rem-base on :root, un-freeze JS reveal), +fingerprint (capture chrome signature), self-heal +(source-vs-build audit). A representative failure mode: a store header collapses when a +carried :where(body.lib-carry-site) header{display:none} matches the +WP-injected wrapper <header>; the fix out-specifies it with +header.wp-block-template-part{display:block!important}.

+ +
+ Verify on the LIVE site, not the captured PNG + A recurring trap: carry replica screenshots can race the source CSS (false-white) or + capture before lazy images load (short screenshots → understated parity scores), and the + WP admin bar adds a ~32px offset. These capture artifacts masquerade as regressions. Always + verify carry fidelity on the live rendered site with a computed-style probe, not a grep of + the DOM or a stale PNG. +
+ +

Where carry and blocks meet

+

Remember Lesson 3: carry islands convert to dla/editable-html too. And native +blocks can be spliced into a carried island with the source's own classes at +zero pixel delta — a hybrid that gets carry's fidelity with some of blocks' +editability. The two paths are not a permanent fork; they're the ends of one spectrum, and the +editable-island bridge already spans part of it.

+ +
+
Why scope carried CSS with :where(body.lib-carry-site) rather than a plain descendant selector?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the three problems carry must solve and the fix for each. Then state the verification + rule that prevents the capture-artifact rabbit hole. +
Check yourself + CSS bleed → :where() scoping; CDN dependence → full self-hosting (image + repoint, missing-media pre-pass, font localize, dead-link strip); dead interactivity → + un-freeze JS reveal + denylist display/visibility. Verify on the live site with a + computed-style probe, not a stale PNG or DOM grep. +
+
+ +
+ Primary source
+ Read src/lib/replicate/html-carry.ts (sanitize + scope + island) + and the carry font/media helpers (carry-fonts.ts, + carry-missing-media.ts) to see self-hosting in action. +
+ +

The chrome-collapse failure mode is a goldmine of how CSS cascade fights play +out here — ask me to walk the full header-rescue trace if you want it.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0010-blocks-engine-adoption.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0010-blocks-engine-adoption.html new file mode 100644 index 0000000000..82bd25e4b5 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0010-blocks-engine-adoption.html @@ -0,0 +1,124 @@ + + + + + +10 · The Shared Blocks Engine + + + + +
+ Data Liberation · Part III — Reconstruction + Lesson 10 / 17 +
+ +
+
Lesson 10 · Reconstruction drill
+

The Shared Blocks Engine

+

The deterministic core of "section spec → native blocks" is general — it isn't DLA-specific — so it lives in a separate, reusable package, @automattic/blocks-engine, which DLA consumes. Understanding this seam is understanding how reconstruction is structured.

+ +

The pure, deterministic "turn a section into block markup" logic isn't unique to data +liberation — any "specs → blocks" consumer needs it. So it lives in its own package, and DLA +is a consumer of that engine plus its own orchestration.

+ +

How the dependency is wired

+

DLA depends on the published package @automattic/blocks-engine (a normal semver +range, e.g. ^0.1.0 in package.json). It is an ordinary npm +dependency — no special checkout required to install DLA.

+ +

The seam: what lives where

+ + + + + + + + + + + + +
In the engineIn DLA
The deterministic reconstruct core: reconstructNativeAggregate + — the pure section-spec → native-block-aggregate transform.The per-page boundary: reconstructPagePattern lives DLA-side and + delegates into the engine core.
The general block-emit logic any "specs → blocks" consumer needs.The orchestration: capture, resume-state, the carry path, diagnostics, the + tick/judgment loop — all DLA.
+ +

reconstructPagePattern is a thin boundary (~200 lines) that hands its core work +to the engine's single-sourced reconstructNativeAggregate. The engine owns the +transform; DLA owns where and when it runs.

+ +
+ The guardrail: a frozen-output harness + The seam is protected by a frozen harness test: the exact reconstruct output is pinned in a + fixture. Any divergence — whether from a DLA-side change or an engine version bump — fails the + test loudly. That is what makes depending on a shared engine safe: the consumer freezes the + precise output it expects, so the shared code can't silently drift under it. +
+ +

What stays DLA-local (and why that's correct)

+

Not everything is shared. The local emit-blocks path (Lesson 13) keeps a distinct +strategy rather than using the engine core, because it preserves the owned source DOM instead +of re-emitting markup from captured specs — a genuinely different transform. The principle: +share the core where the output is identical; keep strategies separate where they truly differ.

+ +
+ Why this matters for architecture calls + When you weigh "shared engine or DLA-local," the test is concrete: can the shared version + produce exactly the output the consumer needs, pinned by a frozen harness? If yes, depend on + it. If two callers genuinely need different strategies, a forced shared abstraction will + leak — keep them separate behind a clean interface instead. +
+ +
+ Test-runner trap + Running vitest from the DLA repo root can go red due to .worktrees/* + pollution (sibling copies of every test colliding on cwd-relative fixtures). Verify with + --dir src or inside a clean worktree, not from a polluted root. +
+ +
+
What makes it safe for DLA to depend on the engine's reconstructNativeAggregate?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + State what lives in the engine vs what lives in DLA, and the guardrail that makes the + dependency safe. Then say why the local emit-blocks path stays DLA-local. +
Check yourself + Engine: deterministic core (reconstructNativeAggregate). DLA: per-page boundary + (reconstructPagePattern), carry, capture, resume-state, diagnostics, + orchestration. Guardrail: a frozen-output harness pins the reconstruct result. The local path + preserves the owned source DOM rather than re-emitting from specs — a different transform. +
+
+ +
+ Primary source
+ Read src/lib/replicate/reconstruct-pages.ts + (reconstructPagePattern's delegation) alongside the engine's + reconstructNativeAggregate. The @automattic/blocks-engine dependency + is declared in package.json. +
+ +

Want to see exactly what the engine's reconstructNativeAggregate +signature takes and returns? Ask and we'll read it together.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0011-output-artifacts.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0011-output-artifacts.html new file mode 100644 index 0000000000..43fcf7b606 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0011-output-artifacts.html @@ -0,0 +1,115 @@ + + + + + +11 · Output Artifacts + + + + +
+ Data Liberation · Part IV — Output & Quality + Lesson 11 / 17 +
+ +
+
Lesson 11 · Output & quality
+

Output Artifacts

+

Three output families, three target formats: content → WXR, products → Woo CSV, assets → self-hosted media. Knowing what each builder guarantees tells you what a downstream WordPress import can rely on.

+ +

Content → WXR

+

WxrBuilder (src/lib/wxr/) assembles/appends +output.wxr, targeting WXR 1.2 spec compliance. Extraction +appends per-URL (Lesson 6's append-only pattern); reconstruction rewrites the post +content:encoded bodies with block markup. The WP block parser is adopted as a +validate-artifacts oracle — a gate that the emitted block markup actually parses.

+ +
+ Never lose source content + A first-principles rule in this codebase: preserve all source content verbatim — no + truncation, no dedup, no dropping. If the source shows it (even a heading plus an identical + paragraph), the output reproduces it. When you review a change that "cleans up" content, that + instinct is usually wrong here. +
+ +

Products → Woo CSV

+

Products stream to products.jsonl (one product per line) and become a +WooCommerce import CSV. WooProduct has first-class seoTitle, +seoDescription, costOfGoods fields plus an open meta +record; the CSV builder always emits the Yoast + cost-of-goods meta: columns, +plus a column per custom meta key. Shopify specifics worth knowing:

+
    +
  • Variant weights normalize to kilograms regardless of source unit (normalizeWeightToKg).
  • +
  • Sale price uses compareAtPrice > price semantics — when set, compareAtPrice becomes regularPrice and price becomes salePrice.
  • +
  • With an adminToken, products come via the Admin GraphQL API (pinned 2025-04), paginated by a resumable endCursor, falling back to the public JSON API on any failure.
  • +
+ +

Assets → self-hosted media

+

media-fetch downloads + dedupes; MediaStubStore tracks status +and the installed wpPostId. Filename collisions get numeric suffixes +(-2, -3), not hashes. The advanced bit — SVG survival:

+
    +
  • Every downloaded SVG is scanned for risky <use>/<defs> (isRiskySvg) and a PNG sibling is rasterized at 1024px via Playwright.
  • +
  • Any library-bound SVG triggers a one-time ensurePlugin(site,'safe-svg'); risky SVGs upload their PNG raster instead.
  • +
+ +
+ The join is filesystem-only — by design + Nothing about screenshots or media is injected into the WXR. The link from a URL to its + screenshots/HTML is screenshots/manifest.json; orchestration correlates by URL + between the manifest and output.wxr/products.jsonl. This keeps the + WXR a clean, portable content artifact and pushes all the "which file goes with which page" + bookkeeping into a sidecar. When you design new outputs, prefer that shape: keep the + portable artifact pure, join by URL in a manifest. +
+ +

The theme shell

+

Under every reconstruct path, the theme shell is emitted deterministically by +liberate_theme_scaffold and installed by liberate_install_theme. +The output base resolves (via liberate_paths / paths.ts) to +~/Studio/_liberations/<host> by default.

+ +
+
How is a page's screenshot associated with its WXR content?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the three output families and their target formats. Then state the "never lose source + content" rule and the "keep the portable artifact pure" principle in your own words. +
Check yourself + Content → WXR 1.2 (WxrBuilder); products → Woo CSV (products.jsonl); + assets → self-hosted media (media-fetch + MediaStubStore). + Preserve all source content verbatim; keep WXR pure and join screenshots/media by URL via a + manifest sidecar. +
+
+ +
+ Primary source
+ Read src/lib/wxr/ (the builder) and + src/lib/woo-csv/woo-product-csv.ts (the product columns). For SVG + survival, src/lib/media-fetch/svg-raster.ts. +
+ +

Want the full WooProduct → CSV column mapping as a printable reference card? +Ask and I'll add it to the shelf.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0012-quality-gates.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0012-quality-gates.html new file mode 100644 index 0000000000..631b61c8b7 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0012-quality-gates.html @@ -0,0 +1,122 @@ + + + + + +12 · Quality Gates & Diagnostics + + + + +
+ Data Liberation · Part IV — Output & Quality + Lesson 12 / 17 +
+ +
+
Lesson 12 · Output & quality
+

Quality Gates & Diagnostics

+

A migration tool is only as trustworthy as its ability to know when it failed. This last lesson is the system's self-awareness layer — the gates that decide "good enough" and the diagnostics that explain "not yet."

+ +

You've met pieces of this across the course. Here it is as one map, because reasoning about +quality is reasoning about which signal answers which question.

+ +

The two kinds of signal

+ + + + + + + +
Verdict gatesWarning diagnostics
JobPass/fail the run.Explain gaps; never flip the verdict.
Examplesvalidate_artifacts, PARITY_GATE_SCORE, the WP block-parser oracle.fallback-diagnostics.json, region-audit.json, asset-triage.json.
How to readHard stop — fix before shipping.Triage queue — what an agent repairs next.
+ +

Visual parity: comparison.json (v2)

+

liberate_compare scores each viewport. The v2 schema adds a height +signal: each ViewportScore gains originHeight, +replicaHeight, and heightMismatchRatio. When the ratio exceeds +HEIGHT_MISMATCH_THRESHOLD (0.02), a magenta-padded full-canvas diff is written — +magenta means content-gap, not style drift. The parity gate proper is +PARITY_GATE_SCORE = 0.995. v1 files still parse (missing fields = gate disabled).

+ +
+ Scores lie when the capture is wrong + The single biggest source of false low parity scores is capture artifacts: + screenshots taken before lazy images load (short replica → understated score), the WP admin + bar's ~32px offset, or a replica PNG racing the source CSS. Before trusting a low score, + confirm scrollHeight matched and no admin bar was in frame. This has burned + hours — treat a surprising score as "verify the capture" before "fix the code." +
+ +

Content loss: the QA differ

+

liberate_qadiffContent (src/lib/qa/content-differ.ts) +measures extraction loss — origin items not present in the WXR (missingImages +etc.). Crucially it measures loss, not hallucination: it tells you what +failed to make it in, not what was invented. Knowing the direction of the measurement keeps +you from misreading a clean QA report as "perfect."

+ +

Refine reports: closing the loop

+

liberate_refine_report validates refine/<slug>/*.json coverage: +every finding id must appear in exactly one of applied/skipped; +orphans fail. This is the bookkeeping that makes an agentic repair loop accountable — +you can't quietly drop a finding.

+ +
+ The philosophy across all of it + Degrade loudly, keep the evidence, make loss legible, and separate "did it + pass" from "here's what's imperfect." This is the same instinct as the preserved + session.json.corrupt.<ts> file (Lesson 6). A tool people trust with their + site's content cannot have silent failure modes — and this codebase is built to not have + them. +
+ +

Faithful-recreation enforcement

+

There's a hard visual-parity gate (section-parity.ts + a run-report verdict) +specifically to stop the shipping skills from flattening "known gaps" into an accepted result. +The honest-assessment value is encoded in the gate itself: the system is designed to refuse to +quietly call a degraded recreation "done."

+ +
+
A magenta band appears in a padded comparison diff. What does it indicate?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Distinguish verdict gates from warning diagnostics with one example of each. Then state what + the QA differ measures (and what it does not), and the capture-artifact rule for + parity scores. +
Check yourself + Gate (hard stop): validate_artifacts / PARITY_GATE_SCORE=0.995. + Diagnostic (triage, never flips verdict): fallback-diagnostics.json. The QA + differ measures extraction loss, not hallucination. Verify the capture + (scrollHeight, no admin bar, lazy images loaded) before trusting a low parity score. +
+
+ +
+ Primary source
+ Read src/lib/replicate/compare.ts (the v2 height signal + + thresholds), src/lib/qa/content-differ.ts (loss measurement), and + src/lib/replicate/refine-report.ts (coverage validator). +
+ +

That's the core spine. Part V drills into five advanced capabilities that +build on it.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0013-local-owned-source-path.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0013-local-owned-source-path.html new file mode 100644 index 0000000000..2960786922 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0013-local-owned-source-path.html @@ -0,0 +1,110 @@ + + + + + +13 · The Local / Owned-Source Path + + + + +
+ Data Liberation · Part V — Advanced + Lesson 13 / 17 +
+ +
+
Lesson 13 · Advanced capability
+

The Local / Owned-Source Path

+

The third reconstruct path is a different animal: no URL, no detection, no live extraction. You hand it a directory of HTML/CSS/JS you already own, and it produces a block theme. Understanding why it bypasses half the pipeline clarifies what the rest of the pipeline is for.

+ +

Front door: the data-liberation:liberate skill routes on input type. A +local directory takes the owned-source path — dispatched inline, no platform +detection, no network extraction, no path checkpoint. A URL takes the remote +path you've studied so far.

+ +

Two tools, one flow

+
+
liberate_ingest_local_site — read the owned directory; inventory its HTML pages, CSS, JS, assets.
+
liberate_convert_local_site — convert that inventory into a live Studio block theme: native nav, page templates, carried CSS/JS, optional data-model.
+
+ +

Why it skips detection and extraction

+

The remote pipeline's expensive, fragile stages exist to deal with a site you don't +control — fingerprint an unknown platform, fight bot defenses, discover URLs, download remote +media. None of that applies to a folder on disk. So the owned path drops straight to +reconstruction. This is the cleanest illustration of the two-phase split (Lesson 1): when phase +one's risk disappears, you keep only phase two.

+ +
+ Preserve-DOM, don't re-emit + The local path's defining strategy: it preserves the owned source DOM rather than + re-emitting markup from captured specs. You wrote that HTML; it's already correct. So the + conversion keeps the structure and styling/animation JS, and turns it into a theme around + that — the opposite instinct from the blocks path, which re-derives everything from specs. + This is exactly why the local emit-blocks strategy stays distinct from the shared engine + core (Lesson 10). +
+ +

From static cards to real WordPress data

+

The sophisticated part: an owned static site often renders its content from JS data +(a catalog grid, a listings array) or from repeated static-HTML cards. The +data-liberation:model-local-data skill analyzes that and emits a +data-model.json — which liberate_convert_local_site consumes to turn +that data into real WordPress posts + taxonomy + native query loops, while keeping the +original styling/animation/modal JS.

+ +
+ The architectural payoff + This is the difference between "a pretty static replica" and "a real CMS." A JS data grid + becomes queryable WordPress posts the owner can edit and extend — without losing the look. + When you weigh whether to build modeling for a given source shape, the question is: is the + content data wearing a presentation, or is it bespoke layout? Data → model it; bespoke + → carry the DOM. +
+ +

Forms convert here too: owned HTML forms become editable Jetpack forms (Lesson 14) on this +path as well, with conditional plugin install.

+ +
+
Why does the owned-source path skip detection and live extraction entirely?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the two local-path tools, the skill that turns JS data into a data model, and the + defining strategy that separates this path from the blocks path. +
Check yourself + liberate_ingest_local_site + liberate_convert_local_site; the + model-local-data skill emits data-model.json. Defining strategy: + preserve the owned source DOM rather than re-emit from specs. +
+
+ +
+ Primary source
+ Read src/mcp-server/handlers/convert-local-site.ts and the + src/lib/replicate/local-site/ + local-theme/ + modules. The data-liberation:model-local-data skill documents the data-model shape. +
+ +

Want to see what a data-model.json looks like for a JS catalog grid? +Ask and we'll sketch one.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0014-forms-to-jetpack.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0014-forms-to-jetpack.html new file mode 100644 index 0000000000..dbed3ec969 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0014-forms-to-jetpack.html @@ -0,0 +1,109 @@ + + + + + +14 · Forms → Jetpack + + + + +
+ Data Liberation · Part V — Advanced + Lesson 14 / 17 +
+ +
+
Lesson 14 · Advanced capability
+

Forms → Jetpack

+

A captured contact form is useless as a screenshot — it has to actually submit. The forms capability turns source forms into editable Jetpack forms, and it's a masterclass in why "emit the obvious block" is often wrong.

+ +

Capture, then emit

+

Forms are captured during the extractFull browser walk: +SectionSpec.forms?: SectionSpecForm[]. Forms with zero recognizable fields are +omitted entirely; file/hidden fields are skipped and flagged in +provenance. On the blocks path, each form emits a +jetpack/contact-form with a jetpack/field-* per field kind.

+ +

The submit-button trap (the whole lesson)

+

The obvious choice for the submit control would be jetpack/button. It's +wrong, and silently so. The submit is emitted as a core/button with +tagName:"button", type:"submit", and className +form-button-submit is-submit — the current Jetpack form-editor grammar.

+ +
+ Why never jetpack/button + jetpack/button lives in the connection-gated extensions loader. On an + unconnected install it is unregistered and renders to nothing — + so the contact form falls back to a server-built button labeled "Submit," losing the + captured label. The captured label has to ride in the saved inner HTML of a + core/button instead. This is the kind of failure that looks fine in the editor + and breaks only on a real unconnected site. +
+ +

The install dance

+

Emitting the markup isn't enough — the runtime has to support it. When any page's specs +carry forms, the pipeline calls ensurePlugin(sitePath,'jetpack') and +wp jetpack module activate contact-form. That second step is essential:

+ +
+ Unconnected installs start with all modules inactive + An offline/unconnected Jetpack starts with every module off. Without explicitly + activating contact-form, you get an empty form wrapper — the markup is correct + but nothing renders. Both the plugin-ensure and the module-activate are idempotent and + non-fatal (markup still emits if they fail), tallied as jetpackEnsured / + jetpackWarning in handler output. +
+ +

The hoist guard (a cross-feature interaction)

+

jetpack/* blocks are excluded from variation hoisting (Lesson 16). +Here's why it matters: Jetpack's get_form_style() regexes is-style-(\S+) +off the contact-form className, and an unknown style makes render_label() return +an empty string — labels vanish. So the hoister must never rewrite a +jetpack/* className. This is a great example of how two independent capabilities +can collide, and why the codebase encodes the guard explicitly.

+ +
+
Why is the form's submit emitted as core/button and never jetpack/button?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the two install commands forms trigger and why each is necessary. Then state why + jetpack/* blocks must be excluded from variation hoisting. +
Check yourself + ensurePlugin(site,'jetpack') (the plugin must exist) + wp jetpack module + activate contact-form (unconnected installs start all modules off → empty wrapper + otherwise). Hoisting a jetpack/* className breaks get_form_style()'s + is-style-* regex → labels render empty. +
+
+ +
+ Primary source
+ Find the form emit on the blocks path (search jetpack/contact-form in + src/lib/replicate/), the ensurePlugin helper in + src/lib/preview/ensure-plugin.ts, and the form capture in the + extractFull browser walk. +
+ +

Want the full jetpack/field-* kind mapping (text, email, select, +textarea…) as a reference card? Ask.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0015-design-foundations.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0015-design-foundations.html new file mode 100644 index 0000000000..75b8e411db --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0015-design-foundations.html @@ -0,0 +1,106 @@ + + + + + +15 · Design Foundations + + + + +
+ Data Liberation · Part V — Advanced + Lesson 15 / 17 +
+ +
+
Lesson 15 · Advanced capability
+

Design Foundations

+

Per-page screenshots capture how the site looks. The design foundation captures how the site's design thinks — the small set of semantic color, type, and spacing roles a coherent theme is built from. This is the bridge from raw measurement to a real theme.json.

+ +

From aggregates to roles

+

The capture stage already produces site-wide raw aggregates (Lesson 5): +palette.json (dominant colors ranked by usage), typography.json (font +metrics per selector), breakpoints.json (the union of media-query widths). Those +are observations, not decisions. The design foundation turns them into +semantic roles: which color is "primary," which is "surface," which type ramp +is the heading scale, what the spacing rhythm is.

+ +
+
liberate_design_foundation_scaffold — build a partial foundation from the aggregates (the raw, un-interpreted starting point).
+
data-liberation:design-foundations skill — interpret the scaffold + aggregate HTML/CSS + representative rendered HTML into a complete, semantic foundation with evidence trails.
+
liberate_design_foundation_validate / _save — check it against the schema, then persist.
+
+ +

Why this is a judgment step, by design

+

Naming roles is exactly the kind of work that sits on the far side of the judgment seam +(Lesson 7). "Which of these twelve observed colors is the brand primary?" is not a +deterministic function of the palette — it needs a model looking at the rendered site. So the +deterministic tools scaffold and validate; the skill does the +interpretation. That division is the tool-vs-skill litmus test in action.

+ +
+ Evidence trails + The foundation doesn't just assert "primary = #7a1f1f"; it carries the evidence for + each role — where in the captured data that decision came from. This is the same + degrade-loudly / make-it-legible value you saw in the diagnostics (Lesson 12): a design + decision you can audit beats a magic number. +
+ +

What consumes it

+

The foundation feeds theme generation — it's the input that lets the reconstructed site share +one coherent theme.json-level design language instead of a thousand inline styles. +Call it after liberation, before theme generation.

+ +
+ The architectural value + This is what separates "a faithful copy of each page" from "a real, editable theme." Without + a foundation, every page carries its own ad-hoc styling. With one, the site has semantic + tokens an owner can retune globally — change "primary" once, the whole site follows. When you + prioritize fidelity work, remember: foundations are what make the result maintainable, + not just accurate. +
+ +
+
Why is building the design foundation split into deterministic tools plus a skill?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the three raw aggregates the foundation interprets, and state which steps are + deterministic tools vs the skill. Then say what the foundation makes possible that per-page + fidelity alone doesn't. +
Check yourself + Aggregates: palette.json, typography.json, breakpoints.json. + Tools: scaffold + validate + save (deterministic); skill: the semantic interpretation. + Payoff: globally retunable semantic tokens → a maintainable theme, not just an accurate copy. +
+
+ +
+ Primary source
+ Read src/lib/design-foundation/ (schema + renderer) and the + data-liberation:design-foundations skill, which documents the full + scaffold → interpret → validate flow and the schema. +
+ +

Want to walk a real palette/typography aggregate and turn it into roles together? +That's the fastest way to feel the judgment step. Ask.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0016-variation-hoisting.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0016-variation-hoisting.html new file mode 100644 index 0000000000..7ce841b63c --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0016-variation-hoisting.html @@ -0,0 +1,115 @@ + + + + + +16 · Variation Hoisting & the Block-Fixer + + + + +
+ Data Liberation · Part V — Advanced + Lesson 16 / 17 +
+ +
+
Lesson 16 · Advanced capability
+

Variation Hoisting & the Block-Fixer

+

Two passes that turn raw reconstructed markup into theme-grade markup: one canonicalizes every block so it survives WordPress, the other lifts repeated styling out of instances into reusable block-style variations. Together they're why the output looks hand-authored, not machine-spat.

+ +

The block-fixer: survive the save cycle

+

Reconstructed markup must round-trip through @wordpress/blocks — open it in the +editor, save it, and the output must be unchanged. The block-fixer is the +canonicalization pass that guarantees this. The practical constraint it imposes:

+ +
+ Express sizing in terms the fixer preserves + Reconstructed sizing has to be encoded in core attributes and classNames, not inline + figure styles — because the fixer (and the theme's height:auto!important + resets) will rewrite anything else. If you fight the canonicalizer with inline styles, your + dimensions vanish on the first save. Work with the grammar, not around it. +
+ +

The fixer is also defensive about unknown blocks: an unknown jetpack/* block +passes through grammar-preserved via core/missing with its +originalContent intact — so a block the fixer doesn't understand is never +destroyed, just carried.

+ +

Variation hoisting: DRY out the styles

+

Reconstruction often emits the same styling constellation on many instances across the site. +Variation hoisting finds recurring instance-style constellations — the +threshold is ≥ 3 site-wide, exact match — and lifts them into +<theme>/styles/blocks/lib-*.json block-style variations, then strips the +inline styling from the instances.

+ + + + + + + + + +
PropertyBehavior
Slug derivationDeterministic: block short-name + style groups present, numeric suffix on collision.
Failure modeFail-open — if the block-fixer sidecar is unavailable, hoisting is skipped with a warning (markup still ships).
DisableThe variationHoist:false tool option (MCP-only; no CLI flag).
Exclusionjetpack/* blocks are excluded (Lesson 14's label-vanishing trap).
+ +
+ Why hoisting is worth the complexity + Hoisting is the difference between a theme with one editable "card" style used 30 times and a + theme with 30 copies of the same inline blob. The former is what a human designer would + build — retune the variation once, every instance updates. It's a maintainability multiplier, + the same value proposition as design foundations (Lesson 15), applied at the block level. +
+ +
+ Exact-match, ≥ 3 — and why those bounds + Hoisting only fires on exact style matches occurring at least three times. + That conservatism is deliberate: a near-match hoist would silently change appearance, and a + 2-instance hoist rarely earns its abstraction. When you reason about extending it (fuzzy + matching, lower thresholds), the risk is always the same — hoisting that alters pixels. +
+ +
+
Why must reconstructed sizing be expressed in core attributes/classNames rather than inline figure styles?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + State what the block-fixer guarantees and the constraint it imposes. Then give the variation + hoisting threshold and what gets excluded (and why). +
Check yourself + Fixer: markup survives a save cycle (round-trips through @wordpress/blocks); + constraint: sizing in core attrs/classNames, not inline figure styles. Hoist: ≥ 3 exact + site-wide matches → styles/blocks/lib-*.json; excludes jetpack/* + (className rewrite would empty form labels). +
+
+ +
+ Primary source
+ Find the hoisting logic and the variationHoist option in + src/lib/replicate/ (search lib- / + styles/blocks), and the block-fixer smoke tests + (blockFixer.smoke.test.js) for the canonicalization guarantees. +
+ +

Want to see a before/after of a hoisted constellation — instance markup vs the +generated lib-*.json variation? Ask.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/lessons/0017-shopify-graphql.html b/packages/data-liberation-agent/docs/how-it-works/lessons/0017-shopify-graphql.html new file mode 100644 index 0000000000..b05f0f9b5a --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/lessons/0017-shopify-graphql.html @@ -0,0 +1,108 @@ + + + + + +17 · The Shopify GraphQL Product Path + + + + +
+ Data Liberation · Part V — Advanced + Lesson 17 / 17 +
+ +
+
Lesson 17 · Advanced capability
+

The Shopify GraphQL Product Path

+

The richest single adapter behavior in the codebase — and the best worked example of the patterns from the whole course: a privileged fast path, a resumable cursor, idempotent output, and a graceful fallback. If you understand this, you understand how an adapter does something hard well.

+ +

Two ways to get products

+

The Shopify adapter has a public JSON path and a privileged GraphQL path. When an +adminToken is present, shopifyAdapter.extract fetches products via the +Shopify Admin GraphQL API (pinned to 2025-04) instead of the +public JSON API. The token unlocks completeness the public API can't give.

+ + + + + + + + + + +
ConcernHow the GraphQL path handles it
HostnameRequires a *.myshopify.com host — throws if a custom storefront domain is derived and shopDomain wasn't passed explicitly.
PaginationWalks endCursor, stored in session.cursors['shopify:products:endCursor']resumable across crashes.
Idempotent outputTracks already-emitted handles in session.cursors['shopify:products:emittedHandles'] — no duplicate CSV rows across a resume.
Safety railsMAX_PAGES = 10000 + a non-advancing-cursor guard to prevent infinite loops.
FallbackAny GraphQL failure falls back to the JSON API + URL loop — the run never dies on a token/API problem.
+ +

Every course pattern, in one feature

+

This path is a microcosm. Spot them:

+
    +
  • Resume-state contract (L6): the cursor and emitted-handles live in + session.cursors — the same single-writer session store, so a crash mid-pagination + resumes exactly where it stopped without re-emitting products.
  • +
  • Degrade loudly / fail safe (L12): GraphQL failure doesn't abort — it falls + back. But a misconfiguration (custom domain without shopDomain) throws + loudly rather than silently producing wrong data.
  • +
  • Platform knowledge at the edge (L4): all of this lives in the Shopify + adapter; the shared extraction loop and product CSV writer know nothing about GraphQL.
  • +
+ +
+ Idempotency is the hard part, not pagination + Plenty of code paginates. The subtle achievement here is idempotent streaming output + across crashes: products.jsonl appends (L11), and the emitted-handles set ensures + a resumed run doesn't double-write a product it already streamed. Pagination cursor + + emitted-set together are what make "crash on page 40 of 200, resume cleanly" actually work. +
+ +

Product modeling specifics worth carrying

+
    +
  • Variant weights normalize to kilograms regardless of source unit (normalizeWeightToKg).
  • +
  • Simple-product sale price uses compareAtPrice > price semantics — when set, compareAtPriceregularPrice and pricesalePrice.
  • +
  • Output flows to products.jsonl → Woo CSV with first-class SEO + cost-of-goods meta columns (L11).
  • +
+ +
+
What makes the GraphQL product stream safe to resume after a mid-pagination crash?
+
    +
  • +
  • +
  • +
  • +
+ +
+ +
+ Retrieval practice + Name the two values stored in session.cursors and what each prevents. Then map + three earlier course concepts onto this one feature. +
Check yourself + endCursor (where to resume pagination) + emittedHandles (prevents + duplicate output). Concepts: resume-state contract (L6, the session store), degrade-loudly / + fallback (L12), platform-knowledge-at-the-edge (L4). Plus idempotent append output (L11). +
+
+ +
+ Primary source
+ Read src/adapters/shopify/products.ts and + …/shopify/extract.ts for the GraphQL path, cursor handling, and the + JSON fallback. normalizeWeightToKg and the sale-price semantics live alongside. +
+ +

That completes the course — all seventeen lessons. Ask me to quiz you across the +whole thing, drill any lesson, or tie it to a live architecture decision. As your teacher, I'm +here for the follow-ups.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/reference/determinism-map.html b/packages/data-liberation-agent/docs/how-it-works/reference/determinism-map.html new file mode 100644 index 0000000000..a6de718da0 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/reference/determinism-map.html @@ -0,0 +1,102 @@ + + + + + +Determinism Map · Data Liberation + + + +
+ Data Liberation · Determinism Map + Reference +
+ +
+

Determinism Map

+

Which steps a function decides, which steps a model decides — and the seam between them. This is the architecture's defining line (see Lesson 7); read the pipeline as two materials joined at the judgmentNeeded[] boundary.

+ +
+ D Deterministic — same inputs → same output; a pure function or a fixed algorithm. + J Judgment — a model/agent looks and decides; lives in a skill, crosses the seam. + M Mixed — deterministic mechanics wrapping (or gated by) a judgment call. +
+ +
+ The rule that draws the line + Everything a function can decide, a function decides. Everything that needs taste or + perception — "is this good enough?", "which color is the brand primary?", "which page + represents this cluster?" — is pushed across the judgmentNeeded[] seam into a + skill. The deterministic tools are the vocabulary; the skills are the + sentences. That is why there are ~38 small tools instead of a few big ones. +
+ +

Phase 1 — Extraction

+

Extraction is almost entirely deterministic. It's measurement and capture: +given a live page, produce the same artifacts. There is no taste involved in reading a sitemap +or computing a section's bounding box.

+ + + + + + + + + +
Stage / stepWhy
Ddetect — platform fingerprintFixed rules over headers/markers → a platform id.
Ddiscover — URL inventorySitemap parse + nav walk; reproducible URL set.
Dextract — content + productsAdapter parses HTML/JSON-LD/GraphQL into structured content.
Ddownload-mediaFetch + dedupe; SVG-risk scan + raster are rule-based.
Dcapture — SectionSpecs, selectors, landmark census, design aggregatesextractFull measures the rendered DOM; the same page yields the same specs.
+

The one judgment that precedes extraction is the path-choice gate +(blocks vs carry vs theme), made by the operator/agent right after discovery. It's not a +pipeline stage — it's a decision that configures the pipeline. J

+ +

The seam

+

Between capture and a finished site sits the streaming tick-scheduler, which +emits judgmentNeeded[]. Everything before the seam is measurement; +everything the seam hands out is taste. Reconstruction then alternates between the two.

+ +

Phase 2 — Reconstruction

+

Reconstruction is where the two materials interleave. The emit is deterministic; +the decisions about what to emit and whether it's good are judgment.

+ + + + + + + + + + + + + +
Stage / stepWhy
Jcluster representative selection"Which page stands for this group?" — a model picks.
Dblocks emitreconstructNativeAggregateSection spec → native blocks; single-sourced engine core (Lesson 10).
Dcarry build — sanitize + scope + islandHTML in → scoped core/html out; deterministic transform.
Dblock-fixer canonicalizationRuns markup through @wordpress/blocks — fixed grammar.
Dvariation hoistingExact-match, ≥3 site-wide → lib-*.json. Pure algorithm.
Jasset triage — keep / decorationVision model classifies decorative assets.
Mdesign foundationScaffold + validate are D; naming semantic roles is J (the skill).
Jparity judgment / refine"Is this section good enough? what to fix?" — the repair loop.
Jdata-model analysis (local path)"Is this content data or bespoke layout?" — model-local-data.
+ +

Phase 2 — Output & quality

+ + + + + + + + +
StepWhy
DWXR build · products CSV · media install · theme scaffold/installAssembly to fixed formats.
Dcomparison scoring (comparison.json)Pixel/height math → a number; same images → same score.
Dregion audit · fallback diagnostics · refine-report validationReconcile/validate against captured truth — pure builders.
Maccept / iterate decisionThe gate (≥ 0.995) is deterministic; deciding whether to keep iterating on a near-miss is judgment.
+ +
+ Why this map is the one to keep open + Every "should this be a tool or a skill?" call is answered here. If a proposed step is + reproducible from its inputs, it's a deterministic tool in src/lib. If it needs a + model to look and decide, it's a skill step across the seam. The danger zone is an LLM call + buried inside a "deterministic" tool — it makes the pipeline unreproducible and untestable. + When you review, the M rows are where to look hardest: confirm + the deterministic and judgment halves are cleanly separated, not entangled. +
+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/reference/file-map.html b/packages/data-liberation-agent/docs/how-it-works/reference/file-map.html new file mode 100644 index 0000000000..d5b01d0099 --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/reference/file-map.html @@ -0,0 +1,84 @@ + + + + + +File Map · Data Liberation + + + +
+ Data Liberation · File Map + Reference · "where does this live?" +
+ +
+

File Map

+

Where each concern lives in src/. The fastest way to answer "what does a change to X touch?" — the core question for any architecture call.

+ +

Entry & dispatch

+ + + + + + + + +
PathConcern
src/cli.tsThe single binary; dispatches watch / subcommands / mcp.
src/mcp-server.ts~38 liberate_* tool registry + handler dispatch map + static adapter imports.
src/mcp-server/handlers/*Thin MCP shells over src/lib.
src/ui/*Thin CLI runners (Ink). watch-runner.ts = the streaming loop + WatchPhase.
+ +

Platform edge

+ + + + + + + +
src/adapters/<platform>/9 adapters: default, godaddy-wm, hostinger, hubspot, shopify, squarespace, webflow, weebly, wix.
src/adapters/resolve-adapter.tsid → adapter, fallback to default.
src/adapters/page-actions.tsThe two opt-in seam types: capture, blocks.
src/adapters/shared.tsShared adapter machinery (the big one).
+ +

Shared core — src/lib/

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
SubdirConcernLesson
detect-platform/fingerprint → platform id4
extraction/sitemap parse, URL classify, request tuning4
html-extract/HTML → structured content4
browser-kit/browser/CDP launch + control5
screenshot/capture + design tokens (palette/typography/breakpoints)5
resume-state/ExtractionLog, ImportSession, MediaStubStore, faultpoint6
streaming/tick-scheduler, judgmentNeeded, compose/transform logs, media install7
replicate/the replica engine: clustering, section specs, blocks/carry/theme reconstruct, parity, fonts, CSS scoping, variation hoisting5,8–10,12,16
replicate/local-site/ · local-theme/owned-source ingest + convert → block theme13
preview/Studio site boot + theme/file install7
design-foundation/semantic design-foundation JSON + renderer15
wxr/WxrBuilder → output.wxr (WXR 1.2)11
woo-csv/WooProduct modeling → products.jsonl + CSV11
media-fetch/download/dedupe; svg-raster (SVG survival)11
import/WP REST/HTTP import client1,11
wordpress/WP helpers (block-policy, island markers)8
qa/content-differ (extraction loss)12
verification/post-extraction verify (stale CDN, failed pages, missing media)12
probe/CDP page probing for debugging
setup/WP connection validation
features/feature detection across a site
url/ · paths.ts · concurrency.tsURL utils · output base resolution · concurrency caps
+ +
+ The one to internalize + src/lib/replicate/ is the largest, most consequential subsystem — almost all of + Phase 2 lives here. When an architecture question is about reconstruction fidelity, this is + the directory. The deterministic reconstruct core (reconstructNativeAggregate) + lives in @automattic/blocks-engine; DLA's reconstructPagePattern + delegates into it (Lesson 10). +
+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/reference/glossary.html b/packages/data-liberation-agent/docs/how-it-works/reference/glossary.html new file mode 100644 index 0000000000..e0c68e990f --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/reference/glossary.html @@ -0,0 +1,138 @@ + + + + + +Glossary · Data Liberation + + + +
+ Data Liberation · Glossary + Reference +
+ +
+

Glossary

+

The canonical language for this course. Every lesson uses these terms — and so should you when you reason about the system out loud.

+ +

The shape of the system

+ +

Data liberation — Extracting content and design out of a closed web +platform (Wix, Shopify, Squarespace…) and producing something a self-hosted WordPress site +can ingest. In this repo, the noun for the whole pipeline. +Avoid: migration, scraping.

+ +

Extraction phase — The first half: turn a live URL into on-disk +artifacts (output.wxr, products.jsonl, screenshots, design +sidecars). Owned by ImportSession stages. +Avoid: crawling, harvesting.

+ +

Reconstruction phase — The second half: turn those artifacts into a +real, styled WordPress site (a block theme + imported content). Three paths exist. +Avoid: rebuild, generation.

+ +

Adapter — A per-platform module under src/adapters/<platform>/ +exposing detect / discover / extract. The seam that +lets one pipeline serve nine platforms. +Avoid: plugin, driver, connector.

+ +

Resolve (an adapter) — Picking the adapter for a detected platform via +resolveAdapter; unknown or unregistered platforms fall back to the +default adapter. Detection and resolution are separate steps.

+ +

The three reconstruct paths

+ +

Blocks path — Reconstruct each page into native Gutenberg block +markup from its captured section specs. Most "WordPress-native," editable; tool = +liberate_reconstruct_pages. +Avoid: native path, block reconstruction (loosely).

+ +

Carry path — Carry sanitized source HTML + scoped CSS verbatim +into core/html islands; highest pixel fidelity, least native; tool = +liberate_reconstruct_pages_carry. +Avoid: passthrough, clone.

+ +

Theme / local path — Convert an owned local static-site directory +(no network extraction) into a live block theme; tool = liberate_convert_local_site. +Avoid: just "local" without qualifier.

+ +

Capture & specs

+ +

SectionSpec — A captured, computed-style description of one visual +section of a page (with a CSS selector that re-locates it in the source DOM). +The unit blocks-path reconstruction consumes. Cached in sections/<slug>.json.

+ +

Landmark census — The per-page tally of main/nav/header/footer +captured in the same browser walk as the section specs; consumed by the region audit to +catch dropped chrome.

+ +

Carried HTML / island — Source HTML wrapped in a core/html +block. A coverage island is one the pipeline emitted as a fallback (marked +lib-coverage-island). Islands convert to editable-html +(dla/editable-html) by default — visible, styled, editable, byte-identical save.

+ +

Resume-state stores

+ +

Resume-state file — A crash-safe, capture-once on-disk store that makes a +stage idempotent. Five of them: extraction-log.jsonl, session.json, +media-stubs.json, products.jsonl, sections/<slug>.json.

+ +

Faultpoint — An injectable crash marker used to test that resume-state +recovers correctly mid-pipeline.

+ +

The agent-in-the-loop

+ +

Watch loop — The default streaming run (data-liberation <url>): +extract per-URL while a Studio preview boots, driven by watch-runner.ts through +WatchPhase states.

+ +

Tick / judgment seam — The boundary where the deterministic +tick-scheduler emits judgmentNeeded[] and hands reasoning to the +agent/skill. The line between "code decides" and "model decides."

+ +

Output & quality

+ +

WXR — WordPress eXtended RSS, the XML export format DLA produces (targets +WXR 1.2). Built/appended by WxrBuilder into output.wxr.

+ +

Block-fixer — The canonicalization pass that runs reconstructed markup +through @wordpress/blocks so it survives a save cycle. Reconstructed sizing must +be expressed in terms it preserves.

+ +

Parity gate — A visual-comparison threshold (PARITY_GATE_SCORE = 0.995) +that decides whether a rebuilt page matches the source closely enough.

+ +

Design foundation — A semantic JSON of color/typography/spacing roles +distilled from the captured design sidecars (palette.json, typography.json, +breakpoints.json), consumed by theme generation. Roles are decisions; the +sidecars are observations.

+ +

Advanced capabilities

+ +

Variation hoisting — Lifting recurring instance-style constellations (≥ 3 +exact site-wide matches) into reusable block-style variations at +styles/blocks/lib-*.json, stripping the inline styling from instances. Fail-open; +excludes jetpack/*.

+ +

ensurePlugin / module-activate — The conditional install dance: a capability +(SVG, forms) triggers a one-time idempotent plugin ensure, and for Jetpack forms also +wp jetpack module activate contact-form (unconnected installs start with all +modules off).

+ +

Owned-source path — The local reconstruct path: an owned directory of +HTML/CSS/JS converted to a block theme with no detection or network extraction, preserving the +source DOM. See Theme / local path above. data-model.json turns JS-rendered +data into real WordPress posts + query loops.

+ +

GraphQL product path — Shopify's privileged extraction (with an +adminToken): Admin GraphQL API, resumable endCursor, idempotent +emittedHandles, JSON-API fallback on any failure.

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/how-it-works/reference/pipeline-map.html b/packages/data-liberation-agent/docs/how-it-works/reference/pipeline-map.html new file mode 100644 index 0000000000..b4338d7ccd --- /dev/null +++ b/packages/data-liberation-agent/docs/how-it-works/reference/pipeline-map.html @@ -0,0 +1,100 @@ + + + + + +Pipeline Map · Data Liberation + + + +
+ Data Liberation · Pipeline Map + Reference · prints on one page +
+ +
+

Pipeline Map

+

The whole flow on one page. Pin it up.

+ +
+ D deterministic + J judgment (agent/skill) + M mixed + — full rationale on the Determinism Map → +
+ +

Phase 1 — Extraction (persisted as ImportStage)

+

Extraction is measurement — almost entirely D.

+
initial → discovering → extracting → downloading-media → screenshotting → finalizing → complete
+                                                                              (error from any stage)
+
+
detect
D platform fingerprint → id (adapter recorded in session.json)
+
disc.
D adapter.discover → URL inventory, nav, archetypes
+
extract
D adapter.extract per-URL → output.wxr · extraction-log.jsonl · products.jsonl · media-stubs.json
+
media
D media-fetch download+dedupe (SVG→safe-svg/raster) → media files
+
shots
D extractFull on settled page → screenshots/ · html/ · sections/<slug>.json · palette/typography/breakpoints.json · manifest.json
+
+

J The path-choice gate (blocks vs carry vs theme) is decided by the operator/agent right after discovery — a decision that configures the pipeline, not a stage in it.

+ +

Streaming orchestration · the seam (live, WatchPhase — not persisted)

+
resolving-agent → resetting → detecting → discovering → extracting
+   → starting-preview → tick-drain → invoking-judgments → done   (+ error)
+                              │
+                              └─ tick-scheduler emits judgmentNeeded[]  ← the agent/skill seam
+

J judgmentNeeded[] is the line where deterministic code hands reasoning to the agent (cluster reps, parity, compose).

+ +

Phase 2 — Reconstruction (three paths)

+

The emit is D; what to emit & whether it's good is J.

+ + + + + + + +
PathToolOutput
D Blocksliberate_reconstruct_pagesnative Gutenberg blocks from section specs
D Carryliberate_reconstruct_pages_carryverbatim HTML + scoped CSS in core/html islands, <slug>-carry theme
D Theme/localliberate_convert_local_siteowned local dir → live block theme (no network)
+

Theme shell under all paths (D): liberate_theme_scaffoldliberate_install_theme. +Native core delegates to engine reconstructNativeAggregate (single-sourced in @automattic/blocks-engine).

+ + + + + + +
Reconstruction judgment steps
Jcluster representative selection · parity / refine · asset triage (vision) · data-model analysis (local)
Mdesign foundation (scaffold/validate = D, role-naming = J)
+ +

Resume-state stores (capture-once)

+ + + + + + + + + +
FileOwnsCrash-safety
extraction-log.jsonlURL dedupe (source of truth)append-only
session.jsonstage, opts, counts, cursorsatomic rename (+ .corrupt.<ts>)
media-stubs.jsonasset status + wpPostIdstatus + retry cap (3)
products.jsonlWoo products (one/line)append (resume:true)
sections/<slug>.jsonSectionSpec cache + censusatomic, independent (no lockfile)
+ +

Quality signals

+ + + + + + + + + + + +
KindArtifact / constantQuestion
GatePARITY_GATE_SCORE = 0.995does it match the source?
Gatevalidate_artifacts / block-parser oracledoes the markup parse?
Diagcomparison.json v2 (height, magenta=content-gap, ratio>0.02)where's the visual gap?
Diagfallback-diagnostics.jsonwhy did this section fall back?
Diagregion-audit.jsondid every landmark survive?
Diagasset-triage.jsonkeep or decoration?
Lossliberate_qa / content-differwhat didn't make it in? (loss, not hallucination)
+ +

Output base: ~/Studio/_liberations/<host> (resolve via liberate_paths).

+ + +
+ + diff --git a/packages/data-liberation-agent/docs/mcp.md b/packages/data-liberation-agent/docs/mcp.md new file mode 100644 index 0000000000..20d1f68a24 --- /dev/null +++ b/packages/data-liberation-agent/docs/mcp.md @@ -0,0 +1,241 @@ +# MCP Tools + +The data-liberation-agent MCP server exposes 12 tools via stdio transport. Start it with: + +```bash +npx tsx src/mcp-server.ts +``` + +## Extraction workflow + +These tools are typically called in order: detect -> discover -> extract -> verify -> setup -> import. + +### liberate_detect + +Detect the platform of a website. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `url` | yes | The URL of the website to detect | + +Returns: `platform` (godaddy-wm, hostinger, hubspot, shopify, squarespace, webflow, weebly, wix, or unknown), `confidence` (high/medium/low), `signals` (what was detected). + +### liberate_discover + +Inventory a website: fetch sitemap, categorize URLs, extract navigation structure, detect platform features. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `url` | yes | The URL of the website to inventory | +| `token` | no | API token for platforms requiring auth | +| `cdpPort` | no | CDP port for browser-based extraction | +| `verbose` | no | Enable detailed logging | + +Returns: `siteUrl`, `siteMeta` (title, tagline, language), `navigation` (nav links), `counts` (URLs by type), `urls` (full URL list with types), `platformFeatures` (detected features with transfer status and WP plugin recommendations). Shopify sites additionally return `shopDomain` — the `*.myshopify.com` hostname auto-detected from the storefront HTML, which `liberate_extract` will use if an `adminToken` is provided. + +### liberate_inspect + +Probe a site to assess extractability. Combines detection, sitemap scan, sample page probes, and feature detection into a single call. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `url` | yes | The URL of the website to inspect | +| `token` | no | API token if needed | +| `cdpPort` | no | CDP port for browser-based inspection | + +Returns: `platform`, `confidence`, `signals`, `sitemapFound`, `urlCount`, `counts` (by type), `probeResults` (sample page extractability), `platformFeatures`, `extractionFeasibility` (ready or limited). + +### liberate_extract + +Extract all content from a website. Produces a WXR file, media directory, redirect map, and optionally a products CSV. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `url` | yes | The URL of the website to extract | +| `outputDir` | yes | Directory to write WXR, media, and logs | +| `token` | no | API token for platforms requiring auth | +| `cdpPort` | no | CDP port for browser-based extraction | +| `delay` | no | Delay between requests in ms (default: 500) | +| `resume` | no | Resume a previous extraction (skip already-processed URLs) | +| `dryRun` | no | Extract 2-3 pages and report without writing WXR | +| `verbose` | no | Enable detailed per-page logging | +| `shopDomain` | no | **Shopify only** — the `*.myshopify.com` hostname used for Admin API calls. Usually unnecessary: `liberate_discover` auto-detects it from the storefront HTML (the `Shopify.shop` JS global) and stores it as `inventory.shopDomain`, so `liberate_extract` picks it up automatically even when the site is served on a custom domain. Only pass explicitly if auto-detection failed (e.g. Cloudflare-protected storefront). | +| `adminToken` | no | **Shopify only** — Admin API access token. When present, products are fetched via the Admin GraphQL API (2025-04) instead of the public JSON API, yielding richer data: `compareAtPrice` sale semantics, `inventoryItem.tracked` + `inventoryPolicy` stock status, `unitCost` cost-of-goods, collections as categories, `measurement.weight` unit normalization, and global SEO metafields. | +| `screenshots` | no | When `true`, runs the screenshot capture loop after extraction. Results land under `/screenshots/` with a `manifest.json` keyed by URL; no postmeta or CSV columns are written — any cross-reference against the WXR / products CSV happens on the filesystem. Adds one `ImportSession` stage (`screenshotting`) that is resumable. | + +Returns: `wxrPath`, `redirectMapPath`, `outputDir`, `summary` (counts, quality scores), `failures` (URLs and errors), `wxrValidation`. + +**Resume semantics:** when `resume: true`, three state files are consulted: +- `extraction-log.jsonl` — skips URLs with a `processed` entry +- `session.json` — restores pipeline stage, original opts, and adapter pagination cursors (Shopify GraphQL resumes mid-catalog via persisted `endCursor` + emitted-handle set) +- `media-stubs.json` — permanently-failed and user-ignored media URLs are skipped + +### liberate_status + +Check progress of a running or completed extraction. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `outputDir` | yes | The output directory of the extraction | + +Returns: `running` (boolean), `processed`, `failed` counts. + +## Screenshots + +### liberate_screenshot + +Capture full-page + scrolled-state screenshots (desktop 1440×900 + mobile 390×844) plus rendered HTML for every URL on a site. Runs independently from extraction — useful for pre-liberation analysis or feeding downstream AI design-system tools. Also produces a site-analysis summary (palette, typography, metadata) sampled from representative pages. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `url` | yes (unless `urls` is set) | The site URL. Used for sitemap discovery and as the same-origin anchor for all captures. | +| `urls` | no | Explicit list of URLs to capture, bypassing sitemap discovery. Every URL must share origin with `url` (or with `urls[0]` if no `url` is given). | +| `outputDir` | yes | Directory to write `screenshots/`, `html/`, `manifest.json`, and `site-analysis.json`. | +| `types` | no | Array of URL types to filter by: `page`, `post`, `product`, `homepage`, `gallery`, `event`. Defaults to all. Ignored when `urls` is passed. | +| `limit` | no | Cap to first N URLs after filtering. | +| `concurrency` | no | Parallel captures. Default 3, max 10. | +| `browserRestartEvery` | no | Close + relaunch Chromium every N URLs to bound memory. Default 100. Restarts happen at batch boundaries, never mid-batch. | +| `cdpPort` | no | Connect to an existing Chrome session via CDP (for authenticated sites). | +| `force` | no | Re-capture even if output files already exist. Default false. | + +**Returns:** `outputDir`, `manifestPath`, `siteAnalysisPath`, `captured` (count), `skipped` (count), `failed` (array of `{ url, error }`), `stage` (`screenshotting` → `complete`). + +**Output layout:** + +``` +/ + screenshots/ + manifest.json URL → files join table + desktop/.png full-page desktop + desktop/.scrolled.png post-scroll viewport (long pages only) + mobile/.png full-page mobile + mobile/.scrolled.png post-scroll viewport (long pages only) + html/.html rendered HTML post-hydration + site-analysis.json palette + typography + metadata +``` + +Scrolled-state screenshots are silently skipped on pages shorter than ~2.5 viewports — they don't have a distinct scrolled state worth capturing. + +**Example:** + +```json +{ + "url": "https://example.com", + "outputDir": "./output/example.com", + "types": ["page", "post"], + "concurrency": 5, + "browserRestartEvery": 50 +} +``` + +To capture screenshots as part of a full extract run (rather than invoking this tool directly), use `liberate_extract` with `screenshots: true`. The manifest and source URLs on extracted content are enough to correlate screenshots with WXR items or CSV product rows on the filesystem. + +## Debugging & Reconnaissance + +### liberate_map_apis + +Map all API endpoints used by a website by navigating pages via CDP and capturing JSON network traffic. Produces a categorized endpoint catalog with sample responses and auth headers. Use during `/adapt` reconnaissance to reverse-engineer a new platform. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `cdpPort` | yes | Chrome DevTools Protocol port (e.g. 9222) | +| `url` | yes | The URL of the site to map | +| `crawlUrls` | no | Additional URLs to navigate (e.g. admin dashboard sections) | +| `followLinks` | no | Follow same-origin links from the main page, up to 20 (default: false) | + +Returns: `totalApiCalls`, `totalEndpoints`, `endpoints` (array sorted by call count, each with path, methods, statuses, sections, queryParams, sampleRequestHeaders, samplePostData, sampleResponsePreview), `categories` (endpoints grouped by type: Content, Site Config, Auth & Identity, Commerce, Analytics, Media & Assets, Other), `authHeaders` (custom headers observed on API calls). + +### liberate_probe + +Probe a browser page via CDP for extraction-relevant data. Requires a running Chrome with `--remote-debugging-port`. Use for debugging extraction failures — called by the `/diagnose` skill. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `cdpPort` | yes | Chrome DevTools Protocol port (e.g. 9222) | +| `url` | no | Only probe pages on this domain (probes all tabs if omitted) | + +Returns an array of probe results (one per matching browser tab), each containing: `globals` (window objects with platform prefixes), `jsonLd` (structured data), `cookies` (names/domains/flags, not values), `localStorage` (key names/sizes/previews), `networkEntries` (API calls from Performance API), `identity` (platform-specific IDs — Shopify shop name, Squarespace websiteId, Wix metaSiteId). + +## Post-extraction + +### liberate_verify + +Verify a completed extraction. Checks for stale CDN URLs, failed pages, missing media, and items needing manual attention. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `outputDir` | yes | The output directory of the extraction to verify | + +Returns: `wxrFound`, `contentItems`, `pages`, `posts`, `mediaAttachments`, `mediaOnDisk`, `staleCdnUrls`, `failedUrls`, `failedMedia`, `redirectCount`, `qualityScores` (high/medium/low), `manualAttentionItems`. + +## Quality assurance + +### liberate_qa + +Compare extracted WXR content against the original source site page by page. Reports text similarity, missing headings/images/links, and grades each page. Optionally patches fixable issues like missing alt text. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `wxrFile` | yes | Path to the WXR file to QA | +| `fix` | no | Patch fixable issues in the WXR (default: false) | + +Returns: `pages` (array of per-page results with slug, sourceUrl, grade, diff details), `skipped` (count of pages without source URL), `summary` (pass/warn/fail/error/fixed counts). + +## WordPress import + +### liberate_setup + +Validate WordPress connection before importing. Checks site reachability, REST API availability, and authentication. Returns step-by-step guidance if anything fails. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `site` | yes | WordPress site domain (e.g. mysite.com) | +| `username` | yes | WordPress username | +| `token` | yes | WordPress application password | + +Returns: `siteUrl`, `siteReachable`, `restApiAvailable`, `authenticated`, `siteName`, `userName`, `errors`, `guidance`. + +### liberate_import + +Import a WXR file into a WordPress site via the REST API. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `wxrFile` | yes | Path to the WXR file to import | +| `site` | yes | WordPress site domain | +| `username` | yes | WordPress username | +| `token` | yes | WordPress application password | +| `dryRun` | no | Preview without importing | +| `delay` | no | Delay between requests in ms (default: 500) | +| `only` | no | Only import specific type (categories, tags, media, pages, posts, comments, menus) | +| `verbose` | no | Enable detailed logging | +| `resume` | no | Resume a previous import, retrying only failed items | +| `importAuthors` | no | Create WordPress users for each author in the WXR (default: false — all content owned by authenticated user) | +| `woocommerceKey` | no | WooCommerce consumer key for product import | +| `woocommerceSecret` | no | WooCommerce consumer secret for product import | + +Returns: per-stage results (media, categories, tags, pages, posts, comments, menus, products) with total/created/failed counts, plus `redirectMap`. + +### `liberate_preview` + +Start a local WordPress site serving a completed extraction using Automattic Studio. Studio is required — if the `studio` CLI is not on PATH, the tool returns an error with a link to https://developer.wordpress.com/studio/. Extraction itself needs no WordPress. + +**Arguments:** +- `outputDir` (string, required) — path to the extraction output directory. +- `open` (boolean, optional) — focus the Studio app after the site is ready. + +**Returns:** `{ status: "ready" | "failed", url?, warnings?, error?, siteName? }`. `siteName` is the Studio site name (e.g. `example-com-2`). + +Each call creates a fresh Studio site with a collision-incremented name; old sites persist until you remove them with `studio site remove`. + +### `liberate_paths` + +Resolve where liberation output lives for a given URL. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `url` | no | The website URL. When provided, `siteDir` is populated. | + +**Returns:** `{ base, siteDir? }`. `base` is the resolved output base (`~/Studio/_liberations` by default; overridable via the `DLA_OUTPUT_DIR` env var or `--output` on the CLI). `siteDir` is `base/` when `url` is given. + +Skills and agents MUST call this tool to locate liberation output instead of assuming `output//` relative to cwd. The default output base changed from `./output` (cwd-relative) to `~/Studio/_liberations` (user-owned, Studio-adjacent). diff --git a/packages/data-liberation-agent/docs/skills.md b/packages/data-liberation-agent/docs/skills.md new file mode 100644 index 0000000000..28f3cb43b6 --- /dev/null +++ b/packages/data-liberation-agent/docs/skills.md @@ -0,0 +1,88 @@ +# Skills + +Skills are guided multi-step workflows available when using data-liberation-agent as an AI plugin. Each skill walks the AI through a complete process with phases, decision points, and quality checks. + +Skill definitions live in `skills//SKILL.md`. + +## User-facing skills + +These are the skills you invoke directly in Claude Code (or another agent). + +### /liberate + +**Front door — capture a site once, then choose the reconstruct path and dispatch the matching sub-skill.** + +Flow (idempotent — re-running on an already-captured site skips straight to the path choice): +1. Detect the platform +2. Discover all content (sitemap, navigation, platform features like stores/bookings/forms) +3. Extract pages, posts, media, and products +4. Capture screenshots, design tokens (palette, typography, breakpoints), and rendered HTML per URL +5. Confirm + path checkpoint: show inventory + estimated scope/cost/time + a platform-informed recommendation, then choose the reconstruct path (picking one is the go-ahead): + - **blocks + products** → dispatch `/replicate-with-blocks` (editable block theme + WooCommerce) + - **theme replication** → dispatch `/replicate-theme` (high-fidelity carry-and-scope) +6. The chosen sub-skill reconstructs, installs into a local WordPress site, and produces its own run-report + +Handles resume for interrupted runs. In agent mode, progress is the agent's own narration; the headless extraction CLI keeps its own Ink TUI. Flags platform-specific features that won't transfer automatically (with WordPress plugin recommendations). + +### /qa + +**Compare extracted WXR content against the original source site and fix discrepancies.** + +QA workflow: +1. Parse the WXR and fetch each original source page +2. Compare text, headings, images, and links with weighted scoring +3. Grade each page (pass/warn/fail) and compute a health score (0-100) +4. Fix issues: patch minor gaps in the WXR, flag major gaps for re-extraction +5. Verify fixes improved quality, revert if not +6. Escalate to `/diagnose` if patterns of failure emerge + +Tiers: quick (fix critical only), standard (fix critical + warnings), exhaustive (fix all). + +### /diagnose + +**Debug failed or low-quality extractions by analyzing logs and probing the source site.** + +Diagnostic workflow: +1. Triage with `liberate_verify` for a structured overview, then dig into raw logs +2. Classify the problem (high failure rate, individual failures, low quality, crash, product issues) +3. Investigate root causes (rate limiting, bot detection, wrong adapter, content selectors, network issues) +4. Deep browser probe with `liberate_probe` via CDP (window globals, cookies, localStorage, network entries, platform identity) — useful for verifying auth state and finding alternate data sources +5. Fix at the right level (adapter code, configuration, data patches) +6. Verify the fix improved results +7. Document findings in DISCOVERIES.md + +### /adapt + +**Build a new platform adapter to extract content from an unsupported platform.** + +Adapter development workflow: +1. Reconnaissance: platform detection signals, content discovery methods, extraction approach +2. API mapping with `liberate_map_apis` via CDP — automatically discovers all JSON API endpoints, categorizes them, captures sample responses and auth headers +3. Browser probing with `liberate_probe` — inspect window globals, localStorage, cookies, and platform identity for client-side data sources +4. Build the adapter implementing `PlatformAdapter` (detect, discover, extract) +5. Add product support if the platform has e-commerce +6. Register in the MCP server and CLI +7. Write tests with fixture data +8. Manual verification with `--dry-run --verbose` +9. Document in README.md and DISCOVERIES.md + +## Orchestration-internal skills + +The following skills are invoked **only by the orchestrator**, not by users — invoking them directly mid-session conflicts with the orchestrator's state. They are marked `disable-model-invocation` so they don't surface in `/`-discovery. + +**The two reconstruct paths** `/liberate` dispatches to (the choice offered at the path checkpoint): + +- **`replicate-with-blocks`** — block reconstruct: design-foundations → theme → clustering → section extraction → builder fan-out → assemble → validate → install → visual-QA loop → `run-report.json`. Editable WordPress block theme + WooCommerce. +- **`replicate-theme`** — carry-and-scope reconstruct: carries source markup into `core/html` islands, scopes the source CSS, installs a `-carry` theme, compares → `run-report-carry.json`. High-fidelity, non-block-editable. + +**The helper skills** these (and `design-qa`) call: + +| Skill | Purpose | +|---|---| +| `design-foundations` | Analyze captured tokens + rep HTML + screenshots → `design-foundation.json` + frozen `design.md` brief | +| `creating-themes` | Scaffold `theme.json`, `style.css`, `functions.php`, parts, base templates, self-hosted fonts | +| `generating-patterns` | Builder (fanned out per cluster rep) → section layout skeletons as strings | +| `compose-page-blocks` | Misfit pages only — full page HTML/content → block `post_content` markup when deterministic slot-fill can't map cleanly | +| `design-qa` | Visual QA loop: replica vs source screenshots → A/B/C classification + fix directives + `run-report.json` | +| `editing-themes` / `editing-blocks` / `creating-blocks` | Apply QA fix directives to theme files or emit new embedded blocks | +| `testing-*` | Gate checks (build, validate-artifacts, responsiveness) run by the orchestrator at checkpoints | diff --git a/packages/data-liberation-agent/docs/wix-content-endpoints.md b/packages/data-liberation-agent/docs/wix-content-endpoints.md new file mode 100644 index 0000000000..9094da715a --- /dev/null +++ b/packages/data-liberation-agent/docs/wix-content-endpoints.md @@ -0,0 +1,186 @@ +# Wix authenticated content endpoints + +The ten load-bearing endpoints a logged-in Wix editor or dashboard tab +hits to read site content the public renderer doesn't expose: drafts, +unpublished products, contacts, members, form submissions, bookings, +blog drafts and full Ricos source, full-resolution media originals, +and hidden / password-protected pages. + +This is a content reference. For Wix's auth scheme, window globals, +and infrastructure endpoints (account, premium status, feature flags, +etc.), see the 2026-03-31 *Wix Dashboard API reverse engineering via +CDP* entry in [`DISCOVERIES.md`](../DISCOVERIES.md). + +URLs are stable in shape across 2024–2026. Wix versions routes +constantly (`v1` → `v2` → `v4`) and A/B-routes the same call through +different hostnames — match on path substrings, not exact URLs. + +## CMS — Data Collections (items) + +- **URL:** `https://editor.wix.com/_api/cloud-data/v2/items/query?.r=` + Variants: `/items/count`, `/items/update` (POST). +- **Method:** GET. Parameters packed into the `.r` query string as + base64url-encoded JSON. +- **Draft inclusion (the key unlock):** the request must set both + `environment: "SANDBOX_PREFERRED"` AND + `publishPluginOptions.includeDraftItems: true`. `"LIVE"` returns + published items only — the slice public scraping sees. +- **Pagination:** offset-based (`paging.offset`, `paging.limit`). + Self-throttled to `limit: 50` even though docs quote 100. +- **Image URIs:** `IMAGE`-typed fields return + `wix:image://v1/~mv2.jpg/#originWidth=N&originHeight=N`. + Resolve to `https://static.wixstatic.com/media/.jpg` (drop the + `~mv2.jpg` segment). +- **System fields on every item:** `_id`, `_owner`, `_createdDate`, + `_updatedDate`, `_publishStatus` (`PUBLISHED` / `DRAFT`), + `_publishDate`, `_draftDate`. +- **Public scraping misses:** every draft item, every field public + templates don't render, private collections. + +## CMS — Collections schema + +- **URL:** `https://editor.wix.com/_api/cloud-data/v2/collections?paging.offset=0&consistentRead=true&includeAllowedDataPermissions=true` +- **Response:** single payload describing every collection on the + site. Per-collection: `id`, `collectionType` (`WIX_APP` / custom), + `displayName`, `displayField`, `fields[]` with `key`, + `displayName`, `type` (`TEXT` / `NUMBER` / `URL` / `MULTI_REFERENCE` + / `IMAGE` / ...), `capabilities.sortable`, + `capabilities.queryOperators`. +- **Why call first:** every items query needs schema to know which + fields exist, which are references, which are images needing URI + resolution. +- **Companion count:** `POST /_api/autocms-server/v1/batch/collections/count`. + +## Contacts + +- **URL:** `https://www.wixapis.com/contacts/v4/contacts/query` (POST), + `/{id}` (GET). +- **Response:** `{ contacts: [{ id, info: { name, emails, phones, + addresses, labels, extendedFields }, primaryInfo, source, + createdDate, updatedDate }] }`. +- **Public scraping misses:** the entire CRM — every contact from + forms, chat, subscriptions. +- **Gotcha:** `extendedFields` are keyed by internal slug; need a + separate `GET /contacts/v4/extended-fields` call to map slug → + display name. Pagination via `paging.cursor`. + +## Members + +- **URL:** `https://www.wixapis.com/members/v1/members/query` (POST), + `/{id}` (GET). Requires Members app installed. +- **Response:** `{ members: [{ id, contactId, loginEmail, status, + profile: { nickname, slug, photo, title, ... }, privacyStatus }] }`. +- **Public scraping misses:** member identity, profile data, and + privacy state. Passwords are not exfiltrable, but WP users can be + recreated from this. +- **Gotcha:** always cross-reference `contactId` to the Contacts API — + the full PII lives there, not on the Member. + +## Stores — products with variants + +- **URL:** `https://www.wixapis.com/stores-reader/v1/products/query` + (POST), legacy `https://www.wixapis.com/stores/v1/products/query`. + Variants: `/products/{productId}/variants/query` (POST). +- **Response:** `{ products: [{ id, name, slug, description, price, + sku, ribbon, stock, media, productOptions, manageVariants, + variants?, collectionIds, additionalInfoSections }] }`. +- **Public scraping misses:** unpublished products, the full variant + Cartesian, internal SKU / barcode / cost, stock quantities. +- **Gotchas:** + - When `manageVariants: false`, the top-level price is authoritative. + - When `manageVariants: true`, you must call the variants endpoint + per product — variants are NOT included inline in the products + list. + - Large stores rate-limit at ~10 QPS; batch with ~500 ms gaps. + +## Forms — submissions + +- **URL:** `https://www.wixapis.com/_api/wix-forms/v4/submissions/query` + (POST). Sometimes proxied through + `editor.wix.com/_api/wix-forms-app/v1/submissions`. +- **Response:** `{ submissions: [{ id, formId, submitter, submissions: + { field-key: value }, status, createdDate }] }`. +- **Public scraping misses:** every submission ever. The public HTML + shows form *definition*; submissions live only in the dashboard. +- **Gotcha:** Wix has shipped three forms products (classic Contact + Forms, Wix Forms app, newer Native Forms). Endpoint differs per + product. Field schemas live at a separate + `GET /wix-forms/v4/forms/{formId}` → + `{ fields: [{ target, label, type, validation }] }` — the `target` + is the key inside each submission. + +## Bookings + +- **URL:** `https://www.wixapis.com/bookings/v2/{bookings,services,slots}/query` + (POST). +- **Response (services):** `{ services: [{ id, name, type: + APPOINTMENT|CLASS|COURSE, schedule, payment, staffMemberIds, + locations, form }] }`. +- **Public scraping misses:** every booking, service config, staff + and location bindings. +- **Gotchas:** the three booking types map to very different WP + structures. The pagination cursor on bookings is shallow — for + historical export, time-range filter + re-query is safer. + +## Blog — drafts and full body + +- **URL:** `https://www.wixapis.com/blog/v3/posts/query` (POST). + Drafts: `/draft-posts/query`. +- **Response:** `{ posts: [{ id, title, excerpt, slug, + firstPublishedDate, url, coverMedia, memberId, categoryIds, tagIds, + richContent: { nodes: [...] } }] }`. +- **Public scraping misses:** drafts, scheduled posts, members-only + posts, and the full Ricos `richContent` JSON. The public HTML is a + *render* of the Ricos tree — having the source produces much higher + fidelity migrations. +- **Gotcha:** two URL shapes seen live — `/blog/v3/*` on `wixapis` and + `/_api/communities-blog-node-api/...` on the editor proxy. Capture + both; metadata field names differ. + +## Media Manager — full-resolution originals + +- **URL:** `https://www.wixapis.com/site-media/v1/files/search` (POST), + `/files/{fileId}/download-url` (GET). +- **Response (search):** `{ files: [{ id, displayName, mimeType, + sizeInBytes, mediaType, url, parentFolderId, labels, namespace, + operationStatus }] }`. +- **Response (download-url):** `{ downloadUrl, expires }` — **signed + URL, ~10 minute expiry.** +- **Public scraping misses:** the originals. Public pages serve resized + derivatives via `static.wixstatic.com/media/...` with `w_` / `h_` / + `q_` transforms. +- **Gotcha:** signed-URL expiry is brutal. Fetch immediately or + persist the URL with a `fetchBy` timestamp and re-request before + expiry. + +## Site structure — pages, menus, published state + +- **URL:** `https://www.wixapis.com/site-content/v1/site-structure` + (GET), `/pages/v1/pages/query` (POST), menus: + `/menus/v1/menus/query` (POST). +- **Response (site-structure):** `{ pages: [{ id, title, url, + pageType, seoData, hidden, password-protected, publishState }], + menus, homePage }`. +- **Public scraping misses:** hidden pages, password-protected pages + (you see they exist + URL, not contents), draft-only pages, menu + hierarchy as configured. +- **Gotcha:** `site-structure` returns top-level pages only. Blog + posts, product pages, etc. are virtual pages — use their own + endpoints above. + +## Bonus content-adjacent endpoints + +- `/email-marketing/v1/campaigns/query` — campaigns and subscriber + lists. +- `/seo-reporter/v1/...` — per-page SEO settings. +- `/analytics-reporter/v1/...` — traffic and conversion data. + +## Cross-cutting content gotchas + +- **Pagination styles vary.** Newer Wix APIs use opaque cursor strings + (`paging.cursor`); older use `paging.offset` / `paging.limit`. +- **Rate limits.** ~10 req/s/user observed on `wixapis.com`. +- **Editor-document parsing.** `editor-document-store/v1/document` + returns a `fixer`-encoded actions feed (not a pages tree). + Materialising pages from it requires Wix's own migration-fixer + library — preserving it as opaque is a reasonable baseline. diff --git a/packages/data-liberation-agent/gemini-extension.json b/packages/data-liberation-agent/gemini-extension.json new file mode 100644 index 0000000000..e8db8a384d --- /dev/null +++ b/packages/data-liberation-agent/gemini-extension.json @@ -0,0 +1,19 @@ +{ + "name": "data-liberation", + "description": "Extract content from closed web platforms (GoDaddy Websites & Marketing, Hostinger, HubSpot, Shopify, Squarespace, Webflow, Weebly, Wix) into WordPress-compatible WXR files. Inspect, extract, QA, and import to WordPress.", + "version": "0.1.0", + "author": { + "name": "Automattic" + }, + "homepage": "https://github.com/Automattic/data-liberation-agent", + "license": "GPL-2.0-or-later", + "contextFileName": "GEMINI.md", + "skills": "./skills/", + "mcpServers": { + "data-liberation": { + "command": "npx", + "args": ["tsx", "${extensionPath}${/}src${/}mcp-server.ts"], + "cwd": "${extensionPath}" + } + } +} diff --git a/packages/data-liberation-agent/package-lock.json b/packages/data-liberation-agent/package-lock.json new file mode 100644 index 0000000000..38202373e7 --- /dev/null +++ b/packages/data-liberation-agent/package-lock.json @@ -0,0 +1,4887 @@ +{ + "name": "data-liberation", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "data-liberation", + "version": "0.1.0", + "hasInstallScript": true, + "dependencies": { + "@automattic/blocks-engine": "^0.2.2", + "@modelcontextprotocol/sdk": "^1.27.0", + "@wordpress/block-serialization-default-parser": "^5.47.0", + "acorn": "^8.17.0", + "cheerio": "^1.2.0", + "domhandler": "^5.0.3", + "fast-xml-parser": "^5.5.10", + "ink": "^6.8.0", + "ink-spinner": "^5.0.0", + "papaparse": "^5.5.3", + "pixelmatch": "^5.3.0", + "playwright": "^1.44.0", + "pngjs": "^7.0.0", + "postcss": "^8.5.15", + "postcss-selector-parser": "^7.1.1", + "react": "^19.2.4", + "zod": "^4.3.6" + }, + "bin": { + "data-liberation": "dist/cli.js" + }, + "devDependencies": { + "@types/papaparse": "^5.5.2", + "@types/pixelmatch": "^5.2.6", + "@types/pngjs": "^6.0.5", + "@types/react": "^19.2.14", + "jsdom": "^26.1.0", + "single-file-cli": "^2.0.83", + "tsx": "^4.19.0", + "typescript": "^5.7.0", + "vitest": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@automattic/blocks-engine": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@automattic/blocks-engine/-/blocks-engine-0.2.2.tgz", + "integrity": "sha512-1d2C7QAhDJfaDlrDdkECE7bUkPttYUHmXizwgCPxZwVQhm8P5MTM99pvWYjh2+NsDRkQK7ZA1xHDNY5iEMW0gQ==", + "license": "GPL-3.0-or-later", + "dependencies": { + "cheerio": "1.2.0", + "domhandler": "5.0.3", + "jsdom": "29.0.1" + }, + "bin": { + "blocks-engine": "dist/cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/@csstools/css-color-parser": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz", + "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", + "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@automattic/blocks-engine/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/tldts": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.5.tgz", + "integrity": "sha512-RfEzKWcq5fHUOFq7J3rl3Oz6ylKGtcHqUznzj4EcXsxLSIjJcvpbXAQtWGeJQ0xKnimR5e0Cn+cn9TssfMzm+g==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.5" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/tldts-core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.5.tgz", + "integrity": "sha512-pGrwzZDvPwKe+7NNUqAunb6rqTfynr0VOUhCMdqbu5xlvNiszsAJygRzwvpVycdzejlbpY+SWJOn+s75Og7FEA==", + "license": "MIT" + }, + "node_modules/@automattic/blocks-engine/node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@automattic/blocks-engine/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz", + "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@nodable/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", + "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pixelmatch": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.6.tgz", + "integrity": "sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pngjs": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.5.tgz", + "integrity": "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wordpress/block-serialization-default-parser": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-5.49.0.tgz", + "integrity": "sha512-Zn2+vJN82kHiuJ8Fd7m1gllM9iqhGPU21QBczS0ac/ixuqd1W4H0ho4JKIiiTr1YLrbZ/QGJccWU4Te84vXbqw==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", + "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.9.3.tgz", + "integrity": "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.2.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^1.0.1", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.4.1", + "xml-naming": "^0.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.27", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", + "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-spinner": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ink-spinner/-/ink-spinner-5.0.0.tgz", + "integrity": "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA==", + "license": "MIT", + "dependencies": { + "cli-spinners": "^2.7.0" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "ink": ">=4.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-unsafe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz", + "integrity": "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/papaparse": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.4.tgz", + "integrity": "sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.1.tgz", + "integrity": "sha512-h7bxdzhHk8Knyc4Tj+jMaa7fEEoUJy7p1qtbVgkYg1Uhpe5Np5VuGXCRZnkZvU+Q42M1vStt0ifa3ueykRJPmQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-cdp": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/simple-cdp/-/simple-cdp-1.8.6.tgz", + "integrity": "sha512-c33zKvBOOIBfdfDCr++N8Dv6kY03wD+cGuu3MEiBJHC4LtHE5aSwjw5E+dOSeuXolS+8MoDzYuWrJdT+IFp8bw==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1.0", + "deno": ">=1.4" + } + }, + "node_modules/single-file-cli": { + "version": "2.0.83", + "resolved": "https://registry.npmjs.org/single-file-cli/-/single-file-cli-2.0.83.tgz", + "integrity": "sha512-MGKOyvuOgdw5HT1Z5lpzgr30O0uq34Tb6+kPRAbUp2rBMvpxdr0oxn0SqVV0x1oM0g5T8JzvQpUdloOVJkZZrA==", + "dev": true, + "dependencies": { + "simple-cdp": "^1.8.6", + "ws": "^8.18.3" + }, + "bin": { + "single-file": "single-file-node.js" + }, + "engines": { + "bun": ">=1.0", + "deno": ">=1.4", + "node": ">=20" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strnum": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/type-fest": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", + "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/packages/data-liberation-agent/package.json b/packages/data-liberation-agent/package.json new file mode 100644 index 0000000000..d92b316b70 --- /dev/null +++ b/packages/data-liberation-agent/package.json @@ -0,0 +1,56 @@ +{ + "name": "data-liberation", + "version": "0.1.0", + "description": "Extract content from closed web platforms into WordPress-compatible WXR files", + "type": "module", + "bin": { + "data-liberation": "./dist/cli.js" + }, + "scripts": { + "import": "tsx src/cli.ts import", + "build": "tsc && node scripts/copy-runtime-assets.mjs", + "copy-runtime-assets": "node scripts/copy-runtime-assets.mjs", + "test": "vitest run", + "test:watch": "vitest", + "mcp": "tsx src/mcp-server.ts", + "inspect": "tsx src/cli.ts inspect", + "qa": "tsx src/cli.ts qa", + "verify": "tsx src/cli.ts verify", + "setup": "tsx src/cli.ts setup", + "liberate": "tsx src/cli.ts", + "postinstall": "playwright install chromium" + }, + "engines": { + "node": ">=18" + }, + "dependencies": { + "@automattic/blocks-engine": "^0.2.2", + "@modelcontextprotocol/sdk": "^1.27.0", + "@wordpress/block-serialization-default-parser": "^5.47.0", + "acorn": "^8.17.0", + "cheerio": "^1.2.0", + "domhandler": "^5.0.3", + "fast-xml-parser": "^5.5.10", + "ink": "^6.8.0", + "ink-spinner": "^5.0.0", + "papaparse": "^5.5.3", + "pixelmatch": "^5.3.0", + "playwright": "^1.44.0", + "pngjs": "^7.0.0", + "postcss": "^8.5.15", + "postcss-selector-parser": "^7.1.1", + "react": "^19.2.4", + "zod": "^4.3.6" + }, + "devDependencies": { + "@types/papaparse": "^5.5.2", + "@types/pixelmatch": "^5.2.6", + "@types/pngjs": "^6.0.5", + "@types/react": "^19.2.14", + "jsdom": "^26.1.0", + "single-file-cli": "^2.0.83", + "tsx": "^4.19.0", + "typescript": "^5.7.0", + "vitest": "^3.0.0" + } +} diff --git a/packages/data-liberation-agent/prompts/godaddy-wm.md b/packages/data-liberation-agent/prompts/godaddy-wm.md new file mode 100644 index 0000000000..22bd85fdd8 --- /dev/null +++ b/packages/data-liberation-agent/prompts/godaddy-wm.md @@ -0,0 +1,94 @@ +# GoDaddy Websites & Marketing to WordPress Migration Prompt + +Copy everything below this line and paste it into your AI assistant (Claude, ChatGPT, Gemini, etc.). + +--- + +I want to migrate my website from GoDaddy's legacy **Websites & Marketing** (W+M) platform to WordPress. My site URL is: **[PASTE YOUR SITE URL HERE]** + +> **Note:** W+M is the older GoDaddy website builder (also called "Go Daddy Website Builder" in page sources), *not* the newer Airo AI Builder. GoDaddy offers no data export from W+M, so this adapter rescues your content by crawling the public site. + +I have (or will create) a WordPress site. Please help me migrate using the playbook at https://github.com/Automattic/data-liberation-agent — read AGENTS.md first for full instructions. + +Here's what I need you to do: + +## Step 1: Inspect my site + +Run the inspection to see what we're working with: + +```bash +npm run inspect -- [SITE URL] +``` + +This will: +- Detect the platform and confirm it's GoDaddy Websites & Marketing (look for the `Go Daddy Website Builder` generator tag and `img1.wsimg.com/isteam` CDN) +- Fetch the three W+M sub-sitemaps (`sitemap.website.xml`, `sitemap.blog.xml`, `sitemap.ols.xml`) and categorize every URL +- Probe sample pages to test extractability + +Show me the full inspection results and wait for my approval before proceeding. + +## Step 2: Extract all content + +Run the full extraction: + +```bash +npm run liberate -- [SITE URL] --output ./output --verbose +``` + +This will: +- Fetch each page via HTTP +- For **blog posts**: parse the `window._BLOG_DATA` hydration blob and convert the Draft.js content structure (blocks + entityMap) into HTML, preserving paragraphs, headings, lists, links, and images +- For **pages**: strip the W+M header (`data-aid="HEADER_SECTION"`) and footer (`[data-aid^="FOOTER_"]`) and keep the remaining body content +- Download every image referenced from `img1.wsimg.com/isteam/...` +- Preserve for each piece of content: title, URL slug, publish date, categories, SEO title and description, featured image, author + +**If extraction gets interrupted:** + +```bash +npm run liberate -- [SITE URL] --output ./output --resume +``` + +## Step 3: Verify the extraction + +```bash +npm run verify -- ./output/[site-directory] +``` + +This reports how many pages, posts, and media files were extracted; flags any failed pages or media; and summarizes quality scores. Show me the verification report. + +## Step 4: Set up WordPress + +I need to create/have a WordPress site. Help me: +- Recommend a theme that matches my current site's visual style +- Create any categories my W+M blog used +- Configure basic settings: site title, tagline, permalink structure + +Then validate the WordPress connection: + +```bash +npm run setup -- --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +## Step 5: Import everything + +```bash +npm run liberate -- import ./output/[site-directory]/output.wxr \ + --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +Imports in order: media files, categories, pages, blog posts (with dates, categories, featured images), and navigation menus. All content is imported as **drafts** — you review and publish manually. + +## Step 6: Verify the import + +After import: +- Show me a URL mapping table: old W+M URL -> new WordPress URL (from `redirect-map.json`) +- Flag any posts/pages that are missing or had import errors +- Run verify again to check for any images still pointing to `img1.wsimg.com` +- List everything that needs manual attention + +## Known limitations of v1 + +- **No GoDaddy Online Store (OLS) product extraction yet.** If your W+M site has a store, products will not be migrated in v1. Let the team know — OLS support is planned for v1.1 once a real store URL is available for testing. +- **Thin-content pages** (e.g. domain-alias landing pages) may extract little more than their title and featured image, since W+M only surfaces the minimal DOM for those. + +Work methodically — do one step at a time, show me progress, and wait for my go-ahead before moving to the next step. diff --git a/packages/data-liberation-agent/prompts/shopify.md b/packages/data-liberation-agent/prompts/shopify.md new file mode 100644 index 0000000000..4be8a9cfa5 --- /dev/null +++ b/packages/data-liberation-agent/prompts/shopify.md @@ -0,0 +1,133 @@ +# Shopify to WordPress Migration Prompt + +Copy everything below this line and paste it into your AI assistant (Claude, ChatGPT, Gemini, etc.). + +--- + +I want to migrate my website from Shopify to WordPress. My Shopify site URL is: **[PASTE YOUR SHOPIFY URL HERE]** + +I have (or will create) a WordPress site. Please help me migrate using the playbook at https://github.com/Automattic/data-liberation-agent — read AGENTS.md first for full instructions. + +Here's what I need you to do: + +## Step 1: Inspect my site + +Run the inspection to see what we're working with: + +```bash +npm run inspect -- [SHOPIFY URL] +``` + +This will: +- Detect the platform and confirm it's Shopify +- Scan the sitemap and categorize every URL (pages, blog posts, products, collections, etc.) +- Detect platform-specific features (Store/E-commerce) and confirm product extraction is supported +- Probe sample pages to test extractability + +Show me the full inspection results and wait for my approval before proceeding. + +## Step 2: Extract all content + +Ask the AI about which of the two extraction tiers is right for you: + +**Tier 1 — Public extraction (no credentials needed)** + +```bash +npm run liberate -- [SHOPIFY URL] --output ./output --verbose +``` + +Uses Shopify's public JSON API (`/pages.json`, `/blogs.json`, `/products.json`) plus HTML fallback. Works on any public Shopify store without any login or token. Gets you the basics: pages, blog posts, products with prices and variants, images, categories, and tags. + +**Tier 2 — Admin API extraction (richer product data)** + +If you have admin access to your store, this gets you significantly more product detail: proper sale pricing, real stock policy, cost of goods, variant-level images, collection categories, and SEO metafields. + +To enable it, create a custom app in Shopify Admin: +1. Go to **Settings → Apps and sales channels → Develop apps → Create an app**. +2. Name it anything (e.g. "Data Liberation"). +3. Under **Configuration → Admin API access scopes**, enable: `read_products`, `read_inventory`, `read_content`, `read_online_store_pages`, and `read_online_store_navigation`. +4. Click **Install app** to generate an Admin API access token — copy it immediately, Shopify only shows it once. +5. Pass the token when running extraction: + ```bash + npm run liberate -- [SHOPIFY URL] --output ./output --admin-token shpat_xxx --verbose + ``` + +You do **not** need to tell the tool your `myshopify.com` subdomain — it auto-detects that from your storefront HTML, even if you use a custom domain. + +**Either tier will:** +- Use Shopify's public JSON API (`/pages.json`, `/blogs.json`, `/products.json`) for structured content when available +- Fall back to appending `.json` to individual page URLs for article/page data +- Fall back to HTML extraction if JSON API is unavailable +- Download every image from Shopify's CDN +- Extract **all products** with full detail: name, description, price, sale price, SKU, images, variants (sizes, colors), stock status, categories, and tags +- Products with multiple variants are exported as WooCommerce variable products with linked variations +- Preserve for each piece of content: title, URL slug, publish date, categories/tags, SEO title and description, featured image + +**If extraction gets interrupted:** + +```bash +npm run liberate -- [SHOPIFY URL] --output ./output --resume +``` + +## Step 3: Verify the extraction + +Check the output before importing: + +```bash +npm run verify -- ./output/[site-directory] +``` + +This reports: +- How many pages, posts, and media files were extracted +- Any stale Shopify CDN URLs still in content (cdn.shopify.com) +- Failed pages or media downloads +- Quality score breakdown +- Items needing manual attention + +Show me the verification report. Pay special attention to the product count — compare it against what's visible on the Shopify store to make sure nothing was missed. + +## Step 4: Set up WordPress + +I need to create/have a WordPress site. Help me: +- Recommend a theme that matches my current site's visual style +- Create all categories and tags from my Shopify site +- Configure basic settings: site title, tagline, permalink structure +- **If migrating products:** install WooCommerce on the WordPress site + +Then validate the WordPress connection: + +```bash +npm run setup -- --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This checks site reachability, REST API availability, and authentication. If anything fails, it shows step-by-step guidance (how to create an Application Password at WordPress Admin > Users > Profile > Application Passwords). Note: on WordPress.com and wpcomstaging.com sites, the password must be generated from the site's own wp-admin, not from wordpress.com/me/security/application-passwords (account-level passwords only work for the WordPress.com public API). + +## Step 5: Import everything + +```bash +npm run liberate -- import ./output/[site-directory]/output.wxr \ + --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This imports in order: +1. Media files to the WordPress media library (Shopify CDN URLs are rewritten to WordPress URLs) +2. Categories and tags +3. Pages with correct parent/child relationships +4. Blog posts with correct dates, categories, tags, and featured images +5. Navigation menus + +**For products:** Import `products.csv` via WooCommerce > Products > Import in WP admin. The CSV includes: +- Simple products (single variant) +- Variable products with linked variations (sizes, colors, etc.) +- Sale prices, SKUs, stock status, images + +## Step 6: Verify the import + +After import: +- Show me a URL mapping table: old Shopify URL -> new WordPress URL (from `redirect-map.json`) +- Flag any posts/pages that are missing or had import errors +- Run verify again to check for any images still pointing to Shopify CDN URLs +- Compare the WooCommerce product count against the original Shopify store +- List everything that needs manual attention with your recommendation for what WordPress plugin to use + +Work methodically — do one step at a time, show me progress, and wait for my go-ahead before moving to the next step. If you hit something unexpected, tell me what you found rather than guessing. diff --git a/packages/data-liberation-agent/prompts/squarespace.md b/packages/data-liberation-agent/prompts/squarespace.md new file mode 100644 index 0000000000..8e2facf9cd --- /dev/null +++ b/packages/data-liberation-agent/prompts/squarespace.md @@ -0,0 +1,139 @@ +# Squarespace to WordPress Migration Prompt + +Copy everything below this line and paste it into your AI assistant (Claude, ChatGPT, Gemini, etc.). + +--- + +I want to migrate my website from Squarespace to WordPress. My Squarespace site URL is: **[PASTE YOUR SQUARESPACE URL HERE]** + +I have (or will create) a WordPress site. Please help me migrate using the playbook at https://github.com/Automattic/data-liberation-agent — read AGENTS.md first for full instructions. + +Here's what I need you to do: + +## Step 1: Inspect my site + +Run the inspection to see what we're working with: + +```bash +npm run inspect -- [SQUARESPACE URL] +``` + +This will: +- Detect the platform and confirm it's Squarespace +- Scan the sitemap and categorize every URL (pages, blog posts, products, galleries, portfolios, etc.) +- Detect platform-specific features (Commerce, Memberships, Scheduling, Forms) and flag which ones transfer automatically vs which need a WordPress plugin +- Probe sample pages to test extractability + +Show me the full inspection results — especially the feature flags — and wait for my approval before proceeding. + +## Step 2: Set up admin extraction (recommended) + +For the best results, I should extract via my Squarespace admin session. This gives access to **drafts, unlisted pages, and richer metadata** that aren't available publicly. Squarespace 7.1 sites using the fluid engine often return empty content from the public API — admin access fixes this. + +Help me set this up: + +1. **Launch Chrome with remote debugging:** + ```bash + # macOS: + /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 + + # Linux: + google-chrome --remote-debugging-port=9222 + + # Windows: + "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 + ``` + +2. **In that Chrome window:** Navigate to my Squarespace site and **log in to my admin dashboard** + +3. **Once logged in:** Confirm I'm ready and proceed to extraction + +If I can't do admin extraction (e.g. I've already lost access), we can still extract public content — just skip the `--cdp-port` flag. The results will be less complete. + +## Step 3: Extract all content + +Run the full extraction with admin access: + +```bash +npm run liberate -- [SQUARESPACE URL] --output ./output --cdp-port 9222 --verbose +``` + +Or without admin access (public only): + +```bash +npm run liberate -- [SQUARESPACE URL] --output ./output --verbose +``` + +This will: +- Fetch each page's structured content via Squarespace's `?format=json` API +- With CDP: intercept admin API responses and `__NEXT_DATA__` hydration for richer content +- Fall back to Playwright DOM extraction for pages where JSON data is empty (common on 7.1 fluid engine sites) +- Download every image from Squarespace's CDN +- Extract the full blog archive (not limited to the 20-post RSS feed) +- Extract products as WooCommerce-compatible CSV (if commerce detected) +- Preserve for each piece of content: title, URL slug, publish date, categories/tags, SEO title and description, featured image + +**If extraction gets interrupted:** + +```bash +npm run liberate -- [SQUARESPACE URL] --output ./output --cdp-port 9222 --resume +``` + +## Step 4: Verify the extraction + +Check the output before importing: + +```bash +npm run verify -- ./output/[site-directory] +``` + +This reports: +- How many pages, posts, and media files were extracted +- Any stale Squarespace CDN URLs still in content +- Failed pages or media downloads +- Quality score breakdown +- Items needing manual attention + +Show me the verification report. If there are failures, offer to investigate with `--resume` or the `/diagnose` workflow. + +## Step 5: Set up WordPress + +I need to create/have a WordPress site. Help me: +- Recommend a theme that matches my current site's visual style +- Create all categories and tags from my Squarespace site +- Configure basic settings: site title, tagline, permalink structure + +Then validate the WordPress connection: + +```bash +npm run setup -- --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This checks site reachability, REST API availability, and authentication. If anything fails, it shows step-by-step guidance (how to create an Application Password at WordPress Admin > Users > Profile > Application Passwords). Note: on WordPress.com and wpcomstaging.com sites, the password must be generated from the site's own wp-admin, not from wordpress.com/me/security/application-passwords (account-level passwords only work for the WordPress.com public API). + +## Step 6: Import everything + +```bash +npm run liberate -- import ./output/[site-directory]/output.wxr \ + --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This imports in order: +1. Media files to the WordPress media library (Squarespace CDN URLs are rewritten to WordPress URLs) +2. Categories and tags +3. Pages with correct parent/child relationships +4. Blog posts with correct dates, categories, tags, and featured images +5. Navigation menus +6. If products were extracted: import `products.csv` via WooCommerce > Products > Import in WP admin + +All content is imported as **drafts** — you review and publish manually. + +## Step 7: Verify the import + +After import: +- Show me a URL mapping table: old Squarespace URL → new WordPress URL (from `redirect-map.json`) +- Flag any posts/pages that are missing or had import errors +- Run verify again to check for any images still pointing to Squarespace CDN URLs +- List everything that needs manual attention (forms, commerce, memberships, scheduling) with your recommendation for what WordPress plugin to use + +Work methodically — do one step at a time, show me progress, and wait for my go-ahead before moving to the next step. If you hit something unexpected, tell me what you found rather than guessing. diff --git a/packages/data-liberation-agent/prompts/webflow.md b/packages/data-liberation-agent/prompts/webflow.md new file mode 100644 index 0000000000..db93c46aee --- /dev/null +++ b/packages/data-liberation-agent/prompts/webflow.md @@ -0,0 +1,108 @@ +# Webflow to WordPress Migration Prompt + +Copy everything below this line and paste it into your AI assistant (Claude, ChatGPT, Gemini, etc.). + +--- + +I want to migrate my website from Webflow to WordPress. My Webflow site URL is: **[PASTE YOUR WEBFLOW URL HERE]** + +I have (or will create) a WordPress site. Please help me migrate using the playbook at https://github.com/Automattic/data-liberation-agent — read AGENTS.md first for full instructions. + +Here's what I need you to do: + +## Step 1: Inspect my site + +Run the inspection to see what we're working with: + +```bash +npm run inspect -- [WEBFLOW URL] +``` + +This will: +- Detect the platform and confirm it's Webflow +- Scan the sitemap and categorize every URL (pages, blog posts, products, etc.) +- Detect platform-specific features (E-commerce, Forms) and flag which ones transfer automatically vs which need a WordPress plugin +- Probe sample pages to test extractability + +Show me the full inspection results — especially the feature flags — and wait for my approval before proceeding. + +## Step 2: Extract all content + +Run the full extraction: + +```bash +npm run liberate -- [WEBFLOW URL] --output ./output --verbose +``` + +This will: +- Fetch each page via HTTP and extract content from Webflow's `.w-richtext` containers +- Fall back to `
`, `
`, or common content selectors if no rich text container is found +- Parse JSON-LD structured data for metadata +- Download every image +- Extract products as WooCommerce-compatible CSV (if e-commerce detected) +- Preserve for each piece of content: title, URL slug, publish date, categories/tags, SEO title and description, featured image + +**If extraction gets interrupted:** + +```bash +npm run liberate -- [WEBFLOW URL] --output ./output --resume +``` + +## Step 3: Verify the extraction + +Check the output before importing: + +```bash +npm run verify -- ./output/[site-directory] +``` + +This reports: +- How many pages, posts, and media files were extracted +- Any stale Webflow CDN URLs still in content (assets-global.website-files.com) +- Failed pages or media downloads +- Quality score breakdown +- Items needing manual attention + +Show me the verification report. If there are failures, offer to investigate with `--resume` or the `/diagnose` workflow. + +## Step 4: Set up WordPress + +I need to create/have a WordPress site. Help me: +- Recommend a theme that matches my current site's visual style +- Create all categories and tags from my Webflow site +- Configure basic settings: site title, tagline, permalink structure + +Then validate the WordPress connection: + +```bash +npm run setup -- --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This checks site reachability, REST API availability, and authentication. If anything fails, it shows step-by-step guidance (how to create an Application Password at WordPress Admin > Users > Profile > Application Passwords). Note: on WordPress.com and wpcomstaging.com sites, the password must be generated from the site's own wp-admin, not from wordpress.com/me/security/application-passwords (account-level passwords only work for the WordPress.com public API). + +## Step 5: Import everything + +```bash +npm run liberate -- import ./output/[site-directory]/output.wxr \ + --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This imports in order: +1. Media files to the WordPress media library +2. Categories and tags +3. Pages with correct parent/child relationships +4. Blog posts with correct dates, categories, tags, and featured images +5. Navigation menus +6. If products were extracted: import `products.csv` via WooCommerce > Products > Import in WP admin + +All content is imported as **drafts** — you review and publish manually. + +## Step 6: Verify the import + +After import: +- Show me a URL mapping table: old Webflow URL -> new WordPress URL (from `redirect-map.json`) +- Flag any posts/pages that are missing or had import errors +- Run verify again to check for any images still pointing to Webflow CDN URLs +- List everything that needs manual attention (forms, interactions, animations) with your recommendation for what WordPress plugin to use + +Work methodically — do one step at a time, show me progress, and wait for my go-ahead before moving to the next step. If you hit something unexpected, tell me what you found rather than guessing. diff --git a/packages/data-liberation-agent/prompts/wix.md b/packages/data-liberation-agent/prompts/wix.md new file mode 100644 index 0000000000..743d9e8a35 --- /dev/null +++ b/packages/data-liberation-agent/prompts/wix.md @@ -0,0 +1,108 @@ +# Wix to WordPress Migration Prompt + +Copy everything below this line and paste it into your AI assistant (Claude, ChatGPT, Gemini, etc.). + +--- + +I want to migrate my website from Wix to WordPress. My Wix site URL is: **[PASTE YOUR WIX URL HERE]** + +I have (or will create) a WordPress site. Please help me migrate using the playbook at https://github.com/Automattic/data-liberation-agent — read AGENTS.md first for full instructions. + +Here's what I need you to do: + +## Step 1: Inspect my site + +Run the inspection to see what we're working with: + +```bash +npm run inspect -- [WIX URL] +``` + +This will: +- Detect the platform and confirm it's Wix +- Scan the sitemap and categorize every URL (pages, blog posts, products, galleries, events, etc.) +- Detect platform-specific features (Wix Stores, Bookings, Forms, Members Area, Events, Forum) and flag which ones transfer automatically vs which need a WordPress plugin +- Probe sample pages to test extractability + +Show me the full inspection results — especially the feature flags — and wait for my approval before proceeding. + +## Step 2: Extract all content + +Run the full extraction: + +```bash +npm run liberate -- [WIX URL] --output ./output --verbose +``` + +This uses a headless browser to: +- Load every page and intercept Wix's internal API calls (the `/_api/` and `wixapis.com` responses contain the real content) +- Extract window globals (`__WIX_DATA__`, `__SITE_DATA__`, etc.) and JSON-LD structured data +- Fall back to DOM extraction and accessibility tree if API data is sparse +- Download every image from Wix's CDN (wixstatic.com/wixmp.com) +- Extract products as WooCommerce-compatible CSV (if store detected) +- Preserve for each piece of content: title, URL slug, publish date, categories/tags, SEO title and description, featured image + +**For large sites:** Extraction may take several minutes. If it gets interrupted, resume with: + +```bash +npm run liberate -- [WIX URL] --output ./output --resume +``` + +## Step 3: Verify the extraction + +Check the output before importing: + +```bash +npm run verify -- ./output/[site-directory] +``` + +This reports: +- How many pages, posts, and media files were extracted +- Any stale Wix CDN URLs still in content (these will break if Wix changes anything) +- Failed pages or media downloads +- Quality score breakdown +- Items needing manual attention + +Show me the verification report. If there are failures, offer to investigate with `--resume` or the `/diagnose` workflow. + +## Step 4: Set up WordPress + +I need to create/have a WordPress site. Help me: +- Recommend a theme that matches my current site's visual style +- Create all categories and tags from my Wix site +- Configure basic settings: site title, tagline, permalink structure + +Then validate the WordPress connection: + +```bash +npm run setup -- --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This checks site reachability, REST API availability, and authentication. If anything fails, it shows step-by-step guidance (how to create an Application Password at WordPress Admin > Users > Profile > Application Passwords). Note: on WordPress.com and wpcomstaging.com sites, the password must be generated from the site's own wp-admin, not from wordpress.com/me/security/application-passwords (account-level passwords only work for the WordPress.com public API). + +## Step 5: Import everything + +```bash +npm run liberate -- import ./output/[site-directory]/output.wxr \ + --site [MY-WORDPRESS-SITE] --username [MY-USERNAME] --token [APP-PASSWORD] +``` + +This imports in order: +1. Media files to the WordPress media library (Wix CDN URLs are rewritten to WordPress URLs) +2. Categories and tags +3. Pages with correct parent/child relationships +4. Blog posts with correct dates, categories, tags, and featured images +5. Navigation menus +6. If products were extracted: import `products.csv` via WooCommerce > Products > Import in WP admin + +All content is imported as **drafts** — you review and publish manually. + +## Step 6: Verify the import + +After import: +- Show me a URL mapping table: old Wix URL → new WordPress URL (from `redirect-map.json`) +- Flag any posts/pages that are missing or had import errors +- Run verify again to check for any images still pointing to Wix CDN URLs +- List everything that needs manual attention (bookings, forms, members area, events) with your recommendation for what WordPress plugin to use + +Work methodically — do one step at a time, show me progress, and wait for my go-ahead before moving to the next step. If you hit something unexpected, tell me what you found rather than guessing. diff --git a/packages/data-liberation-agent/scripts/_install.ts b/packages/data-liberation-agent/scripts/_install.ts new file mode 100644 index 0000000000..71c4b6f13e --- /dev/null +++ b/packages/data-liberation-agent/scripts/_install.ts @@ -0,0 +1,30 @@ +// Throwaway: assemble the on-disk theme tree (text files) into themeFiles[] and +// launch the replica preview (creates/reuses a site, imports WXR + media, +// activates the theme). Binary assets (woff2 fonts, logo.png) are bridged +// separately after install since themeFiles content is string-only. The theme +// slug is derived from the output dir, so this works for any extracted site. +import { readdirSync, readFileSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { startPreview } from '../src/lib/preview/studio.js'; +import { requireOutputDir, installThemeSlug } from './_site-meta.js'; + +const outputDir = requireOutputDir(); +const themeDir = join(outputDir, 'theme'); +const themeSlug = installThemeSlug(outputDir); +const TEXT = /\.(css|json|php|html|svg)$/i; +const files: { relativePath: string; content: string }[] = []; +(function walk(dir: string) { + for (const e of readdirSync(dir, { withFileTypes: true })) { + const p = join(dir, e.name); + if (e.isDirectory()) walk(p); + else if (TEXT.test(e.name)) files.push({ relativePath: relative(themeDir, p), content: readFileSync(p, 'utf8') }); + } +})(themeDir); +console.error(`themeFiles (${files.length}): ${files.map((f) => f.relativePath).join(', ')}`); + +const res = await startPreview({ + outputDir, + themeFiles: files, + themeSlug, +}); +console.log(JSON.stringify(res, null, 2)); diff --git a/packages/data-liberation-agent/scripts/_pw.ts b/packages/data-liberation-agent/scripts/_pw.ts new file mode 100644 index 0000000000..e85f84639e --- /dev/null +++ b/packages/data-liberation-agent/scripts/_pw.ts @@ -0,0 +1,34 @@ +// Shared Playwright helpers for the dev scripts in this directory. +// +// THE __name FIX +// tsx transpiles with esbuild's `keepNames`, which rewrites named functions and +// classes to `__name(fn, "name")` so `fn.name` survives. That's fine in Node, but +// when such a function is handed to `page.evaluate` / `page.addInitScript`, +// Playwright serializes it and runs it IN THE BROWSER — where `__name` does not +// exist → `ReferenceError: __name is not defined`. (vitest's transform does NOT +// keepNames, so vitest-run code is unaffected; this only bites `tsx scripts/*.ts` +// that pass a named function — or a closure containing one — into page.evaluate.) +// +// Fix: define a `__name` identity shim in every page context BEFORE any evaluate. +// The init script is passed as a STRING on purpose — a string is not transpiled by +// esbuild, so it can't itself be rewritten to reference the not-yet-defined shim +// (which a compiled closure would, re-introducing the bootstrap failure). +import type { Browser, Page } from 'playwright'; + +const NAME_SHIM = 'window.__name = window.__name || function (f) { return f; };'; + +/** A new page with the `__name` shim installed — use this instead of + * `browser.newPage()` in any tsx script that calls page.evaluate. */ +export async function newShimmedPage( + browser: Browser, + viewport: { width: number; height: number } = { width: 1440, height: 900 }, +): Promise { + const page = await browser.newPage({ viewport }); + await page.addInitScript(NAME_SHIM); + return page; +} + +/** Install the shim on a page created elsewhere (call before the first evaluate). */ +export async function shimNames(page: Page): Promise { + await page.addInitScript(NAME_SHIM); +} diff --git a/packages/data-liberation-agent/scripts/_qa-shot.ts b/packages/data-liberation-agent/scripts/_qa-shot.ts new file mode 100644 index 0000000000..93e6ac61e9 --- /dev/null +++ b/packages/data-liberation-agent/scripts/_qa-shot.ts @@ -0,0 +1,25 @@ +/** + * Throwaway QA screenshot helper: full-page shot of a URL at a given viewport. + * npx tsx scripts/_qa-shot.ts [width=1440] [height=900] + */ +import { chromium } from 'playwright'; +import { shimNames } from './_pw.js'; + +const [url, out, w = '1440', h = '900'] = process.argv.slice(2); +if (!url || !out) { console.error('usage: _qa-shot.ts [w] [h]'); process.exit(2); } + +const browser = await chromium.launch(); +const ctx = await browser.newContext({ viewport: { width: +w, height: +h }, deviceScaleFactor: 1 }); +const page = await ctx.newPage(); +await shimNames(page); +await page.goto(url, { waitUntil: 'load', timeout: 45000 }); +await page.evaluate(async () => { + for (let y = 0; y < document.body.scrollHeight; y += 600) { window.scrollTo(0, y); await new Promise((r) => setTimeout(r, 50)); } + window.scrollTo(0, 0); + const imgs = Array.from(document.images); + await Promise.race([Promise.all(imgs.map((im) => (im.complete ? 0 : im.decode().catch(() => 0)))), new Promise((r) => setTimeout(r, 5000))]); +}); +await page.waitForTimeout(1200); +await page.screenshot({ path: out, fullPage: true }); +await browser.close(); +console.log('shot:', out); diff --git a/packages/data-liberation-agent/scripts/_shot.ts b/packages/data-liberation-agent/scripts/_shot.ts new file mode 100644 index 0000000000..5226775978 --- /dev/null +++ b/packages/data-liberation-agent/scripts/_shot.ts @@ -0,0 +1,17 @@ +// Full-page screenshot of a URL at a given width, no 5s MCP cap. Used by the +// match-section and match-page skills to capture source-vs-built crops. +// Usage: npx tsx scripts/_shot.ts +import { chromium } from 'playwright'; +const [, , url, out, widthArg] = process.argv; +const width = Number(widthArg || 1008); +const b = await chromium.launch(); +const page = await b.newPage({ viewport: { width, height: 900 } }); +await page.goto(url, { waitUntil: 'networkidle', timeout: 60_000 }).catch(() => {}); +// Force below-fold lazy images to load by scrolling through (mouse.wheel — no +// page.evaluate, so no tsx __name footgun), then back to top. +for (let y = 0; y < 6000; y += 700) { await page.mouse.wheel(0, 700); await page.waitForTimeout(120); } +await page.keyboard.press('Home').catch(() => {}); +await page.waitForTimeout(2000); +await page.screenshot({ path: out, fullPage: true, timeout: 60_000 }); +await b.close(); +console.log('wrote', out); diff --git a/packages/data-liberation-agent/scripts/_site-meta.ts b/packages/data-liberation-agent/scripts/_site-meta.ts new file mode 100644 index 0000000000..e9fe0a60d3 --- /dev/null +++ b/packages/data-liberation-agent/scripts/_site-meta.ts @@ -0,0 +1,63 @@ +// Shared helpers for the replica-reconstruction driver scripts. Site identity is +// always derived from the run's OWN output dir (its WXR + dir name) so the +// scripts work for any extracted site — nothing is hardcoded to one source. +import { existsSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +// Single source of truth for the install theme slug (e.g. +// `output/www.example.com` → `www-example-com-replica`). +export { deriveInstallThemeSlug as installThemeSlug } from '../src/mcp-server/handlers/install-theme.js'; + +/** + * Require an output dir as the first positional CLI arg (e.g. `output/www.example.com`). + * Exits with a usage hint rather than silently defaulting to one site. + */ +export function requireOutputDir(scriptUsage = ' [args...]'): string { + const dir = process.argv[2]; + if (!dir) { + console.error(`usage: tsx scripts/${scriptName()} ${scriptUsage} (e.g. output/www.example.com)`); + process.exit(1); + } + return dir; +} + +function scriptName(): string { + return process.argv[1]?.split('/').pop() ?? ' +
`; + const out = extractMainContent(html); + expect(out).toContain('Genuine body copy worth keeping'); + expect(out).not.toContain('skip me'); + expect(out).not.toContain('site footer'); + expect(out).not.toContain('window.tracker'); + }); + + it('picks the densest text block when there is no landmark', () => { + const html = ` + + +
+

This is the main article. It has several sentences of real prose.

+

Enough text that its density clearly beats the navigation rail.

+
+ `; + const out = extractMainContent(html); + expect(out).toContain('This is the main article'); + expect(out).not.toContain('Archive'); + }); + + it('returns empty string when the body has no textual content', () => { + expect(extractMainContent('
')).toBe(''); + }); +}); + +describe('parseJsonLd', () => { + it('returns [] when there is no ld+json', () => { + expect(parseJsonLd('

no structured data

')).toEqual([]); + }); + + it('parses a single ld+json object', () => { + const html = ``; + expect(parseJsonLd(html)).toEqual([{ '@type': 'WebPage', name: 'Home' }]); + }); + + it('flattens multiple ld+json scripts in document order', () => { + const html = ` + + `; + const out = parseJsonLd(html); + expect(out).toHaveLength(2); + expect(out).toContainEqual({ '@type': 'Organization', name: 'Acme' }); + }); + + it('flattens a top-level array and unwraps @graph', () => { + const html = ` + + `; + const types = parseJsonLd(html).map((o) => (o as Record)['@type']); + expect(types).toEqual(['A', 'B', 'C']); + }); + + it('skips malformed json without throwing', () => { + const html = ` + + `; + expect(parseJsonLd(html)).toEqual([{ '@type': 'Valid' }]); + }); +}); + +describe('detectTypeFromJsonLd', () => { + it('detects a product', () => { + expect(detectTypeFromJsonLd([{ '@type': 'Product', name: 'Gizmo' }])).toBe('product'); + }); + + it('detects a blog post / article', () => { + expect(detectTypeFromJsonLd([{ '@type': 'BlogPosting' }])).toBe('post'); + expect(detectTypeFromJsonLd([{ '@type': 'NewsArticle' }])).toBe('post'); + }); + + it('handles @type given as an array', () => { + expect(detectTypeFromJsonLd([{ '@type': ['Thing', 'Article'] }])).toBe('post'); + }); + + it('returns undefined for non content-bearing types', () => { + expect(detectTypeFromJsonLd([{ '@type': 'WebPage' }])).toBeUndefined(); + expect(detectTypeFromJsonLd([])).toBeUndefined(); + }); + + it('prefers product when both product and article are present', () => { + expect(detectTypeFromJsonLd([{ '@type': 'Article' }, { '@type': 'Product', name: 'X' }])).toBe('product'); + }); +}); + +describe('productLdJsonScript', () => { + it('returns a parseable ld+json script for the first Product node', () => { + const script = productLdJsonScript([{ '@type': 'Product', name: 'Gizmo', offers: { price: '9.99' } }]); + expect(script).toContain('application/ld+json'); + expect(script).toContain('"@type":"Product"'); + expect(script).toContain('Gizmo'); + }); + + it('returns null when there is no product', () => { + expect(productLdJsonScript([{ '@type': 'Article' }])).toBeNull(); + }); +}); diff --git a/packages/data-liberation-agent/src/adapters/default/content.ts b/packages/data-liberation-agent/src/adapters/default/content.ts new file mode 100644 index 0000000000..667487b8ae --- /dev/null +++ b/packages/data-liberation-agent/src/adapters/default/content.ts @@ -0,0 +1,127 @@ +import * as cheerio from 'cheerio'; + +// Structural chrome that is never primary content. Removed from whatever region +// we settle on, so a breadcrumb + + `; + + const chrome = extractThemeChromeFromHtml(html, 'https://example.com/'); + + expect(chrome.header?.logoUrl).toBe('https://example.com/media/logo.png'); + expect(chrome.header?.links).toEqual([ + { label: 'HOME', href: '/', external: false }, + { label: 'PRODUCTS', href: '/products', external: false }, + { label: 'JOBS', href: 'https://jobs.example.com/posting', external: true }, + ]); + }); + + it('captures only the top-level primary menu from a Shopify-style header', () => { + // Mirrors getsnooz.com: an inline desktop menu with mega-menu sub-links, + // a duplicate mobile drawer copy, social icons, and account/cart links. + const html = ` +
+ + + +

SNOOZ

+ + +
+ `; + + const chrome = extractThemeChromeFromHtml(html, 'https://getsnooz.com/'); + + expect(chrome.header?.logoUrl).toBe('https://getsnooz.com/cdn/shop/files/SNOOZ-Logo-PNG.png?v=1'); + expect(chrome.header?.logoAlt).toBe('SNOOZ'); + // Exactly the three real top-level items — no mega-menu sublinks, no + // drawer duplicate, no social/account/cart junk. + expect(chrome.header?.links).toEqual([ + { label: 'Shop', href: '/pages/shop-all', external: false }, + { label: 'Bundle & Save', href: '/pages/sleep-bundle', external: false }, + { label: 'About', href: '/pages/about-us', external: false }, + ]); + // White Shopify header → light tone (default). + expect(chrome.header?.tone).toBe('light'); + }); + + it('infers a dark header tone from a dark inline background', () => { + const html = ` +
+ Brand logo + +
+ `; + const chrome = extractThemeChromeFromHtml(html, 'https://example.com/'); + expect(chrome.header?.tone).toBe('dark'); + }); + + it('captures the visible-bar CTA (not a drawer control) and no utilities when the source has none', () => { + // page-builder shape: nav links + a "CALL US" button in the open bar, plus a + // mobile hamburger dialog holding "Back to site" + a duplicate CTA. + const html = ` +
+ + + +
`; + const chrome = extractThemeChromeFromHtml(html, 'https://demo.example.com/'); + expect(chrome.header?.cta).toEqual({ label: 'CALL US', href: '', external: false }); + expect(chrome.header?.utilities).toBeUndefined(); // no cart/account/search affordances + }); + + it('detects only the utility affordances present (cart + search, not account)', () => { + const html = ` +
+ + cart + +
`; + const chrome = extractThemeChromeFromHtml(html, 'https://shop.example.com/'); + expect(chrome.header?.utilities).toEqual({ search: true, cart: true }); + }); + + it('extracts footer text and links from the source footer', () => { + const html = ` + + `; + + const chrome = extractThemeChromeFromHtml(html, 'https://demo.example.com/'); + + expect(chrome.footer?.text).toContain('Acme Co'); + expect(chrome.footer?.text).toContain('123 Demo Street, Springfield'); + expect(chrome.footer?.links).toEqual([ + { label: 'Privacy Policy', href: '/privacy-policy', external: false }, + { label: 'Accessibility Statement', href: '/accessibility-statement', external: false }, + ]); + }); + + it('records sourceSelector on header and footer evidence', () => { + // Fictional site: header uses a plain
tag, footer a plain