Impeccable 项目 Live 模式一次性配置全指南:config.json 编写、CSP 检测与框架注入适配
本文面向将 impeccable 的交互式 Live 变体模式接入既有项目、并希望搞清楚"启动时那个一次性配置到底要我做什么"的开发者与 AI Agent。核心围绕 live-setup.md 展开:当
live.mjs报告config_missing/config_invalid、需要处理configDrift,或配置缺少cspChecked时,Agent 需要为项目写入.impeccable/live/config.json、选定注入锚点、按框架表挑选注入目标,并完成一次性的 Content Security Policy(CSP)放行。读完本文,你将掌握该配置的完整字段语义、各框架标准值、注入适配器与配置漂移机制、四种 CSP 形态的检测与补丁流程,以及出错时的排查手段。
live-setup 在何时被加载
live-setup 是 Live 模式的一次性项目初始化文档,不是每次会话的热路径。从 live.md 的"First-time setup"一节看,只有当以下三种情况之一成立时,主流程才会读取并执行 live-setup:
live.mjs启动输出{ ok: false, error: "config_missing" | "config_invalid", path }——即.impeccable/live/config.json缺失或非法;- 启动输出携带非空
configDrift,需要向用户说明有 HTML 文件未被覆盖; - 已有配置中缺少
cspChecked字段(表示首次 CSP 检查尚未完成)。
正常情况下,一旦项目配置就绪,后续 Live 会话不会重复走这套流程;因此 live-setup 描述的是一次性、幂等的引导动作,而不是随会话循环执行的过程。
第一步:编写注入配置 .impeccable/live/config.json
live.mjs 启动时会报告它期望的配置文件路径(默认 .impeccable/live/config.json)。完整字段如下:
{
"files": ["<path-or-glob>", "<path-or-glob>", ...],
"exclude": ["<optional-glob>", ...],
"insertBefore": "</body>",
"commentSyntax": "html",
"cspChecked": true
}
各字段语义(依据 live-setup 原文):
| 字段 | 必填 | 说明 |
|---|---|---|
files |
是 | 注入目标:浏览器真正加载的 HTML 文件(而不一定是源码文件)。条目可以是字面路径或 glob;"tracked 源码 vs 生成文件"在这里不重要——wrap 流程另有自己的生成文件守卫。 |
exclude |
否 | 从 files glob 命中结果中排除的文件(如邮件模板、demo fixtures)。 |
insertBefore |
是 | 锚点文本,注入脚本插在该文本所在行之前;默认 </body>。 |
commentSyntax |
是 | 写入文件的注释语法,取 html 或 jsx。 |
cspChecked |
首次写入常缺 | 记录"CSP 检查步骤已执行过";首次配置时缺省,用于让引导流程只在第一次询问用户 CSP 授权。 |
需要特别注意两点硬性约束:
- 硬排除路径(不可覆盖):
**/node_modules/**与**/.git/**永远不可能成为注入目标,向其中注入会插桩第三方代码。 - glob 语法:
**匹配任意数量路径段(含零个),*匹配单个段内内容,?匹配单个字符;路径一律以项目根为基准并使用正斜杠。
各框架的 files / 锚点对照表
live-setup 为常见框架给出了经过验证的标准取值。选注入目标的原则是:挑选在每个文件中都真实存在的一个锚点(</body> 几乎总是可用)。insertAfter 则改为匹配某行之后插入。
| 框架 | files |
insertBefore |
commentSyntax |
|---|---|---|---|
| 单壳 SPA(Vite / React / 纯 HTML) | ["index.html"] |
</body> |
html |
| Next.js(App Router) | ["app/layout.tsx"] |
</body> |
jsx |
| Next.js(Pages Router) | ["pages/_document.tsx"] |
</body> |
jsx |
| Nuxt | ["app.vue"] |
</body> |
html |
| Svelte / SvelteKit | ["src/app.html"] |
</body> |
html |
| TanStack Router(SPA,Vite) | ["index.html"] |
</body> |
html |
| TanStack Start(SSR) | ["src/routes/__root.tsx"] |
<Scripts |
jsx |
| Astro | ["<根布局 .astro>"] |
</body> |
html |
| 多页站点(每路由独立 HTML) | ["public/**/*.html"](对服务目录做 glob) |
</body> |
html |
实战提示(源自原文):
- 多页站点优先用 glob,这样新增页面会被自动纳入注入范围,无需每次改配置。
- 由生成器重建的站点:注入只存活到下一次重新生成。每次构建后需重跑
live.mjs。注意 accept 不受影响——它经由 fallback 流程把最终结果写进真正的源码。 - TanStack Start 的注入锚点是
<Scripts(其 root 组件在渲染脚本时执行),而不是</body>。
框架注入适配器与 inject-journal 自愈
某些框架服务端渲染文档壳层(SvelteKit、Nuxt、TanStack Start),如果直接在入口模板里塞一个原生 <script>,它不会可靠执行。因此注入并非"无脑插一段脚本":
- 每次注入都会把写入内容记录到
.impeccable/live/inject-journal.json;下一次注入或移除操作会据此"愈合"上次因崩溃或错误目录停止而遗留的半成品。 - 注入时会自动检测上述框架并路由到专用适配器:
- SvelteKit:在
+layout.svelte中生成仅开发态可用的根组件; - Nuxt:生成仅开发态可用的
.client.ts插件; - TanStack Start:在
__root中生成仅开发态可用的ImpeccableLiveRoot组件。
- SvelteKit:在
此时 files 字段仍然是有效的检测 / CSP 提示(参见下文 CSP 各形态中 middleware / meta-tag 的判断依赖),但不再是字面的插入位置。而普通的 TanStack Router SPA 则走基线 Vite 路径即可。
配置漂移(configDrift)
每次启动(boot)时,项目会在常见页面根目录(public/、src/、app/、pages/)扫描 HTML 文件,凡未被已解析的 files 列表覆盖的,都会作为 configDrift.orphans 附带提示浮出。Agent 应当每会话只向用户说明一次哪些文件未被覆盖,并建议补进 files 或改用 glob——但绝不自动更新配置:是否扩大注入范围由用户决定。无漂移时 configDrift 为 null。
首次 CSP 检测与四种形态
当 config.cspChecked === true 时整段跳过(说明此前已问过用户)。首次配置则运行:
node .claude/skills/impeccable/scripts/detect-csp.mjs
输出为 { shape, signals };shape 命名的是"补丁机制",而不是具体框架——因此一套模板可以覆盖许多框架。共有四种结果:
| shape | 含义 | 处理方式 |
|---|---|---|
null |
项目没有 CSP | 直接写配置 cspChecked: true,到此为止 |
append-arrays |
CSP 是结构化指令数组 | 可自动打补丁(monorepo helper 的 additionalScriptSrc/additionalConnectSrc、SvelteKit 的 kit.csp.directives、Nuxt 的 nuxt-security) |
append-string |
CSP 是字面值字符串 | 可自动打补丁(内联的 next.config.* headers()、Nuxt routeRules) |
middleware / meta-tag |
检测到但不自动补丁 | 向用户展示检测到的文件,请其手动将 http://localhost:8400 加入 script-src 与 connect-src,然后标记 cspChecked: true 继续 |
CSP 补丁前的同意征询(固定措辞)
只有用户同意才允许改文件。原文档给出了一字不差的提示措辞,Agent 应照此征询:
CSP patch needed. I detected a Content Security Policy in your project that blocks
http://localhost:8400: the live picker won't load without an allowance. Here's the change I'd make:[file: <patchTarget>] [exact diff, 2-5 lines]It's guarded by
NODE_ENV === "development"so the extra entry only appears in dev and never reaches production. You can remove it any time by reverting this file. Apply? [y/n]
关键点在于补丁永远被 NODE_ENV === "development" 守卫,额外的放行条目只出现在开发态,永远不会进入生产构建;用户随时可以通过还原该文件移除它。
- 用户回答 no:跳过补丁,明确告知"在放行条目被手动加入之前 Live 不会工作",但仍要写入
cspChecked: true(因为问题已经问过了,不能每次启动都纠缠用户)。 - 用户回答 yes:按下方对应 shape 应用补丁,然后写
cspChecked: true。
append-arrays:结构化指令数组补丁
在持有 CSP 数组的文件顶部声明一个仅开发态放行数组,再把 ...__impeccableLiveDev 追加进 script-src 与 connect-src 数组:
// Dev-only allowance so impeccable live mode can load. Guarded by NODE_ENV.
const __impeccableLiveDev =
process.env.NODE_ENV === "development" ? ["http://localhost:8400"] : [];
各框架落点:
- Next.js + monorepo 共享 helper:编辑应用自身的
next.config.*(而不是共享 helper 文件),把放行项追加到additionalScriptSrc/additionalConnectSrc; - SvelteKit:编辑
svelte.config.js中的kit.csp.directives['script-src']与['connect-src']; - Nuxt + nuxt-security:编辑
nuxt.config.*中的security.headers.contentSecurityPolicy['script-src']与['connect-src']。
仓库在 tests/framework-fixtures/nextjs-turborepo/expected-after-patch.ts 中提供了 monorepo shared-helper 场景补丁完成后的参考输出。从该文件可以看到最终的期望形态:const baseConfig = createBaseNextConfig({ ..., additionalScriptSrc: [posthogHost, ...__impeccableLiveDev], additionalConnectSrc: [posthogHost, ...__impeccableLiveDev] }),即应用配置展开时把 dev 放行数组汇入共享 helper 的 CSP 数组。SvelteKit 场景的期望形态见 tests/framework-fixtures/sveltekit-csp/expected-after-patch.js。
幂等性:如果文件里已存在 __impeccableLiveDev 标识符,说明补丁已应用过——此时只把 cspChecked 标记为 true 即可,无需重复插入。注意,上述 expected-after-patch.* 文件头部注释注明它们是"供 agent / 人工审阅的参考输出,不参与测试执行",即它们是行为规范快照,而非断言。
append-string:字面值字符串补丁
这是"两点式"补丁:声明一个 dev-only 字符串,把它插值进 CSP 值的两个指令处。字符串带前导空格以保证拼接干净;编辑过程中需要把字面量字符串改写为模板字符串:
// Dev-only allowance so impeccable live mode can load.
const __impeccableLiveDev =
process.env.NODE_ENV === "development" ? " http://localhost:8400" : "";
转换示例(原文档原文):
script-src 'self' 'unsafe-inline'变为`script-src 'self' 'unsafe-inline'${__impeccableLiveDev}`connect-src 'self'变为`connect-src 'self'${__impeccableLiveDev}`
各框架落点:
- Next.js:内联
headers()(在next.config.*内); - Nuxt:
nuxt.config.*的routeRules['/**'].headers['Content-Security-Policy']。
仓库在 tests/framework-fixtures/nuxt-csp/expected-after-patch.ts 中给出 Nuxt 场景补丁后的完整期望配置(含将 Content-Security-Policy 改写为多行 + 拼接、并在 script-src / connect-src 尾部通过 ${__impeccableLiveDev} 插值);Next.js 内联 headers 场景见 tests/framework-fixtures/nextjs-inline-csp/expected-after-patch.js。这两个 fixture 目录还各自附带 files/ 中补丁前的真实工程文件(如 nuxt.config.ts、next.config.js),可以对照学习"改动前后"的完整差异。
middleware / meta-tag:交给用户手动
此形态只做检测与提示:把检测到的文件展示给用户,请其手动放行 http://localhost:8400,确认后写 cspChecked: true 继续。仓库的 CSP 类 fixtures(如 tests/framework-fixtures/nextjs-turborepo/、tests/framework-fixtures/sveltekit-csp/、tests/framework-fixtures/nuxt-csp/、tests/framework-fixtures/nextjs-inline-csp/)正是从"自动可补丁"的两类形态中归纳出的真实工程样本,是判断某项目 CSP 应归入哪一 shape 的直观参照。
故障排查与收尾
- 用户拒绝了 CSP 补丁,后来 Live 不工作:原因几乎必然是开发态 CSP 拦截了
http://localhost:8400。处理办法是从.impeccable/live/config.json中删除cspChecked字段并重跑live.mjs——引导流程会再次询问(这正是该字段存在的意义:用它记住"是否已问过",删掉即可重问)。 - 配置写入完成后:重跑
live.mjs继续正常 Live 会话即可。
整个 setup 流程结束后,.impeccable/live/config.json 会作为项目配置持续存在(cleanup 时 live-server.mjs stop --keep-inject 不会删除它),而下一次会话若无需漂移解释、cspChecked 又在场,则不会再触发 live-setup。
小结:这套引导设计的三个关键原则
从源码结构(live.md 与 live-setup.md 的分工)可以推断其设计意图:
- 一次性、用户拍板:注入目标的选择与 CSP 放行都属于"改变用户项目"的动作,因此配置漂移永不自动改配置、CSP 补丁必须征得同意——引导只负责把代价最小、可逆的改动精确呈现给用户。
- 补丁以"机制"而非"框架"归类:
append-arrays/append-string把成百上千种框架的 CSP 配置收敛为两种可自动处理的模式,配合NODE_ENV === "development"守卫保证零生产影响,这是它能被安全自动化的前提。 - 自愈与可重问:inject-journal 保证注入半成品可愈合;
cspChecked使"已问过"成为可删除、可重问的状态位——一次性的引导因此既能严格触发,又不会把用户锁死在错误的早期决定上。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00