feat: add wordvibe_build_page (Word Page Builder over MCP)
Wraps the site tool wpb_build_page: build or update a Word Page from {kind, html} sections. The page lands as a native Word Page (payload in post meta, builder-rendered), so it stays editable and undoable in the builder. Also documents the builder tools in the bundled skill and README.
This commit is contained in:
@@ -60,6 +60,7 @@ Debug a plugin that won't load with `HERMES_PLUGINS_DEBUG=1 hermes plugins list`
|
||||
| `wordvibe_write_file` | Overwrite a file (the site snapshots the previous version first) |
|
||||
| `wordvibe_edit_file` | Targeted `search` → `replace` edit (safer than a full write) |
|
||||
| `wordvibe_search` | Codebase search across themes/plugins/core, plain text or regex |
|
||||
| `wordvibe_build_page` | Build/update a **Word Page Builder** page from `{kind, html}` sections (native in the builder) |
|
||||
|
||||
Slash command for when you don't want to spend a model turn:
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ def register(ctx):
|
||||
("wordvibe_write_file", schemas.WRITE_FILE, tools.write_file),
|
||||
("wordvibe_edit_file", schemas.EDIT_FILE, tools.edit_file),
|
||||
("wordvibe_search", schemas.SEARCH, tools.search),
|
||||
("wordvibe_build_page", schemas.BUILD_PAGE, tools.build_page),
|
||||
]
|
||||
|
||||
for name, schema, handler in registry:
|
||||
|
||||
@@ -16,6 +16,7 @@ provides_tools:
|
||||
- wordvibe_write_file
|
||||
- wordvibe_edit_file
|
||||
- wordvibe_search
|
||||
- wordvibe_build_page
|
||||
requires_env:
|
||||
- name: WORDVIBE_SITE_URL
|
||||
description: "WordPress site URL (e.g. https://mysite.com). MCP Server Mode must be enabled in WordVibe → Settings → MCP."
|
||||
|
||||
+43
@@ -13,6 +13,49 @@ _SITE = {
|
||||
),
|
||||
}
|
||||
|
||||
BUILD_PAGE = {
|
||||
"name": "wordvibe_build_page",
|
||||
"description": (
|
||||
"Build or update a page in the site's Word Page Builder (WPB) from section HTML. Each "
|
||||
"section is wrapped automatically in <section data-wpb-section=\"kind\" data-wpb-id=\"sec-…\">, "
|
||||
"so the page opens natively in the builder and stays editable/undoable there. Use this to "
|
||||
"publish a landing page, or to rebuild a page's sections, without a browser."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string", "description": "Page title."},
|
||||
"sections": {
|
||||
"type": "array",
|
||||
"description": (
|
||||
"Ordered list of sections. Each item: {kind, html} — kind is a kebab-case section "
|
||||
"name (hero, features, pricing, testimonials, faq, cta, footer, gallery, contact, "
|
||||
"about, services, custom); html is the section's inner HTML (inline styles are fine)."
|
||||
),
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {"type": "string", "description": "Section kind, e.g. 'hero'."},
|
||||
"html": {"type": "string", "description": "Inner HTML for the section."},
|
||||
},
|
||||
"required": ["kind", "html"],
|
||||
},
|
||||
},
|
||||
"slug": {"type": "string", "description": "URL slug (optional)."},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "draft, publish, pending or private (default: draft).",
|
||||
},
|
||||
"post_id": {
|
||||
"type": "integer",
|
||||
"description": "Existing page ID to update; omit to create a new page.",
|
||||
},
|
||||
"site": _SITE,
|
||||
},
|
||||
"required": ["title", "sections"],
|
||||
},
|
||||
}
|
||||
|
||||
SITES = {
|
||||
"name": "wordvibe_sites",
|
||||
"description": (
|
||||
|
||||
@@ -48,8 +48,27 @@ configured site is the default; when several are configured, pass `site` (name o
|
||||
| Database lookups | `wordvibe_call(tool="db_query", arguments={"sql":"SELECT COUNT(*) FROM wp_posts"})` |
|
||||
| Security sweep | `wordvibe_call(tool="security_scan_installed")` |
|
||||
| Roll back an edit | `wordvibe_call(tool="rollback_list")` → `rollback_restore` |
|
||||
| Build a page | `wordvibe_build_page(title=…, sections=[{kind, html}], status="publish")` |
|
||||
| Site basics | `wordvibe_call(tool="site_info")`, `theme_list`, `plugin_list` |
|
||||
|
||||
## Building pages (Word Page Builder)
|
||||
|
||||
`wordvibe_build_page` creates or updates a **Word Page** — a page the site's Word Page Builder owns and
|
||||
renders from its own stored payload, so it opens natively in the builder (edit, undo, redo all work).
|
||||
|
||||
- One call builds the whole page: `title`, `sections: [{kind, html}]`, optional `slug`, `status`, `post_id`.
|
||||
- Section kinds: `hero`, `features`, `pricing`, `testimonials`, `faq`, `cta`, `footer`, `gallery`,
|
||||
`contact`, `about`, `services`, `custom`. Each becomes `<section data-wpb-section="kind" data-wpb-id="sec-…">`.
|
||||
- Write inline styles rather than inventing class names — the builder preserves what you give it, but
|
||||
the site's stylesheet won't contain classes that don't exist.
|
||||
- Editing an existing page: `wordvibe_tools(filter="wpb")` lists the builder tools, then use
|
||||
`wordvibe_call` with `wpb_get_page` / `wpb_get_page_full`, `wpb_replace_section_html`,
|
||||
`wpb_add_section_to_page`, `wpb_delete_section`, `wpb_generate_page` (AI), `wpb_chat_edit_page` (AI, one
|
||||
instruction per call), `wpb_list_pages`.
|
||||
- Needs the page builder present in the site's build (WordVibe 1.0.x cloud/ide). Where it was stripped,
|
||||
the tools answer "The Word Page Builder is not available in this build of the plugin." — say so instead
|
||||
of faking it with a plain page.
|
||||
|
||||
## Multi-site
|
||||
|
||||
With `WORDVIBE_MCP_SITES` configured, name the target explicitly on every call — do not let a default
|
||||
|
||||
@@ -212,3 +212,41 @@ def search(args: dict, **kwargs) -> str:
|
||||
if args.get("regex"):
|
||||
payload["regex"] = True
|
||||
return _call_tool(args.get("site") or "", "codebase_search", payload)
|
||||
|
||||
|
||||
def build_page(args: dict, **kwargs) -> str:
|
||||
"""Build/update a Word Page Builder page (wraps the site's wpb_build_page tool)."""
|
||||
title = str(args.get("title") or "").strip()
|
||||
if not title:
|
||||
return _err("Missing 'title'.")
|
||||
|
||||
sections = args.get("sections")
|
||||
if isinstance(sections, str):
|
||||
try:
|
||||
sections = json.loads(sections)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return _err(f"'sections' must be a JSON array: {exc}")
|
||||
if not isinstance(sections, list) or not sections:
|
||||
return _err("'sections' must be a non-empty array of {kind, html}.")
|
||||
|
||||
clean = []
|
||||
for item in sections:
|
||||
if isinstance(item, str):
|
||||
clean.append({"kind": "custom", "html": item})
|
||||
continue
|
||||
if not isinstance(item, dict) or "html" not in item:
|
||||
return _err("Each section needs {kind, html}.")
|
||||
clean.append({"kind": str(item.get("kind") or "custom"), "html": str(item.get("html") or "")})
|
||||
|
||||
payload = {"title": title, "sections": clean}
|
||||
for key in ("slug", "status"):
|
||||
val = str(args.get(key) or "").strip()
|
||||
if val:
|
||||
payload[key] = val
|
||||
if args.get("post_id"):
|
||||
try:
|
||||
payload["post_id"] = int(args["post_id"])
|
||||
except Exception: # noqa: BLE001
|
||||
return _err("'post_id' must be an integer.")
|
||||
|
||||
return _call_tool(args.get("site") or "", "wpb_build_page", payload)
|
||||
|
||||
Reference in New Issue
Block a user