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_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_edit_file` | Targeted `search` → `replace` edit (safer than a full write) |
|
||||||
| `wordvibe_search` | Codebase search across themes/plugins/core, plain text or regex |
|
| `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:
|
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_write_file", schemas.WRITE_FILE, tools.write_file),
|
||||||
("wordvibe_edit_file", schemas.EDIT_FILE, tools.edit_file),
|
("wordvibe_edit_file", schemas.EDIT_FILE, tools.edit_file),
|
||||||
("wordvibe_search", schemas.SEARCH, tools.search),
|
("wordvibe_search", schemas.SEARCH, tools.search),
|
||||||
|
("wordvibe_build_page", schemas.BUILD_PAGE, tools.build_page),
|
||||||
]
|
]
|
||||||
|
|
||||||
for name, schema, handler in registry:
|
for name, schema, handler in registry:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ provides_tools:
|
|||||||
- wordvibe_write_file
|
- wordvibe_write_file
|
||||||
- wordvibe_edit_file
|
- wordvibe_edit_file
|
||||||
- wordvibe_search
|
- wordvibe_search
|
||||||
|
- wordvibe_build_page
|
||||||
requires_env:
|
requires_env:
|
||||||
- name: WORDVIBE_SITE_URL
|
- name: WORDVIBE_SITE_URL
|
||||||
description: "WordPress site URL (e.g. https://mysite.com). MCP Server Mode must be enabled in WordVibe → Settings → MCP."
|
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 = {
|
SITES = {
|
||||||
"name": "wordvibe_sites",
|
"name": "wordvibe_sites",
|
||||||
"description": (
|
"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"})` |
|
| Database lookups | `wordvibe_call(tool="db_query", arguments={"sql":"SELECT COUNT(*) FROM wp_posts"})` |
|
||||||
| Security sweep | `wordvibe_call(tool="security_scan_installed")` |
|
| Security sweep | `wordvibe_call(tool="security_scan_installed")` |
|
||||||
| Roll back an edit | `wordvibe_call(tool="rollback_list")` → `rollback_restore` |
|
| 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` |
|
| 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
|
## Multi-site
|
||||||
|
|
||||||
With `WORDVIBE_MCP_SITES` configured, name the target explicitly on every call — do not let a default
|
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"):
|
if args.get("regex"):
|
||||||
payload["regex"] = True
|
payload["regex"] = True
|
||||||
return _call_tool(args.get("site") or "", "codebase_search", payload)
|
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