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.
wesper
Common questions
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.



