A CLI that reads what a WordPress site can accept.
Wesper is the read primitive for the agentic WordPress pipeline. It reads one site — block registry, theme tokens, Block Bindings sources and fields, post types — into a single portable site.context.json manifest, with provenance, a content hash you can diff, and every gap recorded as a warning instead of a guess.
Read it once; every consumer reads one known shape. It reads — it never writes.
requires Node 18.12+ · GPL-2.0
❯ wesper collect --wp-path ./public --out site.context.json
reading block registry … 214 block types
reading theme.json … Ollie · 3 colors · 1 font · merged
reading binding sources … core/post-meta, acf/field
reading post types … product (2 bindable fields)
! patterns endpoint 401 → recorded as warning, not invented
stamped sha256:9f2c…b41e · collected 2026-06-25T14:02Z
✓ wrote site.context.json · 1 warning
{
"$schema": "https://humanmade.github.io/wesper/schemas/site-context-v1.schema.json",
"contextVersion": 1,
"site": {
"url": "https://store.northwind.co",
"name": "Northwind Supply Co.",
"environment": "production",
"isMultisite": false
},
"provenance": {
"collectedAt": "2026-06-25T14:02:11Z",
"collector": "wp-cli",
"collectorVersion": "0.1.0",
"sourceHash": "sha256:9f2c4ab1…b41e",
"partial": false
},
"wordpress": { "version": "6.8.1", "locale": "en_US" },
"theme": {
"name": "Ollie",
"isBlockTheme": true,
"tokens": {
"colors": [{ "slug": "base", "value": "#ffffff" },
{ "slug": "contrast", "value": "#0f1115" },
{ "slug": "accent", "value": "#2f6df6" }],
"spacing": [{ "slug": "40", "value": "clamp(2rem, 5vw, 4rem)" }],
"typography": [{ "slug": "display", "value": "Hanken Grotesk" }]
}
},
"bindings": {
"available": true,
"sources": [{ "name": "core/post-meta", "usesContext": ["postId"] },
{ "name": "acf/field" }],
"supportedAttributes": {
"core/paragraph": ["content"],
"core/image": ["url", "alt"],
"core/heading": ["content"]
}
},
"contentModel": {
"postTypes": [{
"name": "product",
"showInRest": true,
"fields": [{ "key": "price", "source": "core/post-meta", "bindable": true },
{ "key": "sku", "source": "core/post-meta", "bindable": true },
{ "key": "_internal","source": "core/post-meta", "bindable": false }]
}]
},
"warnings": [{
"code": "patterns.unreadable",
"severity": "warning",
"surface": "patterns",
"message": "REST patterns endpoint returned 401; pattern list is absent, not empty."
}]
}
Read once, hand off everywhere.
Collect a site into a manifest, then let any consumer read one known shape — instead of every agent re-deriving brittle introspection of its own.
A hand-authored context file goes stale the moment the site moves on — and nothing tells the reader. Every Wesper read is stamped with a content hash, so a stale manifest is something you diff and catch in CI, never a silent wrong answer waiting to ship.
Collect
wesper collect → site.context.json
Read the registry, theme tokens, binding sources and fields, post types, patterns and media rules — by WP-CLI, REST/Abilities/MCP, or a fixture. The collector is swappable; the manifest is the constant.
❯ wesper collect --wp-path ./public --out site.context.json
reading block registry … 214 block types
reading theme.json … Ollie · 3 colors · 1 font · merged
reading binding sources … core/post-meta, acf/field
reading post types … product (2 bindable fields)
! patterns endpoint 401 → recorded as warning, not invented
stamped sha256:9f2c…b41e · collected 2026-06-25T14:02Z
✓ wrote site.context.json · 1 warning
Consume
wesper summarize · validate
Hand the manifest to an agent, the binding pass, or Block Runner. Summarize it for a model, validate it against the schema in CI, and stamp it so a stale read fails loudly instead of shipping a wrong block.
❯ wesper summarize site.context.json
# Northwind Supply Co. · WordPress 6.8.1 · Ollie
block types ……… 214
binding sources … 2 (core/post-meta, acf/field)
post types ……… 4 · bindable fields … 11
patterns ………… absent (see warnings)
product → price, sku · core/paragraph → content
⚠ 1 warning · patterns.unreadable
One manifest, the whole world model.
Everything a consumer needs to answer “what can this specific site safely accept, reference, and bind to?” — normalized into one stable, versioned shape.
Block registry
Every registered block type the site can actually render — not what a model hopes is installed.
Theme tokens
Colour, spacing and typography slugs from theme.json, so output references presets instead of hard-coded values.
Binding sources & fields
Registered Block Bindings sources, their argument schemas, and the meta keys reachable per post type.
Content model
Public post types, their taxonomies, REST visibility, and which fields are bindable versus internal.
Patterns & media
Registered patterns, image sizes, and upload rules — the shapes content has to fit into.
Provenance & freshness
When it was collected, by which collector, and a content hash you can diff — so a stale read is something CI catches, not something that quietly ships.
An agent without context is flying blind.
It invents attributes, binds to fields that do not exist, ignores the theme, and calls the result native. The same agent, handed one manifest, does none of that.
block type
emits wp:acme/pricing-table — not registered here
emits wp:columns — confirmed in the registry
blocks.types[]field binding
binds to meta.subtitle — that key does not exist
binds to meta.price — a real, bindable field
contentModel.postTypes[].fieldstheme token
hard-codes #0a0a0a and 32px
references the “contrast” + “40” theme slugs
theme.tokensmissing data
assumes patterns exist, ships a broken reference
reads the warning, degrades safely, ships nothing wrong
warnings[]binding readiness
The grounded column is only possible because the manifest surfaces this per site: which Block Bindings sources are registered, the context they need, and the fields reachable through them. The slice the binding pass cannot function without.
| source | usesContext | fields | status |
|---|---|---|---|
| core/post-meta | postId | price · sku | ✓ registered |
| acf/field | — | hero_image · cta_url | ✓ registered |
Why we built it.
It is the read half of the agentic-WordPress loop: read the site (Wesper) → author native blocks (Block Runner) → wire them to the site’s real data (the binding pass).
Block Runner already reached into WordPress for theme tokens through a built-in resolver. Wesper is that pattern lifted out, widened to the whole world model — binding sources, fields, registry, patterns, abilities — and made a reusable artifact every consumer can share, instead of each agent re-deriving brittle introspection of its own.
We built it because we needed it ourselves, to run autonomous optimisation experiments on Accelerate, then open-sourced it as the read primitive the ecosystem was missing — so every tool in the pipeline can trust the site it is writing to.
❯ wesper validate site.context.json
checking against site-context-v1 schema …
✓ schema valid · contextVersion 1
✓ provenance present · sourceHash, collectedAt
✗ bindings.sources[1].argsSchema: expected object|null
exit 1 · invalid manifest (1 issue)
The manifest is data, not authority. Consumers validate it against the schema, never eval anything from it, and prefer doing nothing over acting on unverifiable context. And because the manifest travels, credential-looking values are redacted before it is ever hashed or written.
Common questions
wesper
- What does it output?
- One portable site.context.json: the block registry, theme tokens, Block Bindings sources and fields, post types, patterns, media rules and the capability surface — normalized into a single versioned shape, with provenance and freshness, and every gap recorded as a warning rather than a guess.
- Does it change my site?
- No. Wesper reads, it never writes. It does not register binding sources, create fields or post types, or run as a service. Authoring lives in other tools (Block Runner, the binding pass); reading is the whole job. Nothing proprietary has to stay installed to read the manifest later.
- How does it read a site I cannot SSH into?
- Transport is a ladder, not one method. Collect via WP-CLI (local or SSH), via authenticated REST / Abilities / MCP on no-shell hosts, or from a hand-authored fixture in tests and CI. The manifest schema is identical regardless of how it was gathered, so a consumer never cares which rung produced the data.
- What happens when a surface cannot be read?
- It says so. A thin manifest is allowed; a dishonest one is not. When a surface cannot be collected, Wesper records a first-class warning naming exactly where it is thin and leaves the field absent — it never fabricates a meta key, a token, or a binding source to look complete. A consumer that knows what is missing can degrade safely.
- How does this relate to Block Runner?
- They are the two halves of the agentic-WordPress loop: read the site (Wesper) → author native blocks (Block Runner) → wire them to the site’s real data (the binding pass). Block Runner already reached into WordPress for theme tokens; Wesper is that pattern lifted out, widened to the whole world model, and made a reusable artifact every consumer can share.
- Why a portable file, and not just llms.txt or an MCP server?
- Because the hard part is not delivering context — it is trusting it. llms.txt and MCP are transports; Wesper is the verifiable artifact worth transporting. A hand-authored llms.txt goes stale and a reader cannot tell that it has; Wesper stamps every read with a content hash, records each gap as a warning, and validates against a versioned schema. Prefer MCP? Expose the manifest as an MCP resource — the file stays the source of truth.
- Is it really open source?
- Yes, GPL-2.0 on GitHub. It is an npm package you drop into your own pipeline. We built it for our own autonomous experiments on Accelerate, then opened it up as the read primitive the ecosystem was missing.
Built by WordPress Experts
Created by Human Made
Since 2010, Human Made has been one of the world's leading enterprise WordPress agencies, powering platforms for Google, TechCrunch, Harvard, and Siemens. Accelerate brings that same level of expertise to your WordPress site.



