Caveman Mode 浏览器扩展实战解析:如何在 ChatGPT、Claude 与 Gemini 中强制 AI 输出"穴居人式"高密度回答
【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman
Caveman 项目的浏览器扩展(位于extension/目录)解决一个很具体的问题:让 ChatGPT、Claude、Gemini 三大 AI 聊天站点在回复时主动压缩表达——去掉冠词、填充词、客套与含糊措辞,同时完整保留代码块、API 名称、CLI 命令和错误字符串等技术实质。读完本文,你能完整掌握该扩展的安装方式、lite/full/ultra 三档强度调节、"智能混合注入"(首条完整指令 + 后续短提醒)的设计原理、内容脚本如何在不碰网络请求的前提下拦截发送手势,以及其单元测试 + Playwright 真浏览器验证 + 确定性打包的完整研发流水线。
一、扩展做什么:只改你的出站消息,不碰任何请求
扩展的核心行为可以一句话概括:当你发送消息时,它在你自己的消息前加上一段可见的"穴居人指令",然后替你触发发送。从extension/README.md的表述来看,有三个关键约束:
- 只向出站消息添加文本:注入的指令在聊天界面里是明晃晃可见的,扩展不隐藏自己注入了什么;
- 零网络行为:不发起任何网络请求、没有分析或追踪、不读取也不存储你和模型之间的对话内容;
- 全部本地运行:所有逻辑都发生在浏览器本地,不依赖任何构建步骤。
"智能混合"(smart-hybrid)注入策略是它最有意思的设计:
| 消息位置 | 注入内容 |
|---|---|
| 一次会话的第一条消息 | 完整 primer(压缩版 caveman skill,含激活声明 + 风格规则 + 强度条款) |
| 之后的每一条消息 | 极短的"stay caveman"提醒,例如[stay in caveman mode — FULL] |
这样做的动机是 token 效率本身:完整 primer 约数百字符,而后续每条消息只花几十字节的提醒来维持模式。单元测试里甚至有一条断言直接验证"提醒文本长度 ×4 仍小于 primer 长度"(见 directive 测试 中 "a reminder is much shorter than the primer" 用例),从实现层面锁死这个"混合"设计。
primer 中除了风格约束,还包含一条结构化输出指引:当用户想要紧凑的结构化输出、且没有指定具体格式/协议时,优先使用标注为toon的代码围栏(TOON 格式)而非 JSON 或 Markdown 表格;但明确禁止对 tool-call 参数或代码使用 TOON。
二、安装与基本使用(Unpacked 方式)
扩展是标准 Chrome Manifest V3 扩展,无需构建,按 README 的三步即可加载:
- 打开
chrome://extensions,开启Developer mode(开发者模式); - 点击Load unpacked,选择仓库中的
extension/目录; - 固定图标。扩展默认以"开启"状态作用于全部三个站点。
任何 Chromium 系浏览器(Chrome、Edge、Brave、Arc)均可使用,无构建步骤。
2.1 使用方式
- 从 popup 中打开开关,并选择强度档位:lite / full / ultra;
- 正常输入并发送消息即可。扩展会前置注入指令并提交,AI 的回复随之变得简短紧凑;
- 页面上的火焰胶囊指示器(flame pill)显示当前激活状态,点击它可以直接关闭扩展——或者在聊天中直接对 AI 说
stop caveman(primer 本身约定了这个退出口令)。
2.2 三档强度分别约束什么
三档强度对应注入指令中不同的"强度条款",定义在 src/directive.js 的LEVEL_CLAUSE中:
| 档位 | 行为约束 | popup 中的提示文案(popup.js 的HINTS) |
|---|---|---|
lite | 无填充词、无含糊、无客套——但保留冠词和完整句子,专业而紧凑 | "No filler or hedging. Keeps full sentences." |
full(默认) | 丢冠词、允许片段句、用短同义词,经典"穴居人"简洁 | "Classic caveman. Drops articles, fragments OK." |
ultra | 因果关系明确时可去连词;一个词能说完绝不用两个词。禁止发明散文缩写(如 config、request 之类),禁止因果箭头;代码符号、函数/API 名、错误串保持逐字 | "Max compression. No invented abbreviations or causal arrows." |
值得注意的是 ultra 档的反直觉设计:它压缩最狠,却额外禁止模型"自己造缩写"和"用箭头符号"——因为这类模型自创简写对可读性的破坏比原句本身更大。这一约束与仓库中正式的 caveman skill 文档(skills/caveman/SKILL.md)中的禁令保持对齐,directive 测试 中 "Ultra primer stays aligned with canonical skill prohibitions" 一用例专门读取该 SKILL.md 并断言两者一致。
2.3 站点级开关
popup 还提供按站点启用的开关(ChatGPT / Claude / Gemini 各一个),存储结构为{ enabled, level, sites: { [host]: bool } }。内容脚本读取时的语义是s.sites[HOST] !== false——每个站点默认开启,只有显式关掉才不生效(见 src/caveman.js 的refresh())。
三、注入文本的构造:buildPrimer 与 buildReminder
注入文本集中在全仓库最"纯"的一个文件里:src/directive.js。它不碰chrome.*、不碰 DOM、不发网络请求,只导出 5 个纯函数:
const LEVELS = ["lite", "full", "ultra"]; // 会话级 primer 的主体(BASE): // - "[Caveman mode is ON for this whole conversation, until I say "stop caveman"." // - 丢冠词/填充词/客套/含糊;允许片段句;短同义词(fix,而非 "implement a solution for") // - 保留全部技术实质:代码块、函数/API 名、CLI 命令、错误串逐字不动 // - 无 emoji、无装饰性表格、不叙述自己在做什么 // - 大表格且未指定格式时优先 ```toon 围栏;工具调用参数与代码禁用 TOON // - 永不主动宣告此模式;安全警告/不可逆操作确认时正常回答再恢复 // 末尾按档位拼接 LEVEL_CLAUSE[规范档位] function buildPrimer(level) // 首条消息用:BASE + 强度条款 function buildReminder(level) // 后续消息用:"[stay in caveman mode — <LEVEL>]" function isPrefixed(text) // 草稿是否已被注入过(防止二次注入) function normLevel(level) // 未知档位安全回落到 "full"几个设计细节值得展开:
1. 未知档位安全回落。normLevel对任何不在["lite","full","ultra"]中的输入(包括undefined)一律回落为full。directive 测试 断言buildPrimer("banana")和buildPrimer(undefined)都产出 FULL 条款——脏存储永远不会导致注入失败,只会退到中等强度。
2. 双注入防护。isPrefixed检查草稿是否以[Caveman mode或[stay in caveman开头(容忍前导空白),用于"同一输入框第二次发送时不再注入"。这在内容脚本的键盘/点击两个拦截器里都会被检查。
3. primer 是单个闭合方括号指令。测试要求 primer 以[开头、]收尾,且不含不成对的代码围栏——注入文本在聊天界面里是清晰可见的一段方括号注释,而非伪装成用户内容的隐蔽提示。
四、内容脚本:如何在不碰网络的前提下拦截"发送手势"
manifest.json 声明了 content script 的装载顺序与匹配范围,这也是理解整个扩展行为的关键配置:
{ "manifest_version": 3, "permissions": ["storage"], "background": { "service_worker": "src/background.js" }, "content_scripts": [{ "matches": [ "https://chatgpt.com/*", "https://chat.openai.com/*", "https://claude.ai/*", "https://gemini.google.com/*" ], "js": ["src/directive.js", "src/caveman.js"], "css": ["src/indicator.css"], "run_at": "document_idle" }], "web_accessible_resources": [{ "resources": ["fonts/geist-mono.woff2"], "matches": ["https://chatgpt.com/*", "https://chat.openai.com/*", "https://claude.ai/*", "https://gemini.google.com/*"] }] }注意js数组的顺序:directive.js必须先加载,因为它把 API 挂到self.CavemanDirective上,src/caveman.js 开头就会检查——若取不到则整个内容脚本直接静默退出,而不是带病运行。权限只有storage一项;web_accessible_resources仅暴露字体(用于指示器里的 "Caveman Mono" 字族,见 indicator.css 的@font-face)。
4.1 拦截时机:capture 阶段赢在应用之前
内容脚本在document上以capture 阶段(第三个参数true)同时监听keydown(Enter,排除 Shift/Ctrl/Meta/Alt 与输入法合成中)和click(发送按钮)。命中后先e.preventDefault()+e.stopImmediatePropagation()取消原生发送,再执行"注入 + 重新触发发送"。取消在前是刻意的:即使注入逻辑中途抛异常,safeInjectAndSend的兜底分支也会尝试一次纯发送——注释里明说,"吞掉用户的 Enter 之后必须让用户还能发出去,否则用户要刷新页面才能恢复"。
4.2 每站点选择器与容错
三个站点的 composer/发送按钮/消息容器选择器集中在SITES映射中,且每条都故意过度指定、多级回退:
const SITES = { "chatgpt.com": { editor: ["#prompt-textarea", 'div.ProseMirror[contenteditable="true"]'], send: ['button[data-testid="send-button"]', "#composer-submit-button", 'button[aria-label="Send prompt"]'], message: ["[data-message-author-role]"], }, "claude.ai": { /* ProseMirror 编辑器 + aria-label 发送按钮 */ }, "gemini.google.com": { /* Quill .ql-editor + Material 图标按钮,注释说明 `button.send-button` 已在某次改版中被移除 */ }, };README 将其概括为"per-site selectors with fallbacks"。从源码结构看还有几层保险:
- 每个选择器都包在
try/catch里,浏览器无法解析的选择器直接跳过; - 发送按钮必须通过
looksSendable检验:可见、未disabled且未aria-disabled,并且 aria-label/testid/title不匹配/\b(stop|abort|cancel)\b/i——因为生成中站点会把 Send 换成 Stop 按钮且可能共用同一个按钮槽位,误点 Stop 会中断回复却永远发不出消息; - 精确选择器全部落空时,还有一个作用域限定在 composer 区域内的宽松回退(找 aria-label 含 "send" 的按钮),避免误伤页面级的 "Send feedback" / "Resend";
- 找不到发送按钮时的最后手段是对编辑器派发完整合成 Enter 键序列(
dispatchEnter); - 站点改版时的适配成本被 CLAUDE.md 明示:更新
SITES映射即可,每条入口都保留 aria-label 回退。
4.3 写编辑器:走框架自己的输入路径
向 rich editor 写入文本是最容易出事故的一步。setText的处理方式:
<textarea>:通过HTMLTextAreaElement.prototype上的原生valuesetter 写入,再派发input事件;- ProseMirror / Quill / Lexical:
focus→ 全选编辑器内容 →document.execCommand("insertText", false, text),即驱动框架自己监听的那条输入路径,让编辑器内部文档模型同步更新、发送按钮重新可用。
源码注释特别强调了一个反模式:绝不用el.textContent = text直写 DOM——那会让这些编辑器与其模型失同步,下一次击键可能把整个草稿清掉。任何一步失败,setText返回false,调用方执行"fail closed":恢复原始文本并发送未加前缀的原稿,且只有恢复后输入框里确实还有内容才触发发送——绝不向空框/乱码框发按。
4.4 重新触发发送:轮询 + 单一触发保证
注入后站点要隔一个渲染 tick才会渲染/启用发送按钮,所以fireSend采用轮询:约 50ms 一次、最多 16 次(~820ms 预算)查找一个"可用发送按钮"并点击;预算耗尽仍找不到,才回退到合成 Enter。同时置bypass标志位(200ms 后释放),让自己的拦截器对自己的合成手势失效——注释强调"每个手势恰好一次触发(点击或 Enter,绝不双发),因此抑制成功不会变成双重发送"。
4.5 "首条消息"如何判定
判定不是靠"页面加载时设的标记",而是实时数消息:messageCount() === 0时才算首条、才配拿到完整 primer。从源码结构看,这个设计意味着刷新页面或深链进入一段已有对话时,正确得到的是短提醒而不是又一份完整 primer——避免在长对话里反复灌入大段指令。
五、后台 Service Worker 与页面指示器
扩展的后台逻辑薄到几乎可以忽略——src/background.js 只做一件事:把总开关状态镜像到工具栏徽章(开启时显示 "ON",背景色#C75B39)。它监听onInstalled、onStartup和storage.onChanged(且仅响应sync区域的enabled变更)。background 测试 用node:vm加载真实源码并注入伪造的chrome对象,断言了完整的徽章状态机:安装时按存储值刷新、启动时刷新、enabled变更时刷新、非enabled或非sync区域的变更不触发刷新。
页面内的指示器由内容脚本渲染(indicator.css 定义样式):一个固定在右下角的深色玻璃胶囊,内含一个8×8 像素火焰(由FLAME字符矩阵 + 琥珀色渐变色标在 JS 中逐像素拼出,"2" 级像素提亮模拟火苗顶部),旁边是 "Caveman · " 文字。点击胶囊直接翻转总开关(写回chrome.storage.sync)。另有一个 1 秒心跳:既保证 SPA 路由跳转后指示器重新挂回页面,也在扩展被重载、脚本成为孤儿时(chrome.runtime.id消失)干净地自毁并移除指示器。
六、开发与测试流水线
README 给出的研发命令及对应实现(package.json):
npm ci # 安装锁定版本的 Playwright 工具链 npm run setup:browser # 一次性安装锁定版本 Chromium npm test # 纯 directive + service-worker 测试 npm run test:browser # Chromium 内容脚本 + popup 旅程 npm run package # 全量测试 + 产出经过校验的确定性商店 zip6.1 纯逻辑测试(node --test)
npm test跑的是node --test --test-force-exit test/*.test.mjs,其中 directive.test.mjs 用createRequire直接 requiresrc/directive.js(这正是它必须保持"无 chrome、无 DOM"的原因),覆盖:
- primer 以激活声明开头、携带正确强度条款、TOON 指引与保护条款齐全、方括号闭合、围栏配对;
- ultra 与
skills/caveman/SKILL.md的禁令对齐(实时读取该文件断言); - 未知档位回落 FULL;
buildReminder精确字符串;isPrefixed对前导空白/null/undefined 的容忍;normLevel收敛到已知集合。
6.2 真浏览器测试(Playwright)
playwright.config.mjs 配置testMatch: "*.spec.mjs"、headless、单 worker。content-script.spec.mjs 的写法颇有参考价值:用page.route把https://chatgpt.com/**全部拦截,用本地test/harness.html+ 真实的src/directive.js/src/caveman.js源文件响应,从而在真 Chromium 中复现内容脚本的完整旅程。已覆盖的断言包括:
- 第一条 Enter 发出完整 primer、后续点击发出恰好一次短提醒(正则
^\[stay in caveman mode — FULL\]\n{2,}second prompt$精确匹配双换行分隔); - 站点关闭(
sites: { "chatgpt.com": false })与指示器点击关闭后,出站 prompt 保持原文; - 带修饰键 Enter、空白草稿、已带前缀的 prompt 均不会被重复注入。
6.3 打包与发布安全网
npm run package先跑全量测试(test:all),再执行打包脚本;scripts/verify-extension-stage.mjs 维护了一份显式可发货文件白名单(17 个文件:LICENSE、PRIVACY.md、两个字体、四个图标、manifest、popup 三件套、src/下四个脚本),并递归校验:拒绝符号链接、拒绝非普通文件、拒绝任何逃逸扩展根目录的本地引用(../、绝对路径),同时把 manifest/CSS/HTML 中引用的资源与白名单交叉核对。这保证了产出的商店 zip 是确定性的、只包含声明过的文件。
七、隐私边界:它到底访问了什么
PRIVACY.md 给出了与源码可相互印证的完整边界:
- 不收集、不存储、不传输任何个人数据;自身不发起任何网络请求,无分析追踪,不向任何人发送任何东西;
- 仅存储偏好:总开关、强度档位(lite/full/ultra)、启用站点列表——通过
chrome.storage.sync持久化并同步到你自己已登录的 Chrome 实例之间,这些键值不含个人信息; - 访问范围:仅在四个受支持的聊天站点(
chatgpt.com、chat.openai.com、claude.ai、gemini.google.com)上监听"你发送消息"这一手势,对那条出站消息前置一段固定文本并提交。不读取/记录/传输对话与模型回复,不碰页面网络请求,不自动化账号里的其他任何东西,也不访问其他网站; - 数据共享:无。
这与 manifest 的声明完全自洽:permissions只有storage(连host_permissions都没有,content script 靠matches声明注入),代码中不存在任何fetch/XMLHttpRequest调用——"本地只读、只加不减、可见透明"是该扩展的安全模型。
八、小结
Caveman Mode 扩展是 caveman skill 的浏览器侧分发形态:把"少说废话、保留全部技术实质"的压缩风格以可验证、可测试、可审查的方式注入到三大 AI 聊天站点的出站消息中。它的工程取舍值得借鉴——注入逻辑纯函数化以便单测、capture 阶段拦截加单一触发保证、写入走编辑器原生输入路径、首条/后续消息的混合注入控制 token 开销、每站点多级选择器回退应对改版,以及"白名单 + 引用审计"的确定性打包。所有行为都以 extension/README.md 的声明为准,并在 PRIVACY.md 与测试套件中留有可核对的边界。
【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考