关于 canai-yolo
canai-yolo 是一项可选技能,通过 CanAI MCP 工具管理 FluentSnippets 中的 PHP、CSS 和 JS 代码片段。它专注于片段操作与部署防护,仅用于用户明确要求的片段工作。
主要功能
- 01列出并读取代码片段
- 02创建和更新代码片段
- 03带防护措施发布片段
- 04通过哈希校验替换代码
- 05检查存储代码哈希与缓存状态
- 06说明 CanAI 语言处理方式
适用场景
- 01检查站点专用代码片段
- 02创建或更新 FluentSnippets 代码
- 03发布用户要求的 PHP 片段
- 04验证片段写入与缓存状态
MCP 相关
它使用 CanAI MCP 服务,地址为 `{site}/wp-json/mcp/wpcanai`,并通过 API key 认证。文档介绍了管理 FluentSnippets 的工具,这些工具仅在 FluentSnippets 插件启用时注册。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ usewp/ canai --skill canai-yolo安装后在 Claude Code 中输入/canai-yolo确认已加载。
详细介绍
CanAI YOLO — FluentSnippets MCP 工具
高风险。需主动启用。 此技能介绍的 MCP 工具可以在 WordPress 网站上永久发布 PHP。只有用户明确要求处理代码片段时,才安装并调用此技能。
处理模板、页面、国际化、媒体、设置和 Tailwind 时,请改用 canai-mcp。除非用户要求使用此技能,否则不要在普通的 /canai-mcp 内容会话中混用 YOLO 工作流。
传输方式与 canai-mcp 相同:使用同一个 CanAI MCP 服务({site}/wp-json/mcp/wpcanai)和 API 密钥。能力 ID 使用斜杠;MCP 工具名称使用连字符。
严重警告——何时使用此技能
仅在以下情况使用 canai-yolo:
- FluentSnippets(
easy-code-manager)——通过专用 MCP 工具列出、读取、创建、更新或发布网站的 PHP、CSS 或 JS 代码片段。
没有 eval 逃生通道。
wpcanai/eval能力已在插件 v1.59.0 中移除——WP.org 全面禁止eval()——且不再读取WPCANAI_ENABLE_EVAL。每项能力都是真正的能力;没有任何备用方案。如果某个工作流似乎需要 eval,正确的做法是请求添加缺失的能力,而不是临时变通。计划推出一个可选启用的附加插件(specs/2026-08-20-canai-eval-addon-design.md)。
FluentSnippets(PHP/CSS/JS 代码片段)
网站专用的 PHP、CSS 和 JS 通常存放在 FluentSnippets 中,而不是插件代码里。工具:wpcanai-list-snippets、wpcanai-get-snippet、wpcanai-create-snippet、wpcanai-update-snippet、wpcanai-replace-in-snippet、wpcanai-set-snippet-status。这些工具的描述以 [fluent-snippets] / [fluent-snippets, filesystem] 开头(如果网站在写入后会清除页面缓存,还会带有 cache)——参见 canai-mcp 中的 工具标签。
仅凭架构定义不易看出的关键规则:
-
只有在 FluentSnippets 处于启用状态时才会出现这些工具。 如果
easy-code-manager未启用,这六个代码片段工具就不会注册——你不会在工具列表中看到它们。 -
规范格式(插件 v1.80.0)。 发送 PHP 时,正文中不要包含
<?php——写入时会去除开头的<?php和空行(不再拒绝),读取时也会去除。末尾换行会保留;末尾的?>会被移除。sha256/bytes/lines是基于代码经过get-snippet返回后的原样内容计算的,因此比较时请对该字符串计算哈希,而不是对本地文件计算。css/js 不得自行包裹在<style>/<script>标签中。PHP 会在保存前进行语法检查并试运行一次:不得重新声明已存在的函数或类(用function_exists/class_exists做保护),不得打印输出、在顶层使用return,也不得调用尚未加载的内容(请使用钩子)。 -
创建的默认状态为草稿。
wpcanai-create-snippet会写入草稿,除非你传入status: "published"(v1.80.0);此时会先检查 PHP 语法,若存在语法错误则不会写入任何内容。响应中的status是回读的结果——Fluent Snippets 自带的auto_publish仍可能会将草稿发布。 -
只有允许列表中的分组才能成功写入。 管理员可在 CanAI → AI Client → Guardrails → FluentSnippets group allowlist 中指定 Agent 可写入的代码片段分组。允许列表为空(默认状态)意味着所有代码片段写入都会被拒绝(
snippet_writes_disabled)。写入不在允许列表中的分组会返回snippet_group_not_allowed。除非用户另有指定,否则优先使用分组AI。读取(list/get)始终允许,不受允许列表影响。 -
更新采用稀疏方式。 调用
wpcanai-update-snippet时只发送要更改的字段;服务端会读取当前代码片段,将你的更改合并到其完整元数据中,再写回。省略某个字段会保留其当前值——不能只发送code来清空group/priority/run_at/created_at。 -
run_at因类型而异。 PHP →all|backend|frontend;php_content→shortcode|wp_head|wp_body_open|wp_footer|before_content|after_content;css →wp_head|admin_head|everywhere;js →wp_head|wp_footer|admin_head|admin_footer。无效的类型与运行位置组合会在调用 FluentSnippets 前被拒绝(invalid_snippet_type/invalid_run_at)。 -
上游问题(此处未修复)。 CSS 的
everywhere实际上不会在管理界面加载(插件中的拼写错误:everywehere),而 PHP 的frontend不会被强制限制(会在所有位置运行)——由于这两个值是 FluentSnippets 界面提供的选项,因此仍会被接受。不确定时,优先使用all/backend/wp_head。 -
重新启用。 如果某个代码片段发生致命错误,FluentSnippets 会自动将其禁用,并将
has_error设为 true。除非传入"reactivate": true,否则更新会被拒绝(snippet_has_error);传入该参数会清除错误并应用更新——因此,常规编辑不会悄悄重新启用已知有问题的代码。 -
错误信息会说明原因(v1.80.0)。 如果 Fluent Snippets 拒绝保存,错误信息会返回
"{title} on line {line}: {reason} Fix: {fix}"——重试前请先阅读原因和修复建议;不要再次发送相同代码。
通过 wpcanai-update-snippet 的 group 将代码片段移至其他分组时,当前分组和目标分组都必须在允许列表中。
部署循环(plugin v1.80.0)
无需重新发送,即可编辑在线代码片段,并在不使用浏览器的情况下确认:
- 替换 —
wpcanai-replace-in-snippet { "file_name", "replacements": [...], "require_all": true, "expected_sha256"?: "<sha256 from your last read>" }。require_all会在未找到匹配项时不写入任何内容,并指出最接近的匹配项;如果你读取代码片段后有人对其进行了更改,expected_sha256会以hash_mismatch(409)拒绝写入。不确定锚点是否正确时,先使用dry_run: true。仍需通过wpcanai-update-snippet重写整个正文(使用相同的expected_sha256)。 - 检查 — 响应中包含代码实际存储后的
sha256、bytes和lines,这些值是在写入后回读得到的。将sha256与预期代码的哈希值(基于上述规范化形式)进行比较。之后,或在其他会话中,wpcanai-get-snippet { "file_name", "fields": ["hash"] }会返回相同字段,但不返回代码。 - 缓存 — 响应中的
purged表示是否有待处理的页面缓存清除:{ "scope": "all", "plugin": "…" }表示在请求结束时清除整个站点的缓存(代码片段会在每个页面上运行);null表示未安排任何清除操作。仅当缓存插件处于启用状态且purged为null(写入后清除缓存的功能已关闭),或针对写入范围以外的 URL 时,才调用wpcanai-purge-cache(在canai-mcp中)。
在代码片段中编写自定义 REST 路由
Agent 经常使用 FluentSnippets PHP 路由加上 _canai_js fetch,来实现浏览器 → WordPress 中继。有两条规则仅看 schema 不容易发现:
-
绝不要在
X-WP-Nonce中发送自定义 nonce(也不要使用请求参数_wpnonce)。 WordPress 核心的rest_cookie_check_errors()会在站点范围内的每个 REST 请求中拦截这个确切的标头/参数,并根据内置的'wp_rest'操作对其进行验证——这发生在你的路由的permission_callback运行之前。使用该名称传递自定义操作的 nonce 会始终导致rest_cookie_invalid_nonce错误,从而掩盖你自己的权限逻辑。自定义 nonce 操作应使用其他任意标头名称(例如X-<Your-App>-Nonce)。 -
将 HTTP 状态/错误代码显示给客户端(通过控制台日志或不同的页面状态),即使面向用户的文案仍保持通用也应如此。速率限制
429、nonce403和真正的500是不同的故障;将它们合并为一条消息,会让生产事故无法排查。
代码片段中的访客语言(plugin v1.99.0)
在启用了 CanAI 原生翻译的站点上(wpcanai-i18n-get-settings → enabled: true),凡是写入面向访客文本的代码片段(SEO 元数据、表单标签和消息、邮件、表单提交后的重定向),都必须遵循页面语言。向 CanAI 查询,而不是向 WordPress 查询。 get_locale() / determine_locale() 只有在语言的所有者勾选了以此语言显示 WordPress 和插件文本(plugin v1.100.0)时才会跟随页面语言;否则,在 /ms/ 页面上它们仍会返回站点语言。$_SERVER['REQUEST_URI'] 只有从 parse_request 操作开始才会携带 /ms 前缀(v1.100.0);在此之前以及旧版插件中,CanAI 都会移除该前缀。请向 CanAI 查询:
| 功能 | 返回值 |
|---|---|
wpcanai_current_language() | 语言代号('en'、'ms');若无法确定,则返回 null:管理后台、AJAX、REST(无论通过何种方式访问)、cron、WP-CLI、WordPress 系统路径(如 wp-login.php)以及 index.php 以外的入口点(v1.100.0)、setup_theme 之前,以及只有一种语言的站点。 |
wpcanai_language_from_url( string $url ) | 此站点上该 URL 所呈现内容的语言(先检查 ?lang=,再检查前缀,否则使用默认语言);如果属于其他站点、系统路径(/wp-admin/…、/wp-json/…)或只有一种语言,则返回 null。 |
wpcanai_translate( string $source, array $vars = [], ?string $lang = null ) | CanAI → Translations 中的字符串,按 Twig t() 的方式原样呈现(内联标签、{placeholders},绝不显示过期译文);对于默认语言或未知语言,返回源字符串。$lang 为 null 时,使用 wpcanai_current_language()。 |
do_action( 'wpcanai_language_detected', $lang, $request_uri )(v1.100.0) | 这是一个操作钩子,不是函数:每次前端请求触发一次,在 setup_theme 优先级 1 时触发,此时语言刚刚确定。$request_uri 是访问者的 URI,未经处理且带斜杠:使用 wp_unslash() 处理,并在输出前进行转义。 |
// This request's language: the page's, else the form's hidden field, else the Referer.
if ( ! function_exists( 'mysite_request_lang' ) ) {
function mysite_request_lang(): string {
if ( ! function_exists( 'wpcanai_current_language' ) ) {
return 'en'; // CanAI older than 1.99.0: the default language
}
$lang = wpcanai_current_language();
if ( null === $lang && isset( $_POST['mysite_lang'] ) ) {
$lang = sanitize_key( wp_unslash( $_POST['mysite_lang'] ) ); // an unknown slug translates to the source
}
return $lang ?? wpcanai_language_from_url( (string) wp_get_referer() ) ?? 'en';
}
}
// In the code that prints the form (a shortcode, a form plugin's markup filter), never at the top level:
printf( '<input type="hidden" name="mysite_lang" value="%s">', esc_attr( mysite_request_lang() ) );
// Wherever visitor-facing text is built, on the page or in the form handler:
$subject = wpcanai_translate( 'Your download is ready', array(), mysite_request_lang() );
规则:
- 在钩子内读取语言(
template_redirect、wp_head、表单插件的筛选器),不要在代码片段的顶层读取:CanAI 加载后这些函数就已存在,但只有到setup_theme才能确定语言;保存时的测试运行没有页面,因此返回null。若代码必须在template_redirect之前执行操作,请使用wpcanai_language_detected钩子。 - 绝不要自行推导语言。 不要解析
REQUEST_URI(只有路由完成后才会出现前缀,而且?lang=和系统路径遵循 CanAI 的规则),不要使用get_locale()(只有站点所有者启用相应设置时,它才会跟随语言),不要使用wpcanai_langcookie(它记录访问者最近查看的翻译页面,而非当前所在页面),也不要使用 CanAI 内部实现(MenuLocations::language_manager()、new StringStore()、对LanguageManager方法调用remove_action)——这些内容可能在任何版本中变更。 - 提交到
admin-ajax.php/ REST 的表单没有页面语言(wpcanai_current_language()返回null)。在表单渲染时将语言写入表单(使用wpcanai_current_language()设置隐藏字段),并优先读取该字段;wpcanai_language_from_url( (string) wp_get_referer() )是备用方案,因为浏览器不一定会发送 Referer。如果后续步骤(邮件、排队任务)需要语言,请将其存储在条目中——cron 运行时没有请求可供查询。 - 输出结果时进行转义。 纯源字符串会以纯文本形式返回(类似
__());带有内联标签的字符串会以 HTML 形式返回,其中的文本已完成转义。 - 仅供 PHP 使用的字符串不会列在 Strings 标签页或
wpcanai-i18n-list-strings中(两者都只列出模板和菜单中的t()源字符串)。使用wpcanai-i18n-set-translations(位于canai-mcp中)存储这些字符串的译文,键必须与代码片段传入的源字符串完全一致,并向用户提供源字符串 → 译文表。 - Cookie 重定向。 如果未登录访问者的语言 cookie 指定了某种译文,访问者从无前缀的 CanAI 页面访问时会收到 302 重定向,前往该译文页面。如果站点所有者希望每个 URL 都呈现其自身对应的语言:
add_filter( 'wpcanai_redirect_to_cookie_language', '__return_false' );(参数为$redirect, $lang, $target)。不要对 CanAI 的钩子调用remove_action。
wpcanai-list-snippets
- 参数:
{ "status"?: string, "type"?: string, "group"?: string, "include_hash"?: bool }— 所有筛选条件均为可选项(status:published|draft;type:PHP|php_content|css|js;group:分组名称)。(v1.80.0) 设置include_hash: true后,会为每个代码片段添加sha256、bytes、lines(需要读取每个文件,因此需选择性启用)。仅在 FluentSnippets 已启用时显示。 - 返回:
{ "snippets": [{ "file_name", "name", "type", "status", "run_at", "priority", "group", "tags", "description", "created_at", "updated_at", "has_error", "error_message", "writable" }], "groups": string[], "writable_groups": string[] }—writable是针对单个代码片段的标记(其分组在允许列表中);writable_groups是当前允许列表,便于你在尝试写入前了解可修改的分组。读取不受限制。
wpcanai-get-snippet
- 参数:
{ "file_name": string, "fields"?: ["meta"|"code"|"hash"] }— 代码片段的文件名(其标识符,例如3-my-snippet.php);fields默认为全部三项。 - **返回:**完整元数据 +
code正文 + 错误状态 +writable(与list-snippets行包含相同字段,另加code和condition)+ (v1.80.0)sha256、bytes、lines。fields: ["hash"]仅返回file_name、status、group、updated_at、sha256、bytes、lines— 可用它确认写入结果,而无需拉取代码。文件未知时返回错误snippet_not_found。
wpcanai-create-snippet
- 参数:
{ "name": string, "type": string, "run_at": string, "group": string, "code": string, "description"?: string, "tags"?: string, "priority"?: int, "status"?: "draft"|"published" }—name/type/run_at/group/code均为必填项。PHPcode不带<?php(如果开头包含该标记,则会被移除)。type/run_at会根据类型表进行验证。group必须在可写允许列表中。status默认为draft;若为published,则会先对 PHP 代码进行语法检查。 - 返回:
{ "success": true, "file_name": string, "status": string, "group": string, "note": string, "sha256", "bytes", "lines", "purged" }— 文件名会自动生成(无法自行指定),后续所有调用都以此为键。会回读status;稍后可通过wpcanai-set-snippet-status让草稿生效。
wpcanai-update-snippet
- 参数:
{ "file_name": string, "code"?: string, "name"?: string, "description"?: string, "tags"?: string, "group"?: string, "run_at"?: string, "priority"?: int, "reactivate"?: bool, "expected_sha256"?: string }—file_name为必填项;仅发送需要更改的字段(稀疏更新 — 服务会与当前元数据合并)。当前分组(以及移动时的目标group)必须在允许列表中。出错的代码片段需要设置reactivate: true。(v1.80.0)expected_sha256可拒绝过期写入,并返回hash_mismatch(409;消息中包含两个哈希值,不会写入任何内容)。 - 返回:
{ "success": true, "file_name": string, "changed_fields": string[], "sha256", "bytes", "lines", "purged" }— 哈希字段会在写入后回读。 - 已发布的 PHP 代码更新:过去,更新已发布 PHP 代码片段的
code会因Cannot redeclare function而触发致命错误(FluentSnippets 会在已加载在线代码片段的同一请求中进行验证)。CanAI 会透明地处理此问题。如果较旧版本的插件仍返回该错误,请通过wpcanai-set-snippet-status先取消发布,再更新,最后重新发布。
wpcanai-replace-in-snippet(插件 v1.80.0)
- 参数:
{ "file_name": string, "replacements": [{ "from": string, "to": string }], "require_all"?: bool, "ignore_whitespace"?: bool, "regex"?: bool, "scope"?: { "text": string, "occurrence"?: int }, "dry_run"?: bool, "expected_sha256"?: string }— 将wpcanai-replace-in-meta契约(参见canai-mcp)应用于代码片段的代码:在replace_no_match消息中提供相同的require_all未匹配诊断信息,并支持相同的ignore_whitespace/regex/scope/dry_run。写入操作通过update-snippet执行(发布的 PHP 会先进行 lint 检查),并使用相同的组允许列表。 - 返回:
{ "success", "file_name", "total", "dry_run", "replacements": [{ "from", "to", "count", "suggestion"? }], "sha256", "bytes", "lines", "purged"? }— 执行dry_run时,哈希值针对当前代码,且不会返回purged。
wpcanai-set-snippet-status
- 参数:
{ "file_name": string, "status": "published"|"draft" }— 代码片段所属组必须在允许列表中。 - 返回:
{ "success": true, "file_name": string, "status": string, "sha256", "bytes", "lines", "purged" }。会触发 FluentSnippets 的两个生命周期操作,以重建索引缓存,并确保已发布的代码片段实际运行。
操作路由(速查)
| 目标 | 工具 |
|---|---|
| 列出 / 读取 FluentSnippets 代码片段 | wpcanai-list-snippets / wpcanai-get-snippet |
| 创建 / 更新代码片段 | wpcanai-create-snippet(status 可选)/ wpcanai-update-snippet |
| 编辑代码片段的部分内容(部署循环) | wpcanai-replace-in-snippet → 检查 sha256 → 检查 purged |
| 确认已存储的内容,不包含代码 | wpcanai-get-snippet { "fields": ["hash"] } / wpcanai-list-snippets { "include_hash": true } |
| 发布 / 取消发布代码片段 | wpcanai-set-snippet-status |
| 代码片段中访客的语言 / 翻译文本(v1.99.0) | PHP 中的 wpcanai_current_language()、wpcanai_language_from_url()、wpcanai_translate() — 参见 代码片段中的访客语言;通过 wpcanai-i18n-set-translations(canai-mcp)管理翻译 |
安装
npx skills add usewp/canai --skill canai-yolo
与 canai-mcp 配合使用,在同一 MCP 端点上处理内容。