{"owner":"avwo","repo":"whistle","hasSkills":true,"totalSkillsCount":208,"totalTokensCount":239231,"categories":["anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["docs/docs/extensions/api.md","docs/docs/extensions/dev.md","docs/docs/extensions/npm.md","docs/docs/extensions/usage.md","docs/docs/rules/@.md","docs/docs/rules/attachment.md","docs/docs/rules/auth.md","docs/docs/rules/cache.md","docs/docs/rules/cipher.md","docs/docs/rules/cssAppend.md","docs/docs/rules/cssBody.md","docs/docs/rules/cssPrepend.md","docs/docs/rules/delete.md","docs/docs/rules/disable.md","docs/docs/rules/enable.md","docs/docs/rules/excludeFilter.md","docs/docs/rules/file.md","docs/docs/rules/filters.md","docs/docs/rules/forwardedFor.md","docs/docs/rules/frameScript.md","docs/docs/rules/headerReplace.md","docs/docs/rules/host.md","docs/docs/rules/htmlAppend.md","docs/docs/rules/htmlBody.md","docs/docs/rules/htmlPrepend.md","docs/docs/rules/http.md","docs/docs/rules/https-proxy.md","docs/docs/rules/https.md","docs/docs/rules/ignore.md","docs/docs/rules/includeFilter.md","docs/docs/rules/inherit.md","docs/docs/rules/jsAppend.md","docs/docs/rules/jsBody.md","docs/docs/rules/jsPrepend.md","docs/docs/rules/lineProps.md","docs/docs/rules/locationHref.md","docs/docs/rules/log.md","docs/docs/rules/method.md","docs/docs/rules/operation.md","docs/docs/rules/pac.md","docs/docs/rules/pathReplace.md","docs/docs/rules/pattern.md","docs/docs/rules/pipe.md","docs/docs/rules/plugin-vars.md","docs/docs/rules/protocols.md","docs/docs/rules/proxy.md","docs/docs/rules/rawfile.md","docs/docs/rules/redirect.md","docs/docs/rules/referer.md","docs/docs/rules/replaceStatus.md","docs/docs/rules/reqAppend.md","docs/docs/rules/reqBody.md","docs/docs/rules/reqCharset.md","docs/docs/rules/reqCookies.md","docs/docs/rules/reqCors.md","docs/docs/rules/reqDelay.md","docs/docs/rules/reqHeaders.md","docs/docs/rules/reqMerge.md","docs/docs/rules/reqPrepend.md","docs/docs/rules/reqReplace.md","docs/docs/rules/reqRules.md","docs/docs/rules/reqScript.md","docs/docs/rules/reqSpeed.md","docs/docs/rules/reqType.md","docs/docs/rules/reqWrite.md","docs/docs/rules/reqWriteRaw.md","docs/docs/rules/resAppend.md","docs/docs/rules/resBody.md","docs/docs/rules/resCharset.md","docs/docs/rules/resCookies.md","docs/docs/rules/resCors.md","docs/docs/rules/resDelay.md","docs/docs/rules/resHeaders.md","docs/docs/rules/resMerge.md","docs/docs/rules/resPrepend.md","docs/docs/rules/resReplace.md","docs/docs/rules/resRules.md","docs/docs/rules/resScript.md","docs/docs/rules/resSpeed.md","docs/docs/rules/resType.md","docs/docs/rules/resWrite.md","docs/docs/rules/resWriteRaw.md","docs/docs/rules/responseFor.md","docs/docs/rules/rule.md","docs/docs/rules/skip.md","docs/docs/rules/sniCallback.md","docs/docs/rules/socks.md","docs/docs/rules/statusCode.md","docs/docs/rules/style.md","docs/docs/rules/tpl.md","docs/docs/rules/trailers.md","docs/docs/rules/tunnel.md","docs/docs/rules/ua.md","docs/docs/rules/urlParams.md","docs/docs/rules/weinre.md","docs/docs/rules/ws.md","docs/docs/rules/wss.md","docs/docs/rules/xfile.md","docs/docs/rules/xhost.md","docs/docs/rules/xhttps-proxy.md","docs/docs/rules/xproxy.md","docs/docs/rules/xrawfile.md","docs/docs/rules/xsocks.md","docs/docs/rules/xtpl.md","docs/en/docs/extensions/api.md","docs/en/docs/extensions/dev.md","docs/en/docs/extensions/npm.md","docs/en/docs/extensions/usage.md","docs/en/docs/rules/@.md","docs/en/docs/rules/attachment.md","docs/en/docs/rules/auth.md","docs/en/docs/rules/cache.md","docs/en/docs/rules/cipher.md","docs/en/docs/rules/cssAppend.md","docs/en/docs/rules/cssBody.md","docs/en/docs/rules/cssPrepend.md","docs/en/docs/rules/delete.md","docs/en/docs/rules/disable.md","docs/en/docs/rules/enable.md","docs/en/docs/rules/excludeFilter.md","docs/en/docs/rules/file.md","docs/en/docs/rules/filters.md","docs/en/docs/rules/forwardedFor.md","docs/en/docs/rules/frameScript.md","docs/en/docs/rules/headerReplace.md","docs/en/docs/rules/host.md","docs/en/docs/rules/htmlAppend.md","docs/en/docs/rules/htmlBody.md","docs/en/docs/rules/htmlPrepend.md","docs/en/docs/rules/http.md","docs/en/docs/rules/https-proxy.md","docs/en/docs/rules/https.md","docs/en/docs/rules/ignore.md","docs/en/docs/rules/includeFilter.md","docs/en/docs/rules/inherit.md","docs/en/docs/rules/jsAppend.md","docs/en/docs/rules/jsBody.md","docs/en/docs/rules/jsPrepend.md","docs/en/docs/rules/lineProps.md","docs/en/docs/rules/locationHref.md","docs/en/docs/rules/log.md","docs/en/docs/rules/method.md","docs/en/docs/rules/operation.md","docs/en/docs/rules/pac.md","docs/en/docs/rules/pathReplace.md","docs/en/docs/rules/pattern.md","docs/en/docs/rules/pipe.md","docs/en/docs/rules/plugin-vars.md","docs/en/docs/rules/protocols.md","docs/en/docs/rules/proxy.md","docs/en/docs/rules/rawfile.md","docs/en/docs/rules/redirect.md","docs/en/docs/rules/referer.md","docs/en/docs/rules/replaceStatus.md","docs/en/docs/rules/reqAppend.md","docs/en/docs/rules/reqBody.md","docs/en/docs/rules/reqCharset.md","docs/en/docs/rules/reqCookies.md","docs/en/docs/rules/reqCors.md","docs/en/docs/rules/reqDelay.md","docs/en/docs/rules/reqHeaders.md","docs/en/docs/rules/reqMerge.md","docs/en/docs/rules/reqPrepend.md","docs/en/docs/rules/reqReplace.md","docs/en/docs/rules/reqRules.md","docs/en/docs/rules/reqScript.md","docs/en/docs/rules/reqSpeed.md","docs/en/docs/rules/reqType.md","docs/en/docs/rules/reqWrite.md","docs/en/docs/rules/reqWriteRaw.md","docs/en/docs/rules/resAppend.md","docs/en/docs/rules/resBody.md","docs/en/docs/rules/resCharset.md","docs/en/docs/rules/resCookies.md","docs/en/docs/rules/resCors.md","docs/en/docs/rules/resDelay.md","docs/en/docs/rules/resHeaders.md","docs/en/docs/rules/resMerge.md","docs/en/docs/rules/resPrepend.md","docs/en/docs/rules/resReplace.md","docs/en/docs/rules/resRules.md","docs/en/docs/rules/resScript.md","docs/en/docs/rules/resSpeed.md","docs/en/docs/rules/resType.md","docs/en/docs/rules/resWrite.md","docs/en/docs/rules/resWriteRaw.md","docs/en/docs/rules/responseFor.md","docs/en/docs/rules/rule.md","docs/en/docs/rules/skip.md","docs/en/docs/rules/sniCallback.md","docs/en/docs/rules/socks.md","docs/en/docs/rules/statusCode.md","docs/en/docs/rules/style.md","docs/en/docs/rules/tpl.md","docs/en/docs/rules/trailers.md","docs/en/docs/rules/tunnel.md","docs/en/docs/rules/ua.md","docs/en/docs/rules/urlParams.md","docs/en/docs/rules/weinre.md","docs/en/docs/rules/ws.md","docs/en/docs/rules/wss.md","docs/en/docs/rules/xfile.md","docs/en/docs/rules/xhost.md","docs/en/docs/rules/xhttps-proxy.md","docs/en/docs/rules/xproxy.md","docs/en/docs/rules/xrawfile.md","docs/en/docs/rules/xsocks.md","docs/en/docs/rules/xtpl.md"],"skills":{"docs/docs/extensions/api.md":"# Local Agent API\n\nWhistle 的本地 API，供 AI Agent 或第三方进程以编程方式操作 Whistle 代理服务。支持规则管理、插件控制、抓包数据获取、HTTPS 代理开关、证书管理等核心功能，具体用法如下（确保已全局安装 Whistle 且版本 ≥ 2.10.7）：\n\n``` javascript\nconst cp = require('child_process');\nconst qs = require('querystring');\n\nconst loadModule = require;\nlet w2Api;\n\n/**\n * @async 异步加载 Whistle API 模块\n * @function loadApi\n * @description\n * 尝试加载 Whistle 的 API 模块，并校验其版本是否符合最低要求（≥ 2.10.7）。\n * 如果版本过低，或加载过程中发生任何错误（如子进程调用失败、模块加载异常、网络状态获取失败等），\n * 都会抛出对应的错误。\n * \n * @returns {Promise<Object>} 返回 Whistle API 模块对象（通常包含 network、rules 等接口）\n * \n * @throws {Error} 当执行 `w2 root bin/api` 命令失败时抛出（例如 whistle 未安装或未在 PATH 中，需要通过 `npm install -g whistle` 安装）\n * @throws {Error} 当调用 `api.network.getStatus()` 失败时抛出（例如 whistle 或 whistle client 服务未启动）\n * @throws {Error} 当 Whistle 版本低于 2.10.7 时抛出，错误信息为 'Whistle version must be >= 2.10.7'\n * \n * @example\n * try {\n *   const api = await loadApi();\n *   // 使用 api.network.getStatus() 等\n * } catch (err) {\n *   console.error('加载 Whistle API 失败:', err.message);\n * }\n */\nconst loadApi = async () => {\n  if (!w2Api) {\n    w2Api = loadModule(cp.spawnSync('w2', ['root', 'bin/api']).stdout.toString().trim());\n  }\n  const { version } = await w2Api.network.getStatus();\n  const [major, minor, patch] = version.split('.').map(Number);\n  w2Api.version = version;\n  if (major > 2 || (major == 2 && (minor > 10 || (minor == 10 && patch >= 7)))) {\n    return w2Api;\n  }\n  throw new Error('Whistle version must be >= 2.10.7');\n};\n\n/**\n * @abstract 判断当前 Whistle 是否启用了 HTTPS 代理\n * @function isEnabledHTTPS\n * @description\n * 通过 Whistle API 检查当前代理是否启用了 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了 HTTPS 代理\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.isEnabledHTTPS()` 调用失败\n *\n * @example\n * try {\n *   const enabled = await isEnabledHTTPS();\n *   console.log(`当前 HTTPS 代理是否启用: ${enabled}`);\n * } catch (err) {\n *   console.error('检查 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst isEnabledHTTPS = async () => {\n  const api = await loadApi();\n  return await api.isEnabledHTTPS();\n};\n\n/**\n * @async 设置 Whistle 是否启用 HTTPS 代理\n * @function setEnableHTTPS\n * @description\n * 通过 Whistle API 设置当前代理是否启用 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用 HTTPS 代理\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.setEnableHTTPS()` 调用失败\n *\n * @example\n * try {\n *   await setEnableHTTPS(true);\n *   console.log('设置 HTTPS 代理启用状态成功');\n * } catch (err) {\n *   console.error('设置 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst setEnableHTTPS = async (enable) => {\n  const api = await loadApi();\n  await api.setEnableHTTPS(enable);\n};\n\n/**\n * @async 获取 Whistle 的根证书信息\n * @function getRootCA\n * @description\n * 通过 Whistle API 获取当前代理的根证书信息，包括证书内容、私钥等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Buffer>} 返回根证书的 Buffer 对象，可用于保存为 .crt 文件或进行其他操作\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getRootCA()` 调用失败\n */\nconst getRootCA = async () => {\n  const api = await loadApi();\n  return await api.getRootCA();\n};\n\n/**\n * @async 创建一个临时文件并写入指定内容\n * @function createFile\n * @description\n * 通过 Whistle API 创建一个临时文件，并将指定的内容写入该文件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {Buffer|string} data - 要创建的文件内容，可以是 Buffer 或字符串\n * @returns {Promise<string>} 返回创建的文件路径\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.createFile()` 调用失败\n *\n * @example\n * try {\n *   const filePath = await createFile('test');\n *   console.log(`临时文件已创建: ${filePath}`);\n *   // 临时文件已创建: temp/7d2e9a424672279e5bcaab948a7a63fd47cb381626555835e828438762f86a96\n * } catch (err) {\n *   console.error('创建临时文件失败:', err.message);\n * }\n */\nconst createFile = async (data) => {\n  const api = await loadApi();\n  return await api.createFile(data);\n};\n\n/**\n * @async 获取指定文件的内容\n * @function getFile\n * @description\n * 通过 Whistle API 获取指定路径的文件内容，返回其字符串内容。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} filePath - 要读取的文件路径\n * @returns {Promise<string>} 返回文件内容的字符串\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getFile()` 调用失败（如文件不存在）\n */\nconst getFile = async (filePath) => {\n  const api = await loadApi();\n  return await api.getFile(filePath);\n};\n\n/**\n * @async 获取 Whistle 规则引擎的当前状态配置\n * @function getRulesStatus\n * @description\n * 通过 Whistle API 获取规则列表、启用状态以及优先级策略等运行时配置。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<{\n *   laterRulesFirst: boolean,   // 是否启用“后置规则优先”策略（即后匹配的规则覆盖前匹配的）\n *   multiSelect: boolean,       // 是否启用多选模式\n *   pluginsDisabled: boolean,   // 是否全局禁用所有插件规则\n *   disabled: boolean,          // 是否全局禁用 Whistle 代理（所有规则失效）\n *   list: Array<{\n *     name: string,            // 规则文件/配置的名称\n *     selected: boolean        // 当前是否处于激活（勾选）状态\n *   }>\n * }>} 返回包含规则状态和列表的对象\n *\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.getStatus()` 调用失败\n *\n * @example\n * try {\n *   const { list, disabled, multiSelect, laterRulesFirst } = await getRulesStatus();\n *   const activeRules = list.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则状态失败:', err.message);\n * }\n */\nconst getRulesStatus = async () => {\n  const api = await loadApi();\n  return await api.rules.getStatus();\n};\n\n/**\n * @async 关闭 Whistle 规则引擎\n * @function turnOffRules\n * @description\n * 通过 Whistle API 全局禁用所有规则（不包括插件规则）。\n * 该操作会立即生效，影响当前代理会话中的所有请求处理。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOff()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOffRules();\n *   console.log('规则已关闭，所有请求将不再受 Whistle 规则影响');\n * } catch (err) {\n *   console.error('关闭规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOffRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOff();\n};\n\n/**\n * @async 启用 Whistle 规则引擎\n * @function turnOnRules\n * @description\n * 通过 Whistle API 全局启用之前配置的所有规则（不包括插件的规则）。\n * 该操作会立即生效，恢复代理请求的规则处理逻辑。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOn()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOnRules();\n *   console.log('规则已启用，请求将按配置规则处理');\n * } catch (err) {\n *   console.error('启用规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOnRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOn();\n};\n\n/**\n * @async 选中（激活）指定的 Whistle 规则文件，并自动启用规则引擎\n * @function selectRule\n * @description\n * 调用 Whistle API 选中指定名称的规则文件（通常从规则列表中选择），\n * 若选中操作成功（返回 truthy 值），则自动调用 `turnOnRules()` 启用全局规则引擎，\n * 使该规则立即生效。该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要激活的规则文件名称（需与规则列表中的名称精确匹配）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.select()` 调用失败（如网络异常）\n * @throws {Error} 若选中成功但启用规则引擎（`turnOnRules()`）失败时抛出\n *\n * @example\n * try {\n *   await selectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已选中并启用');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst selectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.select(ruleName);\n  if (result) {\n    await turnOnRules();\n  }\n  await result;\n};\n\n/**\n * @async 取消选中（停用）指定的 Whistle 规则文件\n * @function unselectRule\n * @description\n * 调用 Whistle API 取消选中指定名称的规则文件（通常从规则列表中选择），\n * 该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要取消激活的规则文件名称（需与规则列表中的名称精确匹配，如果 ruleName 不填则取消所有规则）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.unselect()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await unselectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已取消选中');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst unselectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.unselect(ruleName);\n  await result;\n};\n\n/**\n * @async 获取指定规则文件的内容详情\n * @function getRule\n * @description\n * 通过 Whistle API 读取指定名称的规则文件内容，返回其详细配置信息。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 规则文件的名称（需与 Whistle 规则列表中的名称精确匹配）\n *\n * @returns {Promise<{\n *   value: string,       // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean,     // 当前是否被选中\n * }>} 返回规则对象，具体字段可能因 Whistle 版本而异\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.get()` 调用失败（如规则名称不存在）\n *\n * @example\n * try {\n *   const rule = await getRule('my-rules');\n *   console.log(`规则的内容:\\n${rule.value}`);\n *   if (rule.selected) {\n *     console.log('该规则当前已激活');\n *   }\n * } catch (err) {\n *   console.error('获取规则失败:', err.message);\n * }\n */\nconst getRule = async (ruleName) => {\n  const api = await loadApi();\n  return await api.rules.get(ruleName);\n};\n\n/**\n * @async 获取当前 Whistle 规则列表\n * @function getRuleList\n * @description\n * 通过 Whistle API 获取所有已配置的规则文件列表，包含每个规则的名称、内容及激活状态。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @returns {Promise<Array<{\n *   name: string,      // 规则文件名称\n *   value: string,     // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean  // 当前是否被选中（激活）\n * }>>} 返回规则对象数组，若无可返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.getList()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   const rules = await getRuleList();\n *   console.log(`共 ${rules.length} 条规则`);\n *   const activeRules = rules.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则列表失败:', err.message);\n * }\n */\nconst getRuleList = async () => {\n  const api = await loadApi();\n  return await api.rules.getList();\n};\n\n/**\n * @async 判断当前 Whistle 是否启用了多选模式\n * @function isMultiSelect\n * @description\n * 通过 Whistle API 检查当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了多选模式\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.isMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   const multiSelect = await isMultiSelect();\n *   console.log(`当前规则引擎是否启用多选模式: ${multiSelect}`);\n * } catch (err) {\n *   console.error('检查多选模式状态失败:', err.message);\n * }\n */\nconst isMultiSelect = async () => {\n  const api = await loadApi();\n  return await api.rules.isMultiSelect();\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用多选模式\n * @function setMultiSelect\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用多选模式\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   await setMultiSelect(true);\n *   console.log('已启用多选模式');\n * } catch (err) {\n *   console.error('设置多选模式失败:', err.message);\n * }\n */\nconst setMultiSelect = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setMultiSelect(enable);\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用后置规则优先\n * @function setLaterRulesFirst\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了后置规则优先。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {boolean} enable - 是否启用后置规则优先\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setLaterRulesFirst()` 调用失败\n *\n * @example\n * try {\n *   await setLaterRulesFirst(true);\n *   console.log('已启用后置规则优先');\n * } catch (err) {\n *   console.error('设置后置规则优先失败:', err.message);\n * }\n */\nconst setLaterRulesFirst = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setLaterFirst(enable);\n};\n\n/**\n * @async 添加一个新的规则文件到 Whistle 规则引擎中\n * @function addRule\n * @description\n * 通过 Whistle API 添加一个新的规则文件，并可选择是否立即选中（激活）该规则。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} name - 规则名称\n * @param {string} value - 规则内容\n * @param {boolean} selected - 是否选中\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.add()` 调用失败\n *\n * @example\n * try {\n *   await addRule('my-rule', '* www.example.com 127.0.0.1', true);\n *   console.log('已添加规则');\n * } catch (err) {\n *   console.error('添加规则失败:', err.message);\n * }\n */\nconst addRule = async (name, value, selected) => {\n  const api = await loadApi();\n  await api.rules.add(name, value, selected);\n};\n\n/**\n * @async 将指定规则文件移动到规则列表顶部\n * @function moveRuleToTop\n * @description\n * 通过 Whistle API 将指定规则文件移动到规则列表的顶部。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} ruleName - 规则文件的名称（需与规则列表中的名称精确匹配）\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.moveToTop()` 调用失败\n *\n * @example\n * try {\n *   await moveRuleToTop('my-rule');\n *   console.log('已将规则移动到顶部');\n * } catch (err) {\n *   console.error('移动规则失败:', err.message);\n * }\n */\nconst moveRuleToTop = async (ruleName) => {\n  const api = await loadApi();\n  await api.rules.moveToTop(ruleName);\n};\n\n/**\n * @async 获取 Whistle 的 Values 列表\n * @function getValueList\n * @description\n * 通过 Whistle API 获取当前配置的 Values 列表，返回每个 Value 的名称和值。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Array<{ name: string, value: string }>>} 返回 Values 对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.getList()` 调用失败\n *\n * @example\n * try {\n *   const values = await getValueList();\n *   console.log(`共 ${values.length} 个 Values`);\n *   values.forEach(v => console.log(`${v.name}: ${v.value}`));\n * } catch (err) {\n *   console.error('获取 Values 列表失败:', err.message);\n * }\n */\nconst getValueList = async () => {\n  const api = await loadApi();\n  return await api.values.getList();\n};\n\n/**\n * @async 获取指定的 Value\n * @function getValue\n * @description\n * 通过 Whistle API 获取指定的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @returns {Promise<{ name: string, value: string }>} 返回 Value 对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.get()` 调用失败\n *\n * @example\n * try {\n *   const value = await getValue('my-value');\n *   console.log(`${value.name}: ${value.value}`);\n * } catch (err) {\n *   console.error('获取 Value 失败:', err.message);\n * }\n */\nconst getValue = async (name) => {\n  const api = await loadApi();\n  return await api.values.get(name);\n};\n\n/**\n * @async 添加一个新的 Value\n * @function addValue\n * @description\n * 通过 Whistle API 添加一个新的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @param {string} value - Value 的值\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.add()` 调用失败\n *\n * @example\n * try {\n *   await addValue('my-value', 'some-value');\n *   console.log('已添加 Value');\n * } catch (err) {\n *   console.error('添加 Value 失败:', err.message);\n * }\n */\nconst addValue = async (name, value) => {\n  const api = await loadApi();\n  await api.values.add(name, value);\n};\n\n/**\n * @async 获取 Whistle 插件的状态信息\n * @function getPluginsStatus\n * @description\n * 通过 Whistle API 获取当前已安装插件的状态信息，包括每个插件的名称、启用状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{ name: string, moduleName: string, version: string, selected: boolean }>>} 返回插件状态对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getStatus()` 调用失败\n * \n * @example\n * try {\n *   const plugins = await getPluginsStatus();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => console.log(`${p.name}: ${p.selected ? '启用' : '禁用'}`));\n * } catch (err) {\n *   console.error('获取插件状态失败:', err.message);\n * }\n */\nconst getPluginsStatus = async () => {\n  const api = await loadApi();\n  return await api.plugins.getStatus();\n};\n\n/**\n * @async 启用 Whistle 插件\n * @function turnOnPlugins\n * @description\n * 通过 Whistle API 启用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOn()` 调用失败\n * \n * @example\n * try {\n *   await turnOnPlugins();\n *   console.log('已启用所有插件');\n * } catch (err) {\n *   console.error('启用插件失败:', err.message);\n * }\n */\nconst turnOnPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOn();\n};\n\n/**\n * @async 禁用 Whistle 插件\n * @function turnOffPlugins\n * @description\n * 通过 Whistle API 禁用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOff()` 调用失败\n * \n * @example\n * try {\n *   await turnOffPlugins();\n *   console.log('已禁用所有插件');\n * } catch (err) {\n *   console.error('禁用插件失败:', err.message);\n * }\n */\nconst turnOffPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOff();\n};\n\n/**\n * @async 获取 Whistle 插件列表\n * @function getPluginList\n * @description\n * 通过 Whistle API 获取当前已安装的所有插件信息，包括插件元数据、版本、描述、规则配置及激活状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>>} 返回插件对象数组，若无插件则返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getList()` 调用失败\n *\n * @example\n * try {\n *   const plugins = await getPluginList();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => {\n *     console.log(`${p.name} (${p.version}): ${p.selected ? '启用' : '禁用'}`);\n *     console.log(`  描述: ${p.description}`);\n *   });\n * } catch (err) {\n *   console.error('获取插件列表失败:', err.message);\n * }\n */\nconst getPluginList = async () => {\n  const api = await loadApi();\n  return await api.plugins.getList();\n};\n\n/**\n * @async 获取指定的 Whistle 插件\n * @function getPlugin\n * @description\n * 通过 Whistle API 获取指定的插件信息。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>} 返回插件对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.get()` 调用失败\n * \n * @example\n * try {\n *   const plugin = await getPlugin('my-plugin');\n *   console.log(`${plugin.name}: ${plugin.enabled ? '启用' : '禁用'}`);\n * } catch (err) {\n *   console.error('获取插件失败:', err.message);\n * }\n */\nconst getPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.get(name);\n};\n\n/**\n * @async 选择指定的 Whistle 插件\n * @function selectPlugin\n * @description\n * 通过 Whistle API 选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.select()` 调用失败\n * \n * @example\n * try {\n *   const result = await selectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已选择并启用');\n *   } else {\n *     console.log('插件选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('选择插件失败:', err.message);\n * }\n */\nconst selectPlugin = async (name) => {\n  const api = await loadApi();\n  const result = await api.plugins.select(name);\n  if (result) {\n    await turnOnPlugins();\n  }\n  return result;\n};\n\n/**\n * @async 取消选择指定的 Whistle 插件\n * @function unselectPlugin\n * @description\n * 通过 Whistle API 取消选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回取消选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.unselect()` 调用失败\n * @example\n * try {\n *   const result = await unselectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已取消选择并禁用');\n *   } else {\n *     console.log('插件取消选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('取消选择插件失败:', err.message);\n * }\n */\nconst unselectPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.unselect(name);\n};\n\nconst URLENCODED_RE = /application\\/x-www-form-urlencoded/i;\nconst POST_RE = /^post$/i;\nconst isUrlEncoded = (req) => {\n  var type = req.headers?.['content-type'];\n  if (!type || !POST_RE.test(req.method)) {\n    return;\n  }\n  return URLENCODED_RE.test(type);\n};\n\nconst formatData = (session, utils, isRes) => {\n  let data = session[isRes ? 'res' : 'req'];\n  let body = data.body || '';\n  let raw;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      raw = raw || (isRes ? utils.getRawRes(data) : utils.getRawReq(data));\n      body = raw ? raw.substring(raw.indexOf('\\r\\n\\r\\n') + 4) : '';\n    }\n    return body;\n  };\n\n  Object.defineProperties(data, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        if (json) {\n          return json;\n        }\n        getBody();\n        if (body) {\n          if (!isRes && isUrlEncoded(data)) {\n            json = qs.parse(body);\n          } else {\n            try {\n              json = JSON.parse(body);\n            } catch (e) {}\n          }\n        }\n        json = json || {};\n        return json;\n      }\n    },\n    raw: {\n      configurable: true,\n      get() {\n        if (raw == null) {\n          raw = isRes ? utils.getRawRes(data) : utils.getRawReq(data);\n        }\n        return raw;\n      }\n    },\n  });\n};\n\nconst formatSession = (session, utils) => {\n  let timings;\n  let rules;\n  Object.defineProperties(session, {\n    timings: {\n      configurable: true,\n      get() {\n        timings = timings || utils.getTimings(session);\n        return timings;\n      }\n    },\n    rawRules: {\n      configurable: true,\n      get() {\n        rules = rules || utils.getRules(session);\n        return rules.join('\\n');\n      }\n    },\n  });\n  formatData(session, utils);\n  formatData(session, utils, false);\n  return session;\n};\n\n/**\n * @async 获取抓包数据（支持自动分页拉取）\n * @function getSessions\n * @description\n * 用于按条件检索 HTTP/WebSocket 等协议的抓包记录。支持基于游标、时间、资源类型、URL 关键词、请求方法、状态码以及请求/响应头的组合筛选。\n *\n * **过滤逻辑**：\n * - 所有非空条件均以 **AND** 关系叠加（即需同时满足）；\n * - 对于 `method` 和 `statusCode`，传入多个值时采用 **OR** 关系（匹配任一即可）；\n * - `latest` 优先级最高，一旦为 `true`，count 最多只返回 120 条最新记录；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n * - `reqHeader` 和 `resHeader` 为对象类型，支持按请求头/响应头的字段名和值进行子串匹配。\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新数据末尾。\n * - 分页游标自动使用上一页最后一条记录的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count` 和任何游标/时间参数。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新数据开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的数据通常为空。如需获取大量历史数据，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {number} [options.count=3600] - 期望获取的最大记录数。实际返回数量 ≤ 此值，且受每页 120 条限制及数据实际数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的记录。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（通常为记录的自增主键或唯一序号，格式如 `\"1784905385300-003\"`）。\n *                                            获取记录 ID **大于**此值的增量数据（即更新的数据）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级）。\n *                                       获取记录生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.type] - 资源类型过滤，同抓包界面 Network 底部过滤栏选项（不区分大小写）。\n *                                  支持以下枚举值（传入其它字符串表示不过滤）：\n *                                  - `JSON`：JSON 请求/响应\n *                                  - `HTML`：HTML 文档\n *                                  - `CSS`：样式表\n *                                  - `JS`：JavaScript 脚本\n *                                  - `Font`：字体文件\n *                                  - `Img`：图片资源\n *                                  - `Media`：音视频媒体\n *                                  - `WS`：WebSocket 帧数据\n *                                  - `Tunnel`：隧道连接（如 WebSocket 升级）\n *                                  - `Wasm`：WebAssembly 模块\n *                                  - `Mock`：Mock 数据\n *                                  - `Rules`：匹配规则的请求\n *                                  - `Import`：导入的数据\n *                                  - `Composer`：通过 Composer 发送的请求\n *                                  - `Error`：错误请求（状态码 >= 400、captureError、中断的请求等）\n *                                  - `captureError`：解析 HTTPS 失败的 Tunnel 请求\n * @param {string} [options.subUrl] - URL 中包含该字符串的请求（不区分大小写）。\n *                                    例如 `\"/api/user\"` 将匹配所有包含该路径的 URL。\n * @param {string|string[]} [options.method] - HTTP 请求方法，支持多个（数组或逗号分隔字符串，如 `\"GET,POST\"`，不区分大小写）。\n *                                             匹配任一方法即满足条件。\n * @param {number|number[]} [options.statusCode] - HTTP 响应状态码，支持多个（数组或逗号分隔字符串，如 `\"200,404\"`）。\n *                                                 匹配任一状态码即满足条件。\n * @param {Object} [options.reqHeader] - 请求头过滤条件。\n * @param {string} options.reqHeader.name - 请求头字段名称（如 `\"X-Request-Id\"`，不区分大小写）。\n * @param {string} [options.reqHeader.subValue] - 可选，请求头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该请求头字段（无论值为何）。\n * @param {Object} [options.resHeader] - 响应头过滤条件。\n * @param {string} options.resHeader.name - 响应头字段名称（如 `\"Content-Type\"`，不区分大小写）。\n * @param {string} [options.resHeader.subValue] - 可选，响应头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该响应头字段（无论值为何）。\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的抓包记录数组（经过 `formatSession` 格式化处理）。\n *                                    每条记录包含以下主要字段（可能因 `formatSession` 处理而略有增减）：\n *                                    - `id` (string): 记录唯一标识，格式为 `\"时间戳-序号\"`（如 `\"1785144349945-573\"`）\n *                                    - `startTime` (number): 请求开始时间戳（Unix 毫秒级）\n *                                    - `url` (string): 完整请求 URL\n *                                    - `req` (Object): 请求详情\n *                                      - `method` (string): 请求方法\n *                                      - `ip` (string): 客户端 IP\n *                                      - `port` (string): 客户端端口\n *                                      - `headers` (Object): 请求头键值对\n *                                      - `json` (Object): 解析后的 JSON 请求体（若存在）\n *                                      - `body` (string): 原始请求体（可能为空）\n *                                      - `raw` (string): 原始请求数据\n *                                    - `res` (Object): 响应详情\n *                                      - `ip` (string): 服务器 IP\n *                                      - `port` (string): 服务器端口\n *                                      - `statusCode` (number): HTTP 状态码\n *                                      - `headers` (Object): 响应头键值对\n *                                      - `json` (Object): 解析后的 JSON 响应体（若存在）\n *                                      - `body` (string): 原始响应体（可能为空）\n *                                      - `raw` (string): 原始响应数据\n *                                    - `timings` (Object): 请求各阶段耗时（单位：毫秒）\n *                                      - `start` (number): 请求开始的时间戳（相对于某个基准点）\n *                                      - `ttfb` (number | null): 首字节时间\n *                                      - `dns` (number | null): DNS 解析耗时\n *                                      - `connect` (number | null): TCP 连接耗时（从 DNS 结束到连接建立）\n *                                      - `request` (number | null): 请求发送耗时（从 DNS 结束到请求结束）\n *                                      - `response` (number | null): 响应接收耗时（从请求结束到接收到响应头）\n *                                      - `download` (number | null): 下载耗时（从接收响应头到接收完响应体）\n *                                      - `total` (number | null): 总耗时\n *                                    - `rules` (Object): 匹配的规则对象\n *                                    - `rawRules` (string): 原始规则字符串\n *                                    - `version` (string): Whistle 版本号\n *                                    - `nodeVersion` (string): Node.js 版本号\n *                                    - `dnsTime` (number): DNS 完成时间戳\n *                                    - `requestTime` (number): 请求发送完成时间戳\n *                                    - `connectTime` (number): 连接建立完成时间戳\n *                                    - `responseTime` (number): 响应头接收完成时间戳\n *                                    - `endTime` (number): 请求结束时间戳\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误，需确保 whistle 已全局安装）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.network.getSessions()` 调用失败（如网络异常、参数错误）\n * @throws {Error} 底层 API 调用可能抛出其他运行时错误（如超时、数据格式异常）\n *\n * @example\n * // 使用游标增量拉取（最多 100 条）\n * const sessions = await getSessions({ startId: '1784905385300-003', count: 100 });\n *\n * @example\n * // 获取最新的 JSON 类型记录（最多 120 条）\n * const sessions = await getSessions({ latest: true, type: 'JSON' });\n *\n * @example\n * // 组合过滤：查询状态码为 200 或 304 的 GET 请求，且 URL 包含 \"/v1\"，同时要求存在特定请求头\n * const sessions = await getSessions({\n *   subUrl: '/v1',\n *   method: 'GET',\n *   statusCode: [200, 304],\n *   reqHeader: { name: 'X-Token', subValue: 'abc' },\n *   count: 50\n * });\n */\nconst getSessions = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const sessions = await api.network.getSessions(options);\n    const len = sessions.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatSession(sessions[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = sessions[len - 1]?.id;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n\nconst formatFrame = (frame, utils) => {\n  let body;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      body = utils.getText(frame);\n    }\n    return body;\n  };\n  Object.defineProperties(frame, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        getBody();\n        if (body) {\n          try {\n            json = JSON.parse(getBody());\n          } catch (e) {}\n        }\n        json = json || {};\n        return json;\n      },\n    }\n  });\n  return frame;\n}\n\n/**\n * @async 获取 WebSocket/Socket 的帧数据（支持自动分页拉取）\n * @function getFrames\n * @description\n * 按条件检索指定连接（通过 `reqId` 关联）的 WebSocket/Socket 通信帧，支持基于游标、时间、来源方向的组合筛选。\n *\n * **过滤逻辑**：\n * - `from` 参数用于过滤帧的发送方向：`client`（客户端→服务端）、`server`（服务端→客户端），不传则返回全部方向；\n * - `latest` 优先级最高，一旦为 `true`，count 最大为 120；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条帧记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新帧末尾。\n * - 分页游标自动使用上一页最后一条帧的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count`。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新帧开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的帧通常为空。如需获取大量历史帧，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {string} options.reqId - 会话 ID，对应抓包记录的 `id` 字段，用于关联帧所属的连接\n * @param {number} [options.count=3600] - 期望获取的最大帧数。实际返回数量 ≤ 此值，且受每页 120 条限制及实际帧数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的帧。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（帧的唯一序号），获取帧 ID **大于**此值的增量数据（即更新的帧）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级），获取帧生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.from] - 帧的来源方向过滤（不区分大小写）：\n *                                   - `'client'`：仅获取客户端→服务端的帧\n *                                   - `'server'`：仅获取服务端→客户端的帧\n *                                   - 不传或传入其他值：获取该连接的所有帧\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的帧数据数组（经过 `formatFrame` 格式化处理）。\n *                                    每条帧包含以下主要字段：\n *                                    - `reqId` (string): 所属会话 ID，对应抓包记录的 `id`\n *                                    - `frameId` (string): 帧数据的唯一标识 ID（可用于游标）\n *                                    - `mask` (boolean): 是否为掩码帧（WebSocket 掩码）\n *                                    - `compressed` (boolean): 帧数据是否经过压缩\n *                                    - `length` (number): 帧数据的原始长度（字节数）\n *                                    - `opcode` (number): 帧操作码，`1` 表示文本帧，`2` 表示二进制帧\n *                                    - `base64` (string): Base64 编码的帧数据（用于二进制或大文本）\n *                                    - `body` (string): 解码后的帧内容（文本或可读字符串）\n *                                    - `json` (Object): 若帧数据为 JSON 格式，则解析为对象，否则为空对象 `{}`\n *\n * @throws {Error} 依赖 `loadApi` 可能抛出的错误（如 Whistle 未安装、版本过低、服务未启动）\n * @throws {Error} 底层 `api.network.getFrames()` 调用失败（如 `reqId` 不存在、参数错误、网络异常）\n *\n * @example\n * // 使用游标增量拉取，获取客户端发出的前 10 条帧\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   startTime: 1784903620156,\n *   count: 10,\n *   from: 'client'\n * });\n *\n * @example\n * // 获取最新的帧（最多 120 条）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   latest: true\n * });\n *\n * @example\n * // 从指定游标开始拉取所有后续帧（直到最新）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   count: 3600\n * });\n */\nconst getFrames = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const frames = await api.network.getFrames(options);\n    const len = frames.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatFrame(frames[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = frames[len - 1]?.frameId;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n```\n","docs/docs/extensions/dev.md":"# 插件开发\n您已了解插件的各项功能特性（参考[使用文档](./usage)），现在我们将通过模块化方式演示具体实现。\n> 每个核心功能作为独立插件实现，保持单一职责原则，实际开发中可自由拼装这些功能模块。\n\n## 准备工作\n1. 创建一个空目录，如\n   ``` sh\n    mkdir examples && cd examples\n   ```\n2. 安装脚手架 [lack](https://github.com/avwo/lack)\n   ``` txt\n    npm i -g lack\n   ```\n   > lack 必须用最新版本（`>= 1.4.0`）\n3. 更新 [Whistle](https://github.com/avwo/whistle)\n   > Whistle 必须用最新版本（`>= 2.9.100`，[客户端](https://github.com/avwo/whistle-client) `>= 1.3.8`）\n\n## rules.txt\n- 插件安装后会自动加载生效\n- 功能跟界面 Rules 配置的规则一样，优先级低于界面 Rules 规则\n- 插件被禁用立即失效\n\n**示例**\n1. 创建包含 `rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules && cd whistle.test-rules\n\n    # 初始化包含 rules.txt 的插件\n    lack init rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `rules.txt` 文件：\n    ``` txt\n    * reqHeaders://x-client=whistle.test-rules\n    ```\n3. 所有经过 Whistle 的请求都会自动添加请求头 `x-client: whistle.test-rules`\n   \n   <img src=\"/img/test-rules.png\" width=\"600\" />\n4. 如果规则修改后未生效？请确认：\n    - 是否已执行 `lack watch`\n    - 是否跟界面配置冲突规则\n    - 插件是否被禁用\n\n## _rules.txt\n- 仅对匹配插件协议的请求生效\n- 支持长协议(whistle.myplugin://)和短协议(myplugin://)\n- 作用于请求处理阶段\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n1. 创建包含 `_rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-req-rules && cd whistle.test-req-rules\n\n    # 初始化包含 _rules.txt 的插件\n    lack init _rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-req-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `_rules.txt` 文件：\n    ``` txt\n    * file://(hello)\n    ```\n    > 所有请求返回响应内容 `hello`\n4. 直接访问 `https://www.example.com/test` 返回正常页面\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/test whistle.test-req-rules://\n    ```\n    后再访问 `https://www.example.com/test` 返回 `hello`\n   \n## resRules.txt\n- 仅对匹配插件协议的请求生效\n- 作用于响应处理阶段\n- 可修改响应状态码和内容\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n\n1. 创建包含 `resRules.txt` 规则文件的插件：\n   ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules && cd whistle.test-res-rules\n\n    # 初始化包含 resRules.txt 的插件\n    lack init resRules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `resRules.txt` 文件：\n    ``` txt\n    * resBody://`(whistle_error_${statusCode})` includeFilter://s:500 includeFilter://s:404\n    ```\n    > 把响应状态码为 `404` 和 `500` 的请求的响应内容改为 `whistle_error_404` 和  `whistle_error_500`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/500 whistle.test-req-rules:// statusCode://500\n    https://www.example.com/404 whistle.test-req-rules:// statusCode://404\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/500` 响应内容被改成 `whistle_error_500`\n      - 访问 `https://www.example.com/404` 响应内容被改成 `whistle_error_404`\n      - 访问 `https://www.example.com/test` 则返回原始内容\n\n## auth\n对经过 Whistle 的请求进行鉴权。\n\n**示例**\n\n1. 创建请求鉴权插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-auth && cd whistle.test-auth\n\n    # 初始化包含 auth 的插件\n    lack init auth\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-auth` 插件\n    lack watch\n    ```\n2. 编辑 `src/auth.ts` 文件：\n    ``` ts\n    export default async (req: Whistle.PluginAuthRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // URL 里面包含 `/test/forbidden` 的响应状态码为 403\n      if (fullUrl.includes('/test/forbidden')) {\n        return false;\n      }\n      // URL 里面包含 `/test/message/forbidden` 的响应状态码为 403，且自定义响应内容\n      if (fullUrl.includes('/test/message/forbidden')) {\n        req.setHtml('<strong>Access Denied</strong>');\n        return false;\n      }\n\n      // URL 里面包含 `/test/login` 要求用户输入用户名和密码\n      if (fullUrl.includes('/test/login')) {\n        const auth = req.headers.authorization || req.headers['proxy-authorization'];\n        if (auth) {\n          // TODO: 校验用户名和密码，如果正确返回 true，否则返回 false\n          return true;\n        }\n        req.setLogin(true);\n        return false;\n      }\n\n      // URL 里面包含 `/test/redirect` 的响应状态码为 302，且重定向到 `https://www.example.com/test`\n      if (fullUrl.includes('/test/redirect')) {\n        req.setRedirect('https://www.example.com/test');\n        return false;\n      }\n      // 其它请求直接放过\n      // 如果需要添加自定义请求头，可以使用 `req.setHeader` 方法\n      // 支持添加 key 前缀为 `x-whistle-` 的请求头\n      // 例如：req.setHeader('x-whistle-xxx', 'value');\n      req.setHeader('x-whistle-custom-header', 'lack');\n      return true;\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com whistle.auth://\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/test/forbidden` 响应状态码 `403`，响应内容 `Forbidden`\n      - 访问 `https://www.example.com/test/message/forbidden` 响应状态码 `403`，响应内容 `<strong>Access Denied</strong>`\n      - 访问 `https://www.example.com/test/login` 弹出浏览器的登录框\n      - 访问 `https://www.example.com/test/redirect` 重定向到 `https://www.example.com/test`\n      - 其它 `www.example.com` 的请求正常，且被添加请求头 `x-whistle-custom-header: lack`\n\n## sniCallback\n通过插件动态下发请求证书。\n\n**示例**\n\n1. 创建自定义证书插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-sni\n\n    # 初始化包含 sni 的插件\n    lack init sni\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-sni` 插件\n    lack watch\n    ```\n2. 编辑 `src/sniCallback.ts` 文件：\n    ``` ts\n    const key = `...`;\n    const cert = `...`;\n\n    // sniCallback 插件处理函数 - 根据请求 URL 动态决定 TLS 隧道处理方式\n    export default async (req: Whistle.PluginSNIRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // 特殊域名返回 false，保持 TUNNEL 状态（不解除 TLS 加密）\n      if (fullUrl === 'https://tunnel.example.com') {\n        return false;\n      }\n\n      // 对特定域名使用自定义证书解密，返回一个包含 key 和 cert 的对象\n      // 支持 .crt、.pem、.cer 等格式的证书\n      if (fullUrl === 'https://custom.example.com') {\n        return { key, cert };\n      }\n      // 默认使用 Whistle 内置证书解密 TLS 流量\n      return true;\n    };\n    ```\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com sniCallback://test-sni\n    ```\n6.  效果：\n   - 访问 `https://tunnel.example.com/path`，请求保持 TUNNEL 状态（不解除 TLS 加密）\n   - 访问 `https://custom.example.com/test/` 报证书出错\n   - 其它请求正常\n\n## rulesServer\n通过 JavaScript 编程实时生成规则，规则功能跟上面的 _rules.txt 静态规则一样，但优先级高于 _rules.txt。\n\n**示例**\n\n1. 创建包含 `rulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-rules-server\n\n    # 初始化包含 rulesServer 的插件\n    lack init rulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/rulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end(JSON.stringify({\n          values: {\n            'whistle.test-rules-server/a.html': 'test normal values',\n          },\n          rules: `\n            \\`\\`\\` whistle.test-rules-server/b.html\n            test inject values\n            \\`\\`\\`\n\n            */a file://{whistle.test-rules-server/a.html}\n            */b file://{whistle.test-rules-server/b.html}\n          `,\n        }));\n      });\n    };\n    ```\n    > 支持响应纯 rules 文本  `res.end(rules)`，或包含 rules & values 的序列化对象 `res.end(JSON.stringify({rules, values}))`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com whistle.test-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/a` 返回 `test inject values`\n   - 访问 `https://test.example.com/b` 返回 `test normal values`\n\n## tunnelRulesServer\ntunnelRulesServer 是专门用于处理 TUNNEL 请求的动态规则生成机制，其核心特性与常规 [rulesServer](#rulesserver) 相同，但应用场景有专门区分：\n1. tunnelRulesServer 仅对 `tunnel://domain:port` 隧道请求生效\n2. rulesServer 对 `HTTP[S]/WebSocket` 请求生效\n\n## resRulesServer\n请求的响应阶段通过 JavaScript 编程实时生成规则（可作用于所有类型的请求），规则功能跟上面的 resRules.txt 静态规则一样，但优先级高于 resRules.txt。\n\n**示例**\n\n1. 创建包含 `resRulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules-server && cd whistle.test-res-rules-server\n\n    # 初始化包含 resRulesServer 的插件\n    lack init resRulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/resRulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end('* resBody://(test-res-rules-server)');\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com/res whistle.test-res-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/res/path` 返回 `test-res-rules-server`\n\n\n## statsServer\n如果仅想获取请求的 URL、请求方法、请求头、请求内容等信息，而不对请求做任何操作，可以用 statsServer。\n\n**示例**\n\n1. 创建包含 `statsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-stats-server && cd whistle.test-stats-server\n\n    # 初始化包含 statsServer 的插件\n    lack init statsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-stats-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/statsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Request Headers:', originalReq.headers);\n        // 获取请求的 body\n        req.getReqSession((reqSession) => {\n          if (reqSession) {\n            console.log('Request Body:', reqSession.req.body);\n          }\n        });\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/stats whistle.test-stats-server://test\n    ```\n4.  效果：\n   - 访问 `https://www.example.com/stats` 控制台输出：\n      ``` sh\n      Value: test\n      URL: https://www.example.com/stats\n      Method: GET\n      Request Headers: {\n        host: 'www.example.com',\n        'cache-control': 'max-age=0',\n        'sec-ch-ua': '\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"',\n        'sec-ch-ua-mobile': '?0',\n        'sec-ch-ua-platform': '\"macOS\"',\n        'upgrade-insecure-requests': '1',\n        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',\n        accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',\n        'sec-fetch-site': 'cross-site',\n        'sec-fetch-mode': 'navigate',\n        'sec-fetch-user': '?1',\n        'sec-fetch-dest': 'document',\n        'accept-encoding': 'gzip, br',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',\n        priority: 'u=0, i',\n        'x-forwarded-for': '127.0.0.1',\n        connection: 'close'\n      }\n      Request Body: \n      ```\n\n## resStatsServer\n如果获取请求的响应状态码、响应头、响应内容等信息，而不对请求做任何操作，可以用 resStatsServer。\n\n**示例**\n\n1. 创建包含 `resStatsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-stats-server && cd whistle.test-res-stats-server\n\n    # 初始化包含 resStatsServer 的插件\n    lack init resStatsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-stats-server` 插件\n    lack watch\n    ```\n2. 安装依赖\n    ``` sh\n    npm i\n    ```\n3. 编辑 `src/resStatsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq, originalRes } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Server IP', originalRes.serverIp);\n        console.log('Status Code:', originalRes.statusCode);\n        console.log('Response Headers:', originalReq.headers);\n        // 获取请求的完整抓包数据\n        req.getSession((reqSession) => {\n          if (reqSession) {\n            console.log('Response Body:', reqSession.res.body);\n          }\n        });\n      });\n    };\n    ```\n4. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/res/stats whistle.test-res-stats-server://testResStats\n    ```\n5.  效果：\n   - 访问 `https://www.example.com/res/stats` 控制台输出：\n      ``` txt\n      Value: testResStats\n      URL: https://www.example.com/res/stats\n      Method: GET\n      Server IP 127.0.0.1\n      Status Code: 200\n      Response Headers: {\n        'accept-ranges': 'bytes',\n        'content-type': 'text/html',\n        etag: '\"84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134\"',\n        'last-modified': 'Mon, 13 Jan 2025 20:11:20 GMT',\n        server: 'AkamaiNetStorage',\n        expires: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'cache-control': 'max-age=0, no-cache, no-store',\n        pragma: 'no-cache',\n        date: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'alt-svc': 'h3=\":443\"; ma=93600,h3-29=\":443\"; ma=93600',\n        'x-forwarded-for': '127.0.0.1',\n        host: 'www.example.com',\n        connection: 'close'\n      }\n      Response Body: <!doctype html>\n      <html>\n      <head>\n          <title>Example Domain</title>\n\n      ......\n      ```\n\n## server\n可以将插件作为 **server** 使用，由它接收并转发请求。插件可以直接响应请求，或在处理后继续转发至目标服务器。目标服务器返回响应后，插件可以进一步处理，最终将结果返回给浏览器或客户端。\n\n**示例**\n\n1. 创建包含 `server` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-server && cd whistle.test-server\n\n    # 初始化包含 server 的插件\n    lack init server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/server.ts` 文件：https://github.com/whistle-plugins/examples/tree/master/whistle.test-server\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json test-server://setCookieFromBody\n    ```\n4.  访问 `https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json`\n    - 浏览器 Cookie 新增 `test-name=whistle`\n    - 响应内容新增 `\"pluginName\":\"whistle\"`\n\n\n## pipe\n如果请求/响应内容被加密，或者需要转成特定格式显示在抓包界面，可以用 pipe 将请求/响应内容交给插件处理。\n> 在抓包界面中：\n> \n> 请求数据中无法看到 `reqWrite` 修改后的内容\n> \n> 响应数据中无法看到 `resWrite` 修改后的内容（这是预期行为）\n> \n> 所有 `xxxRexWrite` 操作的实际修改内容都不会显示在抓包界面\n> \n\n#### HTTP/HTTPS 协议实现\n1. 创建 pipe http 插件:\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-http && cd whistle.test-pipe-http\n\n    # 初始化包含 pipe http 的插件\n    lack init pipeHttp,server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-http` 插件\n    lack watch\n    ```\n4. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-http\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/test-pipe-http pipe://test-pipe-http test-pipe-http://mirror\n    ```\n    > `test-pipe-http://mirror` 的作用是客户端请求什么内容，服务端就响应什么内容\n6. Whistle / Network / Composer 输入 `www.example.com/test-pipe-http`，选择 `POST` 方法，Body 输入 `test-pipe-http`\n    <img src=\"/img/pipe-http.png\" width=\"400\" />\n7.  点击发送按钮，查看返回的 Body 如下\n    <img src=\"/img/pipe-http-result.png\" width=\"400\" />\n   \n\n#### WebSocket 协议实现\n1. 创建 pipe websocket 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-ws && cd whistle.test-pipe-ws\n\n    # 初始化包含 pipe websocket 的插件\n    lack init pipeWs\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-ws` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-ws\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    wss://echo.websocket.org/ pipe://test-pipe-ws\n    ```\n4.  打开页面 `https://echo.websocket.org/.ws` 可以看到如下效果：\n   <img src=\"/img/pipe-ws.png\" width=\"520\" />\n   \n\n\n#### TCP 隧道实现\n1. 创建 pipe tunnel 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-tunnel && cd whistle.test-pipe-tunnel\n\n    # 初始化包含 pipe tunnel 的插件\n    lack init pipeTunnel.server\n\n    # 安装依赖\n    npm i --save-dev lack-proxy && npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-tunnel` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-tunnel\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test-pipe-tunnel.example.com pipe://test-pipe-tunnel test-pipe-tunnel://mirror\n    ```\n4. 执行 `whistle.test-pipe-tunnel` 根目录的 `test.js` 文件：\n    ``` sh\n    node test.js\n    ```\n5. 控制台输出的内容\n    <img src=\"/img/pipe-tunnel.png\" width=\"560\" />\n\n## 插件操作界面\n在 Whistle 插件列表中，点击 `Option` 或插件名称可快速打开插件操作界面，支持以下三种模式：\n1. **Tab 模式**：在 Whistle 界面内以标签页形式打开（默认方式）\n2. **对话框模式**：在 Whistle 界面内以弹窗形式打开\n3. **新标签模式**：在浏览器新标签页或客户端新窗口中打开\n\n---\n\n### 1. Tab 模式（默认）\n**特性**  \n- 插件管理界面默认地址为：`http[s]://domain[:port]/plugin.xxx/`  \n- 通过插件列表的 `Option` 或插件名称直接打开  \n\n**禁用 Tab 模式**  \n若需隐藏此打开方式，可在 `whistleConfig` 中配置：  \n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"noOption\": true // 禁用默认入口\n    ...\n  }\n  ...\n}\n```\n\n---\n\n### 2. 对话框模式\n**特性**  \n- 支持通过弹窗形式打开管理界面  \n- 弹窗页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/modal.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n**配置示例**  \n```js\n{\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {\n      \"width\": 360,  // 弹窗宽度（px）\n      \"height\": 566  // 弹窗高度（px）\n    }\n    ...\n  }\n}\n```\n\n---\n\n### 3. 新标签模式\n**场景**  \n需使用外部页面作为插件管理界面时使用。  \n\n**基础配置**  \n```js\n{\n   \"pluginHomepage\": \"https://your-external-page.com\" // 外部页面地址\n}\n```\n\n**高级选项**  \n若需将外部链接强制在 Whistle 内打开（Tab 或对话框模式）：  \n\n**方式一：对话框模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {  // 强制以弹窗打开\n      \"width\": 360,\n      \"height\": 566\n    }\n    ...\n  }\n  ...\n}\n```\n\n**方式二：Tab 模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInPlugins\": true  // 强制在插件Tab页打开\n    ...\n  }\n  ...\n}\n```\n \n## 完整 Hooks API\n参考：https://github.com/avwo/lack/blob/master/assets/ts/src/types/global.d.ts\n\n## 插件变量配置{#pluginvars}\n支持通过 `%` 符号在规则里面配置，详见：[`%` 符号用途](../rules/plugin-vars)\n\n## 自定义规则补全功能{#rules-hint}\n综上所述，每个插件可以扩展以下规则：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\nsinCallback://myplugin(sinValue)\npipe://myplugin(pipeValue)\n```\n\nWhistle 还支持对以下规则自定义补全功能：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\n```\n\n**示例**\n1. 创建 rules hints 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules-hint && cd whistle.test-rules-hint\n\n    # 初始化包含 uiServer 的插件\n    lack init uiServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-hint` 插件\n    lack watch\n    ```\n5. 在 `package.json` 配置 `whistleConfig`，新增 `pluginVars` 和 `hintUrl`：\n   ``` js\n    {\n      ...\n      \"whistleConfig\": {\n        \"pluginVars\": {\n          ...\n          \"hintUrl\": '/cgi-bin/plugin-vars'\n          ...\n        },\n        \"hintUrl\": '/cgi-bin/get-hints'\n      },\n      ...\n    }\n   ```\n6. 编辑 `src/uiServer/router.ts` 文件：\n    ``` ts\n    export default (router: Router) => {\n      // 针对 `%test-rules-hint[.key]=xxx`\n      router.get('/cgi-bin/plugin-vars', (ctx) => {\n        const { sep, value } = ctx.query;\n        const isKey = sep === '.';\n        let key = '';\n        let keyword = '';\n        if (value && typeof value === 'string') {\n          if (isKey) {\n            const index = value.indexOf('=');\n            // %test-plugin-vars.xxx=yyy\n            if (index !== -1) {\n              key = value.substring(0, index);\n              keyword = value.substring(index + 1).toLowerCase();\n            } else {\n              // %test-plugin-vars.xxx or %test-plugin-vars.xxx=\n              key = value;\n            }\n          } else {\n            // %test-plugin-vars=yyy\n            keyword = value.toLowerCase();\n          }\n        }\n        const result: (string | {\n          isKey: true,\n          value: string,\n        })[] = [];\n        VARS_OPTIONS.forEach((option) => {\n          if (keyword && !option.toLowerCase().includes(keyword)) {\n            return;\n          }\n          if (isKey) {\n            result.push({\n              value: `${key}=${option}`,\n              isKey: true,\n            });\n          } else {\n            result.push(option);\n          }\n        });\n        ctx.body = result;\n      });\n\n      // 针对 `test-rules-hint://xxx` 和 `whistle.test-rules-hint://xxx`\n      // 如果没有配置 `pluginVars.hintUrl`，则对 `%test-rules-hint[.key]=xxx` 也生效\n      router.get('/cgi-bin/get-hints', (ctx) => {\n        const { protocol, value } = ctx.query;\n        if (!protocol || typeof protocol !== 'string' || typeof value !== 'string') {\n          return;\n        }\n        const isVar = protocol.startsWith('%');\n        // 事实上不会有这种情况，除非删除了 `pluginVars.hintUrl` 配置\n        if (isVar) {\n          return;\n        }\n        const isLong = protocol.startsWith('whistle.');\n        const prefix = isLong ? 'long-' : 'short-';\n        const keyword = value.toLowerCase();\n        const result: string[] = [];\n        HINTS_OPTIONS.forEach((option) => {\n          if (`${prefix}${option.toLowerCase()}`.includes(keyword)) {\n            result.push(`${prefix}${option}`);\n          }\n        });\n        ctx.body = result;\n      });\n    };\n\n    ```\n9. 效果：\n   \n   <img src=\"/img/rules-hint1.png\" width=\"300\" />\n\n   <img src=\"/img/rules-hint2.png\" width=\"300\" />\n\n   <img src=\"/img/plugin-vars-hint-url1.png\" width=\"300\" />\n\n## 界面扩展\nWhistle 插件系统支持扩展以下界面功能模块：\n- **Network 模块**\n  - 数据表格列扩展\n  - 上下文菜单扩展\n  - 详情面板 Tab 扩展\n- **Rules 模块**上下文菜单\n- **Values 模块**上下文菜单\n- **Plugins 模块**上下文菜单\n\n1. 创建示例插件\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-ui-ext && cd whistle.test-ui-ext\n\n    # 初始化空白插件\n    lack init blank\n\n    # 开发模式运行\n    lack watch\n    ```\n2. 编辑 `whistleConfig`：https://github.com/whistle-plugins/examples/tree/master/whistle.test-ui-ext/package.json\n3. 执行后可在 Whistle 插件列表看到 `test-ui-ext` 插件\n    ![界面扩展效果图](/img/ui-ext.png)\n\n#### 1. Network 表格列扩展\n**配置示例**：\n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkColumn\": {\n      \"name\": \"Referer\",          // 列显示名称（必填）\n      \"key\": \"req.headers.referer\", // 数据字段路径（必填）\n      \"iconKey\": \"\",              // 图标字段（可选）\n      \"showTitle\": true,          // 是否显示悬浮提示（可选）\n      \"width\": 120                // 列宽（可选，默认120）\n    }\n    ...\n  }\n  ...\n}\n```\n\n**高级数据处理**：\n1. 创建 `/public/webWorker.js`：\n    ```js\n    module.exports = function(session, next) {\n      const isGithub = /^https?:\\/\\/github\\.com\\//.test(session.url);\n      next({\n        testWebWorker: 'custom_value',\n        style: isGithub ? { \n          color: '#fff',\n          fontStyle: 'italic',\n          bgColor: 'red' \n        } : null\n      });\n    };\n    ```\n2. 配置 webWorker：\n    ```js\n    {\n      \"whistleConfig\": {\n        \"networkColumn\": {\n          \"name\": \"Test\",\n          \"key\": \"customData.testWebWorker\"  // 访问 webWorker 返回数据\n          ...\n        },\n        \"webWorker\": \"/public/webWorker.js\"\n      }\n    }\n    ```\n\n#### 2. 详情面板 Tab 扩展\n```js\n{\n  \"whistleConfig\": {\n    \"inspectorsTab\": {\n      \"name\": \"Custom Tab\",       // Tab 显示名称\n      \"action\": \"/public/tab.html\", // 功能页面\n      \"icon\": \"data:image/png;base64,...\", // 图标（可选）\n      \"req\": {                    // Request 子 Tab\n        \"name\": \"Req SubTab\",\n        \"action\": \"/public/req-tab.html\"\n      },\n      \"res\": {                    // Response 子 Tab\n        \"name\": \"Res SubTab\",\n        \"action\": \"/public/res-tab.html\"\n      }\n    },\n    \"composerTab\": { /* 同 inspectorsTab */ },\n    \"toolsTab\": { /* 同 inspectorsTab */ }\n  }\n}\n```\n\n- Tab 页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/tab.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Network 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkMenus\": [\n      {\n        \"name\": \"Network Menu1\",\n        \"action\": \"/public/network-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Network Menu2\", // 菜单项名称\n        \"action\": \"/public/network-menu.html\", // 功能页面\n        \"required\": true, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": true, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Rules 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"rulesMenus\": [\n      {\n        \"name\": \"Rules Menu1\", // 菜单项名称\n        \"action\": \"/public/rules-menu.html\", // 功能页面\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Rules Menu2\",\n        \"action\": \"/public/rules-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 4. Values 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"valuesMenus\": [\n      {\n        \"name\": \"Values Menu1\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Values Menu2\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 5. Plugins 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"pluginsMenus\": [\n      {\n        \"name\": \"Plugins Menu1\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Plugins Menu2\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n## 特殊路径\n\nWhistle 提供专用路径格式，用于在各类型页面下直接请求插件接口，以及只在 Whistle 环境下可以正常访问的路径。\n\n#### 1. Whistle 界面直接访问\n在 Whistle 界面或插件管理界面中，可直接使用以下格式访问插件接口：\n```\npath/to\n```\n\n- 此路径会自动解析为 `/whistle.xxx/path/to`\n- 推荐使用相对路径格式，避免直接使用绝对路径 `/path/to`\n\n#### 2. 普通页面访问\n若需在非 Whistle 的普通网页中调用插件接口，请使用以下特殊路径格式：\n```\n/.whistle-path.5b6af7b9884e1165./whistle.xxx/path/to\n```\n\n- 请求到达插件时会被标准化为：`/whistle.xxx/path/to`\n- 前缀 `/.whistle-path.5b6af7b9884e1165./` 是 Whistle 的专用标识符，用于标记内部请求路由\n\n#### 3. 环境自适应路径转换\n实现经过或不经过 Whistle 代理时使用不同路径，但确保后台服务接收到统一路径：\n``` txt\n/_WHISTLE_5b6af7b9884e1165_/\n```\n\n- 原始请求：`https://www.example.com/.../_WHISTLE_5b6af7b9884e1165_/path/to?query`\n- 经过 Whistle 后转换为：`https://www.example.com/.../path/to?query`\n\n#### 说明：\n- `xxx` 为您的插件名称\n- `path/to` 是插件接口的具体路径\n- 特殊路径中的哈希值 `/.whistle-path.5b6af7b9884e1165./` 为固定标识符\n\n## 其它 whistleConfig 配置\n``` js\n{\n  ...,\n  \"homepage\": '', // 插件帮助页面地址\n  \"pluginHomepage\": '', // 自定义插件操作页面地址，默认为 `/plugin.xxx/\n  \"whistleConfig\": {\n    \"hideLongProtocol\": false, // 是否隐藏插件的长协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"hideShortProtocol\": false, // 是否隐藏插件的短协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"priority\": 0, // 默认值为0，插件优先级按更新时间从旧到新排序（越早更新的优先级越高），可通过此字段调整优先级顺\n    \"favicon\": '', // 插件 Tab 页上的 icon，可以为插件相对路径 `/public/xxx.png` 或绝对路径 `https://xxx` 或 `data:image/png;base64,xxx`\n    \"registry\": '', // 插件的 npm registry\n    \"noOption\": false, // 如果不存在操作界面，可以设置为 true，这样 Option 按钮会置灰\n    \"enableAuthUI\": false, // 插件的 auth hook 是否作用于插件操作界面（慎用）\n    \"tunnelKey\": '', // 字符串或字符串组，如果需要继续将请求通过 TUNNEL 代理到其它 HTTP Proxy 时，可以指定哪些请求头跟着过去\n    ...\n  },\n  ...\n}\n```\n\n## 插件页面内部路径规范\n插件页面的 URL 通常为以下格式：\n- `/plugin.xxx/path/yyy.html`\n- `/whistle.xxx/path/yyy.html`\n\n**根目录**为 `/plugin.xxx/` 或 `/whistle.xxx/`，因此：\n\n✅ 推荐使用相对路径，避免使用绝对路径（如 /path/to）。\n\n---\n\n#### **相对路径使用示例**  \n\n**场景 1：页面位于插件根目录**  \n**页面地址**：`/plugin.xxx/yyy.html`  \n**正确写法**：  \n- `./path/to`  \n- `path/to`  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n\n---\n\n**场景 2：页面位于子目录**  \n**页面地址**：`/plugin.xxx/a/b/c/yyy.html`  \n**正确写法**：  \n- `../../../path/to`（返回 3 级目录再进入目标路径）  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n- ❌ `path/to`（会基于当前目录 `a/b/c/` 查找，导致路径错误）  \n\n---\n\n**最佳实践**  \n1. **推荐使用 `./` 开头**，明确表示相对当前目录（如 `./assets/style.css`）。  \n2. **避免硬编码 `/` 开头的路径**，防止不同部署环境下路径失效。  \n3. **测试路径引用**：在本地和线上环境均验证资源加载是否正常。  \n\n---\n\n**常见问题**  \n❓ **Q：为什么不能用绝对路径？**  \n📌 **A**：插件可能部署在不同环境（如测试/生产/内嵌到其它项目中），绝对路径可能导致资源加载失败。  \n\n❓ **Q：有些构建工具，如 Vite 默认会将资源路径基于 base 设置进行解析。如果 base 设为 `/`，可能会将 `../` 优化为 `/`，如何处理？**  \n📌 **A**：尝试将 base 改成 `./`：\n  ``` js\n  // vite.config.js\n  export default defineConfig({\n    base: './', // 强制使用相对路径\n  });\n  ```\n  > 建议与后台路由配置同步优化\n\n\n## 发布插件\nWhistle 插件的发布方式与常规 NPM 包完全一致，只需遵循标准 NPM 发布流程：\n1. 登录 NPM（或企业私有源，后者需要设置企业私有源的 `npm config set registry https://xxx`）:\n    ``` sh\n    npm login\n    ```\n2. 在插件根目录执行发布命令：\n    ``` sh\n    npm publish\n    ```\n\n## 参考资料\n1. 示例源码仓库：\nhttps://github.com/whistle-plugins/examples\n1. 脚手架命令：https://github.com/avwo/lack\n","docs/docs/extensions/npm.md":"# NPM 模块\nWhistle 可作为 NPM 模块集成到项目中，**强烈建议**在独立子进程中运行 Whistle，以避免与主进程相互影响，同时便于管理。\n\n## 安装依赖\n``` sh\nnpm i --save whistle pfork\n```\n\n## 独立服务模式\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n\n    module.exports = (options, callback) => {\n      startWhistle({ port: options.port }, () => callback());\n    };\n    ```\n    > 禁用插件、自定义插件目录、修改存储目录等启动参数参考：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. 启动 Whistle 进程代码：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n      port: 8080,\n    };\n    const forkWhistle = (retry) => {\n      fork(options, (err, result, child) => {\n        if (err) {\n          // 初始化时报错，直接退出服务\n          if (!retry) {\n            throw err;\n          }\n          // 重试报错，延迟 100 毫秒再重试\n          return setTimeout(forkWhistle, 100);\n        }\n        // 子进程退出自动重试\n        child.once('close', () => forkWhistle(true));\n      });\n    };\n\n    forkWhistle();\n    ```\n\n这种方式启动的 Whistle，跟通过命令行独立启动 Whistle 或 Whistle 客户端的用法一样。\n\n\n## 内部服务模式（推荐）\n\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n    const http = require('http');\n\n    let curPort = 30013;\n\n    const getPort = (callback) => {\n      const server = http.createServer();\n      server.on('error', () => {\n        if (++curPort % 5 === 0) {\n          ++curPort;\n        }\n        getPort(callback);\n      });\n      server.listen(curPort, '127.0.0.1', () => {\n        server.removeAllListeners();\n        server.close(() => callback(curPort));\n      });\n    };\n\n    module.exports = (options, callback) => {\n      // 获取一个随机端口\n      getPort((port) => {\n        // 在随机端口启动 Whistle，启动成功后将启动参数传给父进程\n        const options = {\n          port,\n          host: '127.0.0.1',\n          storage: 'xxx', // 建议设置新存储目录，确保不跟其它实例的目录冲突\n        };\n        startWhistle(options, () => callback(options));\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/whistle.js\n    > \n    > Whistle 会\n2. 启动 Whistle 进程方法（`/lib/fork.js`）：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n    };\n\n    module.exports =() => {\n      return new Promise((resolve, reject) => {\n        fork(options, (err, result) => {\n          if (err) {\n            return reject(err);\n          }\n          resolve(result);\n        });\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/main/whistleMgr.js\n3. 将项目中的指定请求转发到 Whistle：\n   ``` js\n    const { createServer } = require('http');\n    const forkWhistle = require('./fork');\n\n    const server = createServer(async (req, res) => {\n      try {\n        const { host, port } = await forkWhistle();\n        // 将请求转发到 host、port\n      } catch (e) {\n        // handle errors\n      }\n    });\n\n    server.listen(8010);\n   ```\n   > 参考：https://github.com/Tencent/nohost/blob/master/lib/index.js#L160\n\n这种方式启动的 Whistle，端口是随机的且是按需启动的，外部请求不直接访问 Whistle，需要通过所在项目的服务进行转发，用法更灵活，更推荐项目使用这种方式集成 Whistle。\n\n## 相关文档\n\n1. Whistle 的所有启动参数可查看源代码的类型定义：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. pfork：https://github.com/avwo/pfork\n3. 完整示例：https://github.com/Tencent/nohost\n\n","docs/docs/extensions/usage.md":"# 使用插件\n\n## 安装插件\n\n1. 点击左侧导航栏的 Plugins 标签页\n2. 点击顶部的 Install 按钮\n3. 在弹出窗口中输入插件名称（支持同时安装多个插件）：\n   - 多个插件用空格或换行符分隔\n   - 可指定自定义 npm 镜像源：\n     - 直接在插件名后添加 `--registry=镜像地址`\n     - 或从下拉列表选择历史使用过的镜像源\n\n<img width=\"1000\" alt=\"install plugins\" src=\"/img/install-plugins.png\" />\n\n> 文本框中的冗余内容不会影响操作结果，Whistle 会自动过滤并仅提取插件名称及 registry 安装信息。若对话框未显示 Install 按钮，请重新安装全局 Node.js：https://nodejs.org/\n\n安装成功后，稍等片刻即可在插件列表中查看新安装的插件：\n\n<img width=\"1000\" alt=\"plugins\" src=\"/img/plugin-list.png\" />\n\n\n## 自定义协议\n\n每个插件可注册两种协议类型，配置方式跟普通协议一样：\n``` txt\n# 长协议\npattern whistle.plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n# 短协议\npattern plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n```\n\n## Hooks 功能\n\nWhistle 将匹配插件规则的请求与插件对应 Hook 进行交互，可实现以下功能：\n> 插件协议的具体功能看各个插件的帮助文档，插件也可以选择隐藏这些协议\n1. 自动生成并下发 HTTPS 证书\n2. 对代理请求的认证控制，要求用户提供凭证才能访\n3. 实时获取请求头、响应状态码等元数据\n4. 对请求动态设置规则\n5. 将请求转发给插件，由插件完全控制请求处理流程（需要匹配短协议才能实现）\n\n\n## pipe 功能\n有些请求/响应内容可能被加密或 protobuf 序列化无法看到明文，可通过以下两种方式实现内容查看与修改：\n1. **插件全权处理**\n   - 将请求直接转发至插件\n   - 插件完全控制处理流程\n   - 注意事项：\n     - Whistle 内置规则将失效\n     - 需插件自行实现所有处理逻辑\n     - 如需在抓包界面显示解密内容，插件必须调用 Whistle API 回传数据\n2. **管道流式处理（推荐）**\n   - 通过 pipe 协议建立处理管道\n   - 请求/响应流程：\n     - 进入 Whistle 时：插件解密\n     - 离开 Whistle 时：插件加密\n   - 优势：\n     - 保持明文请求特性\n     - 完整支持 Whistle 内置规则\n     - 抓包界面可直接查看明文\n\n**技术说明：**\n- pipe 流功能为可选实现，各插件支持情况请查阅具体文档\n- 方案二处理后的请求完全等同于普通明文请求\n\n## 操作界面\n\n#### 访问插件操作界面\n在 Whistle 插件管理面板中，您可以通过以下任一方式访问插件功能：\n1. 点击插件条目右侧的 `Option` 按钮\n2. 在插件列表直接单击插件名称\n\n#### 界面特性\n**呈现形式：** 模态对话框或独立标签页\n\n**核心能力：**\n1. 提供可视化配置和管理功能\n2. 支持与 Whistle 核心的实时交互\n3. 显示插件运行状态、请求统计数据、日志信息等\n\n#### 技术说明\n1. 插件本质上是与 Whistle 直接交互的 HTTP Server\n2. 操作界面的具体功能由各插件自行实现\n3. 插件可选择不提供操作界面（仅提供后台服务或[命令行操作](../cli#w2-exec)）\n\n## 扩展 Whistle 界面\n\n插件可通过以下方式增强 Whistle 的用户界面功能：\n\n#### Network 扩展\n1. 抓包列表上下文菜单\n    > 在抓包列表项右键菜单中添加自定义操作\n2. 主视图标签页\n    > 在右侧主面板区创建一级功能标签页\n2. Inspectors 分析工具扩展\n    > 在 Inspectors 面板下添加：\n    >   - 二级功能标签页\n    >   - 三级详细视图标签页\n3. Composer 请求构造器扩展\n    > 在 Composer 工具区添加二级功能标签页\n4. Tools 工具集扩展\n    > 在 Tools 功能区添加二级功能标签页\n\n#### Rules 扩展\n左侧列表上下文菜单\n> 在左侧规则文件列表的右键菜单中添加功能项\n\n#### Values 扩展\n左侧列表上下文菜单\n> 在左侧变量文件列表的右键菜单中添加功能项\n\n**实现说明：**\n- 各插件可根据功能需求选择适当的扩展位置\n- 具体扩展功能和实现方式请参阅各插件的开发文档\n- 未使用的扩展点不会影响插件基础功能\n\n## 插件内置规则\n\n插件支持通过以下规则文件扩展系统功能：\n\n1. **全局规则 (rules.txt)**\n   - 自动加载时机： 插件初始化时\n   - 作用范围： 全局请求\n   - 优先级： 低于 Rules 界面配置的规则\n   - 典型用途： 插件默认规则配置\n2. **私有规则 (_rules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 请求处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 请求预处理/改写\n3. **响应阶段规则 (resRules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 响应处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 响应后处理/改写\n\n\n插件支持灵活的内置规则配置机制：\n- **可选性：** 所有内置规则均为可选配置\n- **按需设置：** 插件只需声明必要的规则\n- **规则查看：** 通过插件管理界面的 Rules 按钮可查看当前插件配置的所有规则\n> 注意：未配置规则的插件仍可正常使用其他功能\n\n## 更新卸载插件\n\n**在 Whistle 插件管理界面中：**\n- **更新插件：** 点击插件条目右侧的 `Update` 按钮\n- **卸载插件：** 点击插件条目右侧的 `Uninstall` 按钮\n","docs/docs/rules/@.md":"# `@` 符号用法\n\n当您希望将规则存放在远程服务器或本地项目文件中，并让 Whistle 自动同步更新时，可以利用规则中的 `@` 符号实现。\n\n## 语法规则\n\n## 1. 引入规则文件 (`@path`)\n动态加载外部规则文件内容，Whistle 会定时从规则文件更新内容。\n\n**使用格式：** `@文件路径或URL`\n\n**支持类型：**\n- 远程 URL：`@https://example.com/rules.txt`\n- 本地文件：`@~/projects/rules.txt`（支持所有操作系统路径）\n- 插件接口：`@whistle.nohost/api/rules`（需插件实现接口）\n\n> **Windows 路径兼容性**：支持混用 `/` 和 `\\` 作为路径分隔符：\n> ```txt\n> @C:/whistle/rules.txt\n> @D:\\config\\proxy.rules\n> ```\n\n## 配置示例\n\n### 引入远程规则文件\n```txt\n@https://raw.githubusercontent.com/your-org/configs/master/whistle-rules.txt\n```\n\n### 引入本地项目规则\n```txt\n@/Users/username/projects/my-app/whistle-config.txt\n@~/dev/configs/rules.txt  # 使用用户目录\n```\n\n### 引入插件提供的规则\n```txt\n@whistle.nohost/api/rules\n```\n\n### 混合使用示例\n```txt\n# 引入团队共享的基础规则\n@https://team.example.com/configs/base-rules.txt\n\n# 引入个人项目特定规则\n@~/projects/my-app/whistle-overrides.txt\n\n```\n\n## 文件格式要求\n\n引入的外部文件应包含标准的 Whistle 规则语法：\n\n```txt\n# 远程规则文件示例\nwww.example.com proxy://127.0.0.1:8080\napi.example.com resCors://*\nstatic.example.com cache://3600\n```\n","docs/docs/rules/attachment.md":"# attachment\n`attachment` 协议用于设置响应头中的 `Content-Disposition` 字段，使浏览器将服务器的响应直接视为附件下载，并将文件保存为 `attachment` 设定的名称，而不是在页面内展示。\n> 类似 Koa 的 `attachment` 方法：https://koajs.com/\n\n## 规则语法\n\n`attachment` 支持多种方式来指定下载文件名：\n\n### 1. 内联值（直接指定）\n直接在规则中写明文件名。\n```txt\npattern attachment://filename [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/report attachment://年度报告.pdf\n```\n\n### 2. 内嵌值（使用代码块）\n当文件名较长或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern attachment://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n季度数据-2024Q1.csv\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern attachment://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为目标文件名。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载文件名。\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **filename** | 是 | 下载时显示的文件名。<br>• 例如：`document.pdf`, `export.xlsx`。<br>• 支持通过上述三种方式（内联/内嵌/Values）指定。<br>• ⚠️ 不支持从文件或远程 URL 加载。 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n## 配置示例\n\n### 基础示例\n让访问 example.com 首页时自动下载一个名为 `example.html` 的文件。\n```txt\nhttps://www.example.com/ attachment://example.html\n```\n\n### 配合过滤器使用\n仅当请求包含特定查询参数 `download=true` 时，才触发 PDF 文件下载。\n```txt\nhttps://api.example.com/document attachment://用户手册.pdf includeFilter:///[?&]download=true/\n```\n\n### 使用内嵌值\n当文件名比较复杂或需要注释时，使用内嵌代码块更清晰。\n````txt\nhttps://assets.example.com/data.json attachment://{my-file-name}\n\n``` my-file-name\n配置备份-20240514.json\n```\n````\n\n## 实现远程加载\n\n`attachment` 协议本身不支持从文件路径或远程 URL 加载值。如果需要实现远程加载功能，有以下两种替代方案：\n\n### 1. 通过插件扩展功能\n你可以开发插件来实现更复杂的文件名获取逻辑，比如从远程API动态获取文件名。具体开发方法请参考：[插件开发文档](../extensions/dev)\n\n### 2. 使用 @url 加载完整规则\n将包含 `attachment` 规则的完整配置文件存储在远程服务器上，然后通过 [`@url`](./@) 协议加载整个规则集。\n\n**示例：**\n1. 在远程服务器创建规则文件 `xxx.txt`：\n    ```txt\n    https://api.example.com/export attachment://remote-file.csv\n    https://static.example.com/docs attachment://文档.pdf\n    ```\n2. 在本地 Rules 配置中引用远程规则：\n    ```txt\n    @https://remote-url/xxx.txt\n    ```\n\n这样，远程文件中的 `attachment` 规则就会被加载生效。\n\n## 工作原理与关联协议\n\n1.  **核心原理**：`attachment` 协议本质上是自动设置响应头。\n    上面的示例完全等价于使用 [`resHeaders`](./resHeaders) 协议手动设置：\n    ```txt\n    https://www.example.com/ resHeaders://content-disposition=attachment;filename=\"example.html\"\n    ```\n\n2.  **优势**：相比于直接使用 `resHeaders`，`attachment` 语法更简洁、易读，专注于文件下载场景，自动处理文件名转义等细节。\n\n3.  **注意**：服务器本身返回的响应体内容不会改变。此规则只是通过修改响应头，指示浏览器如何处理这些内容。\n","docs/docs/rules/auth.md":"# auth\n\n`auth` 协议用于快速修改请求头的 `Authorization` 字段，为匹配的请求自动添加 HTTP 基本认证（Basic Authentication）所需的凭证信息。\n\n## 规则语法\n\n`auth` 支持多种方式设置请求鉴权头：\n\n### 1. 内联值（直接指定）\n直接在规则中写明用户名和密码。\n```txt\npattern auth://username:password [lineProps...] [filters...]\n# 或\npattern auth://username=test&password=123 [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/api1/ auth://admin:secret\nhttps://example.com/api2/ auth://username=admin&password=secret\n```\n\n### 2. 内嵌值（使用代码块）\n当凭证信息较复杂或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern auth://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nusername: admin\npassword: my secret password\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern auth://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为包含用户名和密码的对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern auth://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `auth://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report auth://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含鉴权信息的 JSON 或简单 YAML 文件。\n```txt\n# 从本地文件加载\npattern auth:///User/xxx/auth.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern auth://https://config.example.com/auth.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或简单 YAML 格式，包含 `username` 和 `password` 字段：\n```json\n{\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n或\n```yaml\nusername: admin\npassword: secret\n```\n\n## 设置 `Proxy-Authorization`\n```json\n{\n  \"proxy\": true,\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 鉴权凭证信息，支持多种格式：<br>• **直接格式**：`username:password` 或 `username=test&password=123`<br>• **对象格式**：包含 `username` 和 `password` 字段的对象<br>• 支持从本地文件或远程 URL 加载<br>• 支持内联、内嵌、Values、本地文件路径、远程 URL 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n## 配置示例\n\n### 基础示例\n为 example.com 的 API 接口添加基本认证：\n```txt\nhttps://api.example.com/ auth://admin:secret\n```\n\n### 使用内嵌值\n当密码包含特殊字符或空格时，使用内嵌方式更安全：\n````txt\nhttps://internal.example.com/ auth://{prod-credentials}\n\n``` prod-credentials\nusername: service-account\npassword: P@ssw0rd!2024\n```\n````\n\n### 从文件加载\n从本地配置文件加载鉴权信息：\n```txt\nhttps://example.com/api/ auth:///Users/john/config/auth.json\n```\n\n### 配合过滤器使用\n仅对 POST 请求添加鉴权：\n```txt\nhttps://example.com/api/ auth://admin:secret includeFilter://m:POST\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `api-auth` 配置：\n```txt\nhttps://example.com/api/ auth://{api-auth}\n```\n\n## 工作原理与关联协议\n\n1. **核心原理**：`auth` 协议会自动计算用户名和密码的 Base64 编码，并设置 `Authorization` 请求头。\n\n   上面的示例等价于使用 [`reqHeaders`](./reqHeaders) 协议手动设置：\n   ```` txt\n   https://example.com/api/ reqHeaders://{auth.txt} # 内容有空格，不能内联\n\n   ``` auth.txt\n   authorization: Basic YWRtaW46c2VjcmV0\n   ```\n   ````\n   其中 `YWRtaW46c2VjcmV0` 是 `admin:secret` 的 Base64 编码。\n\n2. **优势**：相比于直接使用 `reqHeaders`，`auth` 语法更简洁直观，无需手动计算 Base64 编码值。\n\n## 注意事项\n- `auth` 协议仅支持 HTTP 基本认证（Basic Authentication）\n- 对于更复杂的认证方式（如 Bearer Token、OAuth 等），请使用 [`reqHeaders`](./reqHeaders) 协议直接设置相应的 `Authorization` 头\n- 从远程 URL 加载时，请确保目标 URL 安全可靠\n\n## 关联协议 {#related}\n1. 删除请求头字段（`Delete Request Header`）：[delete://reqHeaders.xxx](./delete)\n2. 删除请求 Cookie（`Delete Request Cookie`）：[delete://reqCookies.xxx](./delete)\n3. 设置请求 Cookie：[reqCookies](./reqCookies)\n4. 设置请求鉴权头：[auth](./auth)\n5. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n","docs/docs/rules/cache.md":"# cache\n\n`cache` 协议用于快速设置响应的缓存控制头，帮助控制浏览器和服务器的缓存行为。可以为匹配的响应设置缓存策略，包括：\n- 设置固定的缓存时间\n- 禁用缓存（`no-cache`）\n- 完全禁止存储（`no-store`）\n\n这可以帮助优化网站性能或确保特定资源不会被缓存。\n\n## 规则语法\n\n`cache` 支持多种方式设置缓存策略：\n\n### 1. 内联值（直接指定）\n直接在规则中指定缓存策略。\n```txt\n# 设置缓存5秒，支持正整数、0、负整数\npattern cache://5 [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-cache`、`pragma: no-cache`\n# `no-cache` 可简写为 `no`\npattern cache://no-cache [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-store`、`pragma: no-cache`\npattern cache://no-store [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nhttps://example.com/api/data cache://no-cache\nhttps://example.com/static/js cache://86400  # 缓存24小时\n```\n\n### 2. 内嵌值（使用代码块）\n当需要复用缓存策略或策略较复杂时可使用此方式。\n````txt\npattern cache://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nno-cache\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的缓存策略。\n```txt\npattern cache://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为缓存策略。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载缓存配置。\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 缓存策略值：<br>• **数字**：缓存秒数（如 `60` 表示缓存60秒）<br>• **no-cache** 或 **no**：允许缓存但每次需重新验证<br>• **no-store**：完全禁止缓存<br>• 支持内联、内嵌、Values 引用<br>• ⚠️ 不支持从文件或远程 URL 加载 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**影响的响应头：**\n- `Cache-Control`\n- `Expires`\n- `Pragma`\n\n## 缓存策略说明\n\n| 策略 | 含义 | 典型使用场景 |\n| :--- | :--- | :--- |\n| **数字（如 `60`）** | 设置资源缓存指定秒数 | 静态资源、不经常变化的API响应 |\n| **no-cache**（或 **no**） | 允许缓存但每次使用前必须验证 | 经常变化的内容，如用户数据、实时报价 |\n| **no-store** | 完全禁止缓存任何版本 | 敏感数据、支付页面、一次性令牌 |\n\n## 配置示例\n\n### 基础示例\n```txt\n# 静态资源缓存1小时\nhttps://example.com/static cache://3600\n\n# API接口禁用缓存\nhttps://example.com/api cache://no-cache\n\n# 敏感数据禁止缓存\nhttps://example.com/account cache://no-store\n```\n\n### 使用内嵌值\n````txt\nhttps://example.com/reports cache://{report-cache-policy}\n\n``` report-cache-policy\nno-cache\n```\n````\n\n### 配合过滤器使用\n仅对特定响应状态码设置缓存：\n```txt\nhttps://example.com/api  cache://300 includeFilter://s:200\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `static-cache` 配置：\n```txt\nhttps://example.com/assets cache://{static-cache}\n```\n\n\n## 工作原理与关联协议\n\n1. **核心原理**：`cache` 协议自动设置相应的缓存控制头：\n\n   - `cache://5` 设置：\n     ```http\n     Cache-Control: max-age=5\n     Expires: [当前时间+5秒]\n     ```\n   \n   - `cache://no-cache` 设置：\n     ```http\n     Cache-Control: no-cache\n     Pragma: no-cache\n     ```\n   \n   - `cache://no-store` 设置：\n     ```http\n     Cache-Control: no-store\n     Pragma: no-cache\n     ```\n\n2. **关联协议**：\n   - **禁用缓存**：更彻底的缓存禁用方案，同时移除请求和响应中的缓存相关头。详见：[disable://cache](./disable)\n   - **手动设置头部**：对于更复杂的缓存控制，可以使用 [`resHeaders`](./resHeaders) 协议手动设置缓存头。\n\n## 注意事项\n- 缓存时间以秒为单位，不支持小数\n- 设置 `no-cache` 不代表\"不缓存\"，而是\"使用前必须验证\"\n- 对于需要完全禁用缓存的场景，建议同时使用 [`disable://cache`](./disable) 协议\n- 实际缓存行为还受服务器配置和浏览器实现的影响\n\n## 关联协议 {#related}\n1. 修改响应头：[resHeaders](./resHeaders)\n2. 删除请求头字段（`Delete Response Header`）：[delete://resHeaders.xxx](./delete)\n3. 删除请求 Cookie（`Delete Response Cookie`）：[delete://resCookies.xxx](./delete)\n4. 设置请求 Cookie：[resCookies](./resCookies)\n5. 设置缓存：[cache](./cache)\n6. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n","docs/docs/rules/cipher.md":"# tlsOptions\n\n`tlsOptions` 协议用于配置 HTTPS/TLS 连接的安全参数，包括加密算法套件、客户端证书等，用于建立加密通信通道和服务器身份验证。\n\n> **版本要求**：仅 Whistle 最新版本（≥ v2.9.101）支持此功能\n\n通过 `tlsOptions` 协议，你可以：\n- 自定义 TLS 加密算法套件\n- 配置双向认证（mTLS）所需的客户端证书\n- 设置 TLS 连接的其他安全参数\n- 控制 TLS 版本和连接选项\n\n**规则合并机制**：`tlsOptions` 支持设置任意多个配置规则，Whistle 会根据从上到下的顺序自动合并这些规则，允许你灵活组合不同的 TLS 配置。\n\n## 规则语法\n\n`tlsOptions` 支持多种方式配置 HTTPS/TLS 连接的安全参数：\n\n### 1. 内联值（直接指定）\n直接在规则中写明操作。\n```txt\n# 设置客户端证书\npattern tlsOptions://key=/path/to/client.key&cert=/path/to/client.crt\n\n# 自定义加密套件\npattern tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n\n# 或使用 ciphers 参数\npattern tlsOptions://ciphers=ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n```\n\n### 2. 内嵌值（使用代码块）\n当配置较复杂或需要复用时可使用此方式。\n````txt\npattern tlsOptions://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n{\n  passphrase: \"123456\",\n  pfx: \"-----BEGIN PKCS7-----\\n...\"\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的配置。\n```txt\npattern tlsOptions://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 TLS 配置对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern tlsOptions://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `tlsOptions://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report tlsOptions://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含 TLS 配置的 JSON 或 YAML 文件。\n```txt\n# 从本地文件加载\npattern tlsOptions:///User/xxx/tlsOptions.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern tlsOptions://https://config.example.com/tlsOptions.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或 YAML 格式：\n```json\n{\n  \"ciphers\": \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \"minVersion\": \"TLSv1.2\",\n  \"maxVersion\": \"TLSv1.3\",\n  \"secureProtocol\": \"TLSv1_2_method\"\n}\n```\n或\n```yaml\nciphers: ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\nminVersion: TLSv1.2\nmaxVersion: TLSv1.3\nsecureProtocol: TLSv1_2_method\n```\n\n---\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | TLS 配置数据，支持多种格式：<br>• 加密算法套件名称（用 `:` 分隔）<br>• `tls.connect(options)` 参数对象<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n---\n\n## 配置对象参考\n\n完整的 TLS 连接配置对象，支持 Node.js [`tls.connect()`](https://nodejs.org/docs/latest/api/tls.html#tlscreatesecurecontextoptions) 的所有参数：\n\n```js\n{\n  // 加密算法套件\n  ciphers: \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \n  // TLS 协议版本控制\n  minVersion: \"TLSv1.2\",\n  maxVersion: \"TLSv1.3\",\n  secureProtocol: \"TLSv1_2_method\",\n  \n  // 证书相关配置\n  ca: \"-----BEGIN CERTIFICATE-----\\n...\",  // CA 证书\n  cert: \"-----BEGIN CERTIFICATE-----\\n...\", // 客户端证书\n  key: \"-----BEGIN PRIVATE KEY-----\\n...\",  // 私钥\n  pfx: \"-----BEGIN PKCS7-----\\n...\",       // PFX/P12 格式证书\n  passphrase: \"cert-password\",             // 证书密码\n  \n  // 其他安全选项\n  honorCipherOrder: true,     // 优先使用服务器端的加密套件顺序\n  requestCert: true,          // 请求客户端证书（双向认证）\n  rejectUnauthorized: true,   // 拒绝未授权的证书\n  \n  // 高级选项\n  ecdhCurve: \"auto\",         // ECDH 曲线\n  dhparam: \"-----BEGIN DH PARAMETERS-----\\n...\",\n  secureOptions: 0,          // SSL 选项标志\n  sessionTimeout: 300,       // 会话超时时间（秒）\n  sessionIdContext: \"whistle\"\n}\n```\n\n---\n\n## 配置示例\n\n### 1. 自定义加密套件\n限制特定域名的 TLS 加密算法：\n```txt\nwww.example.com/path tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384\n```\n\n> **使用场景**：解决特定服务器的兼容性问题。参考：[GitHub Issue #963](https://github.com/avwo/whistle/issues/963)\n\n### 2. 配置客户端证书（双向认证 mTLS）\n\n**cert 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=/User/xxx/test.key&cert=/User/xxx/test.crt\n```\n\n**pem 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=E:\\test.key&cert=E:\\test.pem\n```\n\n**pfx 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=/User/xxx/test.pfx\n```\n\n**p12 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=E:/test.p12\n```\n\n> **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 3. 内嵌证书内容\n直接将证书内容嵌入配置中：\n````txt\n``` test.json\n{\n  key: '----xxx----- ... ----xxx-----',\n  cert: '----yyy----- ... ----yyy-----'\n}\n```\n\nwww.exaple.com/path tlsOptions://{test.json}\n````\n\n### 4. 从本地或远程文件加载配置\n**从本地文件加载：**\n```txt\nwww.example.com/path1 tlsOptions:///User/xxx/test.json\n```\n\n**从远程 URL 加载：**\n```txt\nwww.example.com/path2 tlsOptions://https://www.xxx.com/xxx/params.json\n```\n\n**使用临时文件编辑：**\n```txt\nwww.example.com/path3 tlsOptions://temp/blank.json\n```\n\n### 5. 规则合并示例\n利用规则合并特性，逐步细化配置：\n```txt\n# 第一层：为整个域名设置基础TLS版本\n*.example.com tlsOptions://minVersion=TLSv1.2\n\n# 第二层：为API子域添加客户端证书\napi.example.com tlsOptions://key=/certs/client.key&cert=/certs/client.crt\n\n# 第三层：为特定路径进一步限制加密套件\napi.example.com/secure tlsOptions://ciphers=ECDHE-RSA-AES256-GCM-SHA384\n```\n\n---\n\n## 注意事项\n\n1. **版本兼容性**：确保使用的 Whistle 版本 ≥ v2.9.101\n2. **证书格式**：不同格式的证书需要使用对应的参数：\n   - PEM 格式：使用 `key` 和 `cert` 参数\n   - PFX/P12 格式：使用 `pfx` 和 `passphrase` 参数\n3. **路径处理**：\n   - 相对路径相对于 Whistle 配置文件目录\n   - Windows 路径可以混用 `/` 和 `\\`\n4. **安全警告**：\n   - 谨慎使用 `rejectUnauthorized: false`，仅限开发环境\n   - 妥善保管客户端私钥，避免泄露\n5. **性能影响**：复杂的加密算法可能影响连接性能\n6. **调试建议**：遇到连接问题时，可先用简单的配置测试，逐步添加复杂选项\n\n---\n\n## 故障排除\n\n### Q: 证书加载失败\n**A:** 检查：\n1. 证书文件路径是否正确\n2. 证书格式是否匹配参数类型\n3. PFX 证书密码是否正确\n\n### Q: 连接被拒绝\n**A:** 检查：\n1. TLS 版本是否被服务器支持\n2. 加密算法套件是否被服务器支持\n3. 客户端证书是否有效且被服务器信任\n\n### Q: 配置未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配目标 URL\n2. Whistle 版本是否满足要求\n3. 配置语法是否正确，特别是 JSON 格式\n4. 是否存在冲突的规则（规则按从上到下顺序合并）\n\n### Q: 规则合并不符合预期\n**A:** 检查：\n1. 规则的顺序是否正确（从上到下合并）\n2. 不同规则中的配置是否冲突\n3. 使用更具体的 pattern 是否覆盖了通用配置\n","docs/docs/rules/cssAppend.md":"# cssAppend\n\n`cssAppend` 协议用于在现有的 CSS 类型响应内容体后面插入指定内容。\n\n> **生效条件**：仅对响应类型 `content-type` 包含 `html` 或 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）才生效\n> \n> ⚠️ 注意：`204`、`304` 等无响应内容体的请求不受影响\n> \n> html 页面会自动包裹 `<style>...</style>`\n\n通过 `cssAppend` 协议，你可以：\n- 在现有 CSS 文件的末尾追加新的样式规则\n- 动态添加 CSS 覆盖规则\n- 为特定页面注入自定义样式\n- 在不修改源文件的情况下扩展样式功能\n\n## 规则语法\n\n`cssAppend` 支持多种方式指定要追加的内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要追加的 CSS 内容。\n```txt\n# `value` 小括号内不能用空格或换行符\npattern cssAppend://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/index.html cssAppend://(.custom-btn{background:red;})\n```\n\n### 2. 内嵌值（使用代码块）\n当要追加的 CSS 内容有空格或换行符或需要复用时可使用此方式。\n````txt\npattern cssAppend://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n.dark-mode {\n  background: #333;\n  color: #fff;\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的 CSS 内容。\n```txt\npattern cssAppend://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 CSS 内容。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern cssAppend://temp.css\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `cssAppend://temp.css`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/test.css cssAppend://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.css\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载要追加的 CSS 内容。\n```txt\n# 从本地文件加载\npattern cssAppend:///User/xxx/custom.css\n\n# 从远程 URL 加载（支持 http 和 https）\npattern cssAppend://https://cdn.example.com/styles/override.css\n```\n# 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要追加的 CSS 内容，支持多种格式：<br>• 纯 CSS 代码文本<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**特殊说明：**\n- 仅对 `content-type` 响应头包含 `css` 的请求生效\n- 对 `204`、`304` 等无响应体的状态码不生效\n- 追加的内容会直接拼接到原 CSS 文件的末尾\n\n\n## 配置示例\n\n#### 场景 1：添加响应式断点\n```` txt\nwww.example.com/responsive.css cssAppend://{test.css}\n\n``` test.css\n@media (max-width: 768px) {\n  .sidebar { display: none; }\n  .main-content { width: 100%; }\n}\n```\n\n````\n\n#### 场景 2：覆盖第三方库样式\n```` txt\ncdn.example.com/bootstrap.min.css cssAppend://{test2.css}\n\n``` test2.css\n/* 覆盖 Bootstrap 默认样式 */\n.btn-primary { background-color: #007bff !important; }\n.container { max-width: 1400px; }\n```\n````\n\n#### 场景 3：添加打印样式\n````txt\nwww.example.com/print.css cssAppend://{test3.css}\n\n``` test3.css\n@media print {\n  .no-print { display: none; }\n  a::after { content: \" (\" attr(href) \")\"; }\n}`\n```\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n","docs/docs/rules/cssBody.md":"# cssBody\n将现有响应内容体t替换成指定内容（（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssBody://(Hello) file://(-test-)\nwww.example.com/path2 cssBody://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssBody://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssBody://{body.txt} file://(-test-)\nwww.example.com/path2 cssBody://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello world.</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssBody:///User/xxx/test.css\nwww.example.com/path2 cssBody://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssBody://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/cssPrepend.md":"# cssPrepend\n在现有响应内容体前面插入指定内容（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssPrepend://(Hello) file://(-test-)\nwww.example.com/path2 cssPrepend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 cssPrepend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello world.</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssPrepend:///User/xxx/test.css\nwww.example.com/path2 cssPrepend://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssPrepend://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/delete.md":"# delete\n用于删除请求 URL、请求/响应头、请求/响应内容。\n\n## 规则语法\n``` txt\npattern delete://prop1|prop2|... [filters...]\n\n# 等效于：\npattern delete://prop1 delete://prop2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | `pathname`：删除请求路径（不包含请求参数）<br/>`pathname.index`：`index` 为路径分段序号 `...、-1、0、1、...` 或关键字 `last` 详见下面说明<br/>`urlParams`：删除所有请求参数<br/>`urlParams.xxx`：删除 URL 的 `xxx 参数`<br/>`reqHeaders.xxx`: 删除请求头的 `xxx` 字段<br/>`resHeaders.xxx`: 删除响应头的 `xxx` 字段<br/>`reqBody`: 删除所有请求内容<br/>`resBody`: 删除所有响应内容<br/>`reqBody.xxx.yyy`: 删除请求内容中类型为表单(form)或JSON的 `xxx.yyy` 字段<br/>`resBody.xxx.yyy`:  删除响应类型为 JSONP 或 JSON 的响应内容里的 `xxx.yyy` 字段<br/>`reqType`: 删除请求头 `content-type` 里的类型，不包含可能存在的 charset<br/>`resType`: 删除响应头 `content-type` 里的类型，不包含可能存在的 charset<br/>`reqCharset`: 删除请求头 `content-type` 里可能存在的 charset<br/>`resCharset`: 删除响应头 `content-type` 里可能存在的 charset<br/>`reqCookies.xxx`: 删除请求头的里面名为 `xxx` 的 cookie<br/>`resCookies.xxx`: 删除响应头的里面名为 `xxx` 的 cookie|    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nhttps://www.example.com/path delete://reqCookies.token|resCookies.token\n\nhttps://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json delete://resBody.name resType://json\n```\n上述 cookie 删除操作仅作用于请求/响应过程中的 cookie，不会修改浏览器本地存储的 cookie。如需修改浏览器持久化 cookie，可通过以下方式实现：\n- 通过 [jsPrepend](./jsPrepend) 等注入 JS 删除\n- 通过 [resCookies](./resCookies) 设置 cookie 过期\n\n## 删除路径\n> 支持版本：v2.9.102 及以上\n\n### 规则语法\n```\n# 基本格式\n目标域名 delete://pathname.索引号\n\n# 示例\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.-1\n```\n\n### 规则说明\n该规则用于删除请求 URL 中路径的特定分段。Whistle 会提取不包含查询参数的请求路径，并通过 `/` 分割成数组后进行删除操作。\n\n**示例解析：**\n- 请求URL：`https://www.example.com/api/path/to/xxx?query`\n- 提取路径：`api/path/to/xxx`\n- 分割数组：`['api', 'path', 'to', 'xxx']`\n- 应用规则：\n  - `delete://pathname.0` → 删除 'api'\n  - `delete://pathname.1` → 删除 'path' \n  - `delete://pathname.-1` → 删除 'xxx'\n- 最终路径：`/to`\n- 完整结果：`https://www.example.com/to?query`\n\n### 索引规则\n- **正数索引**：从 0 开始，表示从前往后的顺序\n- **负数索引**：从 -1 开始，表示从后往前的位置（-1 = 最后一段，-2 = 倒数第二段，以此类推）\n- **特殊值**：使用 `last` 可删除最后一段路径但保留末尾斜杠\n- **边界情况**：超出数组范围的索引将被忽略\n\n### 重要说明\n当路径以 `/` 结尾时，分割后的数组最后会包含一个空字符串项：\n- 路径：`/api/path/to/xxx/`\n- 分割结果：`['api', 'path', 'to', 'xxx', '']`\n\n### 使用建议\n- 使用 `pathname.-1` 删除最后一段且不保留末尾斜杠\n- 使用 `pathname.last` 删除最后一段但保留末尾斜杠\n\n**对比示例：**\n```\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.last\n```\n- 请求：`https://www.example.com/api/path/to/xxx?query`\n- 结果：`https://www.example.com/to/?query` (保留末尾斜杠)\n\n## 特殊字符\n如果对象的 key 包含以下字符，在路径表达式中需要使用反斜杠 `\\` 进行转义：\n\n- 分隔符：`|` 和 `&`\n- 空格：` `（一个空格）\n- 点：`.`\n- 控制字符：制表符 `\\t`、换行符 `\\n`、回车符 `\\r`、换页符 `\\f`、垂直制表符 `\\v`\n\n### 转义规则\n| 原始字符 | 转义写法 |\n|----------|----------|\n| `|`      | `\\|`     |\n| `&`      | `\\&`     |\n| 空格     | `\\ `     |\n| `.`      | `\\.`     |\n| 制表符   | `\\t`     |\n| 换行符   | `\\n`     |\n| 回车符   | `\\r`     |\n| 换页符   | `\\f`     |\n| 垂直制表符 | `\\v`   |\n\n> 注意：反斜杠 `\\` 本身也是转义字符，若要表示字面反斜杠，需写作 `\\\\`。\n\n### 示例\n假设需要删除请求 body 中的两个 key：\n- key1 为 `\\n .p`（包含换行符、空格、点、字母 p）\n- key2 为 `test|&test`（包含竖线和 &）\n\n对应的路径表达式应写作：\n```\nreqBody.\\n\\ \\.p.test\\|\\&test\n```\n其中：\n- `\\n` 表示换行符\n- `\\ ` 表示空格\n- `\\.` 表示点\n- `\\|` 表示竖线\n- `\\&` 表示 & 符号\n\n完整请求示例：\n```\n# 删除请求 body 中的 key1 和 key2\nhttps://www.example.com/path delete://reqBody.\\n\\ \\.p.test\\|\\&test\n```","docs/docs/rules/disable.md":"# disable\n通过规则禁用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern disable://action1|action2|... [filters...]\n\n# 等效于：\npattern disable://action1 disable://action2 ... [filters...]\n```\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n- `301`：禁止 `301` 跳转，`301` 强制转 `302`（也可以用 [replaceStatus](./replaceStatus)）\n- `abort`：禁用 abort 功能，详见：[enable](./enable)\n- `abortReq`：禁用 abortReq 功能，详见：[enable](./enable)\n- `abortRes`：禁用 abortRes 功能，详见：[enable](./enable)\n- `authCapture`：禁用 `authCapture` 功能，详见：[enable](./enable)\n- `auto2http`：禁用 auto2http 功能，详见：[enable](./enable)\n- `autoCors`：使用 [file](./file) 协议替换请求时，如果 Whistle 检测到该请求属于跨域请求，会自动添加必要的 CORS (跨域资源共享) 头信息，可以通过 `disable://autoCors` 禁用\n- `ajax`: 删除请求头 `x-requested-with`\n- `bigData`：禁用 bigData 功能，详见：[enable](./enable)\n- `capture` 或 `https`：禁用 `Enable HTTPS`，详见：[enable](./enable)\n- `captureIp`：禁用 captureIp 功能，详见：[enable](./enable)\n- `captureStream`：禁用 captureStream 功能，详见：[enable](./enable)\n- `clientCert`：禁用 clientCert 功能，详见：[enable](./enable)\n- `clientId`：删除请求头 `x-whistle-client-id`\n- `clientIp`：删除请求头 `x-forwarded-for`\n- `customParser`：禁用 customParser 功能，详见：[enable](./enable)\n- `cache`: 禁用缓存\n- `dnsCache`: 禁止 DNS 缓存\n- `csp`: 禁用 CSP\n- `cookies`: 禁用请求和响应的 cookie\n- `reqCookies`: 禁用请求的 cookie\n- `resCookies`: 禁用响应的 cookie\n- `flushHeaders`：禁用 flushHeaders 功能，详见：[enable](./enable)\n- `forHttp`：禁用 forHttp 功能，详见：[enable](./enable)\n- `forHttps`：禁用 forHttps 功能，详见：[enable](./enable)\n- `forceReqWrite`：禁用 forceReqWrite 功能，详见：[enable](./enable)\n- `forceResWrite`： 禁用 forceResWrite 功能，详见：[enable](./enable)\n- `gzip`：禁止压缩响应内容\n- `h2`：禁用 h2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器启用 HTTP2\n- `http2`：禁用 http2 功能，详见：[enable](./enable)\n  > 禁用：浏览器 -xx-> Whistle 代理 -xx-> 服务器全部启用 HTTP2\n- `httpH2`：禁用 httpH2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：禁用 hide 功能，详见：[enable](./enable)\n- `hideComposer`：禁用 hideComposer 功能，详见：[enable](./enable)\n- `hideCaptureError`：禁用 hideCaptureError 功能，详见：[enable](./enable)\n- `interceptConsole`：禁用 interceptConsole 功能，详见：[enable](./enable)\n- `internalProxy`：禁用 internalProxy 功能，详见：[enable](./enable)\n- `proxyFirst`：禁用优先使用 [proxy](./proxy) 规则\n- `proxyHost`：禁用 proxyHost 功能，详见：[enable](./enable)\n- `proxyTunnel`：禁用 proxyTunnel 功能，详见：[enable](./enable)\n- `keepCSP`：禁用 keepCSP 功能，详见：[enable](./enable)\n- `keepAllCSP`：禁用 keepAllCSP 功能，详见：[enable](./enable)\n- `keepCache`：禁用 keepCache 功能，详见：[enable](./enable)\n- `keepAllCache`：禁用 keepAllCache 功能，详见：[enable](./enable)\n- `keepAlive`：禁止缓存请求连接\n- `keepClientId`：禁用 keepClientId 功能，详见：[enable](./enable)\n- `keepH2Session`：无需每个 TUNNEL 连接对应一个 HTTP2 连接（默认 `是`，建议使用默认即可）\n- `safeHtml`：禁用 safeHtml 功能，详见：[enable](./enable)\n- `strictHtml`：禁用 strictHtml 功能，详见：[enable](./enable)\n- `proxyConnection`：通过 proxy、socks 等代理协议转发请求时设置 `proxy-connection: close`\n- `ua`: 删除请求头 `user-agent`\n- `proxyUA`：通过 proxy、socks 等代理协议转发请求时删除请求头 `user-agent`\n- `referer`: 禁用 `referer`\n- `rejectUnauthorized`：`rejectUnauthorized` 设置为 `true`\n- `requestWithMatchedRules`：禁用 requestWithMatchedRules 功能，详见：[enable](./enable)\n- `responseWithMatchedRules`：禁用 responseWithMatchedRules 功能，详见：[enable](./enable)\n- `secureOptions`：禁用建立 HTTP2 连接的默认 `options`\n- `timeout`：禁用请求超时设置\n- `trailerHeader`：删除响应头 `trailer`\n- `trailers`：删除 trailers\n- `tunnelAuthHeader`：删除代理鉴权头 `proxy-authorization`\n- `tunnelHeadersFirst`：禁用 tunnelHeadersFirst 功能，详见：[enable](./enable)\n- `useLocalHost`：禁用 useLocalHost 功能，详见：[enable](./enable)\n- `useSafePort`：禁用 useSafePort 功能，详见：[enable](./enable)\n- `userLogin`：禁用设置 [statusCode://401](./statusCode) 时显示登录框\n- `useSniAsTunnelDomain`：禁用 `enable://useSniAsTunnelDomain`\n- `weakRule`：禁用 weakRule 功能，详见：[enable](./enable)\n\n## 配置示例\n``` txt\n# 禁用请求的缓存，同时删除请求和响应头的缓存字段\n# 跟 cache 协议的区别是，cache 只是用来设置响应的缓存头\nwwww.example.com/path disable://cache\n\n# 禁用请求和响应的 cookies\nwwww.example.com/path disable://cookies\n\n# 只禁用请求的 cookies\nwwww.example.com/path disable://reqCookies\n\n# 只禁用响应的 cookies\nwwww.example.com/path disable://resCookies\n\n# 删除 user-agent\nwwww.example.com/path disable://ua\n\n# 删除 referer\nwwww.test.com/path disable://referer\n\n# 删除csp策略\nwwww.test.com/path disable://csp\n\n# 禁用 timeout，默认情况下 Whistle 对每个请求如果 36s 内没有发生数据传输，会认为请求超时\nwwww.test.com/path disable://timeout\n\n# 把 301 转成 302，防止 cache\nwwww.test.com/path disable://301\n\n# 禁用 HTTPS 拦截\nwwww.test.com/path disable://capture\n\n# 不缓存 DNS 结果\nwwww.test.com/path disable://dnsCache\n\n# 禁用代理服务器请求链接复用\nwwww.test.com/path disable://keepAlive\n\n# 删除请求头 `x-requested-with`\nwwww.test.com/path disable://ajax\n\n# 也可以同时禁用多个\nwww.example.com/path disable://cache|cookies|ua|referer|csp|timeout|301|intercept|dnsCache|keepAlive|autoCors\n```\n\n关联操作：[enable](./enable)\n","docs/docs/rules/enable.md":"# enable\n通过规则启用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern enable://action1|action2|... [filters...]\n\n# 等效于：\npattern enable://action1 enable://action2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n- `abort`：请求或响应阶段中断请求（根据匹配阶段）\n- `abortReq`：请求阶段中断请求\n- `abortRes`：响应阶段中断请求\n- `authCapture`：强制在转成 HTTPS 之前执行 [auth hook](../extensions/dev#auth)（默认是转成 HTTPS 请求后再执行插件的 auth hook）\n- `auto2http`：开启 HTTPS 请求报 TLS 错误自动转成 HTTP 请求，默认情况下如果 serverIP 是本地 IP 会自动启用\n- `bigData`：扩大抓包数据显示限制(2M→16M)\n- `emudhra`：使用已公开泄露的 eMudhra 中间 CA 私钥签发动态生成的 TLS 证书，并发送完整证书链\n- `br`：启用 BR 压缩响应内容\n- `gzip`：启用 GZIP 压缩响应内容\n- `deflate`：启用 Deflate 压缩响应内容\n- `capture` 或 `https`：Enable HTTPS（同 [HTTPS菜单功能](../gui/https.html)）\n- `captureIp` ：域名为 IP 的请求，默认不解密 HTTPS 请求，可以通过 `enable://captureIp` 启用解析 HTTPS 请求\n- `captureStream`：将抓取到的请求与响应内容以数据流的形式，实时输出到抓包界面并动态追加显示\n- `clientCert`：启用客户端与服务器之间的双向认证 (mTLS)\n- `clientId`：请求头带上 `x-whistle-client-id: Whistle本地生成的唯一ID`\n- `clientIp`：为匹配的非本地请求设置 `x-forwarded-for` 请求头，将客户端的真实IP地址透传给上游服务\n- `customParser`：自定义抓包界面显示内容，用法参考插件：https://github.com/whistle-plugins/whistle.custom-parser\n- `flushHeaders`：`response.writeHead(...)` 后调用 [response.flushHeaders](https://nodejs.org/docs/latest/api/http.html#responseflushheaders)（默认执行）\n- `forHttp`：让 `capture` 功能只对 HTTP 请求生效\n- `forHttps`：让 `capture` 功能只对 HTTPS 请求生效\n- `forceReqWrite`：使用 [reqWrite](./reqWrite)、[reqWriteRaw](./reqWriteRaw) 将请求数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceReqWrite` 强制覆盖\n- `forceResWrite`： 使用 [resWrite](./resWrite)、[reqWriteRaw](./reqWriteRaw) 将响应数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceResWrite` 强制覆盖\n- `h2`：Whistle 代理 -> 服务器启用 HTTP2\n- `http2`：浏览器 -> Whistle 代理 -> 服务器全部启用 HTTP2\n- `httpH2`：Whistle 代理 -> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：在界面上隐藏抓包数据（不包括 `captureError` 和 Composer 发出的请求）\n- `hideComposer`：隐藏 Composer 发出的请求\n- `hideCaptureError`：隐藏 `captureError` 请求\n- `showHost`：将服务器 IP 设置到响应头 `x-host-ip`\n- `ignoreSend`：WebSocket 和 TUNNEL 请求时忽略发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `ignoreReceive`：WebSocket 和 TUNNEL 请求时忽略接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseSend`：WebSocket 和 TUNNEL 请求时暂停发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseReceive`：WebSocket 和 TUNNEL 请求时暂停接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `inspect`：使的在 Inspectors / Frames 看到 TUNNEL 请求的内容\n- `interceptConsole`：截获 `console.xxx` 的请求并显示在 Whistle 管理界面的 Log 面板（默认开启）\n- `internalProxy`：利用 `proxy`、`socks` 等代理协议将请求转发至其他代理服务器（如另一 Whistle 实例）。启用此功能后，已在第一层代理解密的 HTTPS 请求将以明文形式在代理链中传输，从而上游代理可以直接获取明文数据\n- `proxyFirst`：优先使用 [proxy](./proxy) 规则（默认情况下，同时匹配 `host` 和 `proxy`，只有 `host` 生效）\n- `proxyHost`：[proxy](./proxy) 和 [host](./host) 同时生效\n- `proxyTunnel`：跟 `proxyHost` 一同使用，让上游代理再次通过隧道代理到上上游的 HTTP 代理，详见下面的示例\n- `keepCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头大 `csp` 字段，如果想保留这些字段可以用 `enable://keepCSP`\n- `keepAllCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的 `csp` 字段，如果想保留这些字段可以用 `enable://keepAllCSP`\n- `keepCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepCache`\n- `keepAllCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepAllCache`\n- `keepClientId`：保留请求原有的 `x-whistle-client-id` 请求头（默认会删除请求带过来的  `x-whistle-client-id`）\n- `safeHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `{` 和 `[`（JSON 对象开头字符），如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `strictHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `<`，如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `multiClient`：Whistle 作为公共代理且启用 `enable://clientId` 时会为所有请求添加一个固定的 `x-whistle-client-id` 请求头，这导致上游服务无法区分不同客户端。启用 `enable://multiClient` 后，将为每个客户端连接生成并维持一个唯一且不变的标识符，确保上游服务能准确识别请求来源\n- `requestWithMatchedRules`：在请求头带上当前匹配的规则\n- `responseWithMatchedRules`：在响应头带上当前匹配的规则\n- `tunnelHeadersFirst`：用于控制请求头合并的优先级。插件可通过 [tunnelKey](../extensions/dev) 将隧道（TUNNEL）请求头传递至后续阶段。默认的合并规则是：若隧道头与解析后的普通请求头存在同名键，则保留普通请求头的值。启用 `enable://tunnelHeadersFirst` 可改变这一行为，确保隧道请求头优先，从而强制覆盖任何冲突的普通头\n- `useLocalHost`：修改 `log` 和 `weinre` 请求 `URL` 的域名，使用内置域名\n- `useSafePort`：修改 `log` 和 `weinre` 请求 `URL` 的端口，使用内置端口\n- `userLogin`：设置 [statusCode://401](./statusCode) 是否显示登录框（默认显示）\n- `useSniAsTunnelDomain`：当 Tunnel 域名与请求中的 SNI 不一致时，使用 SNI 中携带的域名作为 Tunnel 域名\n- `weakRule`：默认情况下，当配置了 [file](./file) 等协议时，[proxy](./proxy) 规则会自动失效。通过设置 `weakRule` 属性，可以提升 [proxy](./proxy) 规则的优先级，使其在上述场景中仍然生效\n- `socket`：在启用 HTTPS 解析（`Enable HTTPS` 或 `enable://https`）后，发往 `80/443` 端口的 TUNNEL 请求会被强制尝试解析为 HTTP/HTTPS 流量。默认情况下，若解析失败，该连接将被销毁；而其他端口的请求解析失败则会继续以 TUNNEL 方式传输。通过设置 `enable://socket`，可让发往 `80/443` 端口的请求在解析失败时同样降级为 TUNNEL 连接，避免连接被销毁\n- `websocket`：用于处理非标准 WebSocket 连接。某些请求虽使用 WebSocket 协议传输，但其 Upgrade 请求头并非标准值（如 `Upgrade: websocket`）。默认情况下，Whistle 会将其视为普通 TCP 连接而不解析数据。启用 `enable://websocket` 可强制 Whistle 识别此类连接为 WebSocket 协议并进行数据解析\n\n\n## 配置示例\n``` txt\n# Enable HTTPS\nwww.example.com enale://https\n\n# 延迟 3000毫秒终止请求\nwww.example.com/path reqDelay://3000 enable://abortReq\n\n# 延迟 5000毫米终止响应\nwww.example.com/path resDelay://5000 enable://abortRes\n\n# 本地替换的内容开启 GZIP\nwww.example.com/path file:///User/xxx/test enable://gzip\n\n# 给上游代理设置 hosts (10.10.10.20:8888)\nwww.example.com/path proxy://10.1.1.1:8080 10.10.10.20:8888 enable://proxyHost\n\n# 通过上游 HTTP 代理 (10.1.1.1:8080) 将请求通过隧道代理到指定的 HTTP 代理(10.10.10.20:8080)\nwww.example.com proxy://10.1.1.1:8080 10.10.10.20:8080 enable://proxyHost|proxyTunnel\n\n# 启用浏览器 -> Whistle 代理 -> 服务器整个链路的 HTTP2 功能\nwww.example.com enable://http2\n\n# 启用 Whistle 代理 -> 服务器的 HTTP2 功能\nwww.example.com enable://h2\n\n# 强制 Whistle 代理 -> 服务器的 HTTP 请求使用 HTTP2 传输\nwww.example.com enable://httpH2\n\n# 安全注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `{` 才注入\nwww.example.com/path enable://safeHtml\n\n# 严格HTML注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `<` 才注入\nwww.example.com/path enable://strictHtml\n\n# 自动添加 x-forwarded-for 请求头传递客户端真实 IP\nwww.example.com enable://clientIp\n\n# 扩大抓包数据显示限制(2M→16M)\nwww.example.com/path enable://bigData\n\n# 修改 log/weinre 请求 `URL` 的域名或端口\nwww.example.com/path enable://useLocalHost|useSafePort\n\n# 强制 reqWrite/reqWriteRaw/resWrite/resWriteRaw 覆盖已有的文件\nwww.example.com/path enable://forceReqWrite|forceResWrite\n\n# 强制 HTTPS 请求被解析前也走 `auth hook`（默认是转成 HTTPS 请求后再执行插件的 auth hook）\nwww.example.com enable://authCapture\n```\n\n关联操作：[disable](./disable)\n","docs/docs/rules/excludeFilter.md":"# excludeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation excludeFilter://p1 excludeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 且不能匹配 `p1`、`p2`、... 中的任一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求不生效\nwww.example.com/api/data proxy://127.0.0.1:8080 excludeFilter://m:GET excludeFilter://m:post\n```\n\n可以跟 [includeFilter](./includeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n","docs/docs/rules/file.md":"# file\n\n`file` 协议用于将请求映射到本地文件系统，用本地文件的内容作为响应返回给客户端，适用于以下场景：\n\n- **搭建本地开发环境**：快速搭建本地开发服务器\n- **调试本地前端页面**：直接调试本地 HTML、CSS、JavaScript 文件\n- **接口 Mock 场景**：使用本地 JSON 文件模拟 API 接口响应\n- **静态资源服务**：将本地目录作为静态资源服务器使用\n\n## 规则语法\n\n`file` 协议支持多种方式指定本地文件内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要返回的内容，适用于简短的文本内容。注意：`value` 不能包含空格和换行符。\n\n```txt\npattern file://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/api/data file://({\"status\":\"ok\"})\n```\n\n**注意：内联值必须用括号 `()` 包裹，否则会被识别为本地文件路径，可能导致 404 错误：**\n``` txt\n# 正确写法：返回字符串 \"success\"\npattern file://(success)\n\n# 错误写法：会被当作路径处理，可能导致 404\npattern file://success\n```\n\n> **file 协议的内联值语法要点**：\n> - 必须使用 `()` 包裹内容\n> - 内容不能包含空格和换行符\n> - 可以是字符串、JSON、HTML片段等\n> - 如果不加 `()`，Whistle 会将其解释为文件路径或 `{key}` 并尝试加载对应文件或 key 值\n\n### 2. 内嵌值（使用代码块）\n当需要处理包含空格、换行符的复杂内容，或希望复用某段配置时，推荐使用此方式。\n\n````txt\npattern file://{custom-key.json} [lineProps...] [filters...]\n\n``` custom-key.json\n{\n  \"status\": \"success\",\n  \"data\": {\n    \"message\": \"Hello from local file\"\n  }\n}\n```\n````\n\n**最佳实践**：建议为 `key` 添加响应类型后缀（如 `.json`、`.html`、`.css`），Whistle 会自动根据后缀设置正确的 `Content-Type`。\n\n### 3. 引用 Values 中的值\n当内容较大时，可以将其存储在 `Values` 配置区中。\n\n```txt\npattern file://{key-of-values.html} [lineProps...] [filters...]\n```\n\n**前提**：在 `Values` 中存在名为 `key-of-values.html` 的键，其值为要返回的内容。\n\n**容量建议**：\n- 小于 2KB 的内容：建议使用内联或内嵌方式\n- 2KB 至 200KB 的内容：建议存储在 `Values` 中\n- 大于 200KB 的内容：建议使用本地文件\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern file://temp.html\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `file://temp.html`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report file://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.html\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件路径或远程 URL 加载\n从本地文件系统或远程 URL 加载响应内容。\n\n```txt\n# 从本地文件加载\npattern file:///User/xxx/test.html\n\n# 从远程 URL 加载\npattern file://https://example.com/template.html\n```\n\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要返回的文件内容或路径，支持多种格式：<br>• 本地文件或目录路径<br>• 远程 URL<br>• 内联、内嵌、Values 引用内容 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>**示例**：<br>• `lineProps://important`：提升规则优先级<br>• `lineProps://weakRule`：当同时配置 [file](./file) 和 [proxy](./proxy) 规则时，默认 [proxy](./proxy) 会失效。通过此属性可提升 [proxy](./proxy) 规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n\n## 配置示例\n\n### 1. 基础文件路径映射\n```txt\n# 将域名映射到本地目录\nwww.example.com/path file:///Users/username/projects/my-site\n\n# Windows 系统路径\nwww.example.com/path file://D:\\projects\\my-site\n```\n\n**特性说明**：\n- **自动路径拼接**：访问 `https://www.example.com/path/x/y/z` 会映射到 `/Users/username/projects/my-site/x/y/z`\n- **禁用路径拼接**：使用 `< >` 包裹路径可以禁用自动拼接\n    ```txt\n    www.example.com/path file://</Users/username/projects/my-site/index.html>\n    ```\n    > 访问 `https://www.example.com/path/x/y/z` 只会返回 `/Users/username/projects/my-site/index.html` 文件\n\n### 2. JSONP 接口 Mock\n````txt\n# 内联方式\nwww.example.com/jsonp file://`(${query.callback}({\"status\":\"ok\"}))`\n\n# 内嵌方式\n``` jsonp-response\n${query.callback}({\n  \"status\": \"error\",\n  \"message\": \"Invalid parameter\"\n})\n```\nwww.example.com/jsonp file://`{jsonp-response}`\n````\n\n> **模板字符串限制**：模板字符串在以下场景无法直接生效：\n> - 引用本地文件路径时\n> - 引用远程 URL 地址时\n> \n> 遇到上述限制时，可以使用 [tpl](./tpl) 协议作为替代方案。\n\n### 3. 配合过滤器使用\n```txt\n# 排除特定接口\nwww.example.com/api/path file:///Users/username/mock-data excludeFilter://*/api/auth\n\n# 根据请求体内容匹配\nwww.example.com/api/search file:///Users/username/search-results.json includeFilter://b:/\"type\":\\s*\"advanced\"/i\n\n# 仅匹配 POST 请求\nwww.example.com/api/submit file:///Users/username/success.json includeFilter://m:POST\n```\n\n### 4. 多目录搜索\n```txt\n# 多个文件或目录用 `|` 分隔\nwww.example.com/static/path file:///path/to/project1|/path/to/project2|/path/to/project3\n```\n\n**查找逻辑**：\n1. 按顺序检查每个目录\n   - `/path/to/project1/static/file.js`\n   - `/path/to/project2/static/file.js`\n   - `/path/to/project3/static/file.js`\n2. 找到第一个存在的文件立即返回\n3. 全部未找到时返回 `404 Not Found`\n\n## 高级用法\n\n### 动态路径处理\n```txt\n# 使用正则表达式捕获组\n/^https?://www\\.example\\.com/user/(\\d+)/profile file:///Users/username/mock/profiles/user-$1.json\n\n# 如果不限制数字，可以用通配符\n^www.example.com/user/*/profile file:///Users/username/mock/profiles/user-$1.json\n```\n\n### 环境特定配置\n````txt\n``` dev-config\n{\n  \"apiBase\": \"http://localhost:3000\",\n  \"debugMode\": true\n}\n```\n\n``` prod-config\n{\n  \"apiBase\": \"https://api.example.com\",\n  \"debugMode\": false\n}\n```\n\n# 通过 cookie：env=dev 判断是否为开发环境\nwww.example.com/config file://{dev-config} includeFilter://reqH:cookie=/env=dev/\nwww.example.com/config file://{prod-config} # 默认正式环境\n````\n\n### 组合使用其他协议\n```txt\n# 先使用 file 提供静态文件，再设置响应头\nwww.example.com file:///Users/username/static-files cache://3600\n\n# 对于不存在的文件，使用 xfile 允许请求继续\nwww.example.com xfile:///Users/username/static-files\n```\n\n## 与 resBody 的区别\n\n`file` 协议与 [`resBody`](./resBody) 协议的主要区别在于请求处理流程和响应机制：\n\n**`file` 协议：***\n- **请求流程**：匹配 `file` 协议的请求**不会发送到后台服务器**\n- **响应机制**：直接返回指定文件内容，并响应 200 状态码\n- **网络开销**：零网络延迟，完全本地处理\n\n**示意图**：\n```\n客户端请求 → Whistle (匹配file规则) → 读取本地文件 → 返回响应 (200)\n                  ↓\n            (不发送到服务器)\n```\n\n**`resBody` 协议：**\n- **请求流程**：请求**会先发送到后台服务器**，获取原始响应\n- **响应机制**：收到服务器响应后，用指定内容**替换**原始响应体\n- **网络开销**：包含完整的请求-响应网络往返\n\n**示意图**：\n```\n客户端请求 → Whistle → 发送到服务器 → 收到原始响应 → 替换响应体 → 返回修改后的响应\n```\n\n## 关联协议\n\n1. **允许未匹配文件继续访问**：[xfile](./xfile)\n   - 当本地文件不存在时，允许请求继续访问原始服务器\n   - 适合静态资源服务器的开发场景\n\n2. **使用远程 URL 内容替换**：[https](./https) 或 [http](./http)\n   - 使用远程服务器的内容作为响应\n   - 注意：不建议使用 `file://https://xxx` 这种形式\n\n3. **域名解析重定向**：[host](./host)\n   - 修改域名解析，将请求导向指定 IP\n\n4. **模板渲染**：[tpl](./tpl)\n   - 支持更复杂的模板渲染和动态内容生成\n\n\n\n## 注意事项\n\n### 路径处理\n1. **相对路径**：除了插件内部的规则外，不建议使用相对路径。相对路径相对于 Whistle 配置文件所在目录。\n2. **绝对路径**：支持系统绝对路径\n3. **用户目录**：支持 `~` 表示用户主目录\n4. **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 文件编码\n1. **文本文件**：默认使用 UTF-8 编码\n2. **编码问题**：如遇乱码，请检查文件的实际编码格式\n\n\n## 故障排除\n\n### Q: 文件未找到（404）\n**A:** 检查：\n1. 文件路径是否正确\n2. 文件是否存在且有读取权限\n3. 是否使用了 `< >` 禁用了路径拼接\n\n### Q: 乱码问题\n**A:** 检查：\n1. 文件的实际编码格式\n2. 响应头的 `Content-Type` 是否正确\n3. 文件内容是否包含非法字符\n\n### Q: 规则未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配\n2. 是否有其他规则覆盖\n3. 过滤器条件是否满足\n\n### Q: 模板字符串不生效\n**A:** 检查：\n1. 是否在文件路径或远程 URL 场景下使用模板字符串\n2. 如果是，请改用 [tpl](./tpl) 协议\n\n\n\n## 扩展阅读\n\n- [匹配模式文档](./pattern)：详细了解 URL 匹配规则\n- [操作指令文档](./operation)：了解内容加载的多种方式\n- [附加配置](./lineProps)：详细了解一些特殊功能\n- [过滤器](./filters)：了解更多过滤器功能\n","docs/docs/rules/filters.md":"# 过滤器\n\n当需要基于请求属性或响应属性（而不仅仅是URL）进行匹配时，可以使用过滤器来实现更精细化的规则控制，语法结构：\n\n``` txt\npattern opertaion includeFilter://pattern1 ... excludeFilter://patternx ...\n```\n> 多个过滤器间为「或」匹配，只要匹配其中一个过滤条件就成立\n\n## 过滤器类型\n\n| 过滤器类型     | 语法格式                  | 用途                     |\n| -------------- | ------------------------- | ------------------------ |\n| **包含过滤器** | `includeFilter://pattern` | 只匹配符合指定条件的请求 |\n| **排除过滤器** | `excludeFilter://pattern` | 排除符合指定条件的请求   |\n\n## pattern 类型\n\n用于 `includeFilter://<pattern>` 和 `excludeFilter://<pattern>` 的匹配表达式。\n\n> **通用规则**\n> - 未指定前缀时，默认匹配请求 URL。\n> - 关键字匹配默认不区分大小写。\n> - 正则格式为 `/regexp/`，末尾可加 `i` 表示忽略大小写，例如 `/regexp/i`。\n> - `chance` 的 `probability` 取值范围为 `0 ~ 1`。\n\n| 语法 | 匹配目标 | 示例 |\n| --- | --- | --- |\n| `b:pattern` | 请求体内容：关键字（不区分大小写）或正则 | `includeFilter://b:keyword`<br>`excludeFilter://b:/regexp/i` |\n| `m:pattern` | HTTP 方法：方法名（不区分大小写）或正则 | `includeFilter://m:get`<br>`excludeFilter://m:/regexp/i` |\n| `i:pattern` | 客户端或服务端 IP：IP 或正则 | `includeFilter://i:192.168.1.10`<br>`excludeFilter://i:/regexp/i` |\n| `chance:probability` | 按概率随机命中：`Math.random() < probability` | `includeFilter://chance:0.5`<br>`excludeFilter://chance:0.3` |\n| `clientIp:pattern` | 客户端 IP：IP 或正则 | `includeFilter://clientIp:192.168.1.10`<br>`excludeFilter://clientIp:/regexp/i` |\n| `serverIp:pattern` | 服务端 IP：IP 或正则 | `includeFilter://serverIp:192.168.1.10`<br>`excludeFilter://serverIp:/regexp/i` |\n| `s:pattern` | 响应状态码：状态码或正则 | `includeFilter://s:/^20/`<br>`excludeFilter://s:200` |\n| `reqH.header-key:pattern` | 请求头：关键字（不区分大小写）或正则 | `includeFilter://reqH.content-type:json`<br>`excludeFilter://reqH.content-type:/regexp/i` |\n| `resH.header-key:pattern` | 响应头：关键字（不区分大小写）或正则 | `includeFilter://resH.content-type:json`<br>`excludeFilter://resH.content-type:/regexp/i` |\n| 无前缀 `pattern`（其它 `xxxxxx`） | 请求 URL，同 [pattern](./pattern) | `includeFilter://*/cgi-*`<br>`excludeFilter://www.test.com`<br>`includeFilter://https://www.test.com/path` |\n","docs/docs/rules/forwardedFor.md":"# forwardedFor\n修改请求头 `x-forwarded-for` 字段，自定义客户端 IP。\n\n## 规则语法\n``` txt\npattern forwardedFor://ip [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| ip   | 自定义客户端 IP<br/>⚠️ 不支持从文件/远程 URL 加载数据 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 设置固定 IP\nwww.example.com/path  forwardedFor://1.1.1.1\n\n# 使用真实客户端 IP（透传模式）\nwww.example.com  forwardedFor://`${clientIp}`\n```\n\n## 关联协议\n1. 直接修改请求头：[reqHeaders://x-forwarded-for=value](./reqHeaders)（这种方式的 `value` 可以不局限于 IP）\n2. 启用自动设置 `x-forwarded-for`：[enable://clientIp](./enable)\n","docs/docs/rules/frameScript.md":"# frameScript\n通过 JavaScript 脚本操作 WebSocket 和普通 TCP 请求数据帧。\n\n## 规则语法\n``` txt\npattern frameScript://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 生成规则的 JS 脚本，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n```` txt\n``` test-frame.js\n// 发送预设数据\nctx.sendToServer('1 = 0x12300000');\nctx.sendToClient('1 = 0x1236666666');\n\n// 处理发往服务端的数据帧\nctx.handleSendToServerFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '***');\n};\n\n// 处理发往客户端的数据帧  \nctx.handleSendToClientFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '+++');\n};\n```\n\nwss://echo.websocket.org/ frameScript://{test-frame.js}\n````\n访问 `https://echo.websocket.org/.ws` 效果：\n\n <img width=\"1200\" alt=\"frame-script\" src=\"/img/frame-script.png\" />\n\n#### 可用全局变量\n\n| 变量/方法          | 描述                                                                 |\n|--------------------|---------------------------------------------------------------------|\n| `url`             | 完整请求URL                                                         |\n| `method`          | 请求方法(GET/POST等)                                                |\n| `ip`/`clientIp`   | 客户端IP地址                                                       |\n| `headers`         | 请求头对象                                                          |\n| `rules`           | 规则数组，通过push添加新规则                                        |\n| `values`          | 临时值存储对象                                                      |\n| `render(tpl,data)`| 微型模板渲染函数                                                    |\n| `getValue(key)`   | 获取Values中的值                                                    |\n| `parseUrl`        | 同Node.js的`url.parse`                                              |\n| `parseQuery`      | 同Node.js的`querystring.parse`                                      |\n| `ctx.sendToClient(frame, options)` | 发送数据（对象、字符串、Buffer）到客户端，用法见下面示例 |\n| `ctx.sendToServer(frame, options)` | 发送数据（对象、字符串、Buffer）到服务端，用法见下面示例 |\n| `ctx.destroy()` | 断开当前连接（`v2.10.11` 新增） |\n| `handleSendToClientFrame(buffer, options)` | 处理（过滤或删除）发送到客户端的数据，用法见下面示例 |\n| `handleSendToServerFrame(buffer, options)` | 处理（过滤或删除）发送到服务端的数据，用法见下面示例 |\n\n> 注意：`handleSendToClientFrame` 内不可调用 `ctx.sendToClient`；`handleSendToServerFrame` 内不可调用 `ctx.sendToServer`\n","docs/docs/rules/headerReplace.md":"# headerReplace\n通过关键字匹配或正则表达式替换指定请求头/响应头。\n\n## 规则语法\n``` txt\npattern headerReplace://value [filters...]\n```\n> `header-name` 不区分大小写\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 三种情况：<br/>• `req.header-name:p1=v1&p2=v2`<br/>• `res.header-name:p1=v1&p2=v2`<br/>• `trailer.trailer-name:p1=v1&p2=v2` | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 请求头 `accept` 字段值的第一个 `html` 关键字被改成 `abc`\nwww.example.com/path headerReplace://req.accept:html=abc\n\n# 请求头 `accept` 字段值的所有 `ml` 关键字被改成 `abc`\nwww.example.com/path2 headerReplace://req.accept:/ml/g=abc\n\n# 修改响应头\nwww.example.com/path3 headerReplace://res.Content-Type:html=plain\n```\n\n`headerReplace` 是用来替换请求/响应头的局部内容，如果要修改请求/响应字段内容，还可以用：\n- 设置请求头：[reqHeaders](./reqHeaders)\n- 设置响应头：[resHeaders](./resHeaders)\n","docs/docs/rules/host.md":"# host\n修改请求的 DNS 解析结果，将指定请求解析到特定 IP 地址（域名）及端口，可以看作终极版的系统 hosts 配置功能。\n\n## 规则语法\n``` txt\npattern host://ipOrDomain[:port] [filters...]\n```\n> `port` 如果不填，则沿用请求 URL 的原始端口，如果是指向域名则相当于 `cname` 功能\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 如果只是 IP 或端口可以省略 `host://`\nwww.example.com/test0 127.0.0.1      # 不改端口，沿用请求 URL 的原始端口\nwww.example.com/test1 127.0.0.1:5173\n\n# CNAME 功能\nwww.example.com/test2 host://www.test.com\nwww.example.com/test3 host://www.test.com:8080\n\n# 高级配置，从请求参数获取目标地址，includeFilter 确保存在该参数\nwww.example.com/test4 host://`${query.target}$:8080` includeFilter:///[?&]target=[\\w-]+/i\n```\n## 与 proxy 的匹配优先级\n以下是优化后的文档，结构更清晰，语言更简洁准确：\n\n---\n\n## 与 host 的匹配优先级\n\n#### 默认行为\n\n当请求同时匹配 `host` 和 `proxy` 规则时：\n- 仅 `host` 规则生效\n- `proxy` 规则自动忽略\n\n#### 修改优先级\n| 配置方式 | 语法 | 效果 |\n|---------|------|------|\n| **优先 proxy** | [`enable://proxyFirst`](./enable) 或 [`lineProps://proxyFirst`](./lineProps) | 仅 `proxy` 生效（覆盖 host） |\n| **同时生效** | [`enable://proxyHost`](./enable) 或 [`lineProps://proxyHost`](./lineProps) | `proxy` 和 `host` 同时生效 |\n\n#### 使用建议\n- 大多数场景使用默认行为即可\n- 需要特殊代理逻辑时才使用 `proxyFirst`\n- 需要双重匹配时使用 `proxyHost`\n\n## 注意事项\n`host` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com 127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求指向 `127.0.0.1:1234`，但由于 `host` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `host` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com 127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `host` 规则匹配，最终指向 `127.0.0.1:1234`。\n\n\n## 常见问题\n1. 与 URL 转换的区别：\n    ``` txt\n    # 服务端收到的 URL 还是 www.example.com\n    www.example.com 127.0.0.1:5173\n\n    # 服务端收到的 URL 是 http://127.0.0.1:5173\n    www.example.com http://127.0.0.1:5173\n    ```\n2. 自动降级为 HTTP 请求：如果配置的目标 IP 为 `127.0.0.1`，且 HTTPS 请求报错会自动降级为 HTTP 请求，方便访问本地服务，用户可以通过以下规则禁用该功能：\n    ``` txt\n    # 禁用本地 HTTPS 自动降级\n    pattern disable://auto2http\n    ```\n","docs/docs/rules/htmlAppend.md":"# htmlAppend\n在现有响应内容体后面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlAppend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlAppend://(Hello) file://(-test-)\nwww.example.com/path2 htmlAppend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlAppend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlAppend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlAppend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlAppend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlAppend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlAppend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `<div>Test</div>test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlAppend:///User/xxx/test.txt\nwww.example.com/path2 htmlAppend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlAppend://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/htmlBody.md":"# htmlBody\n\n将现有响应内容体t替换成指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlBody://(Hello) file://(-test-)\nwww.example.com/path2 htmlBody://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlBody://{body.txt} file://(-test-)\nwww.example.com/path2 htmlBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlBody://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlBody://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlBody://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlBody://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlBody:///User/xxx/test.txt\nwww.example.com/path2 htmlBody://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlBody://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n","docs/docs/rules/htmlPrepend.md":"# htmlPrepend\n在现有响应内容体前面面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlPrepend://(Hello) file://(-test-)\nwww.example.com/path2 htmlPrepend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello world.-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlPrepend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlPrepend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlPrepend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlPrepend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    test<div>Test</div>\n    ```\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlPrepend:///User/xxx/test.txt\nwww.example.com/path2 htmlPrepend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlPrepend://temp/blank.txt\n````\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n","docs/docs/rules/http.md":"# http\n将以下三种请求转换为 HTTP 请求（服务端将收到转换后的 HTTP URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern http://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 http://www.test.com/path/xxx\nhttps://www.example.com/path2 http://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `http://www.example.com/path1`              | `http://www.test.com/path/xxx`             |\n    | `http://www.example.com/path1/a/b/c?query`  | `http://www.test.com/path/xxx/a/b/c?query` |\n    | `https://www.example.com/path2`            | `http://www.abc.com/path3/yyy`             |\n    | `https://www.example.com/path2/a/b/c?query` | `http://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 http://<www.test.com/path/xxx>\n    # www.example.com/path1 http://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `http://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 http://www.test.com/path/xxx\nwss://www.example.com/path2 http://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `ws://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `ws://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 http://www.test.com:123\ntunnel://www.example2.com:443 http://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:80`|\n\n自动忽略匹配 URL 的路径，HTTP 协议默认端口为 `80`，HTTPS 协议默认端口为 `443`\n\n\n","docs/docs/rules/https-proxy.md":"# https-proxy\n`https-proxy` 指令用于将匹配的请求通过指定的 HTTPS 代理服务器转发。\n\n## 规则语法\n``` txt\npattern https-proxy://ipOrDomain[:port] [filters...]\n```\n> `port` 可选，不填则使用默认端口 `443`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 将请求代理到 HTTPS 代理: `127.0.0.1:443`\nwww.example.com/path https-proxy://127.0.0.1 # 默认端口 443\n\n# 将当前域名的所有请求代理到 HTTPS 代理: `127.0.0.1:8080`\nwww.example.com https-proxy://127.0.0.1:8080\n\n# 也可以用域名\nwww.example.com/path https-proxy://test.proxy.com # 默认端口 443\nwww.example.com https-proxy://test.proxy.com:8080\n```\n\n## 高级用法\n默认情况下，上游代理会自行解析请求的域名。但某些场景下，你可能希望强制代理直接访问指定的目标 IP（跳过 DNS 解析），例如：\n- 绕过 DNS 污染\n- 直接访问特定后端 IP\n- 测试不同环境的服务\n``` txt\n# 通过查询参数\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1:8080\n\n# 通过指令启用\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1 enable://proxyHost\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1:8080 enable://proxyHost\n```\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n## 注意事项\n`https-proxy` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com https-proxy://127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求代理到 `127.0.0.1:1234`，但由于 `https-proxy` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `https-proxy` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com https-proxy://127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `https-proxy` 规则匹配，最终代理到 `127.0.0.1:1234`。\n","docs/docs/rules/https.md":"# https\n将以下三种请求转换为 HTTPS 请求（服务端将收到转换后的 HTTPS URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern https://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 https://www.test.com/path/xxx\nhttps://www.example.com/path2 https://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n     | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n     | ----------------------------------------- | ----------------------------------------- |\n     | `http://www.example.com/path1`              | `https://www.test.com/path/xxx`             |\n     | `http://www.example.com/path1/a/b/c?query`  | `https://www.test.com/path/xxx/a/b/c?query` |\n     | `https://www.example.com/path2`            | `https://www.abc.com/path3/yyy`             |\n     | `https://www.example.com/path2/a/b/c?query` | `https://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 https://<www.test.com/path/xxx>\n    # www.example.com/path1 https://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `https://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 https://www.test.com/path/xxx\nwss://www.example.com/path2 https://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `wss://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `wss://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 https://www.test.com:123\ntunnel://www.example2.com:443 https://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:443`|\n\n⚠️ 自动忽略匹配 URL 的路径，HTTPS 协议默认端口为 `443`\n\n","docs/docs/rules/ignore.md":"# ignore\n用于忽略指定协议的匹配规则，也可以忽略当前配置的匹配规则。\n\n## 规则语法\n``` txt\npattern ignore://p1|p2|... [filters...]\n# 等效于：\npattern ignore://p1 ignore://p2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作协议（`protocol`）名称<br/>`*` 表示所以协议<br/>`-p` 表示排除 `p`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作协议列表](./protocols)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 忽略所有规则，只保留 `file` 协议的规则\nwww.example.com/path ignore://*|-file\n\n# 忽略指定协议：file、host\nwww.example.com/path2 ignore://file|host\n```\n\n## 常见问题\n`ignore` 是根据指定协议忽略已匹配的规则，用这种方式忽略的规则不会继续匹配同类型的其他规则，如果想让请求忽略某个规则后再继续尝试匹配剩余的同类型规则可以采用 [skip](./skip)。\n\n","docs/docs/rules/includeFilter.md":"# includeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation includeFilter://p1 includeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 及 `p1`、`p2`、... 中的一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求生效  \nwww.example.com/api/data proxy://127.0.0.1:8080 includeFilter://m:GET includeFilter://m:POST\n```\n\n可以跟 [excludeFilter](./excludeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n","docs/docs/rules/inherit.md":"# 协议继承\n[http](./http)/[https](./https)/[ws](./ws)/[wss](./wss)/[tunnel](./tunnel) 都限制了目标 URL 的协议，Whistle 也支持根据请求 URL 的协议自动补全。\n## 规则语法\n``` txt\npattern //value [filters...]\n# 或\npattern value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 www.test.com/path/xxx\nhttps://www.example.com/path2 www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `http://www.example.com/path1`              | `http://www.test.com/path/xxx`             |\n    | `http://www.example.com/path1/a/b/c?query`  | `http://www.test.com/path/xxx/a/b/c?query` |\n    | `https://www.example.com/path2`            | `https://www.abc.com/path3/yyy`             |\n    | `https://www.example.com/path2/a/b/c?query` | `https://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 //<www.test.com/path/xxx>\n    # www.example.com/path1 //(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `[http/https/wss/ws]://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 www.test.com/path/xxx\nwss://www.example.com/path2 www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `ws://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `wss://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 //www.test.com:123\ntunnel://www.example2.com:443 www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:443`|\n\n","docs/docs/rules/jsAppend.md":"# jsAppend\n在现有响应内容体后面插入指定内容（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsAppend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsAppend://(Hello) file://(-test-)\nwww.example.com/path2 jsAppend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsAppend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-<script>Hello</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-Hello`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsAppend://{body.txt} file://(-test-)\nwww.example.com/path2 jsAppend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsAppend://{body.txt}) file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-<script>Hello world.</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-Hello world.`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsAppend:///User/xxx/test.js\nwww.example.com/path2 jsAppend://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsAppend://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsAppend` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsAppend://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsAppend://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsAppend://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsAppend://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsAppend://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/jsBody.md":"# jsBody\n将现有响应内容体t替换成指定内容（（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsBody://(Hello) file://(-test-)\nwww.example.com/path2 jsBody://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsBody://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<script>Hello</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsBody://{body.txt} file://(-test-)\nwww.example.com/path2 jsBody://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<script>Hello world.</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello world.`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsBody:///User/xxx/test.js\nwww.example.com/path2 jsBody://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsBody://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsBody` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsBody://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsBody://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsBody://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsBody://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsBody://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n","docs/docs/rules/jsPrepend.md":"# jsPrepend\n在现有响应内容体前面插入指定内容（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsPrepend://(Hello) file://(-test-)\nwww.example.com/path2 jsPrepend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsPrepend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <script>Hello</script>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 jsPrepend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsPrepend://{body.txt}) file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <script>Hello world.</script>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello world.-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsPrepend:///User/xxx/test.js\nwww.example.com/path2 jsPrepend://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsPrepend://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsPrepend` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n\n","docs/docs/rules/lineProps.md":"# lineProps\n通过规则启用 proxyHost、proxyTunnel、safeHtml 等功能。\n> 📌 与 [enable](./enable) 的区别：\n>\n> `enable` 是全局生效的配置\n>\n> `lineProps` 只对配置所在行的规则生效\n\n## 规则语法\n``` txt\npattern operation lineProps://action1|action2|... [filters...]\n\n# 等效于：\npattern operation lineProps://action1 lineProps://action2 ... [filters...]\n```\n> `lineProps` 不能单独作为 `operation` 使用，且只对同一行的 `operation` 生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令                          | [操作指令文档](./operation)   |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n- `important`：类似于 CSS 的 !important，用于提升规则优先级，且仅对同类协议的操作生效\n- `safeHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `{` 和 `[`（JSON 对象开头字符），如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `strictHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `<`，如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `disableAutoCors`：禁用 [file](./file) 协议替换请求时自动添加必要的 CORS (跨域资源共享) 头信息\n- `disableUserLogin`：禁用设置 [statusCode://401](./statusCode) 时显示登录框\n- `enableUserLogin`：设置 [statusCode://401](./statusCode) 是否显示登录框（默认显示，用于关闭 `disable.userLogin`）\n- `internal`：将 `proxy`、`socks`、`host` 协议掉规则同时作用于 Whistle 内部请求\n- `internalOnly`：将 `proxy`、`socks`、`host` 协议掉规则只作用于 Whistle 内部请求\n- `internalProxy`：利用 `proxy`、`socks` 等代理协议将请求转发至其他代理服务器（如另一 Whistle 实例）。启用此功能后，已在第一层代理解密的 HTTPS 请求将以明文形式在代理链中传输，从而上游代理可以直接获取明文数据\n- `proxyFirst`：优先使用 [proxy](./proxy) 规则（默认情况下，同时匹配 `host` 和 `proxy`，只有 `host` 生效）\n- `proxyHost`：[proxy](./proxy) 和 [host](./host) 同时生效\n- `proxyHostOnly`：功能同 `proxyHost`，但如果没有匹配 [host](./host) 则 [proxy](./proxy) 自动失效\n- `proxyTunnel`：跟 `proxyHost` 一同使用，让上游代理再次通过隧道代理到上上游的 HTTP 代理，详见下面的示例\n- `weakRule`：默认情况下，当配置了 [file](./file) 等协议时，[proxy](./proxy) 规则会自动失效。通过设置 `weakRule` 属性，可以提升 [proxy](./proxy) 规则的优先级，使其在上述场景中仍然生效\n- `enableBigData`：支持 [reqMerge](./reqMerge) 或 [resMerge](./resMerge) 匹配大的请求/响应内容\n\n## 配置示例\n#### 未使用 `lineProps://important`\n``` txt\nwww.example.com/path file:///User/xxx/important1.html\nwww.example.com/path file:///User/xxx/important2.html\n```\n访问 `https://www.example.com/path ` 将匹配 `file:///User/xxx/important1.html`\n\n#### 使用 `lineProps://important`\n``` txt\nwww.example.com/path file:///User/xxx/important1.html\nwww.example.com/path file:///User/xxx/important2.html lineProps://important\n```\n访问 `https://www.example.com/path ` 将匹配 `file:///User/xxx/important2.html`\n\n#### 注入文本\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) \nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path ` 返回响应内容：\n``` html\n<!DOCTYPE html>\n<style>alert(1)</style>\nalert(1)\n<script>alert(1)</script>test\n```\n\n#### 使用 `enable://strictHtml`\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) enable://strictHtml\nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path` 返回响应内容：\n``` html\ntest\n```\n> `enable://strictHtml` 对所有规则都生效\n\n### 使用 `lineProps://strictHtml`\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) lineProps://strictHtml\nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path` 返回响应内容：\n``` html\n<!DOCTYPE html>\n<style>alert(1)</style>\nalert(1)test\n```\n> `lineProps://strictHtml` 只对所在行的规则生效\n","docs/docs/rules/locationHref.md":"# locationHref\n针对无法通过服务端重定向（`302`/`301`）的场景，通过在 HTML 页面中返回 JavaScript 代码 `window.location.href = targetUrl` 实现客户端跳转。特别适用于：\n- 本地HTML文件加载的APP页面\n- 单页应用(SPA)\n- 特殊框架开发的混合应用(Hybrid App)\n\n## 规则语法\n``` txt\npattern locationHref://targetUrl [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| targetUrl   | 重定向后的 URL，可以是相对路径 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 基础配置\n``` txt\nwww.example.com/path locationHref://https://www.qq.com\n\nwww.example.com/path2 locationHref://../abc/123\n```\n- 访问 `https://www.example.com/path/to` 重定向到 `https://www.qq.com`（不自动拼接路径）\n- 访问 `https://www.example.com/path2/to` 重定向到 `https://www.example.com/abc/123`\n\n#### 实现路径拼接\n``` txt\n# 通配符\n^www.example.com/path/*** locationHref://`https://www.example.com/$1`\n```\n- 访问 `https://www.example.com/path/to?query` 重定向到 `https://www.example.com/to?query`\n\n#### location.replace\n若需通过 `location.replace(targetUrl)` 实现无历史记录的页面跳转，可按以下格式配置：\n``` txt\nwww.example.com/path locationHref://replace:https://www.qq.com\n```\n> 跳转至目标 URL，且当前页面不会存入浏览器历史记录，用户无法通过“返回”按钮回到原页面\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","docs/docs/rules/log.md":"# log 规则\n\n自动在页面中注入 JavaScript 代码，捕获 JavaScript 异常及 `console.xxx` 日志，并在 Whistle 管理界面中实时显示。\n\n## 功能概述\n- **异常监控**：自动捕获页面中的 JavaScript 执行错误和未处理的 Promise 异常。\n- **日志收集**：拦截并收集所有 `console.log()`、`console.warn()`、`console.error()` 等控制台输出。\n- **可视化查看**：在 Whistle 管理界面中按分组实时查看、筛选和搜索日志。\n- **日志预处理**：支持通过自定义脚本对日志内容进行过滤或格式化处理。\n\n## 规则语法\n```\npattern log://id [filters...]\n```\n\n### 参数说明\n| 参数     | 是否必填 | 描述                                                                 | 详细说明                                                                 |\n|----------|----------|----------------------------------------------------------------------|--------------------------------------------------------------------------|\n| pattern  | 是       | 匹配请求 URL 的表达式，用于指定需要监控的页面                         | [匹配模式语法](./pattern)                                                |\n| id       | 是       | 日志分组标识（普通字符串），用于在 Whistle 界面中区分和筛选不同来源的日志 | 同一分组下的日志会集中显示，支持按 ID 快速切换视图                       |\n| filters  | 否       | 过滤器条件，用于进一步匹配请求或响应的特定特征                         | [过滤器语法](./filters) <br> 支持按 URL、方法、请求头、响应状态码等过滤 |\n\n## 使用方法\n\n### 基础用法\n捕获指定域名下所有页面的日志：\n```\nwww.example.com log://myapp\n```\n\n### 多域名分组监控\n为不同域名或路径设置不同的日志分组，便于分类查看：\n```\nke.qq.com log://ke\nnews.qq.com log://news\napi.example.com log://api\n```\n\n## 高级配置：日志预处理\n\n通过 `jsPrepend` 规则注入自定义脚本，可在日志发送到 Whistle 前进行预处理，例如敏感信息脱敏、格式转换或条件过滤。\n\n### 配置示例\n```\nwww.example.com log:// jsPrepend://{handleLog.js}\n```\n\n### 预处理脚本示例\n```javascript\n// handleLog.js\n// 在日志发送到 Whistle 前执行的自定义处理函数\nwindow.onBeforeWhistleLogSend = function(result, level) {\n  // result: 待输出的原始信息数组\n  // level: 日志级别 ('log', 'warn', 'error', 'info', 'debug')\n  \n  result.forEach(function(msg, i) {\n    // 示例1：隐藏敏感关键词\n    if (typeof msg === 'string' && msg.includes('password')) {\n      result[i] = '[SENSITIVE DATA HIDDEN]';\n    }\n    \n    // 示例2：特定内容转换为结构化对象\n    if (msg === 'abc') {\n      result[i] = {\n        name: 'avenwu',\n        level: level,\n        timestamp: new Date().toISOString()\n      };\n    }\n    \n    // 示例3：过滤掉某些类型的日志\n    if (msg === 'ignore-this-message') {\n      // 返回空数组将阻止此条日志发送\n      result.splice(i, 1);\n    }\n  });\n};\n```\n\n## 界面操作指南\n\n### 查看日志\n1. 打开 Whistle 管理界面（默认为 `http://127.0.0.1:8899`）\n2. 切换到 **Network / Tools / Console** 标签页\n3. 在左上角选择对应的日志分组 ID\n4. 实时查看页面输出的日志和错误信息\n\n### 日志筛选\n- 使用顶部的筛选栏可按日志级别（Log/Warn/Error）快速过滤\n- 支持关键词搜索，快速定位特定日志\n","docs/docs/rules/method.md":"# method\n修改请求方法。\n\n## 规则语法\n``` txt\npattern method://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | `get`/`post`/`head` 等请求方法名称（不区分大小写） |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 访问 `https://www.example.com/path/to` 在 Whistle 抓包界面上可以看到请求方法为 `POST`\nwww.example.com/path method://post\n\n# 带过滤条件的方法修改\nwww.example.com/path2 method://patch includeFilter://reqH.content-type=multipart/form-data\n```\n","docs/docs/rules/operation.md":"# operation\n\n在 Whistle 中，每条规则由 匹配模式（`pattern`） 和 操作（`operation`） 两部分组成，其中 `operation` 的通用语法为：\n\n``` txt\nprotocol://[value]\n```\n- **protocol**：指定操作类型（如 `file`、`proxy`、`resReplace` 等）\n- **value**：操作内容（支持多种格式，见下文）\n\n## 内联值\n``` txt\npattern reqHeaders://x-proxy=Whistle   # 设置请求头\npattern statusCode://404               # 修改状态码\npattern file://({\"ec\":0})              # 用内联内容（小括号里面的值：`{\"ec\":0}`）响应请求\n```\n\n当操作内容（Value）包含空格、换行符或特殊字符时，无法直接使用内联方式（Inline），需改用以下方法：\n\n## 内嵌值\n\n```` txt\n``` ua.txt\nMozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1\n```\npattern ua://{ua.txt}\n````\n\n等价于\n\n```` txt\n``` headers.json\nuser-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1\n```\npattern reqHeaders://{headers.json}\n````\n\n## Values 引用\n\n当操作值（Value）需要被多个规则共享时，直接内嵌在规则中会导致无法复用。此时可以将这些值存储在 Whistle 界面的 Values 模块中，并通过键名引用：\n1. 在 Values 里面创建一个名为 `result.json` 的 key 后，填入操作内容\n2. 在规则里面即可通过 `{result.json}` 的方式引用，如：`www.test.com/cgi-bin/test file://{result.json}`\n\n<img src=\"/img//values-demo1.png\" width=\"420\" />\n\n## 文件/远程资源\n``` txt\npattern reqHeaders:///User/xxx/filepath             # 从本地文件加载操作内容\npattern resHeadrs://https://example.com/config.json # 从远处加载 JSON 对象\npattern resHeaders://temp/blank.json                # 通过边境临时文件\n```\n> ⚠️ 注意：http/https/ws/wss/tunnel/host/enable/cache... 等协议禁止通过文件路径或远程 URL 获取内容，详见各协议文档。\n\n## 临时文件\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern protocol://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `protocol://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n## 小括号用途\n在 Whistle 规则中，protocol://value 的 value 部分可能有三种间接引用类型：\n1. `{key}` - 引用内嵌值\n2. `remote-url` - 远程资源地址\n3. `localfilepath` - 本地文件路径\n\n当需要直接引用上述内容本身（而非它们间接引用内容）作为操作内容时，可以用小括号包裹：\n``` txt\nprotocol://(value)\n```\n\n示例：\n1. `reqHeaders:///User/xxx/yyy.txt` - 从本地文件 `/User/xxx/yyy.txt` 加载操作内容\n2. `reqHeaders://(/User/xxx/yyy.txt)` - 将 `/User/xxx/yyy.txt` 直接作为操作内容\n\n## 模板字符串\nWhistle 提供了类似 ES6 的模板字符串功能，允许您动态引用请求信息并应用到规则配置中。支持以下几种模板字符串：\n\n##### 一般内敛值\n``` txt\npattern protocol://`...${version}...`\n```\n\n##### 内嵌值或 Values 引用\n```` txt\n\n``` test.key\n...${reqId}...\n...${version}...\n```\npattern protocol://`{test.key}`\n````\n\n##### 小括号内容\n```` txt\npattern protocol://`(...${now}...)`\n````\n\n##### 字符串变量\n\n| 变量名称              | 取值                                                         |\n| --------------------- | ------------------------------------------------------------ |\n| `${now}`                 | Date.now()                                                   |\n| `${random}`              | Math.random()                                                |\n| `${randomUUID}`          | crypto.randomUUID()                      |\n| `${randomInt(n)}` 或 `${randomInt(n1-n2)}` | 从 [0, n] 或 [n1, n2] 取一个随机正整数 (Added in: v2.9.104)|\n| `${reqId}`               | Whistle 给每个请求分配的 ID                                  |\n| `${url.protocol}`        | url.parse(fullUrl).protocol                                  |\n| `${url.hostname}`        | url.parse(fullUrl).hostname                                  |\n| `${url.host}`            | url.parse(fullUrl).host                                      |\n| `${url.port}`            | url.parse(fullUrl).port                                      |\n| `${url.path}`            | url.parse(fullUrl).path                                      |\n| `${url.pathname}`        | url.parse(fullUrl).pathname                                  |\n| `${url.search}`          | url.parse(fullUrl).search                                    |\n| `${query.xxx}`           | 请求参数 `xxx` 的值                                          |\n| `${url}`                 | 请求完整 URL                                                 |\n| `${querystring}`         | url.parse(fullUrl).search \\|\\| '?'（不为空）                 |\n| `${searchstring}`        | url.parse(fullUrl).search \\|\\| '?'（不为空）                 |\n| `${method}`              | 请求方法                                                     |\n| `${reqHeaders.xxx}`      | 请求头字段 `xxx` 的值                                        |\n| `${resHeaders.xxx}`      | 响应头字段 `xxx` 的值                                        |\n| `${version}`             | Whistle 版本号                                               |\n| `${port}`                | Whitle 端口号                                                |\n| `${host}`                | Whistle 启动时监听的网卡 IP（默认为空）                      |\n| `${realPort}`            | Whistle 界面 Online 对话框显示的 port（一般为 Whistle 端口号） |\n| `${realHost}`            | Whistle 界面 Online 对话框显示的 host（一般为 Whistle 监听的网卡 IP） |\n| `${clientIp}`            | 客户端 IP                                                    |\n| `${clientPort}`          | 客户端端口                                                   |\n| `${serverIp}`            | 服务端 IP                                                    |\n| `${serverPort}`          | 服务端端口                                                   |\n| `${reqCookies.xxx}`      | 请求 cookie `xxx` 的值                                       |\n| `${resCookies.xxx}`      | 响应 cookie  `xxx` 的值                                      |\n| `${statusCode}`          | 响应状态码                                                   |\n| `${env.xxx}`             | process.env.xxx                                              |\n| `${whistle.plugin-name}` | `whistle.plugin-name://value`  或 `plugin-name://value` 的 `value` |\n\n> `${whistle.plugin-name}` 只在插件的内部规则才可能有值\n\n##### 示例\n\n````txt\n``` test.txt\nnow: ${now}\nrandom: ${random}\nrandomUUID: ${randomUUID}\nreqId: ${reqId}\nurl.protocol: ${url.protocol}\nurl.hostname: ${url.hostname}\nurl.host: ${url.host}\nurl.port: ${url.port}\nurl.path: ${url.path}\nurl.pathname: ${url.pathname}\nurl.search; ${url.search}\nquery: ${query.name}\nurl: ${url}\nquerystring: ${querystring}\nsearchstring: ${searchstring}\nmethod: ${method}\nreqHeaders.accept: ${reqHeaders.accept}\nresHeaders.content-type: ${resHeaders.content-type}\nversion: ${version}\nport: ${port}\nhost: ${host}\nrealPort: ${realPort}\nrealHost: ${realHost}\nclientIp: ${clientIp}\nclientPort: ${clientPort}\nserverIp: ${serverIp}\nserverPort: ${serverPort}\nreqCookies.test: ${reqCookies.test}\nresCookies.test: ${resCookies.test}\nstatusCode: ${statusCode}\nenv.USER: ${env.USER}\n```\n\nwww.test.com/index.html file://`{test.txt}`\n````\n\n访问 `https://www.test.com/index.html?name=avenwu` 则返回响应内容：\n\n``` txt\nnow: 1752301623295\nrandom: 0.6819241513880432\nrandomUUID: e917b9fc-e2ef-4255-9209-11eb417235c5\nreqId: 1752301623294-339\nurl.protocol: https:\nurl.hostname: www.test.com\nurl.host: www.test.com\nurl.port: \nurl.path: /index.html?name=avenwu\nurl.pathname: /index.html\nurl.search; ?name=avenwu\nquery: avenwu\nurl: https://www.test.com/index.html?name=avenwu\nquerystring: ?name=avenwu\nsearchstring: ?name=avenwu\nmethod: GET\nreqHeaders.accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\nresHeaders.content-type: \nversion: 2.9.100\nport: 8899\nhost: \nrealPort: 8899\nrealHost: \nclientIp: 127.0.0.1\nclientPort: 60582\nserverIp: \nserverPort: \nreqCookies.test: \nresCookies.test: \nstatusCode: \nenv.USER: av\n```\n\n## 数据对象\n操作内容除了文本或二进制内容，还有可能是 JSON 对象，Whistle 支持以下 3 种数据对象格式：\n\n#### JSON 格式\n``` js\n{\n  \"key1\": value1,\n  \"key2\": value2,\n  \"keyN\": valueN\n}\n```\n\n#### 行格式\n``` txt\nkey1: value1\nkey2:value2\nkeyN: valueN\n```\n> 以 `冒号+空格` 分隔，如果没有 `冒号+空格` ，则以第一个冒号分隔，如果没有冒号，则 `value` 为空字符串\n\n**多级嵌套：**\n``` txt\na.b.c: 123\nc\\.d\\.e: abc\n```\n等价于：\n``` json\n{\n  \"a\": {\n    \"b\": {\n      \"c\": 123\n    }\n  },\n  \"c.d.e\": \"abc\"\n}\n```\n\n#### 内联格式（请求参数格式）\n\n``` txt\nkey1=value1&key2=value2&keyN=valueN\n```\n> `key` 和 `value` 最好都 `encodeURIComponent`\n\n\n## 常见操作指令速查手册 {operation-manual}\n- [修改请求内容](#request)\n  - [修改请求方法](#method)\n  - [修改请求 URL](#url)\n  - [修改 HTTP 版本](#http-version)\n  - [修改请求头](#req-headers)\n  - [修改请求体](#req-body)\n- [修改响应内容](#response)\n  - [修改状态码](#status-code)\n  - [修改响应头](#res-headers)\n  - [修改响应体](#res-body)\n  - [修改尾部响应头](#trailers)\n- [修改连接过程](#connect)\n  - [修改 DNS](#dns)\n  - [设置代理](#proxy)\n  - [代理和 DNS 同时生效](#proxy-host)\n- [页面调试工具](#tools)\n  - [查看页面 DOM 结构](#weinre)\n  - [查看页面日志及错误信息](#log)\n\n---\n\n## 一、修改请求内容 {#request}\n\n### 1.1 修改请求方法 {#method}\n```txt\n# 基础语法\npattern method://新方法\n\n# 示例\nwww.example.com/path method://post\n```\n\n**数据来源支持：**\n- 直接指定：`method://get`\n- 内嵌值：`method://{keyOfEmbedded}`\n- Values配置：`method://{keyOfValues}`\n\n**注意事项：**\n- 方法名不区分大小写\n- 不支持从本地文件或远程URL获取\n\n**实用示例：**\n```txt\n# 示例1：将所有请求方法改为POST\nwww.example.com/path method://post\n\n# 示例2：仅将PUT请求改为POST\nwww.example.com/path method://post includeFilter://m:put\n\n# 示例3：根据请求体内容修改方法\nwww.example.com/api method://put includeFilter://b:cmdname=test\n```\n\n### 1.2 修改请求 URL {#url}\n#### URL映射\n```txt\nwww.example.com/path/to www.test.com/test\n```\n**映射效果：**\n- `https://www.example.com/path/to?query=abc` → `https://www.test.com/test?query=abc`\n- `https://www.example.com/path/to/subpage` → `https://www.test.com/test/subpage`\n- `wss://www.example.com/path/to/api` → `wss://www.test.com/test/api`\n\n#### 修改请求参数\n```txt\n# 新增/替换参数\npattern urlParams://({\"key\":\"value\"})\n\n# 删除参数\npattern delete://urlParams.paramName\n\n# 示例：修改参数并删除指定参数\nwww.example.com/api urlParams://({\"cmdname\":\"Test\"}) delete://urlParams.oldParam\n```\n\n#### 修改Path路径\n```txt\n# 正则替换\npattern pathReplace://({\"/old/ig\":\"new\"})\n\n# 关键字替换\npattern pathReplace://({\"old\":\"new\"})\n```\n\n**数据来源支持：**\n- 内联JSON\n- 内嵌值\n- Values配置\n- 本地文件\n- 远程URL\n\n### 1.3 修改 HTTP 版本 {#http-version}\n```txt\n# 强制使用普通HTTPS（禁用HTTP/2）\npattern disable://h2\n```\n> 默认会尝试使用HTTP/2建立连接，不支持时自动降级为HTTPS\n\n### 1.4 修改请求头 {#req-headers}\n```txt\n# 新增/替换请求头\npattern reqHeaders://({\"Header-Name\":\"value\"})\n\n# 删除请求头\npattern delete://reqHeaders.headerName\n\n# 示例\nwww.example.com reqHeaders://({\"X-Custom-Header\":\"test\"})\n```\n\n### 1.5 修改请求体 {#req-body}\n#### 合并修改（JSON/表单）\n```txt\npattern reqMerge://({\"newField\":\"value\"})\n```\n\n#### 文本替换\n```txt\n# 正则替换\npattern reqReplace://({\"/search/ig\":\"replace\"})\n\n# 关键字替换\npattern reqReplace://({\"search\":\"replace\"})\n```\n\n#### 完全替换\n```txt\npattern reqBody://(新内容)\n```\n\n#### 删除操作\n```txt\n# 删除特定字段\npattern delete://reqBody.fieldName\n\n# 删除整个请求体\npattern delete://reqBody\n```\n\n**数据来源支持：**\n- 内联值\n- 内嵌值\n- Values配置\n- 本地文件\n- 远程URL\n\n---\n\n## 二、修改响应内容 {#response}\n\n### 2.1 修改状态码 {#status-code}\n```txt\n# 替换现有响应状态码（请求会到达服务器）\npattern replaceStatus://500\n\n# 直接响应状态码（请求不发送到服务器）\npattern statusCode://500\n```\n\n### 2.2 修改响应头 {#res-headers}\n```txt\n# 新增/替换响应头\npattern resHeaders://({\"Header-Name\":\"value\"})\n\n# 删除响应头\npattern delete://resHeaders.headerName\n```\n\n### 2.3 修改响应体 {#res-body}\n#### 合并修改（JSON/JSONP）\n```txt\npattern resMerge://({\"newData\":\"value\"})\n```\n\n#### 文本替换\n```txt\npattern resReplace://({\"/old/ig\":\"new\"})\n```\n\n#### 替换响应体\n```txt\n# 替换服务器返回的内容\npattern resBody://(新内容)\n```\n\n#### 直接响应内容\n```txt\n# 请求不发送到服务器\npattern file://(直接返回的内容) resType://html\n```\n\n#### 删除操作\n```txt\n# 删除响应体字段\npattern delete://resBody.fieldName\n\n# 清空响应体\npattern delete://resBody\n```\n\n### 2.4 修改尾部响应头 {#trailers}\n> Trailers是在分块传输响应后发送的额外头部字段\n\n```txt\n# 新增/替换Trailers\npattern trailers://({\"Trailer-Name\":\"value\"})\n\n# 删除Trailers\npattern delete://trailers.trailerName\n```\n\n---\n\n## 三、修改连接过程 {#connect}\n\n### 3.1 修改 DNS {#dns}\n```txt\n# 设置IP地址\npattern 127.0.0.1\n\n# 设置IP和端口\npattern 127.0.0.1:8080\n\n# CNAME效果（指向其他主机）\npattern host://www.target.com:8080\n```\n\n### 3.2 设置代理 {#proxy}\n```txt\n# HTTP代理\npattern proxy://127.0.0.1:8080\n\n# SOCKS5代理\npattern socks://127.0.0.1:1080\n\n# 支持域名\npattern proxy://proxy.example.com:8080\n```\n\n### 3.3 代理和 DNS 同时生效 {#proxy-host}\n**优先级说明：**\n- 默认：host配置优先于proxy\n- 可调整：通过`lineProps://proxyHost`让两者同时生效\n\n```txt\n# 示例1：只生效host配置\npattern 127.0.0.1:8080 socks://10.1.1.1:1080\n\n# 示例2：只生效proxy配置\npattern 127.0.0.1:8080 socks://10.1.1.1:1080 ignore://host\n\n# 示例3：host和proxy同时生效\npattern 127.0.0.1:8080 socks://10.1.1.1:1080 lineProps://proxyHost\n```\n\n---\n\n## 四、页面调试工具 {#tools}\n\n### 4.1 查看页面 DOM 结构 {#weinre}\n```txt\npattern weinre://调试会话名称\n```\n> 详细用法参考：[weinre文档](./weinre)\n\n### 4.2 查看页面日志及错误信息 {#log}\n```txt\npattern log://日志会话名称\n```\n> 详细用法参考：[log文档](./log)\n\n---\n\n## 数据来源速查表\n\n| 操作类型 | 直接内联 | 内嵌值 | Values | 本地文件 | 远程URL |\n|---------|---------|--------|--------|----------|---------|\n| 请求方法 | ✓ | ✓ | ✓ | ✗ | ✗ |\n| URL参数 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 请求头 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 请求体 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 响应头 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 响应体 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| Trailers | ✓ | ✓ | ✓ | ✓ | ✓ |\n\n**语法示例：**\n```txt\n# 直接内联\nprotocol://({\"key\":\"value\"})\n\n# 内嵌值\nprotocol://{embeddedKey}\n\n# Values配置\nprotocol://{valuesKey}\n\n# 本地文件\nprotocol:///path/to/file.json\n\n# 远程URL\nprotocol://https://example.com/data.json\n```\n\n---\n\n## 常用过滤条件\n\n| 过滤器 | 说明 | 示例 |\n|--------|------|------|\n| `includeFilter://m:方法` | 按请求方法过滤 | `includeFilter://m:put` |\n| `includeFilter://b:内容` | 按请求体内容过滤 | `includeFilter://b:cmdname=test` |\n| 正则表达式 | 区分大小写匹配 | `/Test/` |\n\n> **提示：** 更多协议和高级用法请参考[完整协议列表](./protocols)\n","docs/docs/rules/pac.md":"# pac\n\nPAC（Proxy Auto-Config）是一种通过 JavaScript 脚本自动决定请求代理规则的机制，允许您基于 URL、域名、IP 等条件动态选择代理或直连。\n\n## 规则语法\n``` txt\npattern pac://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n```` txt\n# 内嵌 PAC 脚本\n``` test.pac\nfunction FindProxyForURL(url, host) {\n  // ...\n}\n```\nwww.example.com/path pac://{test.pac}\n\n# Values\nwww.example.com/path1 pac://{test2.pac}\n\n# 本地文件\nwww.example.com/path3 pac:///User/xxx/test.pac\n\n# 远程 PAC 脚本\n* pac://https://raw.githubusercontent.com/imweb/node-pac/master/test/scripts/normal.pac\n````\n\n## 高级用法\n将请求代理到上游代理后，默认情况下上游代理会根据请求的域名通过 DNS 获取服务器 IP 再继续请求，如果想让上游代理根据指定 IP及端口继续请求，可以这么处理：\n``` txt\nwww.example.com pac://https://xxx/path/normal.pac 1.1.1.1 enable://proxyHost\nwww.example.com pac:///User/xxx/test.pac 1.1.1.1:8080 enable://proxyHost\n```\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n\n\n## 注意事项\n`pac` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com pac://https://xxx/path/normal.pac\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望 PAC 脚本 `https://xxx/path/normal.pac` 作用于 `https://www.example.com/path`，但由于 `pac` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `pac` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com pac://https://xxx/path/normal.pac\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `pac` 规则匹配。\n","docs/docs/rules/pathReplace.md":"# pathReplace\n提供类似 JavaScript 的 String.replace() 方法的能力，通过正则表达式或字符串匹配来动态修改 URL 的路径(`path`) 部分。\n\n> URL 结构：\n> ``` txt\n>   https://www.example.com:8080/path/to/resource?query=string\n>   \\___/   \\_____________/\\____/\\____________________________/\n>     |           |         |                 |             \n>   协议(scheme)  主机(host) 端口(path)       路径(path) \n> ```\n>\n> **路径部分**指 `path/to/resource?query=string` 即不包含开头的  `/`\n\n## 规则语法\n``` txt\npattern pathReplace://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作数据对象，支持从以下渠道获取：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nwww.example.com/path pathReplace://123=abc\n```\n访问 `https://www.example.com/path/123?test=123&value=123` 服务器收到的 URL：`https://www.example.com/path/abc?test=abc&value=abc`\n\n#### 替换多个\n\n```` txt\n``` test.json\ntest: name\n123: abc\n```\nwww.example.com/path2 pathReplace://{test.json}\n````\n访问 `https://www.example.com/path2/123?test=123&value=123` 服务器收到的 URL：`https://www.example.com/path2/abc?name=abc&value=abc`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 pathReplace:///User/xxx/test.json\nwww.example.com/path2 pathReplace://https://www.xxx.com/xxx/params.json\n# 通过编辑临时文件\nwww.example.com/path3 pathReplace://temp/blank.json\n````\n\n## 注意事项\n以下配置旨在删除指定路径片段：\n```txt\nwww.example.com/api/ pathReplace://(/api/=/)\n```\n\n**期望效果：**  \n将 `https://www.example.com/api/xxx` 中的 `/api/` 替换为 `/`，得到 `https://www.example.com/xxx`\n\n**实际问题：**  \nWhistle 会将 `/api/` 识别为正则表达式而非普通字符串，导致替换后产生多余斜杠：  \n`https://www.example.com///xxx`\n\n> 即使将 `/api/` 视为字符串，它也无法匹配 `api/xxx/...`，pathReplace 匹配的路径不包含开头的  `/`\n\n**解决方案：**  \n\n``` txt\nwww.example.com pathReplace://(/^api//=)\n```\n> Whistle 规则里面的正则不需要对 `/` 进行转义\n>\n> 新版 Whistle 也可以用 `delete://pathname.0` 删除上面 URL 的 `api/` 路径片段，详见 [delete://pathname.xxx](./delete)\n\n## 关联协议 {#related}\n1. 设置请求参数（`Set Param`）：[urlParams://(key=value)](./urlParams)\n2. 删除请求参数（`Delete Param`）：[delete://urlParams.xxx](./delete)\n3. 更灵活的修改请求参数的方式（`Modify Path (path/to?query)`）：[pathReplace](./pathReplace)\n4. 删除路径：[delete://pathname.xxx](./delete)\n\n","docs/docs/rules/pattern.md":"# pattern\n\n`pattern` 是 Whistle 规则中匹配请求 URL 的表达式，支持 URL 片段/域名、通配符、正则表达式等多种匹配方式。\n\n## 请求 URL 类型\n\nWhistle 支持三种请求 URL 类型：\n\n| 类型 | 格式 | 示例 |\n| :--- | :--- | :--- |\n| **隧道代理** | `tunnel://domain:port` | `tunnel://www.test.com:443` |\n| **WebSocket** | `ws[s]://domain[:port]/[path/to[?query]]` | `wss://www.test.com/path?a=1&b=2`<br>`ws://www.example.com:8080/path` |\n| **普通 HTTP/HTTPS** | `http[s]://domain[:port]/[path/to][?query]` | `https://www.test.com/path`<br>`http://www.example.com/path?a=1&b=2` |\n\n> URL 中的 hash（即 `#` 及之后的内容）不会被发送到后台。Hash 仅在客户端（如浏览器）中生效，后台无法直接获取\n\n## URL 片段 {#url}\n\nURL 片段用于匹配请求的 URL，支持多种灵活的模式。以下按匹配规则分类说明。\n\n### 1. 域名匹配\n\n- **普通域名**：`example.com`  \n  匹配该域名下所有端口及协议（http/https）的请求。  \n  ✅ `https://example.com/path/to?query`  \n  ✅ `https://example.com:9090/path/to?query`\n\n- **带端口的域名**：`example.com:8080`  \n  仅匹配指定端口（8080）的请求。  \n  ✅ `https://example.com:8080/path/to?query`  \n  ❌ `https://example.com:9090/path/to?query`\n  ❌ `https://example.com/path/to?query`\n\n### 2. 协议匹配\n\n- **带协议的 URL**：`https://example.com/path/to`  \n  仅匹配相同协议的请求。\n\n- **不带协议的 URL**：`example.com/path/to` 或 `//example.com/path/to`  \n  匹配任意协议（http、https 等），路径匹配规则与带协议时相同（见下文）。\n\n### 3. 路径与查询参数匹配\n\n#### 3.1 不带查询参数的 URL\n\n模式：`https://example.com/path/to`  \n匹配规则：**路径前缀匹配**（以 `/` 为边界）。  \n- ✅ `https://example.com/path/to`  \n- ✅ `https://example.com/path/to/xxx?query`  \n- ❌ `https://example.com/path/toxxx`（`to` 后缺少 `/` 边界）\n\n#### 3.2 带查询参数的 URL\n\n模式：`https://example.com/path/to?xxx`  \n匹配规则：**路径必须完全相同**，且查询字符串以 `xxx` 为前缀。  \n- ✅ `https://example.com/path/to?xxx`  \n- ✅ `https://example.com/path/to?xxxyyy&zzzzz`  \n- ❌ `https://example.com/path/to/yyy?xxx`（路径不同）\n\n### 4. 精确匹配\n\n使用 `$` 前缀，提供更严格的匹配。\n\n- `$https://example.com/path/to`  \n  匹配**路径精确**（以 `/` 为边界），查询参数可任意。  \n  ✅ `https://example.com/path/to`  \n  ✅ `https://example.com/path/to?query`  \n  ❌ `https://example.com/path/to/xxx`\n\n- `$https://example.com/path/to?query`  \n  匹配**路径与查询参数均完全一致**。  \n  ✅ `https://example.com/path/to?query`  \n  ❌ `https://example.com/path/to?query=1`  \n  ❌ `https://example.com/path/to`\n\n- 不带协议的精确匹配：`$example.com/path/to`（效果同上，协议任意）\n\n### 5. 域名通配符{#domain-wildcard}\n\n在`域名:端口`部分使用 `*` 或 `**` 进行模糊匹配。\n\n- `*`：匹配 0 个或多个非 `.` 字符（相当于正则 `/[^/?.]*/`）。  \n  示例：`https://*.example.com/path/to`  \n  ✅ `https://www.example.com/path/to`  \n  ✅ `https://abc.example.com/path/to/xxx?query`\n  ❌ `https://a.b.example.com/path/to`\n\n- `**`：匹配 0 个或多个任意字符（除 `/` 和 `?` 外，相当于正则 `/[^/?]*/`）。  \n  示例：`https://**.example.com:8*/path/to`  \n  ✅ `https://foo-bar.example.com:8080/path/to`\n  ✅ `https://a.b.example.com:8888/path/to`\n\n- `***` 及以上：不推荐使用。\n\n> 注意：`*` 也是 URL 路径中的合法字符。若需在路径部分使用通配符，请参阅 [通配符匹配](#wildcard)。\n\n### 要点总结\n\n| 模式示例 | 匹配协议 | 匹配端口 | 路径匹配规则 | 查询参数匹配规则 |\n|---------|---------|---------|-------------|----------------|\n| `example.com` | 任意 | 任意 | - | - |\n| `example.com:8080` | 任意 | 8080 | - | - |\n| `https://example.com/path/to` | https | 任意 | 前缀（`/`边界） | 任意 |\n| `https://example.com/path/to?xxx` | https | 任意 | 精确 | 前缀 |\n| `$https://example.com/path/to` | https | 任意 | 精确 | 任意 |\n| `$https://example.com/path/to?xxx` | https | 任意 | 精确 | 精确 |\n| `example.com/path/to` <br/> 或 `//example.com/path/to` | 任意 | 任意 | 前缀（`/`边界） | 任意 |\n\n## 通配符匹配 {#wildcard}\n\n在 [URL 片段匹配](#url) 中，由于 `*` 本身是 URL 路径中的合法字符，因此无法直接在路径或查询参数中使用通配符。  \n若需在 **路径** 或 **查询参数** 中使用通配符，请在 URL 前加上 `^` 符号。此时 `*`、`**`、`***` 将作为通配符解析。\n\n### 1. 域名通配符\n\n语法与 [URL 片段中的域名通配符](#domain-wildcard) 完全一致：\n\n- `*`：匹配 0 个或多个非 `.` 字符（正则 `/[^/?.]*/`）\n- `**`：匹配 0 个或多个任意字符（除 `/` 和 `?` 外，正则 `/[^/?]*/`）\n\n**示例**：`^wss://*.example.com/path/to`\n\n✅ 匹配：\n- `wss://a.example.com/path/to`\n- `wss://b.example.com/path/to/xxx?query`\n\n❌ 不匹配：\n- `wss://a.example.com/path/toxxx`（路径缺少 `/` 边界）\n- `wss://a.b.example.com/path/to`（`*` 不匹配点号）\n\n### 2. 路径通配符\n\n作用于 URL 的 **路径部分**（域名后、`?` 之前）。支持三种级别：\n\n| 通配符 | 匹配规则（正则） | 说明 |\n|--------|----------------|------|\n| `*`    | `/[^?/]*/`      | 匹配当前路径段内的任意字符（不含 `/` 和 `?`） |\n| `**`   | `/[^?]*/`       | 匹配任意字符（不含 `?`），可跨路径段 |\n| `***`  | `/.*/`          | 匹配剩余所有字符（含 `/` 和 `?`），通常用于路径末尾 |\n\n**示例 1：`*`**  \n模式：`^https://example.com/path/to/a*b`  \n✅ 匹配：`https://example.com/path/to/axxxb/...?query`  \n❌ 不匹配：`https://example.com/path/to/a/b`（`*` 不能匹配 `/`）\n\n**示例 2：`**`**  \n模式：`^https://example.com/path/to/a**b`  \n✅ 匹配：\n- `https://example.com/path/to/axxxb/...?query`\n- `https://example.com/path/to/a/b`  \n❌ 不匹配：`https://example.com/path/to/a/xxxx?query=b`（`?` 后不属于路径）\n\n**示例 3：`***`**  \n模式：`^https://www.example*.com:8*/path/to/a***b`  \n✅ 匹配：\n- `https://example.com/path/to/axxxb/...?query`\n- `https://example.com/path/to/a/b`\n- `https://example.com/path/to/a/xxxx?query=b`（`***` 会匹配到 `?` 及之后）\n\n> `***` 会“吞掉”包括 `?` 在内的剩余字符，一般仅建议在路径末尾使用。\n\n\n### 3. 查询参数通配符\n\n作用于 URL 的 **查询字符串**（`?` 之后）。支持两种：\n\n| 通配符 | 匹配规则（正则） | 说明 |\n|--------|----------------|------|\n| `*`    | `/[^&]*/`       | 匹配单个参数值内的任意字符（不含 `&`） |\n| `**`   | `/.*/`          | 匹配剩余所有字符（含 `&`），即从当前位置到末尾 |\n\n**示例 1：`*`**  \n模式：`^https://example.com/path/to?query=a*b`  \n✅ 匹配：`https://example.com/path/to?query=ab&q2=xxx`  \n❌ 不匹配：`https://example.com/path/to?query=a&q2=b`（`b` 前无额外字符）\n\n**示例 2：`**`**  \n模式：`^https://example.com/path/to?query=a**b`  \n✅ 匹配：\n- `https://example.com/path/to?query=axxxb&q2=xxx`\n- `https://example.com/path/to?query=a&q2=b`（`**` 匹配了 `&q2=`）\n\n### 4. 不带协议的精确匹配\n\n在 `^` 后直接写域名和路径，协议任意。\n\n**示例**：`^example*.com/path*/to`  \n效果与带协议相同，但可匹配 `http://`、`https://`、`wss://` 等任意协议。\n\n### 5. 精确匹配（边界控制）\n\n在模式末尾使用 `$`，表示匹配必须到此结束（之后不能再有额外字符）。\n\n**示例**：`^https://*.example.com/path/*/to$`  \n✅ 匹配：`https://a.example.com/path/xxx/to`  \n❌ 不匹配：`https://b.example.com/path/xxx/to?query`（`$` 禁止 `?query`）\n\n> 若不加 `$`，模式默认允许路径之后继续存在 `/`、`?` 等字符。\n\n### 使用总结\n\n| 使用场景 | 前缀 | 通配符类型 | 支持的通配符 | 示例 |\n|---------|------|-----------|-------------|------|\n| 普通 URL 匹配（无通配符） | 无 | — | — | `https://example.com/path` |\n| 含通配符的路径/参数 | `^` | 域名、路径、查询 | `*` `**` `***` | `^https://*.example.com/path/*?a**b` |\n| 精确匹配（尾部边界） | `^` + `$` | 同上 | 同上 | `^https://example.com/path/*/to$` |\n\n> **注意**：一旦使用 `^`，URL 中的 `*` 不再作为普通字符，而是通配符。如需匹配字面意义上的 `*`，请勿使用 `^`。\n\n## 正则匹配 {#regexp}\n\n除了前面介绍的 URL 片段匹配和通配符匹配外，Whistle 还提供了完整的正则表达式支持。  \n正则匹配的语法与 JavaScript 正则表达式完全兼容：\n\n```txt\n/pattern/[i]\n```\n\n### 参数说明\n\n- **`pattern`**：正则表达式主体\n- **`i`**（可选）：忽略大小写 \n\n### 使用示例\n\n```txt\n/\\.test\\./              # 匹配字符串中的 \".test.\"\n/key=value/i            # 忽略大小写匹配 \"key=value\" 或 \"KEY=VALUE\"\n```\n\n\n## 子匹配传值\n\n在 Whistle 规则中，可以通过 `$0`、`$1` … `$9` 引用通配符或正则表达式捕获的子匹配内容，并将其传递到目标操作值中。\n\n```txt\npattern protocol://$0_$1_$2_..._$9\n```\n\n**捕获变量说明**：\n\n| 变量 | 含义 |\n|------|------|\n| `$0` | 完整匹配的内容 |\n| `$1` ~ `$9` | 第 1 至第 9 个捕获组匹配的内容 |\n\n### 通配符匹配传值\n\n使用 `^` 前缀的通配符模式（`*`、`**` 等）会自动按顺序捕获匹配段，分别赋给 `$1`、`$2`……。\n\n**示例**：\n```txt\n^http://*.example.com/v0/users/** file:///User/xxx/$1/$2\n```\n\n- 请求 URL：`http://www.example.com/v2/users/alice/test.html?q=1`\n- 匹配过程：\n  - `$1` = `www`（匹配第一个 `*`）\n  - `$2` = `users/alice`（匹配 `**` 捕获的路径段）\n- 最终效果：返回本地文件 `/User/xxx/www/alice/test.html` 的内容\n\n> 通配符捕获规则：\n> - `*` 捕获单个路径段（不含 `/`）\n> - `**` 捕获剩余路径（可含 `/`）\n> - 按从左到右顺序依次对应 `$1`、`$2`……\n\n### 正则匹配传值\n\n使用 `/pattern/flags` 正则表达式时，捕获组 `( )` 中的内容会按顺序赋给 `$1`、`$2`……\n\n**示例**：\n```txt\n/regexp\\/(user|admin)\\/(\\d+)/ reqHeaders://X-Type=$1&X-ID=$2\n```\n\n- 请求 URL：`.../regexp/admin/123`\n- 捕获结果：\n  - `$1` = `admin`\n  - `$2` = `123`\n- 最终效果：为请求添加请求头 `X-Type: admin` 和 `X-ID: 123`\n\n---\n\n## 路径自动拼接规则\n\n当使用 **域名匹配** 或 **路径匹配** 映射到本地文件/目录或远程 URL 时，Whistle 会自动拼接原请求的剩余路径。\n\n**规则**：\n```txt\nhttps://*.example.com/path/to https://www.test.com/test\nwww.example.com file:///Usr/test\n```\n\n**效果说明**：\n\n| 请求 URL | 映射后目标 |\n|----------|------------|\n| `https://abc.example.com/path/to/x/y/z?query` | `https://www.test.com/test/x/y/z?query` |\n| `https://www.example.com/path/to/index.html?query` | 本地文件 `/Usr/test/path/to/index.html`（自动去除 query） |\n\n> 注意：查询参数 `?query` 在映射到本地文件时会被自动忽略（文件系统路径不支持），但映射到远程 URL 时会保留。\n\n---\n\n## 配置示例\n\n### 基础匹配\n\n```txt\n# 精确域名匹配\napi.example.com proxy://127.0.0.1:3000\n\n# 指定端口的域名匹配\nwww.example.com:8080 file:///local/dev\n\n# 路径匹配（映射到本地 JSON 数据）\nwww.example.com/api/users file://{user-data}\n```\n\n### 通配符匹配\n\n```txt\n# 匹配所有子域名\n**.example.com proxy://127.0.0.1:8080\n\n# 匹配特定前缀的子域名\ndev-**.example.com file:///(dev-mock)\n```\n\n### 正则匹配\n\n```txt\n# 匹配数字 ID 的用户页面\n/^https?://www\\.example\\.com/user/(\\d+)/ file://(user-$1)\n\n# 忽略大小写匹配特定路径\n/\\/api\\/v1\\/data/i resBody://({\"version\":\"v1\"})\n\n# 匹配静态资源文件并设置缓存\n/\\.(jpg|png|gif|css|js)$/i cache://86400\n```\n\n### 复杂匹配\n\n```txt\n# 通配符 + 路径捕获传值\n^https://**.example.com/api/*/v*/users reqHeaders://x-api-version=$3\n\n# 按请求方法区分处理\nwww.example.com/api file://({\"status\":\"ok\"}) includeFilter://m:GET\nwww.example.com/api file://({\"status\":\"created\"}) includeFilter://m:POST\n```\n\n---\n\n## 故障排除\n\n### Q：规则没有匹配到请求\n\n**可能原因及解决方案**：\n\n1. **URL 格式不正确**  \n   → 确保规则中的协议（http/https/ws/wss）与请求一致，或使用不带协议的写法（`example.com/path`）。\n\n2. **端口号不匹配**  \n   → 如果请求使用非标准端口，需要在规则中明确指定端口（如 `example.com:8080`）。\n\n3. **通配符或正则表达式有误**  \n   → 使用 [Whistle 在线调试工具](https://github.com/avwo/whistle) 或本地日志验证匹配情况。\n\n4. **规则优先级问题**  \n   → 更具体的规则应写在前面，或使用 `$` 精确匹配提高优先级。\n\n### Q：正则表达式不生效\n\n**检查清单**：\n\n- [ ] 正则表达式是否以 `/` 开头和结尾，如 `/pattern/`？\n- [ ] 特殊字符是否已转义（如 `.` 需写成 `\\.`，`/` 需写成 `\\/`）？\n- [ ] 标志（`i` 等）是否正确添加？\n- [ ] 正则是否匹配完整的 URL？可以在控制台用 `new RegExp('pattern', 'flags').test(url)` 验证。\n\n### Q：子匹配传值不正确\n\n**常见问题**：\n\n1. **捕获组编号错误**  \n   → `$1` 对应第一个 `( )`，`$2` 对应第二个，依次类推。注意嵌套捕获组的顺序。\n\n2. **通配符捕获不符合预期**  \n   → `*` 不匹配点号（`.`）和斜杠（`/`），`**` 匹配任意字符（除 `?` 外）。  \n   → 示例：`*.example.com` 无法匹配 `a.b.example.com`，需改用 `**.example.com`。\n\n3. **正则捕获组未按预期工作**  \n   → 使用 `(?:)` 表示非捕获组，避免干扰编号。  \n   → 使用在线正则测试工具（如 regex101.com）验证捕获内容。\n\n### Q：映射到本地文件时路径错误\n\n- 使用 `file://` 协议时，确保路径是绝对路径（如 `file:///User/xxx`）。\n- 路径拼接遵循自动拼接规则，请求的剩余路径会追加到目标路径后。\n- 查询参数会被自动忽略，不影响文件读取。\n\n---\n\n## 扩展阅读\n\n- [规则语法文档](./rule)：了解完整的规则语法结构\n- [操作指令文档](./operation)：学习如何配置操作指令\n- [过滤器文档](./filters)：了解如何精确控制规则生效条件\n","docs/docs/rules/pipe.md":"# pipe\n将 HTTP/HTTPS/WebSocket/TUNNEL 请求/响应数据流交给插件处理。\n\n## 规则语法\n``` txt\npattern pipe://plugin-name(pipeValue) [filters...]\n```\n> `(pipeValue)` 可选，在插件 Hook 可以通过 `req.originalReq.pipeValue` 获取\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| plugin-name(pipeValue)   | 插件名称 + 可选参数 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\ntunnel://wwww.example.com pipe://test\ntunnel://test-tunnel.example.com pipe://test-pipe-tunnel(abc)\n\nwss://test-ws.example.com/path pipe://test-pipe-ws\n\nhttps://www.example.com/path pipe://test-pipe-http(123)\n\n```\n\n具体用法参考：[插件开发文档](../extensions/dev.md#pipe)\n","docs/docs/rules/plugin-vars.md":"# `%` 符号用法\n\n通过插件管理界面可直观地配置各项参数，例如可以让 [whistle.autosave](https://github.com/whistle-plugins/whistle.autosave) 插件支持以下配置：\n- 启用存储抓包数据\n- 配置存储抓包数据目录\n\n除管理界面外，您还可以直接在规则文件中使用 `%` 符号快速配置。\n\n## 全局配置（对所有请求生效）\n``` txt\n%autosave=123\n%autosave.enableAutoSave=true\n%autosave.storageDir=/User/xxx/test/sessions\n```\n\n## 精细化配置（针对特定请求）\n``` txt\nwww.test.com/api %autosave=abc\nwww.test.com/api %autosave.enableAutoSave=false [filters...]\nwww.test.com/api %autosave.storageDir= [filters...]\n```\n\n## 获取插件变量值\n在插件的 Hooks 里面可以通过以下代码获取到配置的变量列表：\n\n``` js\nreq.originalReq.globalPluginVars; // 全局变量，如 ['123', 'enableAutoSave=true', 'storageDir=/User/xxx/test/sessions']\nreq.originalReq.pluginVars; // 精细化配置的变量，如 ['abc', 'enableAutoSave=false', 'storageDir=']\n```\n\n## 开启规则自动提示\n如果通过插件变量的配置项是固定的，可以通过插件的 `package.json` 里面的 `whistleConfig` 字段配置可选项，这样在规则中可以自动提醒：\n\n#### 简单提示\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": true,\n  ...\n}\n```\n完成上述配置后，在 Whistle 的 Rules 编辑器中输入 % 字符时，将自动提示以下格式的插件变量：\n``` txt\n%test-plugin-vars=\n```\n\n#### 匿名 Key 值提示\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        \"test1\",\n        \"test2\",\n        \"test3\"\n      ]\n    }\n  },\n  ...\n}\n```\n\n<img src=\"/img/plugin-vars-hint-list.png\" width=\"260\" />\n\n值和显示内容分离：\n\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        {\n          \"text\": \"test1\",\n          \"displayText\": \"displayText1\"\n        },\n       {\n          \"text\": \"test2\",\n          \"displayText\": \"displayText2\"\n        },\n        {\n          \"text\": \"test3\",\n          \"displayText\": \"displayText3\",\n          \"help\": \"https://www.example.com/path\"\n        }\n      ]\n    }\n  }\n  ...\n}\n```\n> `displayText` 和 `help` 都是可选，配置 `help: 帮助链接` 时，当选中该提醒时，键盘按 `F1` 键自动打开该帮助链接\n\n<img src=\"/img/plugin-vars-display-list.png\" width=\"220\" />\n\n#### 设置 Key 名称\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        {\n          \"text\": \"test1\",\n          \"displayText\": \"displayText1\"\n        },\n       {\n          \"text\": \"test2\",\n          \"displayText\": \"displayText2\"\n        },\n        {\n          \"text\": \"test3\",\n          \"displayText\": \"displayText3\",\n          \"help\": \"https://www.example.com/path\"\n        }\n      ],\n      \"hintSuffix\": [\n        \"=\",\n        \".key1=123\",\n        \".key2\"\n      ]\n    }\n  }\n  ...\n}\n```\n<img src=\"/img/plugin-vars-key-hint.png\" width=\"300\" />\n\n选中 `%test-plugin-vars=` 后会自动显示 `hintList` 的内容：\n\n<img src=\"/img/plugin-vars-display-list.png\" width=\"220\" />\n\n#### 借助后台接口\n\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintSuffix\": [\n        \"=\",\n        \".key1=123\",\n        \".key2\"\n      ],\n      \"hintUrl\": \"/cgi-bin/plugin-vars\"\n    }\n  }\n  ...\n}\n```\n> `hintList` 和 `hintUrl` 是互斥的，同时只能使用其中一个，`hintSuffix` 可选\n\n<img src=\"/img/plugin-vars-hint-url1.png\" width=\"360\" />\n\n<img src=\"/img/plugin-vars-hint-url2.png\" width=\"360\" />\n\n`/cgi-bin/plugin-vars` 的实现参考：[插件开发文档](../extensions/dev#rules-hint)\n","docs/docs/rules/protocols.md":"# 操作协议列表\n\n## 特殊规则\n1. [@（引入远程规则或设置客户端证书）](./@)\n2. [%（设置插件的变量值）](./plugin-vars)\n\n## Map Local（用本地文件替换响应内容）\n1. [file](./file)\n2. [xfile](./xfile)\n3. [tpl](./tpl)\n4. [xtpl](./xtpl)\n5. [rawfile](./rawfile)\n6. [xrawfile](./xrawfile)\n\n## Map Remote（返回其它 URL 的数据）\n1. [https](./https)\n2. [http](./http)\n3. [wss](./wss)\n4. [ws](./ws)\n5. [tunnel](./tunnel)\n\n## DNS Spoofing（修改请求 IP 地址）\n1. [host](./host)\n2. [xhost](./xhost)\n3. [proxy (http-proxy)](./proxy)\n4. [xproxy (xhttp-proxy)](./xproxy)\n5. [https-proxy](./https-proxy)\n6. [xhttps-proxy](./xhttps-proxy)\n7. [socks](./socks)\n8.  [xsocks](./xsocks)\n9.  [pac](./pac)\n\n## Rewrite Request（修改请求数据）\n1. [urlParams](./urlParams)\n2. [pathReplace](./pathReplace)\n3. [sniCallback](./sniCallback)\n4. [method](./method)\n5. [tlsOptions](./cipher)\n6. [reqHeaders](./reqHeaders)\n7. [forwardedFor](./forwardedFor)\n8. [ua](./ua)\n9. [auth](./auth)\n10. [cache](./cache)\n11. [referer](./referer)\n12. [attachment](./attachment)\n13. [reqCharset](./reqCharset)\n14. [reqCookies](./reqCookies)\n15. [reqCors](./reqCors)\n16. [reqType](./reqType)\n17. [reqBody](./reqBody)\n18. [reqMerge](./reqMerge)\n19. [reqPrepend](./reqPrepend)\n20. [reqAppend](./reqAppend)\n21. [reqReplace](./reqReplace)\n22. [reqWrite](./reqWrite)\n23. [reqWriteRaw](./reqWriteRaw)\n24. [reqRules](./reqRules)\n25. [reqScript](./reqScript)\n\n## Rewrite Response（修改响应数据）\n1. [statusCode](./statusCode)\n2. [replaceStatus](./replaceStatus)\n3. [redirect](./redirect)\n4. [locationHref](./locationHref)\n5. [resHeaders](./resHeaders)\n6. [responseFor](./responseFor)\n7. [resCharset](./resCharset)\n8. [resCookies](./resCookies)\n9. [resCors](./resCors)\n10. [resType](./resType)\n11. [resBody](./resBody)\n12. [resMerge](./resMerge)\n13. [resPrepend](./resPrepend)\n14. [resAppend](./resAppend)\n15. [resReplace](./resReplace)\n16. [htmlPrepend](./htmlPrepend)\n17. [htmlBody](./htmlBody)\n18. [htmlAppend](./htmlAppend)\n19. [cssPrepend](./cssPrepend)\n20. [cssBody](./cssBody)\n21. [cssAppend](./cssAppend)\n22. [jsPrepend](./jsPrepend)\n23. [jsBody](./jsBody)\n24. [jsAppend](./jsAppend)\n25. [trailers](./trailers)\n26. [resWrite](./resWrite)\n27. [resWriteRaw](./resWriteRaw)\n28. [resRules](./resRules)\n29. [resScript](./resScript)\n\n## General（请求/响应通用协议）\n1. [pipe](./pipe)\n2. [delete](./delete)\n3. [headerReplace](./headerReplace)\n\n## Throttle（限制流量速度）\n1. [reqDelay](./reqDelay)\n2. [resDelay](./resDelay)\n3. [reqSpeed](./reqSpeed)\n4. [resSpeed](./resSpeed)\n\n## Tools（调试工具）\n1. [weinre](./weinre)\n2. [log](./log)\n\n## Settings（特殊设置）\n1. [style](./style)\n2. [lineProps](./lineProps)\n3. [enable](./enable)\n4. [disable](./disable)\n\n## Filters（规则过滤器）\n1. [ignore](./ignore)\n2. [skip](./skip)\n3. [excludeFilter](./excludeFilter)\n4. [includeFilter](./includeFilter)\n","docs/docs/rules/proxy.md":"# proxy (http-proxy)\n`proxy`（或 `http-proxy`）指令用于将匹配的请求通过指定的 HTTP 代理服务器转发。两个指令名称功能完全一致，可互换使用。\n\n## 规则语法\n``` txt\npattern proxy://ipOrDomain[:port] [filters...]\n# 等效写法：\npattern http-proxy://ipOrDomain[:port] [filters...]\n```\n> `port` 可选，不填则使用默认端口 `80`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 将请求代理到 HTTP PROXY: `127.0.0.1:80`\nwww.example.com/path proxy://127.0.0.1 # 默认端口 80\n\n# 将当前域名的所有请求代理到 HTTP PROXY: `127.0.0.1:8080`\nwww.example.com proxy://127.0.0.1:8080\n\n# 也可以用域名\nwww.example.com/path proxy://test.proxy.com # 默认端口 80\nwww.example.com proxy://test.proxy.com:8080\n```\n\n## 高级用法\n将请求代理到上游代理后，默认情况下上游代理会根据请求的域名通过 DNS 获取服务器 IP 再继续请求，如果想让上游代理根据指定 IP及端口继续请求，可以这么处理：\n``` txt\n# 通过查询参数\nwww.example.com proxy://127.0.0.1:8080?host=1.1.1.1\nwww.example.com proxy://127.0.0.1:8080?host=1.1.1.1:8080\n\n# 通过指令启用\nwww.example.com proxy://127.0.0.1:8080 1.1.1.1 enable://proxyHost\nwww.example.com proxy://127.0.0.1:8080 1.1.1.1:8080 enable://proxyHost\n````\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n## 与 host 的匹配优先级\n\n#### 默认行为\n\n当请求同时匹配 `host` 和 `proxy` 规则时：\n- 仅 `host` 规则生效\n- `proxy` 规则自动忽略\n\n#### 修改优先级\n| 配置方式 | 语法 | 效果 |\n|---------|------|------|\n| **优先 proxy** | [`enable://proxyFirst`](./enable) 或 [`lineProps://proxyFirst`](./lineProps) | 仅 `proxy` 生效（覆盖 host） |\n| **同时生效** | [`enable://proxyHost`](./enable) 或 [`lineProps://proxyHost`](./lineProps) | `proxy` 和 `host` 同时生效 |\n\n#### 使用建议\n- 大多数场景使用默认行为即可\n- 需要特殊代理逻辑时才使用 `proxyFirst`\n- 需要双重匹配时使用 `proxyHost`\n\n## 注意事项\n`proxy` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com proxy://127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求代理到 `127.0.0.1:1234`，但由于 `proxy` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `proxy` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com proxy://127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `proxy` 规则匹配，最终代理到 `127.0.0.1:1234`。\n","docs/docs/rules/rawfile.md":"# rawfile\nrawfile 是 [file](./file) 的增强版本，除了支持 [file](./file) 的所有功能外，还允许在文件中定义完整的 HTTP 响应，包括：\n- 响应状态码\n- 响应头部\n- 响应内容\n\n## 规则语法\n``` txt\npattern rawfile://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## Mock 接口\n1. 本地文件 `/User/xxx/raw.txt`\n    ``` js\n    HTTP/1.1 500 OK\n    Content-Type: application/json\n    X-Custom-Header: value\n\n    {\n      \"status\": \"success\",\n      \"data\": \"your content here\"\n    }\n    ```\n2. 配置规则\n    ``` txt\n    https://www.example.com/test/rawfile tpl:///User/xxx/raw.txt\n\n    # 等价于\n    #  https://www.example.com/test/rawfile file:///User/xxx/test.json replaceStatus://500 resType://json resHeaders://x-custom-header=value\n    \n    # 支持远程 URL\n    # pattern rawfile://https://example.com/raw.json\n    ```\n3. 请求 `https://www.example.com/test/rawfile` 返回结果：\n    ``` txt\n    // 状态码\n    500\n\n    // 响应头\n    content-type: application/json\n    x-custom-header: value\n    content-length: 56\n\n    // 响应内容\n    {\n      \"status\": \"success\",\n      \"data\": \"your content here\"\n    }\n    ```\n\n其它功能参考：[file](./file)\n","docs/docs/rules/redirect.md":"# redirect\n将匹配的请求立即重定向（302 Found）到指定URL，不请求到后台服务器。\n\n## 规则语法\n``` txt\npattern redirect://targetUrl [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| targetUrl   | 重定向后的 URL，可以是相对路径 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 基础配置\n``` txt\nwww.example.com/path redirect://https://www.qq.com\n\nwww.example.com/path2 redirect://../abc/123\n```\n- 访问 `https://www.example.com/path/to` 重定向到 `https://www.qq.com`（不自动拼接路径）\n- 访问 `https://www.example.com/path2/to` 重定向到 `https://www.example.com/abc/123`\n\n#### 实现路径拼接\n``` txt\n# 通配符\n^www.example.com/path/*** redirect://`https://www.example.com/$1`\n```\n- 访问 `https://www.example.com/path/to?query` 重定向到 `https://www.example.com/to?query`\n\n#### `301` 跳转\n``` txt\nwww.example.com/path/test redirect://https://www.qq.com replaceStatus://301\n```\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","docs/docs/rules/referer.md":"# referer\n修改请求头的 `referer` 字段，有些服务器会校验请求头的 `referer` 字段，这个协议可以用来绕过这个检测或者测试后台的功能。\n\n## 规则语法\n``` txt\npattern referer://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 完整 URL 链接<br/>• 内联/内嵌/Values内容<br/>⚠️ 不支持从文件/远程 URL 加载数据 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nwww.example.com/path referer://https://www.test.com/x/y/z?xxx\n```\n\n## 关联协议\n1. 等价于：[reqHeaders://referer=https://xxx](./reqHeaders)\n","docs/docs/rules/replaceStatus.md":"# replaceStatus\n\n`replaceStatus` 协议用于替换 HTTP 状态码，在请求响应完成后将原始状态码替换成指定的 HTTP 状态码。通过 `replaceStatus` 协议，你可以：\n- **修改服务器响应的状态码**：基于真实服务器响应进行状态码替换\n- **测试不同状态码的处理逻辑**：在不修改后端代码的情况下测试各种状态码场景\n- **错误场景模拟**：将正常响应转换为错误状态码，测试客户端容错能力\n- **重定向测试**：修改重定向状态码或目标\n\n## 规则语法\n\n`replaceStatus` 协议支持多种方式配置：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要替换的状态码。\n\n```txt\npattern replaceStatus://code [lineProps...] [filters...]\n```\n**示例：**\n```txt\nwww.example.com/api/old replaceStatus://301\n```\n\n### 2. 内嵌值（使用代码块）\n当需要根据条件返回不同状态码，或希望复用配置时可使用此方式。\n\n````txt\npattern replaceStatus://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n404\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的状态码。\n\n```txt\npattern replaceStatus://{key-of-values} [lineProps...] [filters...]\n```\n**前提**：在 `Values` 中存在名为 `key-of-values` 的键，其值为状态码。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载内容。\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **code** | 是 | 要替换的 HTTP 响应状态码，如：<br>• `200` OK<br>• `301` 永久重定向<br>• `302` 临时重定向<br>• `404` 未找到<br>• `500` 服务器错误<br>• 支持所有标准 HTTP 状态码 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n## 配置示例\n\n### 基础示例\n```txt\n# 将任何响应替换为 404 状态码\nwww.example.com replaceStatus://404\n\n# 将服务器错误替换为正常状态码\nwww.example.com/api replaceStatus://200 includeFilter://s:500\n\n# 修改重定向状态码\nwww.example.com/redirect replaceStatus://307\n```\n\n### 模拟认证失败\n```txt\n# 将正常响应替换为 401 状态码，触发浏览器认证弹窗\nwww.example.com/secure-page replaceStatus://401\n\n# 禁止弹出登录框，直接返回 401\nwww.example.com/secure-page replaceStatus://401 disable://userLogin\n\n# 或使用 lineProps 达到同样效果\nwww.example.com/secure-page replaceStatus://401 lineProps://disableUserLogin\n```\n\n### 条件替换\n```txt\n# 仅当原始状态码为 200 时替换为 500\nwww.example.com/api replaceStatus://500 includeFilter://s:200\n\n# 仅替换特定路径的响应状态码\nwww.example.com/api/admin replaceStatus://403\n\n```\n\n### 配合其他协议使用\n````txt\n# 替换状态码并修改响应内容\nwww.example.com/api/error replaceStatus://500 resBody://{errMsg}\n\n``` errMsg\n{\"message\":\"Custom error message\"}\n```\n\n# 替换为 301 重定向并设置新的 Location 头\nwww.example.com/old-url replaceStatus://301 resHeaders://location=https://www.example.com/new-url\n````\n\n### 使用内嵌值\n````txt\n``` maintenance-status\n503\n```\n\n# 将网站所有响应替换为维护状态\nwww.example.com replaceStatus://{maintenance-status}\n````\n\n### 环境特定配置\n```txt\n# 仅在测试环境模拟错误\ntest.example.com/api replaceStatus://500\n\n# 开发环境保持原样\n# dev.example.com/api (不设置 replaceStatus)\n```\n\n## 常见使用场景\n\n### 1. 错误处理测试\n```txt\n# 测试客户端对服务器错误的处理\nwww.example.com/api replaceStatus://500 includeFilter://chance:0.1\n```\n\n### 2. 重定向行为测试\n```txt\n# 测试永久重定向缓存\nwww.example.com/old-page replaceStatus://301\n\n# 测试临时重定向\nwww.example.com/temp-redirect replaceStatus://302\n```\n\n### 3. 认证授权测试\n```txt\n# 测试未认证访问\nwww.example.com/protected replaceStatus://401\n\n# 测试权限不足\nwww.example.com/admin replaceStatus://403\n```\n\n### 4. 限流测试\n```txt\n# 测试请求过多场景\nwww.example.com/api/rate-limited replaceStatus://429 resHeaders://retry-after=60\n```\n\n### 5. 维护模式测试\n```txt\n# 测试维护页面\nwww.example.com replaceStatus://503 resBody://(<h1>系统维护中...</h1>)\n```\n\n## 与 statusCode 的区别\n\n`replaceStatus` 协议与 [`statusCode`](./statusCode) 协议的主要区别在于处理时机：\n- **`replaceStatus`**：**先请求后端服务器**，收到响应后替换状态码\n- **`statusCode`**：**不请求后端服务器**，立即返回指定的状态码\n\n\n## 注意事项\n\n### 1. 与 statusCode 的区别\n- **处理时机**：`replaceStatus` 在响应阶段生效，`statusCode` 在请求阶段生效\n- **服务器访问**：`replaceStatus` 会访问服务器，`statusCode` 不会\n- **使用场景**：需要真实服务器数据时用 `replaceStatus`，完全模拟时用 `statusCode`\n\n### 2. 响应内容保留\n- 默认情况下，替换状态码时响应内容保持不变\n- 可通过配合 [`resBody`](./resBody) 协议修改响应内容\n\n### 3. 认证弹窗控制\n- 替换为 `401` 状态码时，默认会触发浏览器认证弹窗\n- 可通过以下方式禁用弹窗：\n  - 添加 `disable://userLogin`\n  - 添加 `lineProps://disableUserLogin`\n  - 使用 [`enable`](./enable) 协议的反向配置\n\n### 4. 重定向处理\n- 替换为重定向状态码（301、302 等）时，通常需要配合修改 `Location` 响应头\n- 否则客户端可能无法正确处理重定向\n\n### 5. 状态码兼容性\n- 确保替换的状态码与响应内容兼容\n- 例如：不应将 HTML 页面替换为 204（无内容）状态码\n\n## 高级用法\n\n### 状态码转换映射\n```txt\n# 将所有重定向统一为 302\nwww.example.com replaceStatus://302 includeFilter://s:301\n```\n\n### 概率性替换\n```txt\n# 10% 的概率将响应替换为错误\nwww.example.com/api replaceStatus://500 inclideFilter://chance:0.1\n```\n\n### 组合多个规则\n```txt\n# 针对不同原始状态码进行不同替换\nwww.example.com/api replaceStatus://200 includeFilter://s:404\nwww.example.com/api replaceStatus://400 includeFilter://s:403\n```\n\n## 故障排除\n\n### Q: 状态码替换没有生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配请求 URL\n2. 是否有其他更高优先级的规则覆盖\n3. 过滤器条件是否满足（特别是对原始状态码的过滤）\n\n### Q: 响应内容与状态码不匹配\n**A:** 检查：\n1. 是否同时修改了响应内容\n2. 响应头是否与新状态码兼容\n3. 客户端是否能正确处理该状态码\n\n### Q: 浏览器弹出认证窗口\n**A:** 对于 401 状态码：\n1. 检查是否添加了 `disable://userLogin` 或 `lineProps://disableUserLogin`\n2. 确认规则顺序是否正确\n\n### Q: 重定向循环\n**A:** 检查：\n1. 是否设置了正确的 `Location` 头\n2. 重定向目标是否正确\n3. 是否有多个重定向规则冲突\n\n## 关联协议\n\n1. **直接返回状态码**：[statusCode](./statusCode)\n   - 不请求服务器，直接返回指定的状态码\n\n2. **禁止认证弹窗**：[disable](./disable) 或 [lineProps](./lineProps)\n   - 禁用 401 状态码触发的浏览器登录弹窗\n\n3. **设置响应内容**：[resBody](./resBody)\n   - 为状态码响应添加自定义内容\n\n4. **设置响应头**：[resHeaders](./resHeaders)\n   - 为重定向等场景设置必要的响应头\n\n## 扩展阅读\n\n- [HTTP 状态码 MDN 文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status)：了解所有 HTTP 状态码的含义\n- [匹配模式文档](./pattern)：详细了解 URL 匹配规则\n- [过滤器文档](./filters)：了解更多过滤器功能\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","docs/docs/rules/reqAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqAppend.md -->","docs/docs/rules/reqBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqBody.md -->","docs/docs/rules/reqCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCharset.md -->","docs/docs/rules/reqCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCookies.md -->","docs/docs/rules/reqCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCors.md -->","docs/docs/rules/reqDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqDelay.md -->","docs/docs/rules/reqHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqHeaders.md -->","docs/docs/rules/reqMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqMerge.md -->","docs/docs/rules/reqPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqPrepend.md -->","docs/docs/rules/reqReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqReplace.md -->","docs/docs/rules/reqRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqRules.md -->","docs/docs/rules/reqScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqScript.md -->","docs/docs/rules/reqSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqSpeed.md -->","docs/docs/rules/reqType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqType.md -->","docs/docs/rules/reqWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqWrite.md -->","docs/docs/rules/reqWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqWriteRaw.md -->","docs/docs/rules/resAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresAppend.md -->","docs/docs/rules/resBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresBody.md -->","docs/docs/rules/resCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCharset.md -->","docs/docs/rules/resCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCookies.md -->","docs/docs/rules/resCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCors.md -->","docs/docs/rules/resDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresDelay.md -->","docs/docs/rules/resHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresHeaders.md -->","docs/docs/rules/resMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresMerge.md -->","docs/docs/rules/resPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresPrepend.md -->","docs/docs/rules/resReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresReplace.md -->","docs/docs/rules/resRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresRules.md -->","docs/docs/rules/resScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresScript.md -->","docs/docs/rules/resSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresSpeed.md -->","docs/docs/rules/resType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresType.md -->","docs/docs/rules/resWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresWrite.md -->","docs/docs/rules/resWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresWriteRaw.md -->","docs/docs/rules/responseFor.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresponseFor.md -->","docs/docs/rules/rule.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Frule.md -->","docs/docs/rules/skip.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fskip.md -->","docs/docs/rules/sniCallback.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FsniCallback.md -->","docs/docs/rules/socks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fsocks.md -->","docs/docs/rules/statusCode.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FstatusCode.md -->","docs/docs/rules/style.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fstyle.md -->","docs/docs/rules/tpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftpl.md -->","docs/docs/rules/trailers.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftrailers.md -->","docs/docs/rules/tunnel.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftunnel.md -->","docs/docs/rules/ua.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fua.md -->","docs/docs/rules/urlParams.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FurlParams.md -->","docs/docs/rules/weinre.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fweinre.md -->","docs/docs/rules/ws.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fws.md -->","docs/docs/rules/wss.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fwss.md -->","docs/docs/rules/xfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxfile.md -->","docs/docs/rules/xhost.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxhost.md -->","docs/docs/rules/xhttps-proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxhttps-proxy.md -->","docs/docs/rules/xproxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxproxy.md -->","docs/docs/rules/xrawfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxrawfile.md -->","docs/docs/rules/xsocks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxsocks.md -->","docs/docs/rules/xtpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxtpl.md -->","docs/en/docs/extensions/api.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fapi.md -->","docs/en/docs/extensions/dev.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fdev.md -->","docs/en/docs/extensions/npm.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fnpm.md -->","docs/en/docs/extensions/usage.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fusage.md -->","docs/en/docs/rules/@.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2F%40.md -->","docs/en/docs/rules/attachment.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fattachment.md -->","docs/en/docs/rules/auth.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fauth.md -->","docs/en/docs/rules/cache.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fcache.md -->","docs/en/docs/rules/cipher.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fcipher.md -->","docs/en/docs/rules/cssAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssAppend.md -->","docs/en/docs/rules/cssBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssBody.md -->","docs/en/docs/rules/cssPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssPrepend.md -->","docs/en/docs/rules/delete.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fdelete.md -->","docs/en/docs/rules/disable.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fdisable.md -->","docs/en/docs/rules/enable.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fenable.md -->","docs/en/docs/rules/excludeFilter.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FexcludeFilter.md -->","docs/en/docs/rules/file.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ffile.md -->","docs/en/docs/rules/filters.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ffilters.md -->","docs/en/docs/rules/forwardedFor.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FforwardedFor.md -->","docs/en/docs/rules/frameScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FframeScript.md -->","docs/en/docs/rules/headerReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FheaderReplace.md -->","docs/en/docs/rules/host.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhost.md -->","docs/en/docs/rules/htmlAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlAppend.md -->","docs/en/docs/rules/htmlBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlBody.md -->","docs/en/docs/rules/htmlPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlPrepend.md -->","docs/en/docs/rules/http.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttp.md -->","docs/en/docs/rules/https-proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttps-proxy.md -->","docs/en/docs/rules/https.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttps.md -->","docs/en/docs/rules/ignore.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fignore.md -->","docs/en/docs/rules/includeFilter.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FincludeFilter.md -->","docs/en/docs/rules/inherit.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Finherit.md -->","docs/en/docs/rules/jsAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsAppend.md -->","docs/en/docs/rules/jsBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsBody.md -->","docs/en/docs/rules/jsPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsPrepend.md -->","docs/en/docs/rules/lineProps.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FlineProps.md -->","docs/en/docs/rules/locationHref.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FlocationHref.md -->","docs/en/docs/rules/log.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Flog.md -->","docs/en/docs/rules/method.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fmethod.md -->","docs/en/docs/rules/operation.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Foperation.md -->","docs/en/docs/rules/pac.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpac.md -->","docs/en/docs/rules/pathReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FpathReplace.md -->","docs/en/docs/rules/pattern.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpattern.md -->","docs/en/docs/rules/pipe.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpipe.md -->","docs/en/docs/rules/plugin-vars.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fplugin-vars.md -->","docs/en/docs/rules/protocols.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fprotocols.md -->","docs/en/docs/rules/proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fproxy.md -->","docs/en/docs/rules/rawfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Frawfile.md -->","docs/en/docs/rules/redirect.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fredirect.md -->","docs/en/docs/rules/referer.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Freferer.md -->","docs/en/docs/rules/replaceStatus.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreplaceStatus.md -->","docs/en/docs/rules/reqAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqAppend.md -->","docs/en/docs/rules/reqBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqBody.md -->","docs/en/docs/rules/reqCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCharset.md -->","docs/en/docs/rules/reqCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCookies.md -->","docs/en/docs/rules/reqCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCors.md -->","docs/en/docs/rules/reqDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqDelay.md -->","docs/en/docs/rules/reqHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqHeaders.md -->","docs/en/docs/rules/reqMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqMerge.md -->","docs/en/docs/rules/reqPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqPrepend.md -->","docs/en/docs/rules/reqReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqReplace.md -->","docs/en/docs/rules/reqRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqRules.md -->","docs/en/docs/rules/reqScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqScript.md -->","docs/en/docs/rules/reqSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqSpeed.md -->","docs/en/docs/rules/reqType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqType.md -->","docs/en/docs/rules/reqWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqWrite.md -->","docs/en/docs/rules/reqWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqWriteRaw.md -->","docs/en/docs/rules/resAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresAppend.md -->","docs/en/docs/rules/resBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresBody.md -->","docs/en/docs/rules/resCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCharset.md -->","docs/en/docs/rules/resCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCookies.md -->","docs/en/docs/rules/resCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCors.md -->","docs/en/docs/rules/resDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresDelay.md -->","docs/en/docs/rules/resHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresHeaders.md -->","docs/en/docs/rules/resMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresMerge.md -->","docs/en/docs/rules/resPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresPrepend.md -->","docs/en/docs/rules/resReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresReplace.md -->","docs/en/docs/rules/resRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresRules.md -->","docs/en/docs/rules/resScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresScript.md -->","docs/en/docs/rules/resSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresSpeed.md -->","docs/en/docs/rules/resType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresType.md -->","docs/en/docs/rules/resWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresWrite.md -->","docs/en/docs/rules/resWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresWriteRaw.md -->","docs/en/docs/rules/responseFor.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresponseFor.md -->","docs/en/docs/rules/rule.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Frule.md -->","docs/en/docs/rules/skip.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fskip.md -->","docs/en/docs/rules/sniCallback.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FsniCallback.md -->","docs/en/docs/rules/socks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fsocks.md -->","docs/en/docs/rules/statusCode.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FstatusCode.md -->","docs/en/docs/rules/style.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fstyle.md -->","docs/en/docs/rules/tpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftpl.md -->","docs/en/docs/rules/trailers.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftrailers.md -->","docs/en/docs/rules/tunnel.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftunnel.md -->","docs/en/docs/rules/ua.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fua.md -->","docs/en/docs/rules/urlParams.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FurlParams.md -->","docs/en/docs/rules/weinre.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fweinre.md -->","docs/en/docs/rules/ws.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fws.md -->","docs/en/docs/rules/wss.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fwss.md -->","docs/en/docs/rules/xfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxfile.md -->","docs/en/docs/rules/xhost.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxhost.md -->","docs/en/docs/rules/xhttps-proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxhttps-proxy.md -->","docs/en/docs/rules/xproxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxproxy.md -->","docs/en/docs/rules/xrawfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxrawfile.md -->","docs/en/docs/rules/xsocks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxsocks.md -->","docs/en/docs/rules/xtpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxtpl.md -->"},"files":{"docs/docs/extensions/api.md":"# Local Agent API\n\nWhistle 的本地 API，供 AI Agent 或第三方进程以编程方式操作 Whistle 代理服务。支持规则管理、插件控制、抓包数据获取、HTTPS 代理开关、证书管理等核心功能，具体用法如下（确保已全局安装 Whistle 且版本 ≥ 2.10.7）：\n\n``` javascript\nconst cp = require('child_process');\nconst qs = require('querystring');\n\nconst loadModule = require;\nlet w2Api;\n\n/**\n * @async 异步加载 Whistle API 模块\n * @function loadApi\n * @description\n * 尝试加载 Whistle 的 API 模块，并校验其版本是否符合最低要求（≥ 2.10.7）。\n * 如果版本过低，或加载过程中发生任何错误（如子进程调用失败、模块加载异常、网络状态获取失败等），\n * 都会抛出对应的错误。\n * \n * @returns {Promise<Object>} 返回 Whistle API 模块对象（通常包含 network、rules 等接口）\n * \n * @throws {Error} 当执行 `w2 root bin/api` 命令失败时抛出（例如 whistle 未安装或未在 PATH 中，需要通过 `npm install -g whistle` 安装）\n * @throws {Error} 当调用 `api.network.getStatus()` 失败时抛出（例如 whistle 或 whistle client 服务未启动）\n * @throws {Error} 当 Whistle 版本低于 2.10.7 时抛出，错误信息为 'Whistle version must be >= 2.10.7'\n * \n * @example\n * try {\n *   const api = await loadApi();\n *   // 使用 api.network.getStatus() 等\n * } catch (err) {\n *   console.error('加载 Whistle API 失败:', err.message);\n * }\n */\nconst loadApi = async () => {\n  if (!w2Api) {\n    w2Api = loadModule(cp.spawnSync('w2', ['root', 'bin/api']).stdout.toString().trim());\n  }\n  const { version } = await w2Api.network.getStatus();\n  const [major, minor, patch] = version.split('.').map(Number);\n  w2Api.version = version;\n  if (major > 2 || (major == 2 && (minor > 10 || (minor == 10 && patch >= 7)))) {\n    return w2Api;\n  }\n  throw new Error('Whistle version must be >= 2.10.7');\n};\n\n/**\n * @abstract 判断当前 Whistle 是否启用了 HTTPS 代理\n * @function isEnabledHTTPS\n * @description\n * 通过 Whistle API 检查当前代理是否启用了 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了 HTTPS 代理\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.isEnabledHTTPS()` 调用失败\n *\n * @example\n * try {\n *   const enabled = await isEnabledHTTPS();\n *   console.log(`当前 HTTPS 代理是否启用: ${enabled}`);\n * } catch (err) {\n *   console.error('检查 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst isEnabledHTTPS = async () => {\n  const api = await loadApi();\n  return await api.isEnabledHTTPS();\n};\n\n/**\n * @async 设置 Whistle 是否启用 HTTPS 代理\n * @function setEnableHTTPS\n * @description\n * 通过 Whistle API 设置当前代理是否启用 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用 HTTPS 代理\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.setEnableHTTPS()` 调用失败\n *\n * @example\n * try {\n *   await setEnableHTTPS(true);\n *   console.log('设置 HTTPS 代理启用状态成功');\n * } catch (err) {\n *   console.error('设置 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst setEnableHTTPS = async (enable) => {\n  const api = await loadApi();\n  await api.setEnableHTTPS(enable);\n};\n\n/**\n * @async 获取 Whistle 的根证书信息\n * @function getRootCA\n * @description\n * 通过 Whistle API 获取当前代理的根证书信息，包括证书内容、私钥等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Buffer>} 返回根证书的 Buffer 对象，可用于保存为 .crt 文件或进行其他操作\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getRootCA()` 调用失败\n */\nconst getRootCA = async () => {\n  const api = await loadApi();\n  return await api.getRootCA();\n};\n\n/**\n * @async 创建一个临时文件并写入指定内容\n * @function createFile\n * @description\n * 通过 Whistle API 创建一个临时文件，并将指定的内容写入该文件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {Buffer|string} data - 要创建的文件内容，可以是 Buffer 或字符串\n * @returns {Promise<string>} 返回创建的文件路径\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.createFile()` 调用失败\n *\n * @example\n * try {\n *   const filePath = await createFile('test');\n *   console.log(`临时文件已创建: ${filePath}`);\n *   // 临时文件已创建: temp/7d2e9a424672279e5bcaab948a7a63fd47cb381626555835e828438762f86a96\n * } catch (err) {\n *   console.error('创建临时文件失败:', err.message);\n * }\n */\nconst createFile = async (data) => {\n  const api = await loadApi();\n  return await api.createFile(data);\n};\n\n/**\n * @async 获取指定文件的内容\n * @function getFile\n * @description\n * 通过 Whistle API 获取指定路径的文件内容，返回其字符串内容。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} filePath - 要读取的文件路径\n * @returns {Promise<string>} 返回文件内容的字符串\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getFile()` 调用失败（如文件不存在）\n */\nconst getFile = async (filePath) => {\n  const api = await loadApi();\n  return await api.getFile(filePath);\n};\n\n/**\n * @async 获取 Whistle 规则引擎的当前状态配置\n * @function getRulesStatus\n * @description\n * 通过 Whistle API 获取规则列表、启用状态以及优先级策略等运行时配置。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<{\n *   laterRulesFirst: boolean,   // 是否启用“后置规则优先”策略（即后匹配的规则覆盖前匹配的）\n *   multiSelect: boolean,       // 是否启用多选模式\n *   pluginsDisabled: boolean,   // 是否全局禁用所有插件规则\n *   disabled: boolean,          // 是否全局禁用 Whistle 代理（所有规则失效）\n *   list: Array<{\n *     name: string,            // 规则文件/配置的名称\n *     selected: boolean        // 当前是否处于激活（勾选）状态\n *   }>\n * }>} 返回包含规则状态和列表的对象\n *\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.getStatus()` 调用失败\n *\n * @example\n * try {\n *   const { list, disabled, multiSelect, laterRulesFirst } = await getRulesStatus();\n *   const activeRules = list.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则状态失败:', err.message);\n * }\n */\nconst getRulesStatus = async () => {\n  const api = await loadApi();\n  return await api.rules.getStatus();\n};\n\n/**\n * @async 关闭 Whistle 规则引擎\n * @function turnOffRules\n * @description\n * 通过 Whistle API 全局禁用所有规则（不包括插件规则）。\n * 该操作会立即生效，影响当前代理会话中的所有请求处理。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOff()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOffRules();\n *   console.log('规则已关闭，所有请求将不再受 Whistle 规则影响');\n * } catch (err) {\n *   console.error('关闭规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOffRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOff();\n};\n\n/**\n * @async 启用 Whistle 规则引擎\n * @function turnOnRules\n * @description\n * 通过 Whistle API 全局启用之前配置的所有规则（不包括插件的规则）。\n * 该操作会立即生效，恢复代理请求的规则处理逻辑。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOn()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOnRules();\n *   console.log('规则已启用，请求将按配置规则处理');\n * } catch (err) {\n *   console.error('启用规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOnRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOn();\n};\n\n/**\n * @async 选中（激活）指定的 Whistle 规则文件，并自动启用规则引擎\n * @function selectRule\n * @description\n * 调用 Whistle API 选中指定名称的规则文件（通常从规则列表中选择），\n * 若选中操作成功（返回 truthy 值），则自动调用 `turnOnRules()` 启用全局规则引擎，\n * 使该规则立即生效。该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要激活的规则文件名称（需与规则列表中的名称精确匹配）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.select()` 调用失败（如网络异常）\n * @throws {Error} 若选中成功但启用规则引擎（`turnOnRules()`）失败时抛出\n *\n * @example\n * try {\n *   await selectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已选中并启用');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst selectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.select(ruleName);\n  if (result) {\n    await turnOnRules();\n  }\n  await result;\n};\n\n/**\n * @async 取消选中（停用）指定的 Whistle 规则文件\n * @function unselectRule\n * @description\n * 调用 Whistle API 取消选中指定名称的规则文件（通常从规则列表中选择），\n * 该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要取消激活的规则文件名称（需与规则列表中的名称精确匹配，如果 ruleName 不填则取消所有规则）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.unselect()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await unselectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已取消选中');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst unselectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.unselect(ruleName);\n  await result;\n};\n\n/**\n * @async 获取指定规则文件的内容详情\n * @function getRule\n * @description\n * 通过 Whistle API 读取指定名称的规则文件内容，返回其详细配置信息。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 规则文件的名称（需与 Whistle 规则列表中的名称精确匹配）\n *\n * @returns {Promise<{\n *   value: string,       // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean,     // 当前是否被选中\n * }>} 返回规则对象，具体字段可能因 Whistle 版本而异\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.get()` 调用失败（如规则名称不存在）\n *\n * @example\n * try {\n *   const rule = await getRule('my-rules');\n *   console.log(`规则的内容:\\n${rule.value}`);\n *   if (rule.selected) {\n *     console.log('该规则当前已激活');\n *   }\n * } catch (err) {\n *   console.error('获取规则失败:', err.message);\n * }\n */\nconst getRule = async (ruleName) => {\n  const api = await loadApi();\n  return await api.rules.get(ruleName);\n};\n\n/**\n * @async 获取当前 Whistle 规则列表\n * @function getRuleList\n * @description\n * 通过 Whistle API 获取所有已配置的规则文件列表，包含每个规则的名称、内容及激活状态。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @returns {Promise<Array<{\n *   name: string,      // 规则文件名称\n *   value: string,     // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean  // 当前是否被选中（激活）\n * }>>} 返回规则对象数组，若无可返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.getList()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   const rules = await getRuleList();\n *   console.log(`共 ${rules.length} 条规则`);\n *   const activeRules = rules.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则列表失败:', err.message);\n * }\n */\nconst getRuleList = async () => {\n  const api = await loadApi();\n  return await api.rules.getList();\n};\n\n/**\n * @async 判断当前 Whistle 是否启用了多选模式\n * @function isMultiSelect\n * @description\n * 通过 Whistle API 检查当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了多选模式\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.isMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   const multiSelect = await isMultiSelect();\n *   console.log(`当前规则引擎是否启用多选模式: ${multiSelect}`);\n * } catch (err) {\n *   console.error('检查多选模式状态失败:', err.message);\n * }\n */\nconst isMultiSelect = async () => {\n  const api = await loadApi();\n  return await api.rules.isMultiSelect();\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用多选模式\n * @function setMultiSelect\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用多选模式\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   await setMultiSelect(true);\n *   console.log('已启用多选模式');\n * } catch (err) {\n *   console.error('设置多选模式失败:', err.message);\n * }\n */\nconst setMultiSelect = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setMultiSelect(enable);\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用后置规则优先\n * @function setLaterRulesFirst\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了后置规则优先。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {boolean} enable - 是否启用后置规则优先\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setLaterRulesFirst()` 调用失败\n *\n * @example\n * try {\n *   await setLaterRulesFirst(true);\n *   console.log('已启用后置规则优先');\n * } catch (err) {\n *   console.error('设置后置规则优先失败:', err.message);\n * }\n */\nconst setLaterRulesFirst = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setLaterFirst(enable);\n};\n\n/**\n * @async 添加一个新的规则文件到 Whistle 规则引擎中\n * @function addRule\n * @description\n * 通过 Whistle API 添加一个新的规则文件，并可选择是否立即选中（激活）该规则。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} name - 规则名称\n * @param {string} value - 规则内容\n * @param {boolean} selected - 是否选中\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.add()` 调用失败\n *\n * @example\n * try {\n *   await addRule('my-rule', '* www.example.com 127.0.0.1', true);\n *   console.log('已添加规则');\n * } catch (err) {\n *   console.error('添加规则失败:', err.message);\n * }\n */\nconst addRule = async (name, value, selected) => {\n  const api = await loadApi();\n  await api.rules.add(name, value, selected);\n};\n\n/**\n * @async 将指定规则文件移动到规则列表顶部\n * @function moveRuleToTop\n * @description\n * 通过 Whistle API 将指定规则文件移动到规则列表的顶部。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} ruleName - 规则文件的名称（需与规则列表中的名称精确匹配）\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.moveToTop()` 调用失败\n *\n * @example\n * try {\n *   await moveRuleToTop('my-rule');\n *   console.log('已将规则移动到顶部');\n * } catch (err) {\n *   console.error('移动规则失败:', err.message);\n * }\n */\nconst moveRuleToTop = async (ruleName) => {\n  const api = await loadApi();\n  await api.rules.moveToTop(ruleName);\n};\n\n/**\n * @async 获取 Whistle 的 Values 列表\n * @function getValueList\n * @description\n * 通过 Whistle API 获取当前配置的 Values 列表，返回每个 Value 的名称和值。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Array<{ name: string, value: string }>>} 返回 Values 对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.getList()` 调用失败\n *\n * @example\n * try {\n *   const values = await getValueList();\n *   console.log(`共 ${values.length} 个 Values`);\n *   values.forEach(v => console.log(`${v.name}: ${v.value}`));\n * } catch (err) {\n *   console.error('获取 Values 列表失败:', err.message);\n * }\n */\nconst getValueList = async () => {\n  const api = await loadApi();\n  return await api.values.getList();\n};\n\n/**\n * @async 获取指定的 Value\n * @function getValue\n * @description\n * 通过 Whistle API 获取指定的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @returns {Promise<{ name: string, value: string }>} 返回 Value 对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.get()` 调用失败\n *\n * @example\n * try {\n *   const value = await getValue('my-value');\n *   console.log(`${value.name}: ${value.value}`);\n * } catch (err) {\n *   console.error('获取 Value 失败:', err.message);\n * }\n */\nconst getValue = async (name) => {\n  const api = await loadApi();\n  return await api.values.get(name);\n};\n\n/**\n * @async 添加一个新的 Value\n * @function addValue\n * @description\n * 通过 Whistle API 添加一个新的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @param {string} value - Value 的值\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.add()` 调用失败\n *\n * @example\n * try {\n *   await addValue('my-value', 'some-value');\n *   console.log('已添加 Value');\n * } catch (err) {\n *   console.error('添加 Value 失败:', err.message);\n * }\n */\nconst addValue = async (name, value) => {\n  const api = await loadApi();\n  await api.values.add(name, value);\n};\n\n/**\n * @async 获取 Whistle 插件的状态信息\n * @function getPluginsStatus\n * @description\n * 通过 Whistle API 获取当前已安装插件的状态信息，包括每个插件的名称、启用状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{ name: string, moduleName: string, version: string, selected: boolean }>>} 返回插件状态对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getStatus()` 调用失败\n * \n * @example\n * try {\n *   const plugins = await getPluginsStatus();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => console.log(`${p.name}: ${p.selected ? '启用' : '禁用'}`));\n * } catch (err) {\n *   console.error('获取插件状态失败:', err.message);\n * }\n */\nconst getPluginsStatus = async () => {\n  const api = await loadApi();\n  return await api.plugins.getStatus();\n};\n\n/**\n * @async 启用 Whistle 插件\n * @function turnOnPlugins\n * @description\n * 通过 Whistle API 启用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOn()` 调用失败\n * \n * @example\n * try {\n *   await turnOnPlugins();\n *   console.log('已启用所有插件');\n * } catch (err) {\n *   console.error('启用插件失败:', err.message);\n * }\n */\nconst turnOnPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOn();\n};\n\n/**\n * @async 禁用 Whistle 插件\n * @function turnOffPlugins\n * @description\n * 通过 Whistle API 禁用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOff()` 调用失败\n * \n * @example\n * try {\n *   await turnOffPlugins();\n *   console.log('已禁用所有插件');\n * } catch (err) {\n *   console.error('禁用插件失败:', err.message);\n * }\n */\nconst turnOffPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOff();\n};\n\n/**\n * @async 获取 Whistle 插件列表\n * @function getPluginList\n * @description\n * 通过 Whistle API 获取当前已安装的所有插件信息，包括插件元数据、版本、描述、规则配置及激活状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>>} 返回插件对象数组，若无插件则返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getList()` 调用失败\n *\n * @example\n * try {\n *   const plugins = await getPluginList();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => {\n *     console.log(`${p.name} (${p.version}): ${p.selected ? '启用' : '禁用'}`);\n *     console.log(`  描述: ${p.description}`);\n *   });\n * } catch (err) {\n *   console.error('获取插件列表失败:', err.message);\n * }\n */\nconst getPluginList = async () => {\n  const api = await loadApi();\n  return await api.plugins.getList();\n};\n\n/**\n * @async 获取指定的 Whistle 插件\n * @function getPlugin\n * @description\n * 通过 Whistle API 获取指定的插件信息。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>} 返回插件对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.get()` 调用失败\n * \n * @example\n * try {\n *   const plugin = await getPlugin('my-plugin');\n *   console.log(`${plugin.name}: ${plugin.enabled ? '启用' : '禁用'}`);\n * } catch (err) {\n *   console.error('获取插件失败:', err.message);\n * }\n */\nconst getPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.get(name);\n};\n\n/**\n * @async 选择指定的 Whistle 插件\n * @function selectPlugin\n * @description\n * 通过 Whistle API 选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.select()` 调用失败\n * \n * @example\n * try {\n *   const result = await selectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已选择并启用');\n *   } else {\n *     console.log('插件选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('选择插件失败:', err.message);\n * }\n */\nconst selectPlugin = async (name) => {\n  const api = await loadApi();\n  const result = await api.plugins.select(name);\n  if (result) {\n    await turnOnPlugins();\n  }\n  return result;\n};\n\n/**\n * @async 取消选择指定的 Whistle 插件\n * @function unselectPlugin\n * @description\n * 通过 Whistle API 取消选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回取消选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.unselect()` 调用失败\n * @example\n * try {\n *   const result = await unselectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已取消选择并禁用');\n *   } else {\n *     console.log('插件取消选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('取消选择插件失败:', err.message);\n * }\n */\nconst unselectPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.unselect(name);\n};\n\nconst URLENCODED_RE = /application\\/x-www-form-urlencoded/i;\nconst POST_RE = /^post$/i;\nconst isUrlEncoded = (req) => {\n  var type = req.headers?.['content-type'];\n  if (!type || !POST_RE.test(req.method)) {\n    return;\n  }\n  return URLENCODED_RE.test(type);\n};\n\nconst formatData = (session, utils, isRes) => {\n  let data = session[isRes ? 'res' : 'req'];\n  let body = data.body || '';\n  let raw;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      raw = raw || (isRes ? utils.getRawRes(data) : utils.getRawReq(data));\n      body = raw ? raw.substring(raw.indexOf('\\r\\n\\r\\n') + 4) : '';\n    }\n    return body;\n  };\n\n  Object.defineProperties(data, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        if (json) {\n          return json;\n        }\n        getBody();\n        if (body) {\n          if (!isRes && isUrlEncoded(data)) {\n            json = qs.parse(body);\n          } else {\n            try {\n              json = JSON.parse(body);\n            } catch (e) {}\n          }\n        }\n        json = json || {};\n        return json;\n      }\n    },\n    raw: {\n      configurable: true,\n      get() {\n        if (raw == null) {\n          raw = isRes ? utils.getRawRes(data) : utils.getRawReq(data);\n        }\n        return raw;\n      }\n    },\n  });\n};\n\nconst formatSession = (session, utils) => {\n  let timings;\n  let rules;\n  Object.defineProperties(session, {\n    timings: {\n      configurable: true,\n      get() {\n        timings = timings || utils.getTimings(session);\n        return timings;\n      }\n    },\n    rawRules: {\n      configurable: true,\n      get() {\n        rules = rules || utils.getRules(session);\n        return rules.join('\\n');\n      }\n    },\n  });\n  formatData(session, utils);\n  formatData(session, utils, false);\n  return session;\n};\n\n/**\n * @async 获取抓包数据（支持自动分页拉取）\n * @function getSessions\n * @description\n * 用于按条件检索 HTTP/WebSocket 等协议的抓包记录。支持基于游标、时间、资源类型、URL 关键词、请求方法、状态码以及请求/响应头的组合筛选。\n *\n * **过滤逻辑**：\n * - 所有非空条件均以 **AND** 关系叠加（即需同时满足）；\n * - 对于 `method` 和 `statusCode`，传入多个值时采用 **OR** 关系（匹配任一即可）；\n * - `latest` 优先级最高，一旦为 `true`，count 最多只返回 120 条最新记录；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n * - `reqHeader` 和 `resHeader` 为对象类型，支持按请求头/响应头的字段名和值进行子串匹配。\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新数据末尾。\n * - 分页游标自动使用上一页最后一条记录的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count` 和任何游标/时间参数。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新数据开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的数据通常为空。如需获取大量历史数据，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {number} [options.count=3600] - 期望获取的最大记录数。实际返回数量 ≤ 此值，且受每页 120 条限制及数据实际数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的记录。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（通常为记录的自增主键或唯一序号，格式如 `\"1784905385300-003\"`）。\n *                                            获取记录 ID **大于**此值的增量数据（即更新的数据）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级）。\n *                                       获取记录生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.type] - 资源类型过滤，同抓包界面 Network 底部过滤栏选项（不区分大小写）。\n *                                  支持以下枚举值（传入其它字符串表示不过滤）：\n *                                  - `JSON`：JSON 请求/响应\n *                                  - `HTML`：HTML 文档\n *                                  - `CSS`：样式表\n *                                  - `JS`：JavaScript 脚本\n *                                  - `Font`：字体文件\n *                                  - `Img`：图片资源\n *                                  - `Media`：音视频媒体\n *                                  - `WS`：WebSocket 帧数据\n *                                  - `Tunnel`：隧道连接（如 WebSocket 升级）\n *                                  - `Wasm`：WebAssembly 模块\n *                                  - `Mock`：Mock 数据\n *                                  - `Rules`：匹配规则的请求\n *                                  - `Import`：导入的数据\n *                                  - `Composer`：通过 Composer 发送的请求\n *                                  - `Error`：错误请求（状态码 >= 400、captureError、中断的请求等）\n *                                  - `captureError`：解析 HTTPS 失败的 Tunnel 请求\n * @param {string} [options.subUrl] - URL 中包含该字符串的请求（不区分大小写）。\n *                                    例如 `\"/api/user\"` 将匹配所有包含该路径的 URL。\n * @param {string|string[]} [options.method] - HTTP 请求方法，支持多个（数组或逗号分隔字符串，如 `\"GET,POST\"`，不区分大小写）。\n *                                             匹配任一方法即满足条件。\n * @param {number|number[]} [options.statusCode] - HTTP 响应状态码，支持多个（数组或逗号分隔字符串，如 `\"200,404\"`）。\n *                                                 匹配任一状态码即满足条件。\n * @param {Object} [options.reqHeader] - 请求头过滤条件。\n * @param {string} options.reqHeader.name - 请求头字段名称（如 `\"X-Request-Id\"`，不区分大小写）。\n * @param {string} [options.reqHeader.subValue] - 可选，请求头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该请求头字段（无论值为何）。\n * @param {Object} [options.resHeader] - 响应头过滤条件。\n * @param {string} options.resHeader.name - 响应头字段名称（如 `\"Content-Type\"`，不区分大小写）。\n * @param {string} [options.resHeader.subValue] - 可选，响应头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该响应头字段（无论值为何）。\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的抓包记录数组（经过 `formatSession` 格式化处理）。\n *                                    每条记录包含以下主要字段（可能因 `formatSession` 处理而略有增减）：\n *                                    - `id` (string): 记录唯一标识，格式为 `\"时间戳-序号\"`（如 `\"1785144349945-573\"`）\n *                                    - `startTime` (number): 请求开始时间戳（Unix 毫秒级）\n *                                    - `url` (string): 完整请求 URL\n *                                    - `req` (Object): 请求详情\n *                                      - `method` (string): 请求方法\n *                                      - `ip` (string): 客户端 IP\n *                                      - `port` (string): 客户端端口\n *                                      - `headers` (Object): 请求头键值对\n *                                      - `json` (Object): 解析后的 JSON 请求体（若存在）\n *                                      - `body` (string): 原始请求体（可能为空）\n *                                      - `raw` (string): 原始请求数据\n *                                    - `res` (Object): 响应详情\n *                                      - `ip` (string): 服务器 IP\n *                                      - `port` (string): 服务器端口\n *                                      - `statusCode` (number): HTTP 状态码\n *                                      - `headers` (Object): 响应头键值对\n *                                      - `json` (Object): 解析后的 JSON 响应体（若存在）\n *                                      - `body` (string): 原始响应体（可能为空）\n *                                      - `raw` (string): 原始响应数据\n *                                    - `timings` (Object): 请求各阶段耗时（单位：毫秒）\n *                                      - `start` (number): 请求开始的时间戳（相对于某个基准点）\n *                                      - `ttfb` (number | null): 首字节时间\n *                                      - `dns` (number | null): DNS 解析耗时\n *                                      - `connect` (number | null): TCP 连接耗时（从 DNS 结束到连接建立）\n *                                      - `request` (number | null): 请求发送耗时（从 DNS 结束到请求结束）\n *                                      - `response` (number | null): 响应接收耗时（从请求结束到接收到响应头）\n *                                      - `download` (number | null): 下载耗时（从接收响应头到接收完响应体）\n *                                      - `total` (number | null): 总耗时\n *                                    - `rules` (Object): 匹配的规则对象\n *                                    - `rawRules` (string): 原始规则字符串\n *                                    - `version` (string): Whistle 版本号\n *                                    - `nodeVersion` (string): Node.js 版本号\n *                                    - `dnsTime` (number): DNS 完成时间戳\n *                                    - `requestTime` (number): 请求发送完成时间戳\n *                                    - `connectTime` (number): 连接建立完成时间戳\n *                                    - `responseTime` (number): 响应头接收完成时间戳\n *                                    - `endTime` (number): 请求结束时间戳\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误，需确保 whistle 已全局安装）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.network.getSessions()` 调用失败（如网络异常、参数错误）\n * @throws {Error} 底层 API 调用可能抛出其他运行时错误（如超时、数据格式异常）\n *\n * @example\n * // 使用游标增量拉取（最多 100 条）\n * const sessions = await getSessions({ startId: '1784905385300-003', count: 100 });\n *\n * @example\n * // 获取最新的 JSON 类型记录（最多 120 条）\n * const sessions = await getSessions({ latest: true, type: 'JSON' });\n *\n * @example\n * // 组合过滤：查询状态码为 200 或 304 的 GET 请求，且 URL 包含 \"/v1\"，同时要求存在特定请求头\n * const sessions = await getSessions({\n *   subUrl: '/v1',\n *   method: 'GET',\n *   statusCode: [200, 304],\n *   reqHeader: { name: 'X-Token', subValue: 'abc' },\n *   count: 50\n * });\n */\nconst getSessions = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const sessions = await api.network.getSessions(options);\n    const len = sessions.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatSession(sessions[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = sessions[len - 1]?.id;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n\nconst formatFrame = (frame, utils) => {\n  let body;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      body = utils.getText(frame);\n    }\n    return body;\n  };\n  Object.defineProperties(frame, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        getBody();\n        if (body) {\n          try {\n            json = JSON.parse(getBody());\n          } catch (e) {}\n        }\n        json = json || {};\n        return json;\n      },\n    }\n  });\n  return frame;\n}\n\n/**\n * @async 获取 WebSocket/Socket 的帧数据（支持自动分页拉取）\n * @function getFrames\n * @description\n * 按条件检索指定连接（通过 `reqId` 关联）的 WebSocket/Socket 通信帧，支持基于游标、时间、来源方向的组合筛选。\n *\n * **过滤逻辑**：\n * - `from` 参数用于过滤帧的发送方向：`client`（客户端→服务端）、`server`（服务端→客户端），不传则返回全部方向；\n * - `latest` 优先级最高，一旦为 `true`，count 最大为 120；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条帧记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新帧末尾。\n * - 分页游标自动使用上一页最后一条帧的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count`。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新帧开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的帧通常为空。如需获取大量历史帧，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {string} options.reqId - 会话 ID，对应抓包记录的 `id` 字段，用于关联帧所属的连接\n * @param {number} [options.count=3600] - 期望获取的最大帧数。实际返回数量 ≤ 此值，且受每页 120 条限制及实际帧数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的帧。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（帧的唯一序号），获取帧 ID **大于**此值的增量数据（即更新的帧）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级），获取帧生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.from] - 帧的来源方向过滤（不区分大小写）：\n *                                   - `'client'`：仅获取客户端→服务端的帧\n *                                   - `'server'`：仅获取服务端→客户端的帧\n *                                   - 不传或传入其他值：获取该连接的所有帧\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的帧数据数组（经过 `formatFrame` 格式化处理）。\n *                                    每条帧包含以下主要字段：\n *                                    - `reqId` (string): 所属会话 ID，对应抓包记录的 `id`\n *                                    - `frameId` (string): 帧数据的唯一标识 ID（可用于游标）\n *                                    - `mask` (boolean): 是否为掩码帧（WebSocket 掩码）\n *                                    - `compressed` (boolean): 帧数据是否经过压缩\n *                                    - `length` (number): 帧数据的原始长度（字节数）\n *                                    - `opcode` (number): 帧操作码，`1` 表示文本帧，`2` 表示二进制帧\n *                                    - `base64` (string): Base64 编码的帧数据（用于二进制或大文本）\n *                                    - `body` (string): 解码后的帧内容（文本或可读字符串）\n *                                    - `json` (Object): 若帧数据为 JSON 格式，则解析为对象，否则为空对象 `{}`\n *\n * @throws {Error} 依赖 `loadApi` 可能抛出的错误（如 Whistle 未安装、版本过低、服务未启动）\n * @throws {Error} 底层 `api.network.getFrames()` 调用失败（如 `reqId` 不存在、参数错误、网络异常）\n *\n * @example\n * // 使用游标增量拉取，获取客户端发出的前 10 条帧\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   startTime: 1784903620156,\n *   count: 10,\n *   from: 'client'\n * });\n *\n * @example\n * // 获取最新的帧（最多 120 条）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   latest: true\n * });\n *\n * @example\n * // 从指定游标开始拉取所有后续帧（直到最新）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   count: 3600\n * });\n */\nconst getFrames = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const frames = await api.network.getFrames(options);\n    const len = frames.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatFrame(frames[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = frames[len - 1]?.frameId;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n```\n","docs/docs/extensions/dev.md":"# 插件开发\n您已了解插件的各项功能特性（参考[使用文档](./usage)），现在我们将通过模块化方式演示具体实现。\n> 每个核心功能作为独立插件实现，保持单一职责原则，实际开发中可自由拼装这些功能模块。\n\n## 准备工作\n1. 创建一个空目录，如\n   ``` sh\n    mkdir examples && cd examples\n   ```\n2. 安装脚手架 [lack](https://github.com/avwo/lack)\n   ``` txt\n    npm i -g lack\n   ```\n   > lack 必须用最新版本（`>= 1.4.0`）\n3. 更新 [Whistle](https://github.com/avwo/whistle)\n   > Whistle 必须用最新版本（`>= 2.9.100`，[客户端](https://github.com/avwo/whistle-client) `>= 1.3.8`）\n\n## rules.txt\n- 插件安装后会自动加载生效\n- 功能跟界面 Rules 配置的规则一样，优先级低于界面 Rules 规则\n- 插件被禁用立即失效\n\n**示例**\n1. 创建包含 `rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules && cd whistle.test-rules\n\n    # 初始化包含 rules.txt 的插件\n    lack init rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `rules.txt` 文件：\n    ``` txt\n    * reqHeaders://x-client=whistle.test-rules\n    ```\n3. 所有经过 Whistle 的请求都会自动添加请求头 `x-client: whistle.test-rules`\n   \n   <img src=\"/img/test-rules.png\" width=\"600\" />\n4. 如果规则修改后未生效？请确认：\n    - 是否已执行 `lack watch`\n    - 是否跟界面配置冲突规则\n    - 插件是否被禁用\n\n## _rules.txt\n- 仅对匹配插件协议的请求生效\n- 支持长协议(whistle.myplugin://)和短协议(myplugin://)\n- 作用于请求处理阶段\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n1. 创建包含 `_rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-req-rules && cd whistle.test-req-rules\n\n    # 初始化包含 _rules.txt 的插件\n    lack init _rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-req-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `_rules.txt` 文件：\n    ``` txt\n    * file://(hello)\n    ```\n    > 所有请求返回响应内容 `hello`\n4. 直接访问 `https://www.example.com/test` 返回正常页面\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/test whistle.test-req-rules://\n    ```\n    后再访问 `https://www.example.com/test` 返回 `hello`\n   \n## resRules.txt\n- 仅对匹配插件协议的请求生效\n- 作用于响应处理阶段\n- 可修改响应状态码和内容\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n\n1. 创建包含 `resRules.txt` 规则文件的插件：\n   ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules && cd whistle.test-res-rules\n\n    # 初始化包含 resRules.txt 的插件\n    lack init resRules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `resRules.txt` 文件：\n    ``` txt\n    * resBody://`(whistle_error_${statusCode})` includeFilter://s:500 includeFilter://s:404\n    ```\n    > 把响应状态码为 `404` 和 `500` 的请求的响应内容改为 `whistle_error_404` 和  `whistle_error_500`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/500 whistle.test-req-rules:// statusCode://500\n    https://www.example.com/404 whistle.test-req-rules:// statusCode://404\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/500` 响应内容被改成 `whistle_error_500`\n      - 访问 `https://www.example.com/404` 响应内容被改成 `whistle_error_404`\n      - 访问 `https://www.example.com/test` 则返回原始内容\n\n## auth\n对经过 Whistle 的请求进行鉴权。\n\n**示例**\n\n1. 创建请求鉴权插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-auth && cd whistle.test-auth\n\n    # 初始化包含 auth 的插件\n    lack init auth\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-auth` 插件\n    lack watch\n    ```\n2. 编辑 `src/auth.ts` 文件：\n    ``` ts\n    export default async (req: Whistle.PluginAuthRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // URL 里面包含 `/test/forbidden` 的响应状态码为 403\n      if (fullUrl.includes('/test/forbidden')) {\n        return false;\n      }\n      // URL 里面包含 `/test/message/forbidden` 的响应状态码为 403，且自定义响应内容\n      if (fullUrl.includes('/test/message/forbidden')) {\n        req.setHtml('<strong>Access Denied</strong>');\n        return false;\n      }\n\n      // URL 里面包含 `/test/login` 要求用户输入用户名和密码\n      if (fullUrl.includes('/test/login')) {\n        const auth = req.headers.authorization || req.headers['proxy-authorization'];\n        if (auth) {\n          // TODO: 校验用户名和密码，如果正确返回 true，否则返回 false\n          return true;\n        }\n        req.setLogin(true);\n        return false;\n      }\n\n      // URL 里面包含 `/test/redirect` 的响应状态码为 302，且重定向到 `https://www.example.com/test`\n      if (fullUrl.includes('/test/redirect')) {\n        req.setRedirect('https://www.example.com/test');\n        return false;\n      }\n      // 其它请求直接放过\n      // 如果需要添加自定义请求头，可以使用 `req.setHeader` 方法\n      // 支持添加 key 前缀为 `x-whistle-` 的请求头\n      // 例如：req.setHeader('x-whistle-xxx', 'value');\n      req.setHeader('x-whistle-custom-header', 'lack');\n      return true;\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com whistle.auth://\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/test/forbidden` 响应状态码 `403`，响应内容 `Forbidden`\n      - 访问 `https://www.example.com/test/message/forbidden` 响应状态码 `403`，响应内容 `<strong>Access Denied</strong>`\n      - 访问 `https://www.example.com/test/login` 弹出浏览器的登录框\n      - 访问 `https://www.example.com/test/redirect` 重定向到 `https://www.example.com/test`\n      - 其它 `www.example.com` 的请求正常，且被添加请求头 `x-whistle-custom-header: lack`\n\n## sniCallback\n通过插件动态下发请求证书。\n\n**示例**\n\n1. 创建自定义证书插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-sni\n\n    # 初始化包含 sni 的插件\n    lack init sni\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-sni` 插件\n    lack watch\n    ```\n2. 编辑 `src/sniCallback.ts` 文件：\n    ``` ts\n    const key = `...`;\n    const cert = `...`;\n\n    // sniCallback 插件处理函数 - 根据请求 URL 动态决定 TLS 隧道处理方式\n    export default async (req: Whistle.PluginSNIRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // 特殊域名返回 false，保持 TUNNEL 状态（不解除 TLS 加密）\n      if (fullUrl === 'https://tunnel.example.com') {\n        return false;\n      }\n\n      // 对特定域名使用自定义证书解密，返回一个包含 key 和 cert 的对象\n      // 支持 .crt、.pem、.cer 等格式的证书\n      if (fullUrl === 'https://custom.example.com') {\n        return { key, cert };\n      }\n      // 默认使用 Whistle 内置证书解密 TLS 流量\n      return true;\n    };\n    ```\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com sniCallback://test-sni\n    ```\n6.  效果：\n   - 访问 `https://tunnel.example.com/path`，请求保持 TUNNEL 状态（不解除 TLS 加密）\n   - 访问 `https://custom.example.com/test/` 报证书出错\n   - 其它请求正常\n\n## rulesServer\n通过 JavaScript 编程实时生成规则，规则功能跟上面的 _rules.txt 静态规则一样，但优先级高于 _rules.txt。\n\n**示例**\n\n1. 创建包含 `rulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-rules-server\n\n    # 初始化包含 rulesServer 的插件\n    lack init rulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/rulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end(JSON.stringify({\n          values: {\n            'whistle.test-rules-server/a.html': 'test normal values',\n          },\n          rules: `\n            \\`\\`\\` whistle.test-rules-server/b.html\n            test inject values\n            \\`\\`\\`\n\n            */a file://{whistle.test-rules-server/a.html}\n            */b file://{whistle.test-rules-server/b.html}\n          `,\n        }));\n      });\n    };\n    ```\n    > 支持响应纯 rules 文本  `res.end(rules)`，或包含 rules & values 的序列化对象 `res.end(JSON.stringify({rules, values}))`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com whistle.test-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/a` 返回 `test inject values`\n   - 访问 `https://test.example.com/b` 返回 `test normal values`\n\n## tunnelRulesServer\ntunnelRulesServer 是专门用于处理 TUNNEL 请求的动态规则生成机制，其核心特性与常规 [rulesServer](#rulesserver) 相同，但应用场景有专门区分：\n1. tunnelRulesServer 仅对 `tunnel://domain:port` 隧道请求生效\n2. rulesServer 对 `HTTP[S]/WebSocket` 请求生效\n\n## resRulesServer\n请求的响应阶段通过 JavaScript 编程实时生成规则（可作用于所有类型的请求），规则功能跟上面的 resRules.txt 静态规则一样，但优先级高于 resRules.txt。\n\n**示例**\n\n1. 创建包含 `resRulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules-server && cd whistle.test-res-rules-server\n\n    # 初始化包含 resRulesServer 的插件\n    lack init resRulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/resRulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end('* resBody://(test-res-rules-server)');\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com/res whistle.test-res-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/res/path` 返回 `test-res-rules-server`\n\n\n## statsServer\n如果仅想获取请求的 URL、请求方法、请求头、请求内容等信息，而不对请求做任何操作，可以用 statsServer。\n\n**示例**\n\n1. 创建包含 `statsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-stats-server && cd whistle.test-stats-server\n\n    # 初始化包含 statsServer 的插件\n    lack init statsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-stats-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/statsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Request Headers:', originalReq.headers);\n        // 获取请求的 body\n        req.getReqSession((reqSession) => {\n          if (reqSession) {\n            console.log('Request Body:', reqSession.req.body);\n          }\n        });\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/stats whistle.test-stats-server://test\n    ```\n4.  效果：\n   - 访问 `https://www.example.com/stats` 控制台输出：\n      ``` sh\n      Value: test\n      URL: https://www.example.com/stats\n      Method: GET\n      Request Headers: {\n        host: 'www.example.com',\n        'cache-control': 'max-age=0',\n        'sec-ch-ua': '\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"',\n        'sec-ch-ua-mobile': '?0',\n        'sec-ch-ua-platform': '\"macOS\"',\n        'upgrade-insecure-requests': '1',\n        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',\n        accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',\n        'sec-fetch-site': 'cross-site',\n        'sec-fetch-mode': 'navigate',\n        'sec-fetch-user': '?1',\n        'sec-fetch-dest': 'document',\n        'accept-encoding': 'gzip, br',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',\n        priority: 'u=0, i',\n        'x-forwarded-for': '127.0.0.1',\n        connection: 'close'\n      }\n      Request Body: \n      ```\n\n## resStatsServer\n如果获取请求的响应状态码、响应头、响应内容等信息，而不对请求做任何操作，可以用 resStatsServer。\n\n**示例**\n\n1. 创建包含 `resStatsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-stats-server && cd whistle.test-res-stats-server\n\n    # 初始化包含 resStatsServer 的插件\n    lack init resStatsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-stats-server` 插件\n    lack watch\n    ```\n2. 安装依赖\n    ``` sh\n    npm i\n    ```\n3. 编辑 `src/resStatsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq, originalRes } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Server IP', originalRes.serverIp);\n        console.log('Status Code:', originalRes.statusCode);\n        console.log('Response Headers:', originalReq.headers);\n        // 获取请求的完整抓包数据\n        req.getSession((reqSession) => {\n          if (reqSession) {\n            console.log('Response Body:', reqSession.res.body);\n          }\n        });\n      });\n    };\n    ```\n4. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/res/stats whistle.test-res-stats-server://testResStats\n    ```\n5.  效果：\n   - 访问 `https://www.example.com/res/stats` 控制台输出：\n      ``` txt\n      Value: testResStats\n      URL: https://www.example.com/res/stats\n      Method: GET\n      Server IP 127.0.0.1\n      Status Code: 200\n      Response Headers: {\n        'accept-ranges': 'bytes',\n        'content-type': 'text/html',\n        etag: '\"84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134\"',\n        'last-modified': 'Mon, 13 Jan 2025 20:11:20 GMT',\n        server: 'AkamaiNetStorage',\n        expires: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'cache-control': 'max-age=0, no-cache, no-store',\n        pragma: 'no-cache',\n        date: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'alt-svc': 'h3=\":443\"; ma=93600,h3-29=\":443\"; ma=93600',\n        'x-forwarded-for': '127.0.0.1',\n        host: 'www.example.com',\n        connection: 'close'\n      }\n      Response Body: <!doctype html>\n      <html>\n      <head>\n          <title>Example Domain</title>\n\n      ......\n      ```\n\n## server\n可以将插件作为 **server** 使用，由它接收并转发请求。插件可以直接响应请求，或在处理后继续转发至目标服务器。目标服务器返回响应后，插件可以进一步处理，最终将结果返回给浏览器或客户端。\n\n**示例**\n\n1. 创建包含 `server` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-server && cd whistle.test-server\n\n    # 初始化包含 server 的插件\n    lack init server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/server.ts` 文件：https://github.com/whistle-plugins/examples/tree/master/whistle.test-server\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json test-server://setCookieFromBody\n    ```\n4.  访问 `https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json`\n    - 浏览器 Cookie 新增 `test-name=whistle`\n    - 响应内容新增 `\"pluginName\":\"whistle\"`\n\n\n## pipe\n如果请求/响应内容被加密，或者需要转成特定格式显示在抓包界面，可以用 pipe 将请求/响应内容交给插件处理。\n> 在抓包界面中：\n> \n> 请求数据中无法看到 `reqWrite` 修改后的内容\n> \n> 响应数据中无法看到 `resWrite` 修改后的内容（这是预期行为）\n> \n> 所有 `xxxRexWrite` 操作的实际修改内容都不会显示在抓包界面\n> \n\n#### HTTP/HTTPS 协议实现\n1. 创建 pipe http 插件:\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-http && cd whistle.test-pipe-http\n\n    # 初始化包含 pipe http 的插件\n    lack init pipeHttp,server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-http` 插件\n    lack watch\n    ```\n4. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-http\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/test-pipe-http pipe://test-pipe-http test-pipe-http://mirror\n    ```\n    > `test-pipe-http://mirror` 的作用是客户端请求什么内容，服务端就响应什么内容\n6. Whistle / Network / Composer 输入 `www.example.com/test-pipe-http`，选择 `POST` 方法，Body 输入 `test-pipe-http`\n    <img src=\"/img/pipe-http.png\" width=\"400\" />\n7.  点击发送按钮，查看返回的 Body 如下\n    <img src=\"/img/pipe-http-result.png\" width=\"400\" />\n   \n\n#### WebSocket 协议实现\n1. 创建 pipe websocket 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-ws && cd whistle.test-pipe-ws\n\n    # 初始化包含 pipe websocket 的插件\n    lack init pipeWs\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-ws` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-ws\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    wss://echo.websocket.org/ pipe://test-pipe-ws\n    ```\n4.  打开页面 `https://echo.websocket.org/.ws` 可以看到如下效果：\n   <img src=\"/img/pipe-ws.png\" width=\"520\" />\n   \n\n\n#### TCP 隧道实现\n1. 创建 pipe tunnel 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-tunnel && cd whistle.test-pipe-tunnel\n\n    # 初始化包含 pipe tunnel 的插件\n    lack init pipeTunnel.server\n\n    # 安装依赖\n    npm i --save-dev lack-proxy && npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-tunnel` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-tunnel\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test-pipe-tunnel.example.com pipe://test-pipe-tunnel test-pipe-tunnel://mirror\n    ```\n4. 执行 `whistle.test-pipe-tunnel` 根目录的 `test.js` 文件：\n    ``` sh\n    node test.js\n    ```\n5. 控制台输出的内容\n    <img src=\"/img/pipe-tunnel.png\" width=\"560\" />\n\n## 插件操作界面\n在 Whistle 插件列表中，点击 `Option` 或插件名称可快速打开插件操作界面，支持以下三种模式：\n1. **Tab 模式**：在 Whistle 界面内以标签页形式打开（默认方式）\n2. **对话框模式**：在 Whistle 界面内以弹窗形式打开\n3. **新标签模式**：在浏览器新标签页或客户端新窗口中打开\n\n---\n\n### 1. Tab 模式（默认）\n**特性**  \n- 插件管理界面默认地址为：`http[s]://domain[:port]/plugin.xxx/`  \n- 通过插件列表的 `Option` 或插件名称直接打开  \n\n**禁用 Tab 模式**  \n若需隐藏此打开方式，可在 `whistleConfig` 中配置：  \n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"noOption\": true // 禁用默认入口\n    ...\n  }\n  ...\n}\n```\n\n---\n\n### 2. 对话框模式\n**特性**  \n- 支持通过弹窗形式打开管理界面  \n- 弹窗页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/modal.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n**配置示例**  \n```js\n{\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {\n      \"width\": 360,  // 弹窗宽度（px）\n      \"height\": 566  // 弹窗高度（px）\n    }\n    ...\n  }\n}\n```\n\n---\n\n### 3. 新标签模式\n**场景**  \n需使用外部页面作为插件管理界面时使用。  \n\n**基础配置**  \n```js\n{\n   \"pluginHomepage\": \"https://your-external-page.com\" // 外部页面地址\n}\n```\n\n**高级选项**  \n若需将外部链接强制在 Whistle 内打开（Tab 或对话框模式）：  \n\n**方式一：对话框模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {  // 强制以弹窗打开\n      \"width\": 360,\n      \"height\": 566\n    }\n    ...\n  }\n  ...\n}\n```\n\n**方式二：Tab 模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInPlugins\": true  // 强制在插件Tab页打开\n    ...\n  }\n  ...\n}\n```\n \n## 完整 Hooks API\n参考：https://github.com/avwo/lack/blob/master/assets/ts/src/types/global.d.ts\n\n## 插件变量配置{#pluginvars}\n支持通过 `%` 符号在规则里面配置，详见：[`%` 符号用途](../rules/plugin-vars)\n\n## 自定义规则补全功能{#rules-hint}\n综上所述，每个插件可以扩展以下规则：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\nsinCallback://myplugin(sinValue)\npipe://myplugin(pipeValue)\n```\n\nWhistle 还支持对以下规则自定义补全功能：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\n```\n\n**示例**\n1. 创建 rules hints 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules-hint && cd whistle.test-rules-hint\n\n    # 初始化包含 uiServer 的插件\n    lack init uiServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-hint` 插件\n    lack watch\n    ```\n5. 在 `package.json` 配置 `whistleConfig`，新增 `pluginVars` 和 `hintUrl`：\n   ``` js\n    {\n      ...\n      \"whistleConfig\": {\n        \"pluginVars\": {\n          ...\n          \"hintUrl\": '/cgi-bin/plugin-vars'\n          ...\n        },\n        \"hintUrl\": '/cgi-bin/get-hints'\n      },\n      ...\n    }\n   ```\n6. 编辑 `src/uiServer/router.ts` 文件：\n    ``` ts\n    export default (router: Router) => {\n      // 针对 `%test-rules-hint[.key]=xxx`\n      router.get('/cgi-bin/plugin-vars', (ctx) => {\n        const { sep, value } = ctx.query;\n        const isKey = sep === '.';\n        let key = '';\n        let keyword = '';\n        if (value && typeof value === 'string') {\n          if (isKey) {\n            const index = value.indexOf('=');\n            // %test-plugin-vars.xxx=yyy\n            if (index !== -1) {\n              key = value.substring(0, index);\n              keyword = value.substring(index + 1).toLowerCase();\n            } else {\n              // %test-plugin-vars.xxx or %test-plugin-vars.xxx=\n              key = value;\n            }\n          } else {\n            // %test-plugin-vars=yyy\n            keyword = value.toLowerCase();\n          }\n        }\n        const result: (string | {\n          isKey: true,\n          value: string,\n        })[] = [];\n        VARS_OPTIONS.forEach((option) => {\n          if (keyword && !option.toLowerCase().includes(keyword)) {\n            return;\n          }\n          if (isKey) {\n            result.push({\n              value: `${key}=${option}`,\n              isKey: true,\n            });\n          } else {\n            result.push(option);\n          }\n        });\n        ctx.body = result;\n      });\n\n      // 针对 `test-rules-hint://xxx` 和 `whistle.test-rules-hint://xxx`\n      // 如果没有配置 `pluginVars.hintUrl`，则对 `%test-rules-hint[.key]=xxx` 也生效\n      router.get('/cgi-bin/get-hints', (ctx) => {\n        const { protocol, value } = ctx.query;\n        if (!protocol || typeof protocol !== 'string' || typeof value !== 'string') {\n          return;\n        }\n        const isVar = protocol.startsWith('%');\n        // 事实上不会有这种情况，除非删除了 `pluginVars.hintUrl` 配置\n        if (isVar) {\n          return;\n        }\n        const isLong = protocol.startsWith('whistle.');\n        const prefix = isLong ? 'long-' : 'short-';\n        const keyword = value.toLowerCase();\n        const result: string[] = [];\n        HINTS_OPTIONS.forEach((option) => {\n          if (`${prefix}${option.toLowerCase()}`.includes(keyword)) {\n            result.push(`${prefix}${option}`);\n          }\n        });\n        ctx.body = result;\n      });\n    };\n\n    ```\n9. 效果：\n   \n   <img src=\"/img/rules-hint1.png\" width=\"300\" />\n\n   <img src=\"/img/rules-hint2.png\" width=\"300\" />\n\n   <img src=\"/img/plugin-vars-hint-url1.png\" width=\"300\" />\n\n## 界面扩展\nWhistle 插件系统支持扩展以下界面功能模块：\n- **Network 模块**\n  - 数据表格列扩展\n  - 上下文菜单扩展\n  - 详情面板 Tab 扩展\n- **Rules 模块**上下文菜单\n- **Values 模块**上下文菜单\n- **Plugins 模块**上下文菜单\n\n1. 创建示例插件\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-ui-ext && cd whistle.test-ui-ext\n\n    # 初始化空白插件\n    lack init blank\n\n    # 开发模式运行\n    lack watch\n    ```\n2. 编辑 `whistleConfig`：https://github.com/whistle-plugins/examples/tree/master/whistle.test-ui-ext/package.json\n3. 执行后可在 Whistle 插件列表看到 `test-ui-ext` 插件\n    ![界面扩展效果图](/img/ui-ext.png)\n\n#### 1. Network 表格列扩展\n**配置示例**：\n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkColumn\": {\n      \"name\": \"Referer\",          // 列显示名称（必填）\n      \"key\": \"req.headers.referer\", // 数据字段路径（必填）\n      \"iconKey\": \"\",              // 图标字段（可选）\n      \"showTitle\": true,          // 是否显示悬浮提示（可选）\n      \"width\": 120                // 列宽（可选，默认120）\n    }\n    ...\n  }\n  ...\n}\n```\n\n**高级数据处理**：\n1. 创建 `/public/webWorker.js`：\n    ```js\n    module.exports = function(session, next) {\n      const isGithub = /^https?:\\/\\/github\\.com\\//.test(session.url);\n      next({\n        testWebWorker: 'custom_value',\n        style: isGithub ? { \n          color: '#fff',\n          fontStyle: 'italic',\n          bgColor: 'red' \n        } : null\n      });\n    };\n    ```\n2. 配置 webWorker：\n    ```js\n    {\n      \"whistleConfig\": {\n        \"networkColumn\": {\n          \"name\": \"Test\",\n          \"key\": \"customData.testWebWorker\"  // 访问 webWorker 返回数据\n          ...\n        },\n        \"webWorker\": \"/public/webWorker.js\"\n      }\n    }\n    ```\n\n#### 2. 详情面板 Tab 扩展\n```js\n{\n  \"whistleConfig\": {\n    \"inspectorsTab\": {\n      \"name\": \"Custom Tab\",       // Tab 显示名称\n      \"action\": \"/public/tab.html\", // 功能页面\n      \"icon\": \"data:image/png;base64,...\", // 图标（可选）\n      \"req\": {                    // Request 子 Tab\n        \"name\": \"Req SubTab\",\n        \"action\": \"/public/req-tab.html\"\n      },\n      \"res\": {                    // Response 子 Tab\n        \"name\": \"Res SubTab\",\n        \"action\": \"/public/res-tab.html\"\n      }\n    },\n    \"composerTab\": { /* 同 inspectorsTab */ },\n    \"toolsTab\": { /* 同 inspectorsTab */ }\n  }\n}\n```\n\n- Tab 页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/tab.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Network 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkMenus\": [\n      {\n        \"name\": \"Network Menu1\",\n        \"action\": \"/public/network-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Network Menu2\", // 菜单项名称\n        \"action\": \"/public/network-menu.html\", // 功能页面\n        \"required\": true, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": true, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Rules 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"rulesMenus\": [\n      {\n        \"name\": \"Rules Menu1\", // 菜单项名称\n        \"action\": \"/public/rules-menu.html\", // 功能页面\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Rules Menu2\",\n        \"action\": \"/public/rules-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 4. Values 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"valuesMenus\": [\n      {\n        \"name\": \"Values Menu1\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Values Menu2\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 5. Plugins 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"pluginsMenus\": [\n      {\n        \"name\": \"Plugins Menu1\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Plugins Menu2\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n## 特殊路径\n\nWhistle 提供专用路径格式，用于在各类型页面下直接请求插件接口，以及只在 Whistle 环境下可以正常访问的路径。\n\n#### 1. Whistle 界面直接访问\n在 Whistle 界面或插件管理界面中，可直接使用以下格式访问插件接口：\n```\npath/to\n```\n\n- 此路径会自动解析为 `/whistle.xxx/path/to`\n- 推荐使用相对路径格式，避免直接使用绝对路径 `/path/to`\n\n#### 2. 普通页面访问\n若需在非 Whistle 的普通网页中调用插件接口，请使用以下特殊路径格式：\n```\n/.whistle-path.5b6af7b9884e1165./whistle.xxx/path/to\n```\n\n- 请求到达插件时会被标准化为：`/whistle.xxx/path/to`\n- 前缀 `/.whistle-path.5b6af7b9884e1165./` 是 Whistle 的专用标识符，用于标记内部请求路由\n\n#### 3. 环境自适应路径转换\n实现经过或不经过 Whistle 代理时使用不同路径，但确保后台服务接收到统一路径：\n``` txt\n/_WHISTLE_5b6af7b9884e1165_/\n```\n\n- 原始请求：`https://www.example.com/.../_WHISTLE_5b6af7b9884e1165_/path/to?query`\n- 经过 Whistle 后转换为：`https://www.example.com/.../path/to?query`\n\n#### 说明：\n- `xxx` 为您的插件名称\n- `path/to` 是插件接口的具体路径\n- 特殊路径中的哈希值 `/.whistle-path.5b6af7b9884e1165./` 为固定标识符\n\n## 其它 whistleConfig 配置\n``` js\n{\n  ...,\n  \"homepage\": '', // 插件帮助页面地址\n  \"pluginHomepage\": '', // 自定义插件操作页面地址，默认为 `/plugin.xxx/\n  \"whistleConfig\": {\n    \"hideLongProtocol\": false, // 是否隐藏插件的长协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"hideShortProtocol\": false, // 是否隐藏插件的短协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"priority\": 0, // 默认值为0，插件优先级按更新时间从旧到新排序（越早更新的优先级越高），可通过此字段调整优先级顺\n    \"favicon\": '', // 插件 Tab 页上的 icon，可以为插件相对路径 `/public/xxx.png` 或绝对路径 `https://xxx` 或 `data:image/png;base64,xxx`\n    \"registry\": '', // 插件的 npm registry\n    \"noOption\": false, // 如果不存在操作界面，可以设置为 true，这样 Option 按钮会置灰\n    \"enableAuthUI\": false, // 插件的 auth hook 是否作用于插件操作界面（慎用）\n    \"tunnelKey\": '', // 字符串或字符串组，如果需要继续将请求通过 TUNNEL 代理到其它 HTTP Proxy 时，可以指定哪些请求头跟着过去\n    ...\n  },\n  ...\n}\n```\n\n## 插件页面内部路径规范\n插件页面的 URL 通常为以下格式：\n- `/plugin.xxx/path/yyy.html`\n- `/whistle.xxx/path/yyy.html`\n\n**根目录**为 `/plugin.xxx/` 或 `/whistle.xxx/`，因此：\n\n✅ 推荐使用相对路径，避免使用绝对路径（如 /path/to）。\n\n---\n\n#### **相对路径使用示例**  \n\n**场景 1：页面位于插件根目录**  \n**页面地址**：`/plugin.xxx/yyy.html`  \n**正确写法**：  \n- `./path/to`  \n- `path/to`  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n\n---\n\n**场景 2：页面位于子目录**  \n**页面地址**：`/plugin.xxx/a/b/c/yyy.html`  \n**正确写法**：  \n- `../../../path/to`（返回 3 级目录再进入目标路径）  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n- ❌ `path/to`（会基于当前目录 `a/b/c/` 查找，导致路径错误）  \n\n---\n\n**最佳实践**  \n1. **推荐使用 `./` 开头**，明确表示相对当前目录（如 `./assets/style.css`）。  \n2. **避免硬编码 `/` 开头的路径**，防止不同部署环境下路径失效。  \n3. **测试路径引用**：在本地和线上环境均验证资源加载是否正常。  \n\n---\n\n**常见问题**  \n❓ **Q：为什么不能用绝对路径？**  \n📌 **A**：插件可能部署在不同环境（如测试/生产/内嵌到其它项目中），绝对路径可能导致资源加载失败。  \n\n❓ **Q：有些构建工具，如 Vite 默认会将资源路径基于 base 设置进行解析。如果 base 设为 `/`，可能会将 `../` 优化为 `/`，如何处理？**  \n📌 **A**：尝试将 base 改成 `./`：\n  ``` js\n  // vite.config.js\n  export default defineConfig({\n    base: './', // 强制使用相对路径\n  });\n  ```\n  > 建议与后台路由配置同步优化\n\n\n## 发布插件\nWhistle 插件的发布方式与常规 NPM 包完全一致，只需遵循标准 NPM 发布流程：\n1. 登录 NPM（或企业私有源，后者需要设置企业私有源的 `npm config set registry https://xxx`）:\n    ``` sh\n    npm login\n    ```\n2. 在插件根目录执行发布命令：\n    ``` sh\n    npm publish\n    ```\n\n## 参考资料\n1. 示例源码仓库：\nhttps://github.com/whistle-plugins/examples\n1. 脚手架命令：https://github.com/avwo/lack\n","docs/docs/extensions/npm.md":"# NPM 模块\nWhistle 可作为 NPM 模块集成到项目中，**强烈建议**在独立子进程中运行 Whistle，以避免与主进程相互影响，同时便于管理。\n\n## 安装依赖\n``` sh\nnpm i --save whistle pfork\n```\n\n## 独立服务模式\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n\n    module.exports = (options, callback) => {\n      startWhistle({ port: options.port }, () => callback());\n    };\n    ```\n    > 禁用插件、自定义插件目录、修改存储目录等启动参数参考：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. 启动 Whistle 进程代码：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n      port: 8080,\n    };\n    const forkWhistle = (retry) => {\n      fork(options, (err, result, child) => {\n        if (err) {\n          // 初始化时报错，直接退出服务\n          if (!retry) {\n            throw err;\n          }\n          // 重试报错，延迟 100 毫秒再重试\n          return setTimeout(forkWhistle, 100);\n        }\n        // 子进程退出自动重试\n        child.once('close', () => forkWhistle(true));\n      });\n    };\n\n    forkWhistle();\n    ```\n\n这种方式启动的 Whistle，跟通过命令行独立启动 Whistle 或 Whistle 客户端的用法一样。\n\n\n## 内部服务模式（推荐）\n\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n    const http = require('http');\n\n    let curPort = 30013;\n\n    const getPort = (callback) => {\n      const server = http.createServer();\n      server.on('error', () => {\n        if (++curPort % 5 === 0) {\n          ++curPort;\n        }\n        getPort(callback);\n      });\n      server.listen(curPort, '127.0.0.1', () => {\n        server.removeAllListeners();\n        server.close(() => callback(curPort));\n      });\n    };\n\n    module.exports = (options, callback) => {\n      // 获取一个随机端口\n      getPort((port) => {\n        // 在随机端口启动 Whistle，启动成功后将启动参数传给父进程\n        const options = {\n          port,\n          host: '127.0.0.1',\n          storage: 'xxx', // 建议设置新存储目录，确保不跟其它实例的目录冲突\n        };\n        startWhistle(options, () => callback(options));\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/whistle.js\n    > \n    > Whistle 会\n2. 启动 Whistle 进程方法（`/lib/fork.js`）：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n    };\n\n    module.exports =() => {\n      return new Promise((resolve, reject) => {\n        fork(options, (err, result) => {\n          if (err) {\n            return reject(err);\n          }\n          resolve(result);\n        });\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/main/whistleMgr.js\n3. 将项目中的指定请求转发到 Whistle：\n   ``` js\n    const { createServer } = require('http');\n    const forkWhistle = require('./fork');\n\n    const server = createServer(async (req, res) => {\n      try {\n        const { host, port } = await forkWhistle();\n        // 将请求转发到 host、port\n      } catch (e) {\n        // handle errors\n      }\n    });\n\n    server.listen(8010);\n   ```\n   > 参考：https://github.com/Tencent/nohost/blob/master/lib/index.js#L160\n\n这种方式启动的 Whistle，端口是随机的且是按需启动的，外部请求不直接访问 Whistle，需要通过所在项目的服务进行转发，用法更灵活，更推荐项目使用这种方式集成 Whistle。\n\n## 相关文档\n\n1. Whistle 的所有启动参数可查看源代码的类型定义：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. pfork：https://github.com/avwo/pfork\n3. 完整示例：https://github.com/Tencent/nohost\n\n","docs/docs/extensions/usage.md":"# 使用插件\n\n## 安装插件\n\n1. 点击左侧导航栏的 Plugins 标签页\n2. 点击顶部的 Install 按钮\n3. 在弹出窗口中输入插件名称（支持同时安装多个插件）：\n   - 多个插件用空格或换行符分隔\n   - 可指定自定义 npm 镜像源：\n     - 直接在插件名后添加 `--registry=镜像地址`\n     - 或从下拉列表选择历史使用过的镜像源\n\n<img width=\"1000\" alt=\"install plugins\" src=\"/img/install-plugins.png\" />\n\n> 文本框中的冗余内容不会影响操作结果，Whistle 会自动过滤并仅提取插件名称及 registry 安装信息。若对话框未显示 Install 按钮，请重新安装全局 Node.js：https://nodejs.org/\n\n安装成功后，稍等片刻即可在插件列表中查看新安装的插件：\n\n<img width=\"1000\" alt=\"plugins\" src=\"/img/plugin-list.png\" />\n\n\n## 自定义协议\n\n每个插件可注册两种协议类型，配置方式跟普通协议一样：\n``` txt\n# 长协议\npattern whistle.plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n# 短协议\npattern plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n```\n\n## Hooks 功能\n\nWhistle 将匹配插件规则的请求与插件对应 Hook 进行交互，可实现以下功能：\n> 插件协议的具体功能看各个插件的帮助文档，插件也可以选择隐藏这些协议\n1. 自动生成并下发 HTTPS 证书\n2. 对代理请求的认证控制，要求用户提供凭证才能访\n3. 实时获取请求头、响应状态码等元数据\n4. 对请求动态设置规则\n5. 将请求转发给插件，由插件完全控制请求处理流程（需要匹配短协议才能实现）\n\n\n## pipe 功能\n有些请求/响应内容可能被加密或 protobuf 序列化无法看到明文，可通过以下两种方式实现内容查看与修改：\n1. **插件全权处理**\n   - 将请求直接转发至插件\n   - 插件完全控制处理流程\n   - 注意事项：\n     - Whistle 内置规则将失效\n     - 需插件自行实现所有处理逻辑\n     - 如需在抓包界面显示解密内容，插件必须调用 Whistle API 回传数据\n2. **管道流式处理（推荐）**\n   - 通过 pipe 协议建立处理管道\n   - 请求/响应流程：\n     - 进入 Whistle 时：插件解密\n     - 离开 Whistle 时：插件加密\n   - 优势：\n     - 保持明文请求特性\n     - 完整支持 Whistle 内置规则\n     - 抓包界面可直接查看明文\n\n**技术说明：**\n- pipe 流功能为可选实现，各插件支持情况请查阅具体文档\n- 方案二处理后的请求完全等同于普通明文请求\n\n## 操作界面\n\n#### 访问插件操作界面\n在 Whistle 插件管理面板中，您可以通过以下任一方式访问插件功能：\n1. 点击插件条目右侧的 `Option` 按钮\n2. 在插件列表直接单击插件名称\n\n#### 界面特性\n**呈现形式：** 模态对话框或独立标签页\n\n**核心能力：**\n1. 提供可视化配置和管理功能\n2. 支持与 Whistle 核心的实时交互\n3. 显示插件运行状态、请求统计数据、日志信息等\n\n#### 技术说明\n1. 插件本质上是与 Whistle 直接交互的 HTTP Server\n2. 操作界面的具体功能由各插件自行实现\n3. 插件可选择不提供操作界面（仅提供后台服务或[命令行操作](../cli#w2-exec)）\n\n## 扩展 Whistle 界面\n\n插件可通过以下方式增强 Whistle 的用户界面功能：\n\n#### Network 扩展\n1. 抓包列表上下文菜单\n    > 在抓包列表项右键菜单中添加自定义操作\n2. 主视图标签页\n    > 在右侧主面板区创建一级功能标签页\n2. Inspectors 分析工具扩展\n    > 在 Inspectors 面板下添加：\n    >   - 二级功能标签页\n    >   - 三级详细视图标签页\n3. Composer 请求构造器扩展\n    > 在 Composer 工具区添加二级功能标签页\n4. Tools 工具集扩展\n    > 在 Tools 功能区添加二级功能标签页\n\n#### Rules 扩展\n左侧列表上下文菜单\n> 在左侧规则文件列表的右键菜单中添加功能项\n\n#### Values 扩展\n左侧列表上下文菜单\n> 在左侧变量文件列表的右键菜单中添加功能项\n\n**实现说明：**\n- 各插件可根据功能需求选择适当的扩展位置\n- 具体扩展功能和实现方式请参阅各插件的开发文档\n- 未使用的扩展点不会影响插件基础功能\n\n## 插件内置规则\n\n插件支持通过以下规则文件扩展系统功能：\n\n1. **全局规则 (rules.txt)**\n   - 自动加载时机： 插件初始化时\n   - 作用范围： 全局请求\n   - 优先级： 低于 Rules 界面配置的规则\n   - 典型用途： 插件默认规则配置\n2. **私有规则 (_rules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 请求处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 请求预处理/改写\n3. **响应阶段规则 (resRules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 响应处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 响应后处理/改写\n\n\n插件支持灵活的内置规则配置机制：\n- **可选性：** 所有内置规则均为可选配置\n- **按需设置：** 插件只需声明必要的规则\n- **规则查看：** 通过插件管理界面的 Rules 按钮可查看当前插件配置的所有规则\n> 注意：未配置规则的插件仍可正常使用其他功能\n\n## 更新卸载插件\n\n**在 Whistle 插件管理界面中：**\n- **更新插件：** 点击插件条目右侧的 `Update` 按钮\n- **卸载插件：** 点击插件条目右侧的 `Uninstall` 按钮\n","docs/docs/rules/@.md":"# `@` 符号用法\n\n当您希望将规则存放在远程服务器或本地项目文件中，并让 Whistle 自动同步更新时，可以利用规则中的 `@` 符号实现。\n\n## 语法规则\n\n## 1. 引入规则文件 (`@path`)\n动态加载外部规则文件内容，Whistle 会定时从规则文件更新内容。\n\n**使用格式：** `@文件路径或URL`\n\n**支持类型：**\n- 远程 URL：`@https://example.com/rules.txt`\n- 本地文件：`@~/projects/rules.txt`（支持所有操作系统路径）\n- 插件接口：`@whistle.nohost/api/rules`（需插件实现接口）\n\n> **Windows 路径兼容性**：支持混用 `/` 和 `\\` 作为路径分隔符：\n> ```txt\n> @C:/whistle/rules.txt\n> @D:\\config\\proxy.rules\n> ```\n\n## 配置示例\n\n### 引入远程规则文件\n```txt\n@https://raw.githubusercontent.com/your-org/configs/master/whistle-rules.txt\n```\n\n### 引入本地项目规则\n```txt\n@/Users/username/projects/my-app/whistle-config.txt\n@~/dev/configs/rules.txt  # 使用用户目录\n```\n\n### 引入插件提供的规则\n```txt\n@whistle.nohost/api/rules\n```\n\n### 混合使用示例\n```txt\n# 引入团队共享的基础规则\n@https://team.example.com/configs/base-rules.txt\n\n# 引入个人项目特定规则\n@~/projects/my-app/whistle-overrides.txt\n\n```\n\n## 文件格式要求\n\n引入的外部文件应包含标准的 Whistle 规则语法：\n\n```txt\n# 远程规则文件示例\nwww.example.com proxy://127.0.0.1:8080\napi.example.com resCors://*\nstatic.example.com cache://3600\n```\n","docs/docs/rules/attachment.md":"# attachment\n`attachment` 协议用于设置响应头中的 `Content-Disposition` 字段，使浏览器将服务器的响应直接视为附件下载，并将文件保存为 `attachment` 设定的名称，而不是在页面内展示。\n> 类似 Koa 的 `attachment` 方法：https://koajs.com/\n\n## 规则语法\n\n`attachment` 支持多种方式来指定下载文件名：\n\n### 1. 内联值（直接指定）\n直接在规则中写明文件名。\n```txt\npattern attachment://filename [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/report attachment://年度报告.pdf\n```\n\n### 2. 内嵌值（使用代码块）\n当文件名较长或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern attachment://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n季度数据-2024Q1.csv\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern attachment://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为目标文件名。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载文件名。\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **filename** | 是 | 下载时显示的文件名。<br>• 例如：`document.pdf`, `export.xlsx`。<br>• 支持通过上述三种方式（内联/内嵌/Values）指定。<br>• ⚠️ 不支持从文件或远程 URL 加载。 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n## 配置示例\n\n### 基础示例\n让访问 example.com 首页时自动下载一个名为 `example.html` 的文件。\n```txt\nhttps://www.example.com/ attachment://example.html\n```\n\n### 配合过滤器使用\n仅当请求包含特定查询参数 `download=true` 时，才触发 PDF 文件下载。\n```txt\nhttps://api.example.com/document attachment://用户手册.pdf includeFilter:///[?&]download=true/\n```\n\n### 使用内嵌值\n当文件名比较复杂或需要注释时，使用内嵌代码块更清晰。\n````txt\nhttps://assets.example.com/data.json attachment://{my-file-name}\n\n``` my-file-name\n配置备份-20240514.json\n```\n````\n\n## 实现远程加载\n\n`attachment` 协议本身不支持从文件路径或远程 URL 加载值。如果需要实现远程加载功能，有以下两种替代方案：\n\n### 1. 通过插件扩展功能\n你可以开发插件来实现更复杂的文件名获取逻辑，比如从远程API动态获取文件名。具体开发方法请参考：[插件开发文档](../extensions/dev)\n\n### 2. 使用 @url 加载完整规则\n将包含 `attachment` 规则的完整配置文件存储在远程服务器上，然后通过 [`@url`](./@) 协议加载整个规则集。\n\n**示例：**\n1. 在远程服务器创建规则文件 `xxx.txt`：\n    ```txt\n    https://api.example.com/export attachment://remote-file.csv\n    https://static.example.com/docs attachment://文档.pdf\n    ```\n2. 在本地 Rules 配置中引用远程规则：\n    ```txt\n    @https://remote-url/xxx.txt\n    ```\n\n这样，远程文件中的 `attachment` 规则就会被加载生效。\n\n## 工作原理与关联协议\n\n1.  **核心原理**：`attachment` 协议本质上是自动设置响应头。\n    上面的示例完全等价于使用 [`resHeaders`](./resHeaders) 协议手动设置：\n    ```txt\n    https://www.example.com/ resHeaders://content-disposition=attachment;filename=\"example.html\"\n    ```\n\n2.  **优势**：相比于直接使用 `resHeaders`，`attachment` 语法更简洁、易读，专注于文件下载场景，自动处理文件名转义等细节。\n\n3.  **注意**：服务器本身返回的响应体内容不会改变。此规则只是通过修改响应头，指示浏览器如何处理这些内容。\n","docs/docs/rules/auth.md":"# auth\n\n`auth` 协议用于快速修改请求头的 `Authorization` 字段，为匹配的请求自动添加 HTTP 基本认证（Basic Authentication）所需的凭证信息。\n\n## 规则语法\n\n`auth` 支持多种方式设置请求鉴权头：\n\n### 1. 内联值（直接指定）\n直接在规则中写明用户名和密码。\n```txt\npattern auth://username:password [lineProps...] [filters...]\n# 或\npattern auth://username=test&password=123 [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/api1/ auth://admin:secret\nhttps://example.com/api2/ auth://username=admin&password=secret\n```\n\n### 2. 内嵌值（使用代码块）\n当凭证信息较复杂或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern auth://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nusername: admin\npassword: my secret password\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern auth://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为包含用户名和密码的对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern auth://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `auth://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report auth://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含鉴权信息的 JSON 或简单 YAML 文件。\n```txt\n# 从本地文件加载\npattern auth:///User/xxx/auth.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern auth://https://config.example.com/auth.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或简单 YAML 格式，包含 `username` 和 `password` 字段：\n```json\n{\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n或\n```yaml\nusername: admin\npassword: secret\n```\n\n## 设置 `Proxy-Authorization`\n```json\n{\n  \"proxy\": true,\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 鉴权凭证信息，支持多种格式：<br>• **直接格式**：`username:password` 或 `username=test&password=123`<br>• **对象格式**：包含 `username` 和 `password` 字段的对象<br>• 支持从本地文件或远程 URL 加载<br>• 支持内联、内嵌、Values、本地文件路径、远程 URL 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n## 配置示例\n\n### 基础示例\n为 example.com 的 API 接口添加基本认证：\n```txt\nhttps://api.example.com/ auth://admin:secret\n```\n\n### 使用内嵌值\n当密码包含特殊字符或空格时，使用内嵌方式更安全：\n````txt\nhttps://internal.example.com/ auth://{prod-credentials}\n\n``` prod-credentials\nusername: service-account\npassword: P@ssw0rd!2024\n```\n````\n\n### 从文件加载\n从本地配置文件加载鉴权信息：\n```txt\nhttps://example.com/api/ auth:///Users/john/config/auth.json\n```\n\n### 配合过滤器使用\n仅对 POST 请求添加鉴权：\n```txt\nhttps://example.com/api/ auth://admin:secret includeFilter://m:POST\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `api-auth` 配置：\n```txt\nhttps://example.com/api/ auth://{api-auth}\n```\n\n## 工作原理与关联协议\n\n1. **核心原理**：`auth` 协议会自动计算用户名和密码的 Base64 编码，并设置 `Authorization` 请求头。\n\n   上面的示例等价于使用 [`reqHeaders`](./reqHeaders) 协议手动设置：\n   ```` txt\n   https://example.com/api/ reqHeaders://{auth.txt} # 内容有空格，不能内联\n\n   ``` auth.txt\n   authorization: Basic YWRtaW46c2VjcmV0\n   ```\n   ````\n   其中 `YWRtaW46c2VjcmV0` 是 `admin:secret` 的 Base64 编码。\n\n2. **优势**：相比于直接使用 `reqHeaders`，`auth` 语法更简洁直观，无需手动计算 Base64 编码值。\n\n## 注意事项\n- `auth` 协议仅支持 HTTP 基本认证（Basic Authentication）\n- 对于更复杂的认证方式（如 Bearer Token、OAuth 等），请使用 [`reqHeaders`](./reqHeaders) 协议直接设置相应的 `Authorization` 头\n- 从远程 URL 加载时，请确保目标 URL 安全可靠\n\n## 关联协议 {#related}\n1. 删除请求头字段（`Delete Request Header`）：[delete://reqHeaders.xxx](./delete)\n2. 删除请求 Cookie（`Delete Request Cookie`）：[delete://reqCookies.xxx](./delete)\n3. 设置请求 Cookie：[reqCookies](./reqCookies)\n4. 设置请求鉴权头：[auth](./auth)\n5. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n","docs/docs/rules/cache.md":"# cache\n\n`cache` 协议用于快速设置响应的缓存控制头，帮助控制浏览器和服务器的缓存行为。可以为匹配的响应设置缓存策略，包括：\n- 设置固定的缓存时间\n- 禁用缓存（`no-cache`）\n- 完全禁止存储（`no-store`）\n\n这可以帮助优化网站性能或确保特定资源不会被缓存。\n\n## 规则语法\n\n`cache` 支持多种方式设置缓存策略：\n\n### 1. 内联值（直接指定）\n直接在规则中指定缓存策略。\n```txt\n# 设置缓存5秒，支持正整数、0、负整数\npattern cache://5 [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-cache`、`pragma: no-cache`\n# `no-cache` 可简写为 `no`\npattern cache://no-cache [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-store`、`pragma: no-cache`\npattern cache://no-store [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nhttps://example.com/api/data cache://no-cache\nhttps://example.com/static/js cache://86400  # 缓存24小时\n```\n\n### 2. 内嵌值（使用代码块）\n当需要复用缓存策略或策略较复杂时可使用此方式。\n````txt\npattern cache://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nno-cache\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的缓存策略。\n```txt\npattern cache://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为缓存策略。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载缓存配置。\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 缓存策略值：<br>• **数字**：缓存秒数（如 `60` 表示缓存60秒）<br>• **no-cache** 或 **no**：允许缓存但每次需重新验证<br>• **no-store**：完全禁止缓存<br>• 支持内联、内嵌、Values 引用<br>• ⚠️ 不支持从文件或远程 URL 加载 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**影响的响应头：**\n- `Cache-Control`\n- `Expires`\n- `Pragma`\n\n## 缓存策略说明\n\n| 策略 | 含义 | 典型使用场景 |\n| :--- | :--- | :--- |\n| **数字（如 `60`）** | 设置资源缓存指定秒数 | 静态资源、不经常变化的API响应 |\n| **no-cache**（或 **no**） | 允许缓存但每次使用前必须验证 | 经常变化的内容，如用户数据、实时报价 |\n| **no-store** | 完全禁止缓存任何版本 | 敏感数据、支付页面、一次性令牌 |\n\n## 配置示例\n\n### 基础示例\n```txt\n# 静态资源缓存1小时\nhttps://example.com/static cache://3600\n\n# API接口禁用缓存\nhttps://example.com/api cache://no-cache\n\n# 敏感数据禁止缓存\nhttps://example.com/account cache://no-store\n```\n\n### 使用内嵌值\n````txt\nhttps://example.com/reports cache://{report-cache-policy}\n\n``` report-cache-policy\nno-cache\n```\n````\n\n### 配合过滤器使用\n仅对特定响应状态码设置缓存：\n```txt\nhttps://example.com/api  cache://300 includeFilter://s:200\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `static-cache` 配置：\n```txt\nhttps://example.com/assets cache://{static-cache}\n```\n\n\n## 工作原理与关联协议\n\n1. **核心原理**：`cache` 协议自动设置相应的缓存控制头：\n\n   - `cache://5` 设置：\n     ```http\n     Cache-Control: max-age=5\n     Expires: [当前时间+5秒]\n     ```\n   \n   - `cache://no-cache` 设置：\n     ```http\n     Cache-Control: no-cache\n     Pragma: no-cache\n     ```\n   \n   - `cache://no-store` 设置：\n     ```http\n     Cache-Control: no-store\n     Pragma: no-cache\n     ```\n\n2. **关联协议**：\n   - **禁用缓存**：更彻底的缓存禁用方案，同时移除请求和响应中的缓存相关头。详见：[disable://cache](./disable)\n   - **手动设置头部**：对于更复杂的缓存控制，可以使用 [`resHeaders`](./resHeaders) 协议手动设置缓存头。\n\n## 注意事项\n- 缓存时间以秒为单位，不支持小数\n- 设置 `no-cache` 不代表\"不缓存\"，而是\"使用前必须验证\"\n- 对于需要完全禁用缓存的场景，建议同时使用 [`disable://cache`](./disable) 协议\n- 实际缓存行为还受服务器配置和浏览器实现的影响\n\n## 关联协议 {#related}\n1. 修改响应头：[resHeaders](./resHeaders)\n2. 删除请求头字段（`Delete Response Header`）：[delete://resHeaders.xxx](./delete)\n3. 删除请求 Cookie（`Delete Response Cookie`）：[delete://resCookies.xxx](./delete)\n4. 设置请求 Cookie：[resCookies](./resCookies)\n5. 设置缓存：[cache](./cache)\n6. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n","docs/docs/rules/cipher.md":"# tlsOptions\n\n`tlsOptions` 协议用于配置 HTTPS/TLS 连接的安全参数，包括加密算法套件、客户端证书等，用于建立加密通信通道和服务器身份验证。\n\n> **版本要求**：仅 Whistle 最新版本（≥ v2.9.101）支持此功能\n\n通过 `tlsOptions` 协议，你可以：\n- 自定义 TLS 加密算法套件\n- 配置双向认证（mTLS）所需的客户端证书\n- 设置 TLS 连接的其他安全参数\n- 控制 TLS 版本和连接选项\n\n**规则合并机制**：`tlsOptions` 支持设置任意多个配置规则，Whistle 会根据从上到下的顺序自动合并这些规则，允许你灵活组合不同的 TLS 配置。\n\n## 规则语法\n\n`tlsOptions` 支持多种方式配置 HTTPS/TLS 连接的安全参数：\n\n### 1. 内联值（直接指定）\n直接在规则中写明操作。\n```txt\n# 设置客户端证书\npattern tlsOptions://key=/path/to/client.key&cert=/path/to/client.crt\n\n# 自定义加密套件\npattern tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n\n# 或使用 ciphers 参数\npattern tlsOptions://ciphers=ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n```\n\n### 2. 内嵌值（使用代码块）\n当配置较复杂或需要复用时可使用此方式。\n````txt\npattern tlsOptions://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n{\n  passphrase: \"123456\",\n  pfx: \"-----BEGIN PKCS7-----\\n...\"\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的配置。\n```txt\npattern tlsOptions://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 TLS 配置对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern tlsOptions://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `tlsOptions://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report tlsOptions://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含 TLS 配置的 JSON 或 YAML 文件。\n```txt\n# 从本地文件加载\npattern tlsOptions:///User/xxx/tlsOptions.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern tlsOptions://https://config.example.com/tlsOptions.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或 YAML 格式：\n```json\n{\n  \"ciphers\": \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \"minVersion\": \"TLSv1.2\",\n  \"maxVersion\": \"TLSv1.3\",\n  \"secureProtocol\": \"TLSv1_2_method\"\n}\n```\n或\n```yaml\nciphers: ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\nminVersion: TLSv1.2\nmaxVersion: TLSv1.3\nsecureProtocol: TLSv1_2_method\n```\n\n---\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | TLS 配置数据，支持多种格式：<br>• 加密算法套件名称（用 `:` 分隔）<br>• `tls.connect(options)` 参数对象<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n---\n\n## 配置对象参考\n\n完整的 TLS 连接配置对象，支持 Node.js [`tls.connect()`](https://nodejs.org/docs/latest/api/tls.html#tlscreatesecurecontextoptions) 的所有参数：\n\n```js\n{\n  // 加密算法套件\n  ciphers: \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \n  // TLS 协议版本控制\n  minVersion: \"TLSv1.2\",\n  maxVersion: \"TLSv1.3\",\n  secureProtocol: \"TLSv1_2_method\",\n  \n  // 证书相关配置\n  ca: \"-----BEGIN CERTIFICATE-----\\n...\",  // CA 证书\n  cert: \"-----BEGIN CERTIFICATE-----\\n...\", // 客户端证书\n  key: \"-----BEGIN PRIVATE KEY-----\\n...\",  // 私钥\n  pfx: \"-----BEGIN PKCS7-----\\n...\",       // PFX/P12 格式证书\n  passphrase: \"cert-password\",             // 证书密码\n  \n  // 其他安全选项\n  honorCipherOrder: true,     // 优先使用服务器端的加密套件顺序\n  requestCert: true,          // 请求客户端证书（双向认证）\n  rejectUnauthorized: true,   // 拒绝未授权的证书\n  \n  // 高级选项\n  ecdhCurve: \"auto\",         // ECDH 曲线\n  dhparam: \"-----BEGIN DH PARAMETERS-----\\n...\",\n  secureOptions: 0,          // SSL 选项标志\n  sessionTimeout: 300,       // 会话超时时间（秒）\n  sessionIdContext: \"whistle\"\n}\n```\n\n---\n\n## 配置示例\n\n### 1. 自定义加密套件\n限制特定域名的 TLS 加密算法：\n```txt\nwww.example.com/path tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384\n```\n\n> **使用场景**：解决特定服务器的兼容性问题。参考：[GitHub Issue #963](https://github.com/avwo/whistle/issues/963)\n\n### 2. 配置客户端证书（双向认证 mTLS）\n\n**cert 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=/User/xxx/test.key&cert=/User/xxx/test.crt\n```\n\n**pem 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=E:\\test.key&cert=E:\\test.pem\n```\n\n**pfx 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=/User/xxx/test.pfx\n```\n\n**p12 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=E:/test.p12\n```\n\n> **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 3. 内嵌证书内容\n直接将证书内容嵌入配置中：\n````txt\n``` test.json\n{\n  key: '----xxx----- ... ----xxx-----',\n  cert: '----yyy----- ... ----yyy-----'\n}\n```\n\nwww.exaple.com/path tlsOptions://{test.json}\n````\n\n### 4. 从本地或远程文件加载配置\n**从本地文件加载：**\n```txt\nwww.example.com/path1 tlsOptions:///User/xxx/test.json\n```\n\n**从远程 URL 加载：**\n```txt\nwww.example.com/path2 tlsOptions://https://www.xxx.com/xxx/params.json\n```\n\n**使用临时文件编辑：**\n```txt\nwww.example.com/path3 tlsOptions://temp/blank.json\n```\n\n### 5. 规则合并示例\n利用规则合并特性，逐步细化配置：\n```txt\n# 第一层：为整个域名设置基础TLS版本\n*.example.com tlsOptions://minVersion=TLSv1.2\n\n# 第二层：为API子域添加客户端证书\napi.example.com tlsOptions://key=/certs/client.key&cert=/certs/client.crt\n\n# 第三层：为特定路径进一步限制加密套件\napi.example.com/secure tlsOptions://ciphers=ECDHE-RSA-AES256-GCM-SHA384\n```\n\n---\n\n## 注意事项\n\n1. **版本兼容性**：确保使用的 Whistle 版本 ≥ v2.9.101\n2. **证书格式**：不同格式的证书需要使用对应的参数：\n   - PEM 格式：使用 `key` 和 `cert` 参数\n   - PFX/P12 格式：使用 `pfx` 和 `passphrase` 参数\n3. **路径处理**：\n   - 相对路径相对于 Whistle 配置文件目录\n   - Windows 路径可以混用 `/` 和 `\\`\n4. **安全警告**：\n   - 谨慎使用 `rejectUnauthorized: false`，仅限开发环境\n   - 妥善保管客户端私钥，避免泄露\n5. **性能影响**：复杂的加密算法可能影响连接性能\n6. **调试建议**：遇到连接问题时，可先用简单的配置测试，逐步添加复杂选项\n\n---\n\n## 故障排除\n\n### Q: 证书加载失败\n**A:** 检查：\n1. 证书文件路径是否正确\n2. 证书格式是否匹配参数类型\n3. PFX 证书密码是否正确\n\n### Q: 连接被拒绝\n**A:** 检查：\n1. TLS 版本是否被服务器支持\n2. 加密算法套件是否被服务器支持\n3. 客户端证书是否有效且被服务器信任\n\n### Q: 配置未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配目标 URL\n2. Whistle 版本是否满足要求\n3. 配置语法是否正确，特别是 JSON 格式\n4. 是否存在冲突的规则（规则按从上到下顺序合并）\n\n### Q: 规则合并不符合预期\n**A:** 检查：\n1. 规则的顺序是否正确（从上到下合并）\n2. 不同规则中的配置是否冲突\n3. 使用更具体的 pattern 是否覆盖了通用配置\n","docs/docs/rules/cssAppend.md":"# cssAppend\n\n`cssAppend` 协议用于在现有的 CSS 类型响应内容体后面插入指定内容。\n\n> **生效条件**：仅对响应类型 `content-type` 包含 `html` 或 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）才生效\n> \n> ⚠️ 注意：`204`、`304` 等无响应内容体的请求不受影响\n> \n> html 页面会自动包裹 `<style>...</style>`\n\n通过 `cssAppend` 协议，你可以：\n- 在现有 CSS 文件的末尾追加新的样式规则\n- 动态添加 CSS 覆盖规则\n- 为特定页面注入自定义样式\n- 在不修改源文件的情况下扩展样式功能\n\n## 规则语法\n\n`cssAppend` 支持多种方式指定要追加的内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要追加的 CSS 内容。\n```txt\n# `value` 小括号内不能用空格或换行符\npattern cssAppend://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/index.html cssAppend://(.custom-btn{background:red;})\n```\n\n### 2. 内嵌值（使用代码块）\n当要追加的 CSS 内容有空格或换行符或需要复用时可使用此方式。\n````txt\npattern cssAppend://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n.dark-mode {\n  background: #333;\n  color: #fff;\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的 CSS 内容。\n```txt\npattern cssAppend://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 CSS 内容。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern cssAppend://temp.css\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `cssAppend://temp.css`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/test.css cssAppend://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.css\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载要追加的 CSS 内容。\n```txt\n# 从本地文件加载\npattern cssAppend:///User/xxx/custom.css\n\n# 从远程 URL 加载（支持 http 和 https）\npattern cssAppend://https://cdn.example.com/styles/override.css\n```\n# 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要追加的 CSS 内容，支持多种格式：<br>• 纯 CSS 代码文本<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**特殊说明：**\n- 仅对 `content-type` 响应头包含 `css` 的请求生效\n- 对 `204`、`304` 等无响应体的状态码不生效\n- 追加的内容会直接拼接到原 CSS 文件的末尾\n\n\n## 配置示例\n\n#### 场景 1：添加响应式断点\n```` txt\nwww.example.com/responsive.css cssAppend://{test.css}\n\n``` test.css\n@media (max-width: 768px) {\n  .sidebar { display: none; }\n  .main-content { width: 100%; }\n}\n```\n\n````\n\n#### 场景 2：覆盖第三方库样式\n```` txt\ncdn.example.com/bootstrap.min.css cssAppend://{test2.css}\n\n``` test2.css\n/* 覆盖 Bootstrap 默认样式 */\n.btn-primary { background-color: #007bff !important; }\n.container { max-width: 1400px; }\n```\n````\n\n#### 场景 3：添加打印样式\n````txt\nwww.example.com/print.css cssAppend://{test3.css}\n\n``` test3.css\n@media print {\n  .no-print { display: none; }\n  a::after { content: \" (\" attr(href) \")\"; }\n}`\n```\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n","docs/docs/rules/cssBody.md":"# cssBody\n将现有响应内容体t替换成指定内容（（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssBody://(Hello) file://(-test-)\nwww.example.com/path2 cssBody://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssBody://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssBody://{body.txt} file://(-test-)\nwww.example.com/path2 cssBody://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello world.</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssBody:///User/xxx/test.css\nwww.example.com/path2 cssBody://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssBody://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/cssPrepend.md":"# cssPrepend\n在现有响应内容体前面插入指定内容（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssPrepend://(Hello) file://(-test-)\nwww.example.com/path2 cssPrepend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 cssPrepend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello world.</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssPrepend:///User/xxx/test.css\nwww.example.com/path2 cssPrepend://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssPrepend://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/delete.md":"# delete\n用于删除请求 URL、请求/响应头、请求/响应内容。\n\n## 规则语法\n``` txt\npattern delete://prop1|prop2|... [filters...]\n\n# 等效于：\npattern delete://prop1 delete://prop2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | `pathname`：删除请求路径（不包含请求参数）<br/>`pathname.index`：`index` 为路径分段序号 `...、-1、0、1、...` 或关键字 `last` 详见下面说明<br/>`urlParams`：删除所有请求参数<br/>`urlParams.xxx`：删除 URL 的 `xxx 参数`<br/>`reqHeaders.xxx`: 删除请求头的 `xxx` 字段<br/>`resHeaders.xxx`: 删除响应头的 `xxx` 字段<br/>`reqBody`: 删除所有请求内容<br/>`resBody`: 删除所有响应内容<br/>`reqBody.xxx.yyy`: 删除请求内容中类型为表单(form)或JSON的 `xxx.yyy` 字段<br/>`resBody.xxx.yyy`:  删除响应类型为 JSONP 或 JSON 的响应内容里的 `xxx.yyy` 字段<br/>`reqType`: 删除请求头 `content-type` 里的类型，不包含可能存在的 charset<br/>`resType`: 删除响应头 `content-type` 里的类型，不包含可能存在的 charset<br/>`reqCharset`: 删除请求头 `content-type` 里可能存在的 charset<br/>`resCharset`: 删除响应头 `content-type` 里可能存在的 charset<br/>`reqCookies.xxx`: 删除请求头的里面名为 `xxx` 的 cookie<br/>`resCookies.xxx`: 删除响应头的里面名为 `xxx` 的 cookie|    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nhttps://www.example.com/path delete://reqCookies.token|resCookies.token\n\nhttps://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json delete://resBody.name resType://json\n```\n上述 cookie 删除操作仅作用于请求/响应过程中的 cookie，不会修改浏览器本地存储的 cookie。如需修改浏览器持久化 cookie，可通过以下方式实现：\n- 通过 [jsPrepend](./jsPrepend) 等注入 JS 删除\n- 通过 [resCookies](./resCookies) 设置 cookie 过期\n\n## 删除路径\n> 支持版本：v2.9.102 及以上\n\n### 规则语法\n```\n# 基本格式\n目标域名 delete://pathname.索引号\n\n# 示例\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.-1\n```\n\n### 规则说明\n该规则用于删除请求 URL 中路径的特定分段。Whistle 会提取不包含查询参数的请求路径，并通过 `/` 分割成数组后进行删除操作。\n\n**示例解析：**\n- 请求URL：`https://www.example.com/api/path/to/xxx?query`\n- 提取路径：`api/path/to/xxx`\n- 分割数组：`['api', 'path', 'to', 'xxx']`\n- 应用规则：\n  - `delete://pathname.0` → 删除 'api'\n  - `delete://pathname.1` → 删除 'path' \n  - `delete://pathname.-1` → 删除 'xxx'\n- 最终路径：`/to`\n- 完整结果：`https://www.example.com/to?query`\n\n### 索引规则\n- **正数索引**：从 0 开始，表示从前往后的顺序\n- **负数索引**：从 -1 开始，表示从后往前的位置（-1 = 最后一段，-2 = 倒数第二段，以此类推）\n- **特殊值**：使用 `last` 可删除最后一段路径但保留末尾斜杠\n- **边界情况**：超出数组范围的索引将被忽略\n\n### 重要说明\n当路径以 `/` 结尾时，分割后的数组最后会包含一个空字符串项：\n- 路径：`/api/path/to/xxx/`\n- 分割结果：`['api', 'path', 'to', 'xxx', '']`\n\n### 使用建议\n- 使用 `pathname.-1` 删除最后一段且不保留末尾斜杠\n- 使用 `pathname.last` 删除最后一段但保留末尾斜杠\n\n**对比示例：**\n```\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.last\n```\n- 请求：`https://www.example.com/api/path/to/xxx?query`\n- 结果：`https://www.example.com/to/?query` (保留末尾斜杠)\n\n## 特殊字符\n如果对象的 key 包含以下字符，在路径表达式中需要使用反斜杠 `\\` 进行转义：\n\n- 分隔符：`|` 和 `&`\n- 空格：` `（一个空格）\n- 点：`.`\n- 控制字符：制表符 `\\t`、换行符 `\\n`、回车符 `\\r`、换页符 `\\f`、垂直制表符 `\\v`\n\n### 转义规则\n| 原始字符 | 转义写法 |\n|----------|----------|\n| `|`      | `\\|`     |\n| `&`      | `\\&`     |\n| 空格     | `\\ `     |\n| `.`      | `\\.`     |\n| 制表符   | `\\t`     |\n| 换行符   | `\\n`     |\n| 回车符   | `\\r`     |\n| 换页符   | `\\f`     |\n| 垂直制表符 | `\\v`   |\n\n> 注意：反斜杠 `\\` 本身也是转义字符，若要表示字面反斜杠，需写作 `\\\\`。\n\n### 示例\n假设需要删除请求 body 中的两个 key：\n- key1 为 `\\n .p`（包含换行符、空格、点、字母 p）\n- key2 为 `test|&test`（包含竖线和 &）\n\n对应的路径表达式应写作：\n```\nreqBody.\\n\\ \\.p.test\\|\\&test\n```\n其中：\n- `\\n` 表示换行符\n- `\\ ` 表示空格\n- `\\.` 表示点\n- `\\|` 表示竖线\n- `\\&` 表示 & 符号\n\n完整请求示例：\n```\n# 删除请求 body 中的 key1 和 key2\nhttps://www.example.com/path delete://reqBody.\\n\\ \\.p.test\\|\\&test\n```","docs/docs/rules/disable.md":"# disable\n通过规则禁用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern disable://action1|action2|... [filters...]\n\n# 等效于：\npattern disable://action1 disable://action2 ... [filters...]\n```\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n- `301`：禁止 `301` 跳转，`301` 强制转 `302`（也可以用 [replaceStatus](./replaceStatus)）\n- `abort`：禁用 abort 功能，详见：[enable](./enable)\n- `abortReq`：禁用 abortReq 功能，详见：[enable](./enable)\n- `abortRes`：禁用 abortRes 功能，详见：[enable](./enable)\n- `authCapture`：禁用 `authCapture` 功能，详见：[enable](./enable)\n- `auto2http`：禁用 auto2http 功能，详见：[enable](./enable)\n- `autoCors`：使用 [file](./file) 协议替换请求时，如果 Whistle 检测到该请求属于跨域请求，会自动添加必要的 CORS (跨域资源共享) 头信息，可以通过 `disable://autoCors` 禁用\n- `ajax`: 删除请求头 `x-requested-with`\n- `bigData`：禁用 bigData 功能，详见：[enable](./enable)\n- `capture` 或 `https`：禁用 `Enable HTTPS`，详见：[enable](./enable)\n- `captureIp`：禁用 captureIp 功能，详见：[enable](./enable)\n- `captureStream`：禁用 captureStream 功能，详见：[enable](./enable)\n- `clientCert`：禁用 clientCert 功能，详见：[enable](./enable)\n- `clientId`：删除请求头 `x-whistle-client-id`\n- `clientIp`：删除请求头 `x-forwarded-for`\n- `customParser`：禁用 customParser 功能，详见：[enable](./enable)\n- `cache`: 禁用缓存\n- `dnsCache`: 禁止 DNS 缓存\n- `csp`: 禁用 CSP\n- `cookies`: 禁用请求和响应的 cookie\n- `reqCookies`: 禁用请求的 cookie\n- `resCookies`: 禁用响应的 cookie\n- `flushHeaders`：禁用 flushHeaders 功能，详见：[enable](./enable)\n- `forHttp`：禁用 forHttp 功能，详见：[enable](./enable)\n- `forHttps`：禁用 forHttps 功能，详见：[enable](./enable)\n- `forceReqWrite`：禁用 forceReqWrite 功能，详见：[enable](./enable)\n- `forceResWrite`： 禁用 forceResWrite 功能，详见：[enable](./enable)\n- `gzip`：禁止压缩响应内容\n- `h2`：禁用 h2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器启用 HTTP2\n- `http2`：禁用 http2 功能，详见：[enable](./enable)\n  > 禁用：浏览器 -xx-> Whistle 代理 -xx-> 服务器全部启用 HTTP2\n- `httpH2`：禁用 httpH2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：禁用 hide 功能，详见：[enable](./enable)\n- `hideComposer`：禁用 hideComposer 功能，详见：[enable](./enable)\n- `hideCaptureError`：禁用 hideCaptureError 功能，详见：[enable](./enable)\n- `interceptConsole`：禁用 interceptConsole 功能，详见：[enable](./enable)\n- `internalProxy`：禁用 internalProxy 功能，详见：[enable](./enable)\n- `proxyFirst`：禁用优先使用 [proxy](./proxy) 规则\n- `proxyHost`：禁用 proxyHost 功能，详见：[enable](./enable)\n- `proxyTunnel`：禁用 proxyTunnel 功能，详见：[enable](./enable)\n- `keepCSP`：禁用 keepCSP 功能，详见：[enable](./enable)\n- `keepAllCSP`：禁用 keepAllCSP 功能，详见：[enable](./enable)\n- `keepCache`：禁用 keepCache 功能，详见：[enable](./enable)\n- `keepAllCache`：禁用 keepAllCache 功能，详见：[enable](./enable)\n- `keepAlive`：禁止缓存请求连接\n- `keepClientId`：禁用 keepClientId 功能，详见：[enable](./enable)\n- `keepH2Session`：无需每个 TUNNEL 连接对应一个 HTTP2 连接（默认 `是`，建议使用默认即可）\n- `safeHtml`：禁用 safeHtml 功能，详见：[enable](./enable)\n- `strictHtml`：禁用 strictHtml 功能，详见：[enable](./enable)\n- `proxyConnection`：通过 proxy、socks 等代理协议转发请求时设置 `proxy-connection: close`\n- `ua`: 删除请求头 `user-agent`\n- `proxyUA`：通过 proxy、socks 等代理协议转发请求时删除请求头 `user-agent`\n- `referer`: 禁用 `referer`\n- `rejectUnauthorized`：`rejectUnauthorized` 设置为 `true`\n- `requestWithMatchedRules`：禁用 requestWithMatchedRules 功能，详见：[enable](./enable)\n- `responseWithMatchedRules`：禁用 responseWithMatchedRules 功能，详见：[enable](./enable)\n- `secureOptions`：禁用建立 HTTP2 连接的默认 `options`\n- `timeout`：禁用请求超时设置\n- `trailerHeader`：删除响应头 `trailer`\n- `trailers`：删除 trailers\n- `tunnelAuthHeader`：删除代理鉴权头 `proxy-authorization`\n- `tunnelHeadersFirst`：禁用 tunnelHeadersFirst 功能，详见：[enable](./enable)\n- `useLocalHost`：禁用 useLocalHost 功能，详见：[enable](./enable)\n- `useSafePort`：禁用 useSafePort 功能，详见：[enable](./enable)\n- `userLogin`：禁用设置 [statusCode://401](./statusCode) 时显示登录框\n- `useSniAsTunnelDomain`：禁用 `enable://useSniAsTunnelDomain`\n- `weakRule`：禁用 weakRule 功能，详见：[enable](./enable)\n\n## 配置示例\n``` txt\n# 禁用请求的缓存，同时删除请求和响应头的缓存字段\n# 跟 cache 协议的区别是，cache 只是用来设置响应的缓存头\nwwww.example.com/path disable://cache\n\n# 禁用请求和响应的 cookies\nwwww.example.com/path disable://cookies\n\n# 只禁用请求的 cookies\nwwww.example.com/path disable://reqCookies\n\n# 只禁用响应的 cookies\nwwww.example.com/path disable://resCookies\n\n# 删除 user-agent\nwwww.example.com/path disable://ua\n\n# 删除 referer\nwwww.test.com/path disable://referer\n\n# 删除csp策略\nwwww.test.com/path disable://csp\n\n# 禁用 timeout，默认情况下 Whistle 对每个请求如果 36s 内没有发生数据传输，会认为请求超时\nwwww.test.com/path disable://timeout\n\n# 把 301 转成 302，防止 cache\nwwww.test.com/path disable://301\n\n# 禁用 HTTPS 拦截\nwwww.test.com/path disable://capture\n\n# 不缓存 DNS 结果\nwwww.test.com/path disable://dnsCache\n\n# 禁用代理服务器请求链接复用\nwwww.test.com/path disable://keepAlive\n\n# 删除请求头 `x-requested-with`\nwwww.test.com/path disable://ajax\n\n# 也可以同时禁用多个\nwww.example.com/path disable://cache|cookies|ua|referer|csp|timeout|301|intercept|dnsCache|keepAlive|autoCors\n```\n\n关联操作：[enable](./enable)\n","docs/docs/rules/enable.md":"# enable\n通过规则启用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern enable://action1|action2|... [filters...]\n\n# 等效于：\npattern enable://action1 enable://action2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n- `abort`：请求或响应阶段中断请求（根据匹配阶段）\n- `abortReq`：请求阶段中断请求\n- `abortRes`：响应阶段中断请求\n- `authCapture`：强制在转成 HTTPS 之前执行 [auth hook](../extensions/dev#auth)（默认是转成 HTTPS 请求后再执行插件的 auth hook）\n- `auto2http`：开启 HTTPS 请求报 TLS 错误自动转成 HTTP 请求，默认情况下如果 serverIP 是本地 IP 会自动启用\n- `bigData`：扩大抓包数据显示限制(2M→16M)\n- `emudhra`：使用已公开泄露的 eMudhra 中间 CA 私钥签发动态生成的 TLS 证书，并发送完整证书链\n- `br`：启用 BR 压缩响应内容\n- `gzip`：启用 GZIP 压缩响应内容\n- `deflate`：启用 Deflate 压缩响应内容\n- `capture` 或 `https`：Enable HTTPS（同 [HTTPS菜单功能](../gui/https.html)）\n- `captureIp` ：域名为 IP 的请求，默认不解密 HTTPS 请求，可以通过 `enable://captureIp` 启用解析 HTTPS 请求\n- `captureStream`：将抓取到的请求与响应内容以数据流的形式，实时输出到抓包界面并动态追加显示\n- `clientCert`：启用客户端与服务器之间的双向认证 (mTLS)\n- `clientId`：请求头带上 `x-whistle-client-id: Whistle本地生成的唯一ID`\n- `clientIp`：为匹配的非本地请求设置 `x-forwarded-for` 请求头，将客户端的真实IP地址透传给上游服务\n- `customParser`：自定义抓包界面显示内容，用法参考插件：https://github.com/whistle-plugins/whistle.custom-parser\n- `flushHeaders`：`response.writeHead(...)` 后调用 [response.flushHeaders](https://nodejs.org/docs/latest/api/http.html#responseflushheaders)（默认执行）\n- `forHttp`：让 `capture` 功能只对 HTTP 请求生效\n- `forHttps`：让 `capture` 功能只对 HTTPS 请求生效\n- `forceReqWrite`：使用 [reqWrite](./reqWrite)、[reqWriteRaw](./reqWriteRaw) 将请求数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceReqWrite` 强制覆盖\n- `forceResWrite`： 使用 [resWrite](./resWrite)、[reqWriteRaw](./reqWriteRaw) 将响应数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceResWrite` 强制覆盖\n- `h2`：Whistle 代理 -> 服务器启用 HTTP2\n- `http2`：浏览器 -> Whistle 代理 -> 服务器全部启用 HTTP2\n- `httpH2`：Whistle 代理 -> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：在界面上隐藏抓包数据（不包括 `captureError` 和 Composer 发出的请求）\n- `hideComposer`：隐藏 Composer 发出的请求\n- `hideCaptureError`：隐藏 `captureError` 请求\n- `showHost`：将服务器 IP 设置到响应头 `x-host-ip`\n- `ignoreSend`：WebSocket 和 TUNNEL 请求时忽略发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `ignoreReceive`：WebSocket 和 TUNNEL 请求时忽略接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseSend`：WebSocket 和 TUNNEL 请求时暂停发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseReceive`：WebSocket 和 TUNNEL 请求时暂停接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `inspect`：使的在 Inspectors / Frames 看到 TUNNEL 请求的内容\n- `interceptConsole`：截获 `console.xxx` 的请求并显示在 Whistle 管理界面的 Log 面板（默认开启）\n- `internalProxy`：利用 `proxy`、`socks` 等代理协议将请求转发至其他代理服务器（如另一 Whistle 实例）。启用此功能后，已在第一层代理解密的 HTTPS 请求将以明文形式在代理链中传输，从而上游代理可以直接获取明文数据\n- `proxyFirst`：优先使用 [proxy](./proxy) 规则（默认情况下，同时匹配 `host` 和 `proxy`，只有 `host` 生效）\n- `proxyHost`：[proxy](./proxy) 和 [host](./host) 同时生效\n- `proxyTunnel`：跟 `proxyHost` 一同使用，让上游代理再次通过隧道代理到上上游的 HTTP 代理，详见下面的示例\n- `keepCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头大 `csp` 字段，如果想保留这些字段可以用 `enable://keepCSP`\n- `keepAllCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的 `csp` 字段，如果想保留这些字段可以用 `enable://keepAllCSP`\n- `keepCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepCache`\n- `keepAllCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepAllCache`\n- `keepClientId`：保留请求原有的 `x-whistle-client-id` 请求头（默认会删除请求带过来的  `x-whistle-client-id`）\n- `safeHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `{` 和 `[`（JSON 对象开头字符），如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `strictHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `<`，如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `multiClient`：Whistle 作为公共代理且启用 `enable://clientId` 时会为所有请求添加一个固定的 `x-whistle-client-id` 请求头，这导致上游服务无法区分不同客户端。启用 `enable://multiClient` 后，将为每个客户端连接生成并维持一个唯一且不变的标识符，确保上游服务能准确识别请求来源\n- `requestWithMatchedRules`：在请求头带上当前匹配的规则\n- `responseWithMatchedRules`：在响应头带上当前匹配的规则\n- `tunnelHeadersFirst`：用于控制请求头合并的优先级。插件可通过 [tunnelKey](../extensions/dev) 将隧道（TUNNEL）请求头传递至后续阶段。默认的合并规则是：若隧道头与解析后的普通请求头存在同名键，则保留普通请求头的值。启用 `enable://tunnelHeadersFirst` 可改变这一行为，确保隧道请求头优先，从而强制覆盖任何冲突的普通头\n- `useLocalHost`：修改 `log` 和 `weinre` 请求 `URL` 的域名，使用内置域名\n- `useSafePort`：修改 `log` 和 `weinre` 请求 `URL` 的端口，使用内置端口\n- `userLogin`：设置 [statusCode://401](./statusCode) 是否显示登录框（默认显示）\n- `useSniAsTunnelDomain`：当 Tunnel 域名与请求中的 SNI 不一致时，使用 SNI 中携带的域名作为 Tunnel 域名\n- `weakRule`：默认情况下，当配置了 [file](./file) 等协议时，[proxy](./proxy) 规则会自动失效。通过设置 `weakRule` 属性，可以提升 [proxy](./proxy) 规则的优先级，使其在上述场景中仍然生效\n- `socket`：在启用 HTTPS 解析（`Enable HTTPS` 或 `enable://https`）后，发往 `80/443` 端口的 TUNNEL 请求会被强制尝试解析为 HTTP/HTTPS 流量。默认情况下，若解析失败，该连接将被销毁；而其他端口的请求解析失败则会继续以 TUNNEL 方式传输。通过设置 `enable://socket`，可让发往 `80/443` 端口的请求在解析失败时同样降级为 TUNNEL 连接，避免连接被销毁\n- `websocket`：用于处理非标准 WebSocket 连接。某些请求虽使用 WebSocket 协议传输，但其 Upgrade 请求头并非标准值（如 `Upgrade: websocket`）。默认情况下，Whistle 会将其视为普通 TCP 连接而不解析数据。启用 `enable://websocket` 可强制 Whistle 识别此类连接为 WebSocket 协议并进行数据解析\n\n\n## 配置示例\n``` txt\n# Enable HTTPS\nwww.example.com enale://https\n\n# 延迟 3000毫秒终止请求\nwww.example.com/path reqDelay://3000 enable://abortReq\n\n# 延迟 5000毫米终止响应\nwww.example.com/path resDelay://5000 enable://abortRes\n\n# 本地替换的内容开启 GZIP\nwww.example.com/path file:///User/xxx/test enable://gzip\n\n# 给上游代理设置 hosts (10.10.10.20:8888)\nwww.example.com/path proxy://10.1.1.1:8080 10.10.10.20:8888 enable://proxyHost\n\n# 通过上游 HTTP 代理 (10.1.1.1:8080) 将请求通过隧道代理到指定的 HTTP 代理(10.10.10.20:8080)\nwww.example.com proxy://10.1.1.1:8080 10.10.10.20:8080 enable://proxyHost|proxyTunnel\n\n# 启用浏览器 -> Whistle 代理 -> 服务器整个链路的 HTTP2 功能\nwww.example.com enable://http2\n\n# 启用 Whistle 代理 -> 服务器的 HTTP2 功能\nwww.example.com enable://h2\n\n# 强制 Whistle 代理 -> 服务器的 HTTP 请求使用 HTTP2 传输\nwww.example.com enable://httpH2\n\n# 安全注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `{` 才注入\nwww.example.com/path enable://safeHtml\n\n# 严格HTML注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `<` 才注入\nwww.example.com/path enable://strictHtml\n\n# 自动添加 x-forwarded-for 请求头传递客户端真实 IP\nwww.example.com enable://clientIp\n\n# 扩大抓包数据显示限制(2M→16M)\nwww.example.com/path enable://bigData\n\n# 修改 log/weinre 请求 `URL` 的域名或端口\nwww.example.com/path enable://useLocalHost|useSafePort\n\n# 强制 reqWrite/reqWriteRaw/resWrite/resWriteRaw 覆盖已有的文件\nwww.example.com/path enable://forceReqWrite|forceResWrite\n\n# 强制 HTTPS 请求被解析前也走 `auth hook`（默认是转成 HTTPS 请求后再执行插件的 auth hook）\nwww.example.com enable://authCapture\n```\n\n关联操作：[disable](./disable)\n","docs/docs/rules/excludeFilter.md":"# excludeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation excludeFilter://p1 excludeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 且不能匹配 `p1`、`p2`、... 中的任一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求不生效\nwww.example.com/api/data proxy://127.0.0.1:8080 excludeFilter://m:GET excludeFilter://m:post\n```\n\n可以跟 [includeFilter](./includeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n","docs/docs/rules/file.md":"# file\n\n`file` 协议用于将请求映射到本地文件系统，用本地文件的内容作为响应返回给客户端，适用于以下场景：\n\n- **搭建本地开发环境**：快速搭建本地开发服务器\n- **调试本地前端页面**：直接调试本地 HTML、CSS、JavaScript 文件\n- **接口 Mock 场景**：使用本地 JSON 文件模拟 API 接口响应\n- **静态资源服务**：将本地目录作为静态资源服务器使用\n\n## 规则语法\n\n`file` 协议支持多种方式指定本地文件内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要返回的内容，适用于简短的文本内容。注意：`value` 不能包含空格和换行符。\n\n```txt\npattern file://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/api/data file://({\"status\":\"ok\"})\n```\n\n**注意：内联值必须用括号 `()` 包裹，否则会被识别为本地文件路径，可能导致 404 错误：**\n``` txt\n# 正确写法：返回字符串 \"success\"\npattern file://(success)\n\n# 错误写法：会被当作路径处理，可能导致 404\npattern file://success\n```\n\n> **file 协议的内联值语法要点**：\n> - 必须使用 `()` 包裹内容\n> - 内容不能包含空格和换行符\n> - 可以是字符串、JSON、HTML片段等\n> - 如果不加 `()`，Whistle 会将其解释为文件路径或 `{key}` 并尝试加载对应文件或 key 值\n\n### 2. 内嵌值（使用代码块）\n当需要处理包含空格、换行符的复杂内容，或希望复用某段配置时，推荐使用此方式。\n\n````txt\npattern file://{custom-key.json} [lineProps...] [filters...]\n\n``` custom-key.json\n{\n  \"status\": \"success\",\n  \"data\": {\n    \"message\": \"Hello from local file\"\n  }\n}\n```\n````\n\n**最佳实践**：建议为 `key` 添加响应类型后缀（如 `.json`、`.html`、`.css`），Whistle 会自动根据后缀设置正确的 `Content-Type`。\n\n### 3. 引用 Values 中的值\n当内容较大时，可以将其存储在 `Values` 配置区中。\n\n```txt\npattern file://{key-of-values.html} [lineProps...] [filters...]\n```\n\n**前提**：在 `Values` 中存在名为 `key-of-values.html` 的键，其值为要返回的内容。\n\n**容量建议**：\n- 小于 2KB 的内容：建议使用内联或内嵌方式\n- 2KB 至 200KB 的内容：建议存储在 `Values` 中\n- 大于 200KB 的内容：建议使用本地文件\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern file://temp.html\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `file://temp.html`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report file://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.html\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件路径或远程 URL 加载\n从本地文件系统或远程 URL 加载响应内容。\n\n```txt\n# 从本地文件加载\npattern file:///User/xxx/test.html\n\n# 从远程 URL 加载\npattern file://https://example.com/template.html\n```\n\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要返回的文件内容或路径，支持多种格式：<br>• 本地文件或目录路径<br>• 远程 URL<br>• 内联、内嵌、Values 引用内容 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>**示例**：<br>• `lineProps://important`：提升规则优先级<br>• `lineProps://weakRule`：当同时配置 [file](./file) 和 [proxy](./proxy) 规则时，默认 [proxy](./proxy) 会失效。通过此属性可提升 [proxy](./proxy) 规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n\n## 配置示例\n\n### 1. 基础文件路径映射\n```txt\n# 将域名映射到本地目录\nwww.example.com/path file:///Users/username/projects/my-site\n\n# Windows 系统路径\nwww.example.com/path file://D:\\projects\\my-site\n```\n\n**特性说明**：\n- **自动路径拼接**：访问 `https://www.example.com/path/x/y/z` 会映射到 `/Users/username/projects/my-site/x/y/z`\n- **禁用路径拼接**：使用 `< >` 包裹路径可以禁用自动拼接\n    ```txt\n    www.example.com/path file://</Users/username/projects/my-site/index.html>\n    ```\n    > 访问 `https://www.example.com/path/x/y/z` 只会返回 `/Users/username/projects/my-site/index.html` 文件\n\n### 2. JSONP 接口 Mock\n````txt\n# 内联方式\nwww.example.com/jsonp file://`(${query.callback}({\"status\":\"ok\"}))`\n\n# 内嵌方式\n``` jsonp-response\n${query.callback}({\n  \"status\": \"error\",\n  \"message\": \"Invalid parameter\"\n})\n```\nwww.example.com/jsonp file://`{jsonp-response}`\n````\n\n> **模板字符串限制**：模板字符串在以下场景无法直接生效：\n> - 引用本地文件路径时\n> - 引用远程 URL 地址时\n> \n> 遇到上述限制时，可以使用 [tpl](./tpl) 协议作为替代方案。\n\n### 3. 配合过滤器使用\n```txt\n# 排除特定接口\nwww.example.com/api/path file:///Users/username/mock-data excludeFilter://*/api/auth\n\n# 根据请求体内容匹配\nwww.example.com/api/search file:///Users/username/search-results.json includeFilter://b:/\"type\":\\s*\"advanced\"/i\n\n# 仅匹配 POST 请求\nwww.example.com/api/submit file:///Users/username/success.json includeFilter://m:POST\n```\n\n### 4. 多目录搜索\n```txt\n# 多个文件或目录用 `|` 分隔\nwww.example.com/static/path file:///path/to/project1|/path/to/project2|/path/to/project3\n```\n\n**查找逻辑**：\n1. 按顺序检查每个目录\n   - `/path/to/project1/static/file.js`\n   - `/path/to/project2/static/file.js`\n   - `/path/to/project3/static/file.js`\n2. 找到第一个存在的文件立即返回\n3. 全部未找到时返回 `404 Not Found`\n\n## 高级用法\n\n### 动态路径处理\n```txt\n# 使用正则表达式捕获组\n/^https?://www\\.example\\.com/user/(\\d+)/profile file:///Users/username/mock/profiles/user-$1.json\n\n# 如果不限制数字，可以用通配符\n^www.example.com/user/*/profile file:///Users/username/mock/profiles/user-$1.json\n```\n\n### 环境特定配置\n````txt\n``` dev-config\n{\n  \"apiBase\": \"http://localhost:3000\",\n  \"debugMode\": true\n}\n```\n\n``` prod-config\n{\n  \"apiBase\": \"https://api.example.com\",\n  \"debugMode\": false\n}\n```\n\n# 通过 cookie：env=dev 判断是否为开发环境\nwww.example.com/config file://{dev-config} includeFilter://reqH:cookie=/env=dev/\nwww.example.com/config file://{prod-config} # 默认正式环境\n````\n\n### 组合使用其他协议\n```txt\n# 先使用 file 提供静态文件，再设置响应头\nwww.example.com file:///Users/username/static-files cache://3600\n\n# 对于不存在的文件，使用 xfile 允许请求继续\nwww.example.com xfile:///Users/username/static-files\n```\n\n## 与 resBody 的区别\n\n`file` 协议与 [`resBody`](./resBody) 协议的主要区别在于请求处理流程和响应机制：\n\n**`file` 协议：***\n- **请求流程**：匹配 `file` 协议的请求**不会发送到后台服务器**\n- **响应机制**：直接返回指定文件内容，并响应 200 状态码\n- **网络开销**：零网络延迟，完全本地处理\n\n**示意图**：\n```\n客户端请求 → Whistle (匹配file规则) → 读取本地文件 → 返回响应 (200)\n                  ↓\n            (不发送到服务器)\n```\n\n**`resBody` 协议：**\n- **请求流程**：请求**会先发送到后台服务器**，获取原始响应\n- **响应机制**：收到服务器响应后，用指定内容**替换**原始响应体\n- **网络开销**：包含完整的请求-响应网络往返\n\n**示意图**：\n```\n客户端请求 → Whistle → 发送到服务器 → 收到原始响应 → 替换响应体 → 返回修改后的响应\n```\n\n## 关联协议\n\n1. **允许未匹配文件继续访问**：[xfile](./xfile)\n   - 当本地文件不存在时，允许请求继续访问原始服务器\n   - 适合静态资源服务器的开发场景\n\n2. **使用远程 URL 内容替换**：[https](./https) 或 [http](./http)\n   - 使用远程服务器的内容作为响应\n   - 注意：不建议使用 `file://https://xxx` 这种形式\n\n3. **域名解析重定向**：[host](./host)\n   - 修改域名解析，将请求导向指定 IP\n\n4. **模板渲染**：[tpl](./tpl)\n   - 支持更复杂的模板渲染和动态内容生成\n\n\n\n## 注意事项\n\n### 路径处理\n1. **相对路径**：除了插件内部的规则外，不建议使用相对路径。相对路径相对于 Whistle 配置文件所在目录。\n2. **绝对路径**：支持系统绝对路径\n3. **用户目录**：支持 `~` 表示用户主目录\n4. **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 文件编码\n1. **文本文件**：默认使用 UTF-8 编码\n2. **编码问题**：如遇乱码，请检查文件的实际编码格式\n\n\n## 故障排除\n\n### Q: 文件未找到（404）\n**A:** 检查：\n1. 文件路径是否正确\n2. 文件是否存在且有读取权限\n3. 是否使用了 `< >` 禁用了路径拼接\n\n### Q: 乱码问题\n**A:** 检查：\n1. 文件的实际编码格式\n2. 响应头的 `Content-Type` 是否正确\n3. 文件内容是否包含非法字符\n\n### Q: 规则未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配\n2. 是否有其他规则覆盖\n3. 过滤器条件是否满足\n\n### Q: 模板字符串不生效\n**A:** 检查：\n1. 是否在文件路径或远程 URL 场景下使用模板字符串\n2. 如果是，请改用 [tpl](./tpl) 协议\n\n\n\n## 扩展阅读\n\n- [匹配模式文档](./pattern)：详细了解 URL 匹配规则\n- [操作指令文档](./operation)：了解内容加载的多种方式\n- [附加配置](./lineProps)：详细了解一些特殊功能\n- [过滤器](./filters)：了解更多过滤器功能\n","docs/docs/rules/filters.md":"# 过滤器\n\n当需要基于请求属性或响应属性（而不仅仅是URL）进行匹配时，可以使用过滤器来实现更精细化的规则控制，语法结构：\n\n``` txt\npattern opertaion includeFilter://pattern1 ... excludeFilter://patternx ...\n```\n> 多个过滤器间为「或」匹配，只要匹配其中一个过滤条件就成立\n\n## 过滤器类型\n\n| 过滤器类型     | 语法格式                  | 用途                     |\n| -------------- | ------------------------- | ------------------------ |\n| **包含过滤器** | `includeFilter://pattern` | 只匹配符合指定条件的请求 |\n| **排除过滤器** | `excludeFilter://pattern` | 排除符合指定条件的请求   |\n\n## pattern 类型\n\n用于 `includeFilter://<pattern>` 和 `excludeFilter://<pattern>` 的匹配表达式。\n\n> **通用规则**\n> - 未指定前缀时，默认匹配请求 URL。\n> - 关键字匹配默认不区分大小写。\n> - 正则格式为 `/regexp/`，末尾可加 `i` 表示忽略大小写，例如 `/regexp/i`。\n> - `chance` 的 `probability` 取值范围为 `0 ~ 1`。\n\n| 语法 | 匹配目标 | 示例 |\n| --- | --- | --- |\n| `b:pattern` | 请求体内容：关键字（不区分大小写）或正则 | `includeFilter://b:keyword`<br>`excludeFilter://b:/regexp/i` |\n| `m:pattern` | HTTP 方法：方法名（不区分大小写）或正则 | `includeFilter://m:get`<br>`excludeFilter://m:/regexp/i` |\n| `i:pattern` | 客户端或服务端 IP：IP 或正则 | `includeFilter://i:192.168.1.10`<br>`excludeFilter://i:/regexp/i` |\n| `chance:probability` | 按概率随机命中：`Math.random() < probability` | `includeFilter://chance:0.5`<br>`excludeFilter://chance:0.3` |\n| `clientIp:pattern` | 客户端 IP：IP 或正则 | `includeFilter://clientIp:192.168.1.10`<br>`excludeFilter://clientIp:/regexp/i` |\n| `serverIp:pattern` | 服务端 IP：IP 或正则 | `includeFilter://serverIp:192.168.1.10`<br>`excludeFilter://serverIp:/regexp/i` |\n| `s:pattern` | 响应状态码：状态码或正则 | `includeFilter://s:/^20/`<br>`excludeFilter://s:200` |\n| `reqH.header-key:pattern` | 请求头：关键字（不区分大小写）或正则 | `includeFilter://reqH.content-type:json`<br>`excludeFilter://reqH.content-type:/regexp/i` |\n| `resH.header-key:pattern` | 响应头：关键字（不区分大小写）或正则 | `includeFilter://resH.content-type:json`<br>`excludeFilter://resH.content-type:/regexp/i` |\n| 无前缀 `pattern`（其它 `xxxxxx`） | 请求 URL，同 [pattern](./pattern) | `includeFilter://*/cgi-*`<br>`excludeFilter://www.test.com`<br>`includeFilter://https://www.test.com/path` |\n","docs/docs/rules/forwardedFor.md":"# forwardedFor\n修改请求头 `x-forwarded-for` 字段，自定义客户端 IP。\n\n## 规则语法\n``` txt\npattern forwardedFor://ip [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| ip   | 自定义客户端 IP<br/>⚠️ 不支持从文件/远程 URL 加载数据 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 设置固定 IP\nwww.example.com/path  forwardedFor://1.1.1.1\n\n# 使用真实客户端 IP（透传模式）\nwww.example.com  forwardedFor://`${clientIp}`\n```\n\n## 关联协议\n1. 直接修改请求头：[reqHeaders://x-forwarded-for=value](./reqHeaders)（这种方式的 `value` 可以不局限于 IP）\n2. 启用自动设置 `x-forwarded-for`：[enable://clientIp](./enable)\n","docs/docs/rules/frameScript.md":"# frameScript\n通过 JavaScript 脚本操作 WebSocket 和普通 TCP 请求数据帧。\n\n## 规则语法\n``` txt\npattern frameScript://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 生成规则的 JS 脚本，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n```` txt\n``` test-frame.js\n// 发送预设数据\nctx.sendToServer('1 = 0x12300000');\nctx.sendToClient('1 = 0x1236666666');\n\n// 处理发往服务端的数据帧\nctx.handleSendToServerFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '***');\n};\n\n// 处理发往客户端的数据帧  \nctx.handleSendToClientFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '+++');\n};\n```\n\nwss://echo.websocket.org/ frameScript://{test-frame.js}\n````\n访问 `https://echo.websocket.org/.ws` 效果：\n\n <img width=\"1200\" alt=\"frame-script\" src=\"/img/frame-script.png\" />\n\n#### 可用全局变量\n\n| 变量/方法          | 描述                                                                 |\n|--------------------|---------------------------------------------------------------------|\n| `url`             | 完整请求URL                                                         |\n| `method`          | 请求方法(GET/POST等)                                                |\n| `ip`/`clientIp`   | 客户端IP地址                                                       |\n| `headers`         | 请求头对象                                                          |\n| `rules`           | 规则数组，通过push添加新规则                                        |\n| `values`          | 临时值存储对象                                                      |\n| `render(tpl,data)`| 微型模板渲染函数                                                    |\n| `getValue(key)`   | 获取Values中的值                                                    |\n| `parseUrl`        | 同Node.js的`url.parse`                                              |\n| `parseQuery`      | 同Node.js的`querystring.parse`                                      |\n| `ctx.sendToClient(frame, options)` | 发送数据（对象、字符串、Buffer）到客户端，用法见下面示例 |\n| `ctx.sendToServer(frame, options)` | 发送数据（对象、字符串、Buffer）到服务端，用法见下面示例 |\n| `ctx.destroy()` | 断开当前连接（`v2.10.11` 新增） |\n| `handleSendToClientFrame(buffer, options)` | 处理（过滤或删除）发送到客户端的数据，用法见下面示例 |\n| `handleSendToServerFrame(buffer, options)` | 处理（过滤或删除）发送到服务端的数据，用法见下面示例 |\n\n> 注意：`handleSendToClientFrame` 内不可调用 `ctx.sendToClient`；`handleSendToServerFrame` 内不可调用 `ctx.sendToServer`\n","docs/docs/rules/headerReplace.md":"# headerReplace\n通过关键字匹配或正则表达式替换指定请求头/响应头。\n\n## 规则语法\n``` txt\npattern headerReplace://value [filters...]\n```\n> `header-name` 不区分大小写\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 三种情况：<br/>• `req.header-name:p1=v1&p2=v2`<br/>• `res.header-name:p1=v1&p2=v2`<br/>• `trailer.trailer-name:p1=v1&p2=v2` | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 请求头 `accept` 字段值的第一个 `html` 关键字被改成 `abc`\nwww.example.com/path headerReplace://req.accept:html=abc\n\n# 请求头 `accept` 字段值的所有 `ml` 关键字被改成 `abc`\nwww.example.com/path2 headerReplace://req.accept:/ml/g=abc\n\n# 修改响应头\nwww.example.com/path3 headerReplace://res.Content-Type:html=plain\n```\n\n`headerReplace` 是用来替换请求/响应头的局部内容，如果要修改请求/响应字段内容，还可以用：\n- 设置请求头：[reqHeaders](./reqHeaders)\n- 设置响应头：[resHeaders](./resHeaders)\n","docs/docs/rules/host.md":"# host\n修改请求的 DNS 解析结果，将指定请求解析到特定 IP 地址（域名）及端口，可以看作终极版的系统 hosts 配置功能。\n\n## 规则语法\n``` txt\npattern host://ipOrDomain[:port] [filters...]\n```\n> `port` 如果不填，则沿用请求 URL 的原始端口，如果是指向域名则相当于 `cname` 功能\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 如果只是 IP 或端口可以省略 `host://`\nwww.example.com/test0 127.0.0.1      # 不改端口，沿用请求 URL 的原始端口\nwww.example.com/test1 127.0.0.1:5173\n\n# CNAME 功能\nwww.example.com/test2 host://www.test.com\nwww.example.com/test3 host://www.test.com:8080\n\n# 高级配置，从请求参数获取目标地址，includeFilter 确保存在该参数\nwww.example.com/test4 host://`${query.target}$:8080` includeFilter:///[?&]target=[\\w-]+/i\n```\n## 与 proxy 的匹配优先级\n以下是优化后的文档，结构更清晰，语言更简洁准确：\n\n---\n\n## 与 host 的匹配优先级\n\n#### 默认行为\n\n当请求同时匹配 `host` 和 `proxy` 规则时：\n- 仅 `host` 规则生效\n- `proxy` 规则自动忽略\n\n#### 修改优先级\n| 配置方式 | 语法 | 效果 |\n|---------|------|------|\n| **优先 proxy** | [`enable://proxyFirst`](./enable) 或 [`lineProps://proxyFirst`](./lineProps) | 仅 `proxy` 生效（覆盖 host） |\n| **同时生效** | [`enable://proxyHost`](./enable) 或 [`lineProps://proxyHost`](./lineProps) | `proxy` 和 `host` 同时生效 |\n\n#### 使用建议\n- 大多数场景使用默认行为即可\n- 需要特殊代理逻辑时才使用 `proxyFirst`\n- 需要双重匹配时使用 `proxyHost`\n\n## 注意事项\n`host` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com 127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求指向 `127.0.0.1:1234`，但由于 `host` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `host` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com 127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `host` 规则匹配，最终指向 `127.0.0.1:1234`。\n\n\n## 常见问题\n1. 与 URL 转换的区别：\n    ``` txt\n    # 服务端收到的 URL 还是 www.example.com\n    www.example.com 127.0.0.1:5173\n\n    # 服务端收到的 URL 是 http://127.0.0.1:5173\n    www.example.com http://127.0.0.1:5173\n    ```\n2. 自动降级为 HTTP 请求：如果配置的目标 IP 为 `127.0.0.1`，且 HTTPS 请求报错会自动降级为 HTTP 请求，方便访问本地服务，用户可以通过以下规则禁用该功能：\n    ``` txt\n    # 禁用本地 HTTPS 自动降级\n    pattern disable://auto2http\n    ```\n","docs/docs/rules/htmlAppend.md":"# htmlAppend\n在现有响应内容体后面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlAppend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlAppend://(Hello) file://(-test-)\nwww.example.com/path2 htmlAppend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlAppend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlAppend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlAppend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlAppend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlAppend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlAppend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `<div>Test</div>test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlAppend:///User/xxx/test.txt\nwww.example.com/path2 htmlAppend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlAppend://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/htmlBody.md":"# htmlBody\n\n将现有响应内容体t替换成指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlBody://(Hello) file://(-test-)\nwww.example.com/path2 htmlBody://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlBody://{body.txt} file://(-test-)\nwww.example.com/path2 htmlBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlBody://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlBody://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlBody://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlBody://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlBody:///User/xxx/test.txt\nwww.example.com/path2 htmlBody://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlBody://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n","docs/docs/rules/htmlPrepend.md":"# htmlPrepend\n在现有响应内容体前面面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlPrepend://(Hello) file://(-test-)\nwww.example.com/path2 htmlPrepend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello world.-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlPrepend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlPrepend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlPrepend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlPrepend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    test<div>Test</div>\n    ```\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlPrepend:///User/xxx/test.txt\nwww.example.com/path2 htmlPrepend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlPrepend://temp/blank.txt\n````\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n","docs/docs/rules/http.md":"# http\n将以下三种请求转换为 HTTP 请求（服务端将收到转换后的 HTTP URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern http://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 http://www.test.com/path/xxx\nhttps://www.example.com/path2 http://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `http://www.example.com/path1`              | `http://www.test.com/path/xxx`             |\n    | `http://www.example.com/path1/a/b/c?query`  | `http://www.test.com/path/xxx/a/b/c?query` |\n    | `https://www.example.com/path2`            | `http://www.abc.com/path3/yyy`             |\n    | `https://www.example.com/path2/a/b/c?query` | `http://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 http://<www.test.com/path/xxx>\n    # www.example.com/path1 http://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `http://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 http://www.test.com/path/xxx\nwss://www.example.com/path2 http://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `ws://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `ws://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 http://www.test.com:123\ntunnel://www.example2.com:443 http://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:80`|\n\n自动忽略匹配 URL 的路径，HTTP 协议默认端口为 `80`，HTTPS 协议默认端口为 `443`\n\n\n","docs/docs/rules/https-proxy.md":"# https-proxy\n`https-proxy` 指令用于将匹配的请求通过指定的 HTTPS 代理服务器转发。\n\n## 规则语法\n``` txt\npattern https-proxy://ipOrDomain[:port] [filters...]\n```\n> `port` 可选，不填则使用默认端口 `443`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 将请求代理到 HTTPS 代理: `127.0.0.1:443`\nwww.example.com/path https-proxy://127.0.0.1 # 默认端口 443\n\n# 将当前域名的所有请求代理到 HTTPS 代理: `127.0.0.1:8080`\nwww.example.com https-proxy://127.0.0.1:8080\n\n# 也可以用域名\nwww.example.com/path https-proxy://test.proxy.com # 默认端口 443\nwww.example.com https-proxy://test.proxy.com:8080\n```\n\n## 高级用法\n默认情况下，上游代理会自行解析请求的域名。但某些场景下，你可能希望强制代理直接访问指定的目标 IP（跳过 DNS 解析），例如：\n- 绕过 DNS 污染\n- 直接访问特定后端 IP\n- 测试不同环境的服务\n``` txt\n# 通过查询参数\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1:8080\n\n# 通过指令启用\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1 enable://proxyHost\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1:8080 enable://proxyHost\n```\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n## 注意事项\n`https-proxy` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com https-proxy://127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求代理到 `127.0.0.1:1234`，但由于 `https-proxy` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `https-proxy` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com https-proxy://127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `https-proxy` 规则匹配，最终代理到 `127.0.0.1:1234`。\n","docs/docs/rules/https.md":"# https\n将以下三种请求转换为 HTTPS 请求（服务端将收到转换后的 HTTPS URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern https://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 https://www.test.com/path/xxx\nhttps://www.example.com/path2 https://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n     | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n     | ----------------------------------------- | ----------------------------------------- |\n     | `http://www.example.com/path1`              | `https://www.test.com/path/xxx`             |\n     | `http://www.example.com/path1/a/b/c?query`  | `https://www.test.com/path/xxx/a/b/c?query` |\n     | `https://www.example.com/path2`            | `https://www.abc.com/path3/yyy`             |\n     | `https://www.example.com/path2/a/b/c?query` | `https://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 https://<www.test.com/path/xxx>\n    # www.example.com/path1 https://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `https://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 https://www.test.com/path/xxx\nwss://www.example.com/path2 https://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `wss://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `wss://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 https://www.test.com:123\ntunnel://www.example2.com:443 https://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:443`|\n\n⚠️ 自动忽略匹配 URL 的路径，HTTPS 协议默认端口为 `443`\n\n","docs/docs/rules/ignore.md":"# ignore\n用于忽略指定协议的匹配规则，也可以忽略当前配置的匹配规则。\n\n## 规则语法\n``` txt\npattern ignore://p1|p2|... [filters...]\n# 等效于：\npattern ignore://p1 ignore://p2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作协议（`protocol`）名称<br/>`*` 表示所以协议<br/>`-p` 表示排除 `p`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作协议列表](./protocols)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 忽略所有规则，只保留 `file` 协议的规则\nwww.example.com/path ignore://*|-file\n\n# 忽略指定协议：file、host\nwww.example.com/path2 ignore://file|host\n```\n\n## 常见问题\n`ignore` 是根据指定协议忽略已匹配的规则，用这种方式忽略的规则不会继续匹配同类型的其他规则，如果想让请求忽略某个规则后再继续尝试匹配剩余的同类型规则可以采用 [skip](./skip)。\n\n","docs/docs/rules/includeFilter.md":"# includeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation includeFilter://p1 includeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 及 `p1`、`p2`、... 中的一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求生效  \nwww.example.com/api/data proxy://127.0.0.1:8080 includeFilter://m:GET includeFilter://m:POST\n```\n\n可以跟 [excludeFilter](./excludeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n","docs/docs/rules/inherit.md":"# 协议继承\n[http](./http)/[https](./https)/[ws](./ws)/[wss](./wss)/[tunnel](./tunnel) 都限制了目标 URL 的协议，Whistle 也支持根据请求 URL 的协议自动补全。\n## 规则语法\n``` txt\npattern //value [filters...]\n# 或\npattern value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 www.test.com/path/xxx\nhttps://www.example.com/path2 www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `http://www.example.com/path1`              | `http://www.test.com/path/xxx`             |\n    | `http://www.example.com/path1/a/b/c?query`  | `http://www.test.com/path/xxx/a/b/c?query` |\n    | `https://www.example.com/path2`            | `https://www.abc.com/path3/yyy`             |\n    | `https://www.example.com/path2/a/b/c?query` | `https://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 //<www.test.com/path/xxx>\n    # www.example.com/path1 //(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `[http/https/wss/ws]://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 www.test.com/path/xxx\nwss://www.example.com/path2 www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `ws://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `wss://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 //www.test.com:123\ntunnel://www.example2.com:443 www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:443`|\n\n","docs/docs/rules/jsAppend.md":"# jsAppend\n在现有响应内容体后面插入指定内容（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsAppend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsAppend://(Hello) file://(-test-)\nwww.example.com/path2 jsAppend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsAppend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-<script>Hello</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-Hello`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsAppend://{body.txt} file://(-test-)\nwww.example.com/path2 jsAppend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsAppend://{body.txt}) file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-<script>Hello world.</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-Hello world.`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsAppend:///User/xxx/test.js\nwww.example.com/path2 jsAppend://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsAppend://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsAppend` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsAppend://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsAppend://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsAppend://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsAppend://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsAppend://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","docs/docs/rules/jsBody.md":"# jsBody\n将现有响应内容体t替换成指定内容（（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsBody://(Hello) file://(-test-)\nwww.example.com/path2 jsBody://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsBody://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<script>Hello</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsBody://{body.txt} file://(-test-)\nwww.example.com/path2 jsBody://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<script>Hello world.</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello world.`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsBody:///User/xxx/test.js\nwww.example.com/path2 jsBody://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsBody://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsBody` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsBody://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsBody://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsBody://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsBody://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsBody://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n","docs/docs/rules/jsPrepend.md":"# jsPrepend\n在现有响应内容体前面插入指定内容（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsPrepend://(Hello) file://(-test-)\nwww.example.com/path2 jsPrepend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsPrepend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <script>Hello</script>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 jsPrepend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsPrepend://{body.txt}) file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <script>Hello world.</script>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello world.-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsPrepend:///User/xxx/test.js\nwww.example.com/path2 jsPrepend://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsPrepend://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsPrepend` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n\n","docs/docs/rules/lineProps.md":"# lineProps\n通过规则启用 proxyHost、proxyTunnel、safeHtml 等功能。\n> 📌 与 [enable](./enable) 的区别：\n>\n> `enable` 是全局生效的配置\n>\n> `lineProps` 只对配置所在行的规则生效\n\n## 规则语法\n``` txt\npattern operation lineProps://action1|action2|... [filters...]\n\n# 等效于：\npattern operation lineProps://action1 lineProps://action2 ... [filters...]\n```\n> `lineProps` 不能单独作为 `operation` 使用，且只对同一行的 `operation` 生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令                          | [操作指令文档](./operation)   |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n- `important`：类似于 CSS 的 !important，用于提升规则优先级，且仅对同类协议的操作生效\n- `safeHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `{` 和 `[`（JSON 对象开头字符），如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `strictHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `<`，如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `disableAutoCors`：禁用 [file](./file) 协议替换请求时自动添加必要的 CORS (跨域资源共享) 头信息\n- `disableUserLogin`：禁用设置 [statusCode://401](./statusCode) 时显示登录框\n- `enableUserLogin`：设置 [statusCode://401](./statusCode) 是否显示登录框（默认显示，用于关闭 `disable.userLogin`）\n- `internal`：将 `proxy`、`socks`、`host` 协议掉规则同时作用于 Whistle 内部请求\n- `internalOnly`：将 `proxy`、`socks`、`host` 协议掉规则只作用于 Whistle 内部请求\n- `internalProxy`：利用 `proxy`、`socks` 等代理协议将请求转发至其他代理服务器（如另一 Whistle 实例）。启用此功能后，已在第一层代理解密的 HTTPS 请求将以明文形式在代理链中传输，从而上游代理可以直接获取明文数据\n- `proxyFirst`：优先使用 [proxy](./proxy) 规则（默认情况下，同时匹配 `host` 和 `proxy`，只有 `host` 生效）\n- `proxyHost`：[proxy](./proxy) 和 [host](./host) 同时生效\n- `proxyHostOnly`：功能同 `proxyHost`，但如果没有匹配 [host](./host) 则 [proxy](./proxy) 自动失效\n- `proxyTunnel`：跟 `proxyHost` 一同使用，让上游代理再次通过隧道代理到上上游的 HTTP 代理，详见下面的示例\n- `weakRule`：默认情况下，当配置了 [file](./file) 等协议时，[proxy](./proxy) 规则会自动失效。通过设置 `weakRule` 属性，可以提升 [proxy](./proxy) 规则的优先级，使其在上述场景中仍然生效\n- `enableBigData`：支持 [reqMerge](./reqMerge) 或 [resMerge](./resMerge) 匹配大的请求/响应内容\n\n## 配置示例\n#### 未使用 `lineProps://important`\n``` txt\nwww.example.com/path file:///User/xxx/important1.html\nwww.example.com/path file:///User/xxx/important2.html\n```\n访问 `https://www.example.com/path ` 将匹配 `file:///User/xxx/important1.html`\n\n#### 使用 `lineProps://important`\n``` txt\nwww.example.com/path file:///User/xxx/important1.html\nwww.example.com/path file:///User/xxx/important2.html lineProps://important\n```\n访问 `https://www.example.com/path ` 将匹配 `file:///User/xxx/important2.html`\n\n#### 注入文本\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) \nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path ` 返回响应内容：\n``` html\n<!DOCTYPE html>\n<style>alert(1)</style>\nalert(1)\n<script>alert(1)</script>test\n```\n\n#### 使用 `enable://strictHtml`\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) enable://strictHtml\nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path` 返回响应内容：\n``` html\ntest\n```\n> `enable://strictHtml` 对所有规则都生效\n\n### 使用 `lineProps://strictHtml`\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) lineProps://strictHtml\nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path` 返回响应内容：\n``` html\n<!DOCTYPE html>\n<style>alert(1)</style>\nalert(1)test\n```\n> `lineProps://strictHtml` 只对所在行的规则生效\n","docs/docs/rules/locationHref.md":"# locationHref\n针对无法通过服务端重定向（`302`/`301`）的场景，通过在 HTML 页面中返回 JavaScript 代码 `window.location.href = targetUrl` 实现客户端跳转。特别适用于：\n- 本地HTML文件加载的APP页面\n- 单页应用(SPA)\n- 特殊框架开发的混合应用(Hybrid App)\n\n## 规则语法\n``` txt\npattern locationHref://targetUrl [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| targetUrl   | 重定向后的 URL，可以是相对路径 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 基础配置\n``` txt\nwww.example.com/path locationHref://https://www.qq.com\n\nwww.example.com/path2 locationHref://../abc/123\n```\n- 访问 `https://www.example.com/path/to` 重定向到 `https://www.qq.com`（不自动拼接路径）\n- 访问 `https://www.example.com/path2/to` 重定向到 `https://www.example.com/abc/123`\n\n#### 实现路径拼接\n``` txt\n# 通配符\n^www.example.com/path/*** locationHref://`https://www.example.com/$1`\n```\n- 访问 `https://www.example.com/path/to?query` 重定向到 `https://www.example.com/to?query`\n\n#### location.replace\n若需通过 `location.replace(targetUrl)` 实现无历史记录的页面跳转，可按以下格式配置：\n``` txt\nwww.example.com/path locationHref://replace:https://www.qq.com\n```\n> 跳转至目标 URL，且当前页面不会存入浏览器历史记录，用户无法通过“返回”按钮回到原页面\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","docs/docs/rules/log.md":"# log 规则\n\n自动在页面中注入 JavaScript 代码，捕获 JavaScript 异常及 `console.xxx` 日志，并在 Whistle 管理界面中实时显示。\n\n## 功能概述\n- **异常监控**：自动捕获页面中的 JavaScript 执行错误和未处理的 Promise 异常。\n- **日志收集**：拦截并收集所有 `console.log()`、`console.warn()`、`console.error()` 等控制台输出。\n- **可视化查看**：在 Whistle 管理界面中按分组实时查看、筛选和搜索日志。\n- **日志预处理**：支持通过自定义脚本对日志内容进行过滤或格式化处理。\n\n## 规则语法\n```\npattern log://id [filters...]\n```\n\n### 参数说明\n| 参数     | 是否必填 | 描述                                                                 | 详细说明                                                                 |\n|----------|----------|----------------------------------------------------------------------|--------------------------------------------------------------------------|\n| pattern  | 是       | 匹配请求 URL 的表达式，用于指定需要监控的页面                         | [匹配模式语法](./pattern)                                                |\n| id       | 是       | 日志分组标识（普通字符串），用于在 Whistle 界面中区分和筛选不同来源的日志 | 同一分组下的日志会集中显示，支持按 ID 快速切换视图                       |\n| filters  | 否       | 过滤器条件，用于进一步匹配请求或响应的特定特征                         | [过滤器语法](./filters) <br> 支持按 URL、方法、请求头、响应状态码等过滤 |\n\n## 使用方法\n\n### 基础用法\n捕获指定域名下所有页面的日志：\n```\nwww.example.com log://myapp\n```\n\n### 多域名分组监控\n为不同域名或路径设置不同的日志分组，便于分类查看：\n```\nke.qq.com log://ke\nnews.qq.com log://news\napi.example.com log://api\n```\n\n## 高级配置：日志预处理\n\n通过 `jsPrepend` 规则注入自定义脚本，可在日志发送到 Whistle 前进行预处理，例如敏感信息脱敏、格式转换或条件过滤。\n\n### 配置示例\n```\nwww.example.com log:// jsPrepend://{handleLog.js}\n```\n\n### 预处理脚本示例\n```javascript\n// handleLog.js\n// 在日志发送到 Whistle 前执行的自定义处理函数\nwindow.onBeforeWhistleLogSend = function(result, level) {\n  // result: 待输出的原始信息数组\n  // level: 日志级别 ('log', 'warn', 'error', 'info', 'debug')\n  \n  result.forEach(function(msg, i) {\n    // 示例1：隐藏敏感关键词\n    if (typeof msg === 'string' && msg.includes('password')) {\n      result[i] = '[SENSITIVE DATA HIDDEN]';\n    }\n    \n    // 示例2：特定内容转换为结构化对象\n    if (msg === 'abc') {\n      result[i] = {\n        name: 'avenwu',\n        level: level,\n        timestamp: new Date().toISOString()\n      };\n    }\n    \n    // 示例3：过滤掉某些类型的日志\n    if (msg === 'ignore-this-message') {\n      // 返回空数组将阻止此条日志发送\n      result.splice(i, 1);\n    }\n  });\n};\n```\n\n## 界面操作指南\n\n### 查看日志\n1. 打开 Whistle 管理界面（默认为 `http://127.0.0.1:8899`）\n2. 切换到 **Network / Tools / Console** 标签页\n3. 在左上角选择对应的日志分组 ID\n4. 实时查看页面输出的日志和错误信息\n\n### 日志筛选\n- 使用顶部的筛选栏可按日志级别（Log/Warn/Error）快速过滤\n- 支持关键词搜索，快速定位特定日志\n","docs/docs/rules/method.md":"# method\n修改请求方法。\n\n## 规则语法\n``` txt\npattern method://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | `get`/`post`/`head` 等请求方法名称（不区分大小写） |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 访问 `https://www.example.com/path/to` 在 Whistle 抓包界面上可以看到请求方法为 `POST`\nwww.example.com/path method://post\n\n# 带过滤条件的方法修改\nwww.example.com/path2 method://patch includeFilter://reqH.content-type=multipart/form-data\n```\n","docs/docs/rules/operation.md":"# operation\n\n在 Whistle 中，每条规则由 匹配模式（`pattern`） 和 操作（`operation`） 两部分组成，其中 `operation` 的通用语法为：\n\n``` txt\nprotocol://[value]\n```\n- **protocol**：指定操作类型（如 `file`、`proxy`、`resReplace` 等）\n- **value**：操作内容（支持多种格式，见下文）\n\n## 内联值\n``` txt\npattern reqHeaders://x-proxy=Whistle   # 设置请求头\npattern statusCode://404               # 修改状态码\npattern file://({\"ec\":0})              # 用内联内容（小括号里面的值：`{\"ec\":0}`）响应请求\n```\n\n当操作内容（Value）包含空格、换行符或特殊字符时，无法直接使用内联方式（Inline），需改用以下方法：\n\n## 内嵌值\n\n```` txt\n``` ua.txt\nMozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1\n```\npattern ua://{ua.txt}\n````\n\n等价于\n\n```` txt\n``` headers.json\nuser-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1\n```\npattern reqHeaders://{headers.json}\n````\n\n## Values 引用\n\n当操作值（Value）需要被多个规则共享时，直接内嵌在规则中会导致无法复用。此时可以将这些值存储在 Whistle 界面的 Values 模块中，并通过键名引用：\n1. 在 Values 里面创建一个名为 `result.json` 的 key 后，填入操作内容\n2. 在规则里面即可通过 `{result.json}` 的方式引用，如：`www.test.com/cgi-bin/test file://{result.json}`\n\n<img src=\"/img//values-demo1.png\" width=\"420\" />\n\n## 文件/远程资源\n``` txt\npattern reqHeaders:///User/xxx/filepath             # 从本地文件加载操作内容\npattern resHeadrs://https://example.com/config.json # 从远处加载 JSON 对象\npattern resHeaders://temp/blank.json                # 通过边境临时文件\n```\n> ⚠️ 注意：http/https/ws/wss/tunnel/host/enable/cache... 等协议禁止通过文件路径或远程 URL 获取内容，详见各协议文档。\n\n## 临时文件\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern protocol://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `protocol://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n## 小括号用途\n在 Whistle 规则中，protocol://value 的 value 部分可能有三种间接引用类型：\n1. `{key}` - 引用内嵌值\n2. `remote-url` - 远程资源地址\n3. `localfilepath` - 本地文件路径\n\n当需要直接引用上述内容本身（而非它们间接引用内容）作为操作内容时，可以用小括号包裹：\n``` txt\nprotocol://(value)\n```\n\n示例：\n1. `reqHeaders:///User/xxx/yyy.txt` - 从本地文件 `/User/xxx/yyy.txt` 加载操作内容\n2. `reqHeaders://(/User/xxx/yyy.txt)` - 将 `/User/xxx/yyy.txt` 直接作为操作内容\n\n## 模板字符串\nWhistle 提供了类似 ES6 的模板字符串功能，允许您动态引用请求信息并应用到规则配置中。支持以下几种模板字符串：\n\n##### 一般内敛值\n``` txt\npattern protocol://`...${version}...`\n```\n\n##### 内嵌值或 Values 引用\n```` txt\n\n``` test.key\n...${reqId}...\n...${version}...\n```\npattern protocol://`{test.key}`\n````\n\n##### 小括号内容\n```` txt\npattern protocol://`(...${now}...)`\n````\n\n##### 字符串变量\n\n| 变量名称              | 取值                                                         |\n| --------------------- | ------------------------------------------------------------ |\n| `${now}`                 | Date.now()                                                   |\n| `${random}`              | Math.random()                                                |\n| `${randomUUID}`          | crypto.randomUUID()                      |\n| `${randomInt(n)}` 或 `${randomInt(n1-n2)}` | 从 [0, n] 或 [n1, n2] 取一个随机正整数 (Added in: v2.9.104)|\n| `${reqId}`               | Whistle 给每个请求分配的 ID                                  |\n| `${url.protocol}`        | url.parse(fullUrl).protocol                                  |\n| `${url.hostname}`        | url.parse(fullUrl).hostname                                  |\n| `${url.host}`            | url.parse(fullUrl).host                                      |\n| `${url.port}`            | url.parse(fullUrl).port                                      |\n| `${url.path}`            | url.parse(fullUrl).path                                      |\n| `${url.pathname}`        | url.parse(fullUrl).pathname                                  |\n| `${url.search}`          | url.parse(fullUrl).search                                    |\n| `${query.xxx}`           | 请求参数 `xxx` 的值                                          |\n| `${url}`                 | 请求完整 URL                                                 |\n| `${querystring}`         | url.parse(fullUrl).search \\|\\| '?'（不为空）                 |\n| `${searchstring}`        | url.parse(fullUrl).search \\|\\| '?'（不为空）                 |\n| `${method}`              | 请求方法                                                     |\n| `${reqHeaders.xxx}`      | 请求头字段 `xxx` 的值                                        |\n| `${resHeaders.xxx}`      | 响应头字段 `xxx` 的值                                        |\n| `${version}`             | Whistle 版本号                                               |\n| `${port}`                | Whitle 端口号                                                |\n| `${host}`                | Whistle 启动时监听的网卡 IP（默认为空）                      |\n| `${realPort}`            | Whistle 界面 Online 对话框显示的 port（一般为 Whistle 端口号） |\n| `${realHost}`            | Whistle 界面 Online 对话框显示的 host（一般为 Whistle 监听的网卡 IP） |\n| `${clientIp}`            | 客户端 IP                                                    |\n| `${clientPort}`          | 客户端端口                                                   |\n| `${serverIp}`            | 服务端 IP                                                    |\n| `${serverPort}`          | 服务端端口                                                   |\n| `${reqCookies.xxx}`      | 请求 cookie `xxx` 的值                                       |\n| `${resCookies.xxx}`      | 响应 cookie  `xxx` 的值                                      |\n| `${statusCode}`          | 响应状态码                                                   |\n| `${env.xxx}`             | process.env.xxx                                              |\n| `${whistle.plugin-name}` | `whistle.plugin-name://value`  或 `plugin-name://value` 的 `value` |\n\n> `${whistle.plugin-name}` 只在插件的内部规则才可能有值\n\n##### 示例\n\n````txt\n``` test.txt\nnow: ${now}\nrandom: ${random}\nrandomUUID: ${randomUUID}\nreqId: ${reqId}\nurl.protocol: ${url.protocol}\nurl.hostname: ${url.hostname}\nurl.host: ${url.host}\nurl.port: ${url.port}\nurl.path: ${url.path}\nurl.pathname: ${url.pathname}\nurl.search; ${url.search}\nquery: ${query.name}\nurl: ${url}\nquerystring: ${querystring}\nsearchstring: ${searchstring}\nmethod: ${method}\nreqHeaders.accept: ${reqHeaders.accept}\nresHeaders.content-type: ${resHeaders.content-type}\nversion: ${version}\nport: ${port}\nhost: ${host}\nrealPort: ${realPort}\nrealHost: ${realHost}\nclientIp: ${clientIp}\nclientPort: ${clientPort}\nserverIp: ${serverIp}\nserverPort: ${serverPort}\nreqCookies.test: ${reqCookies.test}\nresCookies.test: ${resCookies.test}\nstatusCode: ${statusCode}\nenv.USER: ${env.USER}\n```\n\nwww.test.com/index.html file://`{test.txt}`\n````\n\n访问 `https://www.test.com/index.html?name=avenwu` 则返回响应内容：\n\n``` txt\nnow: 1752301623295\nrandom: 0.6819241513880432\nrandomUUID: e917b9fc-e2ef-4255-9209-11eb417235c5\nreqId: 1752301623294-339\nurl.protocol: https:\nurl.hostname: www.test.com\nurl.host: www.test.com\nurl.port: \nurl.path: /index.html?name=avenwu\nurl.pathname: /index.html\nurl.search; ?name=avenwu\nquery: avenwu\nurl: https://www.test.com/index.html?name=avenwu\nquerystring: ?name=avenwu\nsearchstring: ?name=avenwu\nmethod: GET\nreqHeaders.accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\nresHeaders.content-type: \nversion: 2.9.100\nport: 8899\nhost: \nrealPort: 8899\nrealHost: \nclientIp: 127.0.0.1\nclientPort: 60582\nserverIp: \nserverPort: \nreqCookies.test: \nresCookies.test: \nstatusCode: \nenv.USER: av\n```\n\n## 数据对象\n操作内容除了文本或二进制内容，还有可能是 JSON 对象，Whistle 支持以下 3 种数据对象格式：\n\n#### JSON 格式\n``` js\n{\n  \"key1\": value1,\n  \"key2\": value2,\n  \"keyN\": valueN\n}\n```\n\n#### 行格式\n``` txt\nkey1: value1\nkey2:value2\nkeyN: valueN\n```\n> 以 `冒号+空格` 分隔，如果没有 `冒号+空格` ，则以第一个冒号分隔，如果没有冒号，则 `value` 为空字符串\n\n**多级嵌套：**\n``` txt\na.b.c: 123\nc\\.d\\.e: abc\n```\n等价于：\n``` json\n{\n  \"a\": {\n    \"b\": {\n      \"c\": 123\n    }\n  },\n  \"c.d.e\": \"abc\"\n}\n```\n\n#### 内联格式（请求参数格式）\n\n``` txt\nkey1=value1&key2=value2&keyN=valueN\n```\n> `key` 和 `value` 最好都 `encodeURIComponent`\n\n\n## 常见操作指令速查手册 {operation-manual}\n- [修改请求内容](#request)\n  - [修改请求方法](#method)\n  - [修改请求 URL](#url)\n  - [修改 HTTP 版本](#http-version)\n  - [修改请求头](#req-headers)\n  - [修改请求体](#req-body)\n- [修改响应内容](#response)\n  - [修改状态码](#status-code)\n  - [修改响应头](#res-headers)\n  - [修改响应体](#res-body)\n  - [修改尾部响应头](#trailers)\n- [修改连接过程](#connect)\n  - [修改 DNS](#dns)\n  - [设置代理](#proxy)\n  - [代理和 DNS 同时生效](#proxy-host)\n- [页面调试工具](#tools)\n  - [查看页面 DOM 结构](#weinre)\n  - [查看页面日志及错误信息](#log)\n\n---\n\n## 一、修改请求内容 {#request}\n\n### 1.1 修改请求方法 {#method}\n```txt\n# 基础语法\npattern method://新方法\n\n# 示例\nwww.example.com/path method://post\n```\n\n**数据来源支持：**\n- 直接指定：`method://get`\n- 内嵌值：`method://{keyOfEmbedded}`\n- Values配置：`method://{keyOfValues}`\n\n**注意事项：**\n- 方法名不区分大小写\n- 不支持从本地文件或远程URL获取\n\n**实用示例：**\n```txt\n# 示例1：将所有请求方法改为POST\nwww.example.com/path method://post\n\n# 示例2：仅将PUT请求改为POST\nwww.example.com/path method://post includeFilter://m:put\n\n# 示例3：根据请求体内容修改方法\nwww.example.com/api method://put includeFilter://b:cmdname=test\n```\n\n### 1.2 修改请求 URL {#url}\n#### URL映射\n```txt\nwww.example.com/path/to www.test.com/test\n```\n**映射效果：**\n- `https://www.example.com/path/to?query=abc` → `https://www.test.com/test?query=abc`\n- `https://www.example.com/path/to/subpage` → `https://www.test.com/test/subpage`\n- `wss://www.example.com/path/to/api` → `wss://www.test.com/test/api`\n\n#### 修改请求参数\n```txt\n# 新增/替换参数\npattern urlParams://({\"key\":\"value\"})\n\n# 删除参数\npattern delete://urlParams.paramName\n\n# 示例：修改参数并删除指定参数\nwww.example.com/api urlParams://({\"cmdname\":\"Test\"}) delete://urlParams.oldParam\n```\n\n#### 修改Path路径\n```txt\n# 正则替换\npattern pathReplace://({\"/old/ig\":\"new\"})\n\n# 关键字替换\npattern pathReplace://({\"old\":\"new\"})\n```\n\n**数据来源支持：**\n- 内联JSON\n- 内嵌值\n- Values配置\n- 本地文件\n- 远程URL\n\n### 1.3 修改 HTTP 版本 {#http-version}\n```txt\n# 强制使用普通HTTPS（禁用HTTP/2）\npattern disable://h2\n```\n> 默认会尝试使用HTTP/2建立连接，不支持时自动降级为HTTPS\n\n### 1.4 修改请求头 {#req-headers}\n```txt\n# 新增/替换请求头\npattern reqHeaders://({\"Header-Name\":\"value\"})\n\n# 删除请求头\npattern delete://reqHeaders.headerName\n\n# 示例\nwww.example.com reqHeaders://({\"X-Custom-Header\":\"test\"})\n```\n\n### 1.5 修改请求体 {#req-body}\n#### 合并修改（JSON/表单）\n```txt\npattern reqMerge://({\"newField\":\"value\"})\n```\n\n#### 文本替换\n```txt\n# 正则替换\npattern reqReplace://({\"/search/ig\":\"replace\"})\n\n# 关键字替换\npattern reqReplace://({\"search\":\"replace\"})\n```\n\n#### 完全替换\n```txt\npattern reqBody://(新内容)\n```\n\n#### 删除操作\n```txt\n# 删除特定字段\npattern delete://reqBody.fieldName\n\n# 删除整个请求体\npattern delete://reqBody\n```\n\n**数据来源支持：**\n- 内联值\n- 内嵌值\n- Values配置\n- 本地文件\n- 远程URL\n\n---\n\n## 二、修改响应内容 {#response}\n\n### 2.1 修改状态码 {#status-code}\n```txt\n# 替换现有响应状态码（请求会到达服务器）\npattern replaceStatus://500\n\n# 直接响应状态码（请求不发送到服务器）\npattern statusCode://500\n```\n\n### 2.2 修改响应头 {#res-headers}\n```txt\n# 新增/替换响应头\npattern resHeaders://({\"Header-Name\":\"value\"})\n\n# 删除响应头\npattern delete://resHeaders.headerName\n```\n\n### 2.3 修改响应体 {#res-body}\n#### 合并修改（JSON/JSONP）\n```txt\npattern resMerge://({\"newData\":\"value\"})\n```\n\n#### 文本替换\n```txt\npattern resReplace://({\"/old/ig\":\"new\"})\n```\n\n#### 替换响应体\n```txt\n# 替换服务器返回的内容\npattern resBody://(新内容)\n```\n\n#### 直接响应内容\n```txt\n# 请求不发送到服务器\npattern file://(直接返回的内容) resType://html\n```\n\n#### 删除操作\n```txt\n# 删除响应体字段\npattern delete://resBody.fieldName\n\n# 清空响应体\npattern delete://resBody\n```\n\n### 2.4 修改尾部响应头 {#trailers}\n> Trailers是在分块传输响应后发送的额外头部字段\n\n```txt\n# 新增/替换Trailers\npattern trailers://({\"Trailer-Name\":\"value\"})\n\n# 删除Trailers\npattern delete://trailers.trailerName\n```\n\n---\n\n## 三、修改连接过程 {#connect}\n\n### 3.1 修改 DNS {#dns}\n```txt\n# 设置IP地址\npattern 127.0.0.1\n\n# 设置IP和端口\npattern 127.0.0.1:8080\n\n# CNAME效果（指向其他主机）\npattern host://www.target.com:8080\n```\n\n### 3.2 设置代理 {#proxy}\n```txt\n# HTTP代理\npattern proxy://127.0.0.1:8080\n\n# SOCKS5代理\npattern socks://127.0.0.1:1080\n\n# 支持域名\npattern proxy://proxy.example.com:8080\n```\n\n### 3.3 代理和 DNS 同时生效 {#proxy-host}\n**优先级说明：**\n- 默认：host配置优先于proxy\n- 可调整：通过`lineProps://proxyHost`让两者同时生效\n\n```txt\n# 示例1：只生效host配置\npattern 127.0.0.1:8080 socks://10.1.1.1:1080\n\n# 示例2：只生效proxy配置\npattern 127.0.0.1:8080 socks://10.1.1.1:1080 ignore://host\n\n# 示例3：host和proxy同时生效\npattern 127.0.0.1:8080 socks://10.1.1.1:1080 lineProps://proxyHost\n```\n\n---\n\n## 四、页面调试工具 {#tools}\n\n### 4.1 查看页面 DOM 结构 {#weinre}\n```txt\npattern weinre://调试会话名称\n```\n> 详细用法参考：[weinre文档](./weinre)\n\n### 4.2 查看页面日志及错误信息 {#log}\n```txt\npattern log://日志会话名称\n```\n> 详细用法参考：[log文档](./log)\n\n---\n\n## 数据来源速查表\n\n| 操作类型 | 直接内联 | 内嵌值 | Values | 本地文件 | 远程URL |\n|---------|---------|--------|--------|----------|---------|\n| 请求方法 | ✓ | ✓ | ✓ | ✗ | ✗ |\n| URL参数 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 请求头 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 请求体 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 响应头 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 响应体 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| Trailers | ✓ | ✓ | ✓ | ✓ | ✓ |\n\n**语法示例：**\n```txt\n# 直接内联\nprotocol://({\"key\":\"value\"})\n\n# 内嵌值\nprotocol://{embeddedKey}\n\n# Values配置\nprotocol://{valuesKey}\n\n# 本地文件\nprotocol:///path/to/file.json\n\n# 远程URL\nprotocol://https://example.com/data.json\n```\n\n---\n\n## 常用过滤条件\n\n| 过滤器 | 说明 | 示例 |\n|--------|------|------|\n| `includeFilter://m:方法` | 按请求方法过滤 | `includeFilter://m:put` |\n| `includeFilter://b:内容` | 按请求体内容过滤 | `includeFilter://b:cmdname=test` |\n| 正则表达式 | 区分大小写匹配 | `/Test/` |\n\n> **提示：** 更多协议和高级用法请参考[完整协议列表](./protocols)\n","docs/docs/rules/pac.md":"# pac\n\nPAC（Proxy Auto-Config）是一种通过 JavaScript 脚本自动决定请求代理规则的机制，允许您基于 URL、域名、IP 等条件动态选择代理或直连。\n\n## 规则语法\n``` txt\npattern pac://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n```` txt\n# 内嵌 PAC 脚本\n``` test.pac\nfunction FindProxyForURL(url, host) {\n  // ...\n}\n```\nwww.example.com/path pac://{test.pac}\n\n# Values\nwww.example.com/path1 pac://{test2.pac}\n\n# 本地文件\nwww.example.com/path3 pac:///User/xxx/test.pac\n\n# 远程 PAC 脚本\n* pac://https://raw.githubusercontent.com/imweb/node-pac/master/test/scripts/normal.pac\n````\n\n## 高级用法\n将请求代理到上游代理后，默认情况下上游代理会根据请求的域名通过 DNS 获取服务器 IP 再继续请求，如果想让上游代理根据指定 IP及端口继续请求，可以这么处理：\n``` txt\nwww.example.com pac://https://xxx/path/normal.pac 1.1.1.1 enable://proxyHost\nwww.example.com pac:///User/xxx/test.pac 1.1.1.1:8080 enable://proxyHost\n```\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n\n\n## 注意事项\n`pac` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com pac://https://xxx/path/normal.pac\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望 PAC 脚本 `https://xxx/path/normal.pac` 作用于 `https://www.example.com/path`，但由于 `pac` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `pac` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com pac://https://xxx/path/normal.pac\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `pac` 规则匹配。\n","docs/docs/rules/pathReplace.md":"# pathReplace\n提供类似 JavaScript 的 String.replace() 方法的能力，通过正则表达式或字符串匹配来动态修改 URL 的路径(`path`) 部分。\n\n> URL 结构：\n> ``` txt\n>   https://www.example.com:8080/path/to/resource?query=string\n>   \\___/   \\_____________/\\____/\\____________________________/\n>     |           |         |                 |             \n>   协议(scheme)  主机(host) 端口(path)       路径(path) \n> ```\n>\n> **路径部分**指 `path/to/resource?query=string` 即不包含开头的  `/`\n\n## 规则语法\n``` txt\npattern pathReplace://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作数据对象，支持从以下渠道获取：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nwww.example.com/path pathReplace://123=abc\n```\n访问 `https://www.example.com/path/123?test=123&value=123` 服务器收到的 URL：`https://www.example.com/path/abc?test=abc&value=abc`\n\n#### 替换多个\n\n```` txt\n``` test.json\ntest: name\n123: abc\n```\nwww.example.com/path2 pathReplace://{test.json}\n````\n访问 `https://www.example.com/path2/123?test=123&value=123` 服务器收到的 URL：`https://www.example.com/path2/abc?name=abc&value=abc`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 pathReplace:///User/xxx/test.json\nwww.example.com/path2 pathReplace://https://www.xxx.com/xxx/params.json\n# 通过编辑临时文件\nwww.example.com/path3 pathReplace://temp/blank.json\n````\n\n## 注意事项\n以下配置旨在删除指定路径片段：\n```txt\nwww.example.com/api/ pathReplace://(/api/=/)\n```\n\n**期望效果：**  \n将 `https://www.example.com/api/xxx` 中的 `/api/` 替换为 `/`，得到 `https://www.example.com/xxx`\n\n**实际问题：**  \nWhistle 会将 `/api/` 识别为正则表达式而非普通字符串，导致替换后产生多余斜杠：  \n`https://www.example.com///xxx`\n\n> 即使将 `/api/` 视为字符串，它也无法匹配 `api/xxx/...`，pathReplace 匹配的路径不包含开头的  `/`\n\n**解决方案：**  \n\n``` txt\nwww.example.com pathReplace://(/^api//=)\n```\n> Whistle 规则里面的正则不需要对 `/` 进行转义\n>\n> 新版 Whistle 也可以用 `delete://pathname.0` 删除上面 URL 的 `api/` 路径片段，详见 [delete://pathname.xxx](./delete)\n\n## 关联协议 {#related}\n1. 设置请求参数（`Set Param`）：[urlParams://(key=value)](./urlParams)\n2. 删除请求参数（`Delete Param`）：[delete://urlParams.xxx](./delete)\n3. 更灵活的修改请求参数的方式（`Modify Path (path/to?query)`）：[pathReplace](./pathReplace)\n4. 删除路径：[delete://pathname.xxx](./delete)\n\n","docs/docs/rules/pattern.md":"# pattern\n\n`pattern` 是 Whistle 规则中匹配请求 URL 的表达式，支持 URL 片段/域名、通配符、正则表达式等多种匹配方式。\n\n## 请求 URL 类型\n\nWhistle 支持三种请求 URL 类型：\n\n| 类型 | 格式 | 示例 |\n| :--- | :--- | :--- |\n| **隧道代理** | `tunnel://domain:port` | `tunnel://www.test.com:443` |\n| **WebSocket** | `ws[s]://domain[:port]/[path/to[?query]]` | `wss://www.test.com/path?a=1&b=2`<br>`ws://www.example.com:8080/path` |\n| **普通 HTTP/HTTPS** | `http[s]://domain[:port]/[path/to][?query]` | `https://www.test.com/path`<br>`http://www.example.com/path?a=1&b=2` |\n\n> URL 中的 hash（即 `#` 及之后的内容）不会被发送到后台。Hash 仅在客户端（如浏览器）中生效，后台无法直接获取\n\n## URL 片段 {#url}\n\nURL 片段用于匹配请求的 URL，支持多种灵活的模式。以下按匹配规则分类说明。\n\n### 1. 域名匹配\n\n- **普通域名**：`example.com`  \n  匹配该域名下所有端口及协议（http/https）的请求。  \n  ✅ `https://example.com/path/to?query`  \n  ✅ `https://example.com:9090/path/to?query`\n\n- **带端口的域名**：`example.com:8080`  \n  仅匹配指定端口（8080）的请求。  \n  ✅ `https://example.com:8080/path/to?query`  \n  ❌ `https://example.com:9090/path/to?query`\n  ❌ `https://example.com/path/to?query`\n\n### 2. 协议匹配\n\n- **带协议的 URL**：`https://example.com/path/to`  \n  仅匹配相同协议的请求。\n\n- **不带协议的 URL**：`example.com/path/to` 或 `//example.com/path/to`  \n  匹配任意协议（http、https 等），路径匹配规则与带协议时相同（见下文）。\n\n### 3. 路径与查询参数匹配\n\n#### 3.1 不带查询参数的 URL\n\n模式：`https://example.com/path/to`  \n匹配规则：**路径前缀匹配**（以 `/` 为边界）。  \n- ✅ `https://example.com/path/to`  \n- ✅ `https://example.com/path/to/xxx?query`  \n- ❌ `https://example.com/path/toxxx`（`to` 后缺少 `/` 边界）\n\n#### 3.2 带查询参数的 URL\n\n模式：`https://example.com/path/to?xxx`  \n匹配规则：**路径必须完全相同**，且查询字符串以 `xxx` 为前缀。  \n- ✅ `https://example.com/path/to?xxx`  \n- ✅ `https://example.com/path/to?xxxyyy&zzzzz`  \n- ❌ `https://example.com/path/to/yyy?xxx`（路径不同）\n\n### 4. 精确匹配\n\n使用 `$` 前缀，提供更严格的匹配。\n\n- `$https://example.com/path/to`  \n  匹配**路径精确**（以 `/` 为边界），查询参数可任意。  \n  ✅ `https://example.com/path/to`  \n  ✅ `https://example.com/path/to?query`  \n  ❌ `https://example.com/path/to/xxx`\n\n- `$https://example.com/path/to?query`  \n  匹配**路径与查询参数均完全一致**。  \n  ✅ `https://example.com/path/to?query`  \n  ❌ `https://example.com/path/to?query=1`  \n  ❌ `https://example.com/path/to`\n\n- 不带协议的精确匹配：`$example.com/path/to`（效果同上，协议任意）\n\n### 5. 域名通配符{#domain-wildcard}\n\n在`域名:端口`部分使用 `*` 或 `**` 进行模糊匹配。\n\n- `*`：匹配 0 个或多个非 `.` 字符（相当于正则 `/[^/?.]*/`）。  \n  示例：`https://*.example.com/path/to`  \n  ✅ `https://www.example.com/path/to`  \n  ✅ `https://abc.example.com/path/to/xxx?query`\n  ❌ `https://a.b.example.com/path/to`\n\n- `**`：匹配 0 个或多个任意字符（除 `/` 和 `?` 外，相当于正则 `/[^/?]*/`）。  \n  示例：`https://**.example.com:8*/path/to`  \n  ✅ `https://foo-bar.example.com:8080/path/to`\n  ✅ `https://a.b.example.com:8888/path/to`\n\n- `***` 及以上：不推荐使用。\n\n> 注意：`*` 也是 URL 路径中的合法字符。若需在路径部分使用通配符，请参阅 [通配符匹配](#wildcard)。\n\n### 要点总结\n\n| 模式示例 | 匹配协议 | 匹配端口 | 路径匹配规则 | 查询参数匹配规则 |\n|---------|---------|---------|-------------|----------------|\n| `example.com` | 任意 | 任意 | - | - |\n| `example.com:8080` | 任意 | 8080 | - | - |\n| `https://example.com/path/to` | https | 任意 | 前缀（`/`边界） | 任意 |\n| `https://example.com/path/to?xxx` | https | 任意 | 精确 | 前缀 |\n| `$https://example.com/path/to` | https | 任意 | 精确 | 任意 |\n| `$https://example.com/path/to?xxx` | https | 任意 | 精确 | 精确 |\n| `example.com/path/to` <br/> 或 `//example.com/path/to` | 任意 | 任意 | 前缀（`/`边界） | 任意 |\n\n## 通配符匹配 {#wildcard}\n\n在 [URL 片段匹配](#url) 中，由于 `*` 本身是 URL 路径中的合法字符，因此无法直接在路径或查询参数中使用通配符。  \n若需在 **路径** 或 **查询参数** 中使用通配符，请在 URL 前加上 `^` 符号。此时 `*`、`**`、`***` 将作为通配符解析。\n\n### 1. 域名通配符\n\n语法与 [URL 片段中的域名通配符](#domain-wildcard) 完全一致：\n\n- `*`：匹配 0 个或多个非 `.` 字符（正则 `/[^/?.]*/`）\n- `**`：匹配 0 个或多个任意字符（除 `/` 和 `?` 外，正则 `/[^/?]*/`）\n\n**示例**：`^wss://*.example.com/path/to`\n\n✅ 匹配：\n- `wss://a.example.com/path/to`\n- `wss://b.example.com/path/to/xxx?query`\n\n❌ 不匹配：\n- `wss://a.example.com/path/toxxx`（路径缺少 `/` 边界）\n- `wss://a.b.example.com/path/to`（`*` 不匹配点号）\n\n### 2. 路径通配符\n\n作用于 URL 的 **路径部分**（域名后、`?` 之前）。支持三种级别：\n\n| 通配符 | 匹配规则（正则） | 说明 |\n|--------|----------------|------|\n| `*`    | `/[^?/]*/`      | 匹配当前路径段内的任意字符（不含 `/` 和 `?`） |\n| `**`   | `/[^?]*/`       | 匹配任意字符（不含 `?`），可跨路径段 |\n| `***`  | `/.*/`          | 匹配剩余所有字符（含 `/` 和 `?`），通常用于路径末尾 |\n\n**示例 1：`*`**  \n模式：`^https://example.com/path/to/a*b`  \n✅ 匹配：`https://example.com/path/to/axxxb/...?query`  \n❌ 不匹配：`https://example.com/path/to/a/b`（`*` 不能匹配 `/`）\n\n**示例 2：`**`**  \n模式：`^https://example.com/path/to/a**b`  \n✅ 匹配：\n- `https://example.com/path/to/axxxb/...?query`\n- `https://example.com/path/to/a/b`  \n❌ 不匹配：`https://example.com/path/to/a/xxxx?query=b`（`?` 后不属于路径）\n\n**示例 3：`***`**  \n模式：`^https://www.example*.com:8*/path/to/a***b`  \n✅ 匹配：\n- `https://example.com/path/to/axxxb/...?query`\n- `https://example.com/path/to/a/b`\n- `https://example.com/path/to/a/xxxx?query=b`（`***` 会匹配到 `?` 及之后）\n\n> `***` 会“吞掉”包括 `?` 在内的剩余字符，一般仅建议在路径末尾使用。\n\n\n### 3. 查询参数通配符\n\n作用于 URL 的 **查询字符串**（`?` 之后）。支持两种：\n\n| 通配符 | 匹配规则（正则） | 说明 |\n|--------|----------------|------|\n| `*`    | `/[^&]*/`       | 匹配单个参数值内的任意字符（不含 `&`） |\n| `**`   | `/.*/`          | 匹配剩余所有字符（含 `&`），即从当前位置到末尾 |\n\n**示例 1：`*`**  \n模式：`^https://example.com/path/to?query=a*b`  \n✅ 匹配：`https://example.com/path/to?query=ab&q2=xxx`  \n❌ 不匹配：`https://example.com/path/to?query=a&q2=b`（`b` 前无额外字符）\n\n**示例 2：`**`**  \n模式：`^https://example.com/path/to?query=a**b`  \n✅ 匹配：\n- `https://example.com/path/to?query=axxxb&q2=xxx`\n- `https://example.com/path/to?query=a&q2=b`（`**` 匹配了 `&q2=`）\n\n### 4. 不带协议的精确匹配\n\n在 `^` 后直接写域名和路径，协议任意。\n\n**示例**：`^example*.com/path*/to`  \n效果与带协议相同，但可匹配 `http://`、`https://`、`wss://` 等任意协议。\n\n### 5. 精确匹配（边界控制）\n\n在模式末尾使用 `$`，表示匹配必须到此结束（之后不能再有额外字符）。\n\n**示例**：`^https://*.example.com/path/*/to$`  \n✅ 匹配：`https://a.example.com/path/xxx/to`  \n❌ 不匹配：`https://b.example.com/path/xxx/to?query`（`$` 禁止 `?query`）\n\n> 若不加 `$`，模式默认允许路径之后继续存在 `/`、`?` 等字符。\n\n### 使用总结\n\n| 使用场景 | 前缀 | 通配符类型 | 支持的通配符 | 示例 |\n|---------|------|-----------|-------------|------|\n| 普通 URL 匹配（无通配符） | 无 | — | — | `https://example.com/path` |\n| 含通配符的路径/参数 | `^` | 域名、路径、查询 | `*` `**` `***` | `^https://*.example.com/path/*?a**b` |\n| 精确匹配（尾部边界） | `^` + `$` | 同上 | 同上 | `^https://example.com/path/*/to$` |\n\n> **注意**：一旦使用 `^`，URL 中的 `*` 不再作为普通字符，而是通配符。如需匹配字面意义上的 `*`，请勿使用 `^`。\n\n## 正则匹配 {#regexp}\n\n除了前面介绍的 URL 片段匹配和通配符匹配外，Whistle 还提供了完整的正则表达式支持。  \n正则匹配的语法与 JavaScript 正则表达式完全兼容：\n\n```txt\n/pattern/[i]\n```\n\n### 参数说明\n\n- **`pattern`**：正则表达式主体\n- **`i`**（可选）：忽略大小写 \n\n### 使用示例\n\n```txt\n/\\.test\\./              # 匹配字符串中的 \".test.\"\n/key=value/i            # 忽略大小写匹配 \"key=value\" 或 \"KEY=VALUE\"\n```\n\n\n## 子匹配传值\n\n在 Whistle 规则中，可以通过 `$0`、`$1` … `$9` 引用通配符或正则表达式捕获的子匹配内容，并将其传递到目标操作值中。\n\n```txt\npattern protocol://$0_$1_$2_..._$9\n```\n\n**捕获变量说明**：\n\n| 变量 | 含义 |\n|------|------|\n| `$0` | 完整匹配的内容 |\n| `$1` ~ `$9` | 第 1 至第 9 个捕获组匹配的内容 |\n\n### 通配符匹配传值\n\n使用 `^` 前缀的通配符模式（`*`、`**` 等）会自动按顺序捕获匹配段，分别赋给 `$1`、`$2`……。\n\n**示例**：\n```txt\n^http://*.example.com/v0/users/** file:///User/xxx/$1/$2\n```\n\n- 请求 URL：`http://www.example.com/v2/users/alice/test.html?q=1`\n- 匹配过程：\n  - `$1` = `www`（匹配第一个 `*`）\n  - `$2` = `users/alice`（匹配 `**` 捕获的路径段）\n- 最终效果：返回本地文件 `/User/xxx/www/alice/test.html` 的内容\n\n> 通配符捕获规则：\n> - `*` 捕获单个路径段（不含 `/`）\n> - `**` 捕获剩余路径（可含 `/`）\n> - 按从左到右顺序依次对应 `$1`、`$2`……\n\n### 正则匹配传值\n\n使用 `/pattern/flags` 正则表达式时，捕获组 `( )` 中的内容会按顺序赋给 `$1`、`$2`……\n\n**示例**：\n```txt\n/regexp\\/(user|admin)\\/(\\d+)/ reqHeaders://X-Type=$1&X-ID=$2\n```\n\n- 请求 URL：`.../regexp/admin/123`\n- 捕获结果：\n  - `$1` = `admin`\n  - `$2` = `123`\n- 最终效果：为请求添加请求头 `X-Type: admin` 和 `X-ID: 123`\n\n---\n\n## 路径自动拼接规则\n\n当使用 **域名匹配** 或 **路径匹配** 映射到本地文件/目录或远程 URL 时，Whistle 会自动拼接原请求的剩余路径。\n\n**规则**：\n```txt\nhttps://*.example.com/path/to https://www.test.com/test\nwww.example.com file:///Usr/test\n```\n\n**效果说明**：\n\n| 请求 URL | 映射后目标 |\n|----------|------------|\n| `https://abc.example.com/path/to/x/y/z?query` | `https://www.test.com/test/x/y/z?query` |\n| `https://www.example.com/path/to/index.html?query` | 本地文件 `/Usr/test/path/to/index.html`（自动去除 query） |\n\n> 注意：查询参数 `?query` 在映射到本地文件时会被自动忽略（文件系统路径不支持），但映射到远程 URL 时会保留。\n\n---\n\n## 配置示例\n\n### 基础匹配\n\n```txt\n# 精确域名匹配\napi.example.com proxy://127.0.0.1:3000\n\n# 指定端口的域名匹配\nwww.example.com:8080 file:///local/dev\n\n# 路径匹配（映射到本地 JSON 数据）\nwww.example.com/api/users file://{user-data}\n```\n\n### 通配符匹配\n\n```txt\n# 匹配所有子域名\n**.example.com proxy://127.0.0.1:8080\n\n# 匹配特定前缀的子域名\ndev-**.example.com file:///(dev-mock)\n```\n\n### 正则匹配\n\n```txt\n# 匹配数字 ID 的用户页面\n/^https?://www\\.example\\.com/user/(\\d+)/ file://(user-$1)\n\n# 忽略大小写匹配特定路径\n/\\/api\\/v1\\/data/i resBody://({\"version\":\"v1\"})\n\n# 匹配静态资源文件并设置缓存\n/\\.(jpg|png|gif|css|js)$/i cache://86400\n```\n\n### 复杂匹配\n\n```txt\n# 通配符 + 路径捕获传值\n^https://**.example.com/api/*/v*/users reqHeaders://x-api-version=$3\n\n# 按请求方法区分处理\nwww.example.com/api file://({\"status\":\"ok\"}) includeFilter://m:GET\nwww.example.com/api file://({\"status\":\"created\"}) includeFilter://m:POST\n```\n\n---\n\n## 故障排除\n\n### Q：规则没有匹配到请求\n\n**可能原因及解决方案**：\n\n1. **URL 格式不正确**  \n   → 确保规则中的协议（http/https/ws/wss）与请求一致，或使用不带协议的写法（`example.com/path`）。\n\n2. **端口号不匹配**  \n   → 如果请求使用非标准端口，需要在规则中明确指定端口（如 `example.com:8080`）。\n\n3. **通配符或正则表达式有误**  \n   → 使用 [Whistle 在线调试工具](https://github.com/avwo/whistle) 或本地日志验证匹配情况。\n\n4. **规则优先级问题**  \n   → 更具体的规则应写在前面，或使用 `$` 精确匹配提高优先级。\n\n### Q：正则表达式不生效\n\n**检查清单**：\n\n- [ ] 正则表达式是否以 `/` 开头和结尾，如 `/pattern/`？\n- [ ] 特殊字符是否已转义（如 `.` 需写成 `\\.`，`/` 需写成 `\\/`）？\n- [ ] 标志（`i` 等）是否正确添加？\n- [ ] 正则是否匹配完整的 URL？可以在控制台用 `new RegExp('pattern', 'flags').test(url)` 验证。\n\n### Q：子匹配传值不正确\n\n**常见问题**：\n\n1. **捕获组编号错误**  \n   → `$1` 对应第一个 `( )`，`$2` 对应第二个，依次类推。注意嵌套捕获组的顺序。\n\n2. **通配符捕获不符合预期**  \n   → `*` 不匹配点号（`.`）和斜杠（`/`），`**` 匹配任意字符（除 `?` 外）。  \n   → 示例：`*.example.com` 无法匹配 `a.b.example.com`，需改用 `**.example.com`。\n\n3. **正则捕获组未按预期工作**  \n   → 使用 `(?:)` 表示非捕获组，避免干扰编号。  \n   → 使用在线正则测试工具（如 regex101.com）验证捕获内容。\n\n### Q：映射到本地文件时路径错误\n\n- 使用 `file://` 协议时，确保路径是绝对路径（如 `file:///User/xxx`）。\n- 路径拼接遵循自动拼接规则，请求的剩余路径会追加到目标路径后。\n- 查询参数会被自动忽略，不影响文件读取。\n\n---\n\n## 扩展阅读\n\n- [规则语法文档](./rule)：了解完整的规则语法结构\n- [操作指令文档](./operation)：学习如何配置操作指令\n- [过滤器文档](./filters)：了解如何精确控制规则生效条件\n","docs/docs/rules/pipe.md":"# pipe\n将 HTTP/HTTPS/WebSocket/TUNNEL 请求/响应数据流交给插件处理。\n\n## 规则语法\n``` txt\npattern pipe://plugin-name(pipeValue) [filters...]\n```\n> `(pipeValue)` 可选，在插件 Hook 可以通过 `req.originalReq.pipeValue` 获取\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| plugin-name(pipeValue)   | 插件名称 + 可选参数 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\ntunnel://wwww.example.com pipe://test\ntunnel://test-tunnel.example.com pipe://test-pipe-tunnel(abc)\n\nwss://test-ws.example.com/path pipe://test-pipe-ws\n\nhttps://www.example.com/path pipe://test-pipe-http(123)\n\n```\n\n具体用法参考：[插件开发文档](../extensions/dev.md#pipe)\n","docs/docs/rules/plugin-vars.md":"# `%` 符号用法\n\n通过插件管理界面可直观地配置各项参数，例如可以让 [whistle.autosave](https://github.com/whistle-plugins/whistle.autosave) 插件支持以下配置：\n- 启用存储抓包数据\n- 配置存储抓包数据目录\n\n除管理界面外，您还可以直接在规则文件中使用 `%` 符号快速配置。\n\n## 全局配置（对所有请求生效）\n``` txt\n%autosave=123\n%autosave.enableAutoSave=true\n%autosave.storageDir=/User/xxx/test/sessions\n```\n\n## 精细化配置（针对特定请求）\n``` txt\nwww.test.com/api %autosave=abc\nwww.test.com/api %autosave.enableAutoSave=false [filters...]\nwww.test.com/api %autosave.storageDir= [filters...]\n```\n\n## 获取插件变量值\n在插件的 Hooks 里面可以通过以下代码获取到配置的变量列表：\n\n``` js\nreq.originalReq.globalPluginVars; // 全局变量，如 ['123', 'enableAutoSave=true', 'storageDir=/User/xxx/test/sessions']\nreq.originalReq.pluginVars; // 精细化配置的变量，如 ['abc', 'enableAutoSave=false', 'storageDir=']\n```\n\n## 开启规则自动提示\n如果通过插件变量的配置项是固定的，可以通过插件的 `package.json` 里面的 `whistleConfig` 字段配置可选项，这样在规则中可以自动提醒：\n\n#### 简单提示\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": true,\n  ...\n}\n```\n完成上述配置后，在 Whistle 的 Rules 编辑器中输入 % 字符时，将自动提示以下格式的插件变量：\n``` txt\n%test-plugin-vars=\n```\n\n#### 匿名 Key 值提示\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        \"test1\",\n        \"test2\",\n        \"test3\"\n      ]\n    }\n  },\n  ...\n}\n```\n\n<img src=\"/img/plugin-vars-hint-list.png\" width=\"260\" />\n\n值和显示内容分离：\n\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        {\n          \"text\": \"test1\",\n          \"displayText\": \"displayText1\"\n        },\n       {\n          \"text\": \"test2\",\n          \"displayText\": \"displayText2\"\n        },\n        {\n          \"text\": \"test3\",\n          \"displayText\": \"displayText3\",\n          \"help\": \"https://www.example.com/path\"\n        }\n      ]\n    }\n  }\n  ...\n}\n```\n> `displayText` 和 `help` 都是可选，配置 `help: 帮助链接` 时，当选中该提醒时，键盘按 `F1` 键自动打开该帮助链接\n\n<img src=\"/img/plugin-vars-display-list.png\" width=\"220\" />\n\n#### 设置 Key 名称\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        {\n          \"text\": \"test1\",\n          \"displayText\": \"displayText1\"\n        },\n       {\n          \"text\": \"test2\",\n          \"displayText\": \"displayText2\"\n        },\n        {\n          \"text\": \"test3\",\n          \"displayText\": \"displayText3\",\n          \"help\": \"https://www.example.com/path\"\n        }\n      ],\n      \"hintSuffix\": [\n        \"=\",\n        \".key1=123\",\n        \".key2\"\n      ]\n    }\n  }\n  ...\n}\n```\n<img src=\"/img/plugin-vars-key-hint.png\" width=\"300\" />\n\n选中 `%test-plugin-vars=` 后会自动显示 `hintList` 的内容：\n\n<img src=\"/img/plugin-vars-display-list.png\" width=\"220\" />\n\n#### 借助后台接口\n\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintSuffix\": [\n        \"=\",\n        \".key1=123\",\n        \".key2\"\n      ],\n      \"hintUrl\": \"/cgi-bin/plugin-vars\"\n    }\n  }\n  ...\n}\n```\n> `hintList` 和 `hintUrl` 是互斥的，同时只能使用其中一个，`hintSuffix` 可选\n\n<img src=\"/img/plugin-vars-hint-url1.png\" width=\"360\" />\n\n<img src=\"/img/plugin-vars-hint-url2.png\" width=\"360\" />\n\n`/cgi-bin/plugin-vars` 的实现参考：[插件开发文档](../extensions/dev#rules-hint)\n","docs/docs/rules/protocols.md":"# 操作协议列表\n\n## 特殊规则\n1. [@（引入远程规则或设置客户端证书）](./@)\n2. [%（设置插件的变量值）](./plugin-vars)\n\n## Map Local（用本地文件替换响应内容）\n1. [file](./file)\n2. [xfile](./xfile)\n3. [tpl](./tpl)\n4. [xtpl](./xtpl)\n5. [rawfile](./rawfile)\n6. [xrawfile](./xrawfile)\n\n## Map Remote（返回其它 URL 的数据）\n1. [https](./https)\n2. [http](./http)\n3. [wss](./wss)\n4. [ws](./ws)\n5. [tunnel](./tunnel)\n\n## DNS Spoofing（修改请求 IP 地址）\n1. [host](./host)\n2. [xhost](./xhost)\n3. [proxy (http-proxy)](./proxy)\n4. [xproxy (xhttp-proxy)](./xproxy)\n5. [https-proxy](./https-proxy)\n6. [xhttps-proxy](./xhttps-proxy)\n7. [socks](./socks)\n8.  [xsocks](./xsocks)\n9.  [pac](./pac)\n\n## Rewrite Request（修改请求数据）\n1. [urlParams](./urlParams)\n2. [pathReplace](./pathReplace)\n3. [sniCallback](./sniCallback)\n4. [method](./method)\n5. [tlsOptions](./cipher)\n6. [reqHeaders](./reqHeaders)\n7. [forwardedFor](./forwardedFor)\n8. [ua](./ua)\n9. [auth](./auth)\n10. [cache](./cache)\n11. [referer](./referer)\n12. [attachment](./attachment)\n13. [reqCharset](./reqCharset)\n14. [reqCookies](./reqCookies)\n15. [reqCors](./reqCors)\n16. [reqType](./reqType)\n17. [reqBody](./reqBody)\n18. [reqMerge](./reqMerge)\n19. [reqPrepend](./reqPrepend)\n20. [reqAppend](./reqAppend)\n21. [reqReplace](./reqReplace)\n22. [reqWrite](./reqWrite)\n23. [reqWriteRaw](./reqWriteRaw)\n24. [reqRules](./reqRules)\n25. [reqScript](./reqScript)\n\n## Rewrite Response（修改响应数据）\n1. [statusCode](./statusCode)\n2. [replaceStatus](./replaceStatus)\n3. [redirect](./redirect)\n4. [locationHref](./locationHref)\n5. [resHeaders](./resHeaders)\n6. [responseFor](./responseFor)\n7. [resCharset](./resCharset)\n8. [resCookies](./resCookies)\n9. [resCors](./resCors)\n10. [resType](./resType)\n11. [resBody](./resBody)\n12. [resMerge](./resMerge)\n13. [resPrepend](./resPrepend)\n14. [resAppend](./resAppend)\n15. [resReplace](./resReplace)\n16. [htmlPrepend](./htmlPrepend)\n17. [htmlBody](./htmlBody)\n18. [htmlAppend](./htmlAppend)\n19. [cssPrepend](./cssPrepend)\n20. [cssBody](./cssBody)\n21. [cssAppend](./cssAppend)\n22. [jsPrepend](./jsPrepend)\n23. [jsBody](./jsBody)\n24. [jsAppend](./jsAppend)\n25. [trailers](./trailers)\n26. [resWrite](./resWrite)\n27. [resWriteRaw](./resWriteRaw)\n28. [resRules](./resRules)\n29. [resScript](./resScript)\n\n## General（请求/响应通用协议）\n1. [pipe](./pipe)\n2. [delete](./delete)\n3. [headerReplace](./headerReplace)\n\n## Throttle（限制流量速度）\n1. [reqDelay](./reqDelay)\n2. [resDelay](./resDelay)\n3. [reqSpeed](./reqSpeed)\n4. [resSpeed](./resSpeed)\n\n## Tools（调试工具）\n1. [weinre](./weinre)\n2. [log](./log)\n\n## Settings（特殊设置）\n1. [style](./style)\n2. [lineProps](./lineProps)\n3. [enable](./enable)\n4. [disable](./disable)\n\n## Filters（规则过滤器）\n1. [ignore](./ignore)\n2. [skip](./skip)\n3. [excludeFilter](./excludeFilter)\n4. [includeFilter](./includeFilter)\n","docs/docs/rules/proxy.md":"# proxy (http-proxy)\n`proxy`（或 `http-proxy`）指令用于将匹配的请求通过指定的 HTTP 代理服务器转发。两个指令名称功能完全一致，可互换使用。\n\n## 规则语法\n``` txt\npattern proxy://ipOrDomain[:port] [filters...]\n# 等效写法：\npattern http-proxy://ipOrDomain[:port] [filters...]\n```\n> `port` 可选，不填则使用默认端口 `80`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 将请求代理到 HTTP PROXY: `127.0.0.1:80`\nwww.example.com/path proxy://127.0.0.1 # 默认端口 80\n\n# 将当前域名的所有请求代理到 HTTP PROXY: `127.0.0.1:8080`\nwww.example.com proxy://127.0.0.1:8080\n\n# 也可以用域名\nwww.example.com/path proxy://test.proxy.com # 默认端口 80\nwww.example.com proxy://test.proxy.com:8080\n```\n\n## 高级用法\n将请求代理到上游代理后，默认情况下上游代理会根据请求的域名通过 DNS 获取服务器 IP 再继续请求，如果想让上游代理根据指定 IP及端口继续请求，可以这么处理：\n``` txt\n# 通过查询参数\nwww.example.com proxy://127.0.0.1:8080?host=1.1.1.1\nwww.example.com proxy://127.0.0.1:8080?host=1.1.1.1:8080\n\n# 通过指令启用\nwww.example.com proxy://127.0.0.1:8080 1.1.1.1 enable://proxyHost\nwww.example.com proxy://127.0.0.1:8080 1.1.1.1:8080 enable://proxyHost\n````\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n## 与 host 的匹配优先级\n\n#### 默认行为\n\n当请求同时匹配 `host` 和 `proxy` 规则时：\n- 仅 `host` 规则生效\n- `proxy` 规则自动忽略\n\n#### 修改优先级\n| 配置方式 | 语法 | 效果 |\n|---------|------|------|\n| **优先 proxy** | [`enable://proxyFirst`](./enable) 或 [`lineProps://proxyFirst`](./lineProps) | 仅 `proxy` 生效（覆盖 host） |\n| **同时生效** | [`enable://proxyHost`](./enable) 或 [`lineProps://proxyHost`](./lineProps) | `proxy` 和 `host` 同时生效 |\n\n#### 使用建议\n- 大多数场景使用默认行为即可\n- 需要特殊代理逻辑时才使用 `proxyFirst`\n- 需要双重匹配时使用 `proxyHost`\n\n## 注意事项\n`proxy` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com proxy://127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求代理到 `127.0.0.1:1234`，但由于 `proxy` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `proxy` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com proxy://127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `proxy` 规则匹配，最终代理到 `127.0.0.1:1234`。\n","docs/docs/rules/rawfile.md":"# rawfile\nrawfile 是 [file](./file) 的增强版本，除了支持 [file](./file) 的所有功能外，还允许在文件中定义完整的 HTTP 响应，包括：\n- 响应状态码\n- 响应头部\n- 响应内容\n\n## 规则语法\n``` txt\npattern rawfile://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## Mock 接口\n1. 本地文件 `/User/xxx/raw.txt`\n    ``` js\n    HTTP/1.1 500 OK\n    Content-Type: application/json\n    X-Custom-Header: value\n\n    {\n      \"status\": \"success\",\n      \"data\": \"your content here\"\n    }\n    ```\n2. 配置规则\n    ``` txt\n    https://www.example.com/test/rawfile tpl:///User/xxx/raw.txt\n\n    # 等价于\n    #  https://www.example.com/test/rawfile file:///User/xxx/test.json replaceStatus://500 resType://json resHeaders://x-custom-header=value\n    \n    # 支持远程 URL\n    # pattern rawfile://https://example.com/raw.json\n    ```\n3. 请求 `https://www.example.com/test/rawfile` 返回结果：\n    ``` txt\n    // 状态码\n    500\n\n    // 响应头\n    content-type: application/json\n    x-custom-header: value\n    content-length: 56\n\n    // 响应内容\n    {\n      \"status\": \"success\",\n      \"data\": \"your content here\"\n    }\n    ```\n\n其它功能参考：[file](./file)\n","docs/docs/rules/redirect.md":"# redirect\n将匹配的请求立即重定向（302 Found）到指定URL，不请求到后台服务器。\n\n## 规则语法\n``` txt\npattern redirect://targetUrl [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| targetUrl   | 重定向后的 URL，可以是相对路径 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 基础配置\n``` txt\nwww.example.com/path redirect://https://www.qq.com\n\nwww.example.com/path2 redirect://../abc/123\n```\n- 访问 `https://www.example.com/path/to` 重定向到 `https://www.qq.com`（不自动拼接路径）\n- 访问 `https://www.example.com/path2/to` 重定向到 `https://www.example.com/abc/123`\n\n#### 实现路径拼接\n``` txt\n# 通配符\n^www.example.com/path/*** redirect://`https://www.example.com/$1`\n```\n- 访问 `https://www.example.com/path/to?query` 重定向到 `https://www.example.com/to?query`\n\n#### `301` 跳转\n``` txt\nwww.example.com/path/test redirect://https://www.qq.com replaceStatus://301\n```\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","docs/docs/rules/referer.md":"# referer\n修改请求头的 `referer` 字段，有些服务器会校验请求头的 `referer` 字段，这个协议可以用来绕过这个检测或者测试后台的功能。\n\n## 规则语法\n``` txt\npattern referer://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 完整 URL 链接<br/>• 内联/内嵌/Values内容<br/>⚠️ 不支持从文件/远程 URL 加载数据 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nwww.example.com/path referer://https://www.test.com/x/y/z?xxx\n```\n\n## 关联协议\n1. 等价于：[reqHeaders://referer=https://xxx](./reqHeaders)\n","docs/docs/rules/replaceStatus.md":"# replaceStatus\n\n`replaceStatus` 协议用于替换 HTTP 状态码，在请求响应完成后将原始状态码替换成指定的 HTTP 状态码。通过 `replaceStatus` 协议，你可以：\n- **修改服务器响应的状态码**：基于真实服务器响应进行状态码替换\n- **测试不同状态码的处理逻辑**：在不修改后端代码的情况下测试各种状态码场景\n- **错误场景模拟**：将正常响应转换为错误状态码，测试客户端容错能力\n- **重定向测试**：修改重定向状态码或目标\n\n## 规则语法\n\n`replaceStatus` 协议支持多种方式配置：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要替换的状态码。\n\n```txt\npattern replaceStatus://code [lineProps...] [filters...]\n```\n**示例：**\n```txt\nwww.example.com/api/old replaceStatus://301\n```\n\n### 2. 内嵌值（使用代码块）\n当需要根据条件返回不同状态码，或希望复用配置时可使用此方式。\n\n````txt\npattern replaceStatus://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n404\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的状态码。\n\n```txt\npattern replaceStatus://{key-of-values} [lineProps...] [filters...]\n```\n**前提**：在 `Values` 中存在名为 `key-of-values` 的键，其值为状态码。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载内容。\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **code** | 是 | 要替换的 HTTP 响应状态码，如：<br>• `200` OK<br>• `301` 永久重定向<br>• `302` 临时重定向<br>• `404` 未找到<br>• `500` 服务器错误<br>• 支持所有标准 HTTP 状态码 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n## 配置示例\n\n### 基础示例\n```txt\n# 将任何响应替换为 404 状态码\nwww.example.com replaceStatus://404\n\n# 将服务器错误替换为正常状态码\nwww.example.com/api replaceStatus://200 includeFilter://s:500\n\n# 修改重定向状态码\nwww.example.com/redirect replaceStatus://307\n```\n\n### 模拟认证失败\n```txt\n# 将正常响应替换为 401 状态码，触发浏览器认证弹窗\nwww.example.com/secure-page replaceStatus://401\n\n# 禁止弹出登录框，直接返回 401\nwww.example.com/secure-page replaceStatus://401 disable://userLogin\n\n# 或使用 lineProps 达到同样效果\nwww.example.com/secure-page replaceStatus://401 lineProps://disableUserLogin\n```\n\n### 条件替换\n```txt\n# 仅当原始状态码为 200 时替换为 500\nwww.example.com/api replaceStatus://500 includeFilter://s:200\n\n# 仅替换特定路径的响应状态码\nwww.example.com/api/admin replaceStatus://403\n\n```\n\n### 配合其他协议使用\n````txt\n# 替换状态码并修改响应内容\nwww.example.com/api/error replaceStatus://500 resBody://{errMsg}\n\n``` errMsg\n{\"message\":\"Custom error message\"}\n```\n\n# 替换为 301 重定向并设置新的 Location 头\nwww.example.com/old-url replaceStatus://301 resHeaders://location=https://www.example.com/new-url\n````\n\n### 使用内嵌值\n````txt\n``` maintenance-status\n503\n```\n\n# 将网站所有响应替换为维护状态\nwww.example.com replaceStatus://{maintenance-status}\n````\n\n### 环境特定配置\n```txt\n# 仅在测试环境模拟错误\ntest.example.com/api replaceStatus://500\n\n# 开发环境保持原样\n# dev.example.com/api (不设置 replaceStatus)\n```\n\n## 常见使用场景\n\n### 1. 错误处理测试\n```txt\n# 测试客户端对服务器错误的处理\nwww.example.com/api replaceStatus://500 includeFilter://chance:0.1\n```\n\n### 2. 重定向行为测试\n```txt\n# 测试永久重定向缓存\nwww.example.com/old-page replaceStatus://301\n\n# 测试临时重定向\nwww.example.com/temp-redirect replaceStatus://302\n```\n\n### 3. 认证授权测试\n```txt\n# 测试未认证访问\nwww.example.com/protected replaceStatus://401\n\n# 测试权限不足\nwww.example.com/admin replaceStatus://403\n```\n\n### 4. 限流测试\n```txt\n# 测试请求过多场景\nwww.example.com/api/rate-limited replaceStatus://429 resHeaders://retry-after=60\n```\n\n### 5. 维护模式测试\n```txt\n# 测试维护页面\nwww.example.com replaceStatus://503 resBody://(<h1>系统维护中...</h1>)\n```\n\n## 与 statusCode 的区别\n\n`replaceStatus` 协议与 [`statusCode`](./statusCode) 协议的主要区别在于处理时机：\n- **`replaceStatus`**：**先请求后端服务器**，收到响应后替换状态码\n- **`statusCode`**：**不请求后端服务器**，立即返回指定的状态码\n\n\n## 注意事项\n\n### 1. 与 statusCode 的区别\n- **处理时机**：`replaceStatus` 在响应阶段生效，`statusCode` 在请求阶段生效\n- **服务器访问**：`replaceStatus` 会访问服务器，`statusCode` 不会\n- **使用场景**：需要真实服务器数据时用 `replaceStatus`，完全模拟时用 `statusCode`\n\n### 2. 响应内容保留\n- 默认情况下，替换状态码时响应内容保持不变\n- 可通过配合 [`resBody`](./resBody) 协议修改响应内容\n\n### 3. 认证弹窗控制\n- 替换为 `401` 状态码时，默认会触发浏览器认证弹窗\n- 可通过以下方式禁用弹窗：\n  - 添加 `disable://userLogin`\n  - 添加 `lineProps://disableUserLogin`\n  - 使用 [`enable`](./enable) 协议的反向配置\n\n### 4. 重定向处理\n- 替换为重定向状态码（301、302 等）时，通常需要配合修改 `Location` 响应头\n- 否则客户端可能无法正确处理重定向\n\n### 5. 状态码兼容性\n- 确保替换的状态码与响应内容兼容\n- 例如：不应将 HTML 页面替换为 204（无内容）状态码\n\n## 高级用法\n\n### 状态码转换映射\n```txt\n# 将所有重定向统一为 302\nwww.example.com replaceStatus://302 includeFilter://s:301\n```\n\n### 概率性替换\n```txt\n# 10% 的概率将响应替换为错误\nwww.example.com/api replaceStatus://500 inclideFilter://chance:0.1\n```\n\n### 组合多个规则\n```txt\n# 针对不同原始状态码进行不同替换\nwww.example.com/api replaceStatus://200 includeFilter://s:404\nwww.example.com/api replaceStatus://400 includeFilter://s:403\n```\n\n## 故障排除\n\n### Q: 状态码替换没有生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配请求 URL\n2. 是否有其他更高优先级的规则覆盖\n3. 过滤器条件是否满足（特别是对原始状态码的过滤）\n\n### Q: 响应内容与状态码不匹配\n**A:** 检查：\n1. 是否同时修改了响应内容\n2. 响应头是否与新状态码兼容\n3. 客户端是否能正确处理该状态码\n\n### Q: 浏览器弹出认证窗口\n**A:** 对于 401 状态码：\n1. 检查是否添加了 `disable://userLogin` 或 `lineProps://disableUserLogin`\n2. 确认规则顺序是否正确\n\n### Q: 重定向循环\n**A:** 检查：\n1. 是否设置了正确的 `Location` 头\n2. 重定向目标是否正确\n3. 是否有多个重定向规则冲突\n\n## 关联协议\n\n1. **直接返回状态码**：[statusCode](./statusCode)\n   - 不请求服务器，直接返回指定的状态码\n\n2. **禁止认证弹窗**：[disable](./disable) 或 [lineProps](./lineProps)\n   - 禁用 401 状态码触发的浏览器登录弹窗\n\n3. **设置响应内容**：[resBody](./resBody)\n   - 为状态码响应添加自定义内容\n\n4. **设置响应头**：[resHeaders](./resHeaders)\n   - 为重定向等场景设置必要的响应头\n\n## 扩展阅读\n\n- [HTTP 状态码 MDN 文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status)：了解所有 HTTP 状态码的含义\n- [匹配模式文档](./pattern)：详细了解 URL 匹配规则\n- [过滤器文档](./filters)：了解更多过滤器功能\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","docs/docs/rules/reqAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqAppend.md -->","docs/docs/rules/reqBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqBody.md -->","docs/docs/rules/reqCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCharset.md -->","docs/docs/rules/reqCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCookies.md -->","docs/docs/rules/reqCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCors.md -->","docs/docs/rules/reqDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqDelay.md -->","docs/docs/rules/reqHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqHeaders.md -->","docs/docs/rules/reqMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqMerge.md -->","docs/docs/rules/reqPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqPrepend.md -->","docs/docs/rules/reqReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqReplace.md -->","docs/docs/rules/reqRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqRules.md -->","docs/docs/rules/reqScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqScript.md -->","docs/docs/rules/reqSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqSpeed.md -->","docs/docs/rules/reqType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqType.md -->","docs/docs/rules/reqWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqWrite.md -->","docs/docs/rules/reqWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqWriteRaw.md -->","docs/docs/rules/resAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresAppend.md -->","docs/docs/rules/resBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresBody.md -->","docs/docs/rules/resCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCharset.md -->","docs/docs/rules/resCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCookies.md -->","docs/docs/rules/resCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCors.md -->","docs/docs/rules/resDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresDelay.md -->","docs/docs/rules/resHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresHeaders.md -->","docs/docs/rules/resMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresMerge.md -->","docs/docs/rules/resPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresPrepend.md -->","docs/docs/rules/resReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresReplace.md -->","docs/docs/rules/resRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresRules.md -->","docs/docs/rules/resScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresScript.md -->","docs/docs/rules/resSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresSpeed.md -->","docs/docs/rules/resType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresType.md -->","docs/docs/rules/resWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresWrite.md -->","docs/docs/rules/resWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresWriteRaw.md -->","docs/docs/rules/responseFor.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresponseFor.md -->","docs/docs/rules/rule.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Frule.md -->","docs/docs/rules/skip.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fskip.md -->","docs/docs/rules/sniCallback.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FsniCallback.md -->","docs/docs/rules/socks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fsocks.md -->","docs/docs/rules/statusCode.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FstatusCode.md -->","docs/docs/rules/style.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fstyle.md -->","docs/docs/rules/tpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftpl.md -->","docs/docs/rules/trailers.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftrailers.md -->","docs/docs/rules/tunnel.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftunnel.md -->","docs/docs/rules/ua.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fua.md -->","docs/docs/rules/urlParams.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FurlParams.md -->","docs/docs/rules/weinre.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fweinre.md -->","docs/docs/rules/ws.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fws.md -->","docs/docs/rules/wss.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fwss.md -->","docs/docs/rules/xfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxfile.md -->","docs/docs/rules/xhost.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxhost.md -->","docs/docs/rules/xhttps-proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxhttps-proxy.md -->","docs/docs/rules/xproxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxproxy.md -->","docs/docs/rules/xrawfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxrawfile.md -->","docs/docs/rules/xsocks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxsocks.md -->","docs/docs/rules/xtpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxtpl.md -->","docs/en/docs/extensions/api.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fapi.md -->","docs/en/docs/extensions/dev.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fdev.md -->","docs/en/docs/extensions/npm.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fnpm.md -->","docs/en/docs/extensions/usage.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fusage.md -->","docs/en/docs/rules/@.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2F%40.md -->","docs/en/docs/rules/attachment.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fattachment.md -->","docs/en/docs/rules/auth.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fauth.md -->","docs/en/docs/rules/cache.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fcache.md -->","docs/en/docs/rules/cipher.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fcipher.md -->","docs/en/docs/rules/cssAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssAppend.md -->","docs/en/docs/rules/cssBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssBody.md -->","docs/en/docs/rules/cssPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssPrepend.md -->","docs/en/docs/rules/delete.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fdelete.md -->","docs/en/docs/rules/disable.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fdisable.md -->","docs/en/docs/rules/enable.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fenable.md -->","docs/en/docs/rules/excludeFilter.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FexcludeFilter.md -->","docs/en/docs/rules/file.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ffile.md -->","docs/en/docs/rules/filters.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ffilters.md -->","docs/en/docs/rules/forwardedFor.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FforwardedFor.md -->","docs/en/docs/rules/frameScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FframeScript.md -->","docs/en/docs/rules/headerReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FheaderReplace.md -->","docs/en/docs/rules/host.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhost.md -->","docs/en/docs/rules/htmlAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlAppend.md -->","docs/en/docs/rules/htmlBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlBody.md -->","docs/en/docs/rules/htmlPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlPrepend.md -->","docs/en/docs/rules/http.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttp.md -->","docs/en/docs/rules/https-proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttps-proxy.md -->","docs/en/docs/rules/https.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttps.md -->","docs/en/docs/rules/ignore.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fignore.md -->","docs/en/docs/rules/includeFilter.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FincludeFilter.md -->","docs/en/docs/rules/inherit.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Finherit.md -->","docs/en/docs/rules/jsAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsAppend.md -->","docs/en/docs/rules/jsBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsBody.md -->","docs/en/docs/rules/jsPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsPrepend.md -->","docs/en/docs/rules/lineProps.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FlineProps.md -->","docs/en/docs/rules/locationHref.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FlocationHref.md -->","docs/en/docs/rules/log.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Flog.md -->","docs/en/docs/rules/method.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fmethod.md -->","docs/en/docs/rules/operation.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Foperation.md -->","docs/en/docs/rules/pac.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpac.md -->","docs/en/docs/rules/pathReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FpathReplace.md -->","docs/en/docs/rules/pattern.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpattern.md -->","docs/en/docs/rules/pipe.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpipe.md -->","docs/en/docs/rules/plugin-vars.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fplugin-vars.md -->","docs/en/docs/rules/protocols.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fprotocols.md -->","docs/en/docs/rules/proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fproxy.md -->","docs/en/docs/rules/rawfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Frawfile.md -->","docs/en/docs/rules/redirect.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fredirect.md -->","docs/en/docs/rules/referer.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Freferer.md -->","docs/en/docs/rules/replaceStatus.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreplaceStatus.md -->","docs/en/docs/rules/reqAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqAppend.md -->","docs/en/docs/rules/reqBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqBody.md -->","docs/en/docs/rules/reqCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCharset.md -->","docs/en/docs/rules/reqCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCookies.md -->","docs/en/docs/rules/reqCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCors.md -->","docs/en/docs/rules/reqDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqDelay.md -->","docs/en/docs/rules/reqHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqHeaders.md -->","docs/en/docs/rules/reqMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqMerge.md -->","docs/en/docs/rules/reqPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqPrepend.md -->","docs/en/docs/rules/reqReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqReplace.md -->","docs/en/docs/rules/reqRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqRules.md -->","docs/en/docs/rules/reqScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqScript.md -->","docs/en/docs/rules/reqSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqSpeed.md -->","docs/en/docs/rules/reqType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqType.md -->","docs/en/docs/rules/reqWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqWrite.md -->","docs/en/docs/rules/reqWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqWriteRaw.md -->","docs/en/docs/rules/resAppend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresAppend.md -->","docs/en/docs/rules/resBody.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresBody.md -->","docs/en/docs/rules/resCharset.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCharset.md -->","docs/en/docs/rules/resCookies.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCookies.md -->","docs/en/docs/rules/resCors.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCors.md -->","docs/en/docs/rules/resDelay.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresDelay.md -->","docs/en/docs/rules/resHeaders.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresHeaders.md -->","docs/en/docs/rules/resMerge.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresMerge.md -->","docs/en/docs/rules/resPrepend.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresPrepend.md -->","docs/en/docs/rules/resReplace.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresReplace.md -->","docs/en/docs/rules/resRules.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresRules.md -->","docs/en/docs/rules/resScript.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresScript.md -->","docs/en/docs/rules/resSpeed.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresSpeed.md -->","docs/en/docs/rules/resType.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresType.md -->","docs/en/docs/rules/resWrite.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresWrite.md -->","docs/en/docs/rules/resWriteRaw.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresWriteRaw.md -->","docs/en/docs/rules/responseFor.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresponseFor.md -->","docs/en/docs/rules/rule.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Frule.md -->","docs/en/docs/rules/skip.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fskip.md -->","docs/en/docs/rules/sniCallback.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FsniCallback.md -->","docs/en/docs/rules/socks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fsocks.md -->","docs/en/docs/rules/statusCode.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FstatusCode.md -->","docs/en/docs/rules/style.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fstyle.md -->","docs/en/docs/rules/tpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftpl.md -->","docs/en/docs/rules/trailers.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftrailers.md -->","docs/en/docs/rules/tunnel.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftunnel.md -->","docs/en/docs/rules/ua.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fua.md -->","docs/en/docs/rules/urlParams.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FurlParams.md -->","docs/en/docs/rules/weinre.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fweinre.md -->","docs/en/docs/rules/ws.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fws.md -->","docs/en/docs/rules/wss.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fwss.md -->","docs/en/docs/rules/xfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxfile.md -->","docs/en/docs/rules/xhost.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxhost.md -->","docs/en/docs/rules/xhttps-proxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxhttps-proxy.md -->","docs/en/docs/rules/xproxy.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxproxy.md -->","docs/en/docs/rules/xrawfile.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxrawfile.md -->","docs/en/docs/rules/xsocks.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxsocks.md -->","docs/en/docs/rules/xtpl.md":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxtpl.md -->"},"items":[{"name":"api.md","path":"docs/docs/extensions/api.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/extensions/api.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"# Local Agent API\n\nWhistle 的本地 API，供 AI Agent 或第三方进程以编程方式操作 Whistle 代理服务。支持规则管理、插件控制、抓包数据获取、HTTPS 代理开关、证书管理等核心功能，具体用法如下（确保已全局安装 Whistle 且版本 ≥ 2.10.7）：\n\n``` javascript\nconst cp = require('child_process');\nconst qs = require('querystring');\n\nconst loadModule = require;\nlet w2Api;\n\n/**\n * @async 异步加载 Whistle API 模块\n * @function loadApi\n * @description\n * 尝试加载 Whistle 的 API 模块，并校验其版本是否符合最低要求（≥ 2.10.7）。\n * 如果版本过低，或加载过程中发生任何错误（如子进程调用失败、模块加载异常、网络状态获取失败等），\n * 都会抛出对应的错误。\n * \n * @returns {Promise<Object>} 返回 Whistle API 模块对象（通常包含 network、rules 等接口）\n * \n * @throws {Error} 当执行 `w2 root bin/api` 命令失败时抛出（例如 whistle 未安装或未在 PATH 中，需要通过 `npm install -g whistle` 安装）\n * @throws {Error} 当调用 `api.network.getStatus()` 失败时抛出（例如 whistle 或 whistle client 服务未启动）\n * @throws {Error} 当 Whistle 版本低于 2.10.7 时抛出，错误信息为 'Whistle version must be >= 2.10.7'\n * \n * @example\n * try {\n *   const api = await loadApi();\n *   // 使用 api.network.getStatus() 等\n * } catch (err) {\n *   console.error('加载 Whistle API 失败:', err.message);\n * }\n */\nconst loadApi = async () => {\n  if (!w2Api) {\n    w2Api = loadModule(cp.spawnSync('w2', ['root', 'bin/api']).stdout.toString().trim());\n  }\n  const { version } = await w2Api.network.getStatus();\n  const [major, minor, patch] = version.split('.').map(Number);\n  w2Api.version = version;\n  if (major > 2 || (major == 2 && (minor > 10 || (minor == 10 && patch >= 7)))) {\n    return w2Api;\n  }\n  throw new Error('Whistle version must be >= 2.10.7');\n};\n\n/**\n * @abstract 判断当前 Whistle 是否启用了 HTTPS 代理\n * @function isEnabledHTTPS\n * @description\n * 通过 Whistle API 检查当前代理是否启用了 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了 HTTPS 代理\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.isEnabledHTTPS()` 调用失败\n *\n * @example\n * try {\n *   const enabled = await isEnabledHTTPS();\n *   console.log(`当前 HTTPS 代理是否启用: ${enabled}`);\n * } catch (err) {\n *   console.error('检查 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst isEnabledHTTPS = async () => {\n  const api = await loadApi();\n  return await api.isEnabledHTTPS();\n};\n\n/**\n * @async 设置 Whistle 是否启用 HTTPS 代理\n * @function setEnableHTTPS\n * @description\n * 通过 Whistle API 设置当前代理是否启用 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用 HTTPS 代理\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.setEnableHTTPS()` 调用失败\n *\n * @example\n * try {\n *   await setEnableHTTPS(true);\n *   console.log('设置 HTTPS 代理启用状态成功');\n * } catch (err) {\n *   console.error('设置 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst setEnableHTTPS = async (enable) => {\n  const api = await loadApi();\n  await api.setEnableHTTPS(enable);\n};\n\n/**\n * @async 获取 Whistle 的根证书信息\n * @function getRootCA\n * @description\n * 通过 Whistle API 获取当前代理的根证书信息，包括证书内容、私钥等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Buffer>} 返回根证书的 Buffer 对象，可用于保存为 .crt 文件或进行其他操作\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getRootCA()` 调用失败\n */\nconst getRootCA = async () => {\n  const api = await loadApi();\n  return await api.getRootCA();\n};\n\n/**\n * @async 创建一个临时文件并写入指定内容\n * @function createFile\n * @description\n * 通过 Whistle API 创建一个临时文件，并将指定的内容写入该文件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {Buffer|string} data - 要创建的文件内容，可以是 Buffer 或字符串\n * @returns {Promise<string>} 返回创建的文件路径\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.createFile()` 调用失败\n *\n * @example\n * try {\n *   const filePath = await createFile('test');\n *   console.log(`临时文件已创建: ${filePath}`);\n *   // 临时文件已创建: temp/7d2e9a424672279e5bcaab948a7a63fd47cb381626555835e828438762f86a96\n * } catch (err) {\n *   console.error('创建临时文件失败:', err.message);\n * }\n */\nconst createFile = async (data) => {\n  const api = await loadApi();\n  return await api.createFile(data);\n};\n\n/**\n * @async 获取指定文件的内容\n * @function getFile\n * @description\n * 通过 Whistle API 获取指定路径的文件内容，返回其字符串内容。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} filePath - 要读取的文件路径\n * @returns {Promise<string>} 返回文件内容的字符串\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getFile()` 调用失败（如文件不存在）\n */\nconst getFile = async (filePath) => {\n  const api = await loadApi();\n  return await api.getFile(filePath);\n};\n\n/**\n * @async 获取 Whistle 规则引擎的当前状态配置\n * @function getRulesStatus\n * @description\n * 通过 Whistle API 获取规则列表、启用状态以及优先级策略等运行时配置。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<{\n *   laterRulesFirst: boolean,   // 是否启用“后置规则优先”策略（即后匹配的规则覆盖前匹配的）\n *   multiSelect: boolean,       // 是否启用多选模式\n *   pluginsDisabled: boolean,   // 是否全局禁用所有插件规则\n *   disabled: boolean,          // 是否全局禁用 Whistle 代理（所有规则失效）\n *   list: Array<{\n *     name: string,            // 规则文件/配置的名称\n *     selected: boolean        // 当前是否处于激活（勾选）状态\n *   }>\n * }>} 返回包含规则状态和列表的对象\n *\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.getStatus()` 调用失败\n *\n * @example\n * try {\n *   const { list, disabled, multiSelect, laterRulesFirst } = await getRulesStatus();\n *   const activeRules = list.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则状态失败:', err.message);\n * }\n */\nconst getRulesStatus = async () => {\n  const api = await loadApi();\n  return await api.rules.getStatus();\n};\n\n/**\n * @async 关闭 Whistle 规则引擎\n * @function turnOffRules\n * @description\n * 通过 Whistle API 全局禁用所有规则（不包括插件规则）。\n * 该操作会立即生效，影响当前代理会话中的所有请求处理。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOff()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOffRules();\n *   console.log('规则已关闭，所有请求将不再受 Whistle 规则影响');\n * } catch (err) {\n *   console.error('关闭规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOffRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOff();\n};\n\n/**\n * @async 启用 Whistle 规则引擎\n * @function turnOnRules\n * @description\n * 通过 Whistle API 全局启用之前配置的所有规则（不包括插件的规则）。\n * 该操作会立即生效，恢复代理请求的规则处理逻辑。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOn()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOnRules();\n *   console.log('规则已启用，请求将按配置规则处理');\n * } catch (err) {\n *   console.error('启用规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOnRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOn();\n};\n\n/**\n * @async 选中（激活）指定的 Whistle 规则文件，并自动启用规则引擎\n * @function selectRule\n * @description\n * 调用 Whistle API 选中指定名称的规则文件（通常从规则列表中选择），\n * 若选中操作成功（返回 truthy 值），则自动调用 `turnOnRules()` 启用全局规则引擎，\n * 使该规则立即生效。该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要激活的规则文件名称（需与规则列表中的名称精确匹配）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.select()` 调用失败（如网络异常）\n * @throws {Error} 若选中成功但启用规则引擎（`turnOnRules()`）失败时抛出\n *\n * @example\n * try {\n *   await selectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已选中并启用');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst selectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.select(ruleName);\n  if (result) {\n    await turnOnRules();\n  }\n  await result;\n};\n\n/**\n * @async 取消选中（停用）指定的 Whistle 规则文件\n * @function unselectRule\n * @description\n * 调用 Whistle API 取消选中指定名称的规则文件（通常从规则列表中选择），\n * 该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要取消激活的规则文件名称（需与规则列表中的名称精确匹配，如果 ruleName 不填则取消所有规则）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.unselect()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await unselectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已取消选中');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst unselectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.unselect(ruleName);\n  await result;\n};\n\n/**\n * @async 获取指定规则文件的内容详情\n * @function getRule\n * @description\n * 通过 Whistle API 读取指定名称的规则文件内容，返回其详细配置信息。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 规则文件的名称（需与 Whistle 规则列表中的名称精确匹配）\n *\n * @returns {Promise<{\n *   value: string,       // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean,     // 当前是否被选中\n * }>} 返回规则对象，具体字段可能因 Whistle 版本而异\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.get()` 调用失败（如规则名称不存在）\n *\n * @example\n * try {\n *   const rule = await getRule('my-rules');\n *   console.log(`规则的内容:\\n${rule.value}`);\n *   if (rule.selected) {\n *     console.log('该规则当前已激活');\n *   }\n * } catch (err) {\n *   console.error('获取规则失败:', err.message);\n * }\n */\nconst getRule = async (ruleName) => {\n  const api = await loadApi();\n  return await api.rules.get(ruleName);\n};\n\n/**\n * @async 获取当前 Whistle 规则列表\n * @function getRuleList\n * @description\n * 通过 Whistle API 获取所有已配置的规则文件列表，包含每个规则的名称、内容及激活状态。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @returns {Promise<Array<{\n *   name: string,      // 规则文件名称\n *   value: string,     // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean  // 当前是否被选中（激活）\n * }>>} 返回规则对象数组，若无可返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.getList()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   const rules = await getRuleList();\n *   console.log(`共 ${rules.length} 条规则`);\n *   const activeRules = rules.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则列表失败:', err.message);\n * }\n */\nconst getRuleList = async () => {\n  const api = await loadApi();\n  return await api.rules.getList();\n};\n\n/**\n * @async 判断当前 Whistle 是否启用了多选模式\n * @function isMultiSelect\n * @description\n * 通过 Whistle API 检查当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了多选模式\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.isMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   const multiSelect = await isMultiSelect();\n *   console.log(`当前规则引擎是否启用多选模式: ${multiSelect}`);\n * } catch (err) {\n *   console.error('检查多选模式状态失败:', err.message);\n * }\n */\nconst isMultiSelect = async () => {\n  const api = await loadApi();\n  return await api.rules.isMultiSelect();\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用多选模式\n * @function setMultiSelect\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用多选模式\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   await setMultiSelect(true);\n *   console.log('已启用多选模式');\n * } catch (err) {\n *   console.error('设置多选模式失败:', err.message);\n * }\n */\nconst setMultiSelect = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setMultiSelect(enable);\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用后置规则优先\n * @function setLaterRulesFirst\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了后置规则优先。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {boolean} enable - 是否启用后置规则优先\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setLaterRulesFirst()` 调用失败\n *\n * @example\n * try {\n *   await setLaterRulesFirst(true);\n *   console.log('已启用后置规则优先');\n * } catch (err) {\n *   console.error('设置后置规则优先失败:', err.message);\n * }\n */\nconst setLaterRulesFirst = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setLaterFirst(enable);\n};\n\n/**\n * @async 添加一个新的规则文件到 Whistle 规则引擎中\n * @function addRule\n * @description\n * 通过 Whistle API 添加一个新的规则文件，并可选择是否立即选中（激活）该规则。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} name - 规则名称\n * @param {string} value - 规则内容\n * @param {boolean} selected - 是否选中\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.add()` 调用失败\n *\n * @example\n * try {\n *   await addRule('my-rule', '* www.example.com 127.0.0.1', true);\n *   console.log('已添加规则');\n * } catch (err) {\n *   console.error('添加规则失败:', err.message);\n * }\n */\nconst addRule = async (name, value, selected) => {\n  const api = await loadApi();\n  await api.rules.add(name, value, selected);\n};\n\n/**\n * @async 将指定规则文件移动到规则列表顶部\n * @function moveRuleToTop\n * @description\n * 通过 Whistle API 将指定规则文件移动到规则列表的顶部。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} ruleName - 规则文件的名称（需与规则列表中的名称精确匹配）\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.moveToTop()` 调用失败\n *\n * @example\n * try {\n *   await moveRuleToTop('my-rule');\n *   console.log('已将规则移动到顶部');\n * } catch (err) {\n *   console.error('移动规则失败:', err.message);\n * }\n */\nconst moveRuleToTop = async (ruleName) => {\n  const api = await loadApi();\n  await api.rules.moveToTop(ruleName);\n};\n\n/**\n * @async 获取 Whistle 的 Values 列表\n * @function getValueList\n * @description\n * 通过 Whistle API 获取当前配置的 Values 列表，返回每个 Value 的名称和值。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Array<{ name: string, value: string }>>} 返回 Values 对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.getList()` 调用失败\n *\n * @example\n * try {\n *   const values = await getValueList();\n *   console.log(`共 ${values.length} 个 Values`);\n *   values.forEach(v => console.log(`${v.name}: ${v.value}`));\n * } catch (err) {\n *   console.error('获取 Values 列表失败:', err.message);\n * }\n */\nconst getValueList = async () => {\n  const api = await loadApi();\n  return await api.values.getList();\n};\n\n/**\n * @async 获取指定的 Value\n * @function getValue\n * @description\n * 通过 Whistle API 获取指定的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @returns {Promise<{ name: string, value: string }>} 返回 Value 对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.get()` 调用失败\n *\n * @example\n * try {\n *   const value = await getValue('my-value');\n *   console.log(`${value.name}: ${value.value}`);\n * } catch (err) {\n *   console.error('获取 Value 失败:', err.message);\n * }\n */\nconst getValue = async (name) => {\n  const api = await loadApi();\n  return await api.values.get(name);\n};\n\n/**\n * @async 添加一个新的 Value\n * @function addValue\n * @description\n * 通过 Whistle API 添加一个新的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @param {string} value - Value 的值\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.add()` 调用失败\n *\n * @example\n * try {\n *   await addValue('my-value', 'some-value');\n *   console.log('已添加 Value');\n * } catch (err) {\n *   console.error('添加 Value 失败:', err.message);\n * }\n */\nconst addValue = async (name, value) => {\n  const api = await loadApi();\n  await api.values.add(name, value);\n};\n\n/**\n * @async 获取 Whistle 插件的状态信息\n * @function getPluginsStatus\n * @description\n * 通过 Whistle API 获取当前已安装插件的状态信息，包括每个插件的名称、启用状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{ name: string, moduleName: string, version: string, selected: boolean }>>} 返回插件状态对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getStatus()` 调用失败\n * \n * @example\n * try {\n *   const plugins = await getPluginsStatus();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => console.log(`${p.name}: ${p.selected ? '启用' : '禁用'}`));\n * } catch (err) {\n *   console.error('获取插件状态失败:', err.message);\n * }\n */\nconst getPluginsStatus = async () => {\n  const api = await loadApi();\n  return await api.plugins.getStatus();\n};\n\n/**\n * @async 启用 Whistle 插件\n * @function turnOnPlugins\n * @description\n * 通过 Whistle API 启用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOn()` 调用失败\n * \n * @example\n * try {\n *   await turnOnPlugins();\n *   console.log('已启用所有插件');\n * } catch (err) {\n *   console.error('启用插件失败:', err.message);\n * }\n */\nconst turnOnPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOn();\n};\n\n/**\n * @async 禁用 Whistle 插件\n * @function turnOffPlugins\n * @description\n * 通过 Whistle API 禁用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOff()` 调用失败\n * \n * @example\n * try {\n *   await turnOffPlugins();\n *   console.log('已禁用所有插件');\n * } catch (err) {\n *   console.error('禁用插件失败:', err.message);\n * }\n */\nconst turnOffPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOff();\n};\n\n/**\n * @async 获取 Whistle 插件列表\n * @function getPluginList\n * @description\n * 通过 Whistle API 获取当前已安装的所有插件信息，包括插件元数据、版本、描述、规则配置及激活状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>>} 返回插件对象数组，若无插件则返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getList()` 调用失败\n *\n * @example\n * try {\n *   const plugins = await getPluginList();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => {\n *     console.log(`${p.name} (${p.version}): ${p.selected ? '启用' : '禁用'}`);\n *     console.log(`  描述: ${p.description}`);\n *   });\n * } catch (err) {\n *   console.error('获取插件列表失败:', err.message);\n * }\n */\nconst getPluginList = async () => {\n  const api = await loadApi();\n  return await api.plugins.getList();\n};\n\n/**\n * @async 获取指定的 Whistle 插件\n * @function getPlugin\n * @description\n * 通过 Whistle API 获取指定的插件信息。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>} 返回插件对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.get()` 调用失败\n * \n * @example\n * try {\n *   const plugin = await getPlugin('my-plugin');\n *   console.log(`${plugin.name}: ${plugin.enabled ? '启用' : '禁用'}`);\n * } catch (err) {\n *   console.error('获取插件失败:', err.message);\n * }\n */\nconst getPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.get(name);\n};\n\n/**\n * @async 选择指定的 Whistle 插件\n * @function selectPlugin\n * @description\n * 通过 Whistle API 选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.select()` 调用失败\n * \n * @example\n * try {\n *   const result = await selectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已选择并启用');\n *   } else {\n *     console.log('插件选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('选择插件失败:', err.message);\n * }\n */\nconst selectPlugin = async (name) => {\n  const api = await loadApi();\n  const result = await api.plugins.select(name);\n  if (result) {\n    await turnOnPlugins();\n  }\n  return result;\n};\n\n/**\n * @async 取消选择指定的 Whistle 插件\n * @function unselectPlugin\n * @description\n * 通过 Whistle API 取消选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回取消选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.unselect()` 调用失败\n * @example\n * try {\n *   const result = await unselectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已取消选择并禁用');\n *   } else {\n *     console.log('插件取消选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('取消选择插件失败:', err.message);\n * }\n */\nconst unselectPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.unselect(name);\n};\n\nconst URLENCODED_RE = /application\\/x-www-form-urlencoded/i;\nconst POST_RE = /^post$/i;\nconst isUrlEncoded = (req) => {\n  var type = req.headers?.['content-type'];\n  if (!type || !POST_RE.test(req.method)) {\n    return;\n  }\n  return URLENCODED_RE.test(type);\n};\n\nconst formatData = (session, utils, isRes) => {\n  let data = session[isRes ? 'res' : 'req'];\n  let body = data.body || '';\n  let raw;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      raw = raw || (isRes ? utils.getRawRes(data) : utils.getRawReq(data));\n      body = raw ? raw.substring(raw.indexOf('\\r\\n\\r\\n') + 4) : '';\n    }\n    return body;\n  };\n\n  Object.defineProperties(data, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        if (json) {\n          return json;\n        }\n        getBody();\n        if (body) {\n          if (!isRes && isUrlEncoded(data)) {\n            json = qs.parse(body);\n          } else {\n            try {\n              json = JSON.parse(body);\n            } catch (e) {}\n          }\n        }\n        json = json || {};\n        return json;\n      }\n    },\n    raw: {\n      configurable: true,\n      get() {\n        if (raw == null) {\n          raw = isRes ? utils.getRawRes(data) : utils.getRawReq(data);\n        }\n        return raw;\n      }\n    },\n  });\n};\n\nconst formatSession = (session, utils) => {\n  let timings;\n  let rules;\n  Object.defineProperties(session, {\n    timings: {\n      configurable: true,\n      get() {\n        timings = timings || utils.getTimings(session);\n        return timings;\n      }\n    },\n    rawRules: {\n      configurable: true,\n      get() {\n        rules = rules || utils.getRules(session);\n        return rules.join('\\n');\n      }\n    },\n  });\n  formatData(session, utils);\n  formatData(session, utils, false);\n  return session;\n};\n\n/**\n * @async 获取抓包数据（支持自动分页拉取）\n * @function getSessions\n * @description\n * 用于按条件检索 HTTP/WebSocket 等协议的抓包记录。支持基于游标、时间、资源类型、URL 关键词、请求方法、状态码以及请求/响应头的组合筛选。\n *\n * **过滤逻辑**：\n * - 所有非空条件均以 **AND** 关系叠加（即需同时满足）；\n * - 对于 `method` 和 `statusCode`，传入多个值时采用 **OR** 关系（匹配任一即可）；\n * - `latest` 优先级最高，一旦为 `true`，count 最多只返回 120 条最新记录；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n * - `reqHeader` 和 `resHeader` 为对象类型，支持按请求头/响应头的字段名和值进行子串匹配。\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新数据末尾。\n * - 分页游标自动使用上一页最后一条记录的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count` 和任何游标/时间参数。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新数据开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的数据通常为空。如需获取大量历史数据，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {number} [options.count=3600] - 期望获取的最大记录数。实际返回数量 ≤ 此值，且受每页 120 条限制及数据实际数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的记录。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（通常为记录的自增主键或唯一序号，格式如 `\"1784905385300-003\"`）。\n *                                            获取记录 ID **大于**此值的增量数据（即更新的数据）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级）。\n *                                       获取记录生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.type] - 资源类型过滤，同抓包界面 Network 底部过滤栏选项（不区分大小写）。\n *                                  支持以下枚举值（传入其它字符串表示不过滤）：\n *                                  - `JSON`：JSON 请求/响应\n *                                  - `HTML`：HTML 文档\n *                                  - `CSS`：样式表\n *                                  - `JS`：JavaScript 脚本\n *                                  - `Font`：字体文件\n *                                  - `Img`：图片资源\n *                                  - `Media`：音视频媒体\n *                                  - `WS`：WebSocket 帧数据\n *                                  - `Tunnel`：隧道连接（如 WebSocket 升级）\n *                                  - `Wasm`：WebAssembly 模块\n *                                  - `Mock`：Mock 数据\n *                                  - `Rules`：匹配规则的请求\n *                                  - `Import`：导入的数据\n *                                  - `Composer`：通过 Composer 发送的请求\n *                                  - `Error`：错误请求（状态码 >= 400、captureError、中断的请求等）\n *                                  - `captureError`：解析 HTTPS 失败的 Tunnel 请求\n * @param {string} [options.subUrl] - URL 中包含该字符串的请求（不区分大小写）。\n *                                    例如 `\"/api/user\"` 将匹配所有包含该路径的 URL。\n * @param {string|string[]} [options.method] - HTTP 请求方法，支持多个（数组或逗号分隔字符串，如 `\"GET,POST\"`，不区分大小写）。\n *                                             匹配任一方法即满足条件。\n * @param {number|number[]} [options.statusCode] - HTTP 响应状态码，支持多个（数组或逗号分隔字符串，如 `\"200,404\"`）。\n *                                                 匹配任一状态码即满足条件。\n * @param {Object} [options.reqHeader] - 请求头过滤条件。\n * @param {string} options.reqHeader.name - 请求头字段名称（如 `\"X-Request-Id\"`，不区分大小写）。\n * @param {string} [options.reqHeader.subValue] - 可选，请求头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该请求头字段（无论值为何）。\n * @param {Object} [options.resHeader] - 响应头过滤条件。\n * @param {string} options.resHeader.name - 响应头字段名称（如 `\"Content-Type\"`，不区分大小写）。\n * @param {string} [options.resHeader.subValue] - 可选，响应头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该响应头字段（无论值为何）。\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的抓包记录数组（经过 `formatSession` 格式化处理）。\n *                                    每条记录包含以下主要字段（可能因 `formatSession` 处理而略有增减）：\n *                                    - `id` (string): 记录唯一标识，格式为 `\"时间戳-序号\"`（如 `\"1785144349945-573\"`）\n *                                    - `startTime` (number): 请求开始时间戳（Unix 毫秒级）\n *                                    - `url` (string): 完整请求 URL\n *                                    - `req` (Object): 请求详情\n *                                      - `method` (string): 请求方法\n *                                      - `ip` (string): 客户端 IP\n *                                      - `port` (string): 客户端端口\n *                                      - `headers` (Object): 请求头键值对\n *                                      - `json` (Object): 解析后的 JSON 请求体（若存在）\n *                                      - `body` (string): 原始请求体（可能为空）\n *                                      - `raw` (string): 原始请求数据\n *                                    - `res` (Object): 响应详情\n *                                      - `ip` (string): 服务器 IP\n *                                      - `port` (string): 服务器端口\n *                                      - `statusCode` (number): HTTP 状态码\n *                                      - `headers` (Object): 响应头键值对\n *                                      - `json` (Object): 解析后的 JSON 响应体（若存在）\n *                                      - `body` (string): 原始响应体（可能为空）\n *                                      - `raw` (string): 原始响应数据\n *                                    - `timings` (Object): 请求各阶段耗时（单位：毫秒）\n *                                      - `start` (number): 请求开始的时间戳（相对于某个基准点）\n *                                      - `ttfb` (number | null): 首字节时间\n *                                      - `dns` (number | null): DNS 解析耗时\n *                                      - `connect` (number | null): TCP 连接耗时（从 DNS 结束到连接建立）\n *                                      - `request` (number | null): 请求发送耗时（从 DNS 结束到请求结束）\n *                                      - `response` (number | null): 响应接收耗时（从请求结束到接收到响应头）\n *                                      - `download` (number | null): 下载耗时（从接收响应头到接收完响应体）\n *                                      - `total` (number | null): 总耗时\n *                                    - `rules` (Object): 匹配的规则对象\n *                                    - `rawRules` (string): 原始规则字符串\n *                                    - `version` (string): Whistle 版本号\n *                                    - `nodeVersion` (string): Node.js 版本号\n *                                    - `dnsTime` (number): DNS 完成时间戳\n *                                    - `requestTime` (number): 请求发送完成时间戳\n *                                    - `connectTime` (number): 连接建立完成时间戳\n *                                    - `responseTime` (number): 响应头接收完成时间戳\n *                                    - `endTime` (number): 请求结束时间戳\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误，需确保 whistle 已全局安装）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.network.getSessions()` 调用失败（如网络异常、参数错误）\n * @throws {Error} 底层 API 调用可能抛出其他运行时错误（如超时、数据格式异常）\n *\n * @example\n * // 使用游标增量拉取（最多 100 条）\n * const sessions = await getSessions({ startId: '1784905385300-003', count: 100 });\n *\n * @example\n * // 获取最新的 JSON 类型记录（最多 120 条）\n * const sessions = await getSessions({ latest: true, type: 'JSON' });\n *\n * @example\n * // 组合过滤：查询状态码为 200 或 304 的 GET 请求，且 URL 包含 \"/v1\"，同时要求存在特定请求头\n * const sessions = await getSessions({\n *   subUrl: '/v1',\n *   method: 'GET',\n *   statusCode: [200, 304],\n *   reqHeader: { name: 'X-Token', subValue: 'abc' },\n *   count: 50\n * });\n */\nconst getSessions = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const sessions = await api.network.getSessions(options);\n    const len = sessions.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatSession(sessions[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = sessions[len - 1]?.id;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n\nconst formatFrame = (frame, utils) => {\n  let body;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      body = utils.getText(frame);\n    }\n    return body;\n  };\n  Object.defineProperties(frame, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        getBody();\n        if (body) {\n          try {\n            json = JSON.parse(getBody());\n          } catch (e) {}\n        }\n        json = json || {};\n        return json;\n      },\n    }\n  });\n  return frame;\n}\n\n/**\n * @async 获取 WebSocket/Socket 的帧数据（支持自动分页拉取）\n * @function getFrames\n * @description\n * 按条件检索指定连接（通过 `reqId` 关联）的 WebSocket/Socket 通信帧，支持基于游标、时间、来源方向的组合筛选。\n *\n * **过滤逻辑**：\n * - `from` 参数用于过滤帧的发送方向：`client`（客户端→服务端）、`server`（服务端→客户端），不传则返回全部方向；\n * - `latest` 优先级最高，一旦为 `true`，count 最大为 120；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条帧记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新帧末尾。\n * - 分页游标自动使用上一页最后一条帧的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count`。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新帧开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的帧通常为空。如需获取大量历史帧，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {string} options.reqId - 会话 ID，对应抓包记录的 `id` 字段，用于关联帧所属的连接\n * @param {number} [options.count=3600] - 期望获取的最大帧数。实际返回数量 ≤ 此值，且受每页 120 条限制及实际帧数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的帧。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（帧的唯一序号），获取帧 ID **大于**此值的增量数据（即更新的帧）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级），获取帧生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.from] - 帧的来源方向过滤（不区分大小写）：\n *                                   - `'client'`：仅获取客户端→服务端的帧\n *                                   - `'server'`：仅获取服务端→客户端的帧\n *                                   - 不传或传入其他值：获取该连接的所有帧\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的帧数据数组（经过 `formatFrame` 格式化处理）。\n *                                    每条帧包含以下主要字段：\n *                                    - `reqId` (string): 所属会话 ID，对应抓包记录的 `id`\n *                                    - `frameId` (string): 帧数据的唯一标识 ID（可用于游标）\n *                                    - `mask` (boolean): 是否为掩码帧（WebSocket 掩码）\n *                                    - `compressed` (boolean): 帧数据是否经过压缩\n *                                    - `length` (number): 帧数据的原始长度（字节数）\n *                                    - `opcode` (number): 帧操作码，`1` 表示文本帧，`2` 表示二进制帧\n *                                    - `base64` (string): Base64 编码的帧数据（用于二进制或大文本）\n *                                    - `body` (string): 解码后的帧内容（文本或可读字符串）\n *                                    - `json` (Object): 若帧数据为 JSON 格式，则解析为对象，否则为空对象 `{}`\n *\n * @throws {Error} 依赖 `loadApi` 可能抛出的错误（如 Whistle 未安装、版本过低、服务未启动）\n * @throws {Error} 底层 `api.network.getFrames()` 调用失败（如 `reqId` 不存在、参数错误、网络异常）\n *\n * @example\n * // 使用游标增量拉取，获取客户端发出的前 10 条帧\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   startTime: 1784903620156,\n *   count: 10,\n *   from: 'client'\n * });\n *\n * @example\n * // 获取最新的帧（最多 120 条）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   latest: true\n * });\n *\n * @example\n * // 从指定游标开始拉取所有后续帧（直到最新）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   count: 3600\n * });\n */\nconst getFrames = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const frames = await api.network.getFrames(options);\n    const len = frames.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatFrame(frames[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = frames[len - 1]?.frameId;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n```\n","isInternal":false,"tokens":14693,"sizeBytes":50628},{"name":"dev.md","path":"docs/docs/extensions/dev.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/extensions/dev.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"# 插件开发\n您已了解插件的各项功能特性（参考[使用文档](./usage)），现在我们将通过模块化方式演示具体实现。\n> 每个核心功能作为独立插件实现，保持单一职责原则，实际开发中可自由拼装这些功能模块。\n\n## 准备工作\n1. 创建一个空目录，如\n   ``` sh\n    mkdir examples && cd examples\n   ```\n2. 安装脚手架 [lack](https://github.com/avwo/lack)\n   ``` txt\n    npm i -g lack\n   ```\n   > lack 必须用最新版本（`>= 1.4.0`）\n3. 更新 [Whistle](https://github.com/avwo/whistle)\n   > Whistle 必须用最新版本（`>= 2.9.100`，[客户端](https://github.com/avwo/whistle-client) `>= 1.3.8`）\n\n## rules.txt\n- 插件安装后会自动加载生效\n- 功能跟界面 Rules 配置的规则一样，优先级低于界面 Rules 规则\n- 插件被禁用立即失效\n\n**示例**\n1. 创建包含 `rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules && cd whistle.test-rules\n\n    # 初始化包含 rules.txt 的插件\n    lack init rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `rules.txt` 文件：\n    ``` txt\n    * reqHeaders://x-client=whistle.test-rules\n    ```\n3. 所有经过 Whistle 的请求都会自动添加请求头 `x-client: whistle.test-rules`\n   \n   <img src=\"/img/test-rules.png\" width=\"600\" />\n4. 如果规则修改后未生效？请确认：\n    - 是否已执行 `lack watch`\n    - 是否跟界面配置冲突规则\n    - 插件是否被禁用\n\n## _rules.txt\n- 仅对匹配插件协议的请求生效\n- 支持长协议(whistle.myplugin://)和短协议(myplugin://)\n- 作用于请求处理阶段\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n1. 创建包含 `_rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-req-rules && cd whistle.test-req-rules\n\n    # 初始化包含 _rules.txt 的插件\n    lack init _rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-req-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `_rules.txt` 文件：\n    ``` txt\n    * file://(hello)\n    ```\n    > 所有请求返回响应内容 `hello`\n4. 直接访问 `https://www.example.com/test` 返回正常页面\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/test whistle.test-req-rules://\n    ```\n    后再访问 `https://www.example.com/test` 返回 `hello`\n   \n## resRules.txt\n- 仅对匹配插件协议的请求生效\n- 作用于响应处理阶段\n- 可修改响应状态码和内容\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n\n1. 创建包含 `resRules.txt` 规则文件的插件：\n   ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules && cd whistle.test-res-rules\n\n    # 初始化包含 resRules.txt 的插件\n    lack init resRules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `resRules.txt` 文件：\n    ``` txt\n    * resBody://`(whistle_error_${statusCode})` includeFilter://s:500 includeFilter://s:404\n    ```\n    > 把响应状态码为 `404` 和 `500` 的请求的响应内容改为 `whistle_error_404` 和  `whistle_error_500`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/500 whistle.test-req-rules:// statusCode://500\n    https://www.example.com/404 whistle.test-req-rules:// statusCode://404\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/500` 响应内容被改成 `whistle_error_500`\n      - 访问 `https://www.example.com/404` 响应内容被改成 `whistle_error_404`\n      - 访问 `https://www.example.com/test` 则返回原始内容\n\n## auth\n对经过 Whistle 的请求进行鉴权。\n\n**示例**\n\n1. 创建请求鉴权插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-auth && cd whistle.test-auth\n\n    # 初始化包含 auth 的插件\n    lack init auth\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-auth` 插件\n    lack watch\n    ```\n2. 编辑 `src/auth.ts` 文件：\n    ``` ts\n    export default async (req: Whistle.PluginAuthRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // URL 里面包含 `/test/forbidden` 的响应状态码为 403\n      if (fullUrl.includes('/test/forbidden')) {\n        return false;\n      }\n      // URL 里面包含 `/test/message/forbidden` 的响应状态码为 403，且自定义响应内容\n      if (fullUrl.includes('/test/message/forbidden')) {\n        req.setHtml('<strong>Access Denied</strong>');\n        return false;\n      }\n\n      // URL 里面包含 `/test/login` 要求用户输入用户名和密码\n      if (fullUrl.includes('/test/login')) {\n        const auth = req.headers.authorization || req.headers['proxy-authorization'];\n        if (auth) {\n          // TODO: 校验用户名和密码，如果正确返回 true，否则返回 false\n          return true;\n        }\n        req.setLogin(true);\n        return false;\n      }\n\n      // URL 里面包含 `/test/redirect` 的响应状态码为 302，且重定向到 `https://www.example.com/test`\n      if (fullUrl.includes('/test/redirect')) {\n        req.setRedirect('https://www.example.com/test');\n        return false;\n      }\n      // 其它请求直接放过\n      // 如果需要添加自定义请求头，可以使用 `req.setHeader` 方法\n      // 支持添加 key 前缀为 `x-whistle-` 的请求头\n      // 例如：req.setHeader('x-whistle-xxx', 'value');\n      req.setHeader('x-whistle-custom-header', 'lack');\n      return true;\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com whistle.auth://\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/test/forbidden` 响应状态码 `403`，响应内容 `Forbidden`\n      - 访问 `https://www.example.com/test/message/forbidden` 响应状态码 `403`，响应内容 `<strong>Access Denied</strong>`\n      - 访问 `https://www.example.com/test/login` 弹出浏览器的登录框\n      - 访问 `https://www.example.com/test/redirect` 重定向到 `https://www.example.com/test`\n      - 其它 `www.example.com` 的请求正常，且被添加请求头 `x-whistle-custom-header: lack`\n\n## sniCallback\n通过插件动态下发请求证书。\n\n**示例**\n\n1. 创建自定义证书插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-sni\n\n    # 初始化包含 sni 的插件\n    lack init sni\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-sni` 插件\n    lack watch\n    ```\n2. 编辑 `src/sniCallback.ts` 文件：\n    ``` ts\n    const key = `...`;\n    const cert = `...`;\n\n    // sniCallback 插件处理函数 - 根据请求 URL 动态决定 TLS 隧道处理方式\n    export default async (req: Whistle.PluginSNIRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // 特殊域名返回 false，保持 TUNNEL 状态（不解除 TLS 加密）\n      if (fullUrl === 'https://tunnel.example.com') {\n        return false;\n      }\n\n      // 对特定域名使用自定义证书解密，返回一个包含 key 和 cert 的对象\n      // 支持 .crt、.pem、.cer 等格式的证书\n      if (fullUrl === 'https://custom.example.com') {\n        return { key, cert };\n      }\n      // 默认使用 Whistle 内置证书解密 TLS 流量\n      return true;\n    };\n    ```\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com sniCallback://test-sni\n    ```\n6.  效果：\n   - 访问 `https://tunnel.example.com/path`，请求保持 TUNNEL 状态（不解除 TLS 加密）\n   - 访问 `https://custom.example.com/test/` 报证书出错\n   - 其它请求正常\n\n## rulesServer\n通过 JavaScript 编程实时生成规则，规则功能跟上面的 _rules.txt 静态规则一样，但优先级高于 _rules.txt。\n\n**示例**\n\n1. 创建包含 `rulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-rules-server\n\n    # 初始化包含 rulesServer 的插件\n    lack init rulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/rulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end(JSON.stringify({\n          values: {\n            'whistle.test-rules-server/a.html': 'test normal values',\n          },\n          rules: `\n            \\`\\`\\` whistle.test-rules-server/b.html\n            test inject values\n            \\`\\`\\`\n\n            */a file://{whistle.test-rules-server/a.html}\n            */b file://{whistle.test-rules-server/b.html}\n          `,\n        }));\n      });\n    };\n    ```\n    > 支持响应纯 rules 文本  `res.end(rules)`，或包含 rules & values 的序列化对象 `res.end(JSON.stringify({rules, values}))`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com whistle.test-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/a` 返回 `test inject values`\n   - 访问 `https://test.example.com/b` 返回 `test normal values`\n\n## tunnelRulesServer\ntunnelRulesServer 是专门用于处理 TUNNEL 请求的动态规则生成机制，其核心特性与常规 [rulesServer](#rulesserver) 相同，但应用场景有专门区分：\n1. tunnelRulesServer 仅对 `tunnel://domain:port` 隧道请求生效\n2. rulesServer 对 `HTTP[S]/WebSocket` 请求生效\n\n## resRulesServer\n请求的响应阶段通过 JavaScript 编程实时生成规则（可作用于所有类型的请求），规则功能跟上面的 resRules.txt 静态规则一样，但优先级高于 resRules.txt。\n\n**示例**\n\n1. 创建包含 `resRulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules-server && cd whistle.test-res-rules-server\n\n    # 初始化包含 resRulesServer 的插件\n    lack init resRulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/resRulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end('* resBody://(test-res-rules-server)');\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com/res whistle.test-res-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/res/path` 返回 `test-res-rules-server`\n\n\n## statsServer\n如果仅想获取请求的 URL、请求方法、请求头、请求内容等信息，而不对请求做任何操作，可以用 statsServer。\n\n**示例**\n\n1. 创建包含 `statsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-stats-server && cd whistle.test-stats-server\n\n    # 初始化包含 statsServer 的插件\n    lack init statsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-stats-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/statsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Request Headers:', originalReq.headers);\n        // 获取请求的 body\n        req.getReqSession((reqSession) => {\n          if (reqSession) {\n            console.log('Request Body:', reqSession.req.body);\n          }\n        });\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/stats whistle.test-stats-server://test\n    ```\n4.  效果：\n   - 访问 `https://www.example.com/stats` 控制台输出：\n      ``` sh\n      Value: test\n      URL: https://www.example.com/stats\n      Method: GET\n      Request Headers: {\n        host: 'www.example.com',\n        'cache-control': 'max-age=0',\n        'sec-ch-ua': '\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"',\n        'sec-ch-ua-mobile': '?0',\n        'sec-ch-ua-platform': '\"macOS\"',\n        'upgrade-insecure-requests': '1',\n        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',\n        accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',\n        'sec-fetch-site': 'cross-site',\n        'sec-fetch-mode': 'navigate',\n        'sec-fetch-user': '?1',\n        'sec-fetch-dest': 'document',\n        'accept-encoding': 'gzip, br',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',\n        priority: 'u=0, i',\n        'x-forwarded-for': '127.0.0.1',\n        connection: 'close'\n      }\n      Request Body: \n      ```\n\n## resStatsServer\n如果获取请求的响应状态码、响应头、响应内容等信息，而不对请求做任何操作，可以用 resStatsServer。\n\n**示例**\n\n1. 创建包含 `resStatsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-stats-server && cd whistle.test-res-stats-server\n\n    # 初始化包含 resStatsServer 的插件\n    lack init resStatsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-stats-server` 插件\n    lack watch\n    ```\n2. 安装依赖\n    ``` sh\n    npm i\n    ```\n3. 编辑 `src/resStatsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq, originalRes } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Server IP', originalRes.serverIp);\n        console.log('Status Code:', originalRes.statusCode);\n        console.log('Response Headers:', originalReq.headers);\n        // 获取请求的完整抓包数据\n        req.getSession((reqSession) => {\n          if (reqSession) {\n            console.log('Response Body:', reqSession.res.body);\n          }\n        });\n      });\n    };\n    ```\n4. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/res/stats whistle.test-res-stats-server://testResStats\n    ```\n5.  效果：\n   - 访问 `https://www.example.com/res/stats` 控制台输出：\n      ``` txt\n      Value: testResStats\n      URL: https://www.example.com/res/stats\n      Method: GET\n      Server IP 127.0.0.1\n      Status Code: 200\n      Response Headers: {\n        'accept-ranges': 'bytes',\n        'content-type': 'text/html',\n        etag: '\"84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134\"',\n        'last-modified': 'Mon, 13 Jan 2025 20:11:20 GMT',\n        server: 'AkamaiNetStorage',\n        expires: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'cache-control': 'max-age=0, no-cache, no-store',\n        pragma: 'no-cache',\n        date: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'alt-svc': 'h3=\":443\"; ma=93600,h3-29=\":443\"; ma=93600',\n        'x-forwarded-for': '127.0.0.1',\n        host: 'www.example.com',\n        connection: 'close'\n      }\n      Response Body: <!doctype html>\n      <html>\n      <head>\n          <title>Example Domain</title>\n\n      ......\n      ```\n\n## server\n可以将插件作为 **server** 使用，由它接收并转发请求。插件可以直接响应请求，或在处理后继续转发至目标服务器。目标服务器返回响应后，插件可以进一步处理，最终将结果返回给浏览器或客户端。\n\n**示例**\n\n1. 创建包含 `server` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-server && cd whistle.test-server\n\n    # 初始化包含 server 的插件\n    lack init server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/server.ts` 文件：https://github.com/whistle-plugins/examples/tree/master/whistle.test-server\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json test-server://setCookieFromBody\n    ```\n4.  访问 `https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json`\n    - 浏览器 Cookie 新增 `test-name=whistle`\n    - 响应内容新增 `\"pluginName\":\"whistle\"`\n\n\n## pipe\n如果请求/响应内容被加密，或者需要转成特定格式显示在抓包界面，可以用 pipe 将请求/响应内容交给插件处理。\n> 在抓包界面中：\n> \n> 请求数据中无法看到 `reqWrite` 修改后的内容\n> \n> 响应数据中无法看到 `resWrite` 修改后的内容（这是预期行为）\n> \n> 所有 `xxxRexWrite` 操作的实际修改内容都不会显示在抓包界面\n> \n\n#### HTTP/HTTPS 协议实现\n1. 创建 pipe http 插件:\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-http && cd whistle.test-pipe-http\n\n    # 初始化包含 pipe http 的插件\n    lack init pipeHttp,server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-http` 插件\n    lack watch\n    ```\n4. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-http\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/test-pipe-http pipe://test-pipe-http test-pipe-http://mirror\n    ```\n    > `test-pipe-http://mirror` 的作用是客户端请求什么内容，服务端就响应什么内容\n6. Whistle / Network / Composer 输入 `www.example.com/test-pipe-http`，选择 `POST` 方法，Body 输入 `test-pipe-http`\n    <img src=\"/img/pipe-http.png\" width=\"400\" />\n7.  点击发送按钮，查看返回的 Body 如下\n    <img src=\"/img/pipe-http-result.png\" width=\"400\" />\n   \n\n#### WebSocket 协议实现\n1. 创建 pipe websocket 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-ws && cd whistle.test-pipe-ws\n\n    # 初始化包含 pipe websocket 的插件\n    lack init pipeWs\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-ws` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-ws\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    wss://echo.websocket.org/ pipe://test-pipe-ws\n    ```\n4.  打开页面 `https://echo.websocket.org/.ws` 可以看到如下效果：\n   <img src=\"/img/pipe-ws.png\" width=\"520\" />\n   \n\n\n#### TCP 隧道实现\n1. 创建 pipe tunnel 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-tunnel && cd whistle.test-pipe-tunnel\n\n    # 初始化包含 pipe tunnel 的插件\n    lack init pipeTunnel.server\n\n    # 安装依赖\n    npm i --save-dev lack-proxy && npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-tunnel` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-tunnel\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test-pipe-tunnel.example.com pipe://test-pipe-tunnel test-pipe-tunnel://mirror\n    ```\n4. 执行 `whistle.test-pipe-tunnel` 根目录的 `test.js` 文件：\n    ``` sh\n    node test.js\n    ```\n5. 控制台输出的内容\n    <img src=\"/img/pipe-tunnel.png\" width=\"560\" />\n\n## 插件操作界面\n在 Whistle 插件列表中，点击 `Option` 或插件名称可快速打开插件操作界面，支持以下三种模式：\n1. **Tab 模式**：在 Whistle 界面内以标签页形式打开（默认方式）\n2. **对话框模式**：在 Whistle 界面内以弹窗形式打开\n3. **新标签模式**：在浏览器新标签页或客户端新窗口中打开\n\n---\n\n### 1. Tab 模式（默认）\n**特性**  \n- 插件管理界面默认地址为：`http[s]://domain[:port]/plugin.xxx/`  \n- 通过插件列表的 `Option` 或插件名称直接打开  \n\n**禁用 Tab 模式**  \n若需隐藏此打开方式，可在 `whistleConfig` 中配置：  \n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"noOption\": true // 禁用默认入口\n    ...\n  }\n  ...\n}\n```\n\n---\n\n### 2. 对话框模式\n**特性**  \n- 支持通过弹窗形式打开管理界面  \n- 弹窗页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/modal.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n**配置示例**  \n```js\n{\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {\n      \"width\": 360,  // 弹窗宽度（px）\n      \"height\": 566  // 弹窗高度（px）\n    }\n    ...\n  }\n}\n```\n\n---\n\n### 3. 新标签模式\n**场景**  \n需使用外部页面作为插件管理界面时使用。  \n\n**基础配置**  \n```js\n{\n   \"pluginHomepage\": \"https://your-external-page.com\" // 外部页面地址\n}\n```\n\n**高级选项**  \n若需将外部链接强制在 Whistle 内打开（Tab 或对话框模式）：  \n\n**方式一：对话框模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {  // 强制以弹窗打开\n      \"width\": 360,\n      \"height\": 566\n    }\n    ...\n  }\n  ...\n}\n```\n\n**方式二：Tab 模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInPlugins\": true  // 强制在插件Tab页打开\n    ...\n  }\n  ...\n}\n```\n \n## 完整 Hooks API\n参考：https://github.com/avwo/lack/blob/master/assets/ts/src/types/global.d.ts\n\n## 插件变量配置{#pluginvars}\n支持通过 `%` 符号在规则里面配置，详见：[`%` 符号用途](../rules/plugin-vars)\n\n## 自定义规则补全功能{#rules-hint}\n综上所述，每个插件可以扩展以下规则：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\nsinCallback://myplugin(sinValue)\npipe://myplugin(pipeValue)\n```\n\nWhistle 还支持对以下规则自定义补全功能：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\n```\n\n**示例**\n1. 创建 rules hints 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules-hint && cd whistle.test-rules-hint\n\n    # 初始化包含 uiServer 的插件\n    lack init uiServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-hint` 插件\n    lack watch\n    ```\n5. 在 `package.json` 配置 `whistleConfig`，新增 `pluginVars` 和 `hintUrl`：\n   ``` js\n    {\n      ...\n      \"whistleConfig\": {\n        \"pluginVars\": {\n          ...\n          \"hintUrl\": '/cgi-bin/plugin-vars'\n          ...\n        },\n        \"hintUrl\": '/cgi-bin/get-hints'\n      },\n      ...\n    }\n   ```\n6. 编辑 `src/uiServer/router.ts` 文件：\n    ``` ts\n    export default (router: Router) => {\n      // 针对 `%test-rules-hint[.key]=xxx`\n      router.get('/cgi-bin/plugin-vars', (ctx) => {\n        const { sep, value } = ctx.query;\n        const isKey = sep === '.';\n        let key = '';\n        let keyword = '';\n        if (value && typeof value === 'string') {\n          if (isKey) {\n            const index = value.indexOf('=');\n            // %test-plugin-vars.xxx=yyy\n            if (index !== -1) {\n              key = value.substring(0, index);\n              keyword = value.substring(index + 1).toLowerCase();\n            } else {\n              // %test-plugin-vars.xxx or %test-plugin-vars.xxx=\n              key = value;\n            }\n          } else {\n            // %test-plugin-vars=yyy\n            keyword = value.toLowerCase();\n          }\n        }\n        const result: (string | {\n          isKey: true,\n          value: string,\n        })[] = [];\n        VARS_OPTIONS.forEach((option) => {\n          if (keyword && !option.toLowerCase().includes(keyword)) {\n            return;\n          }\n          if (isKey) {\n            result.push({\n              value: `${key}=${option}`,\n              isKey: true,\n            });\n          } else {\n            result.push(option);\n          }\n        });\n        ctx.body = result;\n      });\n\n      // 针对 `test-rules-hint://xxx` 和 `whistle.test-rules-hint://xxx`\n      // 如果没有配置 `pluginVars.hintUrl`，则对 `%test-rules-hint[.key]=xxx` 也生效\n      router.get('/cgi-bin/get-hints', (ctx) => {\n        const { protocol, value } = ctx.query;\n        if (!protocol || typeof protocol !== 'string' || typeof value !== 'string') {\n          return;\n        }\n        const isVar = protocol.startsWith('%');\n        // 事实上不会有这种情况，除非删除了 `pluginVars.hintUrl` 配置\n        if (isVar) {\n          return;\n        }\n        const isLong = protocol.startsWith('whistle.');\n        const prefix = isLong ? 'long-' : 'short-';\n        const keyword = value.toLowerCase();\n        const result: string[] = [];\n        HINTS_OPTIONS.forEach((option) => {\n          if (`${prefix}${option.toLowerCase()}`.includes(keyword)) {\n            result.push(`${prefix}${option}`);\n          }\n        });\n        ctx.body = result;\n      });\n    };\n\n    ```\n9. 效果：\n   \n   <img src=\"/img/rules-hint1.png\" width=\"300\" />\n\n   <img src=\"/img/rules-hint2.png\" width=\"300\" />\n\n   <img src=\"/img/plugin-vars-hint-url1.png\" width=\"300\" />\n\n## 界面扩展\nWhistle 插件系统支持扩展以下界面功能模块：\n- **Network 模块**\n  - 数据表格列扩展\n  - 上下文菜单扩展\n  - 详情面板 Tab 扩展\n- **Rules 模块**上下文菜单\n- **Values 模块**上下文菜单\n- **Plugins 模块**上下文菜单\n\n1. 创建示例插件\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-ui-ext && cd whistle.test-ui-ext\n\n    # 初始化空白插件\n    lack init blank\n\n    # 开发模式运行\n    lack watch\n    ```\n2. 编辑 `whistleConfig`：https://github.com/whistle-plugins/examples/tree/master/whistle.test-ui-ext/package.json\n3. 执行后可在 Whistle 插件列表看到 `test-ui-ext` 插件\n    ![界面扩展效果图](/img/ui-ext.png)\n\n#### 1. Network 表格列扩展\n**配置示例**：\n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkColumn\": {\n      \"name\": \"Referer\",          // 列显示名称（必填）\n      \"key\": \"req.headers.referer\", // 数据字段路径（必填）\n      \"iconKey\": \"\",              // 图标字段（可选）\n      \"showTitle\": true,          // 是否显示悬浮提示（可选）\n      \"width\": 120                // 列宽（可选，默认120）\n    }\n    ...\n  }\n  ...\n}\n```\n\n**高级数据处理**：\n1. 创建 `/public/webWorker.js`：\n    ```js\n    module.exports = function(session, next) {\n      const isGithub = /^https?:\\/\\/github\\.com\\//.test(session.url);\n      next({\n        testWebWorker: 'custom_value',\n        style: isGithub ? { \n          color: '#fff',\n          fontStyle: 'italic',\n          bgColor: 'red' \n        } : null\n      });\n    };\n    ```\n2. 配置 webWorker：\n    ```js\n    {\n      \"whistleConfig\": {\n        \"networkColumn\": {\n          \"name\": \"Test\",\n          \"key\": \"customData.testWebWorker\"  // 访问 webWorker 返回数据\n          ...\n        },\n        \"webWorker\": \"/public/webWorker.js\"\n      }\n    }\n    ```\n\n#### 2. 详情面板 Tab 扩展\n```js\n{\n  \"whistleConfig\": {\n    \"inspectorsTab\": {\n      \"name\": \"Custom Tab\",       // Tab 显示名称\n      \"action\": \"/public/tab.html\", // 功能页面\n      \"icon\": \"data:image/png;base64,...\", // 图标（可选）\n      \"req\": {                    // Request 子 Tab\n        \"name\": \"Req SubTab\",\n        \"action\": \"/public/req-tab.html\"\n      },\n      \"res\": {                    // Response 子 Tab\n        \"name\": \"Res SubTab\",\n        \"action\": \"/public/res-tab.html\"\n      }\n    },\n    \"composerTab\": { /* 同 inspectorsTab */ },\n    \"toolsTab\": { /* 同 inspectorsTab */ }\n  }\n}\n```\n\n- Tab 页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/tab.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Network 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkMenus\": [\n      {\n        \"name\": \"Network Menu1\",\n        \"action\": \"/public/network-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Network Menu2\", // 菜单项名称\n        \"action\": \"/public/network-menu.html\", // 功能页面\n        \"required\": true, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": true, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Rules 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"rulesMenus\": [\n      {\n        \"name\": \"Rules Menu1\", // 菜单项名称\n        \"action\": \"/public/rules-menu.html\", // 功能页面\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Rules Menu2\",\n        \"action\": \"/public/rules-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 4. Values 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"valuesMenus\": [\n      {\n        \"name\": \"Values Menu1\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Values Menu2\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 5. Plugins 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"pluginsMenus\": [\n      {\n        \"name\": \"Plugins Menu1\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Plugins Menu2\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n## 特殊路径\n\nWhistle 提供专用路径格式，用于在各类型页面下直接请求插件接口，以及只在 Whistle 环境下可以正常访问的路径。\n\n#### 1. Whistle 界面直接访问\n在 Whistle 界面或插件管理界面中，可直接使用以下格式访问插件接口：\n```\npath/to\n```\n\n- 此路径会自动解析为 `/whistle.xxx/path/to`\n- 推荐使用相对路径格式，避免直接使用绝对路径 `/path/to`\n\n#### 2. 普通页面访问\n若需在非 Whistle 的普通网页中调用插件接口，请使用以下特殊路径格式：\n```\n/.whistle-path.5b6af7b9884e1165./whistle.xxx/path/to\n```\n\n- 请求到达插件时会被标准化为：`/whistle.xxx/path/to`\n- 前缀 `/.whistle-path.5b6af7b9884e1165./` 是 Whistle 的专用标识符，用于标记内部请求路由\n\n#### 3. 环境自适应路径转换\n实现经过或不经过 Whistle 代理时使用不同路径，但确保后台服务接收到统一路径：\n``` txt\n/_WHISTLE_5b6af7b9884e1165_/\n```\n\n- 原始请求：`https://www.example.com/.../_WHISTLE_5b6af7b9884e1165_/path/to?query`\n- 经过 Whistle 后转换为：`https://www.example.com/.../path/to?query`\n\n#### 说明：\n- `xxx` 为您的插件名称\n- `path/to` 是插件接口的具体路径\n- 特殊路径中的哈希值 `/.whistle-path.5b6af7b9884e1165./` 为固定标识符\n\n## 其它 whistleConfig 配置\n``` js\n{\n  ...,\n  \"homepage\": '', // 插件帮助页面地址\n  \"pluginHomepage\": '', // 自定义插件操作页面地址，默认为 `/plugin.xxx/\n  \"whistleConfig\": {\n    \"hideLongProtocol\": false, // 是否隐藏插件的长协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"hideShortProtocol\": false, // 是否隐藏插件的短协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"priority\": 0, // 默认值为0，插件优先级按更新时间从旧到新排序（越早更新的优先级越高），可通过此字段调整优先级顺\n    \"favicon\": '', // 插件 Tab 页上的 icon，可以为插件相对路径 `/public/xxx.png` 或绝对路径 `https://xxx` 或 `data:image/png;base64,xxx`\n    \"registry\": '', // 插件的 npm registry\n    \"noOption\": false, // 如果不存在操作界面，可以设置为 true，这样 Option 按钮会置灰\n    \"enableAuthUI\": false, // 插件的 auth hook 是否作用于插件操作界面（慎用）\n    \"tunnelKey\": '', // 字符串或字符串组，如果需要继续将请求通过 TUNNEL 代理到其它 HTTP Proxy 时，可以指定哪些请求头跟着过去\n    ...\n  },\n  ...\n}\n```\n\n## 插件页面内部路径规范\n插件页面的 URL 通常为以下格式：\n- `/plugin.xxx/path/yyy.html`\n- `/whistle.xxx/path/yyy.html`\n\n**根目录**为 `/plugin.xxx/` 或 `/whistle.xxx/`，因此：\n\n✅ 推荐使用相对路径，避免使用绝对路径（如 /path/to）。\n\n---\n\n#### **相对路径使用示例**  \n\n**场景 1：页面位于插件根目录**  \n**页面地址**：`/plugin.xxx/yyy.html`  \n**正确写法**：  \n- `./path/to`  \n- `path/to`  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n\n---\n\n**场景 2：页面位于子目录**  \n**页面地址**：`/plugin.xxx/a/b/c/yyy.html`  \n**正确写法**：  \n- `../../../path/to`（返回 3 级目录再进入目标路径）  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n- ❌ `path/to`（会基于当前目录 `a/b/c/` 查找，导致路径错误）  \n\n---\n\n**最佳实践**  \n1. **推荐使用 `./` 开头**，明确表示相对当前目录（如 `./assets/style.css`）。  \n2. **避免硬编码 `/` 开头的路径**，防止不同部署环境下路径失效。  \n3. **测试路径引用**：在本地和线上环境均验证资源加载是否正常。  \n\n---\n\n**常见问题**  \n❓ **Q：为什么不能用绝对路径？**  \n📌 **A**：插件可能部署在不同环境（如测试/生产/内嵌到其它项目中），绝对路径可能导致资源加载失败。  \n\n❓ **Q：有些构建工具，如 Vite 默认会将资源路径基于 base 设置进行解析。如果 base 设为 `/`，可能会将 `../` 优化为 `/`，如何处理？**  \n📌 **A**：尝试将 base 改成 `./`：\n  ``` js\n  // vite.config.js\n  export default defineConfig({\n    base: './', // 强制使用相对路径\n  });\n  ```\n  > 建议与后台路由配置同步优化\n\n\n## 发布插件\nWhistle 插件的发布方式与常规 NPM 包完全一致，只需遵循标准 NPM 发布流程：\n1. 登录 NPM（或企业私有源，后者需要设置企业私有源的 `npm config set registry https://xxx`）:\n    ``` sh\n    npm login\n    ```\n2. 在插件根目录执行发布命令：\n    ``` sh\n    npm publish\n    ```\n\n## 参考资料\n1. 示例源码仓库：\nhttps://github.com/whistle-plugins/examples\n1. 脚手架命令：https://github.com/avwo/lack\n","isInternal":false,"tokens":11801,"sizeBytes":39037},{"name":"npm.md","path":"docs/docs/extensions/npm.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/extensions/npm.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"# NPM 模块\nWhistle 可作为 NPM 模块集成到项目中，**强烈建议**在独立子进程中运行 Whistle，以避免与主进程相互影响，同时便于管理。\n\n## 安装依赖\n``` sh\nnpm i --save whistle pfork\n```\n\n## 独立服务模式\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n\n    module.exports = (options, callback) => {\n      startWhistle({ port: options.port }, () => callback());\n    };\n    ```\n    > 禁用插件、自定义插件目录、修改存储目录等启动参数参考：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. 启动 Whistle 进程代码：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n      port: 8080,\n    };\n    const forkWhistle = (retry) => {\n      fork(options, (err, result, child) => {\n        if (err) {\n          // 初始化时报错，直接退出服务\n          if (!retry) {\n            throw err;\n          }\n          // 重试报错，延迟 100 毫秒再重试\n          return setTimeout(forkWhistle, 100);\n        }\n        // 子进程退出自动重试\n        child.once('close', () => forkWhistle(true));\n      });\n    };\n\n    forkWhistle();\n    ```\n\n这种方式启动的 Whistle，跟通过命令行独立启动 Whistle 或 Whistle 客户端的用法一样。\n\n\n## 内部服务模式（推荐）\n\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n    const http = require('http');\n\n    let curPort = 30013;\n\n    const getPort = (callback) => {\n      const server = http.createServer();\n      server.on('error', () => {\n        if (++curPort % 5 === 0) {\n          ++curPort;\n        }\n        getPort(callback);\n      });\n      server.listen(curPort, '127.0.0.1', () => {\n        server.removeAllListeners();\n        server.close(() => callback(curPort));\n      });\n    };\n\n    module.exports = (options, callback) => {\n      // 获取一个随机端口\n      getPort((port) => {\n        // 在随机端口启动 Whistle，启动成功后将启动参数传给父进程\n        const options = {\n          port,\n          host: '127.0.0.1',\n          storage: 'xxx', // 建议设置新存储目录，确保不跟其它实例的目录冲突\n        };\n        startWhistle(options, () => callback(options));\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/whistle.js\n    > \n    > Whistle 会\n2. 启动 Whistle 进程方法（`/lib/fork.js`）：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n    };\n\n    module.exports =() => {\n      return new Promise((resolve, reject) => {\n        fork(options, (err, result) => {\n          if (err) {\n            return reject(err);\n          }\n          resolve(result);\n        });\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/main/whistleMgr.js\n3. 将项目中的指定请求转发到 Whistle：\n   ``` js\n    const { createServer } = require('http');\n    const forkWhistle = require('./fork');\n\n    const server = createServer(async (req, res) => {\n      try {\n        const { host, port } = await forkWhistle();\n        // 将请求转发到 host、port\n      } catch (e) {\n        // handle errors\n      }\n    });\n\n    server.listen(8010);\n   ```\n   > 参考：https://github.com/Tencent/nohost/blob/master/lib/index.js#L160\n\n这种方式启动的 Whistle，端口是随机的且是按需启动的，外部请求不直接访问 Whistle，需要通过所在项目的服务进行转发，用法更灵活，更推荐项目使用这种方式集成 Whistle。\n\n## 相关文档\n\n1. Whistle 的所有启动参数可查看源代码的类型定义：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. pfork：https://github.com/avwo/pfork\n3. 完整示例：https://github.com/Tencent/nohost\n\n","isInternal":false,"tokens":1187,"sizeBytes":4097},{"name":"usage.md","path":"docs/docs/extensions/usage.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/extensions/usage.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"# 使用插件\n\n## 安装插件\n\n1. 点击左侧导航栏的 Plugins 标签页\n2. 点击顶部的 Install 按钮\n3. 在弹出窗口中输入插件名称（支持同时安装多个插件）：\n   - 多个插件用空格或换行符分隔\n   - 可指定自定义 npm 镜像源：\n     - 直接在插件名后添加 `--registry=镜像地址`\n     - 或从下拉列表选择历史使用过的镜像源\n\n<img width=\"1000\" alt=\"install plugins\" src=\"/img/install-plugins.png\" />\n\n> 文本框中的冗余内容不会影响操作结果，Whistle 会自动过滤并仅提取插件名称及 registry 安装信息。若对话框未显示 Install 按钮，请重新安装全局 Node.js：https://nodejs.org/\n\n安装成功后，稍等片刻即可在插件列表中查看新安装的插件：\n\n<img width=\"1000\" alt=\"plugins\" src=\"/img/plugin-list.png\" />\n\n\n## 自定义协议\n\n每个插件可注册两种协议类型，配置方式跟普通协议一样：\n``` txt\n# 长协议\npattern whistle.plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n# 短协议\npattern plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n```\n\n## Hooks 功能\n\nWhistle 将匹配插件规则的请求与插件对应 Hook 进行交互，可实现以下功能：\n> 插件协议的具体功能看各个插件的帮助文档，插件也可以选择隐藏这些协议\n1. 自动生成并下发 HTTPS 证书\n2. 对代理请求的认证控制，要求用户提供凭证才能访\n3. 实时获取请求头、响应状态码等元数据\n4. 对请求动态设置规则\n5. 将请求转发给插件，由插件完全控制请求处理流程（需要匹配短协议才能实现）\n\n\n## pipe 功能\n有些请求/响应内容可能被加密或 protobuf 序列化无法看到明文，可通过以下两种方式实现内容查看与修改：\n1. **插件全权处理**\n   - 将请求直接转发至插件\n   - 插件完全控制处理流程\n   - 注意事项：\n     - Whistle 内置规则将失效\n     - 需插件自行实现所有处理逻辑\n     - 如需在抓包界面显示解密内容，插件必须调用 Whistle API 回传数据\n2. **管道流式处理（推荐）**\n   - 通过 pipe 协议建立处理管道\n   - 请求/响应流程：\n     - 进入 Whistle 时：插件解密\n     - 离开 Whistle 时：插件加密\n   - 优势：\n     - 保持明文请求特性\n     - 完整支持 Whistle 内置规则\n     - 抓包界面可直接查看明文\n\n**技术说明：**\n- pipe 流功能为可选实现，各插件支持情况请查阅具体文档\n- 方案二处理后的请求完全等同于普通明文请求\n\n## 操作界面\n\n#### 访问插件操作界面\n在 Whistle 插件管理面板中，您可以通过以下任一方式访问插件功能：\n1. 点击插件条目右侧的 `Option` 按钮\n2. 在插件列表直接单击插件名称\n\n#### 界面特性\n**呈现形式：** 模态对话框或独立标签页\n\n**核心能力：**\n1. 提供可视化配置和管理功能\n2. 支持与 Whistle 核心的实时交互\n3. 显示插件运行状态、请求统计数据、日志信息等\n\n#### 技术说明\n1. 插件本质上是与 Whistle 直接交互的 HTTP Server\n2. 操作界面的具体功能由各插件自行实现\n3. 插件可选择不提供操作界面（仅提供后台服务或[命令行操作](../cli#w2-exec)）\n\n## 扩展 Whistle 界面\n\n插件可通过以下方式增强 Whistle 的用户界面功能：\n\n#### Network 扩展\n1. 抓包列表上下文菜单\n    > 在抓包列表项右键菜单中添加自定义操作\n2. 主视图标签页\n    > 在右侧主面板区创建一级功能标签页\n2. Inspectors 分析工具扩展\n    > 在 Inspectors 面板下添加：\n    >   - 二级功能标签页\n    >   - 三级详细视图标签页\n3. Composer 请求构造器扩展\n    > 在 Composer 工具区添加二级功能标签页\n4. Tools 工具集扩展\n    > 在 Tools 功能区添加二级功能标签页\n\n#### Rules 扩展\n左侧列表上下文菜单\n> 在左侧规则文件列表的右键菜单中添加功能项\n\n#### Values 扩展\n左侧列表上下文菜单\n> 在左侧变量文件列表的右键菜单中添加功能项\n\n**实现说明：**\n- 各插件可根据功能需求选择适当的扩展位置\n- 具体扩展功能和实现方式请参阅各插件的开发文档\n- 未使用的扩展点不会影响插件基础功能\n\n## 插件内置规则\n\n插件支持通过以下规则文件扩展系统功能：\n\n1. **全局规则 (rules.txt)**\n   - 自动加载时机： 插件初始化时\n   - 作用范围： 全局请求\n   - 优先级： 低于 Rules 界面配置的规则\n   - 典型用途： 插件默认规则配置\n2. **私有规则 (_rules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 请求处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 请求预处理/改写\n3. **响应阶段规则 (resRules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 响应处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 响应后处理/改写\n\n\n插件支持灵活的内置规则配置机制：\n- **可选性：** 所有内置规则均为可选配置\n- **按需设置：** 插件只需声明必要的规则\n- **规则查看：** 通过插件管理界面的 Rules 按钮可查看当前插件配置的所有规则\n> 注意：未配置规则的插件仍可正常使用其他功能\n\n## 更新卸载插件\n\n**在 Whistle 插件管理界面中：**\n- **更新插件：** 点击插件条目右侧的 `Update` 按钮\n- **卸载插件：** 点击插件条目右侧的 `Uninstall` 按钮\n","isInternal":false,"tokens":1975,"sizeBytes":5632},{"name":"@.md","path":"docs/docs/rules/@.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/@.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# `@` 符号用法\n\n当您希望将规则存放在远程服务器或本地项目文件中，并让 Whistle 自动同步更新时，可以利用规则中的 `@` 符号实现。\n\n## 语法规则\n\n## 1. 引入规则文件 (`@path`)\n动态加载外部规则文件内容，Whistle 会定时从规则文件更新内容。\n\n**使用格式：** `@文件路径或URL`\n\n**支持类型：**\n- 远程 URL：`@https://example.com/rules.txt`\n- 本地文件：`@~/projects/rules.txt`（支持所有操作系统路径）\n- 插件接口：`@whistle.nohost/api/rules`（需插件实现接口）\n\n> **Windows 路径兼容性**：支持混用 `/` 和 `\\` 作为路径分隔符：\n> ```txt\n> @C:/whistle/rules.txt\n> @D:\\config\\proxy.rules\n> ```\n\n## 配置示例\n\n### 引入远程规则文件\n```txt\n@https://raw.githubusercontent.com/your-org/configs/master/whistle-rules.txt\n```\n\n### 引入本地项目规则\n```txt\n@/Users/username/projects/my-app/whistle-config.txt\n@~/dev/configs/rules.txt  # 使用用户目录\n```\n\n### 引入插件提供的规则\n```txt\n@whistle.nohost/api/rules\n```\n\n### 混合使用示例\n```txt\n# 引入团队共享的基础规则\n@https://team.example.com/configs/base-rules.txt\n\n# 引入个人项目特定规则\n@~/projects/my-app/whistle-overrides.txt\n\n```\n\n## 文件格式要求\n\n引入的外部文件应包含标准的 Whistle 规则语法：\n\n```txt\n# 远程规则文件示例\nwww.example.com proxy://127.0.0.1:8080\napi.example.com resCors://*\nstatic.example.com cache://3600\n```\n","isInternal":false,"tokens":494,"sizeBytes":1491},{"name":"attachment.md","path":"docs/docs/rules/attachment.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/attachment.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# attachment\n`attachment` 协议用于设置响应头中的 `Content-Disposition` 字段，使浏览器将服务器的响应直接视为附件下载，并将文件保存为 `attachment` 设定的名称，而不是在页面内展示。\n> 类似 Koa 的 `attachment` 方法：https://koajs.com/\n\n## 规则语法\n\n`attachment` 支持多种方式来指定下载文件名：\n\n### 1. 内联值（直接指定）\n直接在规则中写明文件名。\n```txt\npattern attachment://filename [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/report attachment://年度报告.pdf\n```\n\n### 2. 内嵌值（使用代码块）\n当文件名较长或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern attachment://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n季度数据-2024Q1.csv\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern attachment://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为目标文件名。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载文件名。\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **filename** | 是 | 下载时显示的文件名。<br>• 例如：`document.pdf`, `export.xlsx`。<br>• 支持通过上述三种方式（内联/内嵌/Values）指定。<br>• ⚠️ 不支持从文件或远程 URL 加载。 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n## 配置示例\n\n### 基础示例\n让访问 example.com 首页时自动下载一个名为 `example.html` 的文件。\n```txt\nhttps://www.example.com/ attachment://example.html\n```\n\n### 配合过滤器使用\n仅当请求包含特定查询参数 `download=true` 时，才触发 PDF 文件下载。\n```txt\nhttps://api.example.com/document attachment://用户手册.pdf includeFilter:///[?&]download=true/\n```\n\n### 使用内嵌值\n当文件名比较复杂或需要注释时，使用内嵌代码块更清晰。\n````txt\nhttps://assets.example.com/data.json attachment://{my-file-name}\n\n``` my-file-name\n配置备份-20240514.json\n```\n````\n\n## 实现远程加载\n\n`attachment` 协议本身不支持从文件路径或远程 URL 加载值。如果需要实现远程加载功能，有以下两种替代方案：\n\n### 1. 通过插件扩展功能\n你可以开发插件来实现更复杂的文件名获取逻辑，比如从远程API动态获取文件名。具体开发方法请参考：[插件开发文档](../extensions/dev)\n\n### 2. 使用 @url 加载完整规则\n将包含 `attachment` 规则的完整配置文件存储在远程服务器上，然后通过 [`@url`](./@) 协议加载整个规则集。\n\n**示例：**\n1. 在远程服务器创建规则文件 `xxx.txt`：\n    ```txt\n    https://api.example.com/export attachment://remote-file.csv\n    https://static.example.com/docs attachment://文档.pdf\n    ```\n2. 在本地 Rules 配置中引用远程规则：\n    ```txt\n    @https://remote-url/xxx.txt\n    ```\n\n这样，远程文件中的 `attachment` 规则就会被加载生效。\n\n## 工作原理与关联协议\n\n1.  **核心原理**：`attachment` 协议本质上是自动设置响应头。\n    上面的示例完全等价于使用 [`resHeaders`](./resHeaders) 协议手动设置：\n    ```txt\n    https://www.example.com/ resHeaders://content-disposition=attachment;filename=\"example.html\"\n    ```\n\n2.  **优势**：相比于直接使用 `resHeaders`，`attachment` 语法更简洁、易读，专注于文件下载场景，自动处理文件名转义等细节。\n\n3.  **注意**：服务器本身返回的响应体内容不会改变。此规则只是通过修改响应头，指示浏览器如何处理这些内容。\n","isInternal":false,"tokens":1386,"sizeBytes":4397},{"name":"auth.md","path":"docs/docs/rules/auth.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/auth.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# auth\n\n`auth` 协议用于快速修改请求头的 `Authorization` 字段，为匹配的请求自动添加 HTTP 基本认证（Basic Authentication）所需的凭证信息。\n\n## 规则语法\n\n`auth` 支持多种方式设置请求鉴权头：\n\n### 1. 内联值（直接指定）\n直接在规则中写明用户名和密码。\n```txt\npattern auth://username:password [lineProps...] [filters...]\n# 或\npattern auth://username=test&password=123 [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/api1/ auth://admin:secret\nhttps://example.com/api2/ auth://username=admin&password=secret\n```\n\n### 2. 内嵌值（使用代码块）\n当凭证信息较复杂或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern auth://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nusername: admin\npassword: my secret password\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern auth://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为包含用户名和密码的对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern auth://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `auth://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report auth://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含鉴权信息的 JSON 或简单 YAML 文件。\n```txt\n# 从本地文件加载\npattern auth:///User/xxx/auth.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern auth://https://config.example.com/auth.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或简单 YAML 格式，包含 `username` 和 `password` 字段：\n```json\n{\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n或\n```yaml\nusername: admin\npassword: secret\n```\n\n## 设置 `Proxy-Authorization`\n```json\n{\n  \"proxy\": true,\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 鉴权凭证信息，支持多种格式：<br>• **直接格式**：`username:password` 或 `username=test&password=123`<br>• **对象格式**：包含 `username` 和 `password` 字段的对象<br>• 支持从本地文件或远程 URL 加载<br>• 支持内联、内嵌、Values、本地文件路径、远程 URL 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n## 配置示例\n\n### 基础示例\n为 example.com 的 API 接口添加基本认证：\n```txt\nhttps://api.example.com/ auth://admin:secret\n```\n\n### 使用内嵌值\n当密码包含特殊字符或空格时，使用内嵌方式更安全：\n````txt\nhttps://internal.example.com/ auth://{prod-credentials}\n\n``` prod-credentials\nusername: service-account\npassword: P@ssw0rd!2024\n```\n````\n\n### 从文件加载\n从本地配置文件加载鉴权信息：\n```txt\nhttps://example.com/api/ auth:///Users/john/config/auth.json\n```\n\n### 配合过滤器使用\n仅对 POST 请求添加鉴权：\n```txt\nhttps://example.com/api/ auth://admin:secret includeFilter://m:POST\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `api-auth` 配置：\n```txt\nhttps://example.com/api/ auth://{api-auth}\n```\n\n## 工作原理与关联协议\n\n1. **核心原理**：`auth` 协议会自动计算用户名和密码的 Base64 编码，并设置 `Authorization` 请求头。\n\n   上面的示例等价于使用 [`reqHeaders`](./reqHeaders) 协议手动设置：\n   ```` txt\n   https://example.com/api/ reqHeaders://{auth.txt} # 内容有空格，不能内联\n\n   ``` auth.txt\n   authorization: Basic YWRtaW46c2VjcmV0\n   ```\n   ````\n   其中 `YWRtaW46c2VjcmV0` 是 `admin:secret` 的 Base64 编码。\n\n2. **优势**：相比于直接使用 `reqHeaders`，`auth` 语法更简洁直观，无需手动计算 Base64 编码值。\n\n## 注意事项\n- `auth` 协议仅支持 HTTP 基本认证（Basic Authentication）\n- 对于更复杂的认证方式（如 Bearer Token、OAuth 等），请使用 [`reqHeaders`](./reqHeaders) 协议直接设置相应的 `Authorization` 头\n- 从远程 URL 加载时，请确保目标 URL 安全可靠\n\n## 关联协议 {#related}\n1. 删除请求头字段（`Delete Request Header`）：[delete://reqHeaders.xxx](./delete)\n2. 删除请求 Cookie（`Delete Request Cookie`）：[delete://reqCookies.xxx](./delete)\n3. 设置请求 Cookie：[reqCookies](./reqCookies)\n4. 设置请求鉴权头：[auth](./auth)\n5. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n","isInternal":false,"tokens":1780,"sizeBytes":5572},{"name":"cache.md","path":"docs/docs/rules/cache.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/cache.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# cache\n\n`cache` 协议用于快速设置响应的缓存控制头，帮助控制浏览器和服务器的缓存行为。可以为匹配的响应设置缓存策略，包括：\n- 设置固定的缓存时间\n- 禁用缓存（`no-cache`）\n- 完全禁止存储（`no-store`）\n\n这可以帮助优化网站性能或确保特定资源不会被缓存。\n\n## 规则语法\n\n`cache` 支持多种方式设置缓存策略：\n\n### 1. 内联值（直接指定）\n直接在规则中指定缓存策略。\n```txt\n# 设置缓存5秒，支持正整数、0、负整数\npattern cache://5 [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-cache`、`pragma: no-cache`\n# `no-cache` 可简写为 `no`\npattern cache://no-cache [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-store`、`pragma: no-cache`\npattern cache://no-store [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nhttps://example.com/api/data cache://no-cache\nhttps://example.com/static/js cache://86400  # 缓存24小时\n```\n\n### 2. 内嵌值（使用代码块）\n当需要复用缓存策略或策略较复杂时可使用此方式。\n````txt\npattern cache://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nno-cache\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的缓存策略。\n```txt\npattern cache://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为缓存策略。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载缓存配置。\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 缓存策略值：<br>• **数字**：缓存秒数（如 `60` 表示缓存60秒）<br>• **no-cache** 或 **no**：允许缓存但每次需重新验证<br>• **no-store**：完全禁止缓存<br>• 支持内联、内嵌、Values 引用<br>• ⚠️ 不支持从文件或远程 URL 加载 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**影响的响应头：**\n- `Cache-Control`\n- `Expires`\n- `Pragma`\n\n## 缓存策略说明\n\n| 策略 | 含义 | 典型使用场景 |\n| :--- | :--- | :--- |\n| **数字（如 `60`）** | 设置资源缓存指定秒数 | 静态资源、不经常变化的API响应 |\n| **no-cache**（或 **no**） | 允许缓存但每次使用前必须验证 | 经常变化的内容，如用户数据、实时报价 |\n| **no-store** | 完全禁止缓存任何版本 | 敏感数据、支付页面、一次性令牌 |\n\n## 配置示例\n\n### 基础示例\n```txt\n# 静态资源缓存1小时\nhttps://example.com/static cache://3600\n\n# API接口禁用缓存\nhttps://example.com/api cache://no-cache\n\n# 敏感数据禁止缓存\nhttps://example.com/account cache://no-store\n```\n\n### 使用内嵌值\n````txt\nhttps://example.com/reports cache://{report-cache-policy}\n\n``` report-cache-policy\nno-cache\n```\n````\n\n### 配合过滤器使用\n仅对特定响应状态码设置缓存：\n```txt\nhttps://example.com/api  cache://300 includeFilter://s:200\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `static-cache` 配置：\n```txt\nhttps://example.com/assets cache://{static-cache}\n```\n\n\n## 工作原理与关联协议\n\n1. **核心原理**：`cache` 协议自动设置相应的缓存控制头：\n\n   - `cache://5` 设置：\n     ```http\n     Cache-Control: max-age=5\n     Expires: [当前时间+5秒]\n     ```\n   \n   - `cache://no-cache` 设置：\n     ```http\n     Cache-Control: no-cache\n     Pragma: no-cache\n     ```\n   \n   - `cache://no-store` 设置：\n     ```http\n     Cache-Control: no-store\n     Pragma: no-cache\n     ```\n\n2. **关联协议**：\n   - **禁用缓存**：更彻底的缓存禁用方案，同时移除请求和响应中的缓存相关头。详见：[disable://cache](./disable)\n   - **手动设置头部**：对于更复杂的缓存控制，可以使用 [`resHeaders`](./resHeaders) 协议手动设置缓存头。\n\n## 注意事项\n- 缓存时间以秒为单位，不支持小数\n- 设置 `no-cache` 不代表\"不缓存\"，而是\"使用前必须验证\"\n- 对于需要完全禁用缓存的场景，建议同时使用 [`disable://cache`](./disable) 协议\n- 实际缓存行为还受服务器配置和浏览器实现的影响\n\n## 关联协议 {#related}\n1. 修改响应头：[resHeaders](./resHeaders)\n2. 删除请求头字段（`Delete Response Header`）：[delete://resHeaders.xxx](./delete)\n3. 删除请求 Cookie（`Delete Response Cookie`）：[delete://resCookies.xxx](./delete)\n4. 设置请求 Cookie：[resCookies](./resCookies)\n5. 设置缓存：[cache](./cache)\n6. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n","isInternal":false,"tokens":1777,"sizeBytes":5235},{"name":"cipher.md","path":"docs/docs/rules/cipher.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/cipher.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# tlsOptions\n\n`tlsOptions` 协议用于配置 HTTPS/TLS 连接的安全参数，包括加密算法套件、客户端证书等，用于建立加密通信通道和服务器身份验证。\n\n> **版本要求**：仅 Whistle 最新版本（≥ v2.9.101）支持此功能\n\n通过 `tlsOptions` 协议，你可以：\n- 自定义 TLS 加密算法套件\n- 配置双向认证（mTLS）所需的客户端证书\n- 设置 TLS 连接的其他安全参数\n- 控制 TLS 版本和连接选项\n\n**规则合并机制**：`tlsOptions` 支持设置任意多个配置规则，Whistle 会根据从上到下的顺序自动合并这些规则，允许你灵活组合不同的 TLS 配置。\n\n## 规则语法\n\n`tlsOptions` 支持多种方式配置 HTTPS/TLS 连接的安全参数：\n\n### 1. 内联值（直接指定）\n直接在规则中写明操作。\n```txt\n# 设置客户端证书\npattern tlsOptions://key=/path/to/client.key&cert=/path/to/client.crt\n\n# 自定义加密套件\npattern tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n\n# 或使用 ciphers 参数\npattern tlsOptions://ciphers=ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n```\n\n### 2. 内嵌值（使用代码块）\n当配置较复杂或需要复用时可使用此方式。\n````txt\npattern tlsOptions://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n{\n  passphrase: \"123456\",\n  pfx: \"-----BEGIN PKCS7-----\\n...\"\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的配置。\n```txt\npattern tlsOptions://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 TLS 配置对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern tlsOptions://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `tlsOptions://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report tlsOptions://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含 TLS 配置的 JSON 或 YAML 文件。\n```txt\n# 从本地文件加载\npattern tlsOptions:///User/xxx/tlsOptions.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern tlsOptions://https://config.example.com/tlsOptions.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或 YAML 格式：\n```json\n{\n  \"ciphers\": \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \"minVersion\": \"TLSv1.2\",\n  \"maxVersion\": \"TLSv1.3\",\n  \"secureProtocol\": \"TLSv1_2_method\"\n}\n```\n或\n```yaml\nciphers: ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\nminVersion: TLSv1.2\nmaxVersion: TLSv1.3\nsecureProtocol: TLSv1_2_method\n```\n\n---\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | TLS 配置数据，支持多种格式：<br>• 加密算法套件名称（用 `:` 分隔）<br>• `tls.connect(options)` 参数对象<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n---\n\n## 配置对象参考\n\n完整的 TLS 连接配置对象，支持 Node.js [`tls.connect()`](https://nodejs.org/docs/latest/api/tls.html#tlscreatesecurecontextoptions) 的所有参数：\n\n```js\n{\n  // 加密算法套件\n  ciphers: \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \n  // TLS 协议版本控制\n  minVersion: \"TLSv1.2\",\n  maxVersion: \"TLSv1.3\",\n  secureProtocol: \"TLSv1_2_method\",\n  \n  // 证书相关配置\n  ca: \"-----BEGIN CERTIFICATE-----\\n...\",  // CA 证书\n  cert: \"-----BEGIN CERTIFICATE-----\\n...\", // 客户端证书\n  key: \"-----BEGIN PRIVATE KEY-----\\n...\",  // 私钥\n  pfx: \"-----BEGIN PKCS7-----\\n...\",       // PFX/P12 格式证书\n  passphrase: \"cert-password\",             // 证书密码\n  \n  // 其他安全选项\n  honorCipherOrder: true,     // 优先使用服务器端的加密套件顺序\n  requestCert: true,          // 请求客户端证书（双向认证）\n  rejectUnauthorized: true,   // 拒绝未授权的证书\n  \n  // 高级选项\n  ecdhCurve: \"auto\",         // ECDH 曲线\n  dhparam: \"-----BEGIN DH PARAMETERS-----\\n...\",\n  secureOptions: 0,          // SSL 选项标志\n  sessionTimeout: 300,       // 会话超时时间（秒）\n  sessionIdContext: \"whistle\"\n}\n```\n\n---\n\n## 配置示例\n\n### 1. 自定义加密套件\n限制特定域名的 TLS 加密算法：\n```txt\nwww.example.com/path tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384\n```\n\n> **使用场景**：解决特定服务器的兼容性问题。参考：[GitHub Issue #963](https://github.com/avwo/whistle/issues/963)\n\n### 2. 配置客户端证书（双向认证 mTLS）\n\n**cert 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=/User/xxx/test.key&cert=/User/xxx/test.crt\n```\n\n**pem 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=E:\\test.key&cert=E:\\test.pem\n```\n\n**pfx 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=/User/xxx/test.pfx\n```\n\n**p12 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=E:/test.p12\n```\n\n> **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 3. 内嵌证书内容\n直接将证书内容嵌入配置中：\n````txt\n``` test.json\n{\n  key: '----xxx----- ... ----xxx-----',\n  cert: '----yyy----- ... ----yyy-----'\n}\n```\n\nwww.exaple.com/path tlsOptions://{test.json}\n````\n\n### 4. 从本地或远程文件加载配置\n**从本地文件加载：**\n```txt\nwww.example.com/path1 tlsOptions:///User/xxx/test.json\n```\n\n**从远程 URL 加载：**\n```txt\nwww.example.com/path2 tlsOptions://https://www.xxx.com/xxx/params.json\n```\n\n**使用临时文件编辑：**\n```txt\nwww.example.com/path3 tlsOptions://temp/blank.json\n```\n\n### 5. 规则合并示例\n利用规则合并特性，逐步细化配置：\n```txt\n# 第一层：为整个域名设置基础TLS版本\n*.example.com tlsOptions://minVersion=TLSv1.2\n\n# 第二层：为API子域添加客户端证书\napi.example.com tlsOptions://key=/certs/client.key&cert=/certs/client.crt\n\n# 第三层：为特定路径进一步限制加密套件\napi.example.com/secure tlsOptions://ciphers=ECDHE-RSA-AES256-GCM-SHA384\n```\n\n---\n\n## 注意事项\n\n1. **版本兼容性**：确保使用的 Whistle 版本 ≥ v2.9.101\n2. **证书格式**：不同格式的证书需要使用对应的参数：\n   - PEM 格式：使用 `key` 和 `cert` 参数\n   - PFX/P12 格式：使用 `pfx` 和 `passphrase` 参数\n3. **路径处理**：\n   - 相对路径相对于 Whistle 配置文件目录\n   - Windows 路径可以混用 `/` 和 `\\`\n4. **安全警告**：\n   - 谨慎使用 `rejectUnauthorized: false`，仅限开发环境\n   - 妥善保管客户端私钥，避免泄露\n5. **性能影响**：复杂的加密算法可能影响连接性能\n6. **调试建议**：遇到连接问题时，可先用简单的配置测试，逐步添加复杂选项\n\n---\n\n## 故障排除\n\n### Q: 证书加载失败\n**A:** 检查：\n1. 证书文件路径是否正确\n2. 证书格式是否匹配参数类型\n3. PFX 证书密码是否正确\n\n### Q: 连接被拒绝\n**A:** 检查：\n1. TLS 版本是否被服务器支持\n2. 加密算法套件是否被服务器支持\n3. 客户端证书是否有效且被服务器信任\n\n### Q: 配置未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配目标 URL\n2. Whistle 版本是否满足要求\n3. 配置语法是否正确，特别是 JSON 格式\n4. 是否存在冲突的规则（规则按从上到下顺序合并）\n\n### Q: 规则合并不符合预期\n**A:** 检查：\n1. 规则的顺序是否正确（从上到下合并）\n2. 不同规则中的配置是否冲突\n3. 使用更具体的 pattern 是否覆盖了通用配置\n","isInternal":false,"tokens":2974,"sizeBytes":8659},{"name":"cssAppend.md","path":"docs/docs/rules/cssAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/cssAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# cssAppend\n\n`cssAppend` 协议用于在现有的 CSS 类型响应内容体后面插入指定内容。\n\n> **生效条件**：仅对响应类型 `content-type` 包含 `html` 或 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）才生效\n> \n> ⚠️ 注意：`204`、`304` 等无响应内容体的请求不受影响\n> \n> html 页面会自动包裹 `<style>...</style>`\n\n通过 `cssAppend` 协议，你可以：\n- 在现有 CSS 文件的末尾追加新的样式规则\n- 动态添加 CSS 覆盖规则\n- 为特定页面注入自定义样式\n- 在不修改源文件的情况下扩展样式功能\n\n## 规则语法\n\n`cssAppend` 支持多种方式指定要追加的内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要追加的 CSS 内容。\n```txt\n# `value` 小括号内不能用空格或换行符\npattern cssAppend://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/index.html cssAppend://(.custom-btn{background:red;})\n```\n\n### 2. 内嵌值（使用代码块）\n当要追加的 CSS 内容有空格或换行符或需要复用时可使用此方式。\n````txt\npattern cssAppend://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n.dark-mode {\n  background: #333;\n  color: #fff;\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的 CSS 内容。\n```txt\npattern cssAppend://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 CSS 内容。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern cssAppend://temp.css\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `cssAppend://temp.css`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/test.css cssAppend://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.css\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载要追加的 CSS 内容。\n```txt\n# 从本地文件加载\npattern cssAppend:///User/xxx/custom.css\n\n# 从远程 URL 加载（支持 http 和 https）\npattern cssAppend://https://cdn.example.com/styles/override.css\n```\n# 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要追加的 CSS 内容，支持多种格式：<br>• 纯 CSS 代码文本<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**特殊说明：**\n- 仅对 `content-type` 响应头包含 `css` 的请求生效\n- 对 `204`、`304` 等无响应体的状态码不生效\n- 追加的内容会直接拼接到原 CSS 文件的末尾\n\n\n## 配置示例\n\n#### 场景 1：添加响应式断点\n```` txt\nwww.example.com/responsive.css cssAppend://{test.css}\n\n``` test.css\n@media (max-width: 768px) {\n  .sidebar { display: none; }\n  .main-content { width: 100%; }\n}\n```\n\n````\n\n#### 场景 2：覆盖第三方库样式\n```` txt\ncdn.example.com/bootstrap.min.css cssAppend://{test2.css}\n\n``` test2.css\n/* 覆盖 Bootstrap 默认样式 */\n.btn-primary { background-color: #007bff !important; }\n.container { max-width: 1400px; }\n```\n````\n\n#### 场景 3：添加打印样式\n````txt\nwww.example.com/print.css cssAppend://{test3.css}\n\n``` test3.css\n@media print {\n  .no-print { display: none; }\n  a::after { content: \" (\" attr(href) \")\"; }\n}`\n```\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n","isInternal":false,"tokens":2085,"sizeBytes":6100},{"name":"cssBody.md","path":"docs/docs/rules/cssBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/cssBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# cssBody\n将现有响应内容体t替换成指定内容（（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssBody://(Hello) file://(-test-)\nwww.example.com/path2 cssBody://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssBody://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssBody://{body.txt} file://(-test-)\nwww.example.com/path2 cssBody://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello world.</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssBody:///User/xxx/test.css\nwww.example.com/path2 cssBody://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssBody://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","isInternal":false,"tokens":1302,"sizeBytes":4081},{"name":"cssPrepend.md","path":"docs/docs/rules/cssPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/cssPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# cssPrepend\n在现有响应内容体前面插入指定内容（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssPrepend://(Hello) file://(-test-)\nwww.example.com/path2 cssPrepend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 cssPrepend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello world.</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssPrepend:///User/xxx/test.css\nwww.example.com/path2 cssPrepend://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssPrepend://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","isInternal":false,"tokens":1346,"sizeBytes":4223},{"name":"delete.md","path":"docs/docs/rules/delete.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/delete.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# delete\n用于删除请求 URL、请求/响应头、请求/响应内容。\n\n## 规则语法\n``` txt\npattern delete://prop1|prop2|... [filters...]\n\n# 等效于：\npattern delete://prop1 delete://prop2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | `pathname`：删除请求路径（不包含请求参数）<br/>`pathname.index`：`index` 为路径分段序号 `...、-1、0、1、...` 或关键字 `last` 详见下面说明<br/>`urlParams`：删除所有请求参数<br/>`urlParams.xxx`：删除 URL 的 `xxx 参数`<br/>`reqHeaders.xxx`: 删除请求头的 `xxx` 字段<br/>`resHeaders.xxx`: 删除响应头的 `xxx` 字段<br/>`reqBody`: 删除所有请求内容<br/>`resBody`: 删除所有响应内容<br/>`reqBody.xxx.yyy`: 删除请求内容中类型为表单(form)或JSON的 `xxx.yyy` 字段<br/>`resBody.xxx.yyy`:  删除响应类型为 JSONP 或 JSON 的响应内容里的 `xxx.yyy` 字段<br/>`reqType`: 删除请求头 `content-type` 里的类型，不包含可能存在的 charset<br/>`resType`: 删除响应头 `content-type` 里的类型，不包含可能存在的 charset<br/>`reqCharset`: 删除请求头 `content-type` 里可能存在的 charset<br/>`resCharset`: 删除响应头 `content-type` 里可能存在的 charset<br/>`reqCookies.xxx`: 删除请求头的里面名为 `xxx` 的 cookie<br/>`resCookies.xxx`: 删除响应头的里面名为 `xxx` 的 cookie|    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nhttps://www.example.com/path delete://reqCookies.token|resCookies.token\n\nhttps://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json delete://resBody.name resType://json\n```\n上述 cookie 删除操作仅作用于请求/响应过程中的 cookie，不会修改浏览器本地存储的 cookie。如需修改浏览器持久化 cookie，可通过以下方式实现：\n- 通过 [jsPrepend](./jsPrepend) 等注入 JS 删除\n- 通过 [resCookies](./resCookies) 设置 cookie 过期\n\n## 删除路径\n> 支持版本：v2.9.102 及以上\n\n### 规则语法\n```\n# 基本格式\n目标域名 delete://pathname.索引号\n\n# 示例\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.-1\n```\n\n### 规则说明\n该规则用于删除请求 URL 中路径的特定分段。Whistle 会提取不包含查询参数的请求路径，并通过 `/` 分割成数组后进行删除操作。\n\n**示例解析：**\n- 请求URL：`https://www.example.com/api/path/to/xxx?query`\n- 提取路径：`api/path/to/xxx`\n- 分割数组：`['api', 'path', 'to', 'xxx']`\n- 应用规则：\n  - `delete://pathname.0` → 删除 'api'\n  - `delete://pathname.1` → 删除 'path' \n  - `delete://pathname.-1` → 删除 'xxx'\n- 最终路径：`/to`\n- 完整结果：`https://www.example.com/to?query`\n\n### 索引规则\n- **正数索引**：从 0 开始，表示从前往后的顺序\n- **负数索引**：从 -1 开始，表示从后往前的位置（-1 = 最后一段，-2 = 倒数第二段，以此类推）\n- **特殊值**：使用 `last` 可删除最后一段路径但保留末尾斜杠\n- **边界情况**：超出数组范围的索引将被忽略\n\n### 重要说明\n当路径以 `/` 结尾时，分割后的数组最后会包含一个空字符串项：\n- 路径：`/api/path/to/xxx/`\n- 分割结果：`['api', 'path', 'to', 'xxx', '']`\n\n### 使用建议\n- 使用 `pathname.-1` 删除最后一段且不保留末尾斜杠\n- 使用 `pathname.last` 删除最后一段但保留末尾斜杠\n\n**对比示例：**\n```\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.last\n```\n- 请求：`https://www.example.com/api/path/to/xxx?query`\n- 结果：`https://www.example.com/to/?query` (保留末尾斜杠)\n\n## 特殊字符\n如果对象的 key 包含以下字符，在路径表达式中需要使用反斜杠 `\\` 进行转义：\n\n- 分隔符：`|` 和 `&`\n- 空格：` `（一个空格）\n- 点：`.`\n- 控制字符：制表符 `\\t`、换行符 `\\n`、回车符 `\\r`、换页符 `\\f`、垂直制表符 `\\v`\n\n### 转义规则\n| 原始字符 | 转义写法 |\n|----------|----------|\n| `|`      | `\\|`     |\n| `&`      | `\\&`     |\n| 空格     | `\\ `     |\n| `.`      | `\\.`     |\n| 制表符   | `\\t`     |\n| 换行符   | `\\n`     |\n| 回车符   | `\\r`     |\n| 换页符   | `\\f`     |\n| 垂直制表符 | `\\v`   |\n\n> 注意：反斜杠 `\\` 本身也是转义字符，若要表示字面反斜杠，需写作 `\\\\`。\n\n### 示例\n假设需要删除请求 body 中的两个 key：\n- key1 为 `\\n .p`（包含换行符、空格、点、字母 p）\n- key2 为 `test|&test`（包含竖线和 &）\n\n对应的路径表达式应写作：\n```\nreqBody.\\n\\ \\.p.test\\|\\&test\n```\n其中：\n- `\\n` 表示换行符\n- `\\ ` 表示空格\n- `\\.` 表示点\n- `\\|` 表示竖线\n- `\\&` 表示 & 符号\n\n完整请求示例：\n```\n# 删除请求 body 中的 key1 和 key2\nhttps://www.example.com/path delete://reqBody.\\n\\ \\.p.test\\|\\&test\n```","isInternal":false,"tokens":1758,"sizeBytes":5353},{"name":"disable.md","path":"docs/docs/rules/disable.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/disable.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# disable\n通过规则禁用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern disable://action1|action2|... [filters...]\n\n# 等效于：\npattern disable://action1 disable://action2 ... [filters...]\n```\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n- `301`：禁止 `301` 跳转，`301` 强制转 `302`（也可以用 [replaceStatus](./replaceStatus)）\n- `abort`：禁用 abort 功能，详见：[enable](./enable)\n- `abortReq`：禁用 abortReq 功能，详见：[enable](./enable)\n- `abortRes`：禁用 abortRes 功能，详见：[enable](./enable)\n- `authCapture`：禁用 `authCapture` 功能，详见：[enable](./enable)\n- `auto2http`：禁用 auto2http 功能，详见：[enable](./enable)\n- `autoCors`：使用 [file](./file) 协议替换请求时，如果 Whistle 检测到该请求属于跨域请求，会自动添加必要的 CORS (跨域资源共享) 头信息，可以通过 `disable://autoCors` 禁用\n- `ajax`: 删除请求头 `x-requested-with`\n- `bigData`：禁用 bigData 功能，详见：[enable](./enable)\n- `capture` 或 `https`：禁用 `Enable HTTPS`，详见：[enable](./enable)\n- `captureIp`：禁用 captureIp 功能，详见：[enable](./enable)\n- `captureStream`：禁用 captureStream 功能，详见：[enable](./enable)\n- `clientCert`：禁用 clientCert 功能，详见：[enable](./enable)\n- `clientId`：删除请求头 `x-whistle-client-id`\n- `clientIp`：删除请求头 `x-forwarded-for`\n- `customParser`：禁用 customParser 功能，详见：[enable](./enable)\n- `cache`: 禁用缓存\n- `dnsCache`: 禁止 DNS 缓存\n- `csp`: 禁用 CSP\n- `cookies`: 禁用请求和响应的 cookie\n- `reqCookies`: 禁用请求的 cookie\n- `resCookies`: 禁用响应的 cookie\n- `flushHeaders`：禁用 flushHeaders 功能，详见：[enable](./enable)\n- `forHttp`：禁用 forHttp 功能，详见：[enable](./enable)\n- `forHttps`：禁用 forHttps 功能，详见：[enable](./enable)\n- `forceReqWrite`：禁用 forceReqWrite 功能，详见：[enable](./enable)\n- `forceResWrite`： 禁用 forceResWrite 功能，详见：[enable](./enable)\n- `gzip`：禁止压缩响应内容\n- `h2`：禁用 h2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器启用 HTTP2\n- `http2`：禁用 http2 功能，详见：[enable](./enable)\n  > 禁用：浏览器 -xx-> Whistle 代理 -xx-> 服务器全部启用 HTTP2\n- `httpH2`：禁用 httpH2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：禁用 hide 功能，详见：[enable](./enable)\n- `hideComposer`：禁用 hideComposer 功能，详见：[enable](./enable)\n- `hideCaptureError`：禁用 hideCaptureError 功能，详见：[enable](./enable)\n- `interceptConsole`：禁用 interceptConsole 功能，详见：[enable](./enable)\n- `internalProxy`：禁用 internalProxy 功能，详见：[enable](./enable)\n- `proxyFirst`：禁用优先使用 [proxy](./proxy) 规则\n- `proxyHost`：禁用 proxyHost 功能，详见：[enable](./enable)\n- `proxyTunnel`：禁用 proxyTunnel 功能，详见：[enable](./enable)\n- `keepCSP`：禁用 keepCSP 功能，详见：[enable](./enable)\n- `keepAllCSP`：禁用 keepAllCSP 功能，详见：[enable](./enable)\n- `keepCache`：禁用 keepCache 功能，详见：[enable](./enable)\n- `keepAllCache`：禁用 keepAllCache 功能，详见：[enable](./enable)\n- `keepAlive`：禁止缓存请求连接\n- `keepClientId`：禁用 keepClientId 功能，详见：[enable](./enable)\n- `keepH2Session`：无需每个 TUNNEL 连接对应一个 HTTP2 连接（默认 `是`，建议使用默认即可）\n- `safeHtml`：禁用 safeHtml 功能，详见：[enable](./enable)\n- `strictHtml`：禁用 strictHtml 功能，详见：[enable](./enable)\n- `proxyConnection`：通过 proxy、socks 等代理协议转发请求时设置 `proxy-connection: close`\n- `ua`: 删除请求头 `user-agent`\n- `proxyUA`：通过 proxy、socks 等代理协议转发请求时删除请求头 `user-agent`\n- `referer`: 禁用 `referer`\n- `rejectUnauthorized`：`rejectUnauthorized` 设置为 `true`\n- `requestWithMatchedRules`：禁用 requestWithMatchedRules 功能，详见：[enable](./enable)\n- `responseWithMatchedRules`：禁用 responseWithMatchedRules 功能，详见：[enable](./enable)\n- `secureOptions`：禁用建立 HTTP2 连接的默认 `options`\n- `timeout`：禁用请求超时设置\n- `trailerHeader`：删除响应头 `trailer`\n- `trailers`：删除 trailers\n- `tunnelAuthHeader`：删除代理鉴权头 `proxy-authorization`\n- `tunnelHeadersFirst`：禁用 tunnelHeadersFirst 功能，详见：[enable](./enable)\n- `useLocalHost`：禁用 useLocalHost 功能，详见：[enable](./enable)\n- `useSafePort`：禁用 useSafePort 功能，详见：[enable](./enable)\n- `userLogin`：禁用设置 [statusCode://401](./statusCode) 时显示登录框\n- `useSniAsTunnelDomain`：禁用 `enable://useSniAsTunnelDomain`\n- `weakRule`：禁用 weakRule 功能，详见：[enable](./enable)\n\n## 配置示例\n``` txt\n# 禁用请求的缓存，同时删除请求和响应头的缓存字段\n# 跟 cache 协议的区别是，cache 只是用来设置响应的缓存头\nwwww.example.com/path disable://cache\n\n# 禁用请求和响应的 cookies\nwwww.example.com/path disable://cookies\n\n# 只禁用请求的 cookies\nwwww.example.com/path disable://reqCookies\n\n# 只禁用响应的 cookies\nwwww.example.com/path disable://resCookies\n\n# 删除 user-agent\nwwww.example.com/path disable://ua\n\n# 删除 referer\nwwww.test.com/path disable://referer\n\n# 删除csp策略\nwwww.test.com/path disable://csp\n\n# 禁用 timeout，默认情况下 Whistle 对每个请求如果 36s 内没有发生数据传输，会认为请求超时\nwwww.test.com/path disable://timeout\n\n# 把 301 转成 302，防止 cache\nwwww.test.com/path disable://301\n\n# 禁用 HTTPS 拦截\nwwww.test.com/path disable://capture\n\n# 不缓存 DNS 结果\nwwww.test.com/path disable://dnsCache\n\n# 禁用代理服务器请求链接复用\nwwww.test.com/path disable://keepAlive\n\n# 删除请求头 `x-requested-with`\nwwww.test.com/path disable://ajax\n\n# 也可以同时禁用多个\nwww.example.com/path disable://cache|cookies|ua|referer|csp|timeout|301|intercept|dnsCache|keepAlive|autoCors\n```\n\n关联操作：[enable](./enable)\n","isInternal":false,"tokens":2177,"sizeBytes":6795},{"name":"enable.md","path":"docs/docs/rules/enable.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/enable.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# enable\n通过规则启用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern enable://action1|action2|... [filters...]\n\n# 等效于：\npattern enable://action1 enable://action2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n- `abort`：请求或响应阶段中断请求（根据匹配阶段）\n- `abortReq`：请求阶段中断请求\n- `abortRes`：响应阶段中断请求\n- `authCapture`：强制在转成 HTTPS 之前执行 [auth hook](../extensions/dev#auth)（默认是转成 HTTPS 请求后再执行插件的 auth hook）\n- `auto2http`：开启 HTTPS 请求报 TLS 错误自动转成 HTTP 请求，默认情况下如果 serverIP 是本地 IP 会自动启用\n- `bigData`：扩大抓包数据显示限制(2M→16M)\n- `emudhra`：使用已公开泄露的 eMudhra 中间 CA 私钥签发动态生成的 TLS 证书，并发送完整证书链\n- `br`：启用 BR 压缩响应内容\n- `gzip`：启用 GZIP 压缩响应内容\n- `deflate`：启用 Deflate 压缩响应内容\n- `capture` 或 `https`：Enable HTTPS（同 [HTTPS菜单功能](../gui/https.html)）\n- `captureIp` ：域名为 IP 的请求，默认不解密 HTTPS 请求，可以通过 `enable://captureIp` 启用解析 HTTPS 请求\n- `captureStream`：将抓取到的请求与响应内容以数据流的形式，实时输出到抓包界面并动态追加显示\n- `clientCert`：启用客户端与服务器之间的双向认证 (mTLS)\n- `clientId`：请求头带上 `x-whistle-client-id: Whistle本地生成的唯一ID`\n- `clientIp`：为匹配的非本地请求设置 `x-forwarded-for` 请求头，将客户端的真实IP地址透传给上游服务\n- `customParser`：自定义抓包界面显示内容，用法参考插件：https://github.com/whistle-plugins/whistle.custom-parser\n- `flushHeaders`：`response.writeHead(...)` 后调用 [response.flushHeaders](https://nodejs.org/docs/latest/api/http.html#responseflushheaders)（默认执行）\n- `forHttp`：让 `capture` 功能只对 HTTP 请求生效\n- `forHttps`：让 `capture` 功能只对 HTTPS 请求生效\n- `forceReqWrite`：使用 [reqWrite](./reqWrite)、[reqWriteRaw](./reqWriteRaw) 将请求数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceReqWrite` 强制覆盖\n- `forceResWrite`： 使用 [resWrite](./resWrite)、[reqWriteRaw](./reqWriteRaw) 将响应数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceResWrite` 强制覆盖\n- `h2`：Whistle 代理 -> 服务器启用 HTTP2\n- `http2`：浏览器 -> Whistle 代理 -> 服务器全部启用 HTTP2\n- `httpH2`：Whistle 代理 -> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：在界面上隐藏抓包数据（不包括 `captureError` 和 Composer 发出的请求）\n- `hideComposer`：隐藏 Composer 发出的请求\n- `hideCaptureError`：隐藏 `captureError` 请求\n- `showHost`：将服务器 IP 设置到响应头 `x-host-ip`\n- `ignoreSend`：WebSocket 和 TUNNEL 请求时忽略发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `ignoreReceive`：WebSocket 和 TUNNEL 请求时忽略接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseSend`：WebSocket 和 TUNNEL 请求时暂停发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseReceive`：WebSocket 和 TUNNEL 请求时暂停接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `inspect`：使的在 Inspectors / Frames 看到 TUNNEL 请求的内容\n- `interceptConsole`：截获 `console.xxx` 的请求并显示在 Whistle 管理界面的 Log 面板（默认开启）\n- `internalProxy`：利用 `proxy`、`socks` 等代理协议将请求转发至其他代理服务器（如另一 Whistle 实例）。启用此功能后，已在第一层代理解密的 HTTPS 请求将以明文形式在代理链中传输，从而上游代理可以直接获取明文数据\n- `proxyFirst`：优先使用 [proxy](./proxy) 规则（默认情况下，同时匹配 `host` 和 `proxy`，只有 `host` 生效）\n- `proxyHost`：[proxy](./proxy) 和 [host](./host) 同时生效\n- `proxyTunnel`：跟 `proxyHost` 一同使用，让上游代理再次通过隧道代理到上上游的 HTTP 代理，详见下面的示例\n- `keepCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头大 `csp` 字段，如果想保留这些字段可以用 `enable://keepCSP`\n- `keepAllCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的 `csp` 字段，如果想保留这些字段可以用 `enable://keepAllCSP`\n- `keepCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepCache`\n- `keepAllCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepAllCache`\n- `keepClientId`：保留请求原有的 `x-whistle-client-id` 请求头（默认会删除请求带过来的  `x-whistle-client-id`）\n- `safeHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `{` 和 `[`（JSON 对象开头字符），如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `strictHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `<`，如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `multiClient`：Whistle 作为公共代理且启用 `enable://clientId` 时会为所有请求添加一个固定的 `x-whistle-client-id` 请求头，这导致上游服务无法区分不同客户端。启用 `enable://multiClient` 后，将为每个客户端连接生成并维持一个唯一且不变的标识符，确保上游服务能准确识别请求来源\n- `requestWithMatchedRules`：在请求头带上当前匹配的规则\n- `responseWithMatchedRules`：在响应头带上当前匹配的规则\n- `tunnelHeadersFirst`：用于控制请求头合并的优先级。插件可通过 [tunnelKey](../extensions/dev) 将隧道（TUNNEL）请求头传递至后续阶段。默认的合并规则是：若隧道头与解析后的普通请求头存在同名键，则保留普通请求头的值。启用 `enable://tunnelHeadersFirst` 可改变这一行为，确保隧道请求头优先，从而强制覆盖任何冲突的普通头\n- `useLocalHost`：修改 `log` 和 `weinre` 请求 `URL` 的域名，使用内置域名\n- `useSafePort`：修改 `log` 和 `weinre` 请求 `URL` 的端口，使用内置端口\n- `userLogin`：设置 [statusCode://401](./statusCode) 是否显示登录框（默认显示）\n- `useSniAsTunnelDomain`：当 Tunnel 域名与请求中的 SNI 不一致时，使用 SNI 中携带的域名作为 Tunnel 域名\n- `weakRule`：默认情况下，当配置了 [file](./file) 等协议时，[proxy](./proxy) 规则会自动失效。通过设置 `weakRule` 属性，可以提升 [proxy](./proxy) 规则的优先级，使其在上述场景中仍然生效\n- `socket`：在启用 HTTPS 解析（`Enable HTTPS` 或 `enable://https`）后，发往 `80/443` 端口的 TUNNEL 请求会被强制尝试解析为 HTTP/HTTPS 流量。默认情况下，若解析失败，该连接将被销毁；而其他端口的请求解析失败则会继续以 TUNNEL 方式传输。通过设置 `enable://socket`，可让发往 `80/443` 端口的请求在解析失败时同样降级为 TUNNEL 连接，避免连接被销毁\n- `websocket`：用于处理非标准 WebSocket 连接。某些请求虽使用 WebSocket 协议传输，但其 Upgrade 请求头并非标准值（如 `Upgrade: websocket`）。默认情况下，Whistle 会将其视为普通 TCP 连接而不解析数据。启用 `enable://websocket` 可强制 Whistle 识别此类连接为 WebSocket 协议并进行数据解析\n\n\n## 配置示例\n``` txt\n# Enable HTTPS\nwww.example.com enale://https\n\n# 延迟 3000毫秒终止请求\nwww.example.com/path reqDelay://3000 enable://abortReq\n\n# 延迟 5000毫米终止响应\nwww.example.com/path resDelay://5000 enable://abortRes\n\n# 本地替换的内容开启 GZIP\nwww.example.com/path file:///User/xxx/test enable://gzip\n\n# 给上游代理设置 hosts (10.10.10.20:8888)\nwww.example.com/path proxy://10.1.1.1:8080 10.10.10.20:8888 enable://proxyHost\n\n# 通过上游 HTTP 代理 (10.1.1.1:8080) 将请求通过隧道代理到指定的 HTTP 代理(10.10.10.20:8080)\nwww.example.com proxy://10.1.1.1:8080 10.10.10.20:8080 enable://proxyHost|proxyTunnel\n\n# 启用浏览器 -> Whistle 代理 -> 服务器整个链路的 HTTP2 功能\nwww.example.com enable://http2\n\n# 启用 Whistle 代理 -> 服务器的 HTTP2 功能\nwww.example.com enable://h2\n\n# 强制 Whistle 代理 -> 服务器的 HTTP 请求使用 HTTP2 传输\nwww.example.com enable://httpH2\n\n# 安全注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `{` 才注入\nwww.example.com/path enable://safeHtml\n\n# 严格HTML注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `<` 才注入\nwww.example.com/path enable://strictHtml\n\n# 自动添加 x-forwarded-for 请求头传递客户端真实 IP\nwww.example.com enable://clientIp\n\n# 扩大抓包数据显示限制(2M→16M)\nwww.example.com/path enable://bigData\n\n# 修改 log/weinre 请求 `URL` 的域名或端口\nwww.example.com/path enable://useLocalHost|useSafePort\n\n# 强制 reqWrite/reqWriteRaw/resWrite/resWriteRaw 覆盖已有的文件\nwww.example.com/path enable://forceReqWrite|forceResWrite\n\n# 强制 HTTPS 请求被解析前也走 `auth hook`（默认是转成 HTTPS 请求后再执行插件的 auth hook）\nwww.example.com enable://authCapture\n```\n\n关联操作：[disable](./disable)\n","isInternal":false,"tokens":3470,"sizeBytes":10550},{"name":"excludeFilter.md","path":"docs/docs/rules/excludeFilter.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/excludeFilter.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# excludeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation excludeFilter://p1 excludeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 且不能匹配 `p1`、`p2`、... 中的任一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求不生效\nwww.example.com/api/data proxy://127.0.0.1:8080 excludeFilter://m:GET excludeFilter://m:post\n```\n\n可以跟 [includeFilter](./includeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n","isInternal":false,"tokens":334,"sizeBytes":1149},{"name":"file.md","path":"docs/docs/rules/file.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/file.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# file\n\n`file` 协议用于将请求映射到本地文件系统，用本地文件的内容作为响应返回给客户端，适用于以下场景：\n\n- **搭建本地开发环境**：快速搭建本地开发服务器\n- **调试本地前端页面**：直接调试本地 HTML、CSS、JavaScript 文件\n- **接口 Mock 场景**：使用本地 JSON 文件模拟 API 接口响应\n- **静态资源服务**：将本地目录作为静态资源服务器使用\n\n## 规则语法\n\n`file` 协议支持多种方式指定本地文件内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要返回的内容，适用于简短的文本内容。注意：`value` 不能包含空格和换行符。\n\n```txt\npattern file://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/api/data file://({\"status\":\"ok\"})\n```\n\n**注意：内联值必须用括号 `()` 包裹，否则会被识别为本地文件路径，可能导致 404 错误：**\n``` txt\n# 正确写法：返回字符串 \"success\"\npattern file://(success)\n\n# 错误写法：会被当作路径处理，可能导致 404\npattern file://success\n```\n\n> **file 协议的内联值语法要点**：\n> - 必须使用 `()` 包裹内容\n> - 内容不能包含空格和换行符\n> - 可以是字符串、JSON、HTML片段等\n> - 如果不加 `()`，Whistle 会将其解释为文件路径或 `{key}` 并尝试加载对应文件或 key 值\n\n### 2. 内嵌值（使用代码块）\n当需要处理包含空格、换行符的复杂内容，或希望复用某段配置时，推荐使用此方式。\n\n````txt\npattern file://{custom-key.json} [lineProps...] [filters...]\n\n``` custom-key.json\n{\n  \"status\": \"success\",\n  \"data\": {\n    \"message\": \"Hello from local file\"\n  }\n}\n```\n````\n\n**最佳实践**：建议为 `key` 添加响应类型后缀（如 `.json`、`.html`、`.css`），Whistle 会自动根据后缀设置正确的 `Content-Type`。\n\n### 3. 引用 Values 中的值\n当内容较大时，可以将其存储在 `Values` 配置区中。\n\n```txt\npattern file://{key-of-values.html} [lineProps...] [filters...]\n```\n\n**前提**：在 `Values` 中存在名为 `key-of-values.html` 的键，其值为要返回的内容。\n\n**容量建议**：\n- 小于 2KB 的内容：建议使用内联或内嵌方式\n- 2KB 至 200KB 的内容：建议存储在 `Values` 中\n- 大于 200KB 的内容：建议使用本地文件\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern file://temp.html\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `file://temp.html`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report file://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.html\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件路径或远程 URL 加载\n从本地文件系统或远程 URL 加载响应内容。\n\n```txt\n# 从本地文件加载\npattern file:///User/xxx/test.html\n\n# 从远程 URL 加载\npattern file://https://example.com/template.html\n```\n\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要返回的文件内容或路径，支持多种格式：<br>• 本地文件或目录路径<br>• 远程 URL<br>• 内联、内嵌、Values 引用内容 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>**示例**：<br>• `lineProps://important`：提升规则优先级<br>• `lineProps://weakRule`：当同时配置 [file](./file) 和 [proxy](./proxy) 规则时，默认 [proxy](./proxy) 会失效。通过此属性可提升 [proxy](./proxy) 规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n\n## 配置示例\n\n### 1. 基础文件路径映射\n```txt\n# 将域名映射到本地目录\nwww.example.com/path file:///Users/username/projects/my-site\n\n# Windows 系统路径\nwww.example.com/path file://D:\\projects\\my-site\n```\n\n**特性说明**：\n- **自动路径拼接**：访问 `https://www.example.com/path/x/y/z` 会映射到 `/Users/username/projects/my-site/x/y/z`\n- **禁用路径拼接**：使用 `< >` 包裹路径可以禁用自动拼接\n    ```txt\n    www.example.com/path file://</Users/username/projects/my-site/index.html>\n    ```\n    > 访问 `https://www.example.com/path/x/y/z` 只会返回 `/Users/username/projects/my-site/index.html` 文件\n\n### 2. JSONP 接口 Mock\n````txt\n# 内联方式\nwww.example.com/jsonp file://`(${query.callback}({\"status\":\"ok\"}))`\n\n# 内嵌方式\n``` jsonp-response\n${query.callback}({\n  \"status\": \"error\",\n  \"message\": \"Invalid parameter\"\n})\n```\nwww.example.com/jsonp file://`{jsonp-response}`\n````\n\n> **模板字符串限制**：模板字符串在以下场景无法直接生效：\n> - 引用本地文件路径时\n> - 引用远程 URL 地址时\n> \n> 遇到上述限制时，可以使用 [tpl](./tpl) 协议作为替代方案。\n\n### 3. 配合过滤器使用\n```txt\n# 排除特定接口\nwww.example.com/api/path file:///Users/username/mock-data excludeFilter://*/api/auth\n\n# 根据请求体内容匹配\nwww.example.com/api/search file:///Users/username/search-results.json includeFilter://b:/\"type\":\\s*\"advanced\"/i\n\n# 仅匹配 POST 请求\nwww.example.com/api/submit file:///Users/username/success.json includeFilter://m:POST\n```\n\n### 4. 多目录搜索\n```txt\n# 多个文件或目录用 `|` 分隔\nwww.example.com/static/path file:///path/to/project1|/path/to/project2|/path/to/project3\n```\n\n**查找逻辑**：\n1. 按顺序检查每个目录\n   - `/path/to/project1/static/file.js`\n   - `/path/to/project2/static/file.js`\n   - `/path/to/project3/static/file.js`\n2. 找到第一个存在的文件立即返回\n3. 全部未找到时返回 `404 Not Found`\n\n## 高级用法\n\n### 动态路径处理\n```txt\n# 使用正则表达式捕获组\n/^https?://www\\.example\\.com/user/(\\d+)/profile file:///Users/username/mock/profiles/user-$1.json\n\n# 如果不限制数字，可以用通配符\n^www.example.com/user/*/profile file:///Users/username/mock/profiles/user-$1.json\n```\n\n### 环境特定配置\n````txt\n``` dev-config\n{\n  \"apiBase\": \"http://localhost:3000\",\n  \"debugMode\": true\n}\n```\n\n``` prod-config\n{\n  \"apiBase\": \"https://api.example.com\",\n  \"debugMode\": false\n}\n```\n\n# 通过 cookie：env=dev 判断是否为开发环境\nwww.example.com/config file://{dev-config} includeFilter://reqH:cookie=/env=dev/\nwww.example.com/config file://{prod-config} # 默认正式环境\n````\n\n### 组合使用其他协议\n```txt\n# 先使用 file 提供静态文件，再设置响应头\nwww.example.com file:///Users/username/static-files cache://3600\n\n# 对于不存在的文件，使用 xfile 允许请求继续\nwww.example.com xfile:///Users/username/static-files\n```\n\n## 与 resBody 的区别\n\n`file` 协议与 [`resBody`](./resBody) 协议的主要区别在于请求处理流程和响应机制：\n\n**`file` 协议：***\n- **请求流程**：匹配 `file` 协议的请求**不会发送到后台服务器**\n- **响应机制**：直接返回指定文件内容，并响应 200 状态码\n- **网络开销**：零网络延迟，完全本地处理\n\n**示意图**：\n```\n客户端请求 → Whistle (匹配file规则) → 读取本地文件 → 返回响应 (200)\n                  ↓\n            (不发送到服务器)\n```\n\n**`resBody` 协议：**\n- **请求流程**：请求**会先发送到后台服务器**，获取原始响应\n- **响应机制**：收到服务器响应后，用指定内容**替换**原始响应体\n- **网络开销**：包含完整的请求-响应网络往返\n\n**示意图**：\n```\n客户端请求 → Whistle → 发送到服务器 → 收到原始响应 → 替换响应体 → 返回修改后的响应\n```\n\n## 关联协议\n\n1. **允许未匹配文件继续访问**：[xfile](./xfile)\n   - 当本地文件不存在时，允许请求继续访问原始服务器\n   - 适合静态资源服务器的开发场景\n\n2. **使用远程 URL 内容替换**：[https](./https) 或 [http](./http)\n   - 使用远程服务器的内容作为响应\n   - 注意：不建议使用 `file://https://xxx` 这种形式\n\n3. **域名解析重定向**：[host](./host)\n   - 修改域名解析，将请求导向指定 IP\n\n4. **模板渲染**：[tpl](./tpl)\n   - 支持更复杂的模板渲染和动态内容生成\n\n\n\n## 注意事项\n\n### 路径处理\n1. **相对路径**：除了插件内部的规则外，不建议使用相对路径。相对路径相对于 Whistle 配置文件所在目录。\n2. **绝对路径**：支持系统绝对路径\n3. **用户目录**：支持 `~` 表示用户主目录\n4. **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 文件编码\n1. **文本文件**：默认使用 UTF-8 编码\n2. **编码问题**：如遇乱码，请检查文件的实际编码格式\n\n\n## 故障排除\n\n### Q: 文件未找到（404）\n**A:** 检查：\n1. 文件路径是否正确\n2. 文件是否存在且有读取权限\n3. 是否使用了 `< >` 禁用了路径拼接\n\n### Q: 乱码问题\n**A:** 检查：\n1. 文件的实际编码格式\n2. 响应头的 `Content-Type` 是否正确\n3. 文件内容是否包含非法字符\n\n### Q: 规则未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配\n2. 是否有其他规则覆盖\n3. 过滤器条件是否满足\n\n### Q: 模板字符串不生效\n**A:** 检查：\n1. 是否在文件路径或远程 URL 场景下使用模板字符串\n2. 如果是，请改用 [tpl](./tpl) 协议\n\n\n\n## 扩展阅读\n\n- [匹配模式文档](./pattern)：详细了解 URL 匹配规则\n- [操作指令文档](./operation)：了解内容加载的多种方式\n- [附加配置](./lineProps)：详细了解一些特殊功能\n- [过滤器](./filters)：了解更多过滤器功能\n","isInternal":false,"tokens":3419,"sizeBytes":10234},{"name":"filters.md","path":"docs/docs/rules/filters.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/filters.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# 过滤器\n\n当需要基于请求属性或响应属性（而不仅仅是URL）进行匹配时，可以使用过滤器来实现更精细化的规则控制，语法结构：\n\n``` txt\npattern opertaion includeFilter://pattern1 ... excludeFilter://patternx ...\n```\n> 多个过滤器间为「或」匹配，只要匹配其中一个过滤条件就成立\n\n## 过滤器类型\n\n| 过滤器类型     | 语法格式                  | 用途                     |\n| -------------- | ------------------------- | ------------------------ |\n| **包含过滤器** | `includeFilter://pattern` | 只匹配符合指定条件的请求 |\n| **排除过滤器** | `excludeFilter://pattern` | 排除符合指定条件的请求   |\n\n## pattern 类型\n\n用于 `includeFilter://<pattern>` 和 `excludeFilter://<pattern>` 的匹配表达式。\n\n> **通用规则**\n> - 未指定前缀时，默认匹配请求 URL。\n> - 关键字匹配默认不区分大小写。\n> - 正则格式为 `/regexp/`，末尾可加 `i` 表示忽略大小写，例如 `/regexp/i`。\n> - `chance` 的 `probability` 取值范围为 `0 ~ 1`。\n\n| 语法 | 匹配目标 | 示例 |\n| --- | --- | --- |\n| `b:pattern` | 请求体内容：关键字（不区分大小写）或正则 | `includeFilter://b:keyword`<br>`excludeFilter://b:/regexp/i` |\n| `m:pattern` | HTTP 方法：方法名（不区分大小写）或正则 | `includeFilter://m:get`<br>`excludeFilter://m:/regexp/i` |\n| `i:pattern` | 客户端或服务端 IP：IP 或正则 | `includeFilter://i:192.168.1.10`<br>`excludeFilter://i:/regexp/i` |\n| `chance:probability` | 按概率随机命中：`Math.random() < probability` | `includeFilter://chance:0.5`<br>`excludeFilter://chance:0.3` |\n| `clientIp:pattern` | 客户端 IP：IP 或正则 | `includeFilter://clientIp:192.168.1.10`<br>`excludeFilter://clientIp:/regexp/i` |\n| `serverIp:pattern` | 服务端 IP：IP 或正则 | `includeFilter://serverIp:192.168.1.10`<br>`excludeFilter://serverIp:/regexp/i` |\n| `s:pattern` | 响应状态码：状态码或正则 | `includeFilter://s:/^20/`<br>`excludeFilter://s:200` |\n| `reqH.header-key:pattern` | 请求头：关键字（不区分大小写）或正则 | `includeFilter://reqH.content-type:json`<br>`excludeFilter://reqH.content-type:/regexp/i` |\n| `resH.header-key:pattern` | 响应头：关键字（不区分大小写）或正则 | `includeFilter://resH.content-type:json`<br>`excludeFilter://resH.content-type:/regexp/i` |\n| 无前缀 `pattern`（其它 `xxxxxx`） | 请求 URL，同 [pattern](./pattern) | `includeFilter://*/cgi-*`<br>`excludeFilter://www.test.com`<br>`includeFilter://https://www.test.com/path` |\n","isInternal":false,"tokens":853,"sizeBytes":2625},{"name":"forwardedFor.md","path":"docs/docs/rules/forwardedFor.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/forwardedFor.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# forwardedFor\n修改请求头 `x-forwarded-for` 字段，自定义客户端 IP。\n\n## 规则语法\n``` txt\npattern forwardedFor://ip [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| ip   | 自定义客户端 IP<br/>⚠️ 不支持从文件/远程 URL 加载数据 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 设置固定 IP\nwww.example.com/path  forwardedFor://1.1.1.1\n\n# 使用真实客户端 IP（透传模式）\nwww.example.com  forwardedFor://`${clientIp}`\n```\n\n## 关联协议\n1. 直接修改请求头：[reqHeaders://x-forwarded-for=value](./reqHeaders)（这种方式的 `value` 可以不局限于 IP）\n2. 启用自动设置 `x-forwarded-for`：[enable://clientIp](./enable)\n","isInternal":false,"tokens":314,"sizeBytes":1122},{"name":"frameScript.md","path":"docs/docs/rules/frameScript.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/frameScript.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# frameScript\n通过 JavaScript 脚本操作 WebSocket 和普通 TCP 请求数据帧。\n\n## 规则语法\n``` txt\npattern frameScript://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 生成规则的 JS 脚本，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n```` txt\n``` test-frame.js\n// 发送预设数据\nctx.sendToServer('1 = 0x12300000');\nctx.sendToClient('1 = 0x1236666666');\n\n// 处理发往服务端的数据帧\nctx.handleSendToServerFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '***');\n};\n\n// 处理发往客户端的数据帧  \nctx.handleSendToClientFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '+++');\n};\n```\n\nwss://echo.websocket.org/ frameScript://{test-frame.js}\n````\n访问 `https://echo.websocket.org/.ws` 效果：\n\n <img width=\"1200\" alt=\"frame-script\" src=\"/img/frame-script.png\" />\n\n#### 可用全局变量\n\n| 变量/方法          | 描述                                                                 |\n|--------------------|---------------------------------------------------------------------|\n| `url`             | 完整请求URL                                                         |\n| `method`          | 请求方法(GET/POST等)                                                |\n| `ip`/`clientIp`   | 客户端IP地址                                                       |\n| `headers`         | 请求头对象                                                          |\n| `rules`           | 规则数组，通过push添加新规则                                        |\n| `values`          | 临时值存储对象                                                      |\n| `render(tpl,data)`| 微型模板渲染函数                                                    |\n| `getValue(key)`   | 获取Values中的值                                                    |\n| `parseUrl`        | 同Node.js的`url.parse`                                              |\n| `parseQuery`      | 同Node.js的`querystring.parse`                                      |\n| `ctx.sendToClient(frame, options)` | 发送数据（对象、字符串、Buffer）到客户端，用法见下面示例 |\n| `ctx.sendToServer(frame, options)` | 发送数据（对象、字符串、Buffer）到服务端，用法见下面示例 |\n| `ctx.destroy()` | 断开当前连接（`v2.10.11` 新增） |\n| `handleSendToClientFrame(buffer, options)` | 处理（过滤或删除）发送到客户端的数据，用法见下面示例 |\n| `handleSendToServerFrame(buffer, options)` | 处理（过滤或删除）发送到服务端的数据，用法见下面示例 |\n\n> 注意：`handleSendToClientFrame` 内不可调用 `ctx.sendToClient`；`handleSendToServerFrame` 内不可调用 `ctx.sendToServer`\n","isInternal":false,"tokens":836,"sizeBytes":3404},{"name":"headerReplace.md","path":"docs/docs/rules/headerReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/headerReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# headerReplace\n通过关键字匹配或正则表达式替换指定请求头/响应头。\n\n## 规则语法\n``` txt\npattern headerReplace://value [filters...]\n```\n> `header-name` 不区分大小写\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 三种情况：<br/>• `req.header-name:p1=v1&p2=v2`<br/>• `res.header-name:p1=v1&p2=v2`<br/>• `trailer.trailer-name:p1=v1&p2=v2` | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 请求头 `accept` 字段值的第一个 `html` 关键字被改成 `abc`\nwww.example.com/path headerReplace://req.accept:html=abc\n\n# 请求头 `accept` 字段值的所有 `ml` 关键字被改成 `abc`\nwww.example.com/path2 headerReplace://req.accept:/ml/g=abc\n\n# 修改响应头\nwww.example.com/path3 headerReplace://res.Content-Type:html=plain\n```\n\n`headerReplace` 是用来替换请求/响应头的局部内容，如果要修改请求/响应字段内容，还可以用：\n- 设置请求头：[reqHeaders](./reqHeaders)\n- 设置响应头：[resHeaders](./resHeaders)\n","isInternal":false,"tokens":425,"sizeBytes":1460},{"name":"host.md","path":"docs/docs/rules/host.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/host.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# host\n修改请求的 DNS 解析结果，将指定请求解析到特定 IP 地址（域名）及端口，可以看作终极版的系统 hosts 配置功能。\n\n## 规则语法\n``` txt\npattern host://ipOrDomain[:port] [filters...]\n```\n> `port` 如果不填，则沿用请求 URL 的原始端口，如果是指向域名则相当于 `cname` 功能\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 如果只是 IP 或端口可以省略 `host://`\nwww.example.com/test0 127.0.0.1      # 不改端口，沿用请求 URL 的原始端口\nwww.example.com/test1 127.0.0.1:5173\n\n# CNAME 功能\nwww.example.com/test2 host://www.test.com\nwww.example.com/test3 host://www.test.com:8080\n\n# 高级配置，从请求参数获取目标地址，includeFilter 确保存在该参数\nwww.example.com/test4 host://`${query.target}$:8080` includeFilter:///[?&]target=[\\w-]+/i\n```\n## 与 proxy 的匹配优先级\n以下是优化后的文档，结构更清晰，语言更简洁准确：\n\n---\n\n## 与 host 的匹配优先级\n\n#### 默认行为\n\n当请求同时匹配 `host` 和 `proxy` 规则时：\n- 仅 `host` 规则生效\n- `proxy` 规则自动忽略\n\n#### 修改优先级\n| 配置方式 | 语法 | 效果 |\n|---------|------|------|\n| **优先 proxy** | [`enable://proxyFirst`](./enable) 或 [`lineProps://proxyFirst`](./lineProps) | 仅 `proxy` 生效（覆盖 host） |\n| **同时生效** | [`enable://proxyHost`](./enable) 或 [`lineProps://proxyHost`](./lineProps) | `proxy` 和 `host` 同时生效 |\n\n#### 使用建议\n- 大多数场景使用默认行为即可\n- 需要特殊代理逻辑时才使用 `proxyFirst`\n- 需要双重匹配时使用 `proxyHost`\n\n## 注意事项\n`host` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com 127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求指向 `127.0.0.1:1234`，但由于 `host` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `host` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com 127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `host` 规则匹配，最终指向 `127.0.0.1:1234`。\n\n\n## 常见问题\n1. 与 URL 转换的区别：\n    ``` txt\n    # 服务端收到的 URL 还是 www.example.com\n    www.example.com 127.0.0.1:5173\n\n    # 服务端收到的 URL 是 http://127.0.0.1:5173\n    www.example.com http://127.0.0.1:5173\n    ```\n2. 自动降级为 HTTP 请求：如果配置的目标 IP 为 `127.0.0.1`，且 HTTPS 请求报错会自动降级为 HTTP 请求，方便访问本地服务，用户可以通过以下规则禁用该功能：\n    ``` txt\n    # 禁用本地 HTTPS 自动降级\n    pattern disable://auto2http\n    ```\n","isInternal":false,"tokens":1239,"sizeBytes":3760},{"name":"htmlAppend.md","path":"docs/docs/rules/htmlAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/htmlAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# htmlAppend\n在现有响应内容体后面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlAppend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlAppend://(Hello) file://(-test-)\nwww.example.com/path2 htmlAppend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlAppend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlAppend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlAppend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlAppend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlAppend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlAppend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `<div>Test</div>test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlAppend:///User/xxx/test.txt\nwww.example.com/path2 htmlAppend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlAppend://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","isInternal":false,"tokens":1546,"sizeBytes":4867},{"name":"htmlBody.md","path":"docs/docs/rules/htmlBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/htmlBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# htmlBody\n\n将现有响应内容体t替换成指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlBody://(Hello) file://(-test-)\nwww.example.com/path2 htmlBody://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlBody://{body.txt} file://(-test-)\nwww.example.com/path2 htmlBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlBody://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlBody://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlBody://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlBody://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlBody:///User/xxx/test.txt\nwww.example.com/path2 htmlBody://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlBody://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n","isInternal":false,"tokens":1568,"sizeBytes":4914},{"name":"htmlPrepend.md","path":"docs/docs/rules/htmlPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/htmlPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# htmlPrepend\n在现有响应内容体前面面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlPrepend://(Hello) file://(-test-)\nwww.example.com/path2 htmlPrepend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello world.-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlPrepend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlPrepend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlPrepend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlPrepend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    test<div>Test</div>\n    ```\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlPrepend:///User/xxx/test.txt\nwww.example.com/path2 htmlPrepend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlPrepend://temp/blank.txt\n````\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n","isInternal":false,"tokens":1626,"sizeBytes":5113},{"name":"http.md","path":"docs/docs/rules/http.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/http.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# http\n将以下三种请求转换为 HTTP 请求（服务端将收到转换后的 HTTP URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern http://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 http://www.test.com/path/xxx\nhttps://www.example.com/path2 http://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `http://www.example.com/path1`              | `http://www.test.com/path/xxx`             |\n    | `http://www.example.com/path1/a/b/c?query`  | `http://www.test.com/path/xxx/a/b/c?query` |\n    | `https://www.example.com/path2`            | `http://www.abc.com/path3/yyy`             |\n    | `https://www.example.com/path2/a/b/c?query` | `http://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 http://<www.test.com/path/xxx>\n    # www.example.com/path1 http://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `http://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 http://www.test.com/path/xxx\nwss://www.example.com/path2 http://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `ws://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `ws://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 http://www.test.com:123\ntunnel://www.example2.com:443 http://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:80`|\n\n自动忽略匹配 URL 的路径，HTTP 协议默认端口为 `80`，HTTPS 协议默认端口为 `443`\n\n\n","isInternal":false,"tokens":1010,"sizeBytes":3613},{"name":"https-proxy.md","path":"docs/docs/rules/https-proxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/https-proxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# https-proxy\n`https-proxy` 指令用于将匹配的请求通过指定的 HTTPS 代理服务器转发。\n\n## 规则语法\n``` txt\npattern https-proxy://ipOrDomain[:port] [filters...]\n```\n> `port` 可选，不填则使用默认端口 `443`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 将请求代理到 HTTPS 代理: `127.0.0.1:443`\nwww.example.com/path https-proxy://127.0.0.1 # 默认端口 443\n\n# 将当前域名的所有请求代理到 HTTPS 代理: `127.0.0.1:8080`\nwww.example.com https-proxy://127.0.0.1:8080\n\n# 也可以用域名\nwww.example.com/path https-proxy://test.proxy.com # 默认端口 443\nwww.example.com https-proxy://test.proxy.com:8080\n```\n\n## 高级用法\n默认情况下，上游代理会自行解析请求的域名。但某些场景下，你可能希望强制代理直接访问指定的目标 IP（跳过 DNS 解析），例如：\n- 绕过 DNS 污染\n- 直接访问特定后端 IP\n- 测试不同环境的服务\n``` txt\n# 通过查询参数\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1:8080\n\n# 通过指令启用\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1 enable://proxyHost\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1:8080 enable://proxyHost\n```\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n## 注意事项\n`https-proxy` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com https-proxy://127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求代理到 `127.0.0.1:1234`，但由于 `https-proxy` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `https-proxy` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com https-proxy://127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `https-proxy` 规则匹配，最终代理到 `127.0.0.1:1234`。\n","isInternal":false,"tokens":994,"sizeBytes":2938},{"name":"https.md","path":"docs/docs/rules/https.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/https.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# https\n将以下三种请求转换为 HTTPS 请求（服务端将收到转换后的 HTTPS URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern https://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 https://www.test.com/path/xxx\nhttps://www.example.com/path2 https://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n     | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n     | ----------------------------------------- | ----------------------------------------- |\n     | `http://www.example.com/path1`              | `https://www.test.com/path/xxx`             |\n     | `http://www.example.com/path1/a/b/c?query`  | `https://www.test.com/path/xxx/a/b/c?query` |\n     | `https://www.example.com/path2`            | `https://www.abc.com/path3/yyy`             |\n     | `https://www.example.com/path2/a/b/c?query` | `https://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 https://<www.test.com/path/xxx>\n    # www.example.com/path1 https://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `https://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 https://www.test.com/path/xxx\nwss://www.example.com/path2 https://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `wss://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `wss://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 https://www.test.com:123\ntunnel://www.example2.com:443 https://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:443`|\n\n⚠️ 自动忽略匹配 URL 的路径，HTTPS 协议默认端口为 `443`\n\n","isInternal":false,"tokens":1003,"sizeBytes":3611},{"name":"ignore.md","path":"docs/docs/rules/ignore.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/ignore.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# ignore\n用于忽略指定协议的匹配规则，也可以忽略当前配置的匹配规则。\n\n## 规则语法\n``` txt\npattern ignore://p1|p2|... [filters...]\n# 等效于：\npattern ignore://p1 ignore://p2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作协议（`protocol`）名称<br/>`*` 表示所以协议<br/>`-p` 表示排除 `p`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作协议列表](./protocols)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 忽略所有规则，只保留 `file` 协议的规则\nwww.example.com/path ignore://*|-file\n\n# 忽略指定协议：file、host\nwww.example.com/path2 ignore://file|host\n```\n\n## 常见问题\n`ignore` 是根据指定协议忽略已匹配的规则，用这种方式忽略的规则不会继续匹配同类型的其他规则，如果想让请求忽略某个规则后再继续尝试匹配剩余的同类型规则可以采用 [skip](./skip)。\n\n","isInternal":false,"tokens":453,"sizeBytes":1398},{"name":"includeFilter.md","path":"docs/docs/rules/includeFilter.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/includeFilter.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# includeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation includeFilter://p1 includeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 及 `p1`、`p2`、... 中的一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求生效  \nwww.example.com/api/data proxy://127.0.0.1:8080 includeFilter://m:GET includeFilter://m:POST\n```\n\n可以跟 [excludeFilter](./excludeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n","isInternal":false,"tokens":327,"sizeBytes":1133},{"name":"inherit.md","path":"docs/docs/rules/inherit.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/inherit.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# 协议继承\n[http](./http)/[https](./https)/[ws](./ws)/[wss](./wss)/[tunnel](./tunnel) 都限制了目标 URL 的协议，Whistle 也支持根据请求 URL 的协议自动补全。\n## 规则语法\n``` txt\npattern //value [filters...]\n# 或\npattern value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 www.test.com/path/xxx\nhttps://www.example.com/path2 www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `http://www.example.com/path1`              | `http://www.test.com/path/xxx`             |\n    | `http://www.example.com/path1/a/b/c?query`  | `http://www.test.com/path/xxx/a/b/c?query` |\n    | `https://www.example.com/path2`            | `https://www.abc.com/path3/yyy`             |\n    | `https://www.example.com/path2/a/b/c?query` | `https://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 //<www.test.com/path/xxx>\n    # www.example.com/path1 //(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `[http/https/wss/ws]://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 www.test.com/path/xxx\nwss://www.example.com/path2 www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `ws://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `wss://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 //www.test.com:123\ntunnel://www.example2.com:443 www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:443`|\n\n","isInternal":false,"tokens":901,"sizeBytes":3267},{"name":"jsAppend.md","path":"docs/docs/rules/jsAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/jsAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# jsAppend\n在现有响应内容体后面插入指定内容（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsAppend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsAppend://(Hello) file://(-test-)\nwww.example.com/path2 jsAppend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsAppend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-<script>Hello</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-Hello`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsAppend://{body.txt} file://(-test-)\nwww.example.com/path2 jsAppend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsAppend://{body.txt}) file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-<script>Hello world.</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-Hello world.`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsAppend:///User/xxx/test.js\nwww.example.com/path2 jsAppend://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsAppend://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsAppend` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsAppend://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsAppend://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsAppend://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsAppend://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsAppend://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n","isInternal":false,"tokens":1727,"sizeBytes":5435},{"name":"jsBody.md","path":"docs/docs/rules/jsBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/jsBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# jsBody\n将现有响应内容体t替换成指定内容（（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsBody://(Hello) file://(-test-)\nwww.example.com/path2 jsBody://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsBody://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<script>Hello</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsBody://{body.txt} file://(-test-)\nwww.example.com/path2 jsBody://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<script>Hello world.</script>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello world.`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsBody:///User/xxx/test.js\nwww.example.com/path2 jsBody://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsBody://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsBody` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsBody://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsBody://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsBody://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsBody://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsBody://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n","isInternal":false,"tokens":1719,"sizeBytes":5377},{"name":"jsPrepend.md","path":"docs/docs/rules/jsPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/jsPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# jsPrepend\n在现有响应内容体前面插入指定内容（仅对响应类型 `content-type` 包含 `javascript`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern jsPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 jsPrepend://(Hello) file://(-test-)\nwww.example.com/path2 jsPrepend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 jsPrepend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <script>Hello</script>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 jsPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 jsPrepend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 jsPrepend://{body.txt}) file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <script>Hello world.</script>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `Hello world.-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 jsPrepend:///User/xxx/test.js\nwww.example.com/path2 jsPrepend://https://www.xxx.com/xxx/params.js\n# 通过编辑临时文件\nwww.example.com/path3 jsPrepend://temp/blank.js\n````\n\n## 为注入的脚本设置 `<script>` 标签属性\n\n通过 `jsPrepend` 注入到 HTML 页面的脚本，Whistle 会自动为其包裹 `<script>` 标签。若需要为该标签设置额外的属性，如 `nomodule`、`module`、`defer`、`async`、`crossorigin` 等，可以使用 `lineProps` 参数进行配置。\n\n```txt\nwww.example.com/path1 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://nomodule\nwww.example.com/path2 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://module\nwww.example.com/path3 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://defer\nwww.example.com/path4 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://async\nwww.example.com/path5 jsPrepend://https://www.xxx.com/xxx/params.js lineProps://crossorigin\n```\n\n### 属性说明与示例\n| 属性 | 用途说明 | 配置示例 |\n|------|----------|----------|\n| `nomodule` | 传统浏览器中执行，不支持 ES 模块的浏览器会运行此脚本 | `lineProps://nomodule` |\n| `module` | 声明为 ES 模块，支持模块化导入 | `lineProps://module` |\n| `defer` | 异步加载，在文档解析完成后执行 | `lineProps://defer` |\n| `async` | 异步加载，下载完成后立即执行 | `lineProps://async` |\n| `crossorigin`| 启用跨域资源共享（CORS）模式 | `lineProps://crossorigin` |\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n\n","isInternal":false,"tokens":1769,"sizeBytes":5540},{"name":"lineProps.md","path":"docs/docs/rules/lineProps.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/lineProps.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# lineProps\n通过规则启用 proxyHost、proxyTunnel、safeHtml 等功能。\n> 📌 与 [enable](./enable) 的区别：\n>\n> `enable` 是全局生效的配置\n>\n> `lineProps` 只对配置所在行的规则生效\n\n## 规则语法\n``` txt\npattern operation lineProps://action1|action2|... [filters...]\n\n# 等效于：\npattern operation lineProps://action1 lineProps://action2 ... [filters...]\n```\n> `lineProps` 不能单独作为 `operation` 使用，且只对同一行的 `operation` 生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令                          | [操作指令文档](./operation)   |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n- `important`：类似于 CSS 的 !important，用于提升规则优先级，且仅对同类协议的操作生效\n- `safeHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `{` 和 `[`（JSON 对象开头字符），如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `strictHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `<`，如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `disableAutoCors`：禁用 [file](./file) 协议替换请求时自动添加必要的 CORS (跨域资源共享) 头信息\n- `disableUserLogin`：禁用设置 [statusCode://401](./statusCode) 时显示登录框\n- `enableUserLogin`：设置 [statusCode://401](./statusCode) 是否显示登录框（默认显示，用于关闭 `disable.userLogin`）\n- `internal`：将 `proxy`、`socks`、`host` 协议掉规则同时作用于 Whistle 内部请求\n- `internalOnly`：将 `proxy`、`socks`、`host` 协议掉规则只作用于 Whistle 内部请求\n- `internalProxy`：利用 `proxy`、`socks` 等代理协议将请求转发至其他代理服务器（如另一 Whistle 实例）。启用此功能后，已在第一层代理解密的 HTTPS 请求将以明文形式在代理链中传输，从而上游代理可以直接获取明文数据\n- `proxyFirst`：优先使用 [proxy](./proxy) 规则（默认情况下，同时匹配 `host` 和 `proxy`，只有 `host` 生效）\n- `proxyHost`：[proxy](./proxy) 和 [host](./host) 同时生效\n- `proxyHostOnly`：功能同 `proxyHost`，但如果没有匹配 [host](./host) 则 [proxy](./proxy) 自动失效\n- `proxyTunnel`：跟 `proxyHost` 一同使用，让上游代理再次通过隧道代理到上上游的 HTTP 代理，详见下面的示例\n- `weakRule`：默认情况下，当配置了 [file](./file) 等协议时，[proxy](./proxy) 规则会自动失效。通过设置 `weakRule` 属性，可以提升 [proxy](./proxy) 规则的优先级，使其在上述场景中仍然生效\n- `enableBigData`：支持 [reqMerge](./reqMerge) 或 [resMerge](./resMerge) 匹配大的请求/响应内容\n\n## 配置示例\n#### 未使用 `lineProps://important`\n``` txt\nwww.example.com/path file:///User/xxx/important1.html\nwww.example.com/path file:///User/xxx/important2.html\n```\n访问 `https://www.example.com/path ` 将匹配 `file:///User/xxx/important1.html`\n\n#### 使用 `lineProps://important`\n``` txt\nwww.example.com/path file:///User/xxx/important1.html\nwww.example.com/path file:///User/xxx/important2.html lineProps://important\n```\n访问 `https://www.example.com/path ` 将匹配 `file:///User/xxx/important2.html`\n\n#### 注入文本\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) \nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path ` 返回响应内容：\n``` html\n<!DOCTYPE html>\n<style>alert(1)</style>\nalert(1)\n<script>alert(1)</script>test\n```\n\n#### 使用 `enable://strictHtml`\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) enable://strictHtml\nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path` 返回响应内容：\n``` html\ntest\n```\n> `enable://strictHtml` 对所有规则都生效\n\n### 使用 `lineProps://strictHtml`\n``` txt\nwww.example.com/path file://(test) resType://html\nwww.example.com/path htmlPrepend://(alert(1)) \nwww.example.com/path jsPrepend://(alert(1)) lineProps://strictHtml\nwww.example.com/path cssPrepend://(alert(1)) \n```\n访问 `https://www.example.com/path` 返回响应内容：\n``` html\n<!DOCTYPE html>\n<style>alert(1)</style>\nalert(1)test\n```\n> `lineProps://strictHtml` 只对所在行的规则生效\n","isInternal":false,"tokens":1620,"sizeBytes":5223},{"name":"locationHref.md","path":"docs/docs/rules/locationHref.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/locationHref.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# locationHref\n针对无法通过服务端重定向（`302`/`301`）的场景，通过在 HTML 页面中返回 JavaScript 代码 `window.location.href = targetUrl` 实现客户端跳转。特别适用于：\n- 本地HTML文件加载的APP页面\n- 单页应用(SPA)\n- 特殊框架开发的混合应用(Hybrid App)\n\n## 规则语法\n``` txt\npattern locationHref://targetUrl [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| targetUrl   | 重定向后的 URL，可以是相对路径 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 基础配置\n``` txt\nwww.example.com/path locationHref://https://www.qq.com\n\nwww.example.com/path2 locationHref://../abc/123\n```\n- 访问 `https://www.example.com/path/to` 重定向到 `https://www.qq.com`（不自动拼接路径）\n- 访问 `https://www.example.com/path2/to` 重定向到 `https://www.example.com/abc/123`\n\n#### 实现路径拼接\n``` txt\n# 通配符\n^www.example.com/path/*** locationHref://`https://www.example.com/$1`\n```\n- 访问 `https://www.example.com/path/to?query` 重定向到 `https://www.example.com/to?query`\n\n#### location.replace\n若需通过 `location.replace(targetUrl)` 实现无历史记录的页面跳转，可按以下格式配置：\n``` txt\nwww.example.com/path locationHref://replace:https://www.qq.com\n```\n> 跳转至目标 URL，且当前页面不会存入浏览器历史记录，用户无法通过“返回”按钮回到原页面\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","isInternal":false,"tokens":593,"sizeBytes":2066},{"name":"log.md","path":"docs/docs/rules/log.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/log.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# log 规则\n\n自动在页面中注入 JavaScript 代码，捕获 JavaScript 异常及 `console.xxx` 日志，并在 Whistle 管理界面中实时显示。\n\n## 功能概述\n- **异常监控**：自动捕获页面中的 JavaScript 执行错误和未处理的 Promise 异常。\n- **日志收集**：拦截并收集所有 `console.log()`、`console.warn()`、`console.error()` 等控制台输出。\n- **可视化查看**：在 Whistle 管理界面中按分组实时查看、筛选和搜索日志。\n- **日志预处理**：支持通过自定义脚本对日志内容进行过滤或格式化处理。\n\n## 规则语法\n```\npattern log://id [filters...]\n```\n\n### 参数说明\n| 参数     | 是否必填 | 描述                                                                 | 详细说明                                                                 |\n|----------|----------|----------------------------------------------------------------------|--------------------------------------------------------------------------|\n| pattern  | 是       | 匹配请求 URL 的表达式，用于指定需要监控的页面                         | [匹配模式语法](./pattern)                                                |\n| id       | 是       | 日志分组标识（普通字符串），用于在 Whistle 界面中区分和筛选不同来源的日志 | 同一分组下的日志会集中显示，支持按 ID 快速切换视图                       |\n| filters  | 否       | 过滤器条件，用于进一步匹配请求或响应的特定特征                         | [过滤器语法](./filters) <br> 支持按 URL、方法、请求头、响应状态码等过滤 |\n\n## 使用方法\n\n### 基础用法\n捕获指定域名下所有页面的日志：\n```\nwww.example.com log://myapp\n```\n\n### 多域名分组监控\n为不同域名或路径设置不同的日志分组，便于分类查看：\n```\nke.qq.com log://ke\nnews.qq.com log://news\napi.example.com log://api\n```\n\n## 高级配置：日志预处理\n\n通过 `jsPrepend` 规则注入自定义脚本，可在日志发送到 Whistle 前进行预处理，例如敏感信息脱敏、格式转换或条件过滤。\n\n### 配置示例\n```\nwww.example.com log:// jsPrepend://{handleLog.js}\n```\n\n### 预处理脚本示例\n```javascript\n// handleLog.js\n// 在日志发送到 Whistle 前执行的自定义处理函数\nwindow.onBeforeWhistleLogSend = function(result, level) {\n  // result: 待输出的原始信息数组\n  // level: 日志级别 ('log', 'warn', 'error', 'info', 'debug')\n  \n  result.forEach(function(msg, i) {\n    // 示例1：隐藏敏感关键词\n    if (typeof msg === 'string' && msg.includes('password')) {\n      result[i] = '[SENSITIVE DATA HIDDEN]';\n    }\n    \n    // 示例2：特定内容转换为结构化对象\n    if (msg === 'abc') {\n      result[i] = {\n        name: 'avenwu',\n        level: level,\n        timestamp: new Date().toISOString()\n      };\n    }\n    \n    // 示例3：过滤掉某些类型的日志\n    if (msg === 'ignore-this-message') {\n      // 返回空数组将阻止此条日志发送\n      result.splice(i, 1);\n    }\n  });\n};\n```\n\n## 界面操作指南\n\n### 查看日志\n1. 打开 Whistle 管理界面（默认为 `http://127.0.0.1:8899`）\n2. 切换到 **Network / Tools / Console** 标签页\n3. 在左上角选择对应的日志分组 ID\n4. 实时查看页面输出的日志和错误信息\n\n### 日志筛选\n- 使用顶部的筛选栏可按日志级别（Log/Warn/Error）快速过滤\n- 支持关键词搜索，快速定位特定日志\n","isInternal":false,"tokens":1023,"sizeBytes":3526},{"name":"method.md","path":"docs/docs/rules/method.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/method.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# method\n修改请求方法。\n\n## 规则语法\n``` txt\npattern method://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | `get`/`post`/`head` 等请求方法名称（不区分大小写） |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 访问 `https://www.example.com/path/to` 在 Whistle 抓包界面上可以看到请求方法为 `POST`\nwww.example.com/path method://post\n\n# 带过滤条件的方法修改\nwww.example.com/path2 method://patch includeFilter://reqH.content-type=multipart/form-data\n```\n","isInternal":false,"tokens":248,"sizeBytes":956},{"name":"operation.md","path":"docs/docs/rules/operation.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/operation.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# operation\n\n在 Whistle 中，每条规则由 匹配模式（`pattern`） 和 操作（`operation`） 两部分组成，其中 `operation` 的通用语法为：\n\n``` txt\nprotocol://[value]\n```\n- **protocol**：指定操作类型（如 `file`、`proxy`、`resReplace` 等）\n- **value**：操作内容（支持多种格式，见下文）\n\n## 内联值\n``` txt\npattern reqHeaders://x-proxy=Whistle   # 设置请求头\npattern statusCode://404               # 修改状态码\npattern file://({\"ec\":0})              # 用内联内容（小括号里面的值：`{\"ec\":0}`）响应请求\n```\n\n当操作内容（Value）包含空格、换行符或特殊字符时，无法直接使用内联方式（Inline），需改用以下方法：\n\n## 内嵌值\n\n```` txt\n``` ua.txt\nMozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1\n```\npattern ua://{ua.txt}\n````\n\n等价于\n\n```` txt\n``` headers.json\nuser-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1\n```\npattern reqHeaders://{headers.json}\n````\n\n## Values 引用\n\n当操作值（Value）需要被多个规则共享时，直接内嵌在规则中会导致无法复用。此时可以将这些值存储在 Whistle 界面的 Values 模块中，并通过键名引用：\n1. 在 Values 里面创建一个名为 `result.json` 的 key 后，填入操作内容\n2. 在规则里面即可通过 `{result.json}` 的方式引用，如：`www.test.com/cgi-bin/test file://{result.json}`\n\n<img src=\"/img//values-demo1.png\" width=\"420\" />\n\n## 文件/远程资源\n``` txt\npattern reqHeaders:///User/xxx/filepath             # 从本地文件加载操作内容\npattern resHeadrs://https://example.com/config.json # 从远处加载 JSON 对象\npattern resHeaders://temp/blank.json                # 通过边境临时文件\n```\n> ⚠️ 注意：http/https/ws/wss/tunnel/host/enable/cache... 等协议禁止通过文件路径或远程 URL 获取内容，详见各协议文档。\n\n## 临时文件\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern protocol://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `protocol://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n## 小括号用途\n在 Whistle 规则中，protocol://value 的 value 部分可能有三种间接引用类型：\n1. `{key}` - 引用内嵌值\n2. `remote-url` - 远程资源地址\n3. `localfilepath` - 本地文件路径\n\n当需要直接引用上述内容本身（而非它们间接引用内容）作为操作内容时，可以用小括号包裹：\n``` txt\nprotocol://(value)\n```\n\n示例：\n1. `reqHeaders:///User/xxx/yyy.txt` - 从本地文件 `/User/xxx/yyy.txt` 加载操作内容\n2. `reqHeaders://(/User/xxx/yyy.txt)` - 将 `/User/xxx/yyy.txt` 直接作为操作内容\n\n## 模板字符串\nWhistle 提供了类似 ES6 的模板字符串功能，允许您动态引用请求信息并应用到规则配置中。支持以下几种模板字符串：\n\n##### 一般内敛值\n``` txt\npattern protocol://`...${version}...`\n```\n\n##### 内嵌值或 Values 引用\n```` txt\n\n``` test.key\n...${reqId}...\n...${version}...\n```\npattern protocol://`{test.key}`\n````\n\n##### 小括号内容\n```` txt\npattern protocol://`(...${now}...)`\n````\n\n##### 字符串变量\n\n| 变量名称              | 取值                                                         |\n| --------------------- | ------------------------------------------------------------ |\n| `${now}`                 | Date.now()                                                   |\n| `${random}`              | Math.random()                                                |\n| `${randomUUID}`          | crypto.randomUUID()                      |\n| `${randomInt(n)}` 或 `${randomInt(n1-n2)}` | 从 [0, n] 或 [n1, n2] 取一个随机正整数 (Added in: v2.9.104)|\n| `${reqId}`               | Whistle 给每个请求分配的 ID                                  |\n| `${url.protocol}`        | url.parse(fullUrl).protocol                                  |\n| `${url.hostname}`        | url.parse(fullUrl).hostname                                  |\n| `${url.host}`            | url.parse(fullUrl).host                                      |\n| `${url.port}`            | url.parse(fullUrl).port                                      |\n| `${url.path}`            | url.parse(fullUrl).path                                      |\n| `${url.pathname}`        | url.parse(fullUrl).pathname                                  |\n| `${url.search}`          | url.parse(fullUrl).search                                    |\n| `${query.xxx}`           | 请求参数 `xxx` 的值                                          |\n| `${url}`                 | 请求完整 URL                                                 |\n| `${querystring}`         | url.parse(fullUrl).search \\|\\| '?'（不为空）                 |\n| `${searchstring}`        | url.parse(fullUrl).search \\|\\| '?'（不为空）                 |\n| `${method}`              | 请求方法                                                     |\n| `${reqHeaders.xxx}`      | 请求头字段 `xxx` 的值                                        |\n| `${resHeaders.xxx}`      | 响应头字段 `xxx` 的值                                        |\n| `${version}`             | Whistle 版本号                                               |\n| `${port}`                | Whitle 端口号                                                |\n| `${host}`                | Whistle 启动时监听的网卡 IP（默认为空）                      |\n| `${realPort}`            | Whistle 界面 Online 对话框显示的 port（一般为 Whistle 端口号） |\n| `${realHost}`            | Whistle 界面 Online 对话框显示的 host（一般为 Whistle 监听的网卡 IP） |\n| `${clientIp}`            | 客户端 IP                                                    |\n| `${clientPort}`          | 客户端端口                                                   |\n| `${serverIp}`            | 服务端 IP                                                    |\n| `${serverPort}`          | 服务端端口                                                   |\n| `${reqCookies.xxx}`      | 请求 cookie `xxx` 的值                                       |\n| `${resCookies.xxx}`      | 响应 cookie  `xxx` 的值                                      |\n| `${statusCode}`          | 响应状态码                                                   |\n| `${env.xxx}`             | process.env.xxx                                              |\n| `${whistle.plugin-name}` | `whistle.plugin-name://value`  或 `plugin-name://value` 的 `value` |\n\n> `${whistle.plugin-name}` 只在插件的内部规则才可能有值\n\n##### 示例\n\n````txt\n``` test.txt\nnow: ${now}\nrandom: ${random}\nrandomUUID: ${randomUUID}\nreqId: ${reqId}\nurl.protocol: ${url.protocol}\nurl.hostname: ${url.hostname}\nurl.host: ${url.host}\nurl.port: ${url.port}\nurl.path: ${url.path}\nurl.pathname: ${url.pathname}\nurl.search; ${url.search}\nquery: ${query.name}\nurl: ${url}\nquerystring: ${querystring}\nsearchstring: ${searchstring}\nmethod: ${method}\nreqHeaders.accept: ${reqHeaders.accept}\nresHeaders.content-type: ${resHeaders.content-type}\nversion: ${version}\nport: ${port}\nhost: ${host}\nrealPort: ${realPort}\nrealHost: ${realHost}\nclientIp: ${clientIp}\nclientPort: ${clientPort}\nserverIp: ${serverIp}\nserverPort: ${serverPort}\nreqCookies.test: ${reqCookies.test}\nresCookies.test: ${resCookies.test}\nstatusCode: ${statusCode}\nenv.USER: ${env.USER}\n```\n\nwww.test.com/index.html file://`{test.txt}`\n````\n\n访问 `https://www.test.com/index.html?name=avenwu` 则返回响应内容：\n\n``` txt\nnow: 1752301623295\nrandom: 0.6819241513880432\nrandomUUID: e917b9fc-e2ef-4255-9209-11eb417235c5\nreqId: 1752301623294-339\nurl.protocol: https:\nurl.hostname: www.test.com\nurl.host: www.test.com\nurl.port: \nurl.path: /index.html?name=avenwu\nurl.pathname: /index.html\nurl.search; ?name=avenwu\nquery: avenwu\nurl: https://www.test.com/index.html?name=avenwu\nquerystring: ?name=avenwu\nsearchstring: ?name=avenwu\nmethod: GET\nreqHeaders.accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\nresHeaders.content-type: \nversion: 2.9.100\nport: 8899\nhost: \nrealPort: 8899\nrealHost: \nclientIp: 127.0.0.1\nclientPort: 60582\nserverIp: \nserverPort: \nreqCookies.test: \nresCookies.test: \nstatusCode: \nenv.USER: av\n```\n\n## 数据对象\n操作内容除了文本或二进制内容，还有可能是 JSON 对象，Whistle 支持以下 3 种数据对象格式：\n\n#### JSON 格式\n``` js\n{\n  \"key1\": value1,\n  \"key2\": value2,\n  \"keyN\": valueN\n}\n```\n\n#### 行格式\n``` txt\nkey1: value1\nkey2:value2\nkeyN: valueN\n```\n> 以 `冒号+空格` 分隔，如果没有 `冒号+空格` ，则以第一个冒号分隔，如果没有冒号，则 `value` 为空字符串\n\n**多级嵌套：**\n``` txt\na.b.c: 123\nc\\.d\\.e: abc\n```\n等价于：\n``` json\n{\n  \"a\": {\n    \"b\": {\n      \"c\": 123\n    }\n  },\n  \"c.d.e\": \"abc\"\n}\n```\n\n#### 内联格式（请求参数格式）\n\n``` txt\nkey1=value1&key2=value2&keyN=valueN\n```\n> `key` 和 `value` 最好都 `encodeURIComponent`\n\n\n## 常见操作指令速查手册 {operation-manual}\n- [修改请求内容](#request)\n  - [修改请求方法](#method)\n  - [修改请求 URL](#url)\n  - [修改 HTTP 版本](#http-version)\n  - [修改请求头](#req-headers)\n  - [修改请求体](#req-body)\n- [修改响应内容](#response)\n  - [修改状态码](#status-code)\n  - [修改响应头](#res-headers)\n  - [修改响应体](#res-body)\n  - [修改尾部响应头](#trailers)\n- [修改连接过程](#connect)\n  - [修改 DNS](#dns)\n  - [设置代理](#proxy)\n  - [代理和 DNS 同时生效](#proxy-host)\n- [页面调试工具](#tools)\n  - [查看页面 DOM 结构](#weinre)\n  - [查看页面日志及错误信息](#log)\n\n---\n\n## 一、修改请求内容 {#request}\n\n### 1.1 修改请求方法 {#method}\n```txt\n# 基础语法\npattern method://新方法\n\n# 示例\nwww.example.com/path method://post\n```\n\n**数据来源支持：**\n- 直接指定：`method://get`\n- 内嵌值：`method://{keyOfEmbedded}`\n- Values配置：`method://{keyOfValues}`\n\n**注意事项：**\n- 方法名不区分大小写\n- 不支持从本地文件或远程URL获取\n\n**实用示例：**\n```txt\n# 示例1：将所有请求方法改为POST\nwww.example.com/path method://post\n\n# 示例2：仅将PUT请求改为POST\nwww.example.com/path method://post includeFilter://m:put\n\n# 示例3：根据请求体内容修改方法\nwww.example.com/api method://put includeFilter://b:cmdname=test\n```\n\n### 1.2 修改请求 URL {#url}\n#### URL映射\n```txt\nwww.example.com/path/to www.test.com/test\n```\n**映射效果：**\n- `https://www.example.com/path/to?query=abc` → `https://www.test.com/test?query=abc`\n- `https://www.example.com/path/to/subpage` → `https://www.test.com/test/subpage`\n- `wss://www.example.com/path/to/api` → `wss://www.test.com/test/api`\n\n#### 修改请求参数\n```txt\n# 新增/替换参数\npattern urlParams://({\"key\":\"value\"})\n\n# 删除参数\npattern delete://urlParams.paramName\n\n# 示例：修改参数并删除指定参数\nwww.example.com/api urlParams://({\"cmdname\":\"Test\"}) delete://urlParams.oldParam\n```\n\n#### 修改Path路径\n```txt\n# 正则替换\npattern pathReplace://({\"/old/ig\":\"new\"})\n\n# 关键字替换\npattern pathReplace://({\"old\":\"new\"})\n```\n\n**数据来源支持：**\n- 内联JSON\n- 内嵌值\n- Values配置\n- 本地文件\n- 远程URL\n\n### 1.3 修改 HTTP 版本 {#http-version}\n```txt\n# 强制使用普通HTTPS（禁用HTTP/2）\npattern disable://h2\n```\n> 默认会尝试使用HTTP/2建立连接，不支持时自动降级为HTTPS\n\n### 1.4 修改请求头 {#req-headers}\n```txt\n# 新增/替换请求头\npattern reqHeaders://({\"Header-Name\":\"value\"})\n\n# 删除请求头\npattern delete://reqHeaders.headerName\n\n# 示例\nwww.example.com reqHeaders://({\"X-Custom-Header\":\"test\"})\n```\n\n### 1.5 修改请求体 {#req-body}\n#### 合并修改（JSON/表单）\n```txt\npattern reqMerge://({\"newField\":\"value\"})\n```\n\n#### 文本替换\n```txt\n# 正则替换\npattern reqReplace://({\"/search/ig\":\"replace\"})\n\n# 关键字替换\npattern reqReplace://({\"search\":\"replace\"})\n```\n\n#### 完全替换\n```txt\npattern reqBody://(新内容)\n```\n\n#### 删除操作\n```txt\n# 删除特定字段\npattern delete://reqBody.fieldName\n\n# 删除整个请求体\npattern delete://reqBody\n```\n\n**数据来源支持：**\n- 内联值\n- 内嵌值\n- Values配置\n- 本地文件\n- 远程URL\n\n---\n\n## 二、修改响应内容 {#response}\n\n### 2.1 修改状态码 {#status-code}\n```txt\n# 替换现有响应状态码（请求会到达服务器）\npattern replaceStatus://500\n\n# 直接响应状态码（请求不发送到服务器）\npattern statusCode://500\n```\n\n### 2.2 修改响应头 {#res-headers}\n```txt\n# 新增/替换响应头\npattern resHeaders://({\"Header-Name\":\"value\"})\n\n# 删除响应头\npattern delete://resHeaders.headerName\n```\n\n### 2.3 修改响应体 {#res-body}\n#### 合并修改（JSON/JSONP）\n```txt\npattern resMerge://({\"newData\":\"value\"})\n```\n\n#### 文本替换\n```txt\npattern resReplace://({\"/old/ig\":\"new\"})\n```\n\n#### 替换响应体\n```txt\n# 替换服务器返回的内容\npattern resBody://(新内容)\n```\n\n#### 直接响应内容\n```txt\n# 请求不发送到服务器\npattern file://(直接返回的内容) resType://html\n```\n\n#### 删除操作\n```txt\n# 删除响应体字段\npattern delete://resBody.fieldName\n\n# 清空响应体\npattern delete://resBody\n```\n\n### 2.4 修改尾部响应头 {#trailers}\n> Trailers是在分块传输响应后发送的额外头部字段\n\n```txt\n# 新增/替换Trailers\npattern trailers://({\"Trailer-Name\":\"value\"})\n\n# 删除Trailers\npattern delete://trailers.trailerName\n```\n\n---\n\n## 三、修改连接过程 {#connect}\n\n### 3.1 修改 DNS {#dns}\n```txt\n# 设置IP地址\npattern 127.0.0.1\n\n# 设置IP和端口\npattern 127.0.0.1:8080\n\n# CNAME效果（指向其他主机）\npattern host://www.target.com:8080\n```\n\n### 3.2 设置代理 {#proxy}\n```txt\n# HTTP代理\npattern proxy://127.0.0.1:8080\n\n# SOCKS5代理\npattern socks://127.0.0.1:1080\n\n# 支持域名\npattern proxy://proxy.example.com:8080\n```\n\n### 3.3 代理和 DNS 同时生效 {#proxy-host}\n**优先级说明：**\n- 默认：host配置优先于proxy\n- 可调整：通过`lineProps://proxyHost`让两者同时生效\n\n```txt\n# 示例1：只生效host配置\npattern 127.0.0.1:8080 socks://10.1.1.1:1080\n\n# 示例2：只生效proxy配置\npattern 127.0.0.1:8080 socks://10.1.1.1:1080 ignore://host\n\n# 示例3：host和proxy同时生效\npattern 127.0.0.1:8080 socks://10.1.1.1:1080 lineProps://proxyHost\n```\n\n---\n\n## 四、页面调试工具 {#tools}\n\n### 4.1 查看页面 DOM 结构 {#weinre}\n```txt\npattern weinre://调试会话名称\n```\n> 详细用法参考：[weinre文档](./weinre)\n\n### 4.2 查看页面日志及错误信息 {#log}\n```txt\npattern log://日志会话名称\n```\n> 详细用法参考：[log文档](./log)\n\n---\n\n## 数据来源速查表\n\n| 操作类型 | 直接内联 | 内嵌值 | Values | 本地文件 | 远程URL |\n|---------|---------|--------|--------|----------|---------|\n| 请求方法 | ✓ | ✓ | ✓ | ✗ | ✗ |\n| URL参数 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 请求头 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 请求体 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 响应头 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| 响应体 | ✓ | ✓ | ✓ | ✓ | ✓ |\n| Trailers | ✓ | ✓ | ✓ | ✓ | ✓ |\n\n**语法示例：**\n```txt\n# 直接内联\nprotocol://({\"key\":\"value\"})\n\n# 内嵌值\nprotocol://{embeddedKey}\n\n# Values配置\nprotocol://{valuesKey}\n\n# 本地文件\nprotocol:///path/to/file.json\n\n# 远程URL\nprotocol://https://example.com/data.json\n```\n\n---\n\n## 常用过滤条件\n\n| 过滤器 | 说明 | 示例 |\n|--------|------|------|\n| `includeFilter://m:方法` | 按请求方法过滤 | `includeFilter://m:put` |\n| `includeFilter://b:内容` | 按请求体内容过滤 | `includeFilter://b:cmdname=test` |\n| 正则表达式 | 区分大小写匹配 | `/Test/` |\n\n> **提示：** 更多协议和高级用法请参考[完整协议列表](./protocols)\n","isInternal":false,"tokens":4843,"sizeBytes":16270},{"name":"pac.md","path":"docs/docs/rules/pac.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/pac.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# pac\n\nPAC（Proxy Auto-Config）是一种通过 JavaScript 脚本自动决定请求代理规则的机制，允许您基于 URL、域名、IP 等条件动态选择代理或直连。\n\n## 规则语法\n``` txt\npattern pac://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n```` txt\n# 内嵌 PAC 脚本\n``` test.pac\nfunction FindProxyForURL(url, host) {\n  // ...\n}\n```\nwww.example.com/path pac://{test.pac}\n\n# Values\nwww.example.com/path1 pac://{test2.pac}\n\n# 本地文件\nwww.example.com/path3 pac:///User/xxx/test.pac\n\n# 远程 PAC 脚本\n* pac://https://raw.githubusercontent.com/imweb/node-pac/master/test/scripts/normal.pac\n````\n\n## 高级用法\n将请求代理到上游代理后，默认情况下上游代理会根据请求的域名通过 DNS 获取服务器 IP 再继续请求，如果想让上游代理根据指定 IP及端口继续请求，可以这么处理：\n``` txt\nwww.example.com pac://https://xxx/path/normal.pac 1.1.1.1 enable://proxyHost\nwww.example.com pac:///User/xxx/test.pac 1.1.1.1:8080 enable://proxyHost\n```\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n\n\n## 注意事项\n`pac` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com pac://https://xxx/path/normal.pac\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望 PAC 脚本 `https://xxx/path/normal.pac` 作用于 `https://www.example.com/path`，但由于 `pac` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `pac` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com pac://https://xxx/path/normal.pac\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `pac` 规则匹配。\n","isInternal":false,"tokens":857,"sizeBytes":2727},{"name":"pathReplace.md","path":"docs/docs/rules/pathReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/pathReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# pathReplace\n提供类似 JavaScript 的 String.replace() 方法的能力，通过正则表达式或字符串匹配来动态修改 URL 的路径(`path`) 部分。\n\n> URL 结构：\n> ``` txt\n>   https://www.example.com:8080/path/to/resource?query=string\n>   \\___/   \\_____________/\\____/\\____________________________/\n>     |           |         |                 |             \n>   协议(scheme)  主机(host) 端口(path)       路径(path) \n> ```\n>\n> **路径部分**指 `path/to/resource?query=string` 即不包含开头的  `/`\n\n## 规则语法\n``` txt\npattern pathReplace://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作数据对象，支持从以下渠道获取：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nwww.example.com/path pathReplace://123=abc\n```\n访问 `https://www.example.com/path/123?test=123&value=123` 服务器收到的 URL：`https://www.example.com/path/abc?test=abc&value=abc`\n\n#### 替换多个\n\n```` txt\n``` test.json\ntest: name\n123: abc\n```\nwww.example.com/path2 pathReplace://{test.json}\n````\n访问 `https://www.example.com/path2/123?test=123&value=123` 服务器收到的 URL：`https://www.example.com/path2/abc?name=abc&value=abc`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 pathReplace:///User/xxx/test.json\nwww.example.com/path2 pathReplace://https://www.xxx.com/xxx/params.json\n# 通过编辑临时文件\nwww.example.com/path3 pathReplace://temp/blank.json\n````\n\n## 注意事项\n以下配置旨在删除指定路径片段：\n```txt\nwww.example.com/api/ pathReplace://(/api/=/)\n```\n\n**期望效果：**  \n将 `https://www.example.com/api/xxx` 中的 `/api/` 替换为 `/`，得到 `https://www.example.com/xxx`\n\n**实际问题：**  \nWhistle 会将 `/api/` 识别为正则表达式而非普通字符串，导致替换后产生多余斜杠：  \n`https://www.example.com///xxx`\n\n> 即使将 `/api/` 视为字符串，它也无法匹配 `api/xxx/...`，pathReplace 匹配的路径不包含开头的  `/`\n\n**解决方案：**  \n\n``` txt\nwww.example.com pathReplace://(/^api//=)\n```\n> Whistle 规则里面的正则不需要对 `/` 进行转义\n>\n> 新版 Whistle 也可以用 `delete://pathname.0` 删除上面 URL 的 `api/` 路径片段，详见 [delete://pathname.xxx](./delete)\n\n## 关联协议 {#related}\n1. 设置请求参数（`Set Param`）：[urlParams://(key=value)](./urlParams)\n2. 删除请求参数（`Delete Param`）：[delete://urlParams.xxx](./delete)\n3. 更灵活的修改请求参数的方式（`Modify Path (path/to?query)`）：[pathReplace](./pathReplace)\n4. 删除路径：[delete://pathname.xxx](./delete)\n\n","isInternal":false,"tokens":950,"sizeBytes":3156},{"name":"pattern.md","path":"docs/docs/rules/pattern.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/pattern.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# pattern\n\n`pattern` 是 Whistle 规则中匹配请求 URL 的表达式，支持 URL 片段/域名、通配符、正则表达式等多种匹配方式。\n\n## 请求 URL 类型\n\nWhistle 支持三种请求 URL 类型：\n\n| 类型 | 格式 | 示例 |\n| :--- | :--- | :--- |\n| **隧道代理** | `tunnel://domain:port` | `tunnel://www.test.com:443` |\n| **WebSocket** | `ws[s]://domain[:port]/[path/to[?query]]` | `wss://www.test.com/path?a=1&b=2`<br>`ws://www.example.com:8080/path` |\n| **普通 HTTP/HTTPS** | `http[s]://domain[:port]/[path/to][?query]` | `https://www.test.com/path`<br>`http://www.example.com/path?a=1&b=2` |\n\n> URL 中的 hash（即 `#` 及之后的内容）不会被发送到后台。Hash 仅在客户端（如浏览器）中生效，后台无法直接获取\n\n## URL 片段 {#url}\n\nURL 片段用于匹配请求的 URL，支持多种灵活的模式。以下按匹配规则分类说明。\n\n### 1. 域名匹配\n\n- **普通域名**：`example.com`  \n  匹配该域名下所有端口及协议（http/https）的请求。  \n  ✅ `https://example.com/path/to?query`  \n  ✅ `https://example.com:9090/path/to?query`\n\n- **带端口的域名**：`example.com:8080`  \n  仅匹配指定端口（8080）的请求。  \n  ✅ `https://example.com:8080/path/to?query`  \n  ❌ `https://example.com:9090/path/to?query`\n  ❌ `https://example.com/path/to?query`\n\n### 2. 协议匹配\n\n- **带协议的 URL**：`https://example.com/path/to`  \n  仅匹配相同协议的请求。\n\n- **不带协议的 URL**：`example.com/path/to` 或 `//example.com/path/to`  \n  匹配任意协议（http、https 等），路径匹配规则与带协议时相同（见下文）。\n\n### 3. 路径与查询参数匹配\n\n#### 3.1 不带查询参数的 URL\n\n模式：`https://example.com/path/to`  \n匹配规则：**路径前缀匹配**（以 `/` 为边界）。  \n- ✅ `https://example.com/path/to`  \n- ✅ `https://example.com/path/to/xxx?query`  \n- ❌ `https://example.com/path/toxxx`（`to` 后缺少 `/` 边界）\n\n#### 3.2 带查询参数的 URL\n\n模式：`https://example.com/path/to?xxx`  \n匹配规则：**路径必须完全相同**，且查询字符串以 `xxx` 为前缀。  \n- ✅ `https://example.com/path/to?xxx`  \n- ✅ `https://example.com/path/to?xxxyyy&zzzzz`  \n- ❌ `https://example.com/path/to/yyy?xxx`（路径不同）\n\n### 4. 精确匹配\n\n使用 `$` 前缀，提供更严格的匹配。\n\n- `$https://example.com/path/to`  \n  匹配**路径精确**（以 `/` 为边界），查询参数可任意。  \n  ✅ `https://example.com/path/to`  \n  ✅ `https://example.com/path/to?query`  \n  ❌ `https://example.com/path/to/xxx`\n\n- `$https://example.com/path/to?query`  \n  匹配**路径与查询参数均完全一致**。  \n  ✅ `https://example.com/path/to?query`  \n  ❌ `https://example.com/path/to?query=1`  \n  ❌ `https://example.com/path/to`\n\n- 不带协议的精确匹配：`$example.com/path/to`（效果同上，协议任意）\n\n### 5. 域名通配符{#domain-wildcard}\n\n在`域名:端口`部分使用 `*` 或 `**` 进行模糊匹配。\n\n- `*`：匹配 0 个或多个非 `.` 字符（相当于正则 `/[^/?.]*/`）。  \n  示例：`https://*.example.com/path/to`  \n  ✅ `https://www.example.com/path/to`  \n  ✅ `https://abc.example.com/path/to/xxx?query`\n  ❌ `https://a.b.example.com/path/to`\n\n- `**`：匹配 0 个或多个任意字符（除 `/` 和 `?` 外，相当于正则 `/[^/?]*/`）。  \n  示例：`https://**.example.com:8*/path/to`  \n  ✅ `https://foo-bar.example.com:8080/path/to`\n  ✅ `https://a.b.example.com:8888/path/to`\n\n- `***` 及以上：不推荐使用。\n\n> 注意：`*` 也是 URL 路径中的合法字符。若需在路径部分使用通配符，请参阅 [通配符匹配](#wildcard)。\n\n### 要点总结\n\n| 模式示例 | 匹配协议 | 匹配端口 | 路径匹配规则 | 查询参数匹配规则 |\n|---------|---------|---------|-------------|----------------|\n| `example.com` | 任意 | 任意 | - | - |\n| `example.com:8080` | 任意 | 8080 | - | - |\n| `https://example.com/path/to` | https | 任意 | 前缀（`/`边界） | 任意 |\n| `https://example.com/path/to?xxx` | https | 任意 | 精确 | 前缀 |\n| `$https://example.com/path/to` | https | 任意 | 精确 | 任意 |\n| `$https://example.com/path/to?xxx` | https | 任意 | 精确 | 精确 |\n| `example.com/path/to` <br/> 或 `//example.com/path/to` | 任意 | 任意 | 前缀（`/`边界） | 任意 |\n\n## 通配符匹配 {#wildcard}\n\n在 [URL 片段匹配](#url) 中，由于 `*` 本身是 URL 路径中的合法字符，因此无法直接在路径或查询参数中使用通配符。  \n若需在 **路径** 或 **查询参数** 中使用通配符，请在 URL 前加上 `^` 符号。此时 `*`、`**`、`***` 将作为通配符解析。\n\n### 1. 域名通配符\n\n语法与 [URL 片段中的域名通配符](#domain-wildcard) 完全一致：\n\n- `*`：匹配 0 个或多个非 `.` 字符（正则 `/[^/?.]*/`）\n- `**`：匹配 0 个或多个任意字符（除 `/` 和 `?` 外，正则 `/[^/?]*/`）\n\n**示例**：`^wss://*.example.com/path/to`\n\n✅ 匹配：\n- `wss://a.example.com/path/to`\n- `wss://b.example.com/path/to/xxx?query`\n\n❌ 不匹配：\n- `wss://a.example.com/path/toxxx`（路径缺少 `/` 边界）\n- `wss://a.b.example.com/path/to`（`*` 不匹配点号）\n\n### 2. 路径通配符\n\n作用于 URL 的 **路径部分**（域名后、`?` 之前）。支持三种级别：\n\n| 通配符 | 匹配规则（正则） | 说明 |\n|--------|----------------|------|\n| `*`    | `/[^?/]*/`      | 匹配当前路径段内的任意字符（不含 `/` 和 `?`） |\n| `**`   | `/[^?]*/`       | 匹配任意字符（不含 `?`），可跨路径段 |\n| `***`  | `/.*/`          | 匹配剩余所有字符（含 `/` 和 `?`），通常用于路径末尾 |\n\n**示例 1：`*`**  \n模式：`^https://example.com/path/to/a*b`  \n✅ 匹配：`https://example.com/path/to/axxxb/...?query`  \n❌ 不匹配：`https://example.com/path/to/a/b`（`*` 不能匹配 `/`）\n\n**示例 2：`**`**  \n模式：`^https://example.com/path/to/a**b`  \n✅ 匹配：\n- `https://example.com/path/to/axxxb/...?query`\n- `https://example.com/path/to/a/b`  \n❌ 不匹配：`https://example.com/path/to/a/xxxx?query=b`（`?` 后不属于路径）\n\n**示例 3：`***`**  \n模式：`^https://www.example*.com:8*/path/to/a***b`  \n✅ 匹配：\n- `https://example.com/path/to/axxxb/...?query`\n- `https://example.com/path/to/a/b`\n- `https://example.com/path/to/a/xxxx?query=b`（`***` 会匹配到 `?` 及之后）\n\n> `***` 会“吞掉”包括 `?` 在内的剩余字符，一般仅建议在路径末尾使用。\n\n\n### 3. 查询参数通配符\n\n作用于 URL 的 **查询字符串**（`?` 之后）。支持两种：\n\n| 通配符 | 匹配规则（正则） | 说明 |\n|--------|----------------|------|\n| `*`    | `/[^&]*/`       | 匹配单个参数值内的任意字符（不含 `&`） |\n| `**`   | `/.*/`          | 匹配剩余所有字符（含 `&`），即从当前位置到末尾 |\n\n**示例 1：`*`**  \n模式：`^https://example.com/path/to?query=a*b`  \n✅ 匹配：`https://example.com/path/to?query=ab&q2=xxx`  \n❌ 不匹配：`https://example.com/path/to?query=a&q2=b`（`b` 前无额外字符）\n\n**示例 2：`**`**  \n模式：`^https://example.com/path/to?query=a**b`  \n✅ 匹配：\n- `https://example.com/path/to?query=axxxb&q2=xxx`\n- `https://example.com/path/to?query=a&q2=b`（`**` 匹配了 `&q2=`）\n\n### 4. 不带协议的精确匹配\n\n在 `^` 后直接写域名和路径，协议任意。\n\n**示例**：`^example*.com/path*/to`  \n效果与带协议相同，但可匹配 `http://`、`https://`、`wss://` 等任意协议。\n\n### 5. 精确匹配（边界控制）\n\n在模式末尾使用 `$`，表示匹配必须到此结束（之后不能再有额外字符）。\n\n**示例**：`^https://*.example.com/path/*/to$`  \n✅ 匹配：`https://a.example.com/path/xxx/to`  \n❌ 不匹配：`https://b.example.com/path/xxx/to?query`（`$` 禁止 `?query`）\n\n> 若不加 `$`，模式默认允许路径之后继续存在 `/`、`?` 等字符。\n\n### 使用总结\n\n| 使用场景 | 前缀 | 通配符类型 | 支持的通配符 | 示例 |\n|---------|------|-----------|-------------|------|\n| 普通 URL 匹配（无通配符） | 无 | — | — | `https://example.com/path` |\n| 含通配符的路径/参数 | `^` | 域名、路径、查询 | `*` `**` `***` | `^https://*.example.com/path/*?a**b` |\n| 精确匹配（尾部边界） | `^` + `$` | 同上 | 同上 | `^https://example.com/path/*/to$` |\n\n> **注意**：一旦使用 `^`，URL 中的 `*` 不再作为普通字符，而是通配符。如需匹配字面意义上的 `*`，请勿使用 `^`。\n\n## 正则匹配 {#regexp}\n\n除了前面介绍的 URL 片段匹配和通配符匹配外，Whistle 还提供了完整的正则表达式支持。  \n正则匹配的语法与 JavaScript 正则表达式完全兼容：\n\n```txt\n/pattern/[i]\n```\n\n### 参数说明\n\n- **`pattern`**：正则表达式主体\n- **`i`**（可选）：忽略大小写 \n\n### 使用示例\n\n```txt\n/\\.test\\./              # 匹配字符串中的 \".test.\"\n/key=value/i            # 忽略大小写匹配 \"key=value\" 或 \"KEY=VALUE\"\n```\n\n\n## 子匹配传值\n\n在 Whistle 规则中，可以通过 `$0`、`$1` … `$9` 引用通配符或正则表达式捕获的子匹配内容，并将其传递到目标操作值中。\n\n```txt\npattern protocol://$0_$1_$2_..._$9\n```\n\n**捕获变量说明**：\n\n| 变量 | 含义 |\n|------|------|\n| `$0` | 完整匹配的内容 |\n| `$1` ~ `$9` | 第 1 至第 9 个捕获组匹配的内容 |\n\n### 通配符匹配传值\n\n使用 `^` 前缀的通配符模式（`*`、`**` 等）会自动按顺序捕获匹配段，分别赋给 `$1`、`$2`……。\n\n**示例**：\n```txt\n^http://*.example.com/v0/users/** file:///User/xxx/$1/$2\n```\n\n- 请求 URL：`http://www.example.com/v2/users/alice/test.html?q=1`\n- 匹配过程：\n  - `$1` = `www`（匹配第一个 `*`）\n  - `$2` = `users/alice`（匹配 `**` 捕获的路径段）\n- 最终效果：返回本地文件 `/User/xxx/www/alice/test.html` 的内容\n\n> 通配符捕获规则：\n> - `*` 捕获单个路径段（不含 `/`）\n> - `**` 捕获剩余路径（可含 `/`）\n> - 按从左到右顺序依次对应 `$1`、`$2`……\n\n### 正则匹配传值\n\n使用 `/pattern/flags` 正则表达式时，捕获组 `( )` 中的内容会按顺序赋给 `$1`、`$2`……\n\n**示例**：\n```txt\n/regexp\\/(user|admin)\\/(\\d+)/ reqHeaders://X-Type=$1&X-ID=$2\n```\n\n- 请求 URL：`.../regexp/admin/123`\n- 捕获结果：\n  - `$1` = `admin`\n  - `$2` = `123`\n- 最终效果：为请求添加请求头 `X-Type: admin` 和 `X-ID: 123`\n\n---\n\n## 路径自动拼接规则\n\n当使用 **域名匹配** 或 **路径匹配** 映射到本地文件/目录或远程 URL 时，Whistle 会自动拼接原请求的剩余路径。\n\n**规则**：\n```txt\nhttps://*.example.com/path/to https://www.test.com/test\nwww.example.com file:///Usr/test\n```\n\n**效果说明**：\n\n| 请求 URL | 映射后目标 |\n|----------|------------|\n| `https://abc.example.com/path/to/x/y/z?query` | `https://www.test.com/test/x/y/z?query` |\n| `https://www.example.com/path/to/index.html?query` | 本地文件 `/Usr/test/path/to/index.html`（自动去除 query） |\n\n> 注意：查询参数 `?query` 在映射到本地文件时会被自动忽略（文件系统路径不支持），但映射到远程 URL 时会保留。\n\n---\n\n## 配置示例\n\n### 基础匹配\n\n```txt\n# 精确域名匹配\napi.example.com proxy://127.0.0.1:3000\n\n# 指定端口的域名匹配\nwww.example.com:8080 file:///local/dev\n\n# 路径匹配（映射到本地 JSON 数据）\nwww.example.com/api/users file://{user-data}\n```\n\n### 通配符匹配\n\n```txt\n# 匹配所有子域名\n**.example.com proxy://127.0.0.1:8080\n\n# 匹配特定前缀的子域名\ndev-**.example.com file:///(dev-mock)\n```\n\n### 正则匹配\n\n```txt\n# 匹配数字 ID 的用户页面\n/^https?://www\\.example\\.com/user/(\\d+)/ file://(user-$1)\n\n# 忽略大小写匹配特定路径\n/\\/api\\/v1\\/data/i resBody://({\"version\":\"v1\"})\n\n# 匹配静态资源文件并设置缓存\n/\\.(jpg|png|gif|css|js)$/i cache://86400\n```\n\n### 复杂匹配\n\n```txt\n# 通配符 + 路径捕获传值\n^https://**.example.com/api/*/v*/users reqHeaders://x-api-version=$3\n\n# 按请求方法区分处理\nwww.example.com/api file://({\"status\":\"ok\"}) includeFilter://m:GET\nwww.example.com/api file://({\"status\":\"created\"}) includeFilter://m:POST\n```\n\n---\n\n## 故障排除\n\n### Q：规则没有匹配到请求\n\n**可能原因及解决方案**：\n\n1. **URL 格式不正确**  \n   → 确保规则中的协议（http/https/ws/wss）与请求一致，或使用不带协议的写法（`example.com/path`）。\n\n2. **端口号不匹配**  \n   → 如果请求使用非标准端口，需要在规则中明确指定端口（如 `example.com:8080`）。\n\n3. **通配符或正则表达式有误**  \n   → 使用 [Whistle 在线调试工具](https://github.com/avwo/whistle) 或本地日志验证匹配情况。\n\n4. **规则优先级问题**  \n   → 更具体的规则应写在前面，或使用 `$` 精确匹配提高优先级。\n\n### Q：正则表达式不生效\n\n**检查清单**：\n\n- [ ] 正则表达式是否以 `/` 开头和结尾，如 `/pattern/`？\n- [ ] 特殊字符是否已转义（如 `.` 需写成 `\\.`，`/` 需写成 `\\/`）？\n- [ ] 标志（`i` 等）是否正确添加？\n- [ ] 正则是否匹配完整的 URL？可以在控制台用 `new RegExp('pattern', 'flags').test(url)` 验证。\n\n### Q：子匹配传值不正确\n\n**常见问题**：\n\n1. **捕获组编号错误**  \n   → `$1` 对应第一个 `( )`，`$2` 对应第二个，依次类推。注意嵌套捕获组的顺序。\n\n2. **通配符捕获不符合预期**  \n   → `*` 不匹配点号（`.`）和斜杠（`/`），`**` 匹配任意字符（除 `?` 外）。  \n   → 示例：`*.example.com` 无法匹配 `a.b.example.com`，需改用 `**.example.com`。\n\n3. **正则捕获组未按预期工作**  \n   → 使用 `(?:)` 表示非捕获组，避免干扰编号。  \n   → 使用在线正则测试工具（如 regex101.com）验证捕获内容。\n\n### Q：映射到本地文件时路径错误\n\n- 使用 `file://` 协议时，确保路径是绝对路径（如 `file:///User/xxx`）。\n- 路径拼接遵循自动拼接规则，请求的剩余路径会追加到目标路径后。\n- 查询参数会被自动忽略，不影响文件读取。\n\n---\n\n## 扩展阅读\n\n- [规则语法文档](./rule)：了解完整的规则语法结构\n- [操作指令文档](./operation)：学习如何配置操作指令\n- [过滤器文档](./filters)：了解如何精确控制规则生效条件\n","isInternal":false,"tokens":5354,"sizeBytes":14572},{"name":"pipe.md","path":"docs/docs/rules/pipe.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/pipe.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# pipe\n将 HTTP/HTTPS/WebSocket/TUNNEL 请求/响应数据流交给插件处理。\n\n## 规则语法\n``` txt\npattern pipe://plugin-name(pipeValue) [filters...]\n```\n> `(pipeValue)` 可选，在插件 Hook 可以通过 `req.originalReq.pipeValue` 获取\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| plugin-name(pipeValue)   | 插件名称 + 可选参数 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\ntunnel://wwww.example.com pipe://test\ntunnel://test-tunnel.example.com pipe://test-pipe-tunnel(abc)\n\nwss://test-ws.example.com/path pipe://test-pipe-ws\n\nhttps://www.example.com/path pipe://test-pipe-http(123)\n\n```\n\n具体用法参考：[插件开发文档](../extensions/dev.md#pipe)\n","isInternal":false,"tokens":300,"sizeBytes":1105},{"name":"plugin-vars.md","path":"docs/docs/rules/plugin-vars.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/plugin-vars.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# `%` 符号用法\n\n通过插件管理界面可直观地配置各项参数，例如可以让 [whistle.autosave](https://github.com/whistle-plugins/whistle.autosave) 插件支持以下配置：\n- 启用存储抓包数据\n- 配置存储抓包数据目录\n\n除管理界面外，您还可以直接在规则文件中使用 `%` 符号快速配置。\n\n## 全局配置（对所有请求生效）\n``` txt\n%autosave=123\n%autosave.enableAutoSave=true\n%autosave.storageDir=/User/xxx/test/sessions\n```\n\n## 精细化配置（针对特定请求）\n``` txt\nwww.test.com/api %autosave=abc\nwww.test.com/api %autosave.enableAutoSave=false [filters...]\nwww.test.com/api %autosave.storageDir= [filters...]\n```\n\n## 获取插件变量值\n在插件的 Hooks 里面可以通过以下代码获取到配置的变量列表：\n\n``` js\nreq.originalReq.globalPluginVars; // 全局变量，如 ['123', 'enableAutoSave=true', 'storageDir=/User/xxx/test/sessions']\nreq.originalReq.pluginVars; // 精细化配置的变量，如 ['abc', 'enableAutoSave=false', 'storageDir=']\n```\n\n## 开启规则自动提示\n如果通过插件变量的配置项是固定的，可以通过插件的 `package.json` 里面的 `whistleConfig` 字段配置可选项，这样在规则中可以自动提醒：\n\n#### 简单提示\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": true,\n  ...\n}\n```\n完成上述配置后，在 Whistle 的 Rules 编辑器中输入 % 字符时，将自动提示以下格式的插件变量：\n``` txt\n%test-plugin-vars=\n```\n\n#### 匿名 Key 值提示\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        \"test1\",\n        \"test2\",\n        \"test3\"\n      ]\n    }\n  },\n  ...\n}\n```\n\n<img src=\"/img/plugin-vars-hint-list.png\" width=\"260\" />\n\n值和显示内容分离：\n\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        {\n          \"text\": \"test1\",\n          \"displayText\": \"displayText1\"\n        },\n       {\n          \"text\": \"test2\",\n          \"displayText\": \"displayText2\"\n        },\n        {\n          \"text\": \"test3\",\n          \"displayText\": \"displayText3\",\n          \"help\": \"https://www.example.com/path\"\n        }\n      ]\n    }\n  }\n  ...\n}\n```\n> `displayText` 和 `help` 都是可选，配置 `help: 帮助链接` 时，当选中该提醒时，键盘按 `F1` 键自动打开该帮助链接\n\n<img src=\"/img/plugin-vars-display-list.png\" width=\"220\" />\n\n#### 设置 Key 名称\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintList\": [\n        {\n          \"text\": \"test1\",\n          \"displayText\": \"displayText1\"\n        },\n       {\n          \"text\": \"test2\",\n          \"displayText\": \"displayText2\"\n        },\n        {\n          \"text\": \"test3\",\n          \"displayText\": \"displayText3\",\n          \"help\": \"https://www.example.com/path\"\n        }\n      ],\n      \"hintSuffix\": [\n        \"=\",\n        \".key1=123\",\n        \".key2\"\n      ]\n    }\n  }\n  ...\n}\n```\n<img src=\"/img/plugin-vars-key-hint.png\" width=\"300\" />\n\n选中 `%test-plugin-vars=` 后会自动显示 `hintList` 的内容：\n\n<img src=\"/img/plugin-vars-display-list.png\" width=\"220\" />\n\n#### 借助后台接口\n\n``` js\n{\n  \"name\": \"@scope/whistle.test-plugin-vars\",\n  ...\n  \"whistleConfig\": {\n    \"pluginVars\": {\n      \"hintSuffix\": [\n        \"=\",\n        \".key1=123\",\n        \".key2\"\n      ],\n      \"hintUrl\": \"/cgi-bin/plugin-vars\"\n    }\n  }\n  ...\n}\n```\n> `hintList` 和 `hintUrl` 是互斥的，同时只能使用其中一个，`hintSuffix` 可选\n\n<img src=\"/img/plugin-vars-hint-url1.png\" width=\"360\" />\n\n<img src=\"/img/plugin-vars-hint-url2.png\" width=\"360\" />\n\n`/cgi-bin/plugin-vars` 的实现参考：[插件开发文档](../extensions/dev#rules-hint)\n","isInternal":false,"tokens":1152,"sizeBytes":3861},{"name":"protocols.md","path":"docs/docs/rules/protocols.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/protocols.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# 操作协议列表\n\n## 特殊规则\n1. [@（引入远程规则或设置客户端证书）](./@)\n2. [%（设置插件的变量值）](./plugin-vars)\n\n## Map Local（用本地文件替换响应内容）\n1. [file](./file)\n2. [xfile](./xfile)\n3. [tpl](./tpl)\n4. [xtpl](./xtpl)\n5. [rawfile](./rawfile)\n6. [xrawfile](./xrawfile)\n\n## Map Remote（返回其它 URL 的数据）\n1. [https](./https)\n2. [http](./http)\n3. [wss](./wss)\n4. [ws](./ws)\n5. [tunnel](./tunnel)\n\n## DNS Spoofing（修改请求 IP 地址）\n1. [host](./host)\n2. [xhost](./xhost)\n3. [proxy (http-proxy)](./proxy)\n4. [xproxy (xhttp-proxy)](./xproxy)\n5. [https-proxy](./https-proxy)\n6. [xhttps-proxy](./xhttps-proxy)\n7. [socks](./socks)\n8.  [xsocks](./xsocks)\n9.  [pac](./pac)\n\n## Rewrite Request（修改请求数据）\n1. [urlParams](./urlParams)\n2. [pathReplace](./pathReplace)\n3. [sniCallback](./sniCallback)\n4. [method](./method)\n5. [tlsOptions](./cipher)\n6. [reqHeaders](./reqHeaders)\n7. [forwardedFor](./forwardedFor)\n8. [ua](./ua)\n9. [auth](./auth)\n10. [cache](./cache)\n11. [referer](./referer)\n12. [attachment](./attachment)\n13. [reqCharset](./reqCharset)\n14. [reqCookies](./reqCookies)\n15. [reqCors](./reqCors)\n16. [reqType](./reqType)\n17. [reqBody](./reqBody)\n18. [reqMerge](./reqMerge)\n19. [reqPrepend](./reqPrepend)\n20. [reqAppend](./reqAppend)\n21. [reqReplace](./reqReplace)\n22. [reqWrite](./reqWrite)\n23. [reqWriteRaw](./reqWriteRaw)\n24. [reqRules](./reqRules)\n25. [reqScript](./reqScript)\n\n## Rewrite Response（修改响应数据）\n1. [statusCode](./statusCode)\n2. [replaceStatus](./replaceStatus)\n3. [redirect](./redirect)\n4. [locationHref](./locationHref)\n5. [resHeaders](./resHeaders)\n6. [responseFor](./responseFor)\n7. [resCharset](./resCharset)\n8. [resCookies](./resCookies)\n9. [resCors](./resCors)\n10. [resType](./resType)\n11. [resBody](./resBody)\n12. [resMerge](./resMerge)\n13. [resPrepend](./resPrepend)\n14. [resAppend](./resAppend)\n15. [resReplace](./resReplace)\n16. [htmlPrepend](./htmlPrepend)\n17. [htmlBody](./htmlBody)\n18. [htmlAppend](./htmlAppend)\n19. [cssPrepend](./cssPrepend)\n20. [cssBody](./cssBody)\n21. [cssAppend](./cssAppend)\n22. [jsPrepend](./jsPrepend)\n23. [jsBody](./jsBody)\n24. [jsAppend](./jsAppend)\n25. [trailers](./trailers)\n26. [resWrite](./resWrite)\n27. [resWriteRaw](./resWriteRaw)\n28. [resRules](./resRules)\n29. [resScript](./resScript)\n\n## General（请求/响应通用协议）\n1. [pipe](./pipe)\n2. [delete](./delete)\n3. [headerReplace](./headerReplace)\n\n## Throttle（限制流量速度）\n1. [reqDelay](./reqDelay)\n2. [resDelay](./resDelay)\n3. [reqSpeed](./reqSpeed)\n4. [resSpeed](./resSpeed)\n\n## Tools（调试工具）\n1. [weinre](./weinre)\n2. [log](./log)\n\n## Settings（特殊设置）\n1. [style](./style)\n2. [lineProps](./lineProps)\n3. [enable](./enable)\n4. [disable](./disable)\n\n## Filters（规则过滤器）\n1. [ignore](./ignore)\n2. [skip](./skip)\n3. [excludeFilter](./excludeFilter)\n4. [includeFilter](./includeFilter)\n","isInternal":false,"tokens":1065,"sizeBytes":2947},{"name":"proxy.md","path":"docs/docs/rules/proxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/proxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# proxy (http-proxy)\n`proxy`（或 `http-proxy`）指令用于将匹配的请求通过指定的 HTTP 代理服务器转发。两个指令名称功能完全一致，可互换使用。\n\n## 规则语法\n``` txt\npattern proxy://ipOrDomain[:port] [filters...]\n# 等效写法：\npattern http-proxy://ipOrDomain[:port] [filters...]\n```\n> `port` 可选，不填则使用默认端口 `80`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 将请求代理到 HTTP PROXY: `127.0.0.1:80`\nwww.example.com/path proxy://127.0.0.1 # 默认端口 80\n\n# 将当前域名的所有请求代理到 HTTP PROXY: `127.0.0.1:8080`\nwww.example.com proxy://127.0.0.1:8080\n\n# 也可以用域名\nwww.example.com/path proxy://test.proxy.com # 默认端口 80\nwww.example.com proxy://test.proxy.com:8080\n```\n\n## 高级用法\n将请求代理到上游代理后，默认情况下上游代理会根据请求的域名通过 DNS 获取服务器 IP 再继续请求，如果想让上游代理根据指定 IP及端口继续请求，可以这么处理：\n``` txt\n# 通过查询参数\nwww.example.com proxy://127.0.0.1:8080?host=1.1.1.1\nwww.example.com proxy://127.0.0.1:8080?host=1.1.1.1:8080\n\n# 通过指令启用\nwww.example.com proxy://127.0.0.1:8080 1.1.1.1 enable://proxyHost\nwww.example.com proxy://127.0.0.1:8080 1.1.1.1:8080 enable://proxyHost\n````\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n## 与 host 的匹配优先级\n\n#### 默认行为\n\n当请求同时匹配 `host` 和 `proxy` 规则时：\n- 仅 `host` 规则生效\n- `proxy` 规则自动忽略\n\n#### 修改优先级\n| 配置方式 | 语法 | 效果 |\n|---------|------|------|\n| **优先 proxy** | [`enable://proxyFirst`](./enable) 或 [`lineProps://proxyFirst`](./lineProps) | 仅 `proxy` 生效（覆盖 host） |\n| **同时生效** | [`enable://proxyHost`](./enable) 或 [`lineProps://proxyHost`](./lineProps) | `proxy` 和 `host` 同时生效 |\n\n#### 使用建议\n- 大多数场景使用默认行为即可\n- 需要特殊代理逻辑时才使用 `proxyFirst`\n- 需要双重匹配时使用 `proxyHost`\n\n## 注意事项\n`proxy` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com proxy://127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求代理到 `127.0.0.1:1234`，但由于 `proxy` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `proxy` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com proxy://127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `proxy` 规则匹配，最终代理到 `127.0.0.1:1234`。\n","isInternal":false,"tokens":1231,"sizeBytes":3623},{"name":"rawfile.md","path":"docs/docs/rules/rawfile.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/rawfile.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# rawfile\nrawfile 是 [file](./file) 的增强版本，除了支持 [file](./file) 的所有功能外，还允许在文件中定义完整的 HTTP 响应，包括：\n- 响应状态码\n- 响应头部\n- 响应内容\n\n## 规则语法\n``` txt\npattern rawfile://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## Mock 接口\n1. 本地文件 `/User/xxx/raw.txt`\n    ``` js\n    HTTP/1.1 500 OK\n    Content-Type: application/json\n    X-Custom-Header: value\n\n    {\n      \"status\": \"success\",\n      \"data\": \"your content here\"\n    }\n    ```\n2. 配置规则\n    ``` txt\n    https://www.example.com/test/rawfile tpl:///User/xxx/raw.txt\n\n    # 等价于\n    #  https://www.example.com/test/rawfile file:///User/xxx/test.json replaceStatus://500 resType://json resHeaders://x-custom-header=value\n    \n    # 支持远程 URL\n    # pattern rawfile://https://example.com/raw.json\n    ```\n3. 请求 `https://www.example.com/test/rawfile` 返回结果：\n    ``` txt\n    // 状态码\n    500\n\n    // 响应头\n    content-type: application/json\n    x-custom-header: value\n    content-length: 56\n\n    // 响应内容\n    {\n      \"status\": \"success\",\n      \"data\": \"your content here\"\n    }\n    ```\n\n其它功能参考：[file](./file)\n","isInternal":false,"tokens":529,"sizeBytes":1842},{"name":"redirect.md","path":"docs/docs/rules/redirect.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/redirect.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# redirect\n将匹配的请求立即重定向（302 Found）到指定URL，不请求到后台服务器。\n\n## 规则语法\n``` txt\npattern redirect://targetUrl [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| targetUrl   | 重定向后的 URL，可以是相对路径 |    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 基础配置\n``` txt\nwww.example.com/path redirect://https://www.qq.com\n\nwww.example.com/path2 redirect://../abc/123\n```\n- 访问 `https://www.example.com/path/to` 重定向到 `https://www.qq.com`（不自动拼接路径）\n- 访问 `https://www.example.com/path2/to` 重定向到 `https://www.example.com/abc/123`\n\n#### 实现路径拼接\n``` txt\n# 通配符\n^www.example.com/path/*** redirect://`https://www.example.com/$1`\n```\n- 访问 `https://www.example.com/path/to?query` 重定向到 `https://www.example.com/to?query`\n\n#### `301` 跳转\n``` txt\nwww.example.com/path/test redirect://https://www.qq.com replaceStatus://301\n```\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","isInternal":false,"tokens":454,"sizeBytes":1615},{"name":"referer.md","path":"docs/docs/rules/referer.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/referer.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# referer\n修改请求头的 `referer` 字段，有些服务器会校验请求头的 `referer` 字段，这个协议可以用来绕过这个检测或者测试后台的功能。\n\n## 规则语法\n``` txt\npattern referer://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 完整 URL 链接<br/>• 内联/内嵌/Values内容<br/>⚠️ 不支持从文件/远程 URL 加载数据 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nwww.example.com/path referer://https://www.test.com/x/y/z?xxx\n```\n\n## 关联协议\n1. 等价于：[reqHeaders://referer=https://xxx](./reqHeaders)\n","isInternal":false,"tokens":287,"sizeBytes":1019},{"name":"replaceStatus.md","path":"docs/docs/rules/replaceStatus.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/replaceStatus.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# replaceStatus\n\n`replaceStatus` 协议用于替换 HTTP 状态码，在请求响应完成后将原始状态码替换成指定的 HTTP 状态码。通过 `replaceStatus` 协议，你可以：\n- **修改服务器响应的状态码**：基于真实服务器响应进行状态码替换\n- **测试不同状态码的处理逻辑**：在不修改后端代码的情况下测试各种状态码场景\n- **错误场景模拟**：将正常响应转换为错误状态码，测试客户端容错能力\n- **重定向测试**：修改重定向状态码或目标\n\n## 规则语法\n\n`replaceStatus` 协议支持多种方式配置：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要替换的状态码。\n\n```txt\npattern replaceStatus://code [lineProps...] [filters...]\n```\n**示例：**\n```txt\nwww.example.com/api/old replaceStatus://301\n```\n\n### 2. 内嵌值（使用代码块）\n当需要根据条件返回不同状态码，或希望复用配置时可使用此方式。\n\n````txt\npattern replaceStatus://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n404\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的状态码。\n\n```txt\npattern replaceStatus://{key-of-values} [lineProps...] [filters...]\n```\n**前提**：在 `Values` 中存在名为 `key-of-values` 的键，其值为状态码。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载内容。\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **code** | 是 | 要替换的 HTTP 响应状态码，如：<br>• `200` OK<br>• `301` 永久重定向<br>• `302` 临时重定向<br>• `404` 未找到<br>• `500` 服务器错误<br>• 支持所有标准 HTTP 状态码 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n## 配置示例\n\n### 基础示例\n```txt\n# 将任何响应替换为 404 状态码\nwww.example.com replaceStatus://404\n\n# 将服务器错误替换为正常状态码\nwww.example.com/api replaceStatus://200 includeFilter://s:500\n\n# 修改重定向状态码\nwww.example.com/redirect replaceStatus://307\n```\n\n### 模拟认证失败\n```txt\n# 将正常响应替换为 401 状态码，触发浏览器认证弹窗\nwww.example.com/secure-page replaceStatus://401\n\n# 禁止弹出登录框，直接返回 401\nwww.example.com/secure-page replaceStatus://401 disable://userLogin\n\n# 或使用 lineProps 达到同样效果\nwww.example.com/secure-page replaceStatus://401 lineProps://disableUserLogin\n```\n\n### 条件替换\n```txt\n# 仅当原始状态码为 200 时替换为 500\nwww.example.com/api replaceStatus://500 includeFilter://s:200\n\n# 仅替换特定路径的响应状态码\nwww.example.com/api/admin replaceStatus://403\n\n```\n\n### 配合其他协议使用\n````txt\n# 替换状态码并修改响应内容\nwww.example.com/api/error replaceStatus://500 resBody://{errMsg}\n\n``` errMsg\n{\"message\":\"Custom error message\"}\n```\n\n# 替换为 301 重定向并设置新的 Location 头\nwww.example.com/old-url replaceStatus://301 resHeaders://location=https://www.example.com/new-url\n````\n\n### 使用内嵌值\n````txt\n``` maintenance-status\n503\n```\n\n# 将网站所有响应替换为维护状态\nwww.example.com replaceStatus://{maintenance-status}\n````\n\n### 环境特定配置\n```txt\n# 仅在测试环境模拟错误\ntest.example.com/api replaceStatus://500\n\n# 开发环境保持原样\n# dev.example.com/api (不设置 replaceStatus)\n```\n\n## 常见使用场景\n\n### 1. 错误处理测试\n```txt\n# 测试客户端对服务器错误的处理\nwww.example.com/api replaceStatus://500 includeFilter://chance:0.1\n```\n\n### 2. 重定向行为测试\n```txt\n# 测试永久重定向缓存\nwww.example.com/old-page replaceStatus://301\n\n# 测试临时重定向\nwww.example.com/temp-redirect replaceStatus://302\n```\n\n### 3. 认证授权测试\n```txt\n# 测试未认证访问\nwww.example.com/protected replaceStatus://401\n\n# 测试权限不足\nwww.example.com/admin replaceStatus://403\n```\n\n### 4. 限流测试\n```txt\n# 测试请求过多场景\nwww.example.com/api/rate-limited replaceStatus://429 resHeaders://retry-after=60\n```\n\n### 5. 维护模式测试\n```txt\n# 测试维护页面\nwww.example.com replaceStatus://503 resBody://(<h1>系统维护中...</h1>)\n```\n\n## 与 statusCode 的区别\n\n`replaceStatus` 协议与 [`statusCode`](./statusCode) 协议的主要区别在于处理时机：\n- **`replaceStatus`**：**先请求后端服务器**，收到响应后替换状态码\n- **`statusCode`**：**不请求后端服务器**，立即返回指定的状态码\n\n\n## 注意事项\n\n### 1. 与 statusCode 的区别\n- **处理时机**：`replaceStatus` 在响应阶段生效，`statusCode` 在请求阶段生效\n- **服务器访问**：`replaceStatus` 会访问服务器，`statusCode` 不会\n- **使用场景**：需要真实服务器数据时用 `replaceStatus`，完全模拟时用 `statusCode`\n\n### 2. 响应内容保留\n- 默认情况下，替换状态码时响应内容保持不变\n- 可通过配合 [`resBody`](./resBody) 协议修改响应内容\n\n### 3. 认证弹窗控制\n- 替换为 `401` 状态码时，默认会触发浏览器认证弹窗\n- 可通过以下方式禁用弹窗：\n  - 添加 `disable://userLogin`\n  - 添加 `lineProps://disableUserLogin`\n  - 使用 [`enable`](./enable) 协议的反向配置\n\n### 4. 重定向处理\n- 替换为重定向状态码（301、302 等）时，通常需要配合修改 `Location` 响应头\n- 否则客户端可能无法正确处理重定向\n\n### 5. 状态码兼容性\n- 确保替换的状态码与响应内容兼容\n- 例如：不应将 HTML 页面替换为 204（无内容）状态码\n\n## 高级用法\n\n### 状态码转换映射\n```txt\n# 将所有重定向统一为 302\nwww.example.com replaceStatus://302 includeFilter://s:301\n```\n\n### 概率性替换\n```txt\n# 10% 的概率将响应替换为错误\nwww.example.com/api replaceStatus://500 inclideFilter://chance:0.1\n```\n\n### 组合多个规则\n```txt\n# 针对不同原始状态码进行不同替换\nwww.example.com/api replaceStatus://200 includeFilter://s:404\nwww.example.com/api replaceStatus://400 includeFilter://s:403\n```\n\n## 故障排除\n\n### Q: 状态码替换没有生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配请求 URL\n2. 是否有其他更高优先级的规则覆盖\n3. 过滤器条件是否满足（特别是对原始状态码的过滤）\n\n### Q: 响应内容与状态码不匹配\n**A:** 检查：\n1. 是否同时修改了响应内容\n2. 响应头是否与新状态码兼容\n3. 客户端是否能正确处理该状态码\n\n### Q: 浏览器弹出认证窗口\n**A:** 对于 401 状态码：\n1. 检查是否添加了 `disable://userLogin` 或 `lineProps://disableUserLogin`\n2. 确认规则顺序是否正确\n\n### Q: 重定向循环\n**A:** 检查：\n1. 是否设置了正确的 `Location` 头\n2. 重定向目标是否正确\n3. 是否有多个重定向规则冲突\n\n## 关联协议\n\n1. **直接返回状态码**：[statusCode](./statusCode)\n   - 不请求服务器，直接返回指定的状态码\n\n2. **禁止认证弹窗**：[disable](./disable) 或 [lineProps](./lineProps)\n   - 禁用 401 状态码触发的浏览器登录弹窗\n\n3. **设置响应内容**：[resBody](./resBody)\n   - 为状态码响应添加自定义内容\n\n4. **设置响应头**：[resHeaders](./resHeaders)\n   - 为重定向等场景设置必要的响应头\n\n## 扩展阅读\n\n- [HTTP 状态码 MDN 文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status)：了解所有 HTTP 状态码的含义\n- [匹配模式文档](./pattern)：详细了解 URL 匹配规则\n- [过滤器文档](./filters)：了解更多过滤器功能\n\n## 关联协议\n1. 直接返回状态码（请求不发往服务器）：[statusCode](./statusCode)  \n2. 替换服务器返回的状态码：[replaceStatus](./replaceStatus)  \n3. 客户端页面跳转：[locationHref](./locationHref)\n","isInternal":false,"tokens":2770,"sizeBytes":8398},{"name":"reqAppend.md","path":"docs/docs/rules/reqAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqAppend.md -->","isInternal":false,"tokens":611,"sizeBytes":2089},{"name":"reqBody.md","path":"docs/docs/rules/reqBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqBody.md -->","isInternal":false,"tokens":631,"sizeBytes":2128},{"name":"reqCharset.md","path":"docs/docs/rules/reqCharset.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqCharset.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCharset.md -->","isInternal":false,"tokens":366,"sizeBytes":1319},{"name":"reqCookies.md","path":"docs/docs/rules/reqCookies.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqCookies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCookies.md -->","isInternal":false,"tokens":544,"sizeBytes":1843},{"name":"reqCors.md","path":"docs/docs/rules/reqCors.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqCors.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqCors.md -->","isInternal":false,"tokens":464,"sizeBytes":1622},{"name":"reqDelay.md","path":"docs/docs/rules/reqDelay.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqDelay.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqDelay.md -->","isInternal":false,"tokens":374,"sizeBytes":1247},{"name":"reqHeaders.md","path":"docs/docs/rules/reqHeaders.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqHeaders.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqHeaders.md -->","isInternal":false,"tokens":596,"sizeBytes":2069},{"name":"reqMerge.md","path":"docs/docs/rules/reqMerge.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqMerge.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqMerge.md -->","isInternal":false,"tokens":869,"sizeBytes":2936},{"name":"reqPrepend.md","path":"docs/docs/rules/reqPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqPrepend.md -->","isInternal":false,"tokens":619,"sizeBytes":2095},{"name":"reqReplace.md","path":"docs/docs/rules/reqReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqReplace.md -->","isInternal":false,"tokens":747,"sizeBytes":2524},{"name":"reqRules.md","path":"docs/docs/rules/reqRules.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqRules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqRules.md -->","isInternal":false,"tokens":408,"sizeBytes":1352},{"name":"reqScript.md","path":"docs/docs/rules/reqScript.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqScript.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqScript.md -->","isInternal":false,"tokens":639,"sizeBytes":2755},{"name":"reqSpeed.md","path":"docs/docs/rules/reqSpeed.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqSpeed.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqSpeed.md -->","isInternal":false,"tokens":294,"sizeBytes":1030},{"name":"reqType.md","path":"docs/docs/rules/reqType.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqType.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqType.md -->","isInternal":false,"tokens":596,"sizeBytes":2106},{"name":"reqWrite.md","path":"docs/docs/rules/reqWrite.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqWrite.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqWrite.md -->","isInternal":false,"tokens":743,"sizeBytes":2341},{"name":"reqWriteRaw.md","path":"docs/docs/rules/reqWriteRaw.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/reqWriteRaw.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FreqWriteRaw.md -->","isInternal":false,"tokens":765,"sizeBytes":2411},{"name":"resAppend.md","path":"docs/docs/rules/resAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresAppend.md -->","isInternal":false,"tokens":1104,"sizeBytes":3447},{"name":"resBody.md","path":"docs/docs/rules/resBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresBody.md -->","isInternal":false,"tokens":2871,"sizeBytes":8603},{"name":"resCharset.md","path":"docs/docs/rules/resCharset.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resCharset.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCharset.md -->","isInternal":false,"tokens":382,"sizeBytes":1319},{"name":"resCookies.md","path":"docs/docs/rules/resCookies.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resCookies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCookies.md -->","isInternal":false,"tokens":828,"sizeBytes":2712},{"name":"resCors.md","path":"docs/docs/rules/resCors.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resCors.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresCors.md -->","isInternal":false,"tokens":1071,"sizeBytes":3548},{"name":"resDelay.md","path":"docs/docs/rules/resDelay.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resDelay.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresDelay.md -->","isInternal":false,"tokens":299,"sizeBytes":1028},{"name":"resHeaders.md","path":"docs/docs/rules/resHeaders.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resHeaders.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresHeaders.md -->","isInternal":false,"tokens":607,"sizeBytes":2062},{"name":"resMerge.md","path":"docs/docs/rules/resMerge.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resMerge.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresMerge.md -->","isInternal":false,"tokens":1381,"sizeBytes":4311},{"name":"responseFor.md","path":"docs/docs/rules/responseFor.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/responseFor.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresponseFor.md -->","isInternal":false,"tokens":301,"sizeBytes":1063},{"name":"resPrepend.md","path":"docs/docs/rules/resPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresPrepend.md -->","isInternal":false,"tokens":3274,"sizeBytes":9760},{"name":"resReplace.md","path":"docs/docs/rules/resReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresReplace.md -->","isInternal":false,"tokens":1211,"sizeBytes":3784},{"name":"resRules.md","path":"docs/docs/rules/resRules.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resRules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresRules.md -->","isInternal":false,"tokens":435,"sizeBytes":1423},{"name":"resScript.md","path":"docs/docs/rules/resScript.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resScript.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresScript.md -->","isInternal":false,"tokens":672,"sizeBytes":2843},{"name":"resSpeed.md","path":"docs/docs/rules/resSpeed.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resSpeed.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresSpeed.md -->","isInternal":false,"tokens":295,"sizeBytes":1034},{"name":"resType.md","path":"docs/docs/rules/resType.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resType.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresType.md -->","isInternal":false,"tokens":551,"sizeBytes":1844},{"name":"resWrite.md","path":"docs/docs/rules/resWrite.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resWrite.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresWrite.md -->","isInternal":false,"tokens":754,"sizeBytes":2350},{"name":"resWriteRaw.md","path":"docs/docs/rules/resWriteRaw.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/resWriteRaw.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FresWriteRaw.md -->","isInternal":false,"tokens":780,"sizeBytes":2427},{"name":"rule.md","path":"docs/docs/rules/rule.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/rule.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Frule.md -->","isInternal":false,"tokens":1070,"sizeBytes":3148},{"name":"skip.md","path":"docs/docs/rules/skip.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/skip.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fskip.md -->","isInternal":false,"tokens":488,"sizeBytes":1759},{"name":"sniCallback.md","path":"docs/docs/rules/sniCallback.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/sniCallback.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FsniCallback.md -->","isInternal":false,"tokens":346,"sizeBytes":1233},{"name":"socks.md","path":"docs/docs/rules/socks.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/socks.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fsocks.md -->","isInternal":false,"tokens":988,"sizeBytes":2839},{"name":"statusCode.md","path":"docs/docs/rules/statusCode.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/statusCode.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FstatusCode.md -->","isInternal":false,"tokens":2480,"sizeBytes":7553},{"name":"style.md","path":"docs/docs/rules/style.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/style.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fstyle.md -->","isInternal":false,"tokens":336,"sizeBytes":1155},{"name":"tpl.md","path":"docs/docs/rules/tpl.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/tpl.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftpl.md -->","isInternal":false,"tokens":399,"sizeBytes":1358},{"name":"trailers.md","path":"docs/docs/rules/trailers.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/trailers.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftrailers.md -->","isInternal":false,"tokens":479,"sizeBytes":1658},{"name":"tunnel.md","path":"docs/docs/rules/tunnel.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/tunnel.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Ftunnel.md -->","isInternal":false,"tokens":427,"sizeBytes":1563},{"name":"ua.md","path":"docs/docs/rules/ua.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/ua.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fua.md -->","isInternal":false,"tokens":361,"sizeBytes":1209},{"name":"urlParams.md","path":"docs/docs/rules/urlParams.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/urlParams.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2FurlParams.md -->","isInternal":false,"tokens":538,"sizeBytes":1861},{"name":"weinre.md","path":"docs/docs/rules/weinre.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/weinre.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fweinre.md -->","isInternal":false,"tokens":233,"sizeBytes":863},{"name":"ws.md","path":"docs/docs/rules/ws.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/ws.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fws.md -->","isInternal":false,"tokens":646,"sizeBytes":2306},{"name":"wss.md","path":"docs/docs/rules/wss.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/wss.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fwss.md -->","isInternal":false,"tokens":659,"sizeBytes":2319},{"name":"xfile.md","path":"docs/docs/rules/xfile.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/xfile.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxfile.md -->","isInternal":false,"tokens":132,"sizeBytes":404},{"name":"xhost.md","path":"docs/docs/rules/xhost.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/xhost.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxhost.md -->","isInternal":false,"tokens":134,"sizeBytes":383},{"name":"xhttps-proxy.md","path":"docs/docs/rules/xhttps-proxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/xhttps-proxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxhttps-proxy.md -->","isInternal":false,"tokens":159,"sizeBytes":493},{"name":"xproxy.md","path":"docs/docs/rules/xproxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/xproxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxproxy.md -->","isInternal":false,"tokens":157,"sizeBytes":455},{"name":"xrawfile.md","path":"docs/docs/rules/xrawfile.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/xrawfile.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxrawfile.md -->","isInternal":false,"tokens":143,"sizeBytes":437},{"name":"xsocks.md","path":"docs/docs/rules/xsocks.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/xsocks.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxsocks.md -->","isInternal":false,"tokens":159,"sizeBytes":427},{"name":"xtpl.md","path":"docs/docs/rules/xtpl.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/docs/rules/xtpl.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fdocs%2Frules%2Fxtpl.md -->","isInternal":false,"tokens":132,"sizeBytes":393},{"name":"api.md","path":"docs/en/docs/extensions/api.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/extensions/api.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fapi.md -->","isInternal":false,"tokens":11704,"sizeBytes":50044},{"name":"dev.md","path":"docs/en/docs/extensions/dev.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/extensions/dev.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fdev.md -->","isInternal":false,"tokens":10353,"sizeBytes":41883},{"name":"npm.md","path":"docs/en/docs/extensions/npm.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/extensions/npm.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fnpm.md -->","isInternal":false,"tokens":1031,"sizeBytes":4361},{"name":"usage.md","path":"docs/en/docs/extensions/usage.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/extensions/usage.md","title":"Extensions Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Fextensions%2Fusage.md -->","isInternal":false,"tokens":1401,"sizeBytes":6855},{"name":"@.md","path":"docs/en/docs/rules/@.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/@.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2F%40.md -->","isInternal":false,"tokens":412,"sizeBytes":1702},{"name":"attachment.md","path":"docs/en/docs/rules/attachment.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/attachment.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fattachment.md -->","isInternal":false,"tokens":1100,"sizeBytes":5011},{"name":"auth.md","path":"docs/en/docs/rules/auth.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/auth.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fauth.md -->","isInternal":false,"tokens":1472,"sizeBytes":6114},{"name":"cache.md","path":"docs/en/docs/rules/cache.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/cache.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fcache.md -->","isInternal":false,"tokens":1380,"sizeBytes":5910},{"name":"cipher.md","path":"docs/en/docs/rules/cipher.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/cipher.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fcipher.md -->","isInternal":false,"tokens":2407,"sizeBytes":9668},{"name":"cssAppend.md","path":"docs/en/docs/rules/cssAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/cssAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssAppend.md -->","isInternal":false,"tokens":1665,"sizeBytes":6695},{"name":"cssBody.md","path":"docs/en/docs/rules/cssBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/cssBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssBody.md -->","isInternal":false,"tokens":1065,"sizeBytes":4285},{"name":"cssPrepend.md","path":"docs/en/docs/rules/cssPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/cssPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FcssPrepend.md -->","isInternal":false,"tokens":1097,"sizeBytes":4435},{"name":"delete.md","path":"docs/en/docs/rules/delete.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/delete.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fdelete.md -->","isInternal":false,"tokens":1428,"sizeBytes":5725},{"name":"disable.md","path":"docs/en/docs/rules/disable.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/disable.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fdisable.md -->","isInternal":false,"tokens":1756,"sizeBytes":7135},{"name":"enable.md","path":"docs/en/docs/rules/enable.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/enable.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fenable.md -->","isInternal":false,"tokens":2697,"sizeBytes":11668},{"name":"excludeFilter.md","path":"docs/en/docs/rules/excludeFilter.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/excludeFilter.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FexcludeFilter.md -->","isInternal":false,"tokens":241,"sizeBytes":1155},{"name":"file.md","path":"docs/en/docs/rules/file.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/file.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ffile.md -->","isInternal":false,"tokens":2728,"sizeBytes":11576},{"name":"filters.md","path":"docs/en/docs/rules/filters.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/filters.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ffilters.md -->","isInternal":false,"tokens":703,"sizeBytes":2620},{"name":"forwardedFor.md","path":"docs/en/docs/rules/forwardedFor.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/forwardedFor.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FforwardedFor.md -->","isInternal":false,"tokens":250,"sizeBytes":1131},{"name":"frameScript.md","path":"docs/en/docs/rules/frameScript.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/frameScript.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FframeScript.md -->","isInternal":false,"tokens":672,"sizeBytes":2829},{"name":"headerReplace.md","path":"docs/en/docs/rules/headerReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/headerReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FheaderReplace.md -->","isInternal":false,"tokens":340,"sizeBytes":1477},{"name":"host.md","path":"docs/en/docs/rules/host.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/host.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhost.md -->","isInternal":false,"tokens":907,"sizeBytes":3793},{"name":"htmlAppend.md","path":"docs/en/docs/rules/htmlAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/htmlAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlAppend.md -->","isInternal":false,"tokens":1282,"sizeBytes":5165},{"name":"htmlBody.md","path":"docs/en/docs/rules/htmlBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/htmlBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlBody.md -->","isInternal":false,"tokens":1302,"sizeBytes":5224},{"name":"htmlPrepend.md","path":"docs/en/docs/rules/htmlPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/htmlPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FhtmlPrepend.md -->","isInternal":false,"tokens":1321,"sizeBytes":5323},{"name":"http.md","path":"docs/en/docs/rules/http.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/http.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttp.md -->","isInternal":false,"tokens":865,"sizeBytes":3420},{"name":"https-proxy.md","path":"docs/en/docs/rules/https-proxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/https-proxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttps-proxy.md -->","isInternal":false,"tokens":823,"sizeBytes":3201},{"name":"https.md","path":"docs/en/docs/rules/https.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/https.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fhttps.md -->","isInternal":false,"tokens":862,"sizeBytes":3408},{"name":"ignore.md","path":"docs/en/docs/rules/ignore.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/ignore.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fignore.md -->","isInternal":false,"tokens":309,"sizeBytes":1388},{"name":"includeFilter.md","path":"docs/en/docs/rules/includeFilter.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/includeFilter.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FincludeFilter.md -->","isInternal":false,"tokens":238,"sizeBytes":1135},{"name":"inherit.md","path":"docs/en/docs/rules/inherit.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/inherit.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Finherit.md -->","isInternal":false,"tokens":759,"sizeBytes":3070},{"name":"jsAppend.md","path":"docs/en/docs/rules/jsAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/jsAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsAppend.md -->","isInternal":false,"tokens":1428,"sizeBytes":5811},{"name":"jsBody.md","path":"docs/en/docs/rules/jsBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/jsBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsBody.md -->","isInternal":false,"tokens":1414,"sizeBytes":5739},{"name":"jsPrepend.md","path":"docs/en/docs/rules/jsPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/jsPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FjsPrepend.md -->","isInternal":false,"tokens":1458,"sizeBytes":5924},{"name":"lineProps.md","path":"docs/en/docs/rules/lineProps.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/lineProps.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FlineProps.md -->","isInternal":false,"tokens":1300,"sizeBytes":5505},{"name":"locationHref.md","path":"docs/en/docs/rules/locationHref.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/locationHref.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FlocationHref.md -->","isInternal":false,"tokens":479,"sizeBytes":2188},{"name":"log.md","path":"docs/en/docs/rules/log.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/log.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Flog.md -->","isInternal":false,"tokens":764,"sizeBytes":3744},{"name":"method.md","path":"docs/en/docs/rules/method.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/method.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fmethod.md -->","isInternal":false,"tokens":196,"sizeBytes":918},{"name":"operation.md","path":"docs/en/docs/rules/operation.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/operation.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Foperation.md -->","isInternal":false,"tokens":4236,"sizeBytes":15965},{"name":"pac.md","path":"docs/en/docs/rules/pac.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/pac.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpac.md -->","isInternal":false,"tokens":695,"sizeBytes":2971},{"name":"pathReplace.md","path":"docs/en/docs/rules/pathReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/pathReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FpathReplace.md -->","isInternal":false,"tokens":779,"sizeBytes":3289},{"name":"pattern.md","path":"docs/en/docs/rules/pattern.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/pattern.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpattern.md -->","isInternal":false,"tokens":4126,"sizeBytes":15064},{"name":"pipe.md","path":"docs/en/docs/rules/pipe.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/pipe.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fpipe.md -->","isInternal":false,"tokens":243,"sizeBytes":1120},{"name":"plugin-vars.md","path":"docs/en/docs/rules/plugin-vars.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/plugin-vars.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fplugin-vars.md -->","isInternal":false,"tokens":1055,"sizeBytes":4221},{"name":"protocols.md","path":"docs/en/docs/rules/protocols.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/protocols.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fprotocols.md -->","isInternal":false,"tokens":1015,"sizeBytes":2915},{"name":"proxy.md","path":"docs/en/docs/rules/proxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/proxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fproxy.md -->","isInternal":false,"tokens":915,"sizeBytes":3511},{"name":"rawfile.md","path":"docs/en/docs/rules/rawfile.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/rawfile.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Frawfile.md -->","isInternal":false,"tokens":442,"sizeBytes":1867},{"name":"redirect.md","path":"docs/en/docs/rules/redirect.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/redirect.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fredirect.md -->","isInternal":false,"tokens":367,"sizeBytes":1646},{"name":"referer.md","path":"docs/en/docs/rules/referer.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/referer.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Freferer.md -->","isInternal":false,"tokens":214,"sizeBytes":1021},{"name":"replaceStatus.md","path":"docs/en/docs/rules/replaceStatus.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/replaceStatus.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreplaceStatus.md -->","isInternal":false,"tokens":2153,"sizeBytes":9749},{"name":"reqAppend.md","path":"docs/en/docs/rules/reqAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqAppend.md -->","isInternal":false,"tokens":518,"sizeBytes":2220},{"name":"reqBody.md","path":"docs/en/docs/rules/reqBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqBody.md -->","isInternal":false,"tokens":502,"sizeBytes":2154},{"name":"reqCharset.md","path":"docs/en/docs/rules/reqCharset.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqCharset.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCharset.md -->","isInternal":false,"tokens":306,"sizeBytes":1380},{"name":"reqCookies.md","path":"docs/en/docs/rules/reqCookies.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqCookies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCookies.md -->","isInternal":false,"tokens":460,"sizeBytes":1895},{"name":"reqCors.md","path":"docs/en/docs/rules/reqCors.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqCors.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqCors.md -->","isInternal":false,"tokens":358,"sizeBytes":1589},{"name":"reqDelay.md","path":"docs/en/docs/rules/reqDelay.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqDelay.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqDelay.md -->","isInternal":false,"tokens":285,"sizeBytes":1239},{"name":"reqHeaders.md","path":"docs/en/docs/rules/reqHeaders.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqHeaders.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqHeaders.md -->","isInternal":false,"tokens":501,"sizeBytes":2137},{"name":"reqMerge.md","path":"docs/en/docs/rules/reqMerge.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqMerge.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqMerge.md -->","isInternal":false,"tokens":730,"sizeBytes":3188},{"name":"reqPrepend.md","path":"docs/en/docs/rules/reqPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqPrepend.md -->","isInternal":false,"tokens":525,"sizeBytes":2233},{"name":"reqReplace.md","path":"docs/en/docs/rules/reqReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqReplace.md -->","isInternal":false,"tokens":637,"sizeBytes":2638},{"name":"reqRules.md","path":"docs/en/docs/rules/reqRules.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqRules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqRules.md -->","isInternal":false,"tokens":300,"sizeBytes":1357},{"name":"reqScript.md","path":"docs/en/docs/rules/reqScript.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqScript.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqScript.md -->","isInternal":false,"tokens":474,"sizeBytes":2147},{"name":"reqSpeed.md","path":"docs/en/docs/rules/reqSpeed.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqSpeed.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqSpeed.md -->","isInternal":false,"tokens":224,"sizeBytes":1039},{"name":"reqType.md","path":"docs/en/docs/rules/reqType.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqType.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqType.md -->","isInternal":false,"tokens":508,"sizeBytes":2173},{"name":"reqWrite.md","path":"docs/en/docs/rules/reqWrite.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqWrite.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqWrite.md -->","isInternal":false,"tokens":585,"sizeBytes":2496},{"name":"reqWriteRaw.md","path":"docs/en/docs/rules/reqWriteRaw.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/reqWriteRaw.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FreqWriteRaw.md -->","isInternal":false,"tokens":603,"sizeBytes":2594},{"name":"resAppend.md","path":"docs/en/docs/rules/resAppend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resAppend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresAppend.md -->","isInternal":false,"tokens":875,"sizeBytes":3644},{"name":"resBody.md","path":"docs/en/docs/rules/resBody.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resBody.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresBody.md -->","isInternal":false,"tokens":2275,"sizeBytes":9646},{"name":"resCharset.md","path":"docs/en/docs/rules/resCharset.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resCharset.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCharset.md -->","isInternal":false,"tokens":306,"sizeBytes":1393},{"name":"resCookies.md","path":"docs/en/docs/rules/resCookies.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resCookies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCookies.md -->","isInternal":false,"tokens":718,"sizeBytes":2745},{"name":"resCors.md","path":"docs/en/docs/rules/resCors.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resCors.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresCors.md -->","isInternal":false,"tokens":847,"sizeBytes":3631},{"name":"resDelay.md","path":"docs/en/docs/rules/resDelay.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resDelay.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresDelay.md -->","isInternal":false,"tokens":227,"sizeBytes":1065},{"name":"resHeaders.md","path":"docs/en/docs/rules/resHeaders.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resHeaders.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresHeaders.md -->","isInternal":false,"tokens":503,"sizeBytes":2125},{"name":"resMerge.md","path":"docs/en/docs/rules/resMerge.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resMerge.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresMerge.md -->","isInternal":false,"tokens":1096,"sizeBytes":4675},{"name":"responseFor.md","path":"docs/en/docs/rules/responseFor.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/responseFor.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresponseFor.md -->","isInternal":false,"tokens":236,"sizeBytes":1036},{"name":"resPrepend.md","path":"docs/en/docs/rules/resPrepend.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resPrepend.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresPrepend.md -->","isInternal":false,"tokens":2633,"sizeBytes":10754},{"name":"resReplace.md","path":"docs/en/docs/rules/resReplace.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resReplace.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresReplace.md -->","isInternal":false,"tokens":962,"sizeBytes":3933},{"name":"resRules.md","path":"docs/en/docs/rules/resRules.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resRules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresRules.md -->","isInternal":false,"tokens":322,"sizeBytes":1466},{"name":"resScript.md","path":"docs/en/docs/rules/resScript.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resScript.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresScript.md -->","isInternal":false,"tokens":505,"sizeBytes":2262},{"name":"resSpeed.md","path":"docs/en/docs/rules/resSpeed.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resSpeed.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresSpeed.md -->","isInternal":false,"tokens":226,"sizeBytes":1052},{"name":"resType.md","path":"docs/en/docs/rules/resType.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resType.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresType.md -->","isInternal":false,"tokens":434,"sizeBytes":1925},{"name":"resWrite.md","path":"docs/en/docs/rules/resWrite.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resWrite.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresWrite.md -->","isInternal":false,"tokens":588,"sizeBytes":2519},{"name":"resWriteRaw.md","path":"docs/en/docs/rules/resWriteRaw.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/resWriteRaw.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FresWriteRaw.md -->","isInternal":false,"tokens":606,"sizeBytes":2635},{"name":"rule.md","path":"docs/en/docs/rules/rule.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/rule.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Frule.md -->","isInternal":false,"tokens":823,"sizeBytes":3629},{"name":"skip.md","path":"docs/en/docs/rules/skip.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/skip.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fskip.md -->","isInternal":false,"tokens":407,"sizeBytes":1818},{"name":"sniCallback.md","path":"docs/en/docs/rules/sniCallback.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/sniCallback.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FsniCallback.md -->","isInternal":false,"tokens":281,"sizeBytes":1295},{"name":"socks.md","path":"docs/en/docs/rules/socks.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/socks.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fsocks.md -->","isInternal":false,"tokens":813,"sizeBytes":3101},{"name":"statusCode.md","path":"docs/en/docs/rules/statusCode.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/statusCode.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FstatusCode.md -->","isInternal":false,"tokens":1968,"sizeBytes":8620},{"name":"style.md","path":"docs/en/docs/rules/style.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/style.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fstyle.md -->","isInternal":false,"tokens":263,"sizeBytes":1129},{"name":"tpl.md","path":"docs/en/docs/rules/tpl.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/tpl.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftpl.md -->","isInternal":false,"tokens":298,"sizeBytes":1282},{"name":"trailers.md","path":"docs/en/docs/rules/trailers.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/trailers.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftrailers.md -->","isInternal":false,"tokens":373,"sizeBytes":1655},{"name":"tunnel.md","path":"docs/en/docs/rules/tunnel.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/tunnel.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Ftunnel.md -->","isInternal":false,"tokens":333,"sizeBytes":1481},{"name":"ua.md","path":"docs/en/docs/rules/ua.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/ua.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fua.md -->","isInternal":false,"tokens":287,"sizeBytes":1214},{"name":"urlParams.md","path":"docs/en/docs/rules/urlParams.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/urlParams.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2FurlParams.md -->","isInternal":false,"tokens":452,"sizeBytes":1895},{"name":"weinre.md","path":"docs/en/docs/rules/weinre.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/weinre.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fweinre.md -->","isInternal":false,"tokens":158,"sizeBytes":778},{"name":"ws.md","path":"docs/en/docs/rules/ws.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/ws.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fws.md -->","isInternal":false,"tokens":530,"sizeBytes":2213},{"name":"wss.md","path":"docs/en/docs/rules/wss.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/wss.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fwss.md -->","isInternal":false,"tokens":544,"sizeBytes":2236},{"name":"xfile.md","path":"docs/en/docs/rules/xfile.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/xfile.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxfile.md -->","isInternal":false,"tokens":123,"sizeBytes":465},{"name":"xhost.md","path":"docs/en/docs/rules/xhost.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/xhost.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxhost.md -->","isInternal":false,"tokens":111,"sizeBytes":429},{"name":"xhttps-proxy.md","path":"docs/en/docs/rules/xhttps-proxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/xhttps-proxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxhttps-proxy.md -->","isInternal":false,"tokens":131,"sizeBytes":569},{"name":"xproxy.md","path":"docs/en/docs/rules/xproxy.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/xproxy.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxproxy.md -->","isInternal":false,"tokens":130,"sizeBytes":532},{"name":"xrawfile.md","path":"docs/en/docs/rules/xrawfile.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/xrawfile.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxrawfile.md -->","isInternal":false,"tokens":134,"sizeBytes":498},{"name":"xsocks.md","path":"docs/en/docs/rules/xsocks.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/xsocks.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxsocks.md -->","isInternal":false,"tokens":131,"sizeBytes":507},{"name":"xtpl.md","path":"docs/en/docs/rules/xtpl.md","rawUrl":"https://raw.githubusercontent.com/avwo/whistle/HEAD/docs/en/docs/rules/xtpl.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/avwo/whistle?path=docs%2Fen%2Fdocs%2Frules%2Fxtpl.md -->","isInternal":false,"tokens":125,"sizeBytes":459}],"systemPromptSnippet":"<yakaai_skills repo=\"avwo/whistle\">\n<!-- File: docs/docs/extensions/api.md (Tokens: ~14693 | Category: anthropic-skill) -->\n# Local Agent API\n\nWhistle 的本地 API，供 AI Agent 或第三方进程以编程方式操作 Whistle 代理服务。支持规则管理、插件控制、抓包数据获取、HTTPS 代理开关、证书管理等核心功能，具体用法如下（确保已全局安装 Whistle 且版本 ≥ 2.10.7）：\n\n``` javascript\nconst cp = require('child_process');\nconst qs = require('querystring');\n\nconst loadModule = require;\nlet w2Api;\n\n/**\n * @async 异步加载 Whistle API 模块\n * @function loadApi\n * @description\n * 尝试加载 Whistle 的 API 模块，并校验其版本是否符合最低要求（≥ 2.10.7）。\n * 如果版本过低，或加载过程中发生任何错误（如子进程调用失败、模块加载异常、网络状态获取失败等），\n * 都会抛出对应的错误。\n * \n * @returns {Promise<Object>} 返回 Whistle API 模块对象（通常包含 network、rules 等接口）\n * \n * @throws {Error} 当执行 `w2 root bin/api` 命令失败时抛出（例如 whistle 未安装或未在 PATH 中，需要通过 `npm install -g whistle` 安装）\n * @throws {Error} 当调用 `api.network.getStatus()` 失败时抛出（例如 whistle 或 whistle client 服务未启动）\n * @throws {Error} 当 Whistle 版本低于 2.10.7 时抛出，错误信息为 'Whistle version must be >= 2.10.7'\n * \n * @example\n * try {\n *   const api = await loadApi();\n *   // 使用 api.network.getStatus() 等\n * } catch (err) {\n *   console.error('加载 Whistle API 失败:', err.message);\n * }\n */\nconst loadApi = async () => {\n  if (!w2Api) {\n    w2Api = loadModule(cp.spawnSync('w2', ['root', 'bin/api']).stdout.toString().trim());\n  }\n  const { version } = await w2Api.network.getStatus();\n  const [major, minor, patch] = version.split('.').map(Number);\n  w2Api.version = version;\n  if (major > 2 || (major == 2 && (minor > 10 || (minor == 10 && patch >= 7)))) {\n    return w2Api;\n  }\n  throw new Error('Whistle version must be >= 2.10.7');\n};\n\n/**\n * @abstract 判断当前 Whistle 是否启用了 HTTPS 代理\n * @function isEnabledHTTPS\n * @description\n * 通过 Whistle API 检查当前代理是否启用了 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了 HTTPS 代理\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.isEnabledHTTPS()` 调用失败\n *\n * @example\n * try {\n *   const enabled = await isEnabledHTTPS();\n *   console.log(`当前 HTTPS 代理是否启用: ${enabled}`);\n * } catch (err) {\n *   console.error('检查 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst isEnabledHTTPS = async () => {\n  const api = await loadApi();\n  return await api.isEnabledHTTPS();\n};\n\n/**\n * @async 设置 Whistle 是否启用 HTTPS 代理\n * @function setEnableHTTPS\n * @description\n * 通过 Whistle API 设置当前代理是否启用 HTTPS 代理功能。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用 HTTPS 代理\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.setEnableHTTPS()` 调用失败\n *\n * @example\n * try {\n *   await setEnableHTTPS(true);\n *   console.log('设置 HTTPS 代理启用状态成功');\n * } catch (err) {\n *   console.error('设置 HTTPS 代理状态失败:', err.message);\n * }\n */\nconst setEnableHTTPS = async (enable) => {\n  const api = await loadApi();\n  await api.setEnableHTTPS(enable);\n};\n\n/**\n * @async 获取 Whistle 的根证书信息\n * @function getRootCA\n * @description\n * 通过 Whistle API 获取当前代理的根证书信息，包括证书内容、私钥等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Buffer>} 返回根证书的 Buffer 对象，可用于保存为 .crt 文件或进行其他操作\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getRootCA()` 调用失败\n */\nconst getRootCA = async () => {\n  const api = await loadApi();\n  return await api.getRootCA();\n};\n\n/**\n * @async 创建一个临时文件并写入指定内容\n * @function createFile\n * @description\n * 通过 Whistle API 创建一个临时文件，并将指定的内容写入该文件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {Buffer|string} data - 要创建的文件内容，可以是 Buffer 或字符串\n * @returns {Promise<string>} 返回创建的文件路径\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.createFile()` 调用失败\n *\n * @example\n * try {\n *   const filePath = await createFile('test');\n *   console.log(`临时文件已创建: ${filePath}`);\n *   // 临时文件已创建: temp/7d2e9a424672279e5bcaab948a7a63fd47cb381626555835e828438762f86a96\n * } catch (err) {\n *   console.error('创建临时文件失败:', err.message);\n * }\n */\nconst createFile = async (data) => {\n  const api = await loadApi();\n  return await api.createFile(data);\n};\n\n/**\n * @async 获取指定文件的内容\n * @function getFile\n * @description\n * 通过 Whistle API 获取指定路径的文件内容，返回其字符串内容。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} filePath - 要读取的文件路径\n * @returns {Promise<string>} 返回文件内容的字符串\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.getFile()` 调用失败（如文件不存在）\n */\nconst getFile = async (filePath) => {\n  const api = await loadApi();\n  return await api.getFile(filePath);\n};\n\n/**\n * @async 获取 Whistle 规则引擎的当前状态配置\n * @function getRulesStatus\n * @description\n * 通过 Whistle API 获取规则列表、启用状态以及优先级策略等运行时配置。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<{\n *   laterRulesFirst: boolean,   // 是否启用“后置规则优先”策略（即后匹配的规则覆盖前匹配的）\n *   multiSelect: boolean,       // 是否启用多选模式\n *   pluginsDisabled: boolean,   // 是否全局禁用所有插件规则\n *   disabled: boolean,          // 是否全局禁用 Whistle 代理（所有规则失效）\n *   list: Array<{\n *     name: string,            // 规则文件/配置的名称\n *     selected: boolean        // 当前是否处于激活（勾选）状态\n *   }>\n * }>} 返回包含规则状态和列表的对象\n *\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.getStatus()` 调用失败\n *\n * @example\n * try {\n *   const { list, disabled, multiSelect, laterRulesFirst } = await getRulesStatus();\n *   const activeRules = list.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则状态失败:', err.message);\n * }\n */\nconst getRulesStatus = async () => {\n  const api = await loadApi();\n  return await api.rules.getStatus();\n};\n\n/**\n * @async 关闭 Whistle 规则引擎\n * @function turnOffRules\n * @description\n * 通过 Whistle API 全局禁用所有规则（不包括插件规则）。\n * 该操作会立即生效，影响当前代理会话中的所有请求处理。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOff()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOffRules();\n *   console.log('规则已关闭，所有请求将不再受 Whistle 规则影响');\n * } catch (err) {\n *   console.error('关闭规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOffRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOff();\n};\n\n/**\n * @async 启用 Whistle 规则引擎\n * @function turnOnRules\n * @description\n * 通过 Whistle API 全局启用之前配置的所有规则（不包括插件的规则）。\n * 该操作会立即生效，恢复代理请求的规则处理逻辑。\n * 依赖 `loadApi()` 加载 API 模块，若 Whistle 服务未运行或版本不兼容则会失败。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.turnOn()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await turnOnRules();\n *   console.log('规则已启用，请求将按配置规则处理');\n * } catch (err) {\n *   console.error('启用规则失败:', err.message);\n *   // 可依据错误类型进行降级或重试\n * }\n */\nconst turnOnRules = async () => {\n  const api = await loadApi();\n  await api.rules.turnOn();\n};\n\n/**\n * @async 选中（激活）指定的 Whistle 规则文件，并自动启用规则引擎\n * @function selectRule\n * @description\n * 调用 Whistle API 选中指定名称的规则文件（通常从规则列表中选择），\n * 若选中操作成功（返回 truthy 值），则自动调用 `turnOnRules()` 启用全局规则引擎，\n * 使该规则立即生效。该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要激活的规则文件名称（需与规则列表中的名称精确匹配）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.select()` 调用失败（如网络异常）\n * @throws {Error} 若选中成功但启用规则引擎（`turnOnRules()`）失败时抛出\n *\n * @example\n * try {\n *   await selectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已选中并启用');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst selectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.select(ruleName);\n  if (result) {\n    await turnOnRules();\n  }\n  await result;\n};\n\n/**\n * @async 取消选中（停用）指定的 Whistle 规则文件\n * @function unselectRule\n * @description\n * 调用 Whistle API 取消选中指定名称的规则文件（通常从规则列表中选择），\n * 该操作依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 要取消激活的规则文件名称（需与规则列表中的名称精确匹配，如果 ruleName 不填则取消所有规则）\n *\n * @returns {Promise<boolean>} 对应 ruleName 是否存在\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.unselect()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   await unselectRule('dev-rules');\n *   console.log('规则 \"dev-rules\" 已取消选中');\n * } catch (err) {\n *   console.error('操作失败:', err.message);\n * }\n */\nconst unselectRule = async (ruleName) => {\n  const api = await loadApi();\n  const result = await api.rules.unselect(ruleName);\n  await result;\n};\n\n/**\n * @async 获取指定规则文件的内容详情\n * @function getRule\n * @description\n * 通过 Whistle API 读取指定名称的规则文件内容，返回其详细配置信息。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @param {string} ruleName - 规则文件的名称（需与 Whistle 规则列表中的名称精确匹配）\n *\n * @returns {Promise<{\n *   value: string,       // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean,     // 当前是否被选中\n * }>} 返回规则对象，具体字段可能因 Whistle 版本而异\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.get()` 调用失败（如规则名称不存在）\n *\n * @example\n * try {\n *   const rule = await getRule('my-rules');\n *   console.log(`规则的内容:\\n${rule.value}`);\n *   if (rule.selected) {\n *     console.log('该规则当前已激活');\n *   }\n * } catch (err) {\n *   console.error('获取规则失败:', err.message);\n * }\n */\nconst getRule = async (ruleName) => {\n  const api = await loadApi();\n  return await api.rules.get(ruleName);\n};\n\n/**\n * @async 获取当前 Whistle 规则列表\n * @function getRuleList\n * @description\n * 通过 Whistle API 获取所有已配置的规则文件列表，包含每个规则的名称、内容及激活状态。\n * 依赖 `loadApi()` 加载 API 模块。\n *\n * @returns {Promise<Array<{\n *   name: string,      // 规则文件名称\n *   value: string,     // 规则内容（如 \"* www.example.com 127.0.0.1\"）\n *   selected: boolean  // 当前是否被选中（激活）\n * }>>} 返回规则对象数组，若无可返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.rules.getList()` 调用失败（如网络异常）\n *\n * @example\n * try {\n *   const rules = await getRuleList();\n *   console.log(`共 ${rules.length} 条规则`);\n *   const activeRules = rules.filter(r => r.selected);\n *   console.log(`激活规则 (${activeRules.length}):`, activeRules.map(r => r.name));\n * } catch (err) {\n *   console.error('获取规则列表失败:', err.message);\n * }\n */\nconst getRuleList = async () => {\n  const api = await loadApi();\n  return await api.rules.getList();\n};\n\n/**\n * @async 判断当前 Whistle 是否启用了多选模式\n * @function isMultiSelect\n * @description\n * 通过 Whistle API 检查当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<boolean>} 当前是否启用了多选模式\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.isMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   const multiSelect = await isMultiSelect();\n *   console.log(`当前规则引擎是否启用多选模式: ${multiSelect}`);\n * } catch (err) {\n *   console.error('检查多选模式状态失败:', err.message);\n * }\n */\nconst isMultiSelect = async () => {\n  const api = await loadApi();\n  return await api.rules.isMultiSelect();\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用多选模式\n * @function setMultiSelect\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了多选模式。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {boolean} enable - 是否启用多选模式\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setMultiSelect()` 调用失败\n *\n * @example\n * try {\n *   await setMultiSelect(true);\n *   console.log('已启用多选模式');\n * } catch (err) {\n *   console.error('设置多选模式失败:', err.message);\n * }\n */\nconst setMultiSelect = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setMultiSelect(enable);\n};\n\n/**\n * @async 设置 Whistle 规则引擎是否启用后置规则优先\n * @function setLaterRulesFirst\n * @description\n * 通过 Whistle API 设置当前规则引擎是否启用了后置规则优先。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {boolean} enable - 是否启用后置规则优先\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.setLaterRulesFirst()` 调用失败\n *\n * @example\n * try {\n *   await setLaterRulesFirst(true);\n *   console.log('已启用后置规则优先');\n * } catch (err) {\n *   console.error('设置后置规则优先失败:', err.message);\n * }\n */\nconst setLaterRulesFirst = async (enable) => {\n  const api = await loadApi();\n  await api.rules.setLaterFirst(enable);\n};\n\n/**\n * @async 添加一个新的规则文件到 Whistle 规则引擎中\n * @function addRule\n * @description\n * 通过 Whistle API 添加一个新的规则文件，并可选择是否立即选中（激活）该规则。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @param {string} name - 规则名称\n * @param {string} value - 规则内容\n * @param {boolean} selected - 是否选中\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.add()` 调用失败\n *\n * @example\n * try {\n *   await addRule('my-rule', '* www.example.com 127.0.0.1', true);\n *   console.log('已添加规则');\n * } catch (err) {\n *   console.error('添加规则失败:', err.message);\n * }\n */\nconst addRule = async (name, value, selected) => {\n  const api = await loadApi();\n  await api.rules.add(name, value, selected);\n};\n\n/**\n * @async 将指定规则文件移动到规则列表顶部\n * @function moveRuleToTop\n * @description\n * 通过 Whistle API 将指定规则文件移动到规则列表的顶部。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} ruleName - 规则文件的名称（需与规则列表中的名称精确匹配）\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.rules.moveToTop()` 调用失败\n *\n * @example\n * try {\n *   await moveRuleToTop('my-rule');\n *   console.log('已将规则移动到顶部');\n * } catch (err) {\n *   console.error('移动规则失败:', err.message);\n * }\n */\nconst moveRuleToTop = async (ruleName) => {\n  const api = await loadApi();\n  await api.rules.moveToTop(ruleName);\n};\n\n/**\n * @async 获取 Whistle 的 Values 列表\n * @function getValueList\n * @description\n * 通过 Whistle API 获取当前配置的 Values 列表，返回每个 Value 的名称和值。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n * \n * @returns {Promise<Array<{ name: string, value: string }>>} 返回 Values 对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.getList()` 调用失败\n *\n * @example\n * try {\n *   const values = await getValueList();\n *   console.log(`共 ${values.length} 个 Values`);\n *   values.forEach(v => console.log(`${v.name}: ${v.value}`));\n * } catch (err) {\n *   console.error('获取 Values 列表失败:', err.message);\n * }\n */\nconst getValueList = async () => {\n  const api = await loadApi();\n  return await api.values.getList();\n};\n\n/**\n * @async 获取指定的 Value\n * @function getValue\n * @description\n * 通过 Whistle API 获取指定的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @returns {Promise<{ name: string, value: string }>} 返回 Value 对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.get()` 调用失败\n *\n * @example\n * try {\n *   const value = await getValue('my-value');\n *   console.log(`${value.name}: ${value.value}`);\n * } catch (err) {\n *   console.error('获取 Value 失败:', err.message);\n * }\n */\nconst getValue = async (name) => {\n  const api = await loadApi();\n  return await api.values.get(name);\n};\n\n/**\n * @async 添加一个新的 Value\n * @function addValue\n * @description\n * 通过 Whistle API 添加一个新的 Value。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - Value 的名称\n * @param {string} value - Value 的值\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.values.add()` 调用失败\n *\n * @example\n * try {\n *   await addValue('my-value', 'some-value');\n *   console.log('已添加 Value');\n * } catch (err) {\n *   console.error('添加 Value 失败:', err.message);\n * }\n */\nconst addValue = async (name, value) => {\n  const api = await loadApi();\n  await api.values.add(name, value);\n};\n\n/**\n * @async 获取 Whistle 插件的状态信息\n * @function getPluginsStatus\n * @description\n * 通过 Whistle API 获取当前已安装插件的状态信息，包括每个插件的名称、启用状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{ name: string, moduleName: string, version: string, selected: boolean }>>} 返回插件状态对象数组，若无可返回空数组\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getStatus()` 调用失败\n * \n * @example\n * try {\n *   const plugins = await getPluginsStatus();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => console.log(`${p.name}: ${p.selected ? '启用' : '禁用'}`));\n * } catch (err) {\n *   console.error('获取插件状态失败:', err.message);\n * }\n */\nconst getPluginsStatus = async () => {\n  const api = await loadApi();\n  return await api.plugins.getStatus();\n};\n\n/**\n * @async 启用 Whistle 插件\n * @function turnOnPlugins\n * @description\n * 通过 Whistle API 启用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOn()` 调用失败\n * \n * @example\n * try {\n *   await turnOnPlugins();\n *   console.log('已启用所有插件');\n * } catch (err) {\n *   console.error('启用插件失败:', err.message);\n * }\n */\nconst turnOnPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOn();\n};\n\n/**\n * @async 禁用 Whistle 插件\n * @function turnOffPlugins\n * @description\n * 通过 Whistle API 禁用所有已安装的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<void>} 无返回值，成功时 Promise 解析为空\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.turnOff()` 调用失败\n * \n * @example\n * try {\n *   await turnOffPlugins();\n *   console.log('已禁用所有插件');\n * } catch (err) {\n *   console.error('禁用插件失败:', err.message);\n * }\n */\nconst turnOffPlugins = async () => {\n  const api = await loadApi();\n  await api.plugins.turnOff();\n};\n\n/**\n * @async 获取 Whistle 插件列表\n * @function getPluginList\n * @description\n * 通过 Whistle API 获取当前已安装的所有插件信息，包括插件元数据、版本、描述、规则配置及激活状态等。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @returns {Promise<Array<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>>} 返回插件对象数组，若无插件则返回空数组\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.getList()` 调用失败\n *\n * @example\n * try {\n *   const plugins = await getPluginList();\n *   console.log(`共 ${plugins.length} 个插件`);\n *   plugins.forEach(p => {\n *     console.log(`${p.name} (${p.version}): ${p.selected ? '启用' : '禁用'}`);\n *     console.log(`  描述: ${p.description}`);\n *   });\n * } catch (err) {\n *   console.error('获取插件列表失败:', err.message);\n * }\n */\nconst getPluginList = async () => {\n  const api = await loadApi();\n  return await api.plugins.getList();\n};\n\n/**\n * @async 获取指定的 Whistle 插件\n * @function getPlugin\n * @description\n * 通过 Whistle API 获取指定的插件信息。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<{\n *   moduleName: string,          // 插件模块名（如 'whistle.inspect'）\n *   name: string,                // 插件短名称（如 'inspect'）\n *   mtime: number,               // 插件最后修改时间（Unix 毫秒时间戳）\n *   version: string,             // 插件版本号（如 '2.3.0'）\n *   description: string,         // 插件描述信息\n *   homepage: string,            // 插件主页 URL\n *   rules: string,               // 自定义规则内容（可能为空）\n *   _rules: string,              // 内部规则表达式（通常包含动态替换逻辑）\n *   resRules: string,            // 响应规则内容（可能为空）\n *   selected: boolean            // 当前是否启用该插件\n * }>} 返回插件对象，若不存在返回 `null` 或 `undefined`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.get()` 调用失败\n * \n * @example\n * try {\n *   const plugin = await getPlugin('my-plugin');\n *   console.log(`${plugin.name}: ${plugin.enabled ? '启用' : '禁用'}`);\n * } catch (err) {\n *   console.error('获取插件失败:', err.message);\n * }\n */\nconst getPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.get(name);\n};\n\n/**\n * @async 选择指定的 Whistle 插件\n * @function selectPlugin\n * @description\n * 通过 Whistle API 选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.select()` 调用失败\n * \n * @example\n * try {\n *   const result = await selectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已选择并启用');\n *   } else {\n *     console.log('插件选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('选择插件失败:', err.message);\n * }\n */\nconst selectPlugin = async (name) => {\n  const api = await loadApi();\n  const result = await api.plugins.select(name);\n  if (result) {\n    await turnOnPlugins();\n  }\n  return result;\n};\n\n/**\n * @async 取消选择指定的 Whistle 插件\n * @function unselectPlugin\n * @description\n * 通过 Whistle API 取消选择指定的插件。\n * 依赖 `loadApi()` 加载 API 模块，若加载失败或版本过低会抛出异常。\n *\n * @param {string} name - 插件的名称\n * @returns {Promise<boolean>} 返回取消选择结果，成功返回 `true`，失败返回 `false`\n * @throws {Error} Whistle API 加载失败（如 w2 命令不可用、模块加载错误）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未运行或 `api.plugins.unselect()` 调用失败\n * @example\n * try {\n *   const result = await unselectPlugin('my-plugin');\n *   if (result) {\n *     console.log('插件已取消选择并禁用');\n *   } else {\n *     console.log('插件取消选择失败，可能不存在');\n *   }\n * } catch (err) {\n *   console.error('取消选择插件失败:', err.message);\n * }\n */\nconst unselectPlugin = async (name) => {\n  const api = await loadApi();\n  return await api.plugins.unselect(name);\n};\n\nconst URLENCODED_RE = /application\\/x-www-form-urlencoded/i;\nconst POST_RE = /^post$/i;\nconst isUrlEncoded = (req) => {\n  var type = req.headers?.['content-type'];\n  if (!type || !POST_RE.test(req.method)) {\n    return;\n  }\n  return URLENCODED_RE.test(type);\n};\n\nconst formatData = (session, utils, isRes) => {\n  let data = session[isRes ? 'res' : 'req'];\n  let body = data.body || '';\n  let raw;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      raw = raw || (isRes ? utils.getRawRes(data) : utils.getRawReq(data));\n      body = raw ? raw.substring(raw.indexOf('\\r\\n\\r\\n') + 4) : '';\n    }\n    return body;\n  };\n\n  Object.defineProperties(data, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        if (json) {\n          return json;\n        }\n        getBody();\n        if (body) {\n          if (!isRes && isUrlEncoded(data)) {\n            json = qs.parse(body);\n          } else {\n            try {\n              json = JSON.parse(body);\n            } catch (e) {}\n          }\n        }\n        json = json || {};\n        return json;\n      }\n    },\n    raw: {\n      configurable: true,\n      get() {\n        if (raw == null) {\n          raw = isRes ? utils.getRawRes(data) : utils.getRawReq(data);\n        }\n        return raw;\n      }\n    },\n  });\n};\n\nconst formatSession = (session, utils) => {\n  let timings;\n  let rules;\n  Object.defineProperties(session, {\n    timings: {\n      configurable: true,\n      get() {\n        timings = timings || utils.getTimings(session);\n        return timings;\n      }\n    },\n    rawRules: {\n      configurable: true,\n      get() {\n        rules = rules || utils.getRules(session);\n        return rules.join('\\n');\n      }\n    },\n  });\n  formatData(session, utils);\n  formatData(session, utils, false);\n  return session;\n};\n\n/**\n * @async 获取抓包数据（支持自动分页拉取）\n * @function getSessions\n * @description\n * 用于按条件检索 HTTP/WebSocket 等协议的抓包记录。支持基于游标、时间、资源类型、URL 关键词、请求方法、状态码以及请求/响应头的组合筛选。\n *\n * **过滤逻辑**：\n * - 所有非空条件均以 **AND** 关系叠加（即需同时满足）；\n * - 对于 `method` 和 `statusCode`，传入多个值时采用 **OR** 关系（匹配任一即可）；\n * - `latest` 优先级最高，一旦为 `true`，count 最多只返回 120 条最新记录；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n * - `reqHeader` 和 `resHeader` 为对象类型，支持按请求头/响应头的字段名和值进行子串匹配。\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新数据末尾。\n * - 分页游标自动使用上一页最后一条记录的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count` 和任何游标/时间参数。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新数据开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的数据通常为空。如需获取大量历史数据，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {number} [options.count=3600] - 期望获取的最大记录数。实际返回数量 ≤ 此值，且受每页 120 条限制及数据实际数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的记录。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（通常为记录的自增主键或唯一序号，格式如 `\"1784905385300-003\"`）。\n *                                            获取记录 ID **大于**此值的增量数据（即更新的数据）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级）。\n *                                       获取记录生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.type] - 资源类型过滤，同抓包界面 Network 底部过滤栏选项（不区分大小写）。\n *                                  支持以下枚举值（传入其它字符串表示不过滤）：\n *                                  - `JSON`：JSON 请求/响应\n *                                  - `HTML`：HTML 文档\n *                                  - `CSS`：样式表\n *                                  - `JS`：JavaScript 脚本\n *                                  - `Font`：字体文件\n *                                  - `Img`：图片资源\n *                                  - `Media`：音视频媒体\n *                                  - `WS`：WebSocket 帧数据\n *                                  - `Tunnel`：隧道连接（如 WebSocket 升级）\n *                                  - `Wasm`：WebAssembly 模块\n *                                  - `Mock`：Mock 数据\n *                                  - `Rules`：匹配规则的请求\n *                                  - `Import`：导入的数据\n *                                  - `Composer`：通过 Composer 发送的请求\n *                                  - `Error`：错误请求（状态码 >= 400、captureError、中断的请求等）\n *                                  - `captureError`：解析 HTTPS 失败的 Tunnel 请求\n * @param {string} [options.subUrl] - URL 中包含该字符串的请求（不区分大小写）。\n *                                    例如 `\"/api/user\"` 将匹配所有包含该路径的 URL。\n * @param {string|string[]} [options.method] - HTTP 请求方法，支持多个（数组或逗号分隔字符串，如 `\"GET,POST\"`，不区分大小写）。\n *                                             匹配任一方法即满足条件。\n * @param {number|number[]} [options.statusCode] - HTTP 响应状态码，支持多个（数组或逗号分隔字符串，如 `\"200,404\"`）。\n *                                                 匹配任一状态码即满足条件。\n * @param {Object} [options.reqHeader] - 请求头过滤条件。\n * @param {string} options.reqHeader.name - 请求头字段名称（如 `\"X-Request-Id\"`，不区分大小写）。\n * @param {string} [options.reqHeader.subValue] - 可选，请求头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该请求头字段（无论值为何）。\n * @param {Object} [options.resHeader] - 响应头过滤条件。\n * @param {string} options.resHeader.name - 响应头字段名称（如 `\"Content-Type\"`，不区分大小写）。\n * @param {string} [options.resHeader.subValue] - 可选，响应头字段值需要包含的子字符串（不区分大小写）。\n *                                                若不填，则表示仅要求存在该响应头字段（无论值为何）。\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的抓包记录数组（经过 `formatSession` 格式化处理）。\n *                                    每条记录包含以下主要字段（可能因 `formatSession` 处理而略有增减）：\n *                                    - `id` (string): 记录唯一标识，格式为 `\"时间戳-序号\"`（如 `\"1785144349945-573\"`）\n *                                    - `startTime` (number): 请求开始时间戳（Unix 毫秒级）\n *                                    - `url` (string): 完整请求 URL\n *                                    - `req` (Object): 请求详情\n *                                      - `method` (string): 请求方法\n *                                      - `ip` (string): 客户端 IP\n *                                      - `port` (string): 客户端端口\n *                                      - `headers` (Object): 请求头键值对\n *                                      - `json` (Object): 解析后的 JSON 请求体（若存在）\n *                                      - `body` (string): 原始请求体（可能为空）\n *                                      - `raw` (string): 原始请求数据\n *                                    - `res` (Object): 响应详情\n *                                      - `ip` (string): 服务器 IP\n *                                      - `port` (string): 服务器端口\n *                                      - `statusCode` (number): HTTP 状态码\n *                                      - `headers` (Object): 响应头键值对\n *                                      - `json` (Object): 解析后的 JSON 响应体（若存在）\n *                                      - `body` (string): 原始响应体（可能为空）\n *                                      - `raw` (string): 原始响应数据\n *                                    - `timings` (Object): 请求各阶段耗时（单位：毫秒）\n *                                      - `start` (number): 请求开始的时间戳（相对于某个基准点）\n *                                      - `ttfb` (number | null): 首字节时间\n *                                      - `dns` (number | null): DNS 解析耗时\n *                                      - `connect` (number | null): TCP 连接耗时（从 DNS 结束到连接建立）\n *                                      - `request` (number | null): 请求发送耗时（从 DNS 结束到请求结束）\n *                                      - `response` (number | null): 响应接收耗时（从请求结束到接收到响应头）\n *                                      - `download` (number | null): 下载耗时（从接收响应头到接收完响应体）\n *                                      - `total` (number | null): 总耗时\n *                                    - `rules` (Object): 匹配的规则对象\n *                                    - `rawRules` (string): 原始规则字符串\n *                                    - `version` (string): Whistle 版本号\n *                                    - `nodeVersion` (string): Node.js 版本号\n *                                    - `dnsTime` (number): DNS 完成时间戳\n *                                    - `requestTime` (number): 请求发送完成时间戳\n *                                    - `connectTime` (number): 连接建立完成时间戳\n *                                    - `responseTime` (number): 响应头接收完成时间戳\n *                                    - `endTime` (number): 请求结束时间戳\n *\n * @throws {Error} Whistle API 加载失败（如 `w2` 命令不可用、模块加载错误，需确保 whistle 已全局安装）\n * @throws {Error} Whistle 版本低于 2.10.7\n * @throws {Error} Whistle 服务未启动或 `api.network.getSessions()` 调用失败（如网络异常、参数错误）\n * @throws {Error} 底层 API 调用可能抛出其他运行时错误（如超时、数据格式异常）\n *\n * @example\n * // 使用游标增量拉取（最多 100 条）\n * const sessions = await getSessions({ startId: '1784905385300-003', count: 100 });\n *\n * @example\n * // 获取最新的 JSON 类型记录（最多 120 条）\n * const sessions = await getSessions({ latest: true, type: 'JSON' });\n *\n * @example\n * // 组合过滤：查询状态码为 200 或 304 的 GET 请求，且 URL 包含 \"/v1\"，同时要求存在特定请求头\n * const sessions = await getSessions({\n *   subUrl: '/v1',\n *   method: 'GET',\n *   statusCode: [200, 304],\n *   reqHeader: { name: 'X-Token', subValue: 'abc' },\n *   count: 50\n * });\n */\nconst getSessions = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const sessions = await api.network.getSessions(options);\n    const len = sessions.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatSession(sessions[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = sessions[len - 1]?.id;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n\nconst formatFrame = (frame, utils) => {\n  let body;\n  let json;\n\n  const getBody = () => {\n    if (body == null) {\n      body = utils.getText(frame);\n    }\n    return body;\n  };\n  Object.defineProperties(frame, {\n    body: {\n      configurable: true,\n      get: getBody,\n    },\n    json: {\n      configurable: true,\n      get() {\n        getBody();\n        if (body) {\n          try {\n            json = JSON.parse(getBody());\n          } catch (e) {}\n        }\n        json = json || {};\n        return json;\n      },\n    }\n  });\n  return frame;\n}\n\n/**\n * @async 获取 WebSocket/Socket 的帧数据（支持自动分页拉取）\n * @function getFrames\n * @description\n * 按条件检索指定连接（通过 `reqId` 关联）的 WebSocket/Socket 通信帧，支持基于游标、时间、来源方向的组合筛选。\n *\n * **过滤逻辑**：\n * - `from` 参数用于过滤帧的发送方向：`client`（客户端→服务端）、`server`（服务端→客户端），不传则返回全部方向；\n * - `latest` 优先级最高，一旦为 `true`，count 最大为 120；\n * - 若同时提供 `startId` 与 `startTime`，系统将在**首轮请求**中以较新者作为起始点，后续分页仅使用 `startId`（`startTime` 会被自动清除）；\n *\n * **分页机制**：\n * - 每次最多从 Whistle 服务拉取 120 条帧记录（底层 API 限制），函数会**自动循环分页**，直到满足 `count` 指定的总数或到达最新帧末尾。\n * - 分页游标自动使用上一页最后一条帧的 `id` 作为下一页的 `startId`，并清除 `startTime`，确保后续分页仅基于游标。\n * - 若 `latest=true`，则只请求一次，返回最新的若干条（最多 120 条），忽略 `count`。\n * - **重要限制**：由于 Whistle API 的 `startId` 是“**大于**”语义（即只能向更晚时间滚动），**若未提供 `startId` 或 `startTime`**，函数将从最新帧开始拉取，**只能获取第一页（最多 120 条）**，后续请求尝试获取更新的帧通常为空。如需获取大量历史帧，请先通过其他方式获取一个较早的 `startId` 作为起点。\n *\n * @param {Object} options - 配置对象\n * @param {string} options.reqId - 会话 ID，对应抓包记录的 `id` 字段，用于关联帧所属的连接\n * @param {number} [options.count=3600] - 期望获取的最大帧数。实际返回数量 ≤ 此值，且受每页 120 条限制及实际帧数量影响。若 `latest=true`，此参数无效。\n * @param {boolean} [options.latest=false] - 是否仅获取最新的帧。\n *                                           - `true`：只请求一次，返回最新的若干条（最多 120 条）；\n *                                           - `false`：配合 `startId` 或 `startTime` 进行增量拉取，并自动分页直到凑够 `count` 条或到达数据末尾。\n * @param {string|number} [options.startId] - 游标 ID（帧的唯一序号），获取帧 ID **大于**此值的增量数据（即更新的帧）。\n *                                            若同时提供 `startTime`，则首轮请求取两者中更靠近最新数据的一个作为起始点，后续分页仅使用 `startId`。\n * @param {number} [options.startTime] - 起始时间戳（Unix 毫秒级），获取帧生成时间 **大于**此值的增量数据。\n *                                       仅在首轮请求中生效（若同时提供 `startId` 则取较新者），后续分页会被清除。\n * @param {string} [options.from] - 帧的来源方向过滤（不区分大小写）：\n *                                   - `'client'`：仅获取客户端→服务端的帧\n *                                   - `'server'`：仅获取服务端→客户端的帧\n *                                   - 不传或传入其他值：获取该连接的所有帧\n *\n * @returns {Promise<Array<Object>>} 返回符合条件的帧数据数组（经过 `formatFrame` 格式化处理）。\n *                                    每条帧包含以下主要字段：\n *                                    - `reqId` (string): 所属会话 ID，对应抓包记录的 `id`\n *                                    - `frameId` (string): 帧数据的唯一标识 ID（可用于游标）\n *                                    - `mask` (boolean): 是否为掩码帧（WebSocket 掩码）\n *                                    - `compressed` (boolean): 帧数据是否经过压缩\n *                                    - `length` (number): 帧数据的原始长度（字节数）\n *                                    - `opcode` (number): 帧操作码，`1` 表示文本帧，`2` 表示二进制帧\n *                                    - `base64` (string): Base64 编码的帧数据（用于二进制或大文本）\n *                                    - `body` (string): 解码后的帧内容（文本或可读字符串）\n *                                    - `json` (Object): 若帧数据为 JSON 格式，则解析为对象，否则为空对象 `{}`\n *\n * @throws {Error} 依赖 `loadApi` 可能抛出的错误（如 Whistle 未安装、版本过低、服务未启动）\n * @throws {Error} 底层 `api.network.getFrames()` 调用失败（如 `reqId` 不存在、参数错误、网络异常）\n *\n * @example\n * // 使用游标增量拉取，获取客户端发出的前 10 条帧\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   startTime: 1784903620156,\n *   count: 10,\n *   from: 'client'\n * });\n *\n * @example\n * // 获取最新的帧（最多 120 条）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   latest: true\n * });\n *\n * @example\n * // 从指定游标开始拉取所有后续帧（直到最新）\n * const frames = await getFrames({\n *   reqId: '1784885309943-086',\n *   startId: '1784903620142-000',\n *   count: 3600\n * });\n */\nconst getFrames = async (options) => {\n  options = { ...options };\n  let count = +options.count || 3600;\n  const result = [];\n  const api = await loadApi();\n  while (count > 0) {\n    const frames = await api.network.getFrames(options);\n    const len = frames.length;\n    for (let i = 0; i < len; i++) {\n      result.push(formatFrame(frames[i], api.utils));\n    }\n    if (len < 120 || options.latest) {\n      break;\n    }\n    options.startId = frames[len - 1]?.frameId;\n    options.startTime = undefined;\n    count = count - len;\n  }\n  return result;\n};\n\n```\n\n<!-- File: docs/docs/extensions/dev.md (Tokens: ~11801 | Category: anthropic-skill) -->\n# 插件开发\n您已了解插件的各项功能特性（参考[使用文档](./usage)），现在我们将通过模块化方式演示具体实现。\n> 每个核心功能作为独立插件实现，保持单一职责原则，实际开发中可自由拼装这些功能模块。\n\n## 准备工作\n1. 创建一个空目录，如\n   ``` sh\n    mkdir examples && cd examples\n   ```\n2. 安装脚手架 [lack](https://github.com/avwo/lack)\n   ``` txt\n    npm i -g lack\n   ```\n   > lack 必须用最新版本（`>= 1.4.0`）\n3. 更新 [Whistle](https://github.com/avwo/whistle)\n   > Whistle 必须用最新版本（`>= 2.9.100`，[客户端](https://github.com/avwo/whistle-client) `>= 1.3.8`）\n\n## rules.txt\n- 插件安装后会自动加载生效\n- 功能跟界面 Rules 配置的规则一样，优先级低于界面 Rules 规则\n- 插件被禁用立即失效\n\n**示例**\n1. 创建包含 `rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules && cd whistle.test-rules\n\n    # 初始化包含 rules.txt 的插件\n    lack init rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `rules.txt` 文件：\n    ``` txt\n    * reqHeaders://x-client=whistle.test-rules\n    ```\n3. 所有经过 Whistle 的请求都会自动添加请求头 `x-client: whistle.test-rules`\n   \n   <img src=\"/img/test-rules.png\" width=\"600\" />\n4. 如果规则修改后未生效？请确认：\n    - 是否已执行 `lack watch`\n    - 是否跟界面配置冲突规则\n    - 插件是否被禁用\n\n## _rules.txt\n- 仅对匹配插件协议的请求生效\n- 支持长协议(whistle.myplugin://)和短协议(myplugin://)\n- 作用于请求处理阶段\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n1. 创建包含 `_rules.txt` 规则文件的插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-req-rules && cd whistle.test-req-rules\n\n    # 初始化包含 _rules.txt 的插件\n    lack init _rules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-req-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `_rules.txt` 文件：\n    ``` txt\n    * file://(hello)\n    ```\n    > 所有请求返回响应内容 `hello`\n4. 直接访问 `https://www.example.com/test` 返回正常页面\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/test whistle.test-req-rules://\n    ```\n    后再访问 `https://www.example.com/test` 返回 `hello`\n   \n## resRules.txt\n- 仅对匹配插件协议的请求生效\n- 作用于响应处理阶段\n- 可修改响应状态码和内容\n``` txt\npattern whistle.myplugin://value\npattern myplugin://value\n```\n\n**示例**\n\n1. 创建包含 `resRules.txt` 规则文件的插件：\n   ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules && cd whistle.test-res-rules\n\n    # 初始化包含 resRules.txt 的插件\n    lack init resRules\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules` 插件\n    lack watch\n    ```\n2. 编辑生成的 `resRules.txt` 文件：\n    ``` txt\n    * resBody://`(whistle_error_${statusCode})` includeFilter://s:500 includeFilter://s:404\n    ```\n    > 把响应状态码为 `404` 和 `500` 的请求的响应内容改为 `whistle_error_404` 和  `whistle_error_500`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://www.example.com/500 whistle.test-req-rules:// statusCode://500\n    https://www.example.com/404 whistle.test-req-rules:// statusCode://404\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/500` 响应内容被改成 `whistle_error_500`\n      - 访问 `https://www.example.com/404` 响应内容被改成 `whistle_error_404`\n      - 访问 `https://www.example.com/test` 则返回原始内容\n\n## auth\n对经过 Whistle 的请求进行鉴权。\n\n**示例**\n\n1. 创建请求鉴权插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-auth && cd whistle.test-auth\n\n    # 初始化包含 auth 的插件\n    lack init auth\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-auth` 插件\n    lack watch\n    ```\n2. 编辑 `src/auth.ts` 文件：\n    ``` ts\n    export default async (req: Whistle.PluginAuthRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // URL 里面包含 `/test/forbidden` 的响应状态码为 403\n      if (fullUrl.includes('/test/forbidden')) {\n        return false;\n      }\n      // URL 里面包含 `/test/message/forbidden` 的响应状态码为 403，且自定义响应内容\n      if (fullUrl.includes('/test/message/forbidden')) {\n        req.setHtml('<strong>Access Denied</strong>');\n        return false;\n      }\n\n      // URL 里面包含 `/test/login` 要求用户输入用户名和密码\n      if (fullUrl.includes('/test/login')) {\n        const auth = req.headers.authorization || req.headers['proxy-authorization'];\n        if (auth) {\n          // TODO: 校验用户名和密码，如果正确返回 true，否则返回 false\n          return true;\n        }\n        req.setLogin(true);\n        return false;\n      }\n\n      // URL 里面包含 `/test/redirect` 的响应状态码为 302，且重定向到 `https://www.example.com/test`\n      if (fullUrl.includes('/test/redirect')) {\n        req.setRedirect('https://www.example.com/test');\n        return false;\n      }\n      // 其它请求直接放过\n      // 如果需要添加自定义请求头，可以使用 `req.setHeader` 方法\n      // 支持添加 key 前缀为 `x-whistle-` 的请求头\n      // 例如：req.setHeader('x-whistle-xxx', 'value');\n      req.setHeader('x-whistle-custom-header', 'lack');\n      return true;\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com whistle.auth://\n    ```\n4. 效果：\n      - 访问 `https://www.example.com/test/forbidden` 响应状态码 `403`，响应内容 `Forbidden`\n      - 访问 `https://www.example.com/test/message/forbidden` 响应状态码 `403`，响应内容 `<strong>Access Denied</strong>`\n      - 访问 `https://www.example.com/test/login` 弹出浏览器的登录框\n      - 访问 `https://www.example.com/test/redirect` 重定向到 `https://www.example.com/test`\n      - 其它 `www.example.com` 的请求正常，且被添加请求头 `x-whistle-custom-header: lack`\n\n## sniCallback\n通过插件动态下发请求证书。\n\n**示例**\n\n1. 创建自定义证书插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-sni\n\n    # 初始化包含 sni 的插件\n    lack init sni\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-sni` 插件\n    lack watch\n    ```\n2. 编辑 `src/sniCallback.ts` 文件：\n    ``` ts\n    const key = `...`;\n    const cert = `...`;\n\n    // sniCallback 插件处理函数 - 根据请求 URL 动态决定 TLS 隧道处理方式\n    export default async (req: Whistle.PluginSNIRequest, options: Whistle.PluginOptions) => {\n      const { fullUrl } = req;\n      // 特殊域名返回 false，保持 TUNNEL 状态（不解除 TLS 加密）\n      if (fullUrl === 'https://tunnel.example.com') {\n        return false;\n      }\n\n      // 对特定域名使用自定义证书解密，返回一个包含 key 和 cert 的对象\n      // 支持 .crt、.pem、.cer 等格式的证书\n      if (fullUrl === 'https://custom.example.com') {\n        return { key, cert };\n      }\n      // 默认使用 Whistle 内置证书解密 TLS 流量\n      return true;\n    };\n    ```\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com sniCallback://test-sni\n    ```\n6.  效果：\n   - 访问 `https://tunnel.example.com/path`，请求保持 TUNNEL 状态（不解除 TLS 加密）\n   - 访问 `https://custom.example.com/test/` 报证书出错\n   - 其它请求正常\n\n## rulesServer\n通过 JavaScript 编程实时生成规则，规则功能跟上面的 _rules.txt 静态规则一样，但优先级高于 _rules.txt。\n\n**示例**\n\n1. 创建包含 `rulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-sni && cd whistle.test-rules-server\n\n    # 初始化包含 rulesServer 的插件\n    lack init rulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/rulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end(JSON.stringify({\n          values: {\n            'whistle.test-rules-server/a.html': 'test normal values',\n          },\n          rules: `\n            \\`\\`\\` whistle.test-rules-server/b.html\n            test inject values\n            \\`\\`\\`\n\n            */a file://{whistle.test-rules-server/a.html}\n            */b file://{whistle.test-rules-server/b.html}\n          `,\n        }));\n      });\n    };\n    ```\n    > 支持响应纯 rules 文本  `res.end(rules)`，或包含 rules & values 的序列化对象 `res.end(JSON.stringify({rules, values}))`\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com whistle.test-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/a` 返回 `test inject values`\n   - 访问 `https://test.example.com/b` 返回 `test normal values`\n\n## tunnelRulesServer\ntunnelRulesServer 是专门用于处理 TUNNEL 请求的动态规则生成机制，其核心特性与常规 [rulesServer](#rulesserver) 相同，但应用场景有专门区分：\n1. tunnelRulesServer 仅对 `tunnel://domain:port` 隧道请求生效\n2. rulesServer 对 `HTTP[S]/WebSocket` 请求生效\n\n## resRulesServer\n请求的响应阶段通过 JavaScript 编程实时生成规则（可作用于所有类型的请求），规则功能跟上面的 resRules.txt 静态规则一样，但优先级高于 resRules.txt。\n\n**示例**\n\n1. 创建包含 `resRulesServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-rules-server && cd whistle.test-res-rules-server\n\n    # 初始化包含 resRulesServer 的插件\n    lack init resRulesServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-rules-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/resRulesServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {\n        res.end('* resBody://(test-res-rules-server)');\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test.example.com/res whistle.test-res-rules-server://\n    ```\n4.  效果：\n   - 访问 `https://test.example.com/res/path` 返回 `test-res-rules-server`\n\n\n## statsServer\n如果仅想获取请求的 URL、请求方法、请求头、请求内容等信息，而不对请求做任何操作，可以用 statsServer。\n\n**示例**\n\n1. 创建包含 `statsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-stats-server && cd whistle.test-stats-server\n\n    # 初始化包含 statsServer 的插件\n    lack init statsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-stats-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/statsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Request Headers:', originalReq.headers);\n        // 获取请求的 body\n        req.getReqSession((reqSession) => {\n          if (reqSession) {\n            console.log('Request Body:', reqSession.req.body);\n          }\n        });\n      });\n    };\n    ```\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/stats whistle.test-stats-server://test\n    ```\n4.  效果：\n   - 访问 `https://www.example.com/stats` 控制台输出：\n      ``` sh\n      Value: test\n      URL: https://www.example.com/stats\n      Method: GET\n      Request Headers: {\n        host: 'www.example.com',\n        'cache-control': 'max-age=0',\n        'sec-ch-ua': '\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"',\n        'sec-ch-ua-mobile': '?0',\n        'sec-ch-ua-platform': '\"macOS\"',\n        'upgrade-insecure-requests': '1',\n        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',\n        accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',\n        'sec-fetch-site': 'cross-site',\n        'sec-fetch-mode': 'navigate',\n        'sec-fetch-user': '?1',\n        'sec-fetch-dest': 'document',\n        'accept-encoding': 'gzip, br',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',\n        priority: 'u=0, i',\n        'x-forwarded-for': '127.0.0.1',\n        connection: 'close'\n      }\n      Request Body: \n      ```\n\n## resStatsServer\n如果获取请求的响应状态码、响应头、响应内容等信息，而不对请求做任何操作，可以用 resStatsServer。\n\n**示例**\n\n1. 创建包含 `resStatsServer` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-res-stats-server && cd whistle.test-res-stats-server\n\n    # 初始化包含 resStatsServer 的插件\n    lack init resStatsServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-res-stats-server` 插件\n    lack watch\n    ```\n2. 安装依赖\n    ``` sh\n    npm i\n    ```\n3. 编辑 `src/resStatsServer.ts` 文件：\n    ``` ts\n    export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {\n      server.on('request', (req: Whistle.PluginRequest) => {\n        const { originalReq, originalRes } = req;\n        console.log('Value:', originalReq.ruleValue);\n        console.log('URL:', originalReq.fullUrl);\n        console.log('Method:', originalReq.method);\n        console.log('Server IP', originalRes.serverIp);\n        console.log('Status Code:', originalRes.statusCode);\n        console.log('Response Headers:', originalReq.headers);\n        // 获取请求的完整抓包数据\n        req.getSession((reqSession) => {\n          if (reqSession) {\n            console.log('Response Body:', reqSession.res.body);\n          }\n        });\n      });\n    };\n    ```\n4. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/res/stats whistle.test-res-stats-server://testResStats\n    ```\n5.  效果：\n   - 访问 `https://www.example.com/res/stats` 控制台输出：\n      ``` txt\n      Value: testResStats\n      URL: https://www.example.com/res/stats\n      Method: GET\n      Server IP 127.0.0.1\n      Status Code: 200\n      Response Headers: {\n        'accept-ranges': 'bytes',\n        'content-type': 'text/html',\n        etag: '\"84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134\"',\n        'last-modified': 'Mon, 13 Jan 2025 20:11:20 GMT',\n        server: 'AkamaiNetStorage',\n        expires: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'cache-control': 'max-age=0, no-cache, no-store',\n        pragma: 'no-cache',\n        date: 'Thu, 24 Jul 2025 02:36:23 GMT',\n        'alt-svc': 'h3=\":443\"; ma=93600,h3-29=\":443\"; ma=93600',\n        'x-forwarded-for': '127.0.0.1',\n        host: 'www.example.com',\n        connection: 'close'\n      }\n      Response Body: <!doctype html>\n      <html>\n      <head>\n          <title>Example Domain</title>\n\n      ......\n      ```\n\n## server\n可以将插件作为 **server** 使用，由它接收并转发请求。插件可以直接响应请求，或在处理后继续转发至目标服务器。目标服务器返回响应后，插件可以进一步处理，最终将结果返回给浏览器或客户端。\n\n**示例**\n\n1. 创建包含 `server` 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-server && cd whistle.test-server\n\n    # 初始化包含 server 的插件\n    lack init server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-server` 插件\n    lack watch\n    ```\n2. 编辑 `src/server.ts` 文件：https://github.com/whistle-plugins/examples/tree/master/whistle.test-server\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json test-server://setCookieFromBody\n    ```\n4.  访问 `https://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json`\n    - 浏览器 Cookie 新增 `test-name=whistle`\n    - 响应内容新增 `\"pluginName\":\"whistle\"`\n\n\n## pipe\n如果请求/响应内容被加密，或者需要转成特定格式显示在抓包界面，可以用 pipe 将请求/响应内容交给插件处理。\n> 在抓包界面中：\n> \n> 请求数据中无法看到 `reqWrite` 修改后的内容\n> \n> 响应数据中无法看到 `resWrite` 修改后的内容（这是预期行为）\n> \n> 所有 `xxxRexWrite` 操作的实际修改内容都不会显示在抓包界面\n> \n\n#### HTTP/HTTPS 协议实现\n1. 创建 pipe http 插件:\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-http && cd whistle.test-pipe-http\n\n    # 初始化包含 pipe http 的插件\n    lack init pipeHttp,server\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-http` 插件\n    lack watch\n    ```\n4. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-http\n5. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    www.example.com/test-pipe-http pipe://test-pipe-http test-pipe-http://mirror\n    ```\n    > `test-pipe-http://mirror` 的作用是客户端请求什么内容，服务端就响应什么内容\n6. Whistle / Network / Composer 输入 `www.example.com/test-pipe-http`，选择 `POST` 方法，Body 输入 `test-pipe-http`\n    <img src=\"/img/pipe-http.png\" width=\"400\" />\n7.  点击发送按钮，查看返回的 Body 如下\n    <img src=\"/img/pipe-http-result.png\" width=\"400\" />\n   \n\n#### WebSocket 协议实现\n1. 创建 pipe websocket 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-ws && cd whistle.test-pipe-ws\n\n    # 初始化包含 pipe websocket 的插件\n    lack init pipeWs\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-ws` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-ws\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    wss://echo.websocket.org/ pipe://test-pipe-ws\n    ```\n4.  打开页面 `https://echo.websocket.org/.ws` 可以看到如下效果：\n   <img src=\"/img/pipe-ws.png\" width=\"520\" />\n   \n\n\n#### TCP 隧道实现\n1. 创建 pipe tunnel 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-pipe-tunnel && cd whistle.test-pipe-tunnel\n\n    # 初始化包含 pipe tunnel 的插件\n    lack init pipeTunnel.server\n\n    # 安装依赖\n    npm i --save-dev lack-proxy && npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-pipe-tunnel` 插件\n    lack watch\n    ```\n2. 编辑代码：https://github.com/whistle-plugins/examples/tree/master/whistle.test-pipe-tunnel\n3. 在界面 Rules （或插件的 `rules.txt` 文件）配置以下规则：\n    ``` txt\n    test-pipe-tunnel.example.com pipe://test-pipe-tunnel test-pipe-tunnel://mirror\n    ```\n4. 执行 `whistle.test-pipe-tunnel` 根目录的 `test.js` 文件：\n    ``` sh\n    node test.js\n    ```\n5. 控制台输出的内容\n    <img src=\"/img/pipe-tunnel.png\" width=\"560\" />\n\n## 插件操作界面\n在 Whistle 插件列表中，点击 `Option` 或插件名称可快速打开插件操作界面，支持以下三种模式：\n1. **Tab 模式**：在 Whistle 界面内以标签页形式打开（默认方式）\n2. **对话框模式**：在 Whistle 界面内以弹窗形式打开\n3. **新标签模式**：在浏览器新标签页或客户端新窗口中打开\n\n---\n\n### 1. Tab 模式（默认）\n**特性**  \n- 插件管理界面默认地址为：`http[s]://domain[:port]/plugin.xxx/`  \n- 通过插件列表的 `Option` 或插件名称直接打开  \n\n**禁用 Tab 模式**  \n若需隐藏此打开方式，可在 `whistleConfig` 中配置：  \n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"noOption\": true // 禁用默认入口\n    ...\n  }\n  ...\n}\n```\n\n---\n\n### 2. 对话框模式\n**特性**  \n- 支持通过弹窗形式打开管理界面  \n- 弹窗页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/modal.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n**配置示例**  \n```js\n{\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {\n      \"width\": 360,  // 弹窗宽度（px）\n      \"height\": 566  // 弹窗高度（px）\n    }\n    ...\n  }\n}\n```\n\n---\n\n### 3. 新标签模式\n**场景**  \n需使用外部页面作为插件管理界面时使用。  \n\n**基础配置**  \n```js\n{\n   \"pluginHomepage\": \"https://your-external-page.com\" // 外部页面地址\n}\n```\n\n**高级选项**  \n若需将外部链接强制在 Whistle 内打开（Tab 或对话框模式）：  \n\n**方式一：对话框模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInModal\": {  // 强制以弹窗打开\n      \"width\": 360,\n      \"height\": 566\n    }\n    ...\n  }\n  ...\n}\n```\n\n**方式二：Tab 模式**  \n```js\n{\n  ...\n  \"pluginHomepage\": \"https://your-external-page.com\",\n  \"whistleConfig\": {\n    ...\n    \"openInPlugins\": true  // 强制在插件Tab页打开\n    ...\n  }\n  ...\n}\n```\n \n## 完整 Hooks API\n参考：https://github.com/avwo/lack/blob/master/assets/ts/src/types/global.d.ts\n\n## 插件变量配置{#pluginvars}\n支持通过 `%` 符号在规则里面配置，详见：[`%` 符号用途](../rules/plugin-vars)\n\n## 自定义规则补全功能{#rules-hint}\n综上所述，每个插件可以扩展以下规则：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\nsinCallback://myplugin(sinValue)\npipe://myplugin(pipeValue)\n```\n\nWhistle 还支持对以下规则自定义补全功能：\n``` txt\n%myplugin=xxx\n%myplugin.key=xxx\nwhistle.myplugin://xxx\nmyplugin://xxx\n```\n\n**示例**\n1. 创建 rules hints 插件：\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-rules-hint && cd whistle.test-rules-hint\n\n    # 初始化包含 uiServer 的插件\n    lack init uiServer\n\n    # 安装依赖\n    npm i\n\n    # 编译代码\n    npm run dev\n\n    # 开发模式运行：将插件挂载到 Whistle，挂载后可以在插件列表中看到 `test-rules-hint` 插件\n    lack watch\n    ```\n5. 在 `package.json` 配置 `whistleConfig`，新增 `pluginVars` 和 `hintUrl`：\n   ``` js\n    {\n      ...\n      \"whistleConfig\": {\n        \"pluginVars\": {\n          ...\n          \"hintUrl\": '/cgi-bin/plugin-vars'\n          ...\n        },\n        \"hintUrl\": '/cgi-bin/get-hints'\n      },\n      ...\n    }\n   ```\n6. 编辑 `src/uiServer/router.ts` 文件：\n    ``` ts\n    export default (router: Router) => {\n      // 针对 `%test-rules-hint[.key]=xxx`\n      router.get('/cgi-bin/plugin-vars', (ctx) => {\n        const { sep, value } = ctx.query;\n        const isKey = sep === '.';\n        let key = '';\n        let keyword = '';\n        if (value && typeof value === 'string') {\n          if (isKey) {\n            const index = value.indexOf('=');\n            // %test-plugin-vars.xxx=yyy\n            if (index !== -1) {\n              key = value.substring(0, index);\n              keyword = value.substring(index + 1).toLowerCase();\n            } else {\n              // %test-plugin-vars.xxx or %test-plugin-vars.xxx=\n              key = value;\n            }\n          } else {\n            // %test-plugin-vars=yyy\n            keyword = value.toLowerCase();\n          }\n        }\n        const result: (string | {\n          isKey: true,\n          value: string,\n        })[] = [];\n        VARS_OPTIONS.forEach((option) => {\n          if (keyword && !option.toLowerCase().includes(keyword)) {\n            return;\n          }\n          if (isKey) {\n            result.push({\n              value: `${key}=${option}`,\n              isKey: true,\n            });\n          } else {\n            result.push(option);\n          }\n        });\n        ctx.body = result;\n      });\n\n      // 针对 `test-rules-hint://xxx` 和 `whistle.test-rules-hint://xxx`\n      // 如果没有配置 `pluginVars.hintUrl`，则对 `%test-rules-hint[.key]=xxx` 也生效\n      router.get('/cgi-bin/get-hints', (ctx) => {\n        const { protocol, value } = ctx.query;\n        if (!protocol || typeof protocol !== 'string' || typeof value !== 'string') {\n          return;\n        }\n        const isVar = protocol.startsWith('%');\n        // 事实上不会有这种情况，除非删除了 `pluginVars.hintUrl` 配置\n        if (isVar) {\n          return;\n        }\n        const isLong = protocol.startsWith('whistle.');\n        const prefix = isLong ? 'long-' : 'short-';\n        const keyword = value.toLowerCase();\n        const result: string[] = [];\n        HINTS_OPTIONS.forEach((option) => {\n          if (`${prefix}${option.toLowerCase()}`.includes(keyword)) {\n            result.push(`${prefix}${option}`);\n          }\n        });\n        ctx.body = result;\n      });\n    };\n\n    ```\n9. 效果：\n   \n   <img src=\"/img/rules-hint1.png\" width=\"300\" />\n\n   <img src=\"/img/rules-hint2.png\" width=\"300\" />\n\n   <img src=\"/img/plugin-vars-hint-url1.png\" width=\"300\" />\n\n## 界面扩展\nWhistle 插件系统支持扩展以下界面功能模块：\n- **Network 模块**\n  - 数据表格列扩展\n  - 上下文菜单扩展\n  - 详情面板 Tab 扩展\n- **Rules 模块**上下文菜单\n- **Values 模块**上下文菜单\n- **Plugins 模块**上下文菜单\n\n1. 创建示例插件\n    ```sh\n    # 创建插件目录\n    mkdir whistle.test-ui-ext && cd whistle.test-ui-ext\n\n    # 初始化空白插件\n    lack init blank\n\n    # 开发模式运行\n    lack watch\n    ```\n2. 编辑 `whistleConfig`：https://github.com/whistle-plugins/examples/tree/master/whistle.test-ui-ext/package.json\n3. 执行后可在 Whistle 插件列表看到 `test-ui-ext` 插件\n    ![界面扩展效果图](/img/ui-ext.png)\n\n#### 1. Network 表格列扩展\n**配置示例**：\n```js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkColumn\": {\n      \"name\": \"Referer\",          // 列显示名称（必填）\n      \"key\": \"req.headers.referer\", // 数据字段路径（必填）\n      \"iconKey\": \"\",              // 图标字段（可选）\n      \"showTitle\": true,          // 是否显示悬浮提示（可选）\n      \"width\": 120                // 列宽（可选，默认120）\n    }\n    ...\n  }\n  ...\n}\n```\n\n**高级数据处理**：\n1. 创建 `/public/webWorker.js`：\n    ```js\n    module.exports = function(session, next) {\n      const isGithub = /^https?:\\/\\/github\\.com\\//.test(session.url);\n      next({\n        testWebWorker: 'custom_value',\n        style: isGithub ? { \n          color: '#fff',\n          fontStyle: 'italic',\n          bgColor: 'red' \n        } : null\n      });\n    };\n    ```\n2. 配置 webWorker：\n    ```js\n    {\n      \"whistleConfig\": {\n        \"networkColumn\": {\n          \"name\": \"Test\",\n          \"key\": \"customData.testWebWorker\"  // 访问 webWorker 返回数据\n          ...\n        },\n        \"webWorker\": \"/public/webWorker.js\"\n      }\n    }\n    ```\n\n#### 2. 详情面板 Tab 扩展\n```js\n{\n  \"whistleConfig\": {\n    \"inspectorsTab\": {\n      \"name\": \"Custom Tab\",       // Tab 显示名称\n      \"action\": \"/public/tab.html\", // 功能页面\n      \"icon\": \"data:image/png;base64,...\", // 图标（可选）\n      \"req\": {                    // Request 子 Tab\n        \"name\": \"Req SubTab\",\n        \"action\": \"/public/req-tab.html\"\n      },\n      \"res\": {                    // Response 子 Tab\n        \"name\": \"Res SubTab\",\n        \"action\": \"/public/res-tab.html\"\n      }\n    },\n    \"composerTab\": { /* 同 inspectorsTab */ },\n    \"toolsTab\": { /* 同 inspectorsTab */ }\n  }\n}\n```\n\n- Tab 页面可调用的 API：https://github.com/avwo/whistle/blob/master/assets/tab.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Network 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"networkMenus\": [\n      {\n        \"name\": \"Network Menu1\",\n        \"action\": \"/public/network-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Network Menu2\", // 菜单项名称\n        \"action\": \"/public/network-menu.html\", // 功能页面\n        \"required\": true, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": true, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 3. Rules 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"rulesMenus\": [\n      {\n        \"name\": \"Rules Menu1\", // 菜单项名称\n        \"action\": \"/public/rules-menu.html\", // 功能页面\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Rules Menu2\",\n        \"action\": \"/public/rules-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 4. Values 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"valuesMenus\": [\n      {\n        \"name\": \"Values Menu1\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Values Menu2\",\n        \"action\": \"/public/values-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n#### 5. Plugins 上下文菜单功能\n``` js\n{\n  ...\n  \"whistleConfig\": {\n    ...\n    \"pluginsMenus\": [\n      {\n        \"name\": \"Plugins Menu1\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      },\n      {\n        \"name\": \"Plugins Menu2\",\n        \"action\": \"/public/plugins-menu.html\",\n        \"required\": false, // 默认 false，列表模式下不是列表项的上下文菜单是否禁用该项\n        \"requiredTreeNode\": false, // 默认 false，树形模式下不是列表项的上下文菜单是否禁用该项\n        \"urlPattern\": \"\"\n      }\n    ],\n    ...\n  }\n  ...\n}\n```\n- 上下文菜单的 `action` 页面可以调用的 API：https://github.com/avwo/whistle/blob/master/assets/menu.html\n- `window.whistleBridge` API：https://github.com/avwo/whistle/blob/master/biz/webui/htdocs/src/js/bridge.js\n\n## 特殊路径\n\nWhistle 提供专用路径格式，用于在各类型页面下直接请求插件接口，以及只在 Whistle 环境下可以正常访问的路径。\n\n#### 1. Whistle 界面直接访问\n在 Whistle 界面或插件管理界面中，可直接使用以下格式访问插件接口：\n```\npath/to\n```\n\n- 此路径会自动解析为 `/whistle.xxx/path/to`\n- 推荐使用相对路径格式，避免直接使用绝对路径 `/path/to`\n\n#### 2. 普通页面访问\n若需在非 Whistle 的普通网页中调用插件接口，请使用以下特殊路径格式：\n```\n/.whistle-path.5b6af7b9884e1165./whistle.xxx/path/to\n```\n\n- 请求到达插件时会被标准化为：`/whistle.xxx/path/to`\n- 前缀 `/.whistle-path.5b6af7b9884e1165./` 是 Whistle 的专用标识符，用于标记内部请求路由\n\n#### 3. 环境自适应路径转换\n实现经过或不经过 Whistle 代理时使用不同路径，但确保后台服务接收到统一路径：\n``` txt\n/_WHISTLE_5b6af7b9884e1165_/\n```\n\n- 原始请求：`https://www.example.com/.../_WHISTLE_5b6af7b9884e1165_/path/to?query`\n- 经过 Whistle 后转换为：`https://www.example.com/.../path/to?query`\n\n#### 说明：\n- `xxx` 为您的插件名称\n- `path/to` 是插件接口的具体路径\n- 特殊路径中的哈希值 `/.whistle-path.5b6af7b9884e1165./` 为固定标识符\n\n## 其它 whistleConfig 配置\n``` js\n{\n  ...,\n  \"homepage\": '', // 插件帮助页面地址\n  \"pluginHomepage\": '', // 自定义插件操作页面地址，默认为 `/plugin.xxx/\n  \"whistleConfig\": {\n    \"hideLongProtocol\": false, // 是否隐藏插件的长协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"hideShortProtocol\": false, // 是否隐藏插件的短协议，设置为 true 后，Rules 界面里面配置该插件协议会显示已被删除\n    \"priority\": 0, // 默认值为0，插件优先级按更新时间从旧到新排序（越早更新的优先级越高），可通过此字段调整优先级顺\n    \"favicon\": '', // 插件 Tab 页上的 icon，可以为插件相对路径 `/public/xxx.png` 或绝对路径 `https://xxx` 或 `data:image/png;base64,xxx`\n    \"registry\": '', // 插件的 npm registry\n    \"noOption\": false, // 如果不存在操作界面，可以设置为 true，这样 Option 按钮会置灰\n    \"enableAuthUI\": false, // 插件的 auth hook 是否作用于插件操作界面（慎用）\n    \"tunnelKey\": '', // 字符串或字符串组，如果需要继续将请求通过 TUNNEL 代理到其它 HTTP Proxy 时，可以指定哪些请求头跟着过去\n    ...\n  },\n  ...\n}\n```\n\n## 插件页面内部路径规范\n插件页面的 URL 通常为以下格式：\n- `/plugin.xxx/path/yyy.html`\n- `/whistle.xxx/path/yyy.html`\n\n**根目录**为 `/plugin.xxx/` 或 `/whistle.xxx/`，因此：\n\n✅ 推荐使用相对路径，避免使用绝对路径（如 /path/to）。\n\n---\n\n#### **相对路径使用示例**  \n\n**场景 1：页面位于插件根目录**  \n**页面地址**：`/plugin.xxx/yyy.html`  \n**正确写法**：  \n- `./path/to`  \n- `path/to`  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n\n---\n\n**场景 2：页面位于子目录**  \n**页面地址**：`/plugin.xxx/a/b/c/yyy.html`  \n**正确写法**：  \n- `../../../path/to`（返回 3 级目录再进入目标路径）  \n\n**错误写法**：  \n- ❌ `/path/to`（绝对路径，可能访问错误）  \n- ❌ `path/to`（会基于当前目录 `a/b/c/` 查找，导致路径错误）  \n\n---\n\n**最佳实践**  \n1. **推荐使用 `./` 开头**，明确表示相对当前目录（如 `./assets/style.css`）。  \n2. **避免硬编码 `/` 开头的路径**，防止不同部署环境下路径失效。  \n3. **测试路径引用**：在本地和线上环境均验证资源加载是否正常。  \n\n---\n\n**常见问题**  \n❓ **Q：为什么不能用绝对路径？**  \n📌 **A**：插件可能部署在不同环境（如测试/生产/内嵌到其它项目中），绝对路径可能导致资源加载失败。  \n\n❓ **Q：有些构建工具，如 Vite 默认会将资源路径基于 base 设置进行解析。如果 base 设为 `/`，可能会将 `../` 优化为 `/`，如何处理？**  \n📌 **A**：尝试将 base 改成 `./`：\n  ``` js\n  // vite.config.js\n  export default defineConfig({\n    base: './', // 强制使用相对路径\n  });\n  ```\n  > 建议与后台路由配置同步优化\n\n\n## 发布插件\nWhistle 插件的发布方式与常规 NPM 包完全一致，只需遵循标准 NPM 发布流程：\n1. 登录 NPM（或企业私有源，后者需要设置企业私有源的 `npm config set registry https://xxx`）:\n    ``` sh\n    npm login\n    ```\n2. 在插件根目录执行发布命令：\n    ``` sh\n    npm publish\n    ```\n\n## 参考资料\n1. 示例源码仓库：\nhttps://github.com/whistle-plugins/examples\n1. 脚手架命令：https://github.com/avwo/lack\n\n<!-- File: docs/docs/extensions/npm.md (Tokens: ~1187 | Category: anthropic-skill) -->\n# NPM 模块\nWhistle 可作为 NPM 模块集成到项目中，**强烈建议**在独立子进程中运行 Whistle，以避免与主进程相互影响，同时便于管理。\n\n## 安装依赖\n``` sh\nnpm i --save whistle pfork\n```\n\n## 独立服务模式\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n\n    module.exports = (options, callback) => {\n      startWhistle({ port: options.port }, () => callback());\n    };\n    ```\n    > 禁用插件、自定义插件目录、修改存储目录等启动参数参考：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. 启动 Whistle 进程代码：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n      port: 8080,\n    };\n    const forkWhistle = (retry) => {\n      fork(options, (err, result, child) => {\n        if (err) {\n          // 初始化时报错，直接退出服务\n          if (!retry) {\n            throw err;\n          }\n          // 重试报错，延迟 100 毫秒再重试\n          return setTimeout(forkWhistle, 100);\n        }\n        // 子进程退出自动重试\n        child.once('close', () => forkWhistle(true));\n      });\n    };\n\n    forkWhistle();\n    ```\n\n这种方式启动的 Whistle，跟通过命令行独立启动 Whistle 或 Whistle 客户端的用法一样。\n\n\n## 内部服务模式（推荐）\n\n1. 创建 Whistle 启动脚本（`/lib/whistle.js`）：\n    ``` js\n    const startWhistle = require('whistle');\n    const http = require('http');\n\n    let curPort = 30013;\n\n    const getPort = (callback) => {\n      const server = http.createServer();\n      server.on('error', () => {\n        if (++curPort % 5 === 0) {\n          ++curPort;\n        }\n        getPort(callback);\n      });\n      server.listen(curPort, '127.0.0.1', () => {\n        server.removeAllListeners();\n        server.close(() => callback(curPort));\n      });\n    };\n\n    module.exports = (options, callback) => {\n      // 获取一个随机端口\n      getPort((port) => {\n        // 在随机端口启动 Whistle，启动成功后将启动参数传给父进程\n        const options = {\n          port,\n          host: '127.0.0.1',\n          storage: 'xxx', // 建议设置新存储目录，确保不跟其它实例的目录冲突\n        };\n        startWhistle(options, () => callback(options));\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/whistle.js\n    > \n    > Whistle 会\n2. 启动 Whistle 进程方法（`/lib/fork.js`）：\n    ``` js\n    const { fork } = require('pfork');\n    const path = require('path');\n\n    const script = path.join(__dirname, './whistle.js');\n    const options = {\n      script,\n    };\n\n    module.exports =() => {\n      return new Promise((resolve, reject) => {\n        fork(options, (err, result) => {\n          if (err) {\n            return reject(err);\n          }\n          resolve(result);\n        });\n      });\n    };\n    ```\n    > 参考：https://github.com/Tencent/nohost/blob/master/lib/main/whistleMgr.js\n3. 将项目中的指定请求转发到 Whistle：\n   ``` js\n    const { createServer } = require('http');\n    const forkWhistle = require('./fork');\n\n    const server = createServer(async (req, res) => {\n      try {\n        const { host, port } = await forkWhistle();\n        // 将请求转发到 host、port\n      } catch (e) {\n        // handle errors\n      }\n    });\n\n    server.listen(8010);\n   ```\n   > 参考：https://github.com/Tencent/nohost/blob/master/lib/index.js#L160\n\n这种方式启动的 Whistle，端口是随机的且是按需启动的，外部请求不直接访问 Whistle，需要通过所在项目的服务进行转发，用法更灵活，更推荐项目使用这种方式集成 Whistle。\n\n## 相关文档\n\n1. Whistle 的所有启动参数可查看源代码的类型定义：https://github.com/avwo/whistle/blob/master/index.d.ts\n2. pfork：https://github.com/avwo/pfork\n3. 完整示例：https://github.com/Tencent/nohost\n\n<!-- File: docs/docs/extensions/usage.md (Tokens: ~1975 | Category: anthropic-skill) -->\n# 使用插件\n\n## 安装插件\n\n1. 点击左侧导航栏的 Plugins 标签页\n2. 点击顶部的 Install 按钮\n3. 在弹出窗口中输入插件名称（支持同时安装多个插件）：\n   - 多个插件用空格或换行符分隔\n   - 可指定自定义 npm 镜像源：\n     - 直接在插件名后添加 `--registry=镜像地址`\n     - 或从下拉列表选择历史使用过的镜像源\n\n<img width=\"1000\" alt=\"install plugins\" src=\"/img/install-plugins.png\" />\n\n> 文本框中的冗余内容不会影响操作结果，Whistle 会自动过滤并仅提取插件名称及 registry 安装信息。若对话框未显示 Install 按钮，请重新安装全局 Node.js：https://nodejs.org/\n\n安装成功后，稍等片刻即可在插件列表中查看新安装的插件：\n\n<img width=\"1000\" alt=\"plugins\" src=\"/img/plugin-list.png\" />\n\n\n## 自定义协议\n\n每个插件可注册两种协议类型，配置方式跟普通协议一样：\n``` txt\n# 长协议\npattern whistle.plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n# 短协议\npattern plugin-name://value [inludeFilter://pattern1 ... excludeFilter://pattern2 ...]\n```\n\n## Hooks 功能\n\nWhistle 将匹配插件规则的请求与插件对应 Hook 进行交互，可实现以下功能：\n> 插件协议的具体功能看各个插件的帮助文档，插件也可以选择隐藏这些协议\n1. 自动生成并下发 HTTPS 证书\n2. 对代理请求的认证控制，要求用户提供凭证才能访\n3. 实时获取请求头、响应状态码等元数据\n4. 对请求动态设置规则\n5. 将请求转发给插件，由插件完全控制请求处理流程（需要匹配短协议才能实现）\n\n\n## pipe 功能\n有些请求/响应内容可能被加密或 protobuf 序列化无法看到明文，可通过以下两种方式实现内容查看与修改：\n1. **插件全权处理**\n   - 将请求直接转发至插件\n   - 插件完全控制处理流程\n   - 注意事项：\n     - Whistle 内置规则将失效\n     - 需插件自行实现所有处理逻辑\n     - 如需在抓包界面显示解密内容，插件必须调用 Whistle API 回传数据\n2. **管道流式处理（推荐）**\n   - 通过 pipe 协议建立处理管道\n   - 请求/响应流程：\n     - 进入 Whistle 时：插件解密\n     - 离开 Whistle 时：插件加密\n   - 优势：\n     - 保持明文请求特性\n     - 完整支持 Whistle 内置规则\n     - 抓包界面可直接查看明文\n\n**技术说明：**\n- pipe 流功能为可选实现，各插件支持情况请查阅具体文档\n- 方案二处理后的请求完全等同于普通明文请求\n\n## 操作界面\n\n#### 访问插件操作界面\n在 Whistle 插件管理面板中，您可以通过以下任一方式访问插件功能：\n1. 点击插件条目右侧的 `Option` 按钮\n2. 在插件列表直接单击插件名称\n\n#### 界面特性\n**呈现形式：** 模态对话框或独立标签页\n\n**核心能力：**\n1. 提供可视化配置和管理功能\n2. 支持与 Whistle 核心的实时交互\n3. 显示插件运行状态、请求统计数据、日志信息等\n\n#### 技术说明\n1. 插件本质上是与 Whistle 直接交互的 HTTP Server\n2. 操作界面的具体功能由各插件自行实现\n3. 插件可选择不提供操作界面（仅提供后台服务或[命令行操作](../cli#w2-exec)）\n\n## 扩展 Whistle 界面\n\n插件可通过以下方式增强 Whistle 的用户界面功能：\n\n#### Network 扩展\n1. 抓包列表上下文菜单\n    > 在抓包列表项右键菜单中添加自定义操作\n2. 主视图标签页\n    > 在右侧主面板区创建一级功能标签页\n2. Inspectors 分析工具扩展\n    > 在 Inspectors 面板下添加：\n    >   - 二级功能标签页\n    >   - 三级详细视图标签页\n3. Composer 请求构造器扩展\n    > 在 Composer 工具区添加二级功能标签页\n4. Tools 工具集扩展\n    > 在 Tools 功能区添加二级功能标签页\n\n#### Rules 扩展\n左侧列表上下文菜单\n> 在左侧规则文件列表的右键菜单中添加功能项\n\n#### Values 扩展\n左侧列表上下文菜单\n> 在左侧变量文件列表的右键菜单中添加功能项\n\n**实现说明：**\n- 各插件可根据功能需求选择适当的扩展位置\n- 具体扩展功能和实现方式请参阅各插件的开发文档\n- 未使用的扩展点不会影响插件基础功能\n\n## 插件内置规则\n\n插件支持通过以下规则文件扩展系统功能：\n\n1. **全局规则 (rules.txt)**\n   - 自动加载时机： 插件初始化时\n   - 作用范围： 全局请求\n   - 优先级： 低于 Rules 界面配置的规则\n   - 典型用途： 插件默认规则配置\n2. **私有规则 (_rules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 请求处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 请求预处理/改写\n3. **响应阶段规则 (resRules.txt)**\n   - 触发条件： 匹配插件自定义协议的请求\n   - 生效阶段： 响应处理流程\n   - 执行顺序： 在全局规则之后应用\n   - 典型用途： 响应后处理/改写\n\n\n插件支持灵活的内置规则配置机制：\n- **可选性：** 所有内置规则均为可选配置\n- **按需设置：** 插件只需声明必要的规则\n- **规则查看：** 通过插件管理界面的 Rules 按钮可查看当前插件配置的所有规则\n> 注意：未配置规则的插件仍可正常使用其他功能\n\n## 更新卸载插件\n\n**在 Whistle 插件管理界面中：**\n- **更新插件：** 点击插件条目右侧的 `Update` 按钮\n- **卸载插件：** 点击插件条目右侧的 `Uninstall` 按钮\n\n<!-- File: docs/docs/rules/@.md (Tokens: ~494 | Category: anthropic-skill) -->\n# `@` 符号用法\n\n当您希望将规则存放在远程服务器或本地项目文件中，并让 Whistle 自动同步更新时，可以利用规则中的 `@` 符号实现。\n\n## 语法规则\n\n## 1. 引入规则文件 (`@path`)\n动态加载外部规则文件内容，Whistle 会定时从规则文件更新内容。\n\n**使用格式：** `@文件路径或URL`\n\n**支持类型：**\n- 远程 URL：`@https://example.com/rules.txt`\n- 本地文件：`@~/projects/rules.txt`（支持所有操作系统路径）\n- 插件接口：`@whistle.nohost/api/rules`（需插件实现接口）\n\n> **Windows 路径兼容性**：支持混用 `/` 和 `\\` 作为路径分隔符：\n> ```txt\n> @C:/whistle/rules.txt\n> @D:\\config\\proxy.rules\n> ```\n\n## 配置示例\n\n### 引入远程规则文件\n```txt\n@https://raw.githubusercontent.com/your-org/configs/master/whistle-rules.txt\n```\n\n### 引入本地项目规则\n```txt\n@/Users/username/projects/my-app/whistle-config.txt\n@~/dev/configs/rules.txt  # 使用用户目录\n```\n\n### 引入插件提供的规则\n```txt\n@whistle.nohost/api/rules\n```\n\n### 混合使用示例\n```txt\n# 引入团队共享的基础规则\n@https://team.example.com/configs/base-rules.txt\n\n# 引入个人项目特定规则\n@~/projects/my-app/whistle-overrides.txt\n\n```\n\n## 文件格式要求\n\n引入的外部文件应包含标准的 Whistle 规则语法：\n\n```txt\n# 远程规则文件示例\nwww.example.com proxy://127.0.0.1:8080\napi.example.com resCors://*\nstatic.example.com cache://3600\n```\n\n<!-- File: docs/docs/rules/attachment.md (Tokens: ~1386 | Category: anthropic-skill) -->\n# attachment\n`attachment` 协议用于设置响应头中的 `Content-Disposition` 字段，使浏览器将服务器的响应直接视为附件下载，并将文件保存为 `attachment` 设定的名称，而不是在页面内展示。\n> 类似 Koa 的 `attachment` 方法：https://koajs.com/\n\n## 规则语法\n\n`attachment` 支持多种方式来指定下载文件名：\n\n### 1. 内联值（直接指定）\n直接在规则中写明文件名。\n```txt\npattern attachment://filename [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/report attachment://年度报告.pdf\n```\n\n### 2. 内嵌值（使用代码块）\n当文件名较长或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern attachment://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n季度数据-2024Q1.csv\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern attachment://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为目标文件名。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载文件名。\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **filename** | 是 | 下载时显示的文件名。<br>• 例如：`document.pdf`, `export.xlsx`。<br>• 支持通过上述三种方式（内联/内嵌/Values）指定。<br>• ⚠️ 不支持从文件或远程 URL 加载。 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n## 配置示例\n\n### 基础示例\n让访问 example.com 首页时自动下载一个名为 `example.html` 的文件。\n```txt\nhttps://www.example.com/ attachment://example.html\n```\n\n### 配合过滤器使用\n仅当请求包含特定查询参数 `download=true` 时，才触发 PDF 文件下载。\n```txt\nhttps://api.example.com/document attachment://用户手册.pdf includeFilter:///[?&]download=true/\n```\n\n### 使用内嵌值\n当文件名比较复杂或需要注释时，使用内嵌代码块更清晰。\n````txt\nhttps://assets.example.com/data.json attachment://{my-file-name}\n\n``` my-file-name\n配置备份-20240514.json\n```\n````\n\n## 实现远程加载\n\n`attachment` 协议本身不支持从文件路径或远程 URL 加载值。如果需要实现远程加载功能，有以下两种替代方案：\n\n### 1. 通过插件扩展功能\n你可以开发插件来实现更复杂的文件名获取逻辑，比如从远程API动态获取文件名。具体开发方法请参考：[插件开发文档](../extensions/dev)\n\n### 2. 使用 @url 加载完整规则\n将包含 `attachment` 规则的完整配置文件存储在远程服务器上，然后通过 [`@url`](./@) 协议加载整个规则集。\n\n**示例：**\n1. 在远程服务器创建规则文件 `xxx.txt`：\n    ```txt\n    https://api.example.com/export attachment://remote-file.csv\n    https://static.example.com/docs attachment://文档.pdf\n    ```\n2. 在本地 Rules 配置中引用远程规则：\n    ```txt\n    @https://remote-url/xxx.txt\n    ```\n\n这样，远程文件中的 `attachment` 规则就会被加载生效。\n\n## 工作原理与关联协议\n\n1.  **核心原理**：`attachment` 协议本质上是自动设置响应头。\n    上面的示例完全等价于使用 [`resHeaders`](./resHeaders) 协议手动设置：\n    ```txt\n    https://www.example.com/ resHeaders://content-disposition=attachment;filename=\"example.html\"\n    ```\n\n2.  **优势**：相比于直接使用 `resHeaders`，`attachment` 语法更简洁、易读，专注于文件下载场景，自动处理文件名转义等细节。\n\n3.  **注意**：服务器本身返回的响应体内容不会改变。此规则只是通过修改响应头，指示浏览器如何处理这些内容。\n\n<!-- File: docs/docs/rules/auth.md (Tokens: ~1780 | Category: anthropic-skill) -->\n# auth\n\n`auth` 协议用于快速修改请求头的 `Authorization` 字段，为匹配的请求自动添加 HTTP 基本认证（Basic Authentication）所需的凭证信息。\n\n## 规则语法\n\n`auth` 支持多种方式设置请求鉴权头：\n\n### 1. 内联值（直接指定）\n直接在规则中写明用户名和密码。\n```txt\npattern auth://username:password [lineProps...] [filters...]\n# 或\npattern auth://username=test&password=123 [lineProps...] [filters...]\n```\n**示例：**\n```txt\nhttps://example.com/api1/ auth://admin:secret\nhttps://example.com/api2/ auth://username=admin&password=secret\n```\n\n### 2. 内嵌值（使用代码块）\n当凭证信息较复杂或需要复用时可使用此方式。在规则中引用一个自定义键，并在随后的代码块中定义其值。\n````txt\npattern auth://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nusername: admin\npassword: my secret password\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的值。\n```txt\npattern auth://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为包含用户名和密码的对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern auth://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `auth://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report auth://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含鉴权信息的 JSON 或简单 YAML 文件。\n```txt\n# 从本地文件加载\npattern auth:///User/xxx/auth.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern auth://https://config.example.com/auth.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或简单 YAML 格式，包含 `username` 和 `password` 字段：\n```json\n{\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n或\n```yaml\nusername: admin\npassword: secret\n```\n\n## 设置 `Proxy-Authorization`\n```json\n{\n  \"proxy\": true,\n  \"username\": \"admin\",\n  \"password\": \"secret\"\n}\n```\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 鉴权凭证信息，支持多种格式：<br>• **直接格式**：`username:password` 或 `username=test&password=123`<br>• **对象格式**：包含 `username` 和 `password` 字段的对象<br>• 支持从本地文件或远程 URL 加载<br>• 支持内联、内嵌、Values、本地文件路径、远程 URL 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n## 配置示例\n\n### 基础示例\n为 example.com 的 API 接口添加基本认证：\n```txt\nhttps://api.example.com/ auth://admin:secret\n```\n\n### 使用内嵌值\n当密码包含特殊字符或空格时，使用内嵌方式更安全：\n````txt\nhttps://internal.example.com/ auth://{prod-credentials}\n\n``` prod-credentials\nusername: service-account\npassword: P@ssw0rd!2024\n```\n````\n\n### 从文件加载\n从本地配置文件加载鉴权信息：\n```txt\nhttps://example.com/api/ auth:///Users/john/config/auth.json\n```\n\n### 配合过滤器使用\n仅对 POST 请求添加鉴权：\n```txt\nhttps://example.com/api/ auth://admin:secret includeFilter://m:POST\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `api-auth` 配置：\n```txt\nhttps://example.com/api/ auth://{api-auth}\n```\n\n## 工作原理与关联协议\n\n1. **核心原理**：`auth` 协议会自动计算用户名和密码的 Base64 编码，并设置 `Authorization` 请求头。\n\n   上面的示例等价于使用 [`reqHeaders`](./reqHeaders) 协议手动设置：\n   ```` txt\n   https://example.com/api/ reqHeaders://{auth.txt} # 内容有空格，不能内联\n\n   ``` auth.txt\n   authorization: Basic YWRtaW46c2VjcmV0\n   ```\n   ````\n   其中 `YWRtaW46c2VjcmV0` 是 `admin:secret` 的 Base64 编码。\n\n2. **优势**：相比于直接使用 `reqHeaders`，`auth` 语法更简洁直观，无需手动计算 Base64 编码值。\n\n## 注意事项\n- `auth` 协议仅支持 HTTP 基本认证（Basic Authentication）\n- 对于更复杂的认证方式（如 Bearer Token、OAuth 等），请使用 [`reqHeaders`](./reqHeaders) 协议直接设置相应的 `Authorization` 头\n- 从远程 URL 加载时，请确保目标 URL 安全可靠\n\n## 关联协议 {#related}\n1. 删除请求头字段（`Delete Request Header`）：[delete://reqHeaders.xxx](./delete)\n2. 删除请求 Cookie（`Delete Request Cookie`）：[delete://reqCookies.xxx](./delete)\n3. 设置请求 Cookie：[reqCookies](./reqCookies)\n4. 设置请求鉴权头：[auth](./auth)\n5. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n\n<!-- File: docs/docs/rules/cache.md (Tokens: ~1777 | Category: anthropic-skill) -->\n# cache\n\n`cache` 协议用于快速设置响应的缓存控制头，帮助控制浏览器和服务器的缓存行为。可以为匹配的响应设置缓存策略，包括：\n- 设置固定的缓存时间\n- 禁用缓存（`no-cache`）\n- 完全禁止存储（`no-store`）\n\n这可以帮助优化网站性能或确保特定资源不会被缓存。\n\n## 规则语法\n\n`cache` 支持多种方式设置缓存策略：\n\n### 1. 内联值（直接指定）\n直接在规则中指定缓存策略。\n```txt\n# 设置缓存5秒，支持正整数、0、负整数\npattern cache://5 [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-cache`、`pragma: no-cache`\n# `no-cache` 可简写为 `no`\npattern cache://no-cache [lineProps...] [filters...]\n\n# 设置缓存头：`cache-control: no-store`、`pragma: no-cache`\npattern cache://no-store [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nhttps://example.com/api/data cache://no-cache\nhttps://example.com/static/js cache://86400  # 缓存24小时\n```\n\n### 2. 内嵌值（使用代码块）\n当需要复用缓存策略或策略较复杂时可使用此方式。\n````txt\npattern cache://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\nno-cache\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values` 中预先定义好的缓存策略。\n```txt\npattern cache://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为缓存策略。\n\n### 4. 文件/远程 URL 加载\n**当前不支持** 从本地文件或远程 URL 动态加载缓存配置。\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 缓存策略值：<br>• **数字**：缓存秒数（如 `60` 表示缓存60秒）<br>• **no-cache** 或 **no**：允许缓存但每次需重新验证<br>• **no-store**：完全禁止缓存<br>• 支持内联、内嵌、Values 引用<br>• ⚠️ 不支持从文件或远程 URL 加载 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**影响的响应头：**\n- `Cache-Control`\n- `Expires`\n- `Pragma`\n\n## 缓存策略说明\n\n| 策略 | 含义 | 典型使用场景 |\n| :--- | :--- | :--- |\n| **数字（如 `60`）** | 设置资源缓存指定秒数 | 静态资源、不经常变化的API响应 |\n| **no-cache**（或 **no**） | 允许缓存但每次使用前必须验证 | 经常变化的内容，如用户数据、实时报价 |\n| **no-store** | 完全禁止缓存任何版本 | 敏感数据、支付页面、一次性令牌 |\n\n## 配置示例\n\n### 基础示例\n```txt\n# 静态资源缓存1小时\nhttps://example.com/static cache://3600\n\n# API接口禁用缓存\nhttps://example.com/api cache://no-cache\n\n# 敏感数据禁止缓存\nhttps://example.com/account cache://no-store\n```\n\n### 使用内嵌值\n````txt\nhttps://example.com/reports cache://{report-cache-policy}\n\n``` report-cache-policy\nno-cache\n```\n````\n\n### 配合过滤器使用\n仅对特定响应状态码设置缓存：\n```txt\nhttps://example.com/api  cache://300 includeFilter://s:200\n```\n\n### 引用 Values 中的配置\n假设在 Values 中已有 `static-cache` 配置：\n```txt\nhttps://example.com/assets cache://{static-cache}\n```\n\n\n## 工作原理与关联协议\n\n1. **核心原理**：`cache` 协议自动设置相应的缓存控制头：\n\n   - `cache://5` 设置：\n     ```http\n     Cache-Control: max-age=5\n     Expires: [当前时间+5秒]\n     ```\n   \n   - `cache://no-cache` 设置：\n     ```http\n     Cache-Control: no-cache\n     Pragma: no-cache\n     ```\n   \n   - `cache://no-store` 设置：\n     ```http\n     Cache-Control: no-store\n     Pragma: no-cache\n     ```\n\n2. **关联协议**：\n   - **禁用缓存**：更彻底的缓存禁用方案，同时移除请求和响应中的缓存相关头。详见：[disable://cache](./disable)\n   - **手动设置头部**：对于更复杂的缓存控制，可以使用 [`resHeaders`](./resHeaders) 协议手动设置缓存头。\n\n## 注意事项\n- 缓存时间以秒为单位，不支持小数\n- 设置 `no-cache` 不代表\"不缓存\"，而是\"使用前必须验证\"\n- 对于需要完全禁用缓存的场景，建议同时使用 [`disable://cache`](./disable) 协议\n- 实际缓存行为还受服务器配置和浏览器实现的影响\n\n## 关联协议 {#related}\n1. 修改响应头：[resHeaders](./resHeaders)\n2. 删除请求头字段（`Delete Response Header`）：[delete://resHeaders.xxx](./delete)\n3. 删除请求 Cookie（`Delete Response Cookie`）：[delete://resCookies.xxx](./delete)\n4. 设置请求 Cookie：[resCookies](./resCookies)\n5. 设置缓存：[cache](./cache)\n6. 更灵活的修改请求头的方式：[headerReplace](./headerReplace)\n\n<!-- File: docs/docs/rules/cipher.md (Tokens: ~2974 | Category: anthropic-skill) -->\n# tlsOptions\n\n`tlsOptions` 协议用于配置 HTTPS/TLS 连接的安全参数，包括加密算法套件、客户端证书等，用于建立加密通信通道和服务器身份验证。\n\n> **版本要求**：仅 Whistle 最新版本（≥ v2.9.101）支持此功能\n\n通过 `tlsOptions` 协议，你可以：\n- 自定义 TLS 加密算法套件\n- 配置双向认证（mTLS）所需的客户端证书\n- 设置 TLS 连接的其他安全参数\n- 控制 TLS 版本和连接选项\n\n**规则合并机制**：`tlsOptions` 支持设置任意多个配置规则，Whistle 会根据从上到下的顺序自动合并这些规则，允许你灵活组合不同的 TLS 配置。\n\n## 规则语法\n\n`tlsOptions` 支持多种方式配置 HTTPS/TLS 连接的安全参数：\n\n### 1. 内联值（直接指定）\n直接在规则中写明操作。\n```txt\n# 设置客户端证书\npattern tlsOptions://key=/path/to/client.key&cert=/path/to/client.crt\n\n# 自定义加密套件\npattern tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n\n# 或使用 ciphers 参数\npattern tlsOptions://ciphers=ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384 [lineProps...] [filters...]\n```\n\n### 2. 内嵌值（使用代码块）\n当配置较复杂或需要复用时可使用此方式。\n````txt\npattern tlsOptions://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n{\n  passphrase: \"123456\",\n  pfx: \"-----BEGIN PKCS7-----\\n...\"\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的配置。\n```txt\npattern tlsOptions://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 TLS 配置对象。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern tlsOptions://temp.json\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `tlsOptions://temp.json`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report tlsOptions://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.json\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载包含 TLS 配置的 JSON 或 YAML 文件。\n```txt\n# 从本地文件加载\npattern tlsOptions:///User/xxx/tlsOptions.json\n\n# 从远程 URL 加载（支持 http 和 https）\npattern tlsOptions://https://config.example.com/tlsOptions.json\n```\n\n**文件格式要求：**\n文件内容应为 JSON 或 YAML 格式：\n```json\n{\n  \"ciphers\": \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \"minVersion\": \"TLSv1.2\",\n  \"maxVersion\": \"TLSv1.3\",\n  \"secureProtocol\": \"TLSv1_2_method\"\n}\n```\n或\n```yaml\nciphers: ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\nminVersion: TLSv1.2\nmaxVersion: TLSv1.3\nsecureProtocol: TLSv1_2_method\n```\n\n---\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | TLS 配置数据，支持多种格式：<br>• 加密算法套件名称（用 `:` 分隔）<br>• `tls.connect(options)` 参数对象<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n---\n\n## 配置对象参考\n\n完整的 TLS 连接配置对象，支持 Node.js [`tls.connect()`](https://nodejs.org/docs/latest/api/tls.html#tlscreatesecurecontextoptions) 的所有参数：\n\n```js\n{\n  // 加密算法套件\n  ciphers: \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256\",\n  \n  // TLS 协议版本控制\n  minVersion: \"TLSv1.2\",\n  maxVersion: \"TLSv1.3\",\n  secureProtocol: \"TLSv1_2_method\",\n  \n  // 证书相关配置\n  ca: \"-----BEGIN CERTIFICATE-----\\n...\",  // CA 证书\n  cert: \"-----BEGIN CERTIFICATE-----\\n...\", // 客户端证书\n  key: \"-----BEGIN PRIVATE KEY-----\\n...\",  // 私钥\n  pfx: \"-----BEGIN PKCS7-----\\n...\",       // PFX/P12 格式证书\n  passphrase: \"cert-password\",             // 证书密码\n  \n  // 其他安全选项\n  honorCipherOrder: true,     // 优先使用服务器端的加密套件顺序\n  requestCert: true,          // 请求客户端证书（双向认证）\n  rejectUnauthorized: true,   // 拒绝未授权的证书\n  \n  // 高级选项\n  ecdhCurve: \"auto\",         // ECDH 曲线\n  dhparam: \"-----BEGIN DH PARAMETERS-----\\n...\",\n  secureOptions: 0,          // SSL 选项标志\n  sessionTimeout: 300,       // 会话超时时间（秒）\n  sessionIdContext: \"whistle\"\n}\n```\n\n---\n\n## 配置示例\n\n### 1. 自定义加密套件\n限制特定域名的 TLS 加密算法：\n```txt\nwww.example.com/path tlsOptions://ECDHE-ECDSA-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384\n```\n\n> **使用场景**：解决特定服务器的兼容性问题。参考：[GitHub Issue #963](https://github.com/avwo/whistle/issues/963)\n\n### 2. 配置客户端证书（双向认证 mTLS）\n\n**cert 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=/User/xxx/test.key&cert=/User/xxx/test.crt\n```\n\n**pem 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://key=E:\\test.key&cert=E:\\test.pem\n```\n\n**pfx 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=/User/xxx/test.pfx\n```\n\n**p12 格式证书：**\n```txt\nwww.exaple.com/path tlsOptions://passphrase=123456&pfx=E:/test.p12\n```\n\n> **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 3. 内嵌证书内容\n直接将证书内容嵌入配置中：\n````txt\n``` test.json\n{\n  key: '----xxx----- ... ----xxx-----',\n  cert: '----yyy----- ... ----yyy-----'\n}\n```\n\nwww.exaple.com/path tlsOptions://{test.json}\n````\n\n### 4. 从本地或远程文件加载配置\n**从本地文件加载：**\n```txt\nwww.example.com/path1 tlsOptions:///User/xxx/test.json\n```\n\n**从远程 URL 加载：**\n```txt\nwww.example.com/path2 tlsOptions://https://www.xxx.com/xxx/params.json\n```\n\n**使用临时文件编辑：**\n```txt\nwww.example.com/path3 tlsOptions://temp/blank.json\n```\n\n### 5. 规则合并示例\n利用规则合并特性，逐步细化配置：\n```txt\n# 第一层：为整个域名设置基础TLS版本\n*.example.com tlsOptions://minVersion=TLSv1.2\n\n# 第二层：为API子域添加客户端证书\napi.example.com tlsOptions://key=/certs/client.key&cert=/certs/client.crt\n\n# 第三层：为特定路径进一步限制加密套件\napi.example.com/secure tlsOptions://ciphers=ECDHE-RSA-AES256-GCM-SHA384\n```\n\n---\n\n## 注意事项\n\n1. **版本兼容性**：确保使用的 Whistle 版本 ≥ v2.9.101\n2. **证书格式**：不同格式的证书需要使用对应的参数：\n   - PEM 格式：使用 `key` 和 `cert` 参数\n   - PFX/P12 格式：使用 `pfx` 和 `passphrase` 参数\n3. **路径处理**：\n   - 相对路径相对于 Whistle 配置文件目录\n   - Windows 路径可以混用 `/` 和 `\\`\n4. **安全警告**：\n   - 谨慎使用 `rejectUnauthorized: false`，仅限开发环境\n   - 妥善保管客户端私钥，避免泄露\n5. **性能影响**：复杂的加密算法可能影响连接性能\n6. **调试建议**：遇到连接问题时，可先用简单的配置测试，逐步添加复杂选项\n\n---\n\n## 故障排除\n\n### Q: 证书加载失败\n**A:** 检查：\n1. 证书文件路径是否正确\n2. 证书格式是否匹配参数类型\n3. PFX 证书密码是否正确\n\n### Q: 连接被拒绝\n**A:** 检查：\n1. TLS 版本是否被服务器支持\n2. 加密算法套件是否被服务器支持\n3. 客户端证书是否有效且被服务器信任\n\n### Q: 配置未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配目标 URL\n2. Whistle 版本是否满足要求\n3. 配置语法是否正确，特别是 JSON 格式\n4. 是否存在冲突的规则（规则按从上到下顺序合并）\n\n### Q: 规则合并不符合预期\n**A:** 检查：\n1. 规则的顺序是否正确（从上到下合并）\n2. 不同规则中的配置是否冲突\n3. 使用更具体的 pattern 是否覆盖了通用配置\n\n<!-- File: docs/docs/rules/cssAppend.md (Tokens: ~2085 | Category: anthropic-skill) -->\n# cssAppend\n\n`cssAppend` 协议用于在现有的 CSS 类型响应内容体后面插入指定内容。\n\n> **生效条件**：仅对响应类型 `content-type` 包含 `html` 或 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）才生效\n> \n> ⚠️ 注意：`204`、`304` 等无响应内容体的请求不受影响\n> \n> html 页面会自动包裹 `<style>...</style>`\n\n通过 `cssAppend` 协议，你可以：\n- 在现有 CSS 文件的末尾追加新的样式规则\n- 动态添加 CSS 覆盖规则\n- 为特定页面注入自定义样式\n- 在不修改源文件的情况下扩展样式功能\n\n## 规则语法\n\n`cssAppend` 支持多种方式指定要追加的内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要追加的 CSS 内容。\n```txt\n# `value` 小括号内不能用空格或换行符\npattern cssAppend://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/index.html cssAppend://(.custom-btn{background:red;})\n```\n\n### 2. 内嵌值（使用代码块）\n当要追加的 CSS 内容有空格或换行符或需要复用时可使用此方式。\n````txt\npattern cssAppend://{custom-key} [lineProps...] [filters...]\n\n``` custom-key\n.dark-mode {\n  background: #333;\n  color: #fff;\n}\n```\n````\n\n### 3. 引用 Values 中的值\n引用在界面 `Values`（中央配置区）中预先定义好的 CSS 内容。\n```txt\npattern cssAppend://{key-of-values} [lineProps...] [filters...]\n```\n**前提：** 在 `Values` 中存在一个名为 `key-of-values` 的键，其值为 CSS 内容。\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern cssAppend://temp.css\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `cssAppend://temp.css`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/test.css cssAppend://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.css\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件或远程 URL 加载\n从本地文件或远程 URL 加载要追加的 CSS 内容。\n```txt\n# 从本地文件加载\npattern cssAppend:///User/xxx/custom.css\n\n# 从远程 URL 加载（支持 http 和 https）\npattern cssAppend://https://cdn.example.com/styles/override.css\n```\n# 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要追加的 CSS 内容，支持多种格式：<br>• 纯 CSS 代码文本<br>• 支持从本地文件、远程 URL、内联、内嵌、Values 引用 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>• 例如：`lineProps://important` 可提升此规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n**特殊说明：**\n- 仅对 `content-type` 响应头包含 `css` 的请求生效\n- 对 `204`、`304` 等无响应体的状态码不生效\n- 追加的内容会直接拼接到原 CSS 文件的末尾\n\n\n## 配置示例\n\n#### 场景 1：添加响应式断点\n```` txt\nwww.example.com/responsive.css cssAppend://{test.css}\n\n``` test.css\n@media (max-width: 768px) {\n  .sidebar { display: none; }\n  .main-content { width: 100%; }\n}\n```\n\n````\n\n#### 场景 2：覆盖第三方库样式\n```` txt\ncdn.example.com/bootstrap.min.css cssAppend://{test2.css}\n\n``` test2.css\n/* 覆盖 Bootstrap 默认样式 */\n.btn-primary { background-color: #007bff !important; }\n.container { max-width: 1400px; }\n```\n````\n\n#### 场景 3：添加打印样式\n````txt\nwww.example.com/print.css cssAppend://{test3.css}\n\n``` test3.css\n@media print {\n  .no-print { display: none; }\n  a::after { content: \" (\" attr(href) \")\"; }\n}`\n```\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n<!-- File: docs/docs/rules/cssBody.md (Tokens: ~1302 | Category: anthropic-skill) -->\n# cssBody\n将现有响应内容体t替换成指定内容（（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssBody://(Hello) file://(-test-)\nwww.example.com/path2 cssBody://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssBody://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssBody://{body.txt} file://(-test-)\nwww.example.com/path2 cssBody://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `<style>Hello world.</style>`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssBody:///User/xxx/test.css\nwww.example.com/path2 cssBody://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssBody://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n<!-- File: docs/docs/rules/cssPrepend.md (Tokens: ~1346 | Category: anthropic-skill) -->\n# cssPrepend\n在现有响应内容体前面插入指定内容（仅对响应类型 `content-type` 包含 `css`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern cssPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path1 cssPrepend://(Hello) file://(-test-)\nwww.example.com/path2 cssPrepend://(Hello) file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://(Hello) file://(-test-) resType://css\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path1 cssPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 cssPrepend://{body.txt} file://(-test-) resType://js\nwww.example.com/path3 cssPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path1/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    <style>Hello world.</style>-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `Hello world.-test-`\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 cssPrepend:///User/xxx/test.css\nwww.example.com/path2 cssPrepend://https://www.xxx.com/xxx/params.css\n# 通过编辑临时文件\nwww.example.com/path3 cssPrepend://temp/blank.css\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n<!-- File: docs/docs/rules/delete.md (Tokens: ~1758 | Category: anthropic-skill) -->\n# delete\n用于删除请求 URL、请求/响应头、请求/响应内容。\n\n## 规则语法\n``` txt\npattern delete://prop1|prop2|... [filters...]\n\n# 等效于：\npattern delete://prop1 delete://prop2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | `pathname`：删除请求路径（不包含请求参数）<br/>`pathname.index`：`index` 为路径分段序号 `...、-1、0、1、...` 或关键字 `last` 详见下面说明<br/>`urlParams`：删除所有请求参数<br/>`urlParams.xxx`：删除 URL 的 `xxx 参数`<br/>`reqHeaders.xxx`: 删除请求头的 `xxx` 字段<br/>`resHeaders.xxx`: 删除响应头的 `xxx` 字段<br/>`reqBody`: 删除所有请求内容<br/>`resBody`: 删除所有响应内容<br/>`reqBody.xxx.yyy`: 删除请求内容中类型为表单(form)或JSON的 `xxx.yyy` 字段<br/>`resBody.xxx.yyy`:  删除响应类型为 JSONP 或 JSON 的响应内容里的 `xxx.yyy` 字段<br/>`reqType`: 删除请求头 `content-type` 里的类型，不包含可能存在的 charset<br/>`resType`: 删除响应头 `content-type` 里的类型，不包含可能存在的 charset<br/>`reqCharset`: 删除请求头 `content-type` 里可能存在的 charset<br/>`resCharset`: 删除响应头 `content-type` 里可能存在的 charset<br/>`reqCookies.xxx`: 删除请求头的里面名为 `xxx` 的 cookie<br/>`resCookies.xxx`: 删除响应头的里面名为 `xxx` 的 cookie|    |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\nhttps://www.example.com/path delete://reqCookies.token|resCookies.token\n\nhttps://raw.githubusercontent.com/avwo/whistle/refs/heads/master/package.json delete://resBody.name resType://json\n```\n上述 cookie 删除操作仅作用于请求/响应过程中的 cookie，不会修改浏览器本地存储的 cookie。如需修改浏览器持久化 cookie，可通过以下方式实现：\n- 通过 [jsPrepend](./jsPrepend) 等注入 JS 删除\n- 通过 [resCookies](./resCookies) 设置 cookie 过期\n\n## 删除路径\n> 支持版本：v2.9.102 及以上\n\n### 规则语法\n```\n# 基本格式\n目标域名 delete://pathname.索引号\n\n# 示例\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.-1\n```\n\n### 规则说明\n该规则用于删除请求 URL 中路径的特定分段。Whistle 会提取不包含查询参数的请求路径，并通过 `/` 分割成数组后进行删除操作。\n\n**示例解析：**\n- 请求URL：`https://www.example.com/api/path/to/xxx?query`\n- 提取路径：`api/path/to/xxx`\n- 分割数组：`['api', 'path', 'to', 'xxx']`\n- 应用规则：\n  - `delete://pathname.0` → 删除 'api'\n  - `delete://pathname.1` → 删除 'path' \n  - `delete://pathname.-1` → 删除 'xxx'\n- 最终路径：`/to`\n- 完整结果：`https://www.example.com/to?query`\n\n### 索引规则\n- **正数索引**：从 0 开始，表示从前往后的顺序\n- **负数索引**：从 -1 开始，表示从后往前的位置（-1 = 最后一段，-2 = 倒数第二段，以此类推）\n- **特殊值**：使用 `last` 可删除最后一段路径但保留末尾斜杠\n- **边界情况**：超出数组范围的索引将被忽略\n\n### 重要说明\n当路径以 `/` 结尾时，分割后的数组最后会包含一个空字符串项：\n- 路径：`/api/path/to/xxx/`\n- 分割结果：`['api', 'path', 'to', 'xxx', '']`\n\n### 使用建议\n- 使用 `pathname.-1` 删除最后一段且不保留末尾斜杠\n- 使用 `pathname.last` 删除最后一段但保留末尾斜杠\n\n**对比示例：**\n```\nwww.example.com/api/path/to delete://pathname.0 delete://pathname.1 delete://pathname.last\n```\n- 请求：`https://www.example.com/api/path/to/xxx?query`\n- 结果：`https://www.example.com/to/?query` (保留末尾斜杠)\n\n## 特殊字符\n如果对象的 key 包含以下字符，在路径表达式中需要使用反斜杠 `\\` 进行转义：\n\n- 分隔符：`|` 和 `&`\n- 空格：` `（一个空格）\n- 点：`.`\n- 控制字符：制表符 `\\t`、换行符 `\\n`、回车符 `\\r`、换页符 `\\f`、垂直制表符 `\\v`\n\n### 转义规则\n| 原始字符 | 转义写法 |\n|----------|----------|\n| `|`      | `\\|`     |\n| `&`      | `\\&`     |\n| 空格     | `\\ `     |\n| `.`      | `\\.`     |\n| 制表符   | `\\t`     |\n| 换行符   | `\\n`     |\n| 回车符   | `\\r`     |\n| 换页符   | `\\f`     |\n| 垂直制表符 | `\\v`   |\n\n> 注意：反斜杠 `\\` 本身也是转义字符，若要表示字面反斜杠，需写作 `\\\\`。\n\n### 示例\n假设需要删除请求 body 中的两个 key：\n- key1 为 `\\n .p`（包含换行符、空格、点、字母 p）\n- key2 为 `test|&test`（包含竖线和 &）\n\n对应的路径表达式应写作：\n```\nreqBody.\\n\\ \\.p.test\\|\\&test\n```\n其中：\n- `\\n` 表示换行符\n- `\\ ` 表示空格\n- `\\.` 表示点\n- `\\|` 表示竖线\n- `\\&` 表示 & 符号\n\n完整请求示例：\n```\n# 删除请求 body 中的 key1 和 key2\nhttps://www.example.com/path delete://reqBody.\\n\\ \\.p.test\\|\\&test\n```\n\n<!-- File: docs/docs/rules/disable.md (Tokens: ~2177 | Category: anthropic-skill) -->\n# disable\n通过规则禁用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern disable://action1|action2|... [filters...]\n\n# 等效于：\npattern disable://action1 disable://action2 ... [filters...]\n```\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n- `301`：禁止 `301` 跳转，`301` 强制转 `302`（也可以用 [replaceStatus](./replaceStatus)）\n- `abort`：禁用 abort 功能，详见：[enable](./enable)\n- `abortReq`：禁用 abortReq 功能，详见：[enable](./enable)\n- `abortRes`：禁用 abortRes 功能，详见：[enable](./enable)\n- `authCapture`：禁用 `authCapture` 功能，详见：[enable](./enable)\n- `auto2http`：禁用 auto2http 功能，详见：[enable](./enable)\n- `autoCors`：使用 [file](./file) 协议替换请求时，如果 Whistle 检测到该请求属于跨域请求，会自动添加必要的 CORS (跨域资源共享) 头信息，可以通过 `disable://autoCors` 禁用\n- `ajax`: 删除请求头 `x-requested-with`\n- `bigData`：禁用 bigData 功能，详见：[enable](./enable)\n- `capture` 或 `https`：禁用 `Enable HTTPS`，详见：[enable](./enable)\n- `captureIp`：禁用 captureIp 功能，详见：[enable](./enable)\n- `captureStream`：禁用 captureStream 功能，详见：[enable](./enable)\n- `clientCert`：禁用 clientCert 功能，详见：[enable](./enable)\n- `clientId`：删除请求头 `x-whistle-client-id`\n- `clientIp`：删除请求头 `x-forwarded-for`\n- `customParser`：禁用 customParser 功能，详见：[enable](./enable)\n- `cache`: 禁用缓存\n- `dnsCache`: 禁止 DNS 缓存\n- `csp`: 禁用 CSP\n- `cookies`: 禁用请求和响应的 cookie\n- `reqCookies`: 禁用请求的 cookie\n- `resCookies`: 禁用响应的 cookie\n- `flushHeaders`：禁用 flushHeaders 功能，详见：[enable](./enable)\n- `forHttp`：禁用 forHttp 功能，详见：[enable](./enable)\n- `forHttps`：禁用 forHttps 功能，详见：[enable](./enable)\n- `forceReqWrite`：禁用 forceReqWrite 功能，详见：[enable](./enable)\n- `forceResWrite`： 禁用 forceResWrite 功能，详见：[enable](./enable)\n- `gzip`：禁止压缩响应内容\n- `h2`：禁用 h2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器启用 HTTP2\n- `http2`：禁用 http2 功能，详见：[enable](./enable)\n  > 禁用：浏览器 -xx-> Whistle 代理 -xx-> 服务器全部启用 HTTP2\n- `httpH2`：禁用 httpH2 功能，详见：[enable](./enable)\n  > 禁用：Whistle 代理 -xx-> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：禁用 hide 功能，详见：[enable](./enable)\n- `hideComposer`：禁用 hideComposer 功能，详见：[enable](./enable)\n- `hideCaptureError`：禁用 hideCaptureError 功能，详见：[enable](./enable)\n- `interceptConsole`：禁用 interceptConsole 功能，详见：[enable](./enable)\n- `internalProxy`：禁用 internalProxy 功能，详见：[enable](./enable)\n- `proxyFirst`：禁用优先使用 [proxy](./proxy) 规则\n- `proxyHost`：禁用 proxyHost 功能，详见：[enable](./enable)\n- `proxyTunnel`：禁用 proxyTunnel 功能，详见：[enable](./enable)\n- `keepCSP`：禁用 keepCSP 功能，详见：[enable](./enable)\n- `keepAllCSP`：禁用 keepAllCSP 功能，详见：[enable](./enable)\n- `keepCache`：禁用 keepCache 功能，详见：[enable](./enable)\n- `keepAllCache`：禁用 keepAllCache 功能，详见：[enable](./enable)\n- `keepAlive`：禁止缓存请求连接\n- `keepClientId`：禁用 keepClientId 功能，详见：[enable](./enable)\n- `keepH2Session`：无需每个 TUNNEL 连接对应一个 HTTP2 连接（默认 `是`，建议使用默认即可）\n- `safeHtml`：禁用 safeHtml 功能，详见：[enable](./enable)\n- `strictHtml`：禁用 strictHtml 功能，详见：[enable](./enable)\n- `proxyConnection`：通过 proxy、socks 等代理协议转发请求时设置 `proxy-connection: close`\n- `ua`: 删除请求头 `user-agent`\n- `proxyUA`：通过 proxy、socks 等代理协议转发请求时删除请求头 `user-agent`\n- `referer`: 禁用 `referer`\n- `rejectUnauthorized`：`rejectUnauthorized` 设置为 `true`\n- `requestWithMatchedRules`：禁用 requestWithMatchedRules 功能，详见：[enable](./enable)\n- `responseWithMatchedRules`：禁用 responseWithMatchedRules 功能，详见：[enable](./enable)\n- `secureOptions`：禁用建立 HTTP2 连接的默认 `options`\n- `timeout`：禁用请求超时设置\n- `trailerHeader`：删除响应头 `trailer`\n- `trailers`：删除 trailers\n- `tunnelAuthHeader`：删除代理鉴权头 `proxy-authorization`\n- `tunnelHeadersFirst`：禁用 tunnelHeadersFirst 功能，详见：[enable](./enable)\n- `useLocalHost`：禁用 useLocalHost 功能，详见：[enable](./enable)\n- `useSafePort`：禁用 useSafePort 功能，详见：[enable](./enable)\n- `userLogin`：禁用设置 [statusCode://401](./statusCode) 时显示登录框\n- `useSniAsTunnelDomain`：禁用 `enable://useSniAsTunnelDomain`\n- `weakRule`：禁用 weakRule 功能，详见：[enable](./enable)\n\n## 配置示例\n``` txt\n# 禁用请求的缓存，同时删除请求和响应头的缓存字段\n# 跟 cache 协议的区别是，cache 只是用来设置响应的缓存头\nwwww.example.com/path disable://cache\n\n# 禁用请求和响应的 cookies\nwwww.example.com/path disable://cookies\n\n# 只禁用请求的 cookies\nwwww.example.com/path disable://reqCookies\n\n# 只禁用响应的 cookies\nwwww.example.com/path disable://resCookies\n\n# 删除 user-agent\nwwww.example.com/path disable://ua\n\n# 删除 referer\nwwww.test.com/path disable://referer\n\n# 删除csp策略\nwwww.test.com/path disable://csp\n\n# 禁用 timeout，默认情况下 Whistle 对每个请求如果 36s 内没有发生数据传输，会认为请求超时\nwwww.test.com/path disable://timeout\n\n# 把 301 转成 302，防止 cache\nwwww.test.com/path disable://301\n\n# 禁用 HTTPS 拦截\nwwww.test.com/path disable://capture\n\n# 不缓存 DNS 结果\nwwww.test.com/path disable://dnsCache\n\n# 禁用代理服务器请求链接复用\nwwww.test.com/path disable://keepAlive\n\n# 删除请求头 `x-requested-with`\nwwww.test.com/path disable://ajax\n\n# 也可以同时禁用多个\nwww.example.com/path disable://cache|cookies|ua|referer|csp|timeout|301|intercept|dnsCache|keepAlive|autoCors\n```\n\n关联操作：[enable](./enable)\n\n<!-- File: docs/docs/rules/enable.md (Tokens: ~3470 | Category: anthropic-skill) -->\n# enable\n通过规则启用 HTTPS、隐藏请求、终止请求等功能。\n\n## 规则语法\n``` txt\npattern enable://action1|action2|... [filters...]\n\n# 等效于：\npattern enable://action1 enable://action2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| action  | 具体动作，详见下面的说明 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n- `abort`：请求或响应阶段中断请求（根据匹配阶段）\n- `abortReq`：请求阶段中断请求\n- `abortRes`：响应阶段中断请求\n- `authCapture`：强制在转成 HTTPS 之前执行 [auth hook](../extensions/dev#auth)（默认是转成 HTTPS 请求后再执行插件的 auth hook）\n- `auto2http`：开启 HTTPS 请求报 TLS 错误自动转成 HTTP 请求，默认情况下如果 serverIP 是本地 IP 会自动启用\n- `bigData`：扩大抓包数据显示限制(2M→16M)\n- `emudhra`：使用已公开泄露的 eMudhra 中间 CA 私钥签发动态生成的 TLS 证书，并发送完整证书链\n- `br`：启用 BR 压缩响应内容\n- `gzip`：启用 GZIP 压缩响应内容\n- `deflate`：启用 Deflate 压缩响应内容\n- `capture` 或 `https`：Enable HTTPS（同 [HTTPS菜单功能](../gui/https.html)）\n- `captureIp` ：域名为 IP 的请求，默认不解密 HTTPS 请求，可以通过 `enable://captureIp` 启用解析 HTTPS 请求\n- `captureStream`：将抓取到的请求与响应内容以数据流的形式，实时输出到抓包界面并动态追加显示\n- `clientCert`：启用客户端与服务器之间的双向认证 (mTLS)\n- `clientId`：请求头带上 `x-whistle-client-id: Whistle本地生成的唯一ID`\n- `clientIp`：为匹配的非本地请求设置 `x-forwarded-for` 请求头，将客户端的真实IP地址透传给上游服务\n- `customParser`：自定义抓包界面显示内容，用法参考插件：https://github.com/whistle-plugins/whistle.custom-parser\n- `flushHeaders`：`response.writeHead(...)` 后调用 [response.flushHeaders](https://nodejs.org/docs/latest/api/http.html#responseflushheaders)（默认执行）\n- `forHttp`：让 `capture` 功能只对 HTTP 请求生效\n- `forHttps`：让 `capture` 功能只对 HTTPS 请求生效\n- `forceReqWrite`：使用 [reqWrite](./reqWrite)、[reqWriteRaw](./reqWriteRaw) 将请求数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceReqWrite` 强制覆盖\n- `forceResWrite`： 使用 [resWrite](./resWrite)、[reqWriteRaw](./reqWriteRaw) 将响应数据写入本地文件时，如果对应的文件已存在，默认跳过写入操作以保护现有文件，可以通过 `enable://forceResWrite` 强制覆盖\n- `h2`：Whistle 代理 -> 服务器启用 HTTP2\n- `http2`：浏览器 -> Whistle 代理 -> 服务器全部启用 HTTP2\n- `httpH2`：Whistle 代理 -> 服务器的 HTTP 请求启用 HTTP2\n- `hide`：在界面上隐藏抓包数据（不包括 `captureError` 和 Composer 发出的请求）\n- `hideComposer`：隐藏 Composer 发出的请求\n- `hideCaptureError`：隐藏 `captureError` 请求\n- `showHost`：将服务器 IP 设置到响应头 `x-host-ip`\n- `ignoreSend`：WebSocket 和 TUNNEL 请求时忽略发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `ignoreReceive`：WebSocket 和 TUNNEL 请求时忽略接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseSend`：WebSocket 和 TUNNEL 请求时暂停发送数据帧（TUNNEL 请求要启用 `inspect`）\n- `pauseReceive`：WebSocket 和 TUNNEL 请求时暂停接收数据帧（TUNNEL 请求要启用 `inspect`）\n- `inspect`：使的在 Inspectors / Frames 看到 TUNNEL 请求的内容\n- `interceptConsole`：截获 `console.xxx` 的请求并显示在 Whistle 管理界面的 Log 面板（默认开启）\n- `internalProxy`：利用 `proxy`、`socks` 等代理协议将请求转发至其他代理服务器（如另一 Whistle 实例）。启用此功能后，已在第一层代理解密的 HTTPS 请求将以明文形式在代理链中传输，从而上游代理可以直接获取明文数据\n- `proxyFirst`：优先使用 [proxy](./proxy) 规则（默认情况下，同时匹配 `host` 和 `proxy`，只有 `host` 生效）\n- `proxyHost`：[proxy](./proxy) 和 [host](./host) 同时生效\n- `proxyTunnel`：跟 `proxyHost` 一同使用，让上游代理再次通过隧道代理到上上游的 HTTP 代理，详见下面的示例\n- `keepCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头大 `csp` 字段，如果想保留这些字段可以用 `enable://keepCSP`\n- `keepAllCSP`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的 `csp` 字段，如果想保留这些字段可以用 `enable://keepAllCSP`\n- `keepCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepCache`\n- `keepAllCache`：通过 `htmlXxx`/`jsXxx`/`cssXxx`/`weinre`/`log` 注入内容时会自动删除响应头的缓存字段，如果想保留原有的缓存头可以用 `enable://keepAllCache`\n- `keepClientId`：保留请求原有的 `x-whistle-client-id` 请求头（默认会删除请求带过来的  `x-whistle-client-id`）\n- `safeHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `{` 和 `[`（JSON 对象开头字符），如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `strictHtml`：是一种安全防护机制，当使用 `htmlXxx`/`jsXxx`/`cssXxx` 向 HTML 页面注入内容时，会先检查响应内容的第一个非空白字符是否为 `<`，如果不是才会执行注入操作。这可以有效防止对非标准 HTML 响应（如 JSON 接口）的误注入\n- `multiClient`：Whistle 作为公共代理且启用 `enable://clientId` 时会为所有请求添加一个固定的 `x-whistle-client-id` 请求头，这导致上游服务无法区分不同客户端。启用 `enable://multiClient` 后，将为每个客户端连接生成并维持一个唯一且不变的标识符，确保上游服务能准确识别请求来源\n- `requestWithMatchedRules`：在请求头带上当前匹配的规则\n- `responseWithMatchedRules`：在响应头带上当前匹配的规则\n- `tunnelHeadersFirst`：用于控制请求头合并的优先级。插件可通过 [tunnelKey](../extensions/dev) 将隧道（TUNNEL）请求头传递至后续阶段。默认的合并规则是：若隧道头与解析后的普通请求头存在同名键，则保留普通请求头的值。启用 `enable://tunnelHeadersFirst` 可改变这一行为，确保隧道请求头优先，从而强制覆盖任何冲突的普通头\n- `useLocalHost`：修改 `log` 和 `weinre` 请求 `URL` 的域名，使用内置域名\n- `useSafePort`：修改 `log` 和 `weinre` 请求 `URL` 的端口，使用内置端口\n- `userLogin`：设置 [statusCode://401](./statusCode) 是否显示登录框（默认显示）\n- `useSniAsTunnelDomain`：当 Tunnel 域名与请求中的 SNI 不一致时，使用 SNI 中携带的域名作为 Tunnel 域名\n- `weakRule`：默认情况下，当配置了 [file](./file) 等协议时，[proxy](./proxy) 规则会自动失效。通过设置 `weakRule` 属性，可以提升 [proxy](./proxy) 规则的优先级，使其在上述场景中仍然生效\n- `socket`：在启用 HTTPS 解析（`Enable HTTPS` 或 `enable://https`）后，发往 `80/443` 端口的 TUNNEL 请求会被强制尝试解析为 HTTP/HTTPS 流量。默认情况下，若解析失败，该连接将被销毁；而其他端口的请求解析失败则会继续以 TUNNEL 方式传输。通过设置 `enable://socket`，可让发往 `80/443` 端口的请求在解析失败时同样降级为 TUNNEL 连接，避免连接被销毁\n- `websocket`：用于处理非标准 WebSocket 连接。某些请求虽使用 WebSocket 协议传输，但其 Upgrade 请求头并非标准值（如 `Upgrade: websocket`）。默认情况下，Whistle 会将其视为普通 TCP 连接而不解析数据。启用 `enable://websocket` 可强制 Whistle 识别此类连接为 WebSocket 协议并进行数据解析\n\n\n## 配置示例\n``` txt\n# Enable HTTPS\nwww.example.com enale://https\n\n# 延迟 3000毫秒终止请求\nwww.example.com/path reqDelay://3000 enable://abortReq\n\n# 延迟 5000毫米终止响应\nwww.example.com/path resDelay://5000 enable://abortRes\n\n# 本地替换的内容开启 GZIP\nwww.example.com/path file:///User/xxx/test enable://gzip\n\n# 给上游代理设置 hosts (10.10.10.20:8888)\nwww.example.com/path proxy://10.1.1.1:8080 10.10.10.20:8888 enable://proxyHost\n\n# 通过上游 HTTP 代理 (10.1.1.1:8080) 将请求通过隧道代理到指定的 HTTP 代理(10.10.10.20:8080)\nwww.example.com proxy://10.1.1.1:8080 10.10.10.20:8080 enable://proxyHost|proxyTunnel\n\n# 启用浏览器 -> Whistle 代理 -> 服务器整个链路的 HTTP2 功能\nwww.example.com enable://http2\n\n# 启用 Whistle 代理 -> 服务器的 HTTP2 功能\nwww.example.com enable://h2\n\n# 强制 Whistle 代理 -> 服务器的 HTTP 请求使用 HTTP2 传输\nwww.example.com enable://httpH2\n\n# 安全注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `{` 才注入\nwww.example.com/path enable://safeHtml\n\n# 严格HTML注入模式：当使用 htmlXxx/jsXxx/cssXxx 注入指令时，检测响应首字符不是 `<` 才注入\nwww.example.com/path enable://strictHtml\n\n# 自动添加 x-forwarded-for 请求头传递客户端真实 IP\nwww.example.com enable://clientIp\n\n# 扩大抓包数据显示限制(2M→16M)\nwww.example.com/path enable://bigData\n\n# 修改 log/weinre 请求 `URL` 的域名或端口\nwww.example.com/path enable://useLocalHost|useSafePort\n\n# 强制 reqWrite/reqWriteRaw/resWrite/resWriteRaw 覆盖已有的文件\nwww.example.com/path enable://forceReqWrite|forceResWrite\n\n# 强制 HTTPS 请求被解析前也走 `auth hook`（默认是转成 HTTPS 请求后再执行插件的 auth hook）\nwww.example.com enable://authCapture\n```\n\n关联操作：[disable](./disable)\n\n<!-- File: docs/docs/rules/excludeFilter.md (Tokens: ~334 | Category: anthropic-skill) -->\n# excludeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation excludeFilter://p1 excludeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 且不能匹配 `p1`、`p2`、... 中的任一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求不生效\nwww.example.com/api/data proxy://127.0.0.1:8080 excludeFilter://m:GET excludeFilter://m:post\n```\n\n可以跟 [includeFilter](./includeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n\n<!-- File: docs/docs/rules/file.md (Tokens: ~3419 | Category: anthropic-skill) -->\n# file\n\n`file` 协议用于将请求映射到本地文件系统，用本地文件的内容作为响应返回给客户端，适用于以下场景：\n\n- **搭建本地开发环境**：快速搭建本地开发服务器\n- **调试本地前端页面**：直接调试本地 HTML、CSS、JavaScript 文件\n- **接口 Mock 场景**：使用本地 JSON 文件模拟 API 接口响应\n- **静态资源服务**：将本地目录作为静态资源服务器使用\n\n## 规则语法\n\n`file` 协议支持多种方式指定本地文件内容：\n\n### 1. 内联值（直接指定）\n直接在规则中写明要返回的内容，适用于简短的文本内容。注意：`value` 不能包含空格和换行符。\n\n```txt\npattern file://(value) [lineProps...] [filters...]\n```\n\n**示例：**\n```txt\nwww.example.com/api/data file://({\"status\":\"ok\"})\n```\n\n**注意：内联值必须用括号 `()` 包裹，否则会被识别为本地文件路径，可能导致 404 错误：**\n``` txt\n# 正确写法：返回字符串 \"success\"\npattern file://(success)\n\n# 错误写法：会被当作路径处理，可能导致 404\npattern file://success\n```\n\n> **file 协议的内联值语法要点**：\n> - 必须使用 `()` 包裹内容\n> - 内容不能包含空格和换行符\n> - 可以是字符串、JSON、HTML片段等\n> - 如果不加 `()`，Whistle 会将其解释为文件路径或 `{key}` 并尝试加载对应文件或 key 值\n\n### 2. 内嵌值（使用代码块）\n当需要处理包含空格、换行符的复杂内容，或希望复用某段配置时，推荐使用此方式。\n\n````txt\npattern file://{custom-key.json} [lineProps...] [filters...]\n\n``` custom-key.json\n{\n  \"status\": \"success\",\n  \"data\": {\n    \"message\": \"Hello from local file\"\n  }\n}\n```\n````\n\n**最佳实践**：建议为 `key` 添加响应类型后缀（如 `.json`、`.html`、`.css`），Whistle 会自动根据后缀设置正确的 `Content-Type`。\n\n### 3. 引用 Values 中的值\n当内容较大时，可以将其存储在 `Values` 配置区中。\n\n```txt\npattern file://{key-of-values.html} [lineProps...] [filters...]\n```\n\n**前提**：在 `Values` 中存在名为 `key-of-values.html` 的键，其值为要返回的内容。\n\n**容量建议**：\n- 小于 2KB 的内容：建议使用内联或内嵌方式\n- 2KB 至 200KB 的内容：建议存储在 `Values` 中\n- 大于 200KB 的内容：建议使用本地文件\n\n### 4. 从临时文件中加载\n当需要频繁编辑内容时，可以使用 Whistle 提供的临时文件功能。\n\n```txt\npattern file://temp.html\n```\n\n**操作步骤**：\n1. 在 Rules 编辑器中，按住 `Command`（Mac）/ `Ctrl`（Windows）\n2. 鼠标点击 `file://temp.html`\n3. 在弹出的编辑对话框中输入响应内容\n4. 点击 `Save` 保存\n\n保存后规则会自动变为类似以下格式：\n```txt\nhttps://example.com/report file://temp/11adb9c9e1142df67b30d7646ec59bcd34c855d9011d1a2405c7fc2dfc94568d.html\n```\n\n需要再次编辑时，用同样的方式点击该临时文件链接即可。\n\n### 5. 从文件路径或远程 URL 加载\n从本地文件系统或远程 URL 加载响应内容。\n\n```txt\n# 从本地文件加载\npattern file:///User/xxx/test.html\n\n# 从远程 URL 加载\npattern file://https://example.com/template.html\n```\n\n\n\n## 参数详解\n\n| 参数 | 是否必填 | 描述与示例 |\n| :--- | :--- | :--- |\n| **pattern** | 是 | 用于匹配请求 URL 的表达式。<br>• 支持域名、路径、通配符、正则表达式。<br>• 详见 [匹配模式文档](./pattern)。 |\n| **value** | 是 | 要返回的文件内容或路径，支持多种格式：<br>• 本地文件或目录路径<br>• 远程 URL<br>• 内联、内嵌、Values 引用内容 |\n| **lineProps** | 否 | 为规则设置附加属性。<br>**示例**：<br>• `lineProps://important`：提升规则优先级<br>• `lineProps://weakRule`：当同时配置 [file](./file) 和 [proxy](./proxy) 规则时，默认 [proxy](./proxy) 会失效。通过此属性可提升 [proxy](./proxy) 规则的优先级。<br>• 详见 [lineProps 文档](./lineProps)。 |\n| **filters** | 否 | 可选的过滤条件，用于精确控制规则生效的场景。<br>• 可匹配请求的 URL、方法、头部、体内容。<br>• 可匹配响应的状态码、头部。<br>• 详见 [过滤器文档](./filters)。 |\n\n\n\n## 配置示例\n\n### 1. 基础文件路径映射\n```txt\n# 将域名映射到本地目录\nwww.example.com/path file:///Users/username/projects/my-site\n\n# Windows 系统路径\nwww.example.com/path file://D:\\projects\\my-site\n```\n\n**特性说明**：\n- **自动路径拼接**：访问 `https://www.example.com/path/x/y/z` 会映射到 `/Users/username/projects/my-site/x/y/z`\n- **禁用路径拼接**：使用 `< >` 包裹路径可以禁用自动拼接\n    ```txt\n    www.example.com/path file://</Users/username/projects/my-site/index.html>\n    ```\n    > 访问 `https://www.example.com/path/x/y/z` 只会返回 `/Users/username/projects/my-site/index.html` 文件\n\n### 2. JSONP 接口 Mock\n````txt\n# 内联方式\nwww.example.com/jsonp file://`(${query.callback}({\"status\":\"ok\"}))`\n\n# 内嵌方式\n``` jsonp-response\n${query.callback}({\n  \"status\": \"error\",\n  \"message\": \"Invalid parameter\"\n})\n```\nwww.example.com/jsonp file://`{jsonp-response}`\n````\n\n> **模板字符串限制**：模板字符串在以下场景无法直接生效：\n> - 引用本地文件路径时\n> - 引用远程 URL 地址时\n> \n> 遇到上述限制时，可以使用 [tpl](./tpl) 协议作为替代方案。\n\n### 3. 配合过滤器使用\n```txt\n# 排除特定接口\nwww.example.com/api/path file:///Users/username/mock-data excludeFilter://*/api/auth\n\n# 根据请求体内容匹配\nwww.example.com/api/search file:///Users/username/search-results.json includeFilter://b:/\"type\":\\s*\"advanced\"/i\n\n# 仅匹配 POST 请求\nwww.example.com/api/submit file:///Users/username/success.json includeFilter://m:POST\n```\n\n### 4. 多目录搜索\n```txt\n# 多个文件或目录用 `|` 分隔\nwww.example.com/static/path file:///path/to/project1|/path/to/project2|/path/to/project3\n```\n\n**查找逻辑**：\n1. 按顺序检查每个目录\n   - `/path/to/project1/static/file.js`\n   - `/path/to/project2/static/file.js`\n   - `/path/to/project3/static/file.js`\n2. 找到第一个存在的文件立即返回\n3. 全部未找到时返回 `404 Not Found`\n\n## 高级用法\n\n### 动态路径处理\n```txt\n# 使用正则表达式捕获组\n/^https?://www\\.example\\.com/user/(\\d+)/profile file:///Users/username/mock/profiles/user-$1.json\n\n# 如果不限制数字，可以用通配符\n^www.example.com/user/*/profile file:///Users/username/mock/profiles/user-$1.json\n```\n\n### 环境特定配置\n````txt\n``` dev-config\n{\n  \"apiBase\": \"http://localhost:3000\",\n  \"debugMode\": true\n}\n```\n\n``` prod-config\n{\n  \"apiBase\": \"https://api.example.com\",\n  \"debugMode\": false\n}\n```\n\n# 通过 cookie：env=dev 判断是否为开发环境\nwww.example.com/config file://{dev-config} includeFilter://reqH:cookie=/env=dev/\nwww.example.com/config file://{prod-config} # 默认正式环境\n````\n\n### 组合使用其他协议\n```txt\n# 先使用 file 提供静态文件，再设置响应头\nwww.example.com file:///Users/username/static-files cache://3600\n\n# 对于不存在的文件，使用 xfile 允许请求继续\nwww.example.com xfile:///Users/username/static-files\n```\n\n## 与 resBody 的区别\n\n`file` 协议与 [`resBody`](./resBody) 协议的主要区别在于请求处理流程和响应机制：\n\n**`file` 协议：***\n- **请求流程**：匹配 `file` 协议的请求**不会发送到后台服务器**\n- **响应机制**：直接返回指定文件内容，并响应 200 状态码\n- **网络开销**：零网络延迟，完全本地处理\n\n**示意图**：\n```\n客户端请求 → Whistle (匹配file规则) → 读取本地文件 → 返回响应 (200)\n                  ↓\n            (不发送到服务器)\n```\n\n**`resBody` 协议：**\n- **请求流程**：请求**会先发送到后台服务器**，获取原始响应\n- **响应机制**：收到服务器响应后，用指定内容**替换**原始响应体\n- **网络开销**：包含完整的请求-响应网络往返\n\n**示意图**：\n```\n客户端请求 → Whistle → 发送到服务器 → 收到原始响应 → 替换响应体 → 返回修改后的响应\n```\n\n## 关联协议\n\n1. **允许未匹配文件继续访问**：[xfile](./xfile)\n   - 当本地文件不存在时，允许请求继续访问原始服务器\n   - 适合静态资源服务器的开发场景\n\n2. **使用远程 URL 内容替换**：[https](./https) 或 [http](./http)\n   - 使用远程服务器的内容作为响应\n   - 注意：不建议使用 `file://https://xxx` 这种形式\n\n3. **域名解析重定向**：[host](./host)\n   - 修改域名解析，将请求导向指定 IP\n\n4. **模板渲染**：[tpl](./tpl)\n   - 支持更复杂的模板渲染和动态内容生成\n\n\n\n## 注意事项\n\n### 路径处理\n1. **相对路径**：除了插件内部的规则外，不建议使用相对路径。相对路径相对于 Whistle 配置文件所在目录。\n2. **绝对路径**：支持系统绝对路径\n3. **用户目录**：支持 `~` 表示用户主目录\n4. **Windows 路径**：支持混用 `/` 和 `\\` 作为路径分隔符\n\n### 文件编码\n1. **文本文件**：默认使用 UTF-8 编码\n2. **编码问题**：如遇乱码，请检查文件的实际编码格式\n\n\n## 故障排除\n\n### Q: 文件未找到（404）\n**A:** 检查：\n1. 文件路径是否正确\n2. 文件是否存在且有读取权限\n3. 是否使用了 `< >` 禁用了路径拼接\n\n### Q: 乱码问题\n**A:** 检查：\n1. 文件的实际编码格式\n2. 响应头的 `Content-Type` 是否正确\n3. 文件内容是否包含非法字符\n\n### Q: 规则未生效\n**A:** 检查：\n1. 规则 pattern 是否正确匹配\n2. 是否有其他规则覆盖\n3. 过滤器条件是否满足\n\n### Q: 模板字符串不生效\n**A:** 检查：\n1. 是否在文件路径或远程 URL 场景下使用模板字符串\n2. 如果是，请改用 [tpl](./tpl) 协议\n\n\n\n## 扩展阅读\n\n- [匹配模式文档](./pattern)：详细了解 URL 匹配规则\n- [操作指令文档](./operation)：了解内容加载的多种方式\n- [附加配置](./lineProps)：详细了解一些特殊功能\n- [过滤器](./filters)：了解更多过滤器功能\n\n<!-- File: docs/docs/rules/filters.md (Tokens: ~853 | Category: anthropic-skill) -->\n# 过滤器\n\n当需要基于请求属性或响应属性（而不仅仅是URL）进行匹配时，可以使用过滤器来实现更精细化的规则控制，语法结构：\n\n``` txt\npattern opertaion includeFilter://pattern1 ... excludeFilter://patternx ...\n```\n> 多个过滤器间为「或」匹配，只要匹配其中一个过滤条件就成立\n\n## 过滤器类型\n\n| 过滤器类型     | 语法格式                  | 用途                     |\n| -------------- | ------------------------- | ------------------------ |\n| **包含过滤器** | `includeFilter://pattern` | 只匹配符合指定条件的请求 |\n| **排除过滤器** | `excludeFilter://pattern` | 排除符合指定条件的请求   |\n\n## pattern 类型\n\n用于 `includeFilter://<pattern>` 和 `excludeFilter://<pattern>` 的匹配表达式。\n\n> **通用规则**\n> - 未指定前缀时，默认匹配请求 URL。\n> - 关键字匹配默认不区分大小写。\n> - 正则格式为 `/regexp/`，末尾可加 `i` 表示忽略大小写，例如 `/regexp/i`。\n> - `chance` 的 `probability` 取值范围为 `0 ~ 1`。\n\n| 语法 | 匹配目标 | 示例 |\n| --- | --- | --- |\n| `b:pattern` | 请求体内容：关键字（不区分大小写）或正则 | `includeFilter://b:keyword`<br>`excludeFilter://b:/regexp/i` |\n| `m:pattern` | HTTP 方法：方法名（不区分大小写）或正则 | `includeFilter://m:get`<br>`excludeFilter://m:/regexp/i` |\n| `i:pattern` | 客户端或服务端 IP：IP 或正则 | `includeFilter://i:192.168.1.10`<br>`excludeFilter://i:/regexp/i` |\n| `chance:probability` | 按概率随机命中：`Math.random() < probability` | `includeFilter://chance:0.5`<br>`excludeFilter://chance:0.3` |\n| `clientIp:pattern` | 客户端 IP：IP 或正则 | `includeFilter://clientIp:192.168.1.10`<br>`excludeFilter://clientIp:/regexp/i` |\n| `serverIp:pattern` | 服务端 IP：IP 或正则 | `includeFilter://serverIp:192.168.1.10`<br>`excludeFilter://serverIp:/regexp/i` |\n| `s:pattern` | 响应状态码：状态码或正则 | `includeFilter://s:/^20/`<br>`excludeFilter://s:200` |\n| `reqH.header-key:pattern` | 请求头：关键字（不区分大小写）或正则 | `includeFilter://reqH.content-type:json`<br>`excludeFilter://reqH.content-type:/regexp/i` |\n| `resH.header-key:pattern` | 响应头：关键字（不区分大小写）或正则 | `includeFilter://resH.content-type:json`<br>`excludeFilter://resH.content-type:/regexp/i` |\n| 无前缀 `pattern`（其它 `xxxxxx`） | 请求 URL，同 [pattern](./pattern) | `includeFilter://*/cgi-*`<br>`excludeFilter://www.test.com`<br>`includeFilter://https://www.test.com/path` |\n\n<!-- File: docs/docs/rules/forwardedFor.md (Tokens: ~314 | Category: anthropic-skill) -->\n# forwardedFor\n修改请求头 `x-forwarded-for` 字段，自定义客户端 IP。\n\n## 规则语法\n``` txt\npattern forwardedFor://ip [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| ip   | 自定义客户端 IP<br/>⚠️ 不支持从文件/远程 URL 加载数据 | |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 设置固定 IP\nwww.example.com/path  forwardedFor://1.1.1.1\n\n# 使用真实客户端 IP（透传模式）\nwww.example.com  forwardedFor://`${clientIp}`\n```\n\n## 关联协议\n1. 直接修改请求头：[reqHeaders://x-forwarded-for=value](./reqHeaders)（这种方式的 `value` 可以不局限于 IP）\n2. 启用自动设置 `x-forwarded-for`：[enable://clientIp](./enable)\n\n<!-- File: docs/docs/rules/frameScript.md (Tokens: ~836 | Category: anthropic-skill) -->\n# frameScript\n通过 JavaScript 脚本操作 WebSocket 和普通 TCP 请求数据帧。\n\n## 规则语法\n``` txt\npattern frameScript://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 生成规则的 JS 脚本，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n```` txt\n``` test-frame.js\n// 发送预设数据\nctx.sendToServer('1 = 0x12300000');\nctx.sendToClient('1 = 0x1236666666');\n\n// 处理发往服务端的数据帧\nctx.handleSendToServerFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '***');\n};\n\n// 处理发往客户端的数据帧  \nctx.handleSendToClientFrame = function(buf, opts) {\n  // 可以返回空、null、undefined 等\n  return (buf + '').replace(/1/g, '+++');\n};\n```\n\nwss://echo.websocket.org/ frameScript://{test-frame.js}\n````\n访问 `https://echo.websocket.org/.ws` 效果：\n\n <img width=\"1200\" alt=\"frame-script\" src=\"/img/frame-script.png\" />\n\n#### 可用全局变量\n\n| 变量/方法          | 描述                                                                 |\n|--------------------|---------------------------------------------------------------------|\n| `url`             | 完整请求URL                                                         |\n| `method`          | 请求方法(GET/POST等)                                                |\n| `ip`/`clientIp`   | 客户端IP地址                                                       |\n| `headers`         | 请求头对象                                                          |\n| `rules`           | 规则数组，通过push添加新规则                                        |\n| `values`          | 临时值存储对象                                                      |\n| `render(tpl,data)`| 微型模板渲染函数                                                    |\n| `getValue(key)`   | 获取Values中的值                                                    |\n| `parseUrl`        | 同Node.js的`url.parse`                                              |\n| `parseQuery`      | 同Node.js的`querystring.parse`                                      |\n| `ctx.sendToClient(frame, options)` | 发送数据（对象、字符串、Buffer）到客户端，用法见下面示例 |\n| `ctx.sendToServer(frame, options)` | 发送数据（对象、字符串、Buffer）到服务端，用法见下面示例 |\n| `ctx.destroy()` | 断开当前连接（`v2.10.11` 新增） |\n| `handleSendToClientFrame(buffer, options)` | 处理（过滤或删除）发送到客户端的数据，用法见下面示例 |\n| `handleSendToServerFrame(buffer, options)` | 处理（过滤或删除）发送到服务端的数据，用法见下面示例 |\n\n> 注意：`handleSendToClientFrame` 内不可调用 `ctx.sendToClient`；`handleSendToServerFrame` 内不可调用 `ctx.sendToServer`\n\n<!-- File: docs/docs/rules/headerReplace.md (Tokens: ~425 | Category: anthropic-skill) -->\n# headerReplace\n通过关键字匹配或正则表达式替换指定请求头/响应头。\n\n## 规则语法\n``` txt\npattern headerReplace://value [filters...]\n```\n> `header-name` 不区分大小写\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 三种情况：<br/>• `req.header-name:p1=v1&p2=v2`<br/>• `res.header-name:p1=v1&p2=v2`<br/>• `trailer.trailer-name:p1=v1&p2=v2` | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 请求头 `accept` 字段值的第一个 `html` 关键字被改成 `abc`\nwww.example.com/path headerReplace://req.accept:html=abc\n\n# 请求头 `accept` 字段值的所有 `ml` 关键字被改成 `abc`\nwww.example.com/path2 headerReplace://req.accept:/ml/g=abc\n\n# 修改响应头\nwww.example.com/path3 headerReplace://res.Content-Type:html=plain\n```\n\n`headerReplace` 是用来替换请求/响应头的局部内容，如果要修改请求/响应字段内容，还可以用：\n- 设置请求头：[reqHeaders](./reqHeaders)\n- 设置响应头：[resHeaders](./resHeaders)\n\n<!-- File: docs/docs/rules/host.md (Tokens: ~1239 | Category: anthropic-skill) -->\n# host\n修改请求的 DNS 解析结果，将指定请求解析到特定 IP 地址（域名）及端口，可以看作终极版的系统 hosts 配置功能。\n\n## 规则语法\n``` txt\npattern host://ipOrDomain[:port] [filters...]\n```\n> `port` 如果不填，则沿用请求 URL 的原始端口，如果是指向域名则相当于 `cname` 功能\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 如果只是 IP 或端口可以省略 `host://`\nwww.example.com/test0 127.0.0.1      # 不改端口，沿用请求 URL 的原始端口\nwww.example.com/test1 127.0.0.1:5173\n\n# CNAME 功能\nwww.example.com/test2 host://www.test.com\nwww.example.com/test3 host://www.test.com:8080\n\n# 高级配置，从请求参数获取目标地址，includeFilter 确保存在该参数\nwww.example.com/test4 host://`${query.target}$:8080` includeFilter:///[?&]target=[\\w-]+/i\n```\n## 与 proxy 的匹配优先级\n以下是优化后的文档，结构更清晰，语言更简洁准确：\n\n---\n\n## 与 host 的匹配优先级\n\n#### 默认行为\n\n当请求同时匹配 `host` 和 `proxy` 规则时：\n- 仅 `host` 规则生效\n- `proxy` 规则自动忽略\n\n#### 修改优先级\n| 配置方式 | 语法 | 效果 |\n|---------|------|------|\n| **优先 proxy** | [`enable://proxyFirst`](./enable) 或 [`lineProps://proxyFirst`](./lineProps) | 仅 `proxy` 生效（覆盖 host） |\n| **同时生效** | [`enable://proxyHost`](./enable) 或 [`lineProps://proxyHost`](./lineProps) | `proxy` 和 `host` 同时生效 |\n\n#### 使用建议\n- 大多数场景使用默认行为即可\n- 需要特殊代理逻辑时才使用 `proxyFirst`\n- 需要双重匹配时使用 `proxyHost`\n\n## 注意事项\n`host` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com 127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求指向 `127.0.0.1:1234`，但由于 `host` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `host` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com 127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `host` 规则匹配，最终指向 `127.0.0.1:1234`。\n\n\n## 常见问题\n1. 与 URL 转换的区别：\n    ``` txt\n    # 服务端收到的 URL 还是 www.example.com\n    www.example.com 127.0.0.1:5173\n\n    # 服务端收到的 URL 是 http://127.0.0.1:5173\n    www.example.com http://127.0.0.1:5173\n    ```\n2. 自动降级为 HTTP 请求：如果配置的目标 IP 为 `127.0.0.1`，且 HTTPS 请求报错会自动降级为 HTTP 请求，方便访问本地服务，用户可以通过以下规则禁用该功能：\n    ``` txt\n    # 禁用本地 HTTPS 自动降级\n    pattern disable://auto2http\n    ```\n\n<!-- File: docs/docs/rules/htmlAppend.md (Tokens: ~1546 | Category: anthropic-skill) -->\n# htmlAppend\n在现有响应内容体后面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlAppend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlAppend://(Hello) file://(-test-)\nwww.example.com/path2 htmlAppend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlAppend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlAppend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `-test-Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlAppend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlAppend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlAppend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlAppend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `<div>Test</div>test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlAppend:///User/xxx/test.txt\nwww.example.com/path2 htmlAppend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlAppend://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n\n<!-- File: docs/docs/rules/htmlBody.md (Tokens: ~1568 | Category: anthropic-skill) -->\n# htmlBody\n\n将现有响应内容体t替换成指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlBody://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlBody://(Hello) file://(-test-)\nwww.example.com/path2 htmlBody://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlBody://{body.txt} file://(-test-)\nwww.example.com/path2 htmlBody://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成 `Hello world.`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlBody://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlBody://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlBody://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlBody://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成 `test`\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlBody:///User/xxx/test.txt\nwww.example.com/path2 htmlBody://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlBody://temp/blank.txt\n````\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n<!-- File: docs/docs/rules/htmlPrepend.md (Tokens: ~1626 | Category: anthropic-skill) -->\n# htmlPrepend\n在现有响应内容体前面面插入指定内容（仅对响应类型 `content-type` 包含 `html`，且包含响应内容体的状态码（如 `200`/`500` 等）有才效）\n> ⚠️ 注意：204、304 等无响应内容体的请求不受影响\n\n## 规则语法\n``` txt\npattern htmlPrepend://value [filters...]\n```\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 文本或二进制内容，支持以下类型：<br/>• 目录/文件路径<br/>• 远程 URL<br/>• 内联/内嵌/Values内容 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n#### 内联方式\n``` txt\nwww.example.com/path htmlPrepend://(Hello) file://(-test-)\nwww.example.com/path2 htmlPrepend://(Hello) file://(-test-) resType://js\n```\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 内嵌/Values方式\n```` txt\n``` body.txt\nHello world.\n```\nwww.example.com/path htmlPrepend://{body.txt} file://(-test-)\nwww.example.com/path2 htmlPrepend://{body.txt} file://(-test-) resType://css\n````\n- 请求 `https://www.example.com/path/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    Hello world.-test-\n    ```\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `-test-`\n\n#### 规避不规范的请求\n当接口响应类型（`Content-Type`）不规范地返回为 `text/html` 时，可能导致：\n- 前端误将接口数据当作 HTML 解析\n- 注入内容破坏原始数据结构\n- 引发前端解析错误\n\n使用 enable://strictHtml 或 enable://safeHtml 模式保护非 HTML 内容：\n``` txt\nwww.example.com/path1 htmlPrepend://(test) file://(-test-) enable://strictHtml\nwww.example.com/path2 htmlPrepend://(test) file://([-test-])  enable://strictHtml\nwww.example.com/path3 htmlPrepend://(test) file://([-test-])  enable://safeHtml\nwww.example.com/path4 htmlPrepend://(test) file://(<div>Test</div>) enable://strictHtml\n```\n- 请求 `https://www.example.com/path1/to` 响应内容变成 `-test-`\n- 请求 `https://www.example.com/path2/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path3/to` 响应内容变成 `[-test-]`\n- 请求 `https://www.example.com/path4/to` 响应内容变成\n    ``` html\n    <!DOCTYPE html>\n    test<div>Test</div>\n    ```\n\n`safeHtml`/`strictHtml` 功能参考：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n#### 本地/远程资源\n\n```` txt\nwww.example.com/path1 htmlPrepend:///User/xxx/test.txt\nwww.example.com/path2 htmlPrepend://https://www.xxx.com/xxx/params.txt\n# 通过编辑临时文件\nwww.example.com/path3 htmlPrepend://temp/blank.txt\n````\n\n\n## 关联协议 {#related}\n\n1. 在响应内容前注入内容（`Prepend To Body`）：[resPrepend](./resPrepend)\n2. 在响应内容前注入 HTML 内容（`Prepend HTML To Body`，响应类型必须为 `text/html`）：[htmlPrepend](./htmlPrepend)\n3. 在响应内容前注入 CSS 内容（`Prepend CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssPrepend](./cssPrepend)\n4. 在响应内容前注入 JS 内容（`Prepend JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsPrepend](./jsPrepend)\n5. 替换响应内容（`Replace Body`）：[resBody](./resBody)\n6. 用 HTML 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`）：[htmlBody](./htmlBody)\n7. 用 CSS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssBody](./cssBody)\n8. 用 JS 内容替换响应内容（`Replace Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsBody](./jsBody)\n9. 在响应内容后追加内容（`Append To Body`）：[resAppend](./resAppend)\n10. 在响应内容后追加 HTML 内容（`Append HTML To Body`，响应类型必须为 `text/html`）：[htmlAppend](./htmlAppend)\n11. 在响应内容后追加 CSS 内容（`Append CSS To Body`，响应类型必须为 `text/html` 或 `text/css`）：[cssAppend](./cssAppend)\n12. 在响应内容后追加 JS 内容（`Append JS To Body`，响应类型必须为 `text/html`、`text/css` 或 `application/javascript`）：[jsAppend](./jsAppend)\n13. 使用关键字或正则表达式替换响应内容（`Modify Body Text`）：[resReplace](./resReplace)\n14. 覆盖响应内容中的 JSON/Form 对象（`Modify Form/JSON`）：[resMerge](./resMerge)\n15. 删除响应内容中的 JSON/Form 对象属性（`Delete Form/JSON`）：[delete://resBody.xxx](./delete)\n16. 校验 HTML 内容格式：[enable://safeHtml](./enable)、[lineProps://strictHtml](./lineProps)\n\n<!-- File: docs/docs/rules/http.md (Tokens: ~1010 | Category: anthropic-skill) -->\n# http\n将以下三种请求转换为 HTTP 请求（服务端将收到转换后的 HTTP URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern http://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 http://www.test.com/path/xxx\nhttps://www.example.com/path2 http://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `http://www.example.com/path1`              | `http://www.test.com/path/xxx`             |\n    | `http://www.example.com/path1/a/b/c?query`  | `http://www.test.com/path/xxx/a/b/c?query` |\n    | `https://www.example.com/path2`            | `http://www.abc.com/path3/yyy`             |\n    | `https://www.example.com/path2/a/b/c?query` | `http://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 http://<www.test.com/path/xxx>\n    # www.example.com/path1 http://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `http://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 http://www.test.com/path/xxx\nwss://www.example.com/path2 http://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `ws://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `ws://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 http://www.test.com:123\ntunnel://www.example2.com:443 http://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:80`|\n\n自动忽略匹配 URL 的路径，HTTP 协议默认端口为 `80`，HTTPS 协议默认端口为 `443`\n\n<!-- File: docs/docs/rules/https-proxy.md (Tokens: ~994 | Category: anthropic-skill) -->\n# https-proxy\n`https-proxy` 指令用于将匹配的请求通过指定的 HTTPS 代理服务器转发。\n\n## 规则语法\n``` txt\npattern https-proxy://ipOrDomain[:port] [filters...]\n```\n> `port` 可选，不填则使用默认端口 `443`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | IP + 可选端口 或域名 + 可选端口<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 将请求代理到 HTTPS 代理: `127.0.0.1:443`\nwww.example.com/path https-proxy://127.0.0.1 # 默认端口 443\n\n# 将当前域名的所有请求代理到 HTTPS 代理: `127.0.0.1:8080`\nwww.example.com https-proxy://127.0.0.1:8080\n\n# 也可以用域名\nwww.example.com/path https-proxy://test.proxy.com # 默认端口 443\nwww.example.com https-proxy://test.proxy.com:8080\n```\n\n## 高级用法\n默认情况下，上游代理会自行解析请求的域名。但某些场景下，你可能希望强制代理直接访问指定的目标 IP（跳过 DNS 解析），例如：\n- 绕过 DNS 污染\n- 直接访问特定后端 IP\n- 测试不同环境的服务\n``` txt\n# 通过查询参数\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1\nwww.example.com https-proxy://127.0.0.1:8080?host=1.1.1.1:8080\n\n# 通过指令启用\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1 enable://proxyHost\nwww.example.com https-proxy://127.0.0.1:8080 1.1.1.1:8080 enable://proxyHost\n```\n> `1.1.1.1` 等价于 `host://1.1.1.1`\n\n## 注意事项\n`https-proxy` 协议仅对经过规则替换后生成的 `Final URL`（可在 Overview 面板中查看）生效。若 `Final URL` 为空，则会作用于原始请求的 URL。\n\n例如以下规则：\n\n``` txt\nwww.example.com/api www.example.com https-proxy://127.0.0.1:1234\n```\n\n当请求 `https://www.example.com/api/path` 时，Whistle 会先将其转换为 `https://www.example.com/path`（该结果即为 `Final URL`）。此时希望将该请求代理到 `127.0.0.1:1234`，但由于 `https-proxy` 规则仅匹配替换前的原始域名 `www.example.com/api`，而转换后的 `Final URL` 已经是 `www.example.com/path`，因此无法命中这条 `https-proxy` 规则。\n\n若需要对替换后的请求也生效，可拆解为两条规则：\n\n``` txt\nwww.example.com/api www.example.com\nwww.example.com https-proxy://127.0.0.1:1234\n```\n\n这样，原始请求先被第一条规则重写，生成新的 `Final URL`，然后再被第二条 `https-proxy` 规则匹配，最终代理到 `127.0.0.1:1234`。\n\n<!-- File: docs/docs/rules/https.md (Tokens: ~1003 | Category: anthropic-skill) -->\n# https\n将以下三种请求转换为 HTTPS 请求（服务端将收到转换后的 HTTPS URL）：\n1. **隧道代理：** `tunnel://domain:port`\n    > 示例：`tunnel://www.test.com:443`\n2. **WebSocket：** `ws[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`wss://www.test.com/path?a=1&b=2`\n3. **普通 HTTP/HTTPS：** `http[s]://domain[:port]/[path/to[?query]]`\n    > 示例：`https://www.test.com/path?a=1&b=2`\n\n## 规则语法\n``` txt\npattern https://value [filters...]\n```\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 目标 URL 格式：`domain[:port]/[path][?query]`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## HTTP/HTTPS 转换\n``` txt\nhttp://www.example.com/path1 https://www.test.com/path/xxx\nhttps://www.example.com/path2 https://www.abc.com/path3/yyy\n```\n1. 自动路径拼接：\n     | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n     | ----------------------------------------- | ----------------------------------------- |\n     | `http://www.example.com/path1`              | `https://www.test.com/path/xxx`             |\n     | `http://www.example.com/path1/a/b/c?query`  | `https://www.test.com/path/xxx/a/b/c?query` |\n     | `https://www.example.com/path2`            | `https://www.abc.com/path3/yyy`             |\n     | `https://www.example.com/path2/a/b/c?query` | `https://www.abc.com/path3/yyy/a/b/c?query` |\n2. 禁用路径拼接：使用 `< >` 或 `( )` 包裹路径\n    ``` txt\n    www.example.com/path1 https://<www.test.com/path/xxx>\n    # www.example.com/path1 https://(www.test.com/path/xxx)\n    ```\n    | 原始请求                                  | 转换结果（服务端收到的 URL）              |\n    | ----------------------------------------- | ----------------------------------------- |\n    | `[http/https/wss/ws]://www.example.com/path/x/y/z` | `https://www.test.com/path/xxx` |\n\n## WebSocket 转换\n``` txt\nws://www.example.com/path1 https://www.test.com/path/xxx\nwss://www.example.com/path2 https://www.abc.com/path3/yyy\n```\nWebSocket 请求替换成指定的 ws 请求：\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `ws://www.example.com/path1`              | `wss://www.test.com/path/xxx`             |\n| `wss://www.example.com/path2/a/b/c?query`  | `wss://www.abc.com/path3/yyy/a/b/c?query`|\n\n同样也支持**自动路径拼接**和**禁用路径拼接**。\n\n## TUNNEL 转换\n``` txt\ntunnel://www.example.com:443 https://www.test.com:123\ntunnel://www.example2.com:443 https://www.test2.com/path\n```\n| 原始请求                                  | 转换结果（服务端收到的 URL）              |\n| ----------------------------------------- | ----------------------------------------- |\n| `tunnel://www.example.com:443`              | `tunnel://www.test.com:123`             |\n| `tunnel://www.example2.com:443`  | `tunnel://www.test2.com:443`|\n\n⚠️ 自动忽略匹配 URL 的路径，HTTPS 协议默认端口为 `443`\n\n<!-- File: docs/docs/rules/ignore.md (Tokens: ~453 | Category: anthropic-skill) -->\n# ignore\n用于忽略指定协议的匹配规则，也可以忽略当前配置的匹配规则。\n\n## 规则语法\n``` txt\npattern ignore://p1|p2|... [filters...]\n# 等效于：\npattern ignore://p1 ignore://p2 ... [filters...]\n```\n\n> 分隔符为 `|` 或 `&`\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| value   | 操作协议（`protocol`）名称<br/>`*` 表示所以协议<br/>`-p` 表示排除 `p`<br/>⚠️ 不支持从文件/远程 URL 加载数据 | [操作协议列表](./protocols)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n## 配置示例\n``` txt\n# 忽略所有规则，只保留 `file` 协议的规则\nwww.example.com/path ignore://*|-file\n\n# 忽略指定协议：file、host\nwww.example.com/path2 ignore://file|host\n```\n\n## 常见问题\n`ignore` 是根据指定协议忽略已匹配的规则，用这种方式忽略的规则不会继续匹配同类型的其他规则，如果想让请求忽略某个规则后再继续尝试匹配剩余的同类型规则可以采用 [skip](./skip)。\n\n<!-- File: docs/docs/rules/includeFilter.md (Tokens: ~327 | Category: anthropic-skill) -->\n# includeFilter\n在匹配 [pattern](./pattern) 的基础上，进一步筛选符合指定条件的请求（满足任一条件即可保留）。\n\n## 语法规则\n``` txt\npattern operation includeFilter://p1 includeFilter://p2 ...\n```\n> 请求要匹配 `pattern` 及 `p1`、`p2`、... 中的一个条件才能生效\n\n| 参数    | 描述                                                         | 详细文档                  |\n| ------- | ------------------------------------------------------------ | ------------------------- |\n| pattern | 匹配请求 URL 的表达式                                        | [匹配模式文档](./pattern) |\n| operation   | 操作指令 | [操作指令文档](./operation)   |\n| filters | 可选过滤器，支持匹配：<br/>• 请求URL/方法/头部/内容<br/>• 响应状态码/头部 | [过滤器文档](./filters) |\n\n\n## 配置示例\n``` txt\n# 仅对 GET 或 POST 请求生效  \nwww.example.com/api/data proxy://127.0.0.1:8080 includeFilter://m:GET includeFilter://m:POST\n```\n\n可以跟 [excludeFilter](./excludeFilter) 一起使用，详细用法参考：[过滤器文档](./filters)\n\n<!-- ... +178 more modular skills available via /api/skills/avwo/whistle?path={path} -->\n</yakaai_skills>"}