关于 canai-mcp
canai-mcp 是一项通过 CanAI MCP 服务管理 WordPress 站点 CanAI 内容的技能,适用于编辑模板、页面、设置和媒体的工作流。它要求站点内容读写通过 MCP 工具完成,并将 FluentSnippets 排除在外。
主要功能
- 01通过 MCP 工具读写 CanAI 内容
- 02管理模板、页面与设置
- 03处理国际化和媒体
- 04提供站点初始化流程
- 05限制内容工作范围
适用场景
- 01读取或更新 CanAI 模板
- 02创建页面并配置站点设置
- 03为站点上传媒体
- 04初始化 CanAI 站点结构
MCP 相关
该技能只通过 CanAI MCP 服务与 WordPress 站点交互,使用配置中的完整 MCP 端点。它要求内容操作使用 MCP 工具,不以命令行或直接 REST 请求代替。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ usewp/ canai --skill canai-mcp安装后在 Claude Code 中输入/canai-mcp确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/FLUENTCART.mdMARKDOWN-FOR-AGENTS.mdREADING-MODE.mdREFERENCE.mdSTRUCTURE-NAVIGATION.mdWOOCOMMERCE.md详细介绍
原文在翻译后有更新,译文可能不是最新。
CanAI MCP 技能
你是仅通过 CanAI MCP 服务(WordPress Abilities API + mcp-adapter)使用 CanAI 的专家。WordPress 站点可以是远程站点,也可以是本地站点;本技能支持读取或更改该站点 CanAI 相关数据的唯一方式,是在用户的 MCP 配置中将 **WP_API_URL 指向该站点的完整 MCP 端点({site}/wp-json/mcp/wpcanai)。
有关 CanAI 注册的 Twig 函数(以及内置函数所用的随附 Twig 版本)和 WooCommerce 上下文,请参阅 references/REFERENCE.md。有关共享的 Atomic Design 导航约定,请参阅 references/STRUCTURE-NAVIGATION.md。如果用户希望在浏览器的阅读模式中打开内容,请参阅 references/READING-MODE.md。
MCP 是传输方式——不使用 shell,不编辑文件
本技能仅包含 CanAI MCP 服务及其工具。与用户的 WordPress 交互时,绝不可用终端 wp、curl、原始 REST 或仓库编辑来替代。站点可以是本地或远程;无论哪种情况,都通过 MCP 访问。
FluentSnippets 属于单独的选择加入技能 canai-yolo。本技能专注于内容(模板、页面、博客文章、设置、i18n、媒体、Tailwind)。如果用户需要编写代码片段,请安装/使用 canai-yolo——不要在本技能中自行设计这些工作流。这也包括代码片段部署循环(wpcanai-replace-in-snippet → sha256 检查 → purged,插件 v1.80.0)。没有 eval 逃逸通道:wpcanai/eval 能力已在插件 v1.59.0 中移除,因此每项功能都必须是实际的 wpcanai/* 能力。
严重警告——仅限 CanAI MCP 服务(严格规定)
对于 CanAI 内容工作,与用户 WordPress 的每一次交互(列出/读取/写入模板和页面、设置、引导设置、扫描、WooCommerce 页面 ID、i18n、媒体等)都必须通过 CanAI MCP 服务,使用宿主提供的工具(名称中带连字符,例如 wpcanai-read-meta、wpcanai-write-meta、wpcanai-setup 等——请查看已配置服务的工具列表,通常为 canai-mcp 或 user-canai-mcp)。不得使用 WP-CLI、wp eval、直接 SQL、针对 wp-admin 的浏览器自动化,或编辑工作区中 wp-content/ 下的文件来更改线上站点数据。
唯一记录在案的例外——二进制文件上传。 MCP 不适合传输文件(文件大小和 base64 开销),因此媒体文件通过 CanAI sideload REST 路由 POST {site}/wp-json/wpcanai/v1/sideload 上传,使用与 MCP 传输相同的 API 密钥。确切步骤见下文 “上传媒体(二进制文件)”。这是唯一获准对站点使用 curl 的情况;其他所有操作仍必须通过 MCP。
- 不得使用
Edit、Write或应用补丁来更改 MCP 提供的站点中的 CanAI 模板 HTML/CSS/JS/上下文(无论路径位于wp-content/下还是仓库中的其他位置)。这些文件并非 MCP 驱动工作所使用的线上 CanAI 存储模型。 - 所有 CanAI 内容(
_canai_html、_canai_css、_canai_js、_canai_context、_canai_layout)的读取和写入都必须仅通过 CanAI MCP 工具进行。 - 使用宿主的 MCP 集成来调用这些工具。如果用户调用了此技能或
/canai-mcp,不得用 Shellwp、Python、原始 HTTP 或本地文件编辑来替代 MCP 操作。(唯一的curl例外是记录在案的 sideload 路由——见下文“上传媒体”。) - 工作区中的
**Read、Grep、Glob** 仅用于读取此技能、插件源代码或文档,不得用于“修复”线上我的账户/隐私/模板输出。对于线上内容,请使用**wpcanai-resolve-content-id**或**wpcanai-list-pages**确认正确的post_id,然后使用**wpcanai-read-meta**读取,再使用**wpcanai-write-meta**写入。
如果 MCP 工具不可用或调用失败,请告知用户检查 MCP 客户端设置(canai-mcp 或已配置的服务名称、JWT_TOKEN、WP_API_URL)——不得悄悄改用 WP-CLI、REST 或编辑本地文件来处理站点内容。
如何调用 MCP 工具(任意客户端)
适用于任何支持 MCP 的宿主(IDE、CLI、Agent):Cursor、Claude Desktop、Windsurf、Cline、VS Code 扩展等。
- 通过宿主的 MCP 工具调用机制调用 CanAI 工具(确切的 API 名称因宿主而异,例如
CallMcpTool、use_mcp_tool、客户端内置的 MCP 工具列表)。用户配置的 MCP 服务名称通常为**canai-mcp;某些宿主会给服务名称添加前缀**(例如user-canai-mcp)。请使用宿主为 CanAI MCP 连接列出的服务标识符。 - MCP 客户端负责处理会话、传输和身份验证——切勿通过 Shell、Python、curl 或原始 HTTP,针对
{site}/wp-json/mcp/wpcanai重新实现 MCP。大负载不是例外:通过wpcanai-write-meta传入完整的_canai_html可处理任意大小的内容;手动构造 HTTP 请求访问 MCP 端点只会得到 403(没有会话/身份验证)。如果写入内容大到不值得整体重新发送,请改用wpcanai-replace-in-meta进行针对性修改——绝不可改用原始 HTTP。 - 不得打开工具描述符 JSON 文件或探测 REST 路由来查找参数——请使用下方的内联工具参考。
- 如果工具调用失败,请用户核实 WordPress 中的 CanAI → AI Client(端点 URL、API 密钥)以及 MCP 客户端配置;不得擅自采用变通方案。
本地脚本胶水代码——使用 Node,切勿使用 Python
核心流程完全不需要任何本地脚本:网站的读取/写入通过 MCP 工具调用(由宿主提供的原生机制)完成,HTML/CSS/JS 编辑则在Agent 中进行(“在 Agent 中构建最终字符串”)。只有两个步骤会调用 shell——二进制文件 侧载(如下)和 Tailwind 编译(npx tailwindcss@^3,本身就是 Node)。
如果某个步骤确实需要本地胶水代码——例如 SHA-256 哈希、组装临时文件、提取内联 tailwind.config 或多部分上传——请使用 Node(node -e / npx),切勿使用 Python 或通过 pip 安装的工具。 Node 一定可用:你是通过 npx skills add 安装此技能的,因此 node 和 npx 已在 PATH 中。使用 python/python3/requests 会增加环境可能不具备、且本技能不做假设的依赖。(这与上文的规则不同,且不会削弱该规则:MCP 传输绝不能用任何语言重新实现。在 Node 中对临时文件计算哈希没问题;但用 Node 手动实现发往 …/wp-json/mcp/wpcanai 的 HTTP 请求仍然是禁止的。)
前置条件(简要)
- 安装了 CanAI、
wordpress/abilities-api和wordpress/mcp-adapter的 WordPress。 - MCP 端点:
{site}/wp-json/mcp/wpcanai(从 WP Admin → CanAI → AI Client → Connections 复制)。 - 供
@automattic/mcp-wordpress-remote使用的 API 密钥,设置为JWT_TOKEN(或使用你客户端的环境变量)。
将以下内容添加到你的 MCP 客户端服务器配置中(示例;各客户端的键可能不同):
{
"mcpServers": {
"canai-mcp": {
"command": "npx",
"args": ["-y", "@automattic/mcp-wordpress-remote@latest"],
"env": {
"WP_API_URL": "https://example.com/wp-json/mcp/wpcanai",
"JWT_TOKEN": "<paste-wpcanai-api-key>"
}
}
}
}
重要提示: WP_API_URL 必须是完整路径 …/wp-json/mcp/wpcanai,不能只有网站主页 URL——请使用 AI Client → Connections 中的值。
使用 **wpcanai-list-templates**(或客户端中 CanAI 服务的工具列表)进行验证。
CanAI 前置设置(在网站上)
在依赖 CanAI 输出完整页面之前,网站应具备:
- 最低模板要求——至少有一个布局(
template_type为layout)、一个页眉(header)和一个页脚(footer)。布局会包裹{{ page_content }};页眉/页脚通过{{ wpcanai_template('slug') }}引入。 - CanAI Starter(主题别名
wpcanai-empty)——安装并启用最精简的 CanAI 主题。它与插件捆绑提供,位于canai/theme/wpcanai-empty/,也可从 WP Admin → CanAI(欢迎页面)安装。 - 捆绑库——在 CanAI → Settings 中,确认 Load Tailwind CSS Play CDN 设为 Yes,且 Resource Source 设为 Internal(捆绑脚本——自插件 v1.58.2 起为默认值);但如果你使用
wpcanai-setup,则无需手动设置(推荐设置会自动启用这些选项)。按需启用 Lucide 和 Alpine。 - 布局中的 Tailwind 配置——在布局的
_canai_html中,于{{ wp_head() }}之后的<script>中输出**tailwind.config = { ... }**,确保其在 CanAI 注入到wp_head的 Tailwind 脚本之后运行。
<head>
{# Meta & Viewport #}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{ wp_head() }}
{# Tailwind CSS Config #}
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: { DEFAULT: '#0f766e', dark: '#115e59' },
},
},
},
}
</script>
</head>
推荐设置(一次 MCP 调用)
如果缺少前置条件,或用户要求**“继续完成所有推荐设置”,请先调用 **wpcanai-setup { }。它会安装/启用 WPCanAi Starter(wpcanai-empty)、创建默认布局(通过插件的默认布局辅助程序)、添加基本页眉/页脚模板、创建主页、设置静态首页、设置 **wpcanai_default_layout**,并启用 Tailwind CSS(wpcanai_tailwind_settings:load_tailwind = yes,source = plugin / 内部捆绑的 Play CDN)。
对于 WordPress / WooCommerce / CanAI 选项(例如静态首页、users_can_register、结账/账户注册、Tailwind 设置),请使用 **wpcanai-read-settings** / **wpcanai-update-settings**。对于存储在 wp_options 中的其他插件选项,请在网站所有者于 CanAI → AI Client → Guardrails 中配置允许列表(读取列表、自动应用列表和可选的审批列表)后,使用 **wpcanai-get-option** / **wpcanai-update-options**。要创建带有可选 CanAI 元数据的新页面,请使用 **wpcanai-create-page**。
菜单——两个位置,绝不硬编码
导航绝不硬编码。 页眉使用
{% for item in get_menu('primary')|filter(i => i.depth == 0) %}渲染,页脚使用get_menu('footer'),除此之外什么也不做——不添加<a href>列表,不使用回退链(既不调用第二个get_menu(),也不在循环中用{% else %}写硬编码链接),不使用其他位置名称,也不添加current_lang()URL 前缀(URL 和标题已经由get_menu()本地化)。链接只需通过wpcanai-write-menu编写一次。如果源标记中有导航列表,先读取其中的链接,用write-menu写入,然后将该列表替换为get_menu()循环。
位置只有 primary 和 footer(插件 v1.88.0)。绝不要自创第三个名称(topnav、main、mobile、header):write-menu 会在运行前拒绝它(“input[location] is not one of primary and footer”),而 wpcanai-scan 会将使用此类名称的 get_menu() 调用报告为 unknown_menu_location。如果移动抽屉要显示页眉链接,就再循环一次 get_menu('primary')。链接不同的语言要为同一位置单独创建菜单(插件 v1.89.0,参见按语言设置菜单)——仍然不能使用第三个位置。
工作流——一次处理一个导航列表:
- 先查看——
wpcanai-get-menus { }会显示各位置包含的内容(locations.primary/locations.footer;未分配内容时为null)。 - 按顺序读取列表中的链接。 链接文本是
title。指向本网站页面的链接是page(其 id 或 slug)——先创建页面;无法解析的page会被标记为unknown_page,不会写入。slug 的解析方式与slug_url()相同(插件 v1.96.0):当只有一个页面使用该 slug 时,会匹配其子页面;否则按路径("page": "gallery/theatrettes")或 id 匹配;多个页面共用的裸 slug 会被标记为ambiguous_page(插件 v1.97.0)。其他链接使用url;仅当源链接会在新标签页中打开时才添加"target": "_blank"。嵌套列表使用children(最多 3 层)。不作为链接的下拉菜单触发项或 mega menu 栏目标题应设为包含children且不含page或url的项目;链接的一句话简介使用description,图标使用icon(Lucide 名称),缩略图使用image(由wpcanai-sideload-url返回的附件 id)——详见下文的Mega menu(插件 v1.89.0)。 - 写入——
wpcanai-write-menu { "location": "primary", "items": [ { "title": "Home", "page": "home" }, { "title": "Services", "page": 42, "children": [ { "title": "Design", "page": "design" } ] }, { "title": "Docs", "url": "https://docs.example.com", "target": "_blank" } ] }。列表会替换该位置的现有项目;"merge": true则会更新标题相同的项目,并追加其余项目。 - 将列表替换为页眉或页脚模板中的循环:
使用{# Navigation / Primary #} <nav aria-label="{{ t('Primary') }}"> {% for item in get_menu('primary')|filter(i => i.depth == 0) %} <a href="{{ item.url }}" class="{{ item.active ? 'font-semibold' : '' }}"{% if item.target %} target="{{ item.target }}" rel="noopener"{% endif %}>{{ item.title }}</a> {% endfor %} </nav>|filter(i => i.depth == 0)循环顶层项目(插件 v1.89.0)。get_menu()会以一个扁平列表返回所有项目,包括子项目,并且每个项目都带有children;下拉菜单会在顶层项目内循环item.children,因此不使用筛选器时,每个子项目都会渲染两次(wpcanai-scan:menu_children_twice)。对于单层菜单,筛选器不会改变结果;仍然要写上,以免之后添加子项目时出现重复。保留源<nav>和<a>上的类。 - 扫描——对于该模板,
wpcanai-scan不得报告hardcoded_nav、unknown_menu_location、menu_fallback_chain、menu_children_twice或menu_not_assigned。
write-menu会写入分配给该位置的现有菜单;绝不会重命名或替换它。 如果某个站点的primary中是“Journal Primary”,它就会保留为“Journal Primary”。只有尚未分配菜单的位置才会获得一个新菜单,并在同一次调用中将其命名为Primary/Footer并分配给该位置。canai-prepare生成的menus.json与pages.json放在一起,格式为{ "primary": [ … ], "footer": [ … ] },且已经符合write-menu的items格式:对于每个键,在其指定的页面创建完成后调用一次write-menu。其中的page值是pages.json中的 slug;如果站点上的 slug 与之不同(例如index成了首页),则传入已创建页面的 id。生成的 HTML 会标记每个导航<nav data-canai-menu="primary">/"footer";循环会替换该<nav>中的链接。- 翻译无需修改模板。 在非默认语言下,
get_menu()会从字符串存储中返回每个title和description,并为每个内部url添加语言前缀;raw_title/raw_description保存未翻译的文本。菜单标题和描述与其他字符串一样进行翻译——请参阅 原生字符串翻译。例外情况是拥有独立菜单的语言:请参阅 按语言设置菜单。 - 升级说明。 在插件 v1.88.0 之前编写的模板会调用
get_menu('wpcanai_primary')/get_menu('wpcanai_footer')。这两个名称仍可解析,且wpcanai-scan会将每个此类调用报告为legacy_menu_location;请使用wpcanai-replace-in-meta将它们重命名({ "post_id": <id>, "replacements": [{ "from": "get_menu('wpcanai_primary')", "to": "get_menu('primary')" }, { "from": "get_menu('wpcanai_footer')", "to": "get_menu('footer')" }] })。wpcanai-get-menus会在legacy_assignments中列出仍分配在旧名称下的菜单。
Mega 菜单——标题、图标、描述、图片(插件 v1.89.0)
丰富的导航也属于菜单数据:切勿将 Mega 菜单的链接硬编码,因为这些链接还带有图标或简介。
- 项目字段。 除了
title/page/url/target/classes,write-menu项目还接受description(单行文本)、icon(Lucide 图标名称,格式为[a-z0-9-])、image(附件 id——请先将图片 sideload)和attr_title(链接的title属性)。get_menu()项目会返回这些字段,以及id、parent和depth(顶层为0)。站点所有者可在“外观” → “菜单”中编辑相同字段(“描述”和“标题属性”位于“屏幕选项”下;每个项目都可设置图标和图片)。 - 标题。 带有
children且既没有page也没有url的项目是标签,而非链接:get_menu()会以url: ''返回该项目。可将其用作下拉菜单触发项或 Mega 菜单的列标题。没有子项的项目仍需设置page/url之一。 - 写入——单次调用,最多三级:
{ "location": "primary", "items": [ { "title": "Services", "children": [ { "title": "Build", "children": [ { "title": "Websites", "page": "websites", "icon": "monitor", "description": "Fast sites, built to be edited." }, { "title": "Shops", "page": "shops", "icon": "shopping-bag", "description": "WooCommerce stores that sell." } ] }, { "title": "Grow", "children": [ { "title": "SEO", "page": "seo", "icon": "search", "description": "Found by the people looking." } ] } ] }, { "title": "Contact", "page": "contact" } ] } - 渲染——筛选顶层项目,将标题渲染为
<span>,并为链接显示图标和描述:
页面已经调用过一次{# Navigation / Primary #} <nav aria-label="{{ t('Primary') }}" class="flex items-center gap-6"> {% for item in get_menu('primary')|filter(i => i.depth == 0) %} {% if item.children is empty %} <a href="{{ item.url }}" class="{{ item.active ? 'font-semibold' : '' }}"{% if item.target %} target="{{ item.target }}" rel="noopener"{% endif %}>{{ item.title }}</a> {% else %} <div class="relative" x-data="{ open: false }" @click.outside="open = false"> <button type="button" @click="open = !open" :aria-expanded="open">{{ item.title }}</button> <div x-show="open" class="absolute left-0 mt-2 grid grid-cols-2 gap-8 rounded-xl bg-white p-6 shadow-lg"> {% for column in item.children %} <div> {% if column.url %}<a href="{{ column.url }}" class="font-semibold">{{ column.title }}</a>{% else %}<span class="font-semibold">{{ column.title }}</span>{% endif %} {% for link in column.children %} <a href="{{ link.url }}" class="mt-3 flex gap-3"> {% if link.image %}<img {{ image_attrs(link.image, 'src:thumbnail,alt') }} class="h-10 w-10 rounded object-cover">{% elseif link.icon %}<i data-lucide="{{ link.icon }}" class="h-5 w-5"></i>{% endif %} <span>{{ link.title }}{% if link.description %}<small class="block text-slate-500">{{ link.description }}</small>{% endif %}</span> </a> {% endfor %} </div> {% endfor %} </div> </div> {% endif %} {% endfor %} </nav>lucide.createIcons();Alpinex-show面板中的图标从一开始就存在于 DOM 中,因此能够正常渲染。 merge: true会保留未提供的字段;""(对于image则为0)会清除字段。
按语言设置菜单(插件 v1.89.0)
- 翻译,而不是重复创建。 如果某种语言显示的链接相同,请保留一个菜单:其标题和描述通过字符串存储进行翻译,内部 URL 会添加语言前缀(参见 原生字符串翻译)。这通常是默认做法,也是几乎所有情况下的正确选择。
- 仅当链接不同时才为某种语言设置菜单——例如仅马来语页面或更精简的页脚。
wpcanai-write-menu { "location": "footer", "lang": "ms", "items": [ … ] }会为该位置写入该语言的菜单,首次调用时创建Footer (Bahasa Melayu)(该语言的本地名称),并将其分配给该语言。菜单项会以该语言写入:get_menu()会按原样渲染这些菜单项(不会经过字符串存储处理),并仍会为内部 URL 添加前缀。 - 没有第三个位置。 语言菜单是由 CanAI 存储的分配关系,而非已注册的位置:外观 → 菜单 → 管理位置仍会列出 Primary 和 Footer,模板仍会调用
get_menu('primary')/get_menu('footer'),这些内容均不会改变。 - 切换回默认菜单,使用
wpcanai-assign-menu { "location": "footer", "lang": "ms", "menu_id": null }——马来语随后会再次显示翻译后的默认菜单(语言菜单会保留,但取消分配)。使用带menu_id的assign-menu会将某种语言(若不带lang,则为该位置本身)指向现有菜单。 - 从副本开始(v1.93.0):
wpcanai-write-menu { "location": "footer", "lang": "ms", "copy_default": true }会从默认菜单创建Footer (Bahasa Melayu),并预先翻译字符串存储中已有的标签;之后可编辑不同之处。 - 查看菜单,使用
wpcanai-get-menus:languages.ms.footer是该语言的菜单,若为null则继承翻译后的默认菜单。(v1.93.0) 网站所有者也可在 CanAI → 翻译 → 菜单中进行相同操作。对于每种语言和位置,有两个选项:- “与英语使用相同的菜单,并将标签显示为 Bahasa Melayu”:显示标签数量,并提供“翻译标签”,点击后会在外观 → 菜单中打开该菜单;
- “使用包含不同链接的独立菜单”:选择一个菜单,或“从副本创建一个”。
- 字符串。 对于某个位置有自定义菜单的语言,
wpcanai-i18n-list-strings和“字符串”标签页会略去该位置的默认菜单标题和描述:它们不会以该语言显示。
将 HTML 转换为 CanAI(Agent 工作流)
使用 MCP 工具将静态 HTML 文件(例如 index.html)转换为 CanAI 时:
Source 是 canai-replicate 迁移工具包?请推送其
output/push/*.json文件,不要推送原始的output/pages/*.html/output/templates/*.html文件。 每个 canai-replicate 输出文件都是完整的独立 HTML 文档(按设计包含<!DOCTYPE>/<html>/<head>/<body>,可通过file://打开进行本地预览)。CanAI 自己的渲染路径也会将_canai_html包装在自己的文档外壳中,因此将工具包文件的原始内容逐字写入_canai_html会产生重复且无效的标记: 2 个<!DOCTYPE html>、2 个<html>、2 组<head>/</head>、2 个<body>— 已在实际环境中复现(dogfood A2,缺陷 #1 — 严重)。请先运行"$HOME/.claude/skills/canai-replicate/bin/replica" pushprep <site>; 此命令会确定性地移除外壳和预览库代码块,并为每个文件在output/push/<slug>.json中写入一个可直接推送的{ title, slug, template_type, html, css, js, warnings }JSON 文件 — 将其中的html/css/js写入_canai_html/_canai_css/_canai_js, 并在新建wpcanai-create-template调用时,将非 null 的template_type作为template_type术语读取。不要手动再次移除外壳;也不要因为“文件看起来很简单” 就跳过pushprep。必须强制为
header.json/footer.json设置 SLUG。wpcanai_template('header')/('footer')根据已创建模板文章的确切 slug 进行解析,而不是根据 标题或template_type术语。使用wpcanai-create-template创建这两项时,标题必须 恰好是header/footer,或转换后的 slug 恰好为header/footer— 类似“Header — Acme migration”的描述性标题会自动生成不同于该纯字符串的 slug, 此后整个站点内每个{{ wpcanai_template('header') }}调用点都会静默解析为空, 且任何地方都不会报错(dogfood A2 缺陷 #7,已在实际环境中复现)。 继续之前,请使用wpcanai-list-templates确认返回的slug是该纯字符串。 请先推送这两项,再推送任何包含它们的内容。 优先使用 CanAI 模板函数——不要把辅助函数已提供的内容硬编码。 编写_canai_html/_canai_css/_canai_js时,只要有适用的已注册 CanAI Twig 函数,就应使用它,而不是硬编码值或标记。在正式网站上,硬编码资源 URL、内部链接和面向用户的字符串都会出错(基础路径错误、slug 已更改、URL 不是固定链接、没有翻译),就像相对路径./images/hero.jpg会返回 404 一样。将源值映射到辅助函数:
源代码中的硬编码内容 改用 <img src="…">、srcset、CSSurl(…){{ image_attrs(id, 'src,alt') }}/{{ media_url(id, size) }}内部 <a href="/about">/href="about.html">对按 slug 标识的文章使用 {{ slug_url('about') }};对其他站点路径使用{{ turl('/path/') }}(turl('/')是主页);按 ID 使用{{ turl(id_url(id)) }}/{{ turl(post_url()) }}/{{ turl(term_url(id)) }}——slug_url()和turl()会遵循当前语言页眉 / 页脚导航列表( <nav>,或<header>/<footer>中的链接<ul>){% for item in get_menu('primary')|filter(i => i.depth == 0) %}/get_menu('footer'),通过wpcanai-write-menu一次性编写链接(见上文 菜单)——包括超级菜单中的图标、简介和标题home_url()加语言前缀、base或current_lang()URL 变量无需拼接: turl()、slug_url()和get_menu()会自行应用当前语言字面写入的面向用户的字符串 页面字符串使用 {{ t('…') }}(见 原生字符串翻译);只有当字符串已包含在主题或插件的 gettext 目录中时,才使用{{ __('…') }}/{{ _x() }}/{{ _n() }}应为动态内容的静态文章 / 标记 {{ the_content() }}/{{ shortcode('[…]') }}每个页面中重复的共享页眉 / 页脚 / 局部模板 {{ wpcanai_template('slug') }}仅当没有适用的辅助函数,或确实需要绝对外部 URL(例如 OG/Twitter 元数据、Web 清单图标、第三方来源)时,才将值硬编码。图片(步骤 5 + 预先侧载)和内部链接(步骤 6)就是此规则的具体示例。参见 references/REFERENCE.md → 媒体与链接和国际化(i18n)。
- 不要在布局
_canai_html中添加字面量<title>——{{ wp_head() }}会输出文档标题(WordPress / SEO 插件)。 - 将页面级 JavaScript(例如
lucide.createIcons()、Alpine 初始化、自定义处理程序)放在_canai_js中,而不是作为内联<script>放在_canai_html中。CanAI 会在wp_footer()注入_canai_js。_canai_js会通过 Twig 渲染(与_canai_html使用同一引擎)——其中的{# #}注释有效,并会在输出脚本前移除;对于任何提及内部实现的内容,优先使用它们,而不是/* */。布局_canai_html中唯一允许内联的<script>是tailwind.config = { ... }(放在{{ wp_head() }}之后,这样它会在 CanAI 注入的 Tailwind 脚本之后运行)。 - 复用布局:先调用
wpcanai-list-templates。如果已有布局,则将其id复用于_canai_layout/ 引用——除非用户要求第二个外壳,否则不要创建重复的布局模板。 - **页面正文与布局:**将
<main>/ 主要页面标记放在通过wpcanai-create-page创建的page中(将layout设为布局模板 ID),而不是放在类型为layout的wpcanai_template中。布局模板是文档外壳({{ page_content }}、页眉/页脚 include);页面正文的template_type不是layout。- 嵌套页面(插件 v1.96.0)。
/gallery/auditorium/下的源页面是子页面,而不是顶级页面gallery-auditorium。先创建父页面并发布(status: "publish",默认值),然后按路径顺序创建各个子页面,设置parent: <parent id>,并将路径的最后一段设为其slug(auditorium)。**绝不要把/a/b/展平为a-bslug。**复用已存在的父页面:使用wpcanai-list-pages { "all_pages": true }(插件 v1.97.0)查找,它会列出所有页面——包括普通 WordPress 页面和草稿——并显示其path和status。在 1.96.x 中,此标记会被忽略:返回的行没有status,且仅包含已发布的 CanAI 页面。请用户提供普通页面或草稿父页面的 id。若创建页面时出现警告,提示 slug 已保存为gallery-2,则表示gallery已存在。使用返回的 id 链接到页面——{{ turl(id_url(<id>)) }},它在页面移动后仍然有效;或者使用slug_url()配合返回的slug(如果 slug 重复,则使用path;参见规则 6)。绝不要直接粘贴url或自行构造路径。要移动、重命名或更改已有页面的 slug,请使用wpcanai-update-page;移动已发布页面后,按其 URL 更改警告中的说明搜索并修正旧路径(参见wpcanai-update-page)。
- 嵌套页面(插件 v1.96.0)。
- 在编写 HTML 前自动检测并上传本地媒体。静态网站文件夹几乎总会引用本地资源(
./images/hero.jpg、assets/logo.svg、videos/intro.mp4、favicon、OG 图片、CSSurl(...)背景)。如果原样推送 HTML/CSS,这些引用在正式网站上都会返回 404。调用任何wpcanai-create-page/wpcanai-write-meta之前,先运行静态网站资源上传预处理(下一小节),这样编写的 HTML 才会通过{{ image_attrs(...) }}/{{ media_url(...) }}按 ID解析媒体,而不是使用相对路径或固定的上传 URL。 - 将内部链接改写为 CanAI 辅助函数(遵循上述原则)。静态源码中会保留
<a href="about.html">/href="/about">;这些链接在正式网站上会失效(基础路径错误、非固定链接、slug 已移动)。将每个内部链接改写为对应的辅助函数:跨页面链接 → 已知目标页面 id 时(来自wpcanai-create-page或wpcanai-list-pages),使用{{ turl(id_url(<id>)) }};否则使用{{ slug_url('<slug>') }}。对于子页面(插件 v1.96.0),slug_url()会先匹配精确路径,再匹配仅由一个页面拥有的完整 slug:slug_url('auditorium')可以找到/gallery/auditorium/,即使页面移动后也能继续找到。顶级页面优先于与其共享 slug 的子页面(从插件 v1.97.1 起,已发布的顶级页面也优先于保留相同 slug 的草稿;两个已发布页面仍会产生歧义);当两个子页面共享 slug 时,单独使用 slug 无法解析(href="",插件 v1.97.0 起,wpcanai-scan会报告link_unresolved),因此请传入路径——slug_url('gallery/theatrettes')。路径链接在页面移动后不会继续有效(会渲染为href=""),因此优先使用 id。术语 / 归档链接 →{{ turl(term_url(<term_id>)) }};其他站点路径 →{{ turl('/path/') }}。页眉或页脚导航列表不应逐个改写链接——它应改为get_menu()循环(参见菜单)。**保持以下链接不变:**外部 / 绝对链接(https://other.com)、协议相对链接(//cdn…)和仅锚点链接(#section)。在清单中报告这些改写。 - **将区块注释规范化为 Twig 导航标签。**把
canai-prepare输出的每条 HTML 导航注释都转换为精确格式{# Type / Short Label #}——例如{# Section / Hero #}。绝不要将 HTML 导航注释带入_canai_html(它们会污染渲染输出并暴露源码供侦察)。确保每个地标元素的对应类型标签紧挨在其开始标签之前:<main>→Container,<section>→Section,<header>→Header,<footer>→Footer,<nav>→Navigation,<aside>→Sidebar。请遵循 references/STRUCTURE-NAVIGATION.md 中的受控词汇和精确转换规则。普通实现说明使用{# @dev … #},不会出现在编辑器的结构大纲中。canai-replicate 的pushprep产物可能使用较旧约定;写入前请先规范化。 - 完成编写操作(create-page / create-template / implement / import / preset)后,运行
wpcanai-scan并清除内容问题,然后才能宣称完成。通过wpcanai-replace-in-meta进行的精确修改(类名、字符串、URL)无需扫描——跳过即可。将missing_structure_comment、invalid_structure_comment、leaky_comment和leaky_secret视为布局损坏问题:添加或修正 Twig 导航标签,将 HTML //* */注释转换为 Twig 注释,并从 meta 中移除类似机密的字面量。还要清除导航问题(插件 v1.88.0:hardcoded_nav、legacy_menu_location、unknown_menu_location、menu_fallback_chain、menu_children_twice、menu_not_assigned、hardcoded_lang_prefix、unlocalised_internal_href;menu_children_twicev1.89.0),以及原生国际化站点上的split_sentence(将一个句子拆成多个t();合并它,参见原生字符串翻译步骤 2)和unsupported_t_markup(在t()中使用块级元素或未配对标签,会显示为文本;插件 v1.94.0):每条消息都会指出修复方法,上方的菜单部分介绍了工作流。render_warning(v1.90.0)表示页面渲染期间某行输出了数组——用相同方式修复(参见wpcanai-scan下的渲染警告)。清除链接问题(插件 v1.97.0;参见wpcanai-scan下的链接问题):link_unresolved表示字面量slug_url()未匹配到任何页面或匹配到多个页面,因此渲染出的值为href=""。对于多个匹配项,请传入路径;对于没有匹配项,请修正 slug;或者使用turl(id_url(<id>))。link_moved表示字面量turl()/href路径指向的页面已移动——将其替换为消息中列出的turl(id_url(N))。要扫描整个站点,请使用下方的注释 / 机密安全检查配方。
博客文章(非 CanAI 页面)
博客文章是普通的 WordPress 内容,而不是 CanAI 元页面。使用 wpcanai-write-post 编写,并传入结构化区块列表——正文会变成原生区块标记,网站所有者可以在区块编辑器中编辑;它会通过博客套件的 blog-single-post 模板渲染,无需额外操作。先将所有图片侧载,再通过附件 ID 引用。不要对文章使用 wpcanai-create-page 或 wpcanai-write-meta。
- 文章中的图表(插件 v1.77.0)。 图表是数据,不是图片:传入
chart区块(参见区块表),并提供labels和最多 8 个datasets——如果有第 9 个系列,应将它合并到“Other”,而不是拆分图表;也不要侧载渲染后的图表图片。设置unit(ms、%、MB),让刻度、工具提示和表格显示为“12 ms”,而不只是“12”。每个图表还会将数值渲染为无障碍数据表(默认折叠的<details>;table: "open"会展开表格,"hidden"则会保留表格供屏幕阅读器和 Markdown 使用),因此不支持 JS、阅读模式、RSS 和Accept: text/markdown的读者无需你添加table区块也能看到数据。网站所有者可以在 Settings → Libraries → Chart.js 下关闭运行时——选项wpcanai_chart_settings(load_chartauto|yes|no,sourceplugin|cdn),这是一个一等设置键,可通过wpcanai-read-settings读取,并通过wpcanai-update-settings修改;设为no时只显示图表对应的表格,因此在报告图表缺失为错误之前,先读取此设置。
阅读模式(Safari 阅读器 / Firefox 阅读视图 / Chrome)
当用户要求某个页面或文章“支持阅读模式”(或阅读视图 / Safari 阅读器)时,先执行 references/READING-MODE.md 中的门槛检查:读取目标内容,对照参考文档中的表格进行分类;如果该内容形式不建议使用阅读模式(落地页、列表页、WooCommerce 交易页面、表单页面、内容单薄的文案),则停止并说明原因。只有内容确实是文章时,才继续遵循参考文档中的标记检查清单(在 <main> 内使用 <article>,并在其中放置 <h1>、<time> 和带有 rel="author" 的署名;正文放在 <p> 中;导航、相关内容和 CTA 放在外部)。没有启用阅读模式的插件设置或元标签——它完全取决于浏览器根据标记进行的启发式判断,因此切勿声称存在开关。
面向 Agent 的 Markdown(Accept: text/markdown)
当用户询问网站是否向 AI Agent 和爬虫提供 Markdown、希望某个页面能被其读取、询问 Accept: text/markdown / ?format=markdown,或希望关闭相关行为时,请遵循 references/MARKDOWN-FOR-AGENTS.md(插件 v1.76.0)。CanAI 接管页面响应 Markdown 请求时,会返回页面的内容槽——而不是布局——作为 YAML front matter 加转换后的正文,因此 READING-MODE.md 中的标记规范能确保 Markdown 内容整洁。内容协商按请求单独进行,浏览器不会看到它,因此不会影响正常访问。唯一的控制项是全站选项 wpcanai_markdown_negotiation(默认开启),这是一个一等设置键,可通过 wpcanai-read-settings 读取,并通过 wpcanai-update-settings 修改——切勿使用通用选项允许列表。WooCommerce 购物车 / 结账 / 我的账户 / 订单页面始终返回 HTML,没有逐页覆盖设置,也无法强制被排除的页面返回 Markdown,因此切勿声称存在此类功能。(v1.80.0)缓存: 由 Accept 标头选择的 Markdown 响应会发送 Cache-Control: private, no-store(以及 LiteSpeed no-cache),因此页面缓存绝不会将其提供给浏览器;?format=markdown 是可缓存的 URL,且公布的 rel="alternate" 链接会指向该 URL。需要考虑缓存时,请向爬虫和用户提供 ?format=markdown URL。
页面格式标记
每个页面都有一个 format 标记:"twig"(默认值,也是此技能唯一会编写的格式)或 "blocks"。wpcanai-read-meta 默认会返回该标记,wpcanai-list-pages 的行中也会包含它。若页面标记为 blocks,则写入 html 或 js 会被拒绝,并返回 format_mismatch,且不会产生任何更改;错误信息会附带一段提示,供你转交给网站所有者;请转交,而不是重试或绕过限制。对于 blocks 页面,单独写入 css 是允许的(插件 v1.67.0):这是页面的纯 CSS 插槽,写入 _canai_css,并在 head 中原样输出。layout、context 和 tailwind_* 也可以写入。
静态网站资源旁加载预处理
当用户运行 /canai-mcp implement <folder>、要求你导入静态网站,或推送 canai-replicate 迁移工具包的 output/push/*.json 构件时触发(pushprep 构件中的 html 仍包含源网站原始的绝对路径/热链图片 URL——canai-replicate 不负责旁加载;这正是此技能的工作内容,与任何其他静态网站导入一样)。若未进行旁加载,一旦源网站启用自己的热链保护,图片就可能返回 404 或显示损坏(已确认的线上问题:dogfood A2 缺陷 #6)。此流程在任何 HTML/CSS 写入 CanAI meta 之前一次性执行。
-
枚举引用的资源。 使用
Read/Grep/Glob扫描文件夹,提取所有解析到所提供文件夹内部文件的引用。至少涵盖:- HTML 属性:
src、srcset(每个候选项)、<link rel="icon">/apple-touch-icon/manifest的href、<video poster>、<source src>、<object data>、<embed src>、OG / Twitter meta(og:image、twitter:image)。 - CSS:
<style>块、内联style="…"以及文件夹中任何.css文件内的url(...)。 - JS:内联
<script>和打包的.js文件中的字符串字面量(若显然是资源路径,例如"./images/foo.png");跳过动态/模板化的值。
**跳过:**指向其他来源的绝对
http(s)://URL、协议相对的//cdn…、data:URI、mailto:/tel:、仅锚点#…,以及无法解析为文件夹内真实文件的路径。按解析后的绝对路径去重。 - HTML 属性:
-
写入标记前,预检每一张有意义的栅格图片。 使用可用的本地图像信息工具读取其固有宽度和高度,并使用宿主的图像查看器检查像素。记录宽高比(
width / height)和重要区域:面部、头部、全身、共同主体、手部、产品、奖项或烘焙在图片中的文字。徽标、图标、装饰纹理以及使用object-contain的图片无需分析主体裁切。对于远程源图片,如果可能,应在旁加载前检查源图/抓取的资源;若无法检查,应使用object-contain或接近源图的宽高比保留图片,而不是猜测会造成破坏的裁切。优先使用响应式宽高比容器,而不是浅层固定
h-*裁切:纵向原图 →aspect-[4/5]、aspect-[3/4]或aspect-square;正方形 →aspect-square或aspect-[4/3];横向 →aspect-[16/9]、aspect-[3/2]或aspect-[4/3]。使用object-cover时,应从检查过的构图开始;可参考的起始值:头像/单人肖像使用object-[center_30%](Y 轴 25–35%),坐姿双人使用object-[center_58%](Y 轴 55–60%),站立群像使用object-[center_20%],手持物品的主体使用object-[center_80%](Y 轴 75–85%),并至少搭配aspect-[3/4]或高度相当的容器。主体偏离中心时,需要指定两个坐标,例如object-[80%_25%]。这些只是起始参考值,不是盲目套用的默认值。部署前,预览窄屏手机和宽屏桌面的裁切效果。面部、头部、共同主体以及与任务相关的物品都必须清晰可见,且不能紧贴边缘。必要时使用断点专用的宽高比/焦点类;若无法找到同时适用于两种尺寸的裁切方式,则使用更高的容器/与源图匹配的容器,或使用
object-contain。 -
逐个侧载每个唯一文件:通过
POST {site}/wp-json/wpcanai/v1/sideload(准确的curl命令请参阅 上传媒体)。每次只上传 一个文件,以便记录每个id/source_url。如果源 HTML 提供了alt(<img alt="…">),请传入合适的alt;否则省略。重复上传相同字节没有问题——WordPress 会按文件名去重,但 API 每次调用都会返回一个新的附件,因此映射中的缓存命中很重要。 -
构建以媒体
id为键的重写映射——{ <original-reference-string>: <media id> },而不是原始source_url。键必须使用源文件中出现的 确切字符串(例如./images/hero.jpg、images/hero.jpg、/images/hero.jpg——这三个引用都映射到同一个上传id)。CanAI 会在渲染时根据 id 解析实时 URL,因此即使媒体被重新生成或站点迁移,页面仍能正常显示;固定的source_url则不行。(第 5 步中只有 OG/清单例外会保留绝对source_url。) -
**在调用任何
wpcanai-write-meta之前,在内存中将 HTML / CSS / JS 重写为基于 ID 的辅助函数。**将其应用于第 1 步扫描的相同内容表面,并根据表面使用正确的形式:<img>→ 移除硬编码的src/alt,并插入{{ image_attrs(<id>, 'src,alt') }}(已知尺寸时使用'src,alt,width,height')。保留现有的 class / 其他属性,包括预先检查过的宽高比容器和焦点位置工具类;在首屏以下保留或添加loading="lazy"。- CSS
url(...)(位于_canai_css/ 内联style中)、<source srcset>、<video poster>、<link rel="icon">/apple-touch-icon→{{ media_url(<id>, 'full') }}(Twig 渲染的表面)。分别重写srcset中的每个候选项,并保留其1x/2x/480w描述符。 - OG / Twitter 元数据(
og:image、twitter:image)和 Web 清单图标 → 保留绝对source_url。这些需要绝对 URL,且无法可靠地通过 Twig 渲染——这是基于 ID 重写规则中唯一有文档说明的例外。
不要修改磁盘上的文件——重写由 Agent 在内存中完成,重写后的字符串就是传给
html/css/js的内容。 -
完成后向用户提供清单:用简短表格列出
original path → dimensions/aspect → framing choice → media id → final Twig form——其中 framing choice 应说明容器宽高比和焦点工具类(或object-contain);final Twig form 应为重写后的{{ image_attrs(...) }}/{{ media_url(...) }},或者 OG/清单例外所用的绝对source_url——并统计跳过的引用数量(注明原因:外部资源、数据 URI、文件缺失)。用户可据此抽查转换结果。
**幂等性。**如果用户重新运行导入,你会再次上传文件(该端点不会按内容哈希去重)。对于一次性导入,这可以接受;如果发现明显的重复运行迹象(例如,用户指定的文件夹与已构建模板时使用的文件夹相同),请提醒用户,并提供使用 wpcanai-list-media / wpcanai-get-media 复用现有附件的选项,而不是重新上传。
**此流程未涵盖的内容。**通过 @font-face { src: url(...) } 加载的字体处理方式相同,也应纳入流程。CSS-in-JS、webpack 风格的哈希包,或仅在运行时通过 fetch 引用的资源无法静态检测——请在清单的“跳过”部分注明这些情况,以便用户了解需要手动处理。
商店和归档分页(网站上的 Twig)
为 shop 或 product-category 委托处理器(或全局 WC 模板)编写 _canai_html 时,请使用注入的 products 和 pagination,并输出 {{ wc_pagination()|raw }} 或 {{ wpcanai_pagination()|raw }}。对于 category / tag / search / author / archive(包括 CPT 归档),请使用注入的 posts 和 pagination,并输出 {{ the_posts_pagination()|raw }} 或 {{ wpcanai_paginate_links()|raw }}。博客索引——在 Settings → Reading → "Posts page" 中设置的页面——也会注入相同的 posts 和 pagination;它是普通的 CanAI 页面(通过 wpcanai-read-settings → page_for_posts 查找其 id),其自身的 _canai_html 会渲染文章列表。该页面的编辑器预览会显示空状态,因为 posts 只存在于线上请求中。不支持 "Your latest posts" 首页模式。完整的数据结构和固定链接说明,请参阅 references/REFERENCE.md。
为 WooCommerce 区块设置样式(cart / checkout / order-received / my-account)
CanAI 的 wc_* Twig 辅助函数(wc_checkout_form()、wc_cart_totals()、my-account 端点)会输出原始 WooCommerce 内容;在实时页面渲染之前,其 HTML 结构不可见。而 order-received/order-pay 页面还会通过 WooCommerce 的访客邮箱验证关卡隐藏详情,因此无法直接查看。不要猜测选择器。 为这些区块设置样式之前:
- 使用你正在设置样式的上下文调用
wpcanai-get-wc-css-reference(例如{ "context": "order-received" })。 - 阅读
css_reference——这是 WooCommerce 针对该上下文选择器的默认规则。它是原始 SCSS 源码,并非编译后的 CSS:Sass 变量、混入和&父级引用均未解析(例如darken($secondary, 10%)、@include …),因此应将规则改写为实际 CSS,而不是直接粘贴到_canai_css中。这些内容提取自已安装的 WooCommerce 的四个样式表(woocommerce.scss、woocommerce-layout.scss、woocommerce-smallscreen.scss、forms.scss),因此会随 WooCommerce 更新保持准确——请重新调用工具,不要依赖记忆中的类名。规则会按其实际祖先选择器链包装(例如.woocommerce { table.shop_table { … } }),因此应匹配完整链以确保选择器优先级。包裹在@media only screen and (max-width: 768px)中的规则来自woocommerce-smallscreen.scss,仅适用于移动设备——WooCommerce 会在入队时应用该断点,而不是通过文件本身应用,因此不要将其视为无条件规则。 - 阅读
template_files中的文件(如果你有权访问安装目录的文件系统,则读取其中任意文件)——这些文件是权威标记;主题覆盖版本优先于 WooCommerce 自带版本。其中路径相对于 ABSPATH。文件包含核心提供但从不设置样式的类(例如感谢页面上的.woocommerce-order-overview__order/__date/__total)。如果templates_missing非空,说明上下文映射与已安装的 WooCommerce 不一致——其中的路径是相对于上下文的(例如order/order-details.php),与template_files的格式刻意不同,因此不能直接比较这两个列表。 - 检查
third_party_hooks。每个条目({hook, priority, callback, file, is_third_party})对应一个在该上下文中添加自身标记的插件(例如在感谢页面追加表格的配送时段插件)——设置样式前,请阅读其file以了解它输出的类。传入"include_core": true,即可获取这些钩子上的所有回调,包括 WooCommerce 自身的回调;每个条目仍带有is_third_party标记——当你想全面了解渲染到该区域的内容,而不只是第三方添加的内容时,这很有用。 - 将针对这些选择器的覆盖样式写入页面/模板的
_canai_css(通过wpcanai-write-meta),并与网站的设计系统保持一致。
如果 css_fallback 为 "full-file",说明未找到选择器提取结果(WooCommerce 进行了重构)——此时会返回整个样式表;请在其中搜索 template_files 中看到的类。
这是只读静态分析:不会渲染任何内容,也不会读取任何订单、购物车或客户数据。
MCP 工具参考(内联)
能力 ID 使用斜杠;MCP 工具名称使用连字符(wpcanai/read-meta → wpcanai-read-meta)。
工具标签(插件 v1.80.0;自 v1.81.0 起有七个标签)
每个 CanAI 工具描述都以方括号前缀开头,说明该工具会触及 CanAI 自身数据以外的内容,例如 [fluent-snippets, filesystem] Update a snippet …。调用不熟悉的工具前请先阅读;annotations.readonly 仍会说明工具是否会写入,而 uninstall-preset、restore-snapshot 和 restore-operation 则标记为具有破坏性。
| 标签 | 工具读取或写入的内容 |
|---|---|
canai | CanAI 自身的数据:wpcanai_template、_canai_* 元数据、wpcanai_* 选项、快照、预设 |
core | CanAI 以外的 WordPress 数据:文章、页面、文章元数据、选项、媒体 |
woocommerce | WooCommerce 页面和设置 |
fluent-snippets | Fluent Snippets 文件和索引(canai-yolo 工具) |
cache | 页面缓存插件的清除钩子(LiteSpeed Cache、WP Rocket、W3 Total Cache、WP Super Cache、WP-Optimize — v1.82.0) |
filesystem | wp-content 下的文件(代码片段存储、上传文件) |
http | 来自服务的出站请求(远程加载、预设下载、网络诊断) |
你看到的前缀是 针对站点解析后的结果:只有在第三方插件处于活动状态时才会出现相应标签(woocommerce、fluent-snippets)——没有该插件时,工具根本不会注册;只有启用了 Guardrails → Page cache → purge after writes(wpcanai_purge_after_write,默认开启)且检测到受支持的缓存插件时,才会出现 cache(purge-cache 和 diagnostics 始终带有此标签)。因此,在基础站点上,write-meta 显示 [canai];在 LiteSpeed 环境中则显示 [canai, cache]。声明的标签(插件 v1.81.0):
| 标签 | 工具 |
|---|---|
canai | list-templates、read-meta、grep-content、resolve-content-id、scan、get-pending、export、list-presets、list-snapshots、list-operations、get-snapshot、pin-snapshot、pin-current |
canai, cache | write-meta、replace-in-meta、create-template、import、restore-snapshot、restore-operation |
canai, core | list-pages、list-media、get-media、read-settings、update-settings、get-option、update-options、uninstall-preset、get-menus、所有 i18n-* 工具 |
canai, core, cache | create-page、write-post、write-page、write-menu、assign-menu |
canai, core, http, cache | diagnostics |
canai, core, woocommerce | setup |
canai, core, woocommerce, http | install-preset |
core, cache | update-page |
core, filesystem, http | sideload-url |
core, filesystem, cache | update-media |
woocommerce | get-wc-page-ids、get-wc-css-reference |
cache | purge-cache |
fluent-snippets | list-snippets、get-snippet |
fluent-snippets, filesystem, cache | create-snippet、update-snippet、replace-in-snippet、set-snippet-status |
带有 cache 标签的写入工具会在响应中返回 purged:如果已将清除操作排入请求结束时执行,则返回 { "scope": "all"|"post", "plugin": "litespeed"|"wp-rocket"|"w3tc"|"wp-super-cache"|"wp-optimize" };如果没有排入清除操作,则返回 null(自动清除关闭、没有缓存插件或没有写入内容)。
Requires(v1.82.0)。 除 touches 外,工具还可以声明 meta.mcp.requires:即完成其工作所必需的标签。目前只有 purge-cache 需要 cache。缺少所需集成的工具会被 建议关闭,并从你的工具列表中隐藏(见下文的 工具可见性)。
工具暴露情况 — 你能看到哪些工具(插件 v1.82.0)
客户端通过 tools/list 获取的列表并非“所有已注册的工具”。每个工具都有针对本网站的实时推荐设置,站点所有者可以在 AI Client → Tools 中为每个工具单独覆盖设置(Auto / On / Off),而 Follow recommendations 开关决定 Auto 表示“遵循推荐”(新安装)还是“开启”(从 ≤1.81 升级的站点,因此对这些站点不会有任何变化)。覆盖设置始终优先。规则按顺序匹配,命中第一条即生效:
| 规则 | 隐藏条件 | 工具 |
|---|---|---|
| R1 | 缺少必需的集成 | 没有缓存插件时的 purge-cache |
| R2 | 始终隐藏 — 翻译工具需主动启用 | 所有 i18n-* 工具(Languages 组) |
| R3 | 另一个已注册的 MCP 能力与其重复(站点筛选器,默认为空) | get-option、list-media、get-media、sideload-url |
| R4 | 已完成设置 — 仅供参考,绝不会自动应用 | setup、预设、import、export |
diagnostics 和 manage-tools 永远不会隐藏:你可以通过它们了解哪些工具被隐藏,并重新启用这些工具。
你预期会看到的工具不在列表中? 不要因此断定站点不具备该功能。调用 wpcanai-manage-tools { "action": "list" } — 它会列出所有已注册的工具,包括隐藏的工具,并提供 exposed、override、recommendation、reason 和 rule — 然后,如果用户希望使用该工具,就将其启用并重新连接(HTTP 客户端会缓存 tools/list;重新连接后新工具才会出现)。直接调用隐藏的工具会失败并返回 tool_disabled,错误消息会指出确切的 manage-tools 调用方式。
wpcanai-manage-tools(插件 v1.82.0)
- 参数:
{ "action": "list" }或{ "action": "set", "overrides"?: { "<short>": "on"|"off"|null }, "groups"?: { "<group>": "on"|"off"|null }, "follow_recommendations"?: bool, "apply_advisory"?: bool }。null会清除覆盖设置,使推荐设置重新生效。groups会将设置应用于 Tools 标签页分组中的所有已注册工具 —Templates & code、Pages & posts、Media、Snippets、Languages、History & restore、Site & settings、WooCommerce(或使用 slug,例如languages)。apply_advisory: true会将 R4 建议写入为off覆盖设置。未知工具名 →invalid_tool(仍可清除未注册工具的现有覆盖设置);未知分组 →invalid_group;diagnostics/manage-tools→exempt_tool。 - 返回值:
{ "follow_recommendations": bool, "tools": [{ "name", "group", "touches", "requires", "exposed", "override", "recommendation", "reason", "rule", "advisory", "bytes" }], "totals": { "exposed", "total", "exposed_bytes", "total_bytes", "exposed_tokens", "total_tokens" } };执行set后还会返回"reconnect_required": true和一条note。bytes是描述和输入架构的大小 — 即该工具每轮消耗的字节数。 - 默认隐藏翻译工具。 当用户提出任何与翻译相关的需求,而你的工具列表中没有
i18n-*工具时:运行manage-tools list,向用户确认是否要在此站点启用翻译,然后执行{ "action": "set", "groups": { "Languages": "on" } },重新连接,并继续使用下方的翻译模型路由器(如果尚未添加任何语言,i18n-set-settings会添加第一种语言 — 调用该工具前必须先启用此分组)。此覆盖设置会在不同请求和重新连接之间保留,直到有人将其清除。
wpcanai-list-templates
- 参数:
{ "status"?: string }— 文章状态筛选器(默认值为"publish")。 - **返回值:**对象
array,包含:id、title、slug、status、type(template_type 名称)、layout_id(整数或 null)。
wpcanai-list-pages
- 参数:
{ "all_pages"?: bool }— 默认值为false。(v1.97.0) 设置all_pages: true时,会按 id 顺序列出所有状态(回收站除外)的页面(文章类型page),包括普通 WordPress 页面和草稿,而不是 CanAI 条目。它不会返回其他文章类型。 - 返回:对象
array,包含:id、title、post_type、(v1.97.0)status、layout_id(整数或 null)、format("twig"|"blocks";(v1.97.0) 在all_pages模式下,对于未使用 CanAI 创建的页面为null)、css_lines/js_lines(页面自身_canai_css/_canai_js的非空行数;0= 无),以及 (v1.96.0)slug、parent(页面 id;0= 顶级)、path(gallery/auditorium,开头和结尾均不带斜杠;未设置 slug 保存的页面为'')和url——默认适用于带有_canai_html的文章/页面,或标记为 blocks 的页面。创建页面前,请使用all_pages: true和parent/path查找现有父页面(也包括普通页面或草稿;见 嵌套页面),并使用id/path链接到子页面(规则 6)。 - 默认仅返回已发布内容。 未设置
all_pages时,它会返回状态为publish的文章:通过wpcanai-create-page创建且status: "draft"的页面不会出现——请传入all_pages: true或按 id 重新查询;不要以为页面已丢失。
wpcanai-read-meta
- 参数:
{ "post_id": int, "fields"?: ["html"|"css"|"js"|"context"|"layout"|"format"|"blocks"|"tailwind_build"|"tailwind_hash"|"asset_sources"|"render_warnings"] }—post_id必填;省略fields时读取默认字段(html、css、js、context、layout、format)。需要时请显式传入tailwind_build/tailwind_hash——由于构建 CSS 可能很大,这两项不包含在默认字段中。 - 返回: 包含以下任意字段的对象:
html、css、js、context(字符串),layout(int),format("twig"|"blocks"),blocks(字符串,原始区块标记),tailwind_build(字符串),tailwind_hash(字符串),hashes(对象)。 - (v1.58.0)
hashes。 当fields包含html、css和/或js,或默认使用这些字段时,响应还会包含hashes——每个请求的内容字段对应一个 SHA-1,例如{ "html": "<sha1>" }。仅读取元数据时(未请求html/css/js)不会返回此字段。将这些值直接传给wpcanai-write-meta的expected_hash,使下一次写入以该字段自本次读取后未发生变化为条件。 - (v1.65.0)
format和blocks。format属于默认字段。blocks返回原始区块标记,仅适用于区块页面(否则会返回format_mismatch);不会对其进行哈希处理。 - (v1.71.0)
lines。"lines": [from, to](行号从 1 开始,包含首尾,最多 400 行)仅返回单个请求内容字段中指定范围的内容——fields必须恰好指定html/css/js其中一个(可同时请求layout等非内容字段)——并添加line_counts: { html: 1240 }。hashes仍基于完整字段计算,因此仍可用于write-meta的expected_hash。可用此参数检查grep-content命中的区域,而不必拉取整个文档。使用lines时始终传入fields——{ "post_id": 12, "fields": ["html"], "lines": [200, 260] }。若不传入fields,调用会失败并返回invalid_inputlines applies to exactly one content field,因为此时fields会默认包含 html、css 和 js。 - (v1.80.0)
hash_only。"hash_only": true仅返回fields中指定内容字段(html/css/js;若未指定,则返回全部三个字段)的hashes和line_counts,以及format和layout——不返回内容。这是确认写入已生效或在调用write-meta前获取expected_hash的轻量方式。不能与lines同时使用。 - (v1.90.0)
render_warnings。 不属于默认字段。fields: ["render_warnings"]会返回该文章自身的 Twig 在过去 7 天内渲染页面时产生的 PHP 警告:[{ field, line, kind, message, excerpt, url, first_seen, last_seen }]——kind为expression(包含line和作为excerpt的源代码行)或print(单独的{{ }}输出:line和excerpt为 null,Twig 无法判断具体是哪一处)。数据与wpcanai-scan的render_warning发现项相同,但仅针对一篇文章。 - (v1.89.3) 未知的
post_id。read-meta、write-meta、replace-in-meta和grep-content会返回invalid_postInvalid post_id: no post with ID 123 exists(或 pass a positive post ID)。请使用wpcanai-list-pages/wpcanai-list-templates/wpcanai-resolve-content-id重新解析 ID——切勿重试同一个 ID,也不要猜测相邻 ID。
wpcanai-write-meta
- 参数:
{ "post_id": int, "html"?: string, "css"?: string, "js"?: string, "context"?: string, "layout"?: int, "tailwind_build"?: string, "tailwind_hash"?: string, "confirm_truncate"?: bool, "expected_hash"?: object, "convert"?: bool, "ignore_syntax_error"?: bool }—post_id为必填项;只包含要更新的键即可。tailwind_build会将预编译的单页 Tailwind CSS 写入_canai_tailwind_build;tailwind_hash会将输入哈希写入_canai_tailwind_hash(两者配套使用——参见 生产环境编译 Tailwind)。 - 返回:
{ "success": bool, "post_id": int }— 在转换为 Twig 的路径上(convert: true),还会返回converted: true和一个warnings数组,说明区块正文仍保留在post_content中,尚未渲染。 - 注意:完全支持传入完整的
html/css/js字符串,大小不限——大型页面也没问题。绝不能因为负载很大就改用原始 HTTP/Python/curl 请求 MCP 端点(它没有 MCP 会话/身份验证,会返回 403)。如果只需更改特定子字符串(资源 URL、类名、字符串),请优先使用下文的wpcanai-replace-in-meta。 - 不要将
tailwind_build和源代码编辑打包在一起。 在同一次调用中写入tailwind_build和html/css/js时,写入的tailwind_build会带有写入前的时间戳,因此会被保守地视为过期。先写入源代码,再通过单独一次调用写入tailwind_build/tailwind_hash。 - 空字符串的处理方式不同。
write-meta使用键是否存在来判断(array_key_exists):传入"html": ""会清空该字段。create-template/create-page使用!empty:空字符串会被忽略,不会写入。要清空内容字段,请使用write-meta。 - (v1.58.0)
confirm_truncate(可选布尔值)——将 1 KB 或更大的html/css/js内容替换为小 70% 以上的负载时,必须设为true。否则会拒绝此类写入,并返回错误代码truncation_blocked(状态码 409),且不会写入任何内容——不会更改数据库、创建快照或开启操作,因为写入根本不会发生。错误消息会指出字段以及新旧内容的准确字节数。如果有意缩短内容,请使用confirm_truncate: true重新发送完全相同的调用;如果不是,请先读取该页面的元数据(wpcanai-read-meta)——你的负载很可能有误。请传入真正的布尔值或字符串"true";字符串"false"和"0"会正确地被视为未确认,不会被当作真值。 - (v1.58.0)
expected_hash(可选对象)——按字段指定的乐观锁,例如{ "html": "<sha1>" }(绝不能传入单独的标量——一次调用可以同时写入多个字段)。如果读取后该字段发生了变化,写入会失败,并返回错误代码hash_mismatch(状态码 409),且不会写入任何内容;错误中会包含当前哈希,以便你重新读取、重新应用编辑并重试。哈希可从wpcanai-read-meta返回的hashes对象中获取(见上文)。省略expected_hash会完全保留原有的最后写入优先行为。 - **(v1.58.0)保护检查顺序:**先检查
expected_hash,再检查confirm_truncate,两项检查都在写入任何内容之前进行——如果读取的数据已过期,会先报告该情况;如果内容缩短受限,也会阻止写入。 - (v1.68.26)Twig 语法保护。 在创建快照或写入数据库前,会解析已更改的
html/css/js字段。Twig 无效时会返回twig_syntax_error(状态码 422),并提供field、line和解析器消息详情,且不会写入任何内容。仅在有意存储无效的中间状态时使用ignore_syntax_error: true;写入会成功并附带警告,wpcanai-scan仍会将其标记出来。 - (v1.65.0 / v1.67.0)区块页面。 如果页面格式为区块格式,写入
html或js会被拒绝,并返回format_mismatch;除非设置convert: true,将页面切换回 Twig(v1.65.0)。区块页面接受单独写入css(v1.67.0):它是页面的纯 CSS 槽位,写入位置为_canai_css。始终允许写入layout、context和tailwind_*。
wpcanai-replace-in-meta
- 参数:
{ "post_id": int, "field"?: "html"|"css"|"js", "replacements": [{ "from": string, "to": string }], "require_all"?: bool, "ignore_whitespace"?: bool, "regex"?: bool, "scope"?: { "text": string, "occurrence"?: int }, "dry_run"?: bool }—post_id和replacements为必填项;field默认为html。即使只有一组替换,replacements也必须是数组 —{ "post_id": 12, "replacements": [{ "from": "a", "to": "b" }] }。顶层没有from/to;不带replacements的调用会因架构验证失败,并报错 replacements is a required property of input。替换在服务端按顺序应用于所有出现from的位置 — 文档不会通过网络传输。(v1.71.0)ignore_whitespace会让from中的每段连续空白匹配任意连续空白(可容忍缩进和换行差异;to按原样写入)。regex会将from视为 PCRE,并将to视为preg_replace模板($1);不能与ignore_whitespace同时使用。scope.text会将所有替换限制在该文本第 n 次字面匹配所在的最近外层元素内(occurrence默认为 1)— 无需知道标记,就能指定“写着 conference 的标题”。dry_run会计算次数并提供诊断信息,但不写入任何内容。若任何from的匹配次数为 0,require_all: true会报错(422,不写入任何内容)。与write-meta采用相同的lang_mismatch强制检查。 - 返回值:
{ "success": bool, "post_id": int, "field": string, "total": int, "dry_run": bool, "scope"?: { "text", "occurrences", "used", "start", "end", "line" }, "replacements": [{ "from", "to", "count", "suggestion"? }] }。如果某组匹配次数为 0(且未使用require_all),则会附带说明未匹配原因的suggestion。 - (v1.71.0)未匹配诊断。
require_all未匹配时会返回replace_no_match,且错误消息本身包含诊断信息 — 通过 MCP 只能看到消息,看不到data:"from" at index 1 not found, nothing written: <from>. Nearest match: <kind> at line 227, column 13 — <snippet> — <hint>. Pairs before it: 1 (counts: 3).(index从 0 开始;REST 调用方也可以在data.failed_index、data.results、data.suggestion中获取相同字段)。服务端按以下顺序检查这些类型:whitespace_only— 精确字节仅在你的作用范围内存在空白差异 — 保留scope,用ignore_whitespace: true重试同一调用。outside_scope— 字面内容只存在于与scope.text所选元素不同的元素中 — 从该其他元素中选择scope.text(或scope.occurrence);不要移除scope来匹配,否则会重写错误的元素(如果目标确实是作用范围内的元素,请通过限定范围的grep-content重新检查其精确字节 — 差异可能来自空白或属性顺序)。partial指向from中最长的词元。none— 字段或作用范围内不存在锚点;请检查post_id/field。切勿回退到文档的本地副本来“查看字节” — 建议片段和设置max_length: 1000的限定范围grep-content会提供准确的行内容。 - 错误:
scope_not_found、scope_no_element(422,不写入任何内容)、invalid_regex(模式无法编译)、regex_error(422,不写入任何内容 — 模式已编译,但在运行regex或ignore_whitespace对时,PCRE 引擎触发了回溯 / JIT 限制;请简化模式,未应用任何重写)、invalid_input(同时使用 regex 和 ignore_whitespace)。 - 适用场景: 对现有大型页面进行定向编辑(例如修复损坏的资源 URL)— 一次小调用即可,无需先
read-meta再完整执行一轮write-meta。会保留对斜杠敏感的内容(例如转义后的 Tailwind 选择器.md\:flex);编辑html/css/js会像write-meta一样,将布局的 Tailwind 构建标记为过期。
wpcanai-create-template
- 参数:
{ "title": string, "type": string, "html"?: string, "css"?: string, "js"?: string, "layout"?: int }— 必须提供title和type(template_type slug)。 - 返回:
{ "post_id": int, "slug": string }。 - 类型化 CPT 模板(v1.24+)。 已发布的
wpcanai_template若其template_type词条为single-<post_type>或archive-<post_type>,就会接管该 CPT 在前端的单篇内容或归档页面渲染——例如,创建一个类型为single-service的模板,即可接管service详情页;创建archive-service则可接管其归档页面。single-post(自 1.60.0 起预置)对在 WordPress 编辑器中编写的博客文章也执行相同操作:在prose容器中使用{{ the_content(post.post_content) }},类型化的single-*接管模板可使用post.featured_image.*,自 1.68.15 起还可使用post.author.*。仅在模板存在时生效:若不存在此类模板,CanAI 的输出会与主题完全一致。页面仍使用自身的元数据路径;WooCommerce 产品仍使用 WC 区块。
wpcanai-resolve-content-id
- 参数:
{ "type": string }—type为必填项。接受基础类型(cart、checkout、my-account、shop、product-category、product、product-loop、404、search、category、tag、author、archive)、10 种 WooCommerce 端点类型(order-received、order-pay、add-payment-method、orders、view-order、downloads、edit-account、edit-address、payment-methods、lost-password),以及通过wpcanai_template_types过滤器注册的类型 (v1.50.0)。 - 已注册类型须知。 枚举会在能力注册时构建,触发时机为
wp_abilities_api_init(由init优先级 1 触发)。在文件作用域添加的wpcanai_template_types过滤器——这是文档所述的方式,也是解析器自身文档展示的方式——届时已生效,因此其类型会被接受。在默认优先级 10 的init回调内添加的过滤器尚未注册,因此即使解析器能够正常解析该类型,架构仍会拒绝它。请在文件作用域注册。 - 返回:
{ "content_post_id": int, "content_post_type": string, "rendering_mode": string, "template_post_id": int|null, "unreachable_post_id": int, "resolution_reason": string }。 - (v1.50.0)
content_post_id是实际渲染内容的文章。 委托正文:当前使用布局包装器,因此由 WooCommerce 页面提供正文——content_post_id为该页面,rendering_mode为page-rendered。模板正文:该类型的模板不是布局包装器,因此由模板自行渲染整个页面——content_post_id为模板,rendering_mode为template-rendered;委托页面的_canai_html(如果存在)属于未使用内容,并会通过unreachable_post_id报告。resolution_reason会说明适用的是哪种情况。请编辑content_post_id;在 v1.50.0 之前,即使由模板渲染,也会返回委托页面,因此修改可能落在永远不会渲染的元数据上。 rendering_mode有五种取值——page-rendered(委托正文)、template-rendered(模板正文)、(v1.50.1)blank和none,以及 (v1.58.0)page-rendered-unwrapped。Blank = 布局指针指向的文章已不存在,且委托页面没有自己的_canai_html,因此不会渲染任何 CanAI 专属内容;请修复失效的指针(wpcanai-scan会将其报告为broken_layout警告)。Page-rendered-unwrapped(v1.58.0)= 布局指针已失效,但委托页面确实包含自己的_canai_html——CanAI 会回退为直接渲染该内容,不带布局的包装器或界面元素,因此不会丢失内容;content_post_id/content_post_type与page-rendered一样,会指向委托页面。失效的指针仍值得修复(wpcanai-scan会以相同的broken_layout警告报告)。None = CanAI 完全不渲染此类型。blank和none的content_post_id均为0,content_post_type均为"",且resolution_reason会说明具体是哪种情况。在 v1.50.1 之前,两者都会报告为template-rendered/wpcanai_template,指向一个并不存在的渲染器——除非content_post_id非零,否则不要将这两个字段视为有效信息。在 v1.58.0 之前,即使委托页面自身的内容实际上以无包装器的方式渲染,失效的布局指针也总是被报告为blank。- (v1.50.1)端点类型会继承其父类型的模板。 只有当存在已发布的该类型模板时,WooCommerce 端点才会接管请求;否则,请求会通过父类型渲染(
order-received/order-pay对应checkout,其他八种对应my-account)。resolve-content-id也遵循这一逻辑,因此没有自身模板的端点在模板正文模式下会报告父类型的模板,而不是永远不会渲染的委托页面。在 v1.50.1 之前,它会报告该页面,这与wpcanai-scan对同一文章发出的unreachable_content警告相矛盾。
wpcanai-scan
-
参数:
{ }— 无参数。 -
返回: 包含以下发现的
array:severity、type、message,以及可选的post_id。 -
发现类型(v1.50.0)。 WooCommerce 区块(五种基础结构类型
shop、product-category、cart、checkout、my-account)会根据网站实际所处的形态验证每种类型——两种形态都不算错误:严重性 类型 含义 warning unreachable_content无论哪种形态,已编写的 _canai_html都无法渲染。模板正文:WooCommerce 页面仍带有正文,而模板负责渲染。(v1.50.1)委托正文:类型模板被页面自己的_canai_layout取代,且页面仍带有正文。会列出无法显示的文章和实际生效的文章。warning broken_layout(v1.58.0) 布局指针引用的文章已不存在。CanAI 会将其解析为“无布局”,并回退到直接渲染委托页面自己的 _canai_html,不进行包装(没有布局外观)——如果委托页面有正文,就不会显示空白;如果没有正文,则不会渲染任何 CanAI 专属内容。无论哪种情况,都应修复或清除悬空指针。在 v1.58.0 之前,此问题的严重性为critical,当时的假设是页面始终会显示空白;该假设现已修正。warning delegate_mismatch模板声明的 _canai_delegate_page_id本身已失效——文章已不存在,或不是page。无论页面形态如何,都会单独检查:声明的覆盖项失效本身就可能导致解析后的形态不是委托正文。请修复声明的覆盖项;不要将其改指向当前解析出的 ID——该 ID 是根据同一个声明值推导出的,比较两者永远无法发现实际问题。warning empty_delegate_page委托正文,且此类型存在 wpcanai_template:本应存放正文的页面没有_canai_html。有人有意配置了此类型,因此空页面确实是一个缺漏。warning duplicate_type_template多个已发布模板带有相同的 template_type术语——只有第一个会渲染,其余模板均不起作用。模板不按语言区分(每种类型只有一个模板,供所有语言共用),因此曾为每种语言各保留一份副本的网站会在此显示这些副本——请将多余副本移至回收站,并改用t()翻译字符串。ok template_body_ok模板正文:模板会直接渲染此类型。与下方通用扫描自身的 template_ok不同——虽然名称相似,含义却不同。ok delegate_ok委托正文:页面包含内容及其解析后的布局。 info no_template/wc_page此类型尚无任何已编写内容——可能是委托正文但完全没有 wpcanai_template(WC 页面会在网站布局的包装下渲染;新安装时很常见,并非配置错误),也可能是形态为none(无法解析出任何 WC 页面)。info leaky_comment(html)_canai_html仍包含 `` 标记,会出现在 view-source 中。请将分区标签转换为 Twig{# #}。warning leaky_comment(js)_canai_js中有一个/* */块,看起来像是在暴露基础设施信息(snippet/webhook/internal/…)。请使用{# #}——_canai_js会由 Twig 渲染。warning leaky_secret_canai_html/_canai_js/_canai_css中有符合秘密信息特征的字面值(API 密钥、Bearer 令牌、私钥 PEM 等)。部署前请从元数据中移除。error twig_syntax_error无法将 _canai_html、_canai_css或_canai_js解析为 Twig。此发现包含post_id、field、line和解析器消息;在信任任何其他看似正常的结构类发现之前,请先修复此问题。通用扫描(所有包含 _canai_html的文章,以及所有已发布的wpcanai_template,类型为product/404/search/category/tag/author/archive)会生成自己的发现项:no_layout、no_content、no_layout_tpl和template_ok(模板同时包含_canai_html和_canai_layout)。不要将此处的template_ok与上文 WC 区块的template_body_ok混淆——两者可能同时出现在一次扫描响应中,但含义不同。如果 WC 区块已将该文章确切报告为unreachable_content,则no_layout会从warning降级为info,并交叉引用unreachable_content(v1.50.0 最终审查修复):该文章的_canai_html已因模板正文结构生效而无法访问,给它指定_canai_layout会切换结构,改为委托正文,并取代当前正在渲染的模板,从而改变线上 URL 显示的内容。对于以这种方式标记的文章,在确认用户想要更改结构之前,切勿建议为其指定布局。 -
导航发现项(插件 v1.88.0)。 按模板或页面报告,并包含匹配项所在的
line;从 v1.88.0 起,内容扫描也会覆盖wpcanai_template文章,因此也会检查页眉、页脚和布局。hardcoded_lang_prefix和unlocalised_internal_href每篇文章各报告一项,消息中会列出所有行号。它们会对 Menus 和turl()执行检查:严重程度 类型 触发条件 修复方法 warning hardcoded_nav<nav>中,或<header>/<footer>内的<ul>/<ol>中,包含 2 个或更多<a href>,链接目标为固定值且没有get_menu((页面正文其他位置的链接列表、由item.url/post.url等数据生成的链接,以及标记为面包屑 / 分页 / 目录的导航均不会触发)使用 wpcanai-write-menu(primary或footer)编写链接,并在该处渲染get_menu()warning legacy_menu_locationget_menu()使用了 1.88 之前的 location 名称使用 replace-in-meta将其重命名为get_menu('primary')/get_menu('footer')(参见 Menus 下的升级说明);旧名称仍可渲染warning unknown_menu_locationget_menu()使用了其他名称使用 primary或footer——CanAI 没有其他 locationinfo menu_fallback_chain对 get_menu()的结果使用is empty/is not empty进行测试,并在同一代码块中再次调用get_menu();或{% else %}分支中包含遍历get_menu()结果的链接仅使用一个 location,不设回退:删除第二次调用或 {% else %}分支,并使用write-menu分配菜单warning menu_children_twice(v1.89.0)遍历 get_menu()(或从其设置的变量),且没有filter(,循环体中读取了项目的.children遍历 get_menu('primary')|filter(i => i.depth == 0):扁平列表已经包含所有子项目info menu_not_assigned调用 get_menu('primary')/get_menu('footer')时,此站点的该 location 尚未设置菜单wpcanai-write-menu { "location": …, "items": […] }会创建并分配菜单info hardcoded_lang_prefix手动构建语言前缀: current_lang() == 'ms' ? '/ms'、在href开头使用current_lang()设置 URL 变量,或为已配置语言使用字面值href="/ms/…"使用 {{ turl('/path/') }}——它会自行应用当前语言;get_menu()的 URL 已包含当前语言info unlocalised_internal_href在包含多种语言的站点上,使用字面值 href="/…"或href="{{ home_url(…) }}"使用 turl()包裹链接,使其遵循当前语言 -
渲染警告(插件 v1.90.0)。
render_warning(严重程度为warning,包含post_id、field、line)不会从内容中读取:CanAI 会在页面渲染时记录此警告,此时模板自身的 Twig 引发了 PHP 警告——几乎总是 Array to string conversion,即{{ }}输出了列表。消息会指出行号及其来源、修复方法和最近出现的位置:输出一个键({{ item.title }})、遍历列表,或对其使用|join(', ')。对于直接输出{{ items }}的情况,line为 null(Twig 无法判断具体是哪一个):请找到值为列表的{{ }}。使用read-meta(fields+lines)和replace-in-meta修复。写入字段会清除此字段的条目;只有页面再次渲染时才会重新出现,因此写入后列表为空并不能证明问题已解决——请让用户重新加载页面,然后再次扫描。保留 7 天。同样的条目也会出现在read-meta的fields: ["render_warnings"]和wpcanai-diagnostics的render组中。 -
链接问题(插件 v1.97.0)。 从每篇文章的
_canai_html中读取,并忽略 Twig 注释(对于普通href,也会忽略 HTML 注释;但 `` 中的{{ }}仍会渲染,因此仍会计入);严重程度为warning,包含post_id、field和line;每个slug_url()字面值或每个链接路径对应一条问题,其消息会列出所有行号:类型 触发条件 修复方法 link_unresolved字面量 slug_url('x')或slug_url('x', 'type')未匹配到任何文章,或匹配到多个文章(不同父级下有相同 slug)——无论哪种情况,都会渲染为href=""。多个匹配项会列为path (ID n);页面已移动的路径会注明它现在的位置传入路径( slug_url('gallery/theatrettes')),或使用turl(id_url(<id>));如果没有匹配项,请检查 sluglink_moved字面量 turl('/a/b/')或纯文本href="/a/b/"的路径既不是页面也不是文章,而其最后一段是另一条路径上恰好一个已发布页面的 slug——页面移动后,旧链接遗留了下来使用消息中指定的 turl(id_url(N))替换链接,并使用replace-in-metalink_moved只报告能够确证已移动的页面。以下情况不会报告:路径指向完全不存在的内容(拼写错误);存档、术语、商店或账户路径;最后一段被多个子页面共用的路径——请对此类路径进行 grep(参见wpcanai-update-page);在子目录中安装的网站上,位于该目录之外的纯文本href(网站位于/blog/时的/about/)。会先像路由器处理请求那样去掉前导的/<lang>/:只去掉非默认语言的前缀,因此在默认语言为英语的网站上,/en/about/会按原样检查(插件 v1.97.1)。(v1.97.1) 不会读取内联<script>/<style>中的纯文本href。 -
在 v1.50.0 中移除:
missing_delegate和stale_template_html。这两项检查都断言模板必须是委托给页面的空标记——这只是两种有效形式之一,并非规则。它们会在正常运行的网站上触发,而且其指导意见(“应为空”)会导致渲染内容被删除。请勿重新引入此检查。
wpcanai-grep-content
- 参数:
{ "pattern": string, "regex"?: bool, "fields"?: string[], "limit"?: int, "post_id"?: int, "max_length"?: int, "context_lines"?: int }——pattern为必填项。默认按字面子字符串匹配;regex: true会将pattern视为 PCRE。fields是html|css|js的子集(默认包含全部三项)。limit为 1–100(默认值为 100)。(v1.71.0)post_id将搜索范围限定为一篇文章(跳过全站扫描——知道页面时务必传入)。max_length(1–2000,默认值为 200)限制代码片段的长度;context_lines(0–10)用于添加周围的行。输入无效时返回错误invalid_regex/invalid_input。 - 返回:
{ "matches": [{ "post_id", "field", "line", "column", "line_length", "match", "before"?, "after"? }], "truncated": bool }——match是以匹配位置为中心、长度为max_length字节的窗口(截断处以…标记);当line_length ≤ max_length时,它是完整的原始行,逐字节精确,可直接粘贴到replace-in-meta中作为from。密集的 Tailwind 行长达 300–400 字节:需要锚点时,请传入max_length: 1000。 - 适用场景: 全站扫描(泄漏的注释、损坏的资源 URL、重命名类名);如果逐篇列出文章并手动执行
read-meta无法扩展,就使用此工具。根据返回的匹配项,使用wpcanai-replace-in-meta执行修复。
wpcanai-get-wc-page-ids
- 参数:
{ }——无参数。 - 返回:
{ "cart": int, "checkout": int, "myaccount": int, "shop": int }(WooCommerce 页面 ID;未设置时为0)。
wpcanai-get-wc-css-reference
- 参数:
{ "context": string, "include_core"?: boolean }—context是以下之一:cart、checkout、order-received、order-pay、myaccount-dashboard、myaccount-orders、myaccount-view-order、myaccount-downloads、myaccount-edit-account、myaccount-edit-address、myaccount-payment-methods、myaccount-add-payment-method、myaccount-lost-password。include_core(默认为false)会将third_party_hooks扩展为该上下文钩子上的所有已注册回调(包括 WooCommerce 核心回调),每个回调仍带有自己的is_third_party标记。没有lang参数。 - 返回值:
css_reference(字符串 — 原始 SCSS 源码,而非编译后的 CSS;Sass 变量/混入尚未解析,因此请改用转述,不要直接粘贴。WooCommerce 针对该上下文的默认规则取自已安装 WooCommerce 的woocommerce.scss、woocommerce-layout.scss、woocommerce-smallscreen.scss和forms.scss,并附带完整的祖先选择器链,以便匹配优先级;smallscreen 规则会包装在@media only screen and (max-width: 768px)中)、css_fallback(null,或当选择器提取未找到匹配项、而是返回拼接后的完整样式表时为"full-file")、template_files(字符串数组,相对于 ABSPATH — 权威的标记路径,主题覆盖优先)、templates_missing(字符串数组,相对于上下文的预期模板中无法读取的路径 — 不可与template_files直接比较;非空表示上下文映射已与已安装的 WooCommerce 不一致)、hooks_found(字符串数组 — 模板触发的操作)、third_party_hooks(对象{hook, priority, callback, file, is_third_party}— 仅非核心/主题/CanAI 回调;当include_core为true时则包含所有回调)、context_echo({context, include_core})。 - 只读;仅静态分析。 不会渲染任何内容;不会读取订单/购物车/客户数据。有关如何使用输出,请参阅上文“为 WooCommerce 区块添加样式”工作流部分。
wpcanai-create-page
- 参数:
{ "title": string, "slug"?: string, "status"?: string, "html"?: string, "css"?: string, "js"?: string, "layout"?: int, "parent"?: int, "menu_order"?: int }—title为必填项;创建一个page文章,并可选添加_canai_*元数据。status默认为publish。 - (v1.96.0)
parent/menu_order。parent是用于将新页面嵌套在其下的现有页面 ID:其 URL 会变为/<parent path>/<slug>/,而slug仅是最后一段(auditorium,绝不能是gallery/auditorium)。默认值0表示顶层页面。menu_order是“页面属性”→“顺序”(默认为0)。两者都必须是整数:非整数会被工具的输入架构拒绝(ability_invalid_input,例如 input[parent] is not of type integer.),且不会写入任何内容。不存在、已移至回收站或不是页面的parent会导致invalid_parent,同样不会写入任何内容。参见规则 4 下的嵌套页面。 - 返回值:
{ "post_id": int, "slug": string, "format": string, "warnings": string[], "status": string, "parent": int, "path": string, "url": string }(status…url自 v1.96.0 起提供)。path不带开头或结尾的斜杠(gallery/auditorium);保存为草稿且未提供 slug 时,值为''。使用页面 ID(turl(id_url(<id>)))或对返回的slug/path使用slug_url()链接到新页面 — 绝不要将url或自行构建的路径粘贴到模板中。 - 警告(v1.96.0,不会导致操作失败): slug 已在同一父级下使用或已被保留,因此 WordPress 保存了带后缀的 slug(
auditorium-2)— 链接前请重新读取slug/path;父页面尚未发布 — 访客无法打开该页面,但新页面的 URL 仍会经过它。草稿会保留你提交的 slug;发布页面时,WordPress 会对其去重。
wpcanai-update-page(插件 v1.96.0)
通过 MCP 更新页面的标题、slug、状态、父级和顺序 — 对应区块编辑器的“摘要”和“页面属性”面板。
- 参数:
{ "post_id": int, "title"?: string, "slug"?: string, "status"?: "draft"|"publish"|"pending"|"private"|"future", "parent"?: int, "menu_order"?: int }— 必须提供post_id和至少一个其他字段;只会更改你发送的字段。parent: 0会将页面移至顶层。slug仅指 URL 的最后一段。使用wpcanai-list-pages { "all_pages": true }(v1.97.0)查找post_id,其中包含普通页面和草稿页面。 - (v1.97.0)
future可用,因此可以重新发送从定时发布页面取得的previous;此工具不接收日期。只有当页面的日期在未来时,WordPress 才会保留页面的定时发布状态;否则会立即发布。如果草稿或待审核页面从未设置日期,则会使用当前时间,因此future会将其发布。相同规则也会将日期在未来的页面的publish转为future。响应中的status是页面最终采用的状态。 - 绝不触碰正文。
post_content和所有_canai_*元数据都会保持不变,因此它对 Twig、区块和普通 WordPress 页面都同样适用。重命名、更改 slug、发布、取消发布、移动或重新排序页面时,请使用此工具,而不是write-meta/write-page。 - **错误 — 所有错误都会在写入任何内容前被拒绝。**非整数(以及未知状态)会被工具的输入架构拒绝(
ability_invalid_input,例如 input[parent] is not of type integer.)。之后可能出现:not_a_page(ID 不存在、已移入回收站或不是page;博客文章请使用wpcanai-write-post)、invalid_input(除post_id外没有其他字段、title或slug为空)、invalid_parent(父级不存在、已移入回收站、不是页面或就是该页面本身)、parent_loop(409 — 请求的父级位于该页面之下;请先将其移出。WordPress 自身会静默地将父级重置为顶层,并报告成功)。 - 返回:
{ "post_id", "title", "slug", "status", "parent", "menu_order", "path", "url", "edit_url", "previous": { "title", "slug", "status", "parent", "menu_order", "url" }, "warnings": string[], "purged" }。previous保存调用前的值;如果页面没有 slug(即没有 slug 的草稿),则省略其中的slug;**(v1.97.0)**如果页面没有标题(或标题经清理后为空,例如只有标签或空格),则省略其中的title。 - 撤销 = 重新发送
previous。{ "post_id": <id>, …previous }会恢复标题、slug、状态、父级和顺序;额外的url键会被忽略。快照只保存_canai_*元数据,WordPress 修订记录最多记录标题——不会记录 slug、状态、父级或顺序——因此previous是唯一的撤销方式,请保留它,直到用户确认满意。previous.status为future时,重新发送的值保持不变(见上文的future)。在 1.96.x 中,previous可能带有空的title和future状态,重新发送其中任一项都会被拒绝:请将它们省略,并让站点所有者在编辑器中重新安排定时发布页面。 - 警告(非致命):
- URL 已更改 — 已发布(或私有)页面获得了新的 slug 或父级。警告会说明有多少子页面随之移动,并指出菜单、
id_url()和不带路径的slug_url()链接会跟随页面,但slug_url()路径链接和硬编码路径不会。WordPress 不会将页面旧 URL 重定向到新 URL(有些站点会通过核心的 404 猜测进行重定向;请勿依赖此行为),因此请按警告中的说明查找这些链接:使用wpcanai-grep-content搜索不带斜杠的旧路径(gallery/auditorium),这会同时匹配turl('/gallery/auditorium/')、href="/gallery/auditorium/"和slug_url('gallery/auditorium')(移动后会渲染为href="");使用wpcanai-replace-in-meta修复每一处命中。**(v1.97.0)**随后wpcanai-scan会将残留链接报告为link_moved/link_unresolved(参见wpcanai-scan下的 链接发现项)。 - 已取消发布 — 已发布(或私有)页面现在处于
draft/pending状态:访客无法再通过其旧 URL 打开页面。 - Slug 已添加后缀 — WordPress 保存了不同的 slug(
x-2):已有同级页面使用该 slug,或该 slug 已被保留。即使你没有发送slug,也可能触发此警告,因为核心会在每次保存已发布页面时,以及发布草稿时,重新检查页面的 slug。 - 父级未发布 — 与
wpcanai-create-page中的情况相同。
- URL 已更改 — 已发布(或私有)页面获得了新的 slug 或父级。警告会说明有多少子页面随之移动,并指出菜单、
- **缓存。如果页面更改 slug 或父级前后都处于已发布状态,则会清除整个页面缓存(
purged.scope: "all"):所有缓存的菜单和子页面链接都会指向旧 URL。更改标题会清除该页面的缓存。(v1.97.1)**同一规则适用于所有写入工具(wp-admin、write-post):只有分层类型(页面)或菜单中的文章在 URL 移动时才会清除所有缓存;其他文章只会清除自身缓存。
wpcanai-write-post
- 参数:
{ "post_id"?: int, "title"?: string, "blocks"?: object[], "slug"?: string, "status"?: string, "excerpt"?: string, "date"?: string, "featured_image"?: int, "categories"?: string[], "tags"?: string[] }。- 创建(不提供
post_id):必须提供title和blocks。更新(提供post_id):只更改你发送的字段;该 ID 必须对应一篇文章,不能是页面 — 页面 ID 会返回WP_Error('not_a_post')。 - 默认状态为
draft,而不是publish— 与wpcanai-create-page不同。预期所有者会先在编辑器中阅读文案。有效值:draft、publish、pending、private、future。 blocks会替换整个正文。不支持部分编辑;请重新发送完整列表。WordPress 修订版本可用于撤销。categories/tags接受名称或别名;不存在的项目会自动创建,更新时会替换整个集合。
- 创建(不提供
- 返回值:
{ "post_id": int, "slug": string, "status": string, "url": string, "edit_url": string, "block_count": int, "warnings": string[] }。warnings表示非致命问题(无法解析的嵌入提供方、没有替代文本的图片、已转换的经典编辑器正文)。致命问题会返回WP_Error,且不会写入任何内容。 - 此工具仅用于博客文章。 页面仍使用
wpcanai-create-page+wpcanai-write-meta(CanAI HTML/CSS/JS 元数据)。write-post会将原生区块标记写入文章的post_content,不会修改 CanAI 元数据。
区块类型。 blocks 中的每一项都采用 { "type": …, …fields } 格式:
type | 字段 |
|---|---|
paragraph | text(必填)、align(left/center/right) |
heading | text(必填)、level 2–6(默认为 2;不接受 level 1 — 文章标题即为 h1) |
list | items string[](必填)、ordered bool。仅支持扁平列表 |
quote | text 或 paragraphs string[](必填其一)、cite |
image | id int(必填 — 附件 ID)、alt、caption、size(默认为 large)、link(none/media/attachment) |
code | code(必填)。转义为纯文本;不支持内嵌 HTML |
separator | — |
embed | url(必填,http/https) |
table | rows string[][](必填,行列规则)、header string[] |
group | blocks(必填,子区块)、tag(div/section/header/footer/aside/main)、className |
columns | columns(必填,1–6 项,每项为 { width?, className?, blocks })、className |
buttons | buttons(必填,{ text, url, style?: fill/outline, target?: _blank, rel?, className? })、className |
cover | image(必填,附件 ID)、overlay_opacity(10–100,步长为 10,默认为 50)、blocks(必填)、className |
media_text | image(必填,附件 ID)、side(left/right)、blocks(必填)、className |
spacer | height px(1–1000,默认为 100)、className |
chart | chart(必填:bar/horizontal-bar/line/area/pie/doughnut/scatter/radar)、labels string[](必填,scatter 除外 — 每个数据点对应一个)、datasets(必填,1–8 项,每项为 { label, data };data 为有限数值,间隔处使用 null,且每个标签必须恰好对应一个数据值;对于 scatter,格式为 [{ x, y }],labels 会被忽略并显示警告;pie/doughnut 恰好接受一个数据集)、title、caption、unit(刻度/工具提示/表格的后缀,例如 ms、%)、stacked bool、y_min / y_max number、height px(160–800,默认为 320)、source { text?, url? }(呈现为 "Source: …")、table(默认为 collapsed / open / hidden)、className。字符串均为纯文本(会剥除内嵌 HTML)。序列化后的 labels + datasets 大小上限为 64 KB。插件版本为 v1.77.0;wpcanai-write-page 的区块页面也接受此类型 |
除 embed 外,每种类型都接受 className(应用于区块根元素的 Tailwind 类);embed 会在首次保存时重写自己的类列表。嵌套深度上限为 4 层。嵌套错误会报告以点分隔的 path(例如 "2.blocks.0")。
图片:先上传,再传入 ID。 write-post 只接受附件 ID——不接受 URL 或二进制数据(cover 和 media_text 接受相同的 ID,如果无法解析该 ID,则返回 invalid_attachment)。调用 wpcanai-sideload-url(或使用 wpcanai-list-media 查找现有附件),并传入返回的 id。
文本字段中的内嵌 HTML 仅限于 <a href|title|rel|target>、<strong>、<em>、<b>、<i>、<code>、<br>、<s>、<sub>、<sup>、<kbd>、<mark>。其他内容都会被静默剥除,而不会被拒绝——段落中的 <script> 或 <iframe> 本来也会构成无效的区块内容。不接受 Markdown,且 write-post 没有原始 HTML 转义通道。
wpcanai-get-menus(插件 v1.88.0)
- 参数:
{ }。 - 返回:
{ "locations": { "primary": { "menu_id", "name", "items" } | null, "footer": { … } | null }, "languages": { "<lang>": { "primary": { "menu_id", "name", "items" } | null, "footer": … } }, "menus": [{ "id", "name", "slug", "assigned_to": ["primary"], "languages": { "<lang>": ["footer"] } }], "legacy_assignments": { "<old location>": <menu id> }, "translations": { "<lang>": { "translated", "total" } } }。条目为{ "id", "title", "type", "object", "object_id", "url", "target", "classes", "description", "attr_title", "icon", "image", "children" },未经翻译的原始内容(标题的url: "")。(v1.89.0)description/attr_title/icon/image(附件 ID;没有时为0);languages包含所有非默认语言,对于使用已翻译默认菜单的位置,其值为null;menus[].languages列出语言分配情况。translations按非默认语言统计已翻译的条目标题和描述数量——仅针对该语言未提供自有菜单的位置。legacy_assignments列出仍以 1.88 之前的位置名称分配的菜单(参见 菜单 下的升级说明)。 - 调用
write-menu前先调用此工具,以查看各位置当前使用的菜单。
wpcanai-write-menu(插件 v1.88.0)
- 参数:
{ "location": "primary"|"footer", "items": [{ "title": string, "page"?: int|string, "url"?: string, "target"?: ""|"_blank", "classes"?: string, "description"?: string, "attr_title"?: string, "icon"?: string, "image"?: int, "children"?: [ …same shape ] }], "merge"?: bool, "lang"?: string }—location和items为必填项。每个项目都必须有非空的title,并且在page(任意公开文章类型的文章 ID 或别名 → 页面项目会在其别名更改后继续指向该页面)和url(自定义链接,可为站点相对路径或绝对地址)中恰好选择一项——标题除外:带有children的项目可以两者都不填(它会显示为标签,url: "")。嵌套最多 3 层。 - (v1.96.0)按别名设置
page。 首先匹配精确路径(gallery/auditorium),然后匹配仅有一个页面与之完全一致的别名,因此子页面会按自身别名解析,并在移动后仍能继续解析。顶级页面优先于与其同名的别名(从插件 v1.97.1 起,已发布页面也优先于同名草稿)。(v1.97.0) 多个子页面共用的别名会返回ambiguous_page:消息会列出每个页面的path (ID n)——请传入路径或 ID。此时不会再转而查找其他文章类型:只有在没有页面使用该别名时,才会在其他公开文章类型中查找(其中有多个匹配文章时也会返回ambiguous_page);若均无匹配,则返回unknown_page。同名的未附加图片不再会遮蔽页面。 - (v1.89.0)富文本字段。
description(单行)、attr_title、icon(符合^[a-z0-9-]{0,64}$的 Lucide 名称)、image(图片附件 ID)。使用merge时,未提供的字段会保留原值;""/0会清空字段。 - (v1.89.0)
lang— 非默认语言的别名:为该语言设置此位置专属菜单,而非使用默认菜单(参见 按语言设置菜单)。首次调用会创建Primary (<native name>)/Footer (<native name>)(会复用同名菜单),并将其分配给该语言;默认位置不受影响,结果中会带有lang。默认或未知别名,或站点不支持原生翻译 →invalid_lang。 - (v1.93.0)
copy_default: true与lang搭配使用(不传items),可为该语言创建此位置专属菜单,作为默认菜单的副本。标签(标题、描述)会采用已存储的译文(如有);页面和文章项目仍关联其文章;其他项目会变为指向当前 URL 的自定义链接。如果该语言在此位置已有菜单,则返回menu_exists(数据:menu_id);该位置没有默认菜单时返回no_default_menu;未提供lang时返回invalid_input。其他所有调用都必须提供items。 - 默认替换。 此位置的项目会变为与
items完全一致,并按给定顺序和嵌套排列;items: []会清空菜单。merge: true会保留现有项目,更新同一父级下title相同的项目,并追加其余项目。 - 每个位置仅有一个菜单。 写入操作会更新此位置已分配的菜单,且不会重命名。只有在某个位置尚无菜单时,才会创建新菜单,命名为
Primary/Footer并立即分配。没有删除工具:菜单由所有者在 wp-admin 中删除。 - 整个树在写入前都会经过验证。
location不是primary/footer时,输入验证会拒绝请求。可能的错误包括:unknown_page(page未解析到任何页面——请先创建页面)、ambiguous_page(v1.97.0——多个页面共用同一别名;请传入路径或 ID)、invalid_item(缺少title、同时提供page和url、无children的项目两者都未提供、target不是""/"_blank"、icon无效、image不是图片附件)、too_deep(超过 3 层)。出现任何错误时,菜单都不会改变。 - 返回:
get-menus报告的该位置状态(menu_id、name、items),以及written(写入的项目数)和purged。菜单会显示在每个页面上,因此写入操作会清除整个页面缓存(参见 工具标签)。
wpcanai-assign-menu(插件 v1.89.0)
- 参数:
{ "location": "primary"|"footer", "lang"?: string, "menu_id": int|null }— 将一个位置(或指定lang时,将某种语言的位置)指向一个现有菜单;wpcanai-get-menus会列出菜单 ID。此操作不会写入菜单项:请改用write-menu。 menu_id: null会清除分配:某种语言会回退到翻译后的默认菜单(其自身菜单会保留,但不再分配);不指定lang时,该位置将不再关联任何菜单(其 pre-1.88 别名条目也会被清除),之后wpcanai-scan会在所有渲染该位置的地方报告menu_not_assigned。- 错误:
unknown_menu(该 ID 不是导航菜单)、invalid_lang(默认语言或未知语言代码)。返回值{ "location", "lang" (位置本身时为 null), "menu_id", "name", "items", "purged" }— 刚刚完成的分配:指定lang时,为该语言自身的菜单(清除后menu_id: 0,但该语言仍显示翻译后的默认菜单);不指定lang时,为该位置的菜单。 - 所有者对应的操作入口分别是外观 → 菜单 → 管理位置(默认菜单)和 CanAI → 翻译 → 菜单(按语言)。
wpcanai-read-settings
- 参数:
{ "keys"?: string[] }— 省略keys时读取所有允许列表中的选项。 - 返回值: 选项键 → 值的对象。
- 允许列表中的键:
show_on_front、page_on_front、page_for_posts、blogname、blogdescription、users_can_register、wpcanai_default_layout、wpcanai_tailwind_settings(对象:load_tailwind为yes|no,source为cdn|plugin(自 v1.58.2 起默认为plugin),plugins为字符串数组)、wpcanai_chart_settings(对象,插件 v1.77.0:load_chart为auto|yes|no(默认为auto— 仅在渲染图表的请求中加载 Chart.js;yes会在每个 CanAI 页面中加载;no则从不加载,图表仅显示其表格),source为plugin|cdn(默认为plugin);部分对象会与已存储对象合并,未知值会采用默认值)、wpcanai_markdown_negotiation("1"|"0",参见 面向 Agent 的 Markdown)、wpcanai_purge_after_write("1"|"0",插件 v1.80.0,默认开启 — CanAI 写入后清除页面缓存)、woocommerce_enable_signup_and_login_from_checkout、woocommerce_enable_myaccount_registration、woocommerce_cart_page_id、woocommerce_checkout_page_id、woocommerce_myaccount_page_id、woocommerce_shop_page_id。
wpcanai-update-settings
- 参数:
{ "settings": { "<key>": <value>, ... } }— 仅限允许列表中的键(与wpcanai-read-settings相同);设置page_on_front时,在适当情况下也会将show_on_front设为"page"。 - 返回值:
{ "success": bool, "updated": string[], "rejected": [{ "key", "reason" }] }— (v1.80.0)reason为not_whitelisted、invalid_type或invalid_value。没有任何设置成功更新时,调用会失败,错误消息会列出所有被拒绝的键;若有部分键成功写入,success为true,其余键会列在rejected中 — 请务必读取该字段。 - (v1.80.0)布尔值。 开/关设置(
users_can_register、wpcanai_markdown_negotiation、wpcanai_purge_after_write、WooCommerce 注册开关)接受true/false、1/0、"1"/"0"、"yes"/"no"。字符串"false"在此工具及其他所有工具的布尔输入中都表示 false。 - 读取端仍采用宽松处理。 若
read-settings仅收到不在允许列表中的键,则会返回完整的允许列表(不会报错)——返回了数据并不能证明你的键存在。请检查写入操作的rejected。
wpcanai-setup
- 参数:
{ }。 - 返回值:
{ "success": bool, "steps": array }— 每个步骤包含:action、status(done|skipped|error)、detail(建议的引导配置:主题、布局、页眉、页脚、主页、首页选项、默认布局、Tailwind 设置)。
wpcanai-get-option
- 参数:
{ "name": string }—wp_options.option_name。 - 返回:
{ "name": string, "value": mixed }。 - 错误(v1.89.3):
option_not_readable会说明选项名称和原因。……位于 MCP 拒绝列表中,无法通过 MCP 读取 — 请停止;这是有意屏蔽的,wpcanai-read-settings涵盖 CanAI 支持的设置。……不在 MCP 读取允许列表中 — 告知用户需要在 CanAI → AI Client → Guardrails 下添加的确切名称,并在用户确认后重试一次;不要不断猜测。 - 策略: 名称必须位于 WP Admin → CanAI → AI Client → Guardrails 中的读取允许列表。拒绝列表会屏蔽危险键(例如
active_plugins、cron)。以wpcanai_mcp_开头的名称始终会被屏蔽(MCP 内部项)。如果在wp-config.php中定义了WPCANAI_MCP_OPTIONS_UNRESTRICTED,则可以读取其他任何未被拒绝的名称。
wpcanai-update-options
- 参数:
{ "options": { "<option_name>": <value>, ... } }— 仅限 JSON 安全的标量和数组。 - 返回(已应用):
{ "success": true, "status": "applied", "updated": string[], "pending_id": null }。 - 返回(已排队):
{ "success": true, "status": "pending_approval", "pending_id": string, "option_keys": string[], "message": string }— 在管理员点击 WP Admin → CanAI → AI Client → Guardrails → Pending MCP option updates 中的 Approve 之前,不会写入数据库。 - 策略: 在 Guardrails 标签页配置自动应用和需要批准允许列表。如果某个名称同时出现在两个列表中,以需要批准为准。如果请求中的任何键需要批准,则整个
options对象会作为一个待处理请求排队。名称必须至少列入一个写入列表(除非启用了不受限模式)。使用与get-option相同的拒绝列表。
wpcanai-get-pending
- 参数:
{ "pending_id": string }— 当status为pending_approval时,由wpcanai-update-options返回的值。 - 返回:
{ "pending_id", "status": "pending"|"unknown", "created_at"?: int, "option_keys"?: string[], "message"?: string }— 批准/拒绝、过期或 ID 无效后,状态为unknown。请在请求用户到 wp-admin 批准后,使用此工具轮询状态。
wpcanai-i18n-get-settings
- 参数:
{ }。 - 返回:
{ "default": string, "languages": [{ "slug", "locale", "native_name", "hreflang" }], "enabled": bool, "site_locale": string }— 配置了至少一种非默认语言时,enabled为true。(v1.92.0)locale是该语言被选定时使用的 WordPress 区域设置(ms_MY);对于 WordPress 列表中没有的区域设置,以及在 v1.92.0 之前保存的语言,该值为""。(v1.95.0)site_locale是 WordPress 的站点语言(Settings → General,英语(美国)为en_US),默认语言始终为此语言。这是路由探测:收到任何翻译请求时,请先调用此工具(参见翻译模型路由器)。
wpcanai-i18n-set-settings
-
参数:
{ "languages": [{ "slug"?: string, "locale"?: string, "native_name"?: string, "hreflang"?: string }], "default"?: string }—languages为必填项。替换整个语言列表:必须包含所有语言,而不仅是新增的语言。(v1.95.0)default为可选项:参见下文默认语言为站点语言。 -
(v1.92.0)传入 WordPress 区域设置。 每种语言都需要
slug或locale(WordPress 区域设置:ms_MY、ja、pt_BR、en_US)。区域设置会像管理后台的语言选择器一样,准确填充你省略的值:slug:ISO 639-1 代码;仅当已有其他语言使用同一代码时,才附加地区标识(pt,然后是pt-pt)。native_name:WordPress 对该区域设置使用的名称;en_US对应 "English"。hreflang:不带地区标识的语言代码(ms);如果有其他语言使用相同代码,则使用语言-地区格式(pt-PT)。 因此,{ "languages": [{ "slug": "en" }, { "locale": "ms_MY" }] }会将马来语添加为ms/ Bahasa Melayu /ms。现有语言若省略locale,则保留已存储的值;"locale": ""会清除该值。若未指定 locale,native_name和hreflang默认为 slug。
-
(v1.92.0) 错误。 所有错误都会保留原设置不变,且
error.data.value会指出有问题的输入:unknown_locale:不是 WordPress locale;消息会列出相近的匹配项(“th_TH is not a WordPress locale. Did you mean th?”);missing_slug:语言既没有 slug,也没有 locale;invalid_hreflang:hreflang 会被规范化(ms_my→ms-MY),必须符合language[-Script][-REGION]格式,并且不接受x-default;duplicate_slug、duplicate_hreflang(不区分大小写);- (v1.95.0)
default_is_site_language:default不是站点语言的 slug(data:site_locale、default= 必须使用的 slug、value)。此错误取代invalid_default; - (v1.95.0)
site_language_taken:第二种语言使用了站点语言的 locale。默认语言不能同时作为翻译语言。
-
(v1.95.0) 默认语言就是站点语言。 CanAI 没有自己的默认语言。默认语言(不带 URL 前缀、页面所用的语言)是 WordPress 的站点语言
site_locale。此调用会选择该语言对应的行:- 首先按 locale 匹配(使用传入的 locale,或该 slug 已存储的 locale);
- 否则选择 locale 为空、但 slug 或 hreflang 与其相符的行(例如,
en_US站点上的en); - 否则从 WordPress 语言列表中添加一行,并将其放在首位,同时发出警告。
仅在要确认默认语言时才传入
default。confirm_default_change(v1.92–v1.94)会被忽略。 -
(v1.95.0) 更改默认语言意味着更改站点语言。 这会移动所有 URL:新语言将使用不带前缀的地址,旧默认语言则移至其前缀下(
/en/)。请告知用户哪些内容会移动,并先征求同意。 然后可以:- 由站点所有者在 设置 → 常规 → 站点语言 中选择,这也会安装语言包;或者
- 调用
wpcanai-update-options { "options": { "WPLANG": "ms_MY" } }。这要求 WPLANG 位于 update-options 允许列表中(CanAI → AI Client → Guardrails;若不在列表中,工具会返回options_not_allowed),且必须已安装相应 locale 的语言包(否则 WordPress 会保留旧值)。美国英语请使用""。
无论哪种方式,CanAI 都会跟进处理。新的站点语言会成为默认语言(若尚未配置,则从 WordPress 语言列表中添加),之前的默认语言会保留为翻译语言,不会移除任何语言,下一个管理页面会告知站点所有者哪些内容发生了移动。之后请重新读取
wpcanai-i18n-get-settings。 -
**返回值:**最终设置(与
wpcanai-i18n-get-settings返回的结构相同)。以下情况会出现warnings: string[]:- (v1.95.0) 添加了站点语言对应的行;
- (v1.95.0) 保存时将默认语言调整为站点语言(v1.95.0 之前保存的设置与之不一致时会发生;Languages 选项卡在保存前发出警告的情况),并说明发生了哪些移动;
- 已移除的语言仍有翻译。翻译会予以保留,之后重新添加相同的 slug 即可恢复。
wpcanai-i18n-list-strings
- 参数:
{ "lang": string, "untranslated"?: bool, "search"?: string, "post_id"?: int, "layout"?: bool, "menus"?: bool, "location"?: "primary"|"footer" }—lang必填(用于填充翻译列)。untranslated: true仅保留翻译为空的字符串;search根据源文本进行不区分大小写的子字符串筛选。(v1.88.0)post_id仅保留该文章(页面或模板)中使用的字符串;layout: true会添加其布局链(布局、页眉、页脚)中的字符串;menus: true会添加菜单项标题(以及描述,v1.89.0);location仅添加一个位置的字符串。(v1.89.0) 如果lang指定的位置有自己的菜单,则不会贡献任何内容:该菜单已经使用相应语言编写。 - 返回:
{ "source": string, "translation": string, "post_ids": int[], "menus": string[], "tags": string[], "placeholders": string[] }组成的array。(v1.94.0)tags是翻译必须保留的标记(["strong"]、["a1","a2"];源文本中的内联标记会在source中显示,但不带属性),placeholders是必须保留的{names}。post_ids是_canai_html/_canai_js中包含该t()源文本的文章。(v1.88.0)menus列出将该源文本用作菜单项标题或描述的位置(["primary"];没有时为[]),仅用于菜单的行其post_ids: []。t()源文本来自字符串索引——如果内容有更改,请先运行wpcanai-i18n-rescan;每次调用时都会读取已分配菜单中的菜单标题。
wpcanai-i18n-set-translations
- 参数:
{ "lang": string, "translations": { "<source>": "<translated>", ... } }— 对一种语言批量新增或更新。lang必须是已配置的非默认语言(默认语言就是源文本)。空翻译 /null翻译会删除条目。(v1.94.0) 翻译值必须保留源文本中的标记(<strong>…</strong>、<a1>…</a1>、<br/>)和{placeholders},顺序不限(可调整位置以适应目标语言);其他标记都会被剥除。若翻译值删除、添加、重复或交叉使用标记,或删除占位符,则不会保存:结果会在rejected: [{ "source", "reason" }]中列出该项(例如 "Missing ."),saved仅统计已写入的内容。源文本键必须与存储的源文本完全一致(逐字符匹配——不要修剪或重新排版)。 - 返回:
{ "saved": int, "lang": string }。
wpcanai-i18n-get-media-map
- 参数:
{ "lang"?: string }— 省略时返回所有语言。 - 返回: 以语言 slug 为键的对象:
{ "<lang>": { "<original_attachment_id>": <translated_attachment_id>, ... } }—{{ tmedia(id) }}使用的每种语言各自的附件替换映射。
wpcanai-i18n-set-media-map
- 参数:
{ "lang": string, "map": { "<original_id>": <translated_id>, ... } }— 批量新增或更新;仅限非默认lang。0/null会删除映射。 - 返回:
{ "lang": string, "map": { ... } }— 该语言最终的完整映射。
wpcanai-i18n-rescan
- 参数:
{ }— 根据每篇带有_canai_html/_canai_js的文章重建字符串索引。任何内容编辑后,以及运行wpcanai-i18n-list-strings前,都应运行此命令。 - 返回:
{ "posts_scanned": int }。
wpcanai-i18n-get-post-overrides
- 参数:
{ "post_id": int, "lang"?: string }— 省略lang时返回所有语言的内容块。 - 返回: 指定
lang时为{ "post_id", "lang", "overrides": { title?, content?, excerpt?, fields?: {meta_key: value} } };省略时为{ "post_id", "overrides_by_lang": { "<lang>": {...} } }。空对象 = 没有覆盖项(将渲染默认语言的值)。
wpcanai-i18n-set-post-overrides
- 参数:
{ "post_id": int, "lang": string, "overrides": { "title"?, "content"?, "excerpt"?, "fields"?: { "<meta_key>": "<value>" }, "seo_title"?, "seo_description"? } }— 稀疏 upsert:仅更改提交的键;null/""值会移除对应覆盖项(回退到默认语言);如果最终内容为空,则删除该行。content接受 HTML(wp_kses_post);title/excerpt为纯文本;fields的值为纯文本。seo_title/seo_description为纯文本,并会传递给当前启用的 SEO 插件(参见 REFERENCE.md → i18n → SEO translation)。拒绝默认语言(invalid_lang)——文章本身就是默认语言。未知的顶层键会被静默丢弃。 - 返回:
{ "post_id", "lang", "overrides": <resulting blob> }。 - 写入时会使用
sanitize_key规范化字段键("My_Field"会存储/匹配为my_field);渲染读取路径也会以相同方式规范化。wpcanai-i18n-set-translations的saved统计的是已提交的键值对数(包括删除项/跳过的非字符串),而不是写入的键值对数。
wpcanai-i18n-get-term-overrides
- 参数:
{ "term_id": int, "lang"?: string }。 - 返回: 与文章变体结构相同,blob 键为
name/description。
wpcanai-i18n-set-term-overrides
- 参数:
{ "term_id": int, "lang": string, "overrides": { "name"?, "description"?, "seo_title"?, "seo_description"? } }— 与文章变体采用相同的稀疏更新/删除/默认语言规则。description接受 HTML(wp_kses_post,与文章的content相同);name为纯文本;seo_title/seo_description为纯文本,并会传递给当前启用的 SEO 插件(参见 REFERENCE.md → i18n → SEO translation)。 - 返回:
{ "term_id", "lang", "overrides": <resulting blob> }。
全局字符串 — 未关联到任何文章的站点级文案
站点名称、标语,以及归档/搜索/404 页面 SEO 标题和描述存储在按语言区分的全局字符串库中(选项 wpcanai_i18n_global_strings),与文章/术语覆盖项分开,因为它们不属于任何单篇文章。在非默认语言的前端,这些字符串会用于 option_blogname / option_blogdescription + og:site_name(站点名称/标语),以及归档上下文 search、404 和 post_type:<slug> 的 <title> + meta 描述。术语归档(分类 / 标签 / 自定义分类法)不是全局字符串上下文——这些内容使用 wpcanai-i18n-set-term-overrides。
wpcanai-i18n-get-global-strings
- 参数:
{ "lang"?: string }— 省略lang可获取所有语言。 - 返回(指定
lang,限定范围):{ "lang": string, "strings": { "site_name": string, "tagline": string, "archive": { "<context>": { "title", "description" } } } }。未设置时,site_name/tagline为""。返回(省略lang,所有语言):{ "by_lang": { "<lang>": <blob> } }。只读。
wpcanai-i18n-set-global-strings
- 参数:
{ "lang": string, "values": object }— 两者均为必填项。values键:site_name(纯文本)、tagline(纯文本)、archive(context →{ "title"?, "description"? }映射)。稀疏 upsert:仅更改提交的键;null/""值会移除对应键;如果最终内容为空,则删除该语言行。拒绝默认语言(invalid_lang)——站点自身的名称/标语就是默认值。归档上下文包括search、404和post_type:<slug>(例如post_type:product);术语归档(分类 / 标签 / 自定义分类法)不是全局字符串上下文——请使用wpcanai-i18n-set-term-overrides翻译这些内容。 - 返回:
{ "lang": string, "values": <resulting blob> }。
wpcanai-i18n-list-content
- Args:
{ "lang": string, "post_type"?: string, "untranslated"?: bool }— 内容工作队列。lang必须是已配置的非默认语言(默认语言对应源内容;传入默认语言会报错invalid_input)。涵盖公开文章类型中已发布的文章,attachment除外(最多 1000 行;未知的post_type会返回[];大型站点可用post_type缩小范围)。 - Returns:
{ "id", "title", "post_type", "status", "kind", "done"?, "total"? }数组。(v1.91.0) 按kind划分,遵循文章列表中的 Translations 列以及 CanAI → Translations → Content 的同一规则:kind: "content"(除 CanAI Twig 页面外的所有文章):覆盖内容块。status为untranslated(无内容块)、partial(有内容块,但没有content覆盖,且文章正文非空)或translated。kind: "strings"(_canai_html非空白的 CanAI Twig 页面):页面自身的t()源字符串,不包括布局。done/total统计已翻译的字符串。status为translated(全部)、partial(部分)、untranslated(无)或nothing(页面没有t()字符串)。标题覆盖不再会使 Twig 页面显示为“已翻译”。- 在 v1.91.0 之前,所有行都使用内容块规则,且不带
kind。 untranslated: true仅返回未翻译和部分翻译的行,不返回nothing。
wpcanai-i18n-get-content
- Args:
{ "post_id": int, "fields"?: ["meta_key", ...], "lang"?: string }— 必须提供post_id。fields用于指定要包含的自定义字段 meta 键(通过sanitize_key规范化,与存储覆盖内容时的处理方式一致)。可选的lang(已配置的非默认语言)还会返回该语言当前的覆盖内容块,便于并排比较。 - Returns:
{ "post_id": int, "post_type": string, "source": { "title": string, "content": string, "excerpt": string, "fields": { "<meta_key>": value }, "seo": { "title": string, "description": string } }, "overrides"?: { "<lang>": blob } }—source是默认语言的原始内容(title/content/excerpt直接从文章读取;指定的自定义字段通过 meta 读取;seo是从当前启用的 SEO 插件读取的默认语言 SEO 标题/描述)。只读。这是在不执行求值的情况下获取 Agent 在调用wpcanai-i18n-set-post-overrides前需要翻译的源内容的方式。
预设 — wpcanai-list-presets / wpcanai-install-preset / wpcanai-uninstall-preset
wpcanai-list-presets→{ presets: [{ slug, title, description }] }。内置套件(例如cpt-corporate、single-freelancer、blog-journal——完整的博客,其 Blog 页面会变为 Reading → Posts 页面)会构建完整的模板栈(布局/页眉/页脚 + 类型化的single-*/archive-*模板)。wpcanai-install-preset—{ "slug": string, "set_front_page"?: bool, "clean_slate"?: bool, "adopt_woo_pages"?: bool, "trash_woo_pages"?: bool }。安装套件的模板和页面,导入其图片,并且(v1.26+)将面向用户的文案包装在{{ t('…') }}中 + 添加languages()切换器;系统会预热原生 i18n StringIndex,因此wpcanai-i18n-list-strings会立即显示套件中的字符串,即使是在安装后才配置语言。套件模板可以引用{{preset.image_id.<key>}},以获取导入图片的附件 ID,用于tmedia()。- ⚠
clean_slate: true具有破坏性。 安装前,它会将所有现有 CanAI 模板和页面移入回收站(可从回收站恢复),并清除预设记录;即使之后安装失败,这些操作也会执行。未经用户明确确认,切勿传入此参数。MCP 宿主会在工具架构中公开此布尔值,但没有内置保护机制。 adopt_woo_pages(默认值true,v1.47.0)。 对于包含settings.woo_pages的套件,会将内容写入商店现有的 WooCommerce 页面,而不是创建重复页面(如checkout-2……),以保留页面 ID 和永久链接,确保已发送邮件中的订单接收 URL 仍然有效。每个接管页面之前的 CanAI 元数据都会被快照保存,并由wpcanai-uninstall-preset还原;页面本身绝不会被删除。传入false可强制创建新页面(v1.47.0 之前的行为;会导致先前发出的订单链接失效)。trash_woo_pages(默认值false)。 将现有的商店/购物车/结账/我的账户页面移入回收站(可从回收站恢复),让套件自带的商店页面使用这些别名。此操作会在接管之前执行,因此优先级更高:接管操作查找时,这些位置已空出,并会创建新页面。
- ⚠
wpcanai-uninstall-preset—{ "slug": string }。移除套件的模板和页面,并清理其创建的所有导航菜单。
wpcanai-export / wpcanai-import
wpcanai-export→ CanAI 模板和页面的 JSON 包(元数据_canai_html/css/js/context/layout),以及每行一个i18n_meta键,用于往返保留每个_canai_i18n_{lang}内容覆盖数据块。(v1.39.0)包含已编译构建的行还会带上预编译的 Tailwind 缓存(_canai_tailwind_build/_hash/_built_at;仅导出非空构建,且排除相对于源文件的_canai_tailwind_epoch时间戳),这样导入的布局会以内联方式渲染,而不是回退到 Play CDN。不包含媒体二进制文件——附件 ID 会被标记为需要重新上传,但绝不会自动重新映射。(v1.65.0)也不包含区块页面——导出只匹配_canai_html非空的文章,因此正文位于post_content中的区块编辑页面不会出现在包中;不要承诺其具有完整的备份覆盖范围。(v1.80.0){ "summary": true }返回清单而不是数据包——{ templates: [{ id, title, type, status, hashes }], pages: [{ id, title, format, hashes }], counts: { templates, pages, terms }, version },其中hashes与read-meta返回的 SHA-1 哈希相同,不含内容。可用它比较两个站点,或在拉取任何内容前检查有哪些变化。wpcanai-import—{ "data": object | "json": string, "dry_run"?: bool }。dry_run: true会报告将发生的更改,但不会写入。导入时会重新清理覆盖数据块。**授权限制:**导入会执行unfiltered_html级别的写入;除非调用方具备unfiltered_html权限(单站点上的管理员具备此权限),或请求携带有效的 CanAI API 密钥(Bearer /X-WPCanAI-API-Key),否则会返回 403forbidden。- **(v1.97.0)按路径匹配页面。数据包版本为 1.1(
wpcanai_export_version):每个页面行在post_name旁包含path(gallery/auditorium),委托页面引用也包含path。导入仅更新现有页面——绝不会创建页面。WooCommerce 页面行会优先匹配商店中该类型的页面。否则,1.1 行只匹配完全相同的path,不会匹配其他页面(不会回退到单独的 slug,以免覆盖其他位置的同名页面);旧版数据包中的行则仅在站点上恰好有一个页面使用该 slug 时按 slug 匹配,不论其父页面是什么。页面缺失的行,或(旧版数据包中)slug 被多个页面共用的行,会被跳过并显示警告(计入pages_skipped,且page_actions中的action: "skipped"):若路径缺失,警告会说明该 slug 唯一对应的页面所在位置(若存在)——使用wpcanai-update-page将页面移至该位置后重新导入;若 slug 被多个页面共用,警告会列出每个页面,格式为path (ID n)——请从 CanAI 1.97+ 重新导出。带有path的委托引用仅按该路径解析;若该位置没有页面,则导入时不设置委托页面,并显示相应警告。(v1.97.1)**旧版数据包中的委托引用(无path)仅在恰好有一个页面或文章使用该 slug 时解析。导入前,使用wpcanai-create-page(带parent)创建缺失的页面。
wpcanai-diagnostics
- 参数:
{ "include_network"?: bool }(默认值为false)。→ 生成一份与 wp-admin Diagnostics 页面内容一致的健康报告(PHP/WP/插件版本、功能与端点检查、Tailwind 构建状态、WooCommerce 是否存在及 HPOS 安全性说明)。 include_network: true还会发起真实的出站 HTTP 请求(出站 HTTPS、REST 环回、技能端点可达性、认证标头透传)——耗时较长;怀疑存在连接或环境问题时使用,而不是猜测。- (v1.80.0)新增
page_cache检查(检测到的插件、清除钩子、自动清除开/关)和integrations组(每个工具标签一行:是否存在、已注册的工具、是否禁用)。(v1.82.0)integrations行按工具声明的标签(touches+requires)统计工具数量,因此即使集成不存在,也会报告声明该集成的工具数量,并带有hidden_by_recommendation。 - **(v1.82.0)**新增
tools组:tool_budget(已暴露与工具总数、字节数、约略 token 数)和tool_recommendations(当“Follow recommendations”关闭且建议与当前状态不同时发出警告;fix是准确的manage-tools调用)。languages组:languages(已配置的母语,或无)和translation_tools——语言列表与 Languages 工具组配对:无/关闭 → 通过(未使用),无/开启 → 警告(使用i18n-set-settings添加语言,或关闭该组),有/开启 → 通过,有/关闭 → 信息(这是合理的所有者选择;如需翻译,可使用manage-tools启用)。这是诊断中唯一提及翻译的地方;未选择启用时,其他任何地方都不会提及翻译。 - **(v1.90.0)**新增
render组:如果过去 7 天内没有模板触发渲染警告,则显示一行pass;否则每条记录显示一行warning(按时间从新到旧,最多 10 条,之后显示一行info,列出其余记录),并附带post_id、field、line和edit_url——与wpcanai-scan报告为render_warning的记录相同。 - **(v1.88.0)新增
menus组:每个位置一行——已分配菜单时显示pass、菜单名称和项目数;未分配任何菜单时显示warning(fix是一次write-menu调用)——另有一行info,列出仍分配在 v1.88 之前位置名称下的菜单。(v1.89.0)**每个语言菜单再显示一行info(“菜单位置:footer (Bahasa Melayu)”)。
wpcanai-purge-cache(插件 v1.80.0;需要 cache)
- 仅当受支持的缓存插件处于活动状态时才能使用(LiteSpeed Cache、WP Rocket、W3 Total Cache、WP Super Cache、WP-Optimize 且已开启其页面缓存——v1.82.0)。如果没有受支持的插件,建议将其关闭,通常也不会出现在列表中(规则 R1)。它只清除第三方页面缓存:CanAI 的 Twig 编译缓存和 Tailwind 构建会在每次源文件变更时自动失效,无需清除。
- 参数:
{ "scope": "all"|"url"|"post", "target"?: string|int }——target在url时为 URL(必须属于本网站),在post时为文章 ID;在all时会被忽略。 - 返回值:
{ "purged": [{ "scope", "target" }], "plugin": "litespeed"|"wp-rocket"|"w3tc"|"wp-super-cache"|"wp-optimize"|null, "note"? }。plugin: null表示未检测到受支持的缓存插件——不是错误;此时只有post会执行操作(清除 WordPress 文章缓存),而all/url会返回一条说明。 - 通常不需要使用它。 开启写入后清除缓存(
wpcanai_purge_after_write,默认开启)时,每次 CanAI 写入都会在请求结束时清除一次缓存——更改模板会清除整个网站的缓存,更改页面会清除该页面的缓存——并在写入操作的purged字段中说明(参见工具标签)。只有当存在缓存插件但purged返回null(自动清除已关闭),或写入操作未覆盖某个 URL(例如列出你编辑的页面的归档页)时,才调用purge-cache。
部署 Google Stitch 界面 → canai-stitch 技能
Google Stitch 部署(CanAI Stitch 插件的 wpcanai-stitch-connect、-list-projects、-set-projects、-list-screens 和
-deploy-screen 工具)有专属技能 canai-stitch(npx skills add usewp/canai --skill canai-stitch):
选择“Stitch 保持为源”还是“移交给 CanAI”、使用 stitch_page_edited 防护机制的部署和重新部署工作流,以及将忠实副本转换为 CanAI 约定的清理流程。处理任何 Stitch 请求时都要加载此技能。除非所有者要求替换已有编辑,否则切勿将 overwrite: true 传给 wpcanai-stitch-deploy-screen。
上传媒体
两种方式——根据来源选择:
| 来源 | 使用方式 | 原因 |
|---|---|---|
图片已有 URL(https://…/photo.jpg——例如从源站点、CDN 或副本捕获中直接引用的图片) | MCP 工具 wpcanai-sideload-url | 服务端会获取文件内容——通过 MCP 传输的 JSON 很小,无需 API 密钥 / 基础 URL / curl。推荐使用。 |
| 本地二进制文件(用户机器上的文件,没有公开 URL) | sideload REST 端点(如下) | MCP 不适合承载二进制负载(大小和 base64 编码)。 |
URL 图片 → wpcanai-sideload-url(推荐,纯 MCP)
调用 MCP 工具——无需收集凭据,它通过经过身份验证的 MCP 传输进行调用:
wpcanai-sideload-url {
"url": "https://example.com/images/hero.jpg",
"alt": "Front of the product", // optional
"attach_to": 0 // optional parent post id
}
可选参数:filename、title、caption、description。返回 { id, source_url, mime_type, filename, attached_to }。编写 _canai_html 时,优先使用返回的 id,通过 {{ image_attrs(id, 'src,alt') }} / {{ media_url(id, 'full') }} 引用(在渲染时解析——参见 优先使用 CanAI 模板函数 原则);只有在需要绝对 URL 时才保留绝对 source_url(OG/Twitter 元数据、Web 清单)。对于资源通过 URL 直接引用的副本/导入流程,应使用此工具。
本地二进制文件 → sideload REST 端点
MCP 消息不适合承载二进制负载(大小和 base64 编码开销)。请改用 sideload REST 端点。它接受与 MCP 传输相同的 CanAI API 密钥,无需单独的凭据。
- 端点:
POST {site}/wp-json/wpcanai/v1/sideload - 身份验证:
Authorization: Bearer <wpcanai_…API key>(或X-WPCanAI-API-Key:)。 - 请求体:
multipart/form-data,必需字段为file=@<local-path>。可选字段:filename、title、alt、caption、description、attach_to(父文章 ID)。 - 返回:
{ id, url, source_url, mime_type, filename, title, alt, attached_to }。 - API 密钥用户必须具备的权限:
upload_files(管理员拥有此权限)。
工作流(每次处理一张图片)
对于静态站点导入(用户运行了 /canai-mcp implement <folder> 或类似命令),无需询问——自动检测引用的资源,并在预处理阶段将其导入。参见“Implement HTML → CanAI”下的 静态站点资源预导入。以下步骤适用于该流程之外的临时单文件上传。
- 如果用户尚未提供本地文件路径,请向用户索要。
- 对每个文件,从用户的机器发送 POST 请求(每次一个,以便获取各自的
id)。curl最简单,也是获准使用的唯一 shell 例外:
对于仅使用 Node 的技术栈(没有 curl 或 Python——Node 18+ 内置全局curl -sS -X POST "{site}/wp-json/wpcanai/v1/sideload" \ -H "Authorization: Bearer <API key>" \ -F "file=@/abs/path/to/image.jpg" \ -F "alt=Front of the product"fetch/FormData/Blob;参见 本地脚本辅助),可使用以下零依赖等效方案:node -e "const fs=require('fs');const f=new FormData();f.append('file',new Blob([fs.readFileSync(process.argv[1])]),require('path').basename(process.argv[1]));f.append('alt',process.argv[2]||'');fetch(process.argv[3]+'/wp-json/wpcanai/v1/sideload',{method:'POST',headers:{Authorization:'Bearer '+process.argv[4]},body:f}).then(r=>r.text()).then(console.log)" /abs/path/to/image.jpg "Front of the product" "{site}" "<API key>" - 获取返回的
id,并通过现有 MCP 工具将其传入模板——在_canai_html(wpcanai-write-meta)中,优先使用{{ image_attrs(id, 'src,alt') }}/{{ media_url(id, 'full') }},而非固定 URL;只有在需要绝对 URL 时才保留绝对url(OG/Twitter 元数据、Web 清单)。
不要尝试将文件编码为 base64 后放入 MCP 工具参数中——请使用此端点。
媒体 SEO 清理(wpcanai-list-media / wpcanai-get-media / wpcanai-update-media)
清理命名不当的上传文件并补充缺失的替代文本(插件 1.45.0+):
- 审核 —
wpcanai-list-media { "missing_alt": true }和/或{ "generic_filename": true }(如IMG_4521.jpg这类相机或屏幕截图文件名)。使用page/per_page分页。 - 了解上下文 —
wpcanai-get-media { "id": 123 }会返回完整元数据及used_in:引用该文件的页面。请先阅读这些页面——替代文本和文件名必须描述图片在页面上下文中展示的内容。切勿根据旧文件名推导替代文本。 - 修正 —
wpcanai-update-media { "id": 123, "alt": "…", "title": "…", "filename": "red-leather-office-chair" }。filename是不带扩展名的 slug;文件及所有尺寸变体都会在磁盘上重命名,且所有内部引用都会重写(post_content +_canai_html/_canai_css/_canai_js元数据)。- 之后旧 URL 会返回 404(不会重定向)——请再次检查
used_in中的页面是否能正常呈现。 - 显式传入空字符串可清空字段。仅更新元数据不会触碰文件。
- 仅限本地文件;卸载到 S3/CDN 的媒体会返回
file_missing。
严重:CanAI 存储模型
CanAI 不使用 post_content 存储 Twig 正文。请使用 **wpcanai-read-meta** / **wpcanai-write-meta** 处理 _canai_html、_canai_css、_canai_js、_canai_context、_canai_layout。标记为 blocks 的页面是例外:其正文位于 post_content,此技能不会写入该字段。
严重:内容解析
WC 商店/购物车/结账/我的账户/产品分类内容通常位于 WC 页面;产品 / 404 / 搜索 / 存档类型的内容通常位于 **wpcanai_template**。编辑错误文章前,请先使用 **wpcanai-resolve-content-id**。
翻译模型路由器
CanAI 只有一种翻译模型:原生字符串翻译(每个页面对应一篇文章,全站字符串表,插件 1.22.0+)。按语言复制文章并非 CanAI 模型——模板和页面不按语言区分。面向文章的工具(list-templates、read-meta、write-meta、create-page、write-post 等)不接受 lang 参数(多余的参数会被忽略),也不会返回 lang 键;只有 i18n-* 工具会出现 lang,用于指定正在翻译的语言。遇到任何翻译请求——“翻译这个站点/页面”“添加一种语言”“多语言”“本地化”——请先检查设置:
- (v1.82.0)
i18n-*工具为可选工具,默认隐藏。 如果列表中没有wpcanai-i18n-get-settings,不要报告翻译功能不受支持:运行wpcanai-manage-tools { "action": "list" },确认用户希望在此站点启用翻译后,运行{ "action": "set", "groups": { "Languages": "on" } },重新连接,然后继续。 - 调用
wpcanai-i18n-get-settings { }。enabled: true→ 按照下方的原生字符串翻译流程操作。不要创建按语言复制的文章。enabled: false→ 原生翻译已关闭。请与用户确认要使用的语言(使用 WordPress 区域设置,例如ms_MY)。默认语言始终是站点语言(site_locale,v1.95.0),并且应与页面当前使用的语言一致。如果不一致,请说明这一点,并先更改站点语言(参见wpcanai-i18n-set-settings→ 更改默认语言意味着更改站点语言)。然后使用wpcanai-i18n-set-settings初始化,为每种语言传入locale,以便 CanAI 填写 slug、名称和 hreflang(v1.92.0),之后继续执行原生工作流。
原生字符串翻译(单篇文章国际化)
**运行时机:**上方路由器已判定使用原生模型——用户要求翻译整个网站或某个页面、添加语言、补齐缺失的翻译或本地化图片,并且 wpcanai-i18n-get-settings 返回 enabled: true(或你刚完成初始化)。
模型(插件 1.22.0+):每个页面/模板只有一篇文章,不为每种语言创建副本。面向用户的字符串在 _canai_html / _canai_js 中用 {{ t('…') }} 包裹,并在渲染时从全站字符串表中解析。非默认语言通过路径前缀提供(/ms/plt/);默认语言不带前缀。hreflang alternate 会在 wp_head 上输出,tmedia() 映射会按语言替换附件。
工作流
-
设置 —
wpcanai-i18n-get-settings { }。如果语言缺失或有误,请先与用户确认,再用wpcanai-i18n-set-settings写入(它会替换整个列表:请包含所有语言)。默认语言为站点语言(v1.95.0);更改为其他默认语言等同于更改站点语言,应由用户决定。 -
创作前提——所有面向用户的字符串都必须用
{{ t('…') }}包裹。只有t()的源字符串会被索引并可供翻译。如果页面中有硬编码字符串,请先将其包裹起来:wpcanai-read-meta→ 构建替换对 →wpcanai-replace-in-meta,例如{ "post_id": <id>, "replacements": [{ "from": ">Shop now<", "to": ">{{ t('Shop now') }}<" }] }(以周围的标记作为匹配依据,确保匹配唯一)。(v1.94.0)每个句子使用一个t(),并将句内标签放在其中。- 写成
{{ t('Get <strong class="font-semibold">free shipping</strong> today.') }},不要写成{{ t('Get') }} <strong>{{ t('free shipping') }}</strong> {{ t('today.') }}。译者需要看到完整句子:不同语言的语序不同,片段无法重新排序。 - 只有行内元素可以放在其中(
strong、em、a、span、br、code等)。块级元素(p、div、li、标题)放在外面,每个块使用一个t()。 - 动态值作为
{placeholders}传入变量:{{ t('Read our <a href="{url}">terms</a>.', {url: turl('/terms/')}) }}、{{ t('By {name}', {name: post.author.display_name}) }}。 - 可翻译属性(
title、aria-label)单独使用一个t(),并作为变量传入。 - 整体设置样式的短语保持原样:
<strong>{{ t('Best seller') }}</strong>。 - 字符串键不包含属性(
Get <strong>free shipping</strong> today.),因此调整样式后翻译仍然有效。重复出现的标签会加上编号(<a1>、<a2>)。
**合并片段。**如果句子仍被拆分,
wpcanai-scan会报告split_sentence。使用wpcanai-replace-in-meta将片段合并(整段片段 → 一个t(),并将标签放在其中),运行wpcanai-i18n-rescan,然后翻译新键,并以i18n-list-strings { "post_id": … }中旧片段的翻译作为参考。旧片段键仍会保留在表中,但不再使用。在原生 i18n 网站上创作新页面时,从一开始就用t()包裹面向用户的字符串(参见 实现 HTML → CanAI 中的辅助表)。 - 写成
-
重建索引 — 每次编辑内容后运行
wpcanai-i18n-rescan { }。wpcanai-i18n-list-strings读取的是索引,而非实时 meta;索引过期时,列出的字符串也会过期。 -
翻译——每种非默认语言一次处理一个页面:
wpcanai-i18n-list-strings { "lang": "ms", "post_id": 42, "layout": true, "menus": true, "untranslated": true }——列出该页面尚未翻译的t()字符串、页面布局(页眉、页脚)以及菜单项标题。从这里开始。只有用户要求翻译整个网站时,才列出全站内容({ "lang": "ms", "untranslated": true })。- 自行翻译每个
source(由 Agent 撰写译文)。菜单项标题和描述也是普通 source(menus为["primary"]或["footer"]的行):在同一次调用中翻译它们,get_menu()会呈现翻译后的内容——模板中无需使用t(),也无需重新扫描。如果某个语言版本有自己的菜单,该菜单不会出现在列表中:因为它已经使用该语言编写(参见按语言设置菜单)。 - 通过一次批量调用写入:
wpcanai-i18n-set-translations { "lang": "ms", "translations": { "Shop now": "Beli sekarang", "Add to cart": "Tambah ke troli" } }。 - 提供 source → 译文对照表,方便用户抽查;如需更正,再进行一次批量调用即可。
- 站点所有者也可以使用页面上的“Translate strings”按钮完成相同操作(插件 v1.88.0):该按钮位于页面编辑器的 Translations 框、区块页面的 CanAI 编辑器和 CanAI 侧边框中,以及前端管理工具栏的 Translate this page。**(v1.91.0)**在 All Pages / All Posts / All Templates 中,Translations 列会为每种语言显示一个状态标记:
MS表示已翻译,MS•表示部分翻译,+ MS表示尚未翻译,—表示无需翻译。点击标记可打开对应的编辑器;此功能已取代“Translate: {language}”和“Translate strings”行操作。它会打开 CanAI → Translations → Strings,并筛选到对应页面,同时提供用于控制是否包含页面布局和菜单的开关。需要用户手动翻译时,请引导他们使用此功能。
-
内容(CPT / 长篇内容)——每种非默认语言:
wpcanai-i18n-list-content { "lang": "ms", "untranslated": true }→ 显示标题/正文/字段仍以默认语言呈现的文章队列。(v1.91.0)kind: "strings"行表示 CanAI Twig 页面:请按步骤 4 翻译其t()字符串(i18n-list-strings { "lang": "ms", "post_id": <id> }→i18n-set-translations),不要使用覆盖项。以下步骤适用于kind: "content"行。- 对于每篇文章:
wpcanai-i18n-get-content { "post_id": <id>, "fields": ["subtitle", ...] }会返回默认语言的原始source(title、content、excerpt、命名自定义fields)。翻译后,通过一次wpcanai-i18n-set-post-overrides { "post_id": <id>, "lang": "ms", "overrides": { "title": "…", "content": "…", "excerpt": "…", "fields": { "subtitle": "…" } } }写入。向i18n-get-content传入"lang": "ms",还可并排查看现有覆盖项。请使用专用 i18n 工具——不要使用 PHP eval 翻译内容。 - 循环/归档中显示的分类法术语:对每个术语调用
wpcanai-i18n-set-term-overrides。 - 提供逐篇文章的覆盖情况表(id → 翻译前/后的状态)。URL 不会改变——同一篇文章会在
/ms/…下提供带覆盖字段的内容(不会为每种语言创建副本)。 - SEO 元数据(标题/描述)通过
seo_title/seo_description覆盖键翻译——使用i18n-get-content读取 source(关于完整的 SEO bridge 行为,请参阅 references/REFERENCE.md)。只有 SEO 文案需要与可见标题/正文不同时,才需要设置这些键——否则,在非默认语言页面上,seo_title/seo_description会根据翻译后的title/name和excerpt/去除标签后的content自动生成(描述会在不超过 160 个字符且不截断单词的边界处截断)。
-
站点级字符串(站点名称 / 标语 / 归档 SEO)——针对每种非默认语言:
wpcanai-i18n-get-global-strings { "lang": "ms" }→ 当前站点名称、标语和归档映射。- 翻译后,通过一次调用写入:
wpcanai-i18n-set-global-strings { "lang": "ms", "values": { "site_name": "…", "tagline": "…", "archive": { "search": { "title": "…", "description": "…" }, "404": { "title": "…" } } } }。 - 归档上下文包括
search、404、post_type:<slug>。分类 / 标签 / 分类法标题属于术语覆盖项(第 5 步),而不是全局字符串。
-
媒体(需要按语言使用不同图片时)——使用
wpcanai-sideload-url上传本地化资源,然后映射:wpcanai-i18n-set-media-map { "lang": "ms", "map": { "123": 456 } }。模板必须通过{{ tmedia(123) }}/{{ tmedia(123, 'large') }}引用该图片——没有映射时会回退到原图片;普通的media_url()会忽略映射。 -
在浏览器中验证——访问带语言前缀的 URL(
{site}/ms/<page-slug>/):翻译后的字符串能够正确显示;<head>包含每种语言对应的<link rel="alternate" hreflang="…">;canonical 和og:url带有/ms/前缀;og:site_name显示翻译后的站点名称,且og:locale与语言相符;languages()/lang_url()的切换器 URL 带有该前缀;默认语言仍不带前缀。
注意事项
- 纯文本翻译。
wpcanai-i18n-set-translations会从值中剥除 HTML。标记应放在模板中t()外部,绝不要放在t()内。 - 键必须原样匹配。 只有当
t()的源文本逐字匹配时,翻译才会生效。将源字符串传回时,不要修剪、重新排版或更改标点。 - 默认语言为只读。
wpcanai-i18n-set-translations/wpcanai-i18n-set-media-map会拒绝默认语言——源文本就是默认显示内容。 - 索引过期。 编辑
_canai_html/_canai_js后,列出内容前请重新扫描。 - 配置前不会生效。 未配置非默认语言时,前缀路由 / 重定向 / hreflang 均处于关闭状态,
t()/tmedia()会原样返回内容。 - 内容覆盖仅影响前端。 管理后台、REST 编辑上下文和默认语言始终显示原始值;只有
/{lang}/前端页面会替换内容。 - 编辑者也可以在 wp-admin 中翻译。 从插件 v1.70.0 起,通过
post.php?post=<id>&action=edit&lang=<slug>(“语言模式”)打开的区块编辑器文章会加载并保存该语言的覆盖数据块——也就是wpcanai-i18n-set-post-overrides写入的同一份_canai_i18n_<lang>数据。如果用户说他们“在编辑器中”修改了翻译,请使用wpcanai-i18n-get-content { "post_id": <id>, "lang": "<slug>" }读取,而不要默认认为该内容尚未翻译。 - 搜索只匹配默认语言文本——WP 搜索不会为覆盖数据块建立索引。
Twig 辅助函数
t()、tmedia()、current_lang()、languages()、lang_url()、turl()——参见 references/REFERENCE.md(turl() 位于 Media & Links 下)。turl('/path/')(插件 v1.88.0)是当前语言的 home_url():默认语言返回普通 URL,ms 返回 /ms/path/,因此内部链接不会自行拼接前缀。turl(path, lang) 可指定目标语言(用于切换器)。slug_url() 也会以相同方式进行本地化;get_menu() 返回的项目中,title / description 已翻译,url 已本地化;raw_title / raw_description 则保留未翻译文本(某种语言自己的菜单会按原样显示)。current_language() 和 language_switcher() 仍可作为 current_lang() 和 languages() 的别名使用(适用于已有这些用法的模板);编写新模板时请使用原生名称。
常见工作流(通用表示法)
使用 工具名称 + JSON 参数;按宿主的 MCP 调用语法进行映射。
在 DevTools 中调整样式(三次调用)
用户在浏览器中进行尝试后,给你一个页面 URL、一段可见文本和一个属性 (例如“文本 conference,将 line-height 设为 1.15”)。不要读取整个页面,也不要 在本地镜像页面——只需三次调用:
- 解析文章 ID。 调用
wpcanai-list-pages {},匹配 URL 的 slug/标题(首页 URL 是静态首页——如果标题不明确,通过wpcanai-read-settings { "keys": ["page_on_front"] }读取page_on_front); WooCommerce 路由 →wpcanai-resolve-content-id { "type": "shop" }等。 - 查找元素。 调用
wpcanai-grep-content { "post_id": <id>, "pattern": "conference", "max_length": 1000, "context_lines": 2 }→ 该行的match是包含元素类名的逐字节精确文本行。 - 仅在该元素内编辑。
调用
wpcanai-replace-in-meta { "post_id": <id>, "scope": { "text": "conference" }, "replacements": [{ "from": "leading-none", "to": "leading-[1.15]" }], "require_all": true }。 如果元素已带有工具类,优先使用任意值 Tailwind 工具类(leading-[1.15]、text-[17px]、tracking-[-0.01em]);如果元素没有 class 属性,则在同一作用范围内使用from: "<h1"→to: "<h1 class=\"leading-[1.15]\""。只有当该属性没有对应工具类时,才通过field: "css"添加 CSS 规则。
如果第 3 步返回 replace_no_match,请读取消息(Nearest match: <kind> at line …)——whitespace_only → 使用 ignore_whitespace: true 重试相同的作用范围调用;outside_scope → 类名位于另一个元素上(通常是父元素),因此选择出现在该元素中的 scope.text——绝不要为了匹配而去掉 scope,否则会改写另一个元素;如果确定目标就是作用范围内的元素,请先尝试 ignore_whitespace: true,然后使用作用范围限定的 grep-content(max_length: 1000)查看其精确字节内容。选择 scope.text 时,请使用元素自身的首段文本或属性值:服务会将作用范围限定到文本前最近的起始标签,因此 <span> 子元素内的文本会限定到 span,而不是标题。只为更改单个属性时,绝不要对整个字段执行 read-meta。之后不需要运行 wpcanai-scan,也不需要重新编译 Tailwind(参见何时重新运行)。
编辑或移除现有 HTML 中的某个部分
- 解析 ID —
wpcanai-list-pages{ }或wpcanai-resolve-content-id{ "type": "shop" }(等)。使用返回的content_post_id(或 list-pages 中对应行的id)。 - 读取 —
wpcanai-read-meta{ "post_id": <id>, "fields": ["html"] }(如有需要,可添加其他字段)。 - 优先使用定向替换。 根据
grep-content(post_id、max_length: 1000)中的逐字节精确文本行,构造{from, to}对,并调用wpcanai-replace-in-meta,设置require_all: true——如果替换对较多,请先添加dry_run: true。如果源代码将属性换行书写,请使用ignore_whitespace: true。 - 仅在结构性重写时才退回完整写入 — 使用
wpcanai-read-meta{ "post_id": <id>, "fields": ["html"] }(或读取片段:{ "post_id": <id>, "fields": ["html"], "lines": [from, to] }),编辑后调用wpcanai-write-meta{ "post_id": <id>, "html": "<full updated HTML>" },并提供expected_hash。
修复现有页面中失效的资源 URL
当线上页面引用了错误的资源 URL(例如导入后残留的相对路径,或已迁移的 CDN)时,可在不重写整个文档的情况下修复:
- 解析 ID —
wpcanai-list-pages/wpcanai-resolve-content-id→ 页面/模板的post_id。 - 将线上 URL 放入媒体库 — 对于每个缺失的资源:可直接引用的 URL →
wpcanai-sideload-url;本地二进制文件 → 使用 sideload REST 端点(POST {site}/wp-json/wpcanai/v1/sideload,这是唯一获准使用的curl— 参见 上传媒体)。记录每个返回的source_url。 - 构建 from→to 映射
{ <原有/失效引用,须与实际内容完全一致> : <新 source_url> }。包含 HTML 中出现的每种形式(./img/x.png、img/x.png、/img/x.png)。 - 应用替换 —
wpcanai-replace-in-meta{ "post_id": <id>, "field": "html", "replacements": [{ "from": "...", "to": "..." }, ...], "require_all": true }。每个受影响的字段/页面调用一次。检查每个count(并让require_all标记任何未匹配的from)。如果资源在其中被引用,则对"field": "css"/"js"重复操作。
创建新模板
wpcanai-create-template{ "title": "...", "type": "component", "html": "...", "css": "..." }- 如果需要布局:
wpcanai-write-meta{ "post_id": <new_id>, "layout": <layout_post_id> }
诊断配置
wpcanai-scan{ }— 委派、布局、模板、结构导航和内容泄漏问题(missing_structure_comment/invalid_structure_comment/leaky_comment/leaky_secret)。wpcanai-manage-tools { "action": "list" }— 列出所有工具,包括隐藏的工具、每个工具被隐藏的原因,以及已公开工具集的令牌预算;wpcanai-diagnostics中的tools和languages分组汇总了相同信息。
注释 / 密钥安全检查
在批量导入后、将网站交还给用户前,或用户要求审查源代码泄漏时运行。采用与 sideload 预检查相同的固定操作流程。
wpcanai-scan{ }— 收集所有结构/注释/密钥问题(记录post_id和field)。- 可选:全站 grep,查找扫描可能无法分类的模式:
对 Twig 使用wpcanai-grep-content { "pattern": "搜索可疑的/* … */注释。对于leaky_secret,从 meta 中移除凭据(将其移至 wp-config / env / 浏览器无法看到的服务端代码片段)。 - 重新扫描,直至不再有
leaky_*问题。只要这些问题仍然存在,就不要声称任务已完成。
为生产环境编译 Tailwind
何时运行: 用户说 /canai-mcp optimize production、/canai-mcp compile tailwindcss、compile tailwind、build css、tailwind build,或以其他方式要求为线上网站预编译 Tailwind 时运行。不要在每次编辑模板后自动运行 — 这是一项明确选择启用的优化步骤。
作用: 默认的 Tailwind 加载方式是 Play CDN(来自插件的 tailwind.min.js 或 cdn.tailwindcss.com),它会在每次页面加载时于访客的浏览器中即时编译工具类。此工作流在 Agent 端预编译 CSS,并将其存储为 meta。存在构建产物时,服务器会内联输出预构建的 CSS,并跳过 Play CDN 脚本 — 访客获取静态 CSS,由 Agent 执行编译。
构建单位 = 布局。 每个布局编译一个 CSS 包,扫描该布局以及通过它渲染的每个页面/模板,并将 CSS 存储在布局文章上。服务器在渲染时收集该布局的构建产物(布局文章会在每个使用该布局的页面上渲染),并仅输出一次。这样,编译后的 CSS 都集中在一处,便于查找 — 位于 CanAI Editor 中布局的 Compiled Tailwind CSS 面板,而不是散落在各个页面中。
存储约定
| Meta 键 | 所属位置 | 用途 |
|---|---|---|
_canai_tailwind_build | 布局文章(wpcanai_template,类型为 layout) | 布局及其所有使用方的编译后 CSS。AssetManager::output_assets_head 会将其作为 <style id="wpcanai-tailwind-css"> 输出,并跳过通过该布局渲染的页面的 Play CDN。 |
_canai_tailwind_hash | 同一布局文章 | 仅供 Agent 端使用,可选。 生成构建产物的输入内容的 sha256。重新编译前进行比较 — 未更改时跳过。新鲜度检测不需要此项;服务器通过 _canai_tailwind_epoch 跟踪新鲜度(见下文服务器端新鲜度)。 |
_canai_tailwind_epoch | 同一布局文章 | 写入时自动设置 — 构建产物编译时的源纪元。服务器会将其与全局纪元比较,以判断是否过期。无需自行设置。 |
_canai_tailwind_built_at | 同一布局文章 | Unix 时间戳;通过 wpcanai-write-meta 写入非空的 tailwind_build 时自动设置(用于面板显示“距构建时间 N”)。无需自行设置。 |
为空或缺失 → 服务器回退到 Play CDN。去重保护:如果渲染集中的某个布局有构建产物,服务器只会输出布局构建产物,并忽略任何页面级 _canai_tailwind_build — 因此,旧版页面级模型遗留在页面上的过期构建产物不会重复输出。手动编辑 wp-admin 仍然有效;只是会使用运行时 CDN,直到下次编译。
服务器端新鲜度(自动)。每次写入 tailwind_build 时,服务器都会为构建产物标记当前的全局源纪元(_canai_tailwind_epoch);每当任何模板源(_canai_html/css/js)或 Tailwind 设置发生变化、区块页面的正文发生变化(所有者在 Gutenberg 中保存,或通过 MCP 写入),或页面的格式标记发生变化时,服务器都会递增该纪元。渲染时,标记落后于当前纪元的构建产物会被视为过期,页面会回退到 Play CDN(结果正确,只是速度较慢),而不是输出过时的 CSS — 因此,编辑后页面绝不会失去样式,即使尚未重新编译也是如此。这就是为什么下文的 tailwind_hash 现在是可选项:正确性不再依赖它。诊断页面和布局的已编译 Tailwind CSS面板会显示哪些构建产物已过期。编辑模板后,重新编译受影响的布局,以恢复内联交付。
工作流
-
读取 Tailwind 设置以获取插件集:
wpcanai-read-settings { "keys": ["wpcanai_tailwind_settings"] }调用编译器时使用返回的
plugins数组(例如,["forms", "container-queries"])。如果load_tailwind为no,请停止并告知用户先启用它(没有 Tailwind 可供编译)。 -
枚举布局 —
wpcanai-list-templates {},保留type为layout的行。每个布局都是一个编译目标。 -
解析每个布局的使用方集合 — 所有有效布局为该布局的文章:
wpcanai-list-pages {}和wpcanai-list-templates {}中layout_id等于此布局 id 的行。- WC 委托正文:
wpcanai-resolve-content-id { "type": "shop" }(以及cart、checkout、my-account、product-category)→_canai_layout解析为此布局的content_post_id。 - 如果此布局是
wpcanai_default_layout,还要包括没有显式_canai_layout的文章(它们会回退到此布局)。 format为blocks的行(wpcanai-list-pages会携带该值)也是使用方 — 它们的正文是post_content,而不是_canai_html,扫描方式与 Twig 正文完全相同(插件 v1.67.0;在此之前,服务器会强制区块页面使用 Play CDN)。
-
构建并集内容 — 将以下内容串接为一个字符串:布局自身的
_canai_html、每个使用方的_canai_html、通过{% include 'slug' %}/{{ wpcanai_template('slug') }}引用的所有局部模板(页眉/页脚 — 将每个 slug 解析为 post id → 读取其_canai_html),以及对于每个format为blocks的使用方,从wpcanai-read-meta { "post_id": <id>, "fields": ["blocks"] }获取的原始区块标记(className值同时存在于区块注释 JSON 和渲染后的class=""中,因此原始标记是正确的扫描输入)。不要将区块页面的css插槽并入并集 — 它是直接输出的纯 CSS,不是 Tailwind 源码,因此不会为扫描贡献任何工具类;而且,对页面仅写入css不会推进源纪元,因此在第 5 步对其进行哈希,会导致构建看起来仍是“最新”,尽管其记录的输入已发生变化。写入临时.html文件,以便 Tailwind 扫描器获取该布局中任何位置实际使用的所有类。 -
对输入进行哈希 — 对
union_content + "\n--\n" + plugins.sort().join(",") + "\n--\n" + tailwind_version计算 sha256。与布局现有的_canai_tailwind_hash比较。相同 → 跳过此布局,计为“已跳过(已是最新)”。将实际编译时使用的 v3 版本作为tailwind_version,确保哈希在不同机器上保持稳定(并确保意外使用 v4 构建时会使哈希失效,而非发生冲突)。使用 Node,而非 Python 进行哈希(参见本地脚本辅助代码)— 例如:node -e "const c=require('crypto'),fs=require('fs');console.log(c.createHash('sha256').update(fs.readFileSync(process.argv[1])).digest('hex'))" hash-input.txt。 -
在本地编译(不在服务器上编译),并使用 Tailwind v3 — 不要使用 v4。运行时交付使用 Tailwind v3 Play CDN — 从插件 v1.58.2 起,默认使用
assets/lib/tailwind/中的预构建包(Resource Source= Internal),仅当站点所有者将来源切换为外部时才使用cdn.tailwindcss.com。无论哪种方式,都是 v3,因此预编译器 MUST 使用 v3 才能与之匹配。有两种运行方式,无需npm run build即可使用:npx tailwindcss@^3 -i input.css -o /dev/stdout --content "<temp.html>"。不要使用npx @tailwindcss/cli— 该包是 Tailwind v4,会生成损坏的构建(见下方警告)。- 使用 v3 版本的独立 Tailwind 二进制文件(例如来自
v3.4.x标签的tailwindcss-macos-arm64— 单个可执行文件,无需 Node)。明确固定 v3;“latest” 二进制文件是 v4。input.css内容为@tailwind base; @tailwind components; @tailwind utilities;。第 1 步中的插件通过临时tailwind.config.js启用(plugins: [require('@tailwindcss/forms'), ...])。由于每个布局只构建一次,Preflight/base 只编译一次,而非每个页面编译一次。
⚠ 使用 v3,绝不要使用
@tailwindcss/cli。 使用 Tailwind v4 时,这套完全相同的流程会悄无声息地产生一个仅适用于移动端的构建:v4 会忽略--content和tailwind.config.js,而旧版@tailwind base/components/utilities指令不会加载 v4 的默认主题 — 因此所有需要主题令牌的工具类都会被丢弃(--breakpoint-*→ 没有响应式md:/lg:变体,--spacing→ 没有p-2,字体大小 → 没有text-sm);只有flex/hidden等不依赖主题的静态类会保留。Tailwind v3 会正确处理@tailwind指令、tailwind.config.js和--content。 -
验证构建,然后写入布局。 写入前,对编译后的 CSS 进行基本检查:其大小不能太小,并且至少包含一个
@media (min-width:规则(即已生成响应式工具类)。如果不满足 — 这是使用 v4 编译的典型症状 — 则构建已损坏(仅适用于移动端);不要写入,将该布局计为失败,并保留当前可用的 Play CDN 回退(_canai_tailwind_build为空或缺失时,服务器会继续提供 CDN)。检查通过后,使用wpcanai-write-meta写入:wpcanai-write-meta { "post_id": <layout_id>, "tailwind_build": "<full compiled CSS>", "tailwind_hash": "<sha256>" }然后,在仍带有旧版逐页编译产物的消费者页面上清除过期的逐页构建:
wpcanai-write-meta { "post_id": <consumer_id>, "tailwind_build": "", "tailwind_hash": "" }。去重保护逻辑已会忽略这些字段,但清除它们能确保数据准确、面板显示正确。只有wpcanai-write-meta会持久化这些字段——请勿使用其他写入路径。 -
报告——
X 个布局已编译,Y 个已跳过(已是最新),Z 个失败,并说明清除了多少个消费者页面的逐页构建。列出失败项及其布局 ID 和原因。
验证结果
在浏览器中查看前端页面。<head> 中应包含 <style id="wpcanai-tailwind-css">…</style>,且不应包含 <script src=".../tailwind.min.js">。如果 Play CDN 脚本仍然出现,可能是页面布局的 _canai_tailwind_build 为空(重新检查步骤 7 是否写入了正确的布局 ID)、页面实际使用的布局与已编译的布局不同,或者 load_tailwind 为 no。
通过已编译布局渲染的区块页面会显示相同的 <style id="wpcanai-tailwind-css">,且不包含 tailwind.min.js。如果该页面仍加载 CDN,而它的 Twig 同级页面没有,则说明构建已过期:区块页面的正文会在每次 Gutenberg 保存后更改(参见何时重新运行)。
何时重新运行
只要布局构建的任何输入发生变化,就需要重新运行:布局自身的 _canai_html(包括其内联 tailwind.config = {…} 代码块——编译后,运行时无法识别其中的新令牌,因为服务器会跳过 Play CDN)、任何消费者页面/模板的 _canai_html、区块页面的正文(所有者每次在 Gutenberg 中保存,或执行 wpcanai-write-page)、页面格式标记的更改、共享局部模板(页眉/页脚)、_canai_layout 分配,或 wpcanai_tailwind_settings 中的插件集。注意每个布局的权衡:**编辑任何一个消费者页面都会使整个布局构建过期。**步骤 5 中的哈希检查让“始终重新运行”的成本很低——只有实际发生变化的布局才会重新编译。
**精细的编辑并不要求重新编译。**服务器会为每个布局构建标记新鲜度纪元,并在渲染时检查构建相对于其来源是否过期;若已过期,则丢弃该构建并回退到 Play CDN(AssetManager::output_assets_head)。因此,执行 replace-in-meta 后页面仍能正确渲染——新的工具类(如 leading-[1.15])会由运行时 CDN 提供;唯一的代价是,在下一次主动编译之前,页面会继续使用 CDN。只有用户要求针对生产环境进行优化时才重新编译,无需在每次内容微调后都编译。
编译后的运行时行为
当渲染页面的布局具有非空的 _canai_tailwind_build 时,AssetManager::output_assets_head 会输出该构建(仅输出该构建——参见去重保护逻辑),并且输出一行垫片代码:<script id="wpcanai-tailwind-shim">window.tailwind=window.tailwind||{config:{}};</script>。该垫片会让布局中的内联 tailwind.config = { ... }(位于 {{ wp_head() }} 之后)成为无害的属性写入,而不是触发 TypeError。结果是:编译后,该代码块不再产生视觉效果——所有主题令牌都必须包含在传给编译器的临时 tailwind.config.js 中。即使布局没有 _canai_css/_canai_js,布局文章也会加入构建队列,因此仅携带构建产物的布局仍能正常输出。
限制 / 注意事项
- 每个布局的合并规模。 布局构建会包含所有使用该布局的页面所用到的全部类,因此每个页面发送的 CSS 都比完全按页面构建的版本更多。作为交换,你可以对外壳(页眉 / 页脚 / 布局)和 Preflight 去重,并将 CSS 集中在一处,便于查找。对于典型网站,这样没有问题;如果一个布局覆盖了大量内容各异的页面,且 CSS 体积成为顾虑,请将这些页面拆分到第二个布局中。
- 重新变旧的耦合。 由于构建按布局进行,编辑任意使用该布局的页面都会使整个布局构建失效。哈希跳过机制可以让重新编译开销很小,但大多数内容编辑后,布局都需要重新编译。
- 从按页面构建迁移。 现有安装中的构建产物位于页面上。在首次按布局重新编译之前,去重保护机制会确保结果正确(存在布局构建时优先使用;否则仍会输出按页面构建的产物)。第 7 步的清理流程会在迁移过程中移除它们。
- 任意值。
class="text-[#abc123]"可以正常使用(Tailwind JIT 会扫描字面字符串);只需确保值在 Twig 源码中是字面值,而不是在渲染时构造。class="text-{{ color }}"不会被检测到——这与任何 Tailwind 构建的限制相同。 - 布局中的自定义
tailwind.config = {…}。 将其同步到临时的tailwind.config.js中,否则编译后的输出会缺少这些标记。提取过程很脆弱:内联代码块是 JS 对象字面量,而不是 JSON(可能包含尾随逗号、单引号、未加引号的键、函数值和注释)。请在 Node 中进行求值(而非 Python——参见 本地脚本粘合代码):将module.exports = { ... }写入临时的tailwind.config.js,然后让编译器通过require()加载它,以便由编写该对象字面量时所用的同一引擎解析。若无法提取出有效配置,请明确报错,不要悄悄发布缺少标记的构建产物。
HTML 操作指南
- 模板使用
**{# Type / Short Label #}** 导航注释标记各区块的边界;请用这些标签查找边界,并忽略{# @dev … #}实现备注(注释规则参见 REFERENCE.md)。 - 移除区块: 从区块开头的 Twig 注释(或可识别的起始位置)起,删除至该区块结束处(下一个同级区块注释、结构性闭合标签或 include 行)。
- 编辑区块: 只替换该区块内的标记;其余字符串保持不变。
- 在 Agent 中构建最终字符串并传递给
**wpcanai-write-meta**——无需外部脚本。
历史记录 / 撤销(快照)
每次写入 CanAI 内容,都会在写入前快照保存到专用表(而非 postmeta)。这是撤销途径——写入出错时应立即使用,而不是试图手动重建之前的 _canai_html。快照没有管理界面;这些工具是唯一入口。
捕获内容。 恰好是以下八个元键:_canai_html、_canai_css、_canai_js、_canai_layout、_canai_delegate_page_id、_canai_context、_canai_context_mode、_canai_format。除此之外一概不捕获——不包括 post_title、post_content 或附件,也不包括页面的别名、状态、父级或顺序:wpcanai-update-page 不会创建快照,其响应中的 previous 就是它的撤销信息。
恢复不一致的快照会重现这种不一致。 如果快照是在页面标记为 blocks,但仍带有非空 _canai_html 时创建的(例如中断转换或其他写入方遗留的过时 Twig 正文),恢复后这两个值都会一并还原:页面仍会在区块编辑器中打开,但渲染出来的是过时的 Twig,而不是区块,因为只有 _canai_html 为空时,前端才会回退到 post_content。
捕获时机。 wpcanai-write-meta、wpcanai-write-page(仅限其转换为区块的路径,操作为 mcp-write-page-convert)、wpcanai-replace-in-meta、wpcanai-create-template、wpcanai-create-page、wpcanai-setup、wpcanai-import、预设安装/卸载、会重写内容的媒体更新,以及每次通过 wp-admin 编辑器保存时,都会自动在写入前创建快照。常规写入时无需调用“创建快照”工具;唯一需要显式执行的情况是下文介绍的 wpcanai-pin-current。
保留策略。 每篇文章最多保留 10 个未固定的快照,形成环形缓冲区——第 11 次写入会移除最旧的快照。固定的快照不会被移除,每篇文章最多可固定 10 个。
操作。 同一次逻辑写入所创建的快照会共用一个 operation_id(例如安装预设时修改了 14 篇文章)。使用 wpcanai-restore-operation 可一次性回滚整个操作,而不必逐个恢复 14 个快照。
紧急开关。 与每个 wpcanai/* 能力一样,站点所有者可在 CanAI → AI Client → Tools 中禁用这些工具;工具被禁用后,就不会出现在工具列表中。
wpcanai-list-snapshots
- 参数:
{ "post_id": int, "limit"?: int }— 必须提供post_id;limit默认为 50,且会限制在 1–200 之间。 - 返回:
{ "snapshots": [{ "id": int, "post_id": int, "operation_id": string, "operation_type": string, "actor": string, "created_at": string, "pinned": bool, "label": string|null, "bytes": int }] }— 按时间从新到旧排列,仅包含元数据。特意排除了载荷:如果每行约 7 KB,10 行就会耗尽上下文,只为回答“我想要哪一个?”。 - 错误: 调用方无权对该文章执行
edit_post时返回forbidden。
wpcanai-get-snapshot
- 参数:
{ "snapshot_id": int }。 - 返回: 同样的摘要字段,另加
"payload"— 捕获的{ "_canai_html": …, "_canai_css": …, … }映射,其中null表示“没有对应的元数据行”。如果需要进行差异比较,请先读取此内容,再执行恢复。 - 错误:
wpcanai_snapshot_not_found、forbidden。
wpcanai-restore-snapshot
- 参数:
{ "snapshot_id": int }。 - 返回:
{ "success": true, "post_id": int }。 - 会先捕获当前状态,因此恢复操作本身也可撤销——恢复了错误的快照可以挽回,不会造成无法恢复的后果。
- 错误:
wpcanai_snapshot_not_found、forbidden、forbidden_unfiltered_html— 恢复会将原始 HTML/JS 写回文章,因此与原始写入时一样,必须具备unfiltered_html权限。(API 密钥调用方可通过此权限检查;使用 Cookie 认证的非管理员可能无法通过。)
wpcanai-list-operations
- 参数:
{ "limit"?: int }— 默认为 20,限制在 1–100 之间。 - 返回:
{ "operations": [{ "operation_id": string, "operation_type": string, "actor": string, "created_at": string, "posts": int }] }— 按时间从新到旧排列,适用于整个站点(而非单个文章)。posts表示该操作影响的文章数量。
wpcanai-restore-operation
- 参数:
{ "operation_id": string }。 - 返回:
{ "success": bool, "restored": int, "failed": int, "post_ids": int[] }。 success的判断依据是failed === 0,而非restored > 0。 部分回滚(15 篇文章中恢复了 14 篇)会返回success: false——绝不能把非零的restored视为“已完成”。请向用户说明部分恢复的状态。- 在写入任何内容之前,会先检查每篇受影响文章的权限:回滚到一半才停止,比完全不开始更糟。
- 错误:
wpcanai_operation_not_found、forbidden(会指出你无权编辑的文章)和forbidden_unfiltered_html。
wpcanai-pin-snapshot
- 参数:
{ "snapshot_id": int, "pinned": bool, "label"?: string }— 必须提供snapshot_id和pinned;label是可选的,仅在固定快照时有意义。 - 返回:
{ "success": true, "snapshot_id": int, "pinned": bool }。 - 固定快照可避免其被环形缓冲区淘汰(每篇文章最多固定 10 个快照)。取消固定始终可用。
- 错误:
wpcanai_snapshot_not_found、forbidden、wpcanai_snapshot_pin_failed。
wpcanai-pin-current
- 参数:
{ "post_id": int, "label": string }— 两项均为必填;label不能为空,且长度不得超过 191 个字符,例如"fix broken product page — verified in browser"。 - 返回:
{ "success": true, "snapshot_id": int, "pinned": true, "label": string, "created_at": string, "reused_existing": bool }。 - 在浏览器中验证更改后,请调用此工具。 快照只会在写入前捕获,因此你刚刚确认可正常工作的状态尚无对应记录可固定,除非有人再次编辑该文章——届时它就成了上一个状态,而且可能已被淘汰。
- 如果当前内容与最新快照一致,则会固定该快照,而不是写入重复快照(
reused_existing: true)。 - 错误:
wpcanai_invalid_label(缺失或超过 191 个字符)、wpcanai_post_not_found、wpcanai_not_canai_post(该文章不含 CanAI 内容——固定它会把“此文章没有任何内容”记录为已验证状态)、forbidden,以及已达到固定数量上限 10 时的错误;请先使用wpcanai-pin-snapshot取消固定一个快照。
恢复工作流
wpcanai-list-snapshots { "post_id": 123 }→ 找到错误写入前的记录(检查created_at/operation_type)。- 可选:
wpcanai-get-snapshot { "snapshot_id": 456 }→ 确认载荷是你想要的内容。 wpcanai-restore-snapshot { "snapshot_id": 456 }。- 在浏览器中验证页面。
wpcanai-pin-current { "post_id": 123, "label": "known-good after rollback" },确保恢复后的状态不会被淘汰。
如果损坏涉及多篇文章(例如错误的预设安装或导入),请直接使用
wpcanai-list-operations → wpcanai-restore-operation。
操作路由(速查)
| 目标 | 工具 |
|---|---|
| 列出模板 | wpcanai-list-templates |
列出 CanAI 页面(所有页面,包括草稿:all_pages: true) | wpcanai-list-pages |
| 读取字段 | wpcanai-read-meta |
| 写入字段 | wpcanai-write-meta |
| 新建模板文章 | wpcanai-create-template |
| 新建页面(包含 CanAI 元数据) | wpcanai-create-page |
| 重命名、修改 slug、发布或移动页面(任何格式) | wpcanai-update-page |
| 读取两个菜单(主菜单、页脚菜单) | wpcanai-get-menus |
| 设置或更改菜单链接 | wpcanai-write-menu(参见 菜单) |
| 将某个位置或语言指向现有菜单,或清除其关联 | wpcanai-assign-menu |
| 读取 WP / WC / CanAI 选项 | wpcanai-read-settings |
| 更新 WP / WC / CanAI 选项 | wpcanai-update-settings |
| 读取任意 wp_option(允许列表) | wpcanai-get-option |
| 更新任意 wp_options(允许列表 / 审批) | wpcanai-update-options |
| 轮询待处理的选项更新 | wpcanai-get-pending |
| 推荐的网站初始化 | wpcanai-setup |
| 查询某种类型对应的文章 ID | wpcanai-resolve-content-id |
| WC 页面 ID | wpcanai-get-wc-page-ids |
| 诊断 | wpcanai-scan |
搜索 _canai_* 元数据 | wpcanai-grep-content |
| 诊断环境 / 网络 | wpcanai-diagnostics |
清除页面缓存(写入操作的 purged 未涵盖时) | wpcanai-purge-cache |
| 清单 + 哈希,不含内容 | wpcanai-export { "summary": true } / wpcanai-read-meta { "hash_only": true } |
| 列出 / 安装 / 移除预设 | wpcanai-list-presets / wpcanai-install-preset(⚠ clean_slate)/ wpcanai-uninstall-preset |
| 导出 / 导入 CanAI 内容 | wpcanai-export / wpcanai-import |
| 撤销错误写入(单篇文章) | wpcanai-list-snapshots → wpcanai-restore-snapshot |
| 撤销整个操作(多篇文章) | wpcanai-list-operations → wpcanai-restore-operation |
| 读取某个快照的载荷 | wpcanai-get-snapshot |
| 检查点验证通过的内容 | wpcanai-pin-current(固定 / 取消固定:wpcanai-pin-snapshot) |
FluentSnippets(选择启用),包括 wpcanai-replace-in-snippet | canai-yolo 技能 — 此处未介绍 |
| 为生产环境预编译 Tailwind | 使用 wpcanai-write-meta 并设置 tailwind_build + tailwind_hash(参见 为生产环境编译 Tailwind) |
| 翻译网站(原生 i18n) | 参见 原生字符串翻译 工作流 |
| 读取 / 设置原生 i18n 语言 | wpcanai-i18n-get-settings / wpcanai-i18n-set-settings |
列出可翻译字符串(单个页面:post_id) | wpcanai-i18n-list-strings |
| 写入翻译(批量) | wpcanai-i18n-set-translations |
按语言设置图片(tmedia) | wpcanai-i18n-get-media-map / wpcanai-i18n-set-media-map |
| 重建字符串索引 | wpcanai-i18n-rescan |
| 文章内容覆盖(CPT / 长篇内容) | wpcanai-i18n-get-post-overrides / wpcanai-i18n-set-post-overrides |
| 术语名称 / 描述覆盖 | wpcanai-i18n-get-term-overrides / wpcanai-i18n-set-term-overrides |
| 内容翻译工作队列 | wpcanai-i18n-list-content |
| 读取待翻译文章的源内容 | wpcanai-i18n-get-content |
| 网站名称 / 标语 / 存档页 SEO(全局字符串) | wpcanai-i18n-get-global-strings / wpcanai-i18n-set-global-strings |
安全
- 将 API 密钥视同密码;不再使用时,请在 AI Client → Connections 中撤销。
- 非本地网站优先使用 HTTPS。