Impeccable Live Mode 项目一次性配置详解:注入配置、框架适配器与 CSP 检测同意流程
Impeccable 的 live 变体模式(live variant mode)允许你在浏览器中圈选元素、选择设计动作,并由 AI 生成 HTML+CSS 变体经 HMR 热切换。首次进入 live 模式前,live.mjs 引导阶段若报告 config_missing / config_invalid、存在 configDrift 或配置缺少 cspChecked,就需要执行一次性项目配置。本文以 live-setup.md 为主体,结合 live-inject.mjs、detect-csp.mjs 等源码,完整讲解配置文件的每个字段、各框架的注入目标差异、配置漂移检测与 CSP 同意补丁流程,读完即可独立为任意前端项目完成 live 模式初始化。
何时进入一次性配置流程
live-setup.md 明确说明:该流程不是每会话热路径的一部分,只在 live.md 所描述的会话启动中由以下三种情况触发:
live.mjs引导输出{ ok: false, error: "config_missing", ... }或config_invalid;- 引导输出携带非空的
configDrift需要处理; - 配置已存在但缺少
cspChecked字段。
从源码看,live.mjs 的第一步就是检查 .impeccable/live/config.json(其 --help 输出写明"config_missing"时打印 { ok: false, error: "config_missing", configPath, hint }),检查路径即引导输出中报告的 path(默认 .impeccable/live/config.json)。live-inject.mjs 中的 validateConfig 定义了 config_invalid 的判定标准:files 必须是非空字符串数组、exclude 若存在必须是字符串数组、insertBefore 与 insertAfter 至少有一个、commentSyntax 只能是 html 或 jsx、cspChecked 若存在必须是布尔值。
写入配置文件
完整配置结构与字段说明
在引导报告的 path 处创建配置文件,默认位置为 .impeccable/live/config.json:
{
"files": ["<path-or-glob>", "<path-or-glob>", ...],
"exclude": ["<optional-glob>", ...],
"insertBefore": "</body>",
"commentSyntax": "html",
"cspChecked": true
}
各字段的语义(含源码级印证):
files(注入目标):浏览器实际加载的 HTML 文件列表,条目可以是字面路径或 glob 模式。关键认知是:这里指向的是"浏览器实际加载的文件",而不是"源码文件"——跟踪 vs 生成在这里无关紧要,wrap 流程有自己的生成文件保护。live-inject.mjs 的resolveFiles展示了展开逻辑:不含* ? [的条目按字面路径直接透传(即使文件尚不存在也保留,由调用方逐条报告file_not_found,且exclude列表对显式字面条目不生效——用户既然点名了就该尊重);含通配符的条目通过fs.globSync展开,逐条套用排除规则并去重,顺序按首次出现保持。exclude(可选):跳过filesglob 本会包含的文件(如邮件模板、演示 fixture)。insertBefore/insertAfter:注入锚点。insertBefore在锚点行之前插入,insertAfter在锚点行之后插入,二者必配其一(与validateConfig的校验一致)。</body>几乎在所有文件中都存在,是最稳的默认锚点。commentSyntax:取值html或jsx,决定注入的标记注释使用哪种语法(HTML 注释<!-- ... -->还是 JSX 兼容形式),由目标文件的编写环境决定。cspChecked:记录后文的 CSP 检测步骤已经执行过;首次配置时该字段缺省,检测流程走完后补写为true。
硬性排除路径与 glob 语法
两条硬性排除规则不可被配置覆盖:**/node_modules/** 与 **/.git/**——向第三方代码注入脚本是不可接受的。这与 live-inject.mjs 中的 HARD_EXCLUDES 常量一一对应,源码注释强调"这些永远不是用户页面,匹配它们会悄悄向第三方代码注入跟踪脚本,用户无法通过配置关闭它们——这是底线"。
Glob 语法约定:** 匹配任意数量的路径段(含零段)、* 在单个段内匹配、? 匹配单个字符;所有路径相对于项目根目录,且统一使用正斜杠。
各框架的 files 配置对照表
原文档给出的框架对照表是实操核心,完整保留如下:
| Framework | files |
insertBefore |
commentSyntax |
|---|---|---|---|
| SPA with single shell (Vite / React / Plain HTML) | ["index.html"] |
</body> |
html |
| Next.js (App Router) | ["app/layout.tsx"] |
</body> |
jsx |
| Next.js (Pages) | ["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 | ["<root layout .astro>"] |
</body> |
html |
| Multi-page (separate HTML per route) | ["public/**/*.html"] glob over the served dir |
</body> |
html |
锚点选择原则:选一个在每个目标文件中都存在的锚点(</body> 几乎总能工作);多页站点优先使用 glob,这样新增页面会被自动纳入。一个重要的时效性限制:如果页面的 HTML 由生成器重建(generator rebuild),注入只会存活到下一次重新生成为止——每次构建后重新运行 live.mjs 即可;这不影响 accept 流程,因为 accept 走 fallback 流程写入的是真正的源码。
框架适配器:注入时自动检测
每一次注入都会把写入了什么记录到 .impeccable/live/inject-journal.json;下一次注入或移除会借此修复崩溃或"错误目录退出"留下的残留。live-inject.mjs 的注入路径印证了这一点:写入前先 healInjectJournal(保留本次将要拥有的工件,使重复注入保持字节级幂等),成功后 recordInjection 落盘。
SvelteKit、Nuxt、TanStack Start 三种框架在服务端渲染整个文档外壳,在入口模板里放一个裸 <script> 不能可靠执行。live-inject.mjs 会检测到它们并路由到专用适配器:
- SvelteKit:从
+layout.svelte派生的 dev-only 根组件; - Nuxt:dev-only 的
.client.ts插件; - TanStack Start:生成一个 dev-only 的
ImpeccableLiveRoot组件挂到__root。
在适配器路径下,files 值仍是一个有效的检测/CSP 提示(detection/CSP hint),但不再是字面的插入位置。普通 TanStack Router SPA(纯客户端)则走基线 Vite 路径。
框架检测的优先级在 live/frameworks/index.mjs 中显式声明:SvelteKit → Nuxt → TanStack Start → Astro → Next.js → Vite 通用 → 静态 HTML,首个匹配的 detect() 胜出;由于静态 HTML 永远匹配,resolveFramework 实际上不会返回空。
配置漂移(Config Drift)检测
每次启动时,live.mjs 的 scanForDrift 会扫描常见页面根目录(public/、src/、app/、pages/)下的 HTML 文件,找出未被解析后的 files 列表覆盖的"孤儿"文件,以 configDrift.orphans 加上 hint 的形式上报;没有漂移时 configDrift 为 null。源码中的实现细节值得注意:
- 扫描跳过
node_modules、.next、.nuxt、.svelte-kit、.astro、dist、build等构建产物目录; - 匹配用户
excludeglob 的文件视为有意排除,不算漂移,保证孤儿列表保持信号密度; - 孤儿列表最多展示 20 条(
orphans.slice(0, 20)),并附orphanCount与 hint 文案(建议追加条目或改用"public/**/*.html"这类 glob)。
处理原则与原文档一致:每个会话只向用户说明一次哪些文件未被覆盖,并提供"把它们加入 files 或把 files 切换为 glob"两个选项;绝不自动更新配置——决定权在用户手中。
CSP 检测(仅首次执行)
若配置中 cspChecked === true,跳过整个 CSP 小节——用户已经被问过这一次了。首次检测运行:
node .agent/skills/impeccable/scripts/detect-csp.mjs
输出 { shape, signals }。注意 shape 命名的是补丁机制而非框架来源,因此一个模板可以覆盖多个框架。detect-csp.mjs 的头部注释完整定义了五种形态:
null:未检测到 CSP 信号;把配置写成cspChecked: true并结束。append-arrays:CSP 以结构化指令数组表达,可自动补丁(monorepo helper 的additionalScriptSrc/additionalConnectSrc、SvelteKit 的kit.csp.directives、Nuxt 的nuxt-security)。append-string:CSP 以字面量字符串表达,可自动补丁(next.config.*中内联的headers()、NuxtrouteRules)。middleware/meta-tag:能检测到但不自动补丁。展示检测到的文件,请用户手动把http://localhost:8400加入script-src与connect-src,然后标记cspChecked: true继续。
检测器的实现边界
从 detect-csp.mjs 源码看,这是一次纯机械的 grep 式扫描:无网络、无 dev server、无 JS 求值。它递归遍历项目树(跳过 .next、.turbo、.svelte-kit、.nuxt、.astro、dist、build、out、.vercel 等目录,MAX_DEPTH = 6,每个文件最多读 64KB),仅检查 .js/.mjs/.cjs/.ts/.mts/.cts/.tsx/.jsx 等配置类扩展名与 .tsx/.jsx/.astro/.vue/.svelte/.html 等布局类扩展名。各类信号由正则组表达,例如 monorepo helper 信号包含 buildCSPConfig、additionalScriptSrc、createBaseNextConfig 等;middleware 形态匹配 headers.set("Content-Security-Policy"),meta-tag 形态匹配 http-equiv="Content-Security-Policy"。
判定的优先级写死在 detect-csp.mjs:append-arrays > append-string > middleware > meta-tag。源码注释解释了理由:结构化补丁比字符串拼接更安全,运行时与 HTML 注入式补丁可靠性差,v1 不自动应用。
同意提示词(Consent Prompt)
对可自动补丁的形态,向用户展示如下固定措辞:
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]
用户答 "no":跳过补丁,注明在手动加入放行前 live 模式无法工作,但仍然写入 cspChecked: true(问题已经问过,不应重复询问)。用户答 "yes":应用对应形态的补丁,然后写入 cspChecked: true。
形态一:append-arrays
在持有 CSP 数组的文件顶部附近声明一个 dev-only 常量,然后在 script-src 与 connect-src 数组中追加 ...__impeccableLiveDev:
// 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 场景下 additionalScriptSrc: [posthogHost, ...__impeccableLiveDev] 的写法;tests/framework-fixtures/sveltekit-csp/expected-after-patch.js 展示了 'script-src': ['self', 'unsafe-inline', ...__impeccableLiveDev] 的数组展开。
幂等性:若 __impeccableLiveDev 已存在于该文件,仍应用补丁流程,只需标记 cspChecked: true(即不重复追加、不重复询问)。
形态二:append-string
两点补丁(two-point patch):声明一个 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 的 next.config.* 内联 headers();Nuxt 的 nuxt.config.* 中 routeRules['/**'].headers['Content-Security-Policy']。参考输出见 tests/framework-fixtures/nextjs-inline-csp/expected-after-patch.js 与 tests/framework-fixtures/nuxt-csp/expected-after-patch.ts——前者展示 next.config.js 中 headers() 的模板字符串拼接,后者展示 Nuxt routeRules 场景(原字符串是 + 拼接时,需把 script-src 与 connect-src 两行改写成含 ${__impeccableLiveDev} 的模板字符串)。
故障排查与收尾
原文档给出的两条排查规则:
- 用户拒绝 CSP 补丁后 live 不工作:其 dev CSP 仍然在拦截
http://localhost:8400。处理方式是删除.impeccable/live/config.json中的cspChecked字段并重新运行live.mjs,setup 会重新发起询问。 - setup 完成后:重新运行
live.mjs进入正常会话流程(引导成功输出serverPort、serverToken、pageFiles等 JSON,随后按 live.md 的契约进入轮询循环)。
补充两个源码层面值得了解的行为:注入时 live-inject.mjs 会把 live 模式的运行时产物(inject-journal.json、sessions/、previews/ 等,见 LIVE_IGNORE_PATTERNS)写入一个由标记注释包裹的 gitignore 块,优先落在 .git/info/exclude,无 .git 时回退 .gitignore,确保临时状态不会污染版本库;移除注入(--remove)时同样会先 healInjectJournal 再 clearInjectJournal,与注入路径形成完整的自愈闭环。这些机制共同保证了本文所描述的 setup 是一个低摩擦、可重入、可撤销的一次性过程。
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 StartedRust0623
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