npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wftoast

v0.3.1

Published

CLI for migrating a Webflow site to Astro

Downloads

1,199

Readme

wftoast

CLI for the Webflow → Astro migration. Runs a local server on http://localhost:3774 that two browser clients connect to, and writes what they return under webflow/ in the directory you run it from.

pull-cms is the exception: it reads the CMS straight from Webflow's public Data API with a site token, so it needs no browser client at all.

For pull and fetch-dom, both clients are required and must be set up first:

  • packages/bridge — the Webflow Designer Extension, for pages and components. It is a Webflow App installed into your Workspace: open it from the Designer's Apps panel and leave the panel open, on the site you're pulling, for the whole run. Nothing to build.
  • packages/extension — the Chrome extension, for each page's DOM and for listing which Designer sites are open. Download it from Releases and load it unpacked at chrome://extensions, with a Designer tab open. (No release exists yet — until one does, build it from source; see its README.)

Commands

wftoast pull

Full extraction. Both clients are required. Before pulling anything it confirms which site you mean and that the bridge panel is open on that site — so a wrong target fails with nothing written, rather than half-way through:

webflow/
  site.json                          # global site settings     (extension)
  dom.json                           # homepage's whole /dom    (extension)
  pages/<pageId>/
    settings.json                    # Webflow's raw settings   (extension)
    designer.json                    # Designer API page doc    (bridge)
    whtml.html                       # rendered WHTML           (bridge)
    raw.json                         # that page's domNodes     (extension)
  components/<componentId>/
    whtml.html                       # rendered WHTML           (bridge)
    props.json                       #                          (bridge)
    variants.json                    #                          (bridge)
    settings.json                    #                          (bridge)
    raw.json                         # nodes from `symbols`     (extension)

Folders get no directory — they have no DOM, and a page's publishPath already carries its full nested path, so nothing is lost. They're reported as skipped.

Webflow returns the whole site (~6 MB, including account credentials) on every DOM call, so the extension keeps only symbols and domNodes inside the tab; none of the rest reaches disk.

| Flag | Purpose | | --- | --- | | --site <shortName\|url> | Which Designer site to use. Required only when several are open. Takes a short name (alex-sandbox-2f4b44) or any Designer URL — only the origin has to match. | | --wait <seconds> | How long to wait for each client to connect (default 60). |

With exactly one Designer site open, --site can be omitted and the CLI reports which site it picked.

wftoast fetch-dom [--page <pageId>]

Re-fetches a single page's DOM to webflow/pages/<pageId>/raw.json — for filling a gap left by a partial pull (a page that 404'd, or a 401 mid-sweep) without re-sweeping the whole site. Needs only the Chrome extension, so there's no bridge check. Takes --site and --wait too; --page defaults to whichever page the Designer tab has open.

wftoast pull-cms

Pulls the CMS and generates Astro content collections from it. This one uses neither browser client — CMS collections and items are in Webflow's public Data API, so it needs a site token instead:

webflow/cms/
  site.json                          # the Data API site record, incl. locales
  collections.json
  <collectionSlug>/schema.json       # fields + validations
  <collectionSlug>/items.json        # every item, all pages
webflow/
  forms.json                         # form definitions
  redirects.json                     # 301s — Enterprise workspaces only
  assets/<fingerprint>_<name>        # every referenced file, mirrored verbatim
src/
  content.config.ts                  # registers every collection
  content/<collectionSlug>/data.json # the entries
  content/<collectionSlug>/index.ts  # the collection: loader and zod schema
  content/<collectionSlug>/images/   # the images it uses, under their uploaded names

webflow/ is a re-fetchable mirror of what Webflow gave us. src/content/ is generated Astro source that belongs in your repo — commit it, don't ignore it. The collection directory name is the same in both trees, so an entry is traceable to its raw item by eye.

Forms and redirects sit at the top level because webflow/cms/ mirrors the CMS endpoints and neither is CMS. A failure on either is a warning, not an abort — a non-Enterprise site gets no redirects.json rather than an empty one, so "we couldn't look" stays distinct from "there are none".

| Flag | Purpose | | --- | --- | | --token <token> | Webflow site API token. Defaults to $WEBFLOW_API_TOKEN, then .env. | | --site-id <id> | Webflow site ID. Defaults to $WEBFLOW_SITE_ID, then .env. | | --astro-dir <path> | Astro project root to write src/content/ into. Defaults to the current directory. | | --from-cache | Regenerate from a previous run's webflow/cms/ files, without refetching. |

Credentials resolve flags → environment → .env, so the usual setup is a .env in the directory you run from:

WEBFLOW_API_TOKEN=...
WEBFLOW_SITE_ID=...

Mint the token in Webflow at site settings → Apps & integrations → API access → Generate API token, with the cms:read, sites:read and forms:read scopes. Webflow fixes scopes at creation, so mint it with all three: a token without forms:read still exports the CMS but skips webflow/forms.json with a warning. The token is never logged or written to disk.

Then check the generated output in the Astro project:

npx astro sync    # validates every entry against every generated schema
npx astro check   # ...and typechecks your pages against the generated types

Two stages, and --from-cache re-runs only the second. Items page at 100 against a per-token rate limit, so refetching a whole site to fix a mapping bug is expensive.

The generated schema deliberately asserts nothing the data can violate — every mapped field is optional, an Option field is a string rather than an enum, and a date that doesn't parse is dropped. Astro validates the data against these schemas at build time, so a constraint that doesn't hold would break your site rather than warn.

⚠️ Webflow's staged CMS is exported, so drafts and archived items are all present and carry webflow.isDraft / webflow.isArchived. Nothing filters them for you:

await getCollection("blog", ({ data }) => !data.webflow.isDraft && !data.webflow.isArchived);

Images are downloaded into the collection that uses them, under the name they were uploaded with, and typed with Astro's image() — so they go straight to <Image> and get optimised like any local asset. (sharp must be installed for that; <img src={img.src}> works without it.)

What doesn't come across, all reported as warnings with a non-zero exit rather than left to be discovered later: e-commerce collections, secondary locales, taxonomies, references whose target isn't in the export, field types with no equivalent, redirects on a non-Enterprise plan, and File fields — which can hold a PDF, so they keep their remote URL rather than being mistyped as images. Rich text is kept as verbatim HTML, which renders without Webflow's stylesheet, keeps its <img> tags pointing at Webflow, and whose embedded scripts no longer have Webflow's jQuery to call. The draft/archived counts are an unconditional Note: rather than a warning, because nothing was lost, so exit 0 genuinely means no gaps were found.

wftoast mcp

Runs a local MCP server on stdio that serves the migration guides — how to drive this CLI, and how a Webflow → Astro migration actually goes — to Claude Code and any other MCP client. It exposes two tools (list_guides, then get_guide for the slugs that match), one resource per guide (wftoast://<slug>.md), a migrate_site prompt, and a short server instructions string that tells an agent the tools exist.

Built on the official @modelcontextprotocol/server SDK, so one registration serves both protocol eras: a modern 2026-07-28 client (per-request _meta, server/discover) and a 2025-11-25-era client (the initialize handshake, which is what every shipping client sends today).

It is deliberately read-only — there is no tool that runs pull or pull-cms. The agent already has a shell, pull needs a live browser and minutes of streaming, and pull-cms carries a site token. Nothing but JSON-RPC is written to stdout.

Register it with Claude Code:

# in a migration project — pin the version, so a cached npx never serves an older build
claude mcp add wftoast -s user -- npx -y wftoast@latest mcp

# working on this repo itself
claude mcp add wftoast -s user -- node /abs/path/to/packages/cli/dist/index.js mcp

Or per project, in .mcp.json:

{
  "mcpServers": {
    "wftoast": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "wftoast@latest", "mcp"]
    }
  }
}

Other clients take the same command / args pair — Claude Desktop, Cursor, Codex CLI, Copilot CLI and Zed in their own mcpServers JSON; VS Code via MCP: Add Server… → Command (stdio).

wftoast docs

The same guides, printed to the terminal — for an agent with only a shell, or a human reading along. Both commands call the same handlers the MCP tools do.

wftoast docs list                             # every guide, with its slug and use cases
wftoast docs get runbook astro/content-collections   # one or more guides by slug

Errors

This CLI never prompts — it's meant to be driven by AI agents, which have no stdin to answer with. When it needs a decision it exits with a code, a cause, every valid option, and the exact flag to re-run with:

Error [site_not_specified]: 2 Webflow Designer sites are open, so --site is required.

Cause: More than one site is open in the Designer and no --site was given, so the target would be ambiguous.

Detail:
  open_designer_sites:
    - site-aaa  (https://site-aaa.design.webflow.com)  "Site site-aaa - Webflow"
    - site-bbb  (https://site-bbb.design.webflow.com)  "Site site-bbb - Webflow"

How to fix:
  1. Re-run with: --site site-aaa
  2. Re-run with: --site site-bbb
  3. Or close the Designer tabs for the sites you are not migrating.

Every pre-flight failure happens before any data is written, so a wrong target leaves no partial output.

Individual pages failing during phase 2 is a warning, not an abort — those pages get no raw.json, the rest still land, and the exit code is non-zero. A 404 is normal for some CMS/utility pages; a 401/403 means the Webflow session expired, so sign in again in Chrome and re-run to fill the gaps.

See the root CLAUDE.md for the full list of codes and the wire protocol.

Development

pnpm --filter wftoast run build   # src/ -> dist/
pnpm --filter wftoast run start   # node dist/index.js

WFTOAST_PORT overrides the server port for local testing only — the browser clients hard-code 3774, so a real pull can't use another port. WFTOAST_WEBFLOW_API likewise points pull-cms at a fake Data API so the tests never touch a real site.