screenshot-to-code 的 Agent 指令实践:Poetry 环境、测试校验与本地服务启动规范
本文以截图转代码项目 screenshot-to-code 中的 CLAUDE.md(与 AGENTS.md 内容一致的 AI Agent 项目指令文档)为核心,逐条解读其定义的 Python 虚拟环境约定、后端测试与类型检查策略、前端 lint 规则、Prompt 代码风格约定,以及 Cursor Cloud 云环境下的依赖安装与服务启动方式。读完本文,你既能按文档完整跑通该项目的本地开发与验证闭环,也能理解每条指令背后对应的 pyproject.toml、pytest.ini 等真实配置,并把这套"给 Agent 写开发规约"的思路复用到自己的仓库中。
文档定位:CLAUDE.md 在仓库中是什么
CLAUDE.md 是一份面向 AI 编码助手(Claude Code 等 Agent)的"项目级操作手册",仓库根目录下的 AGENTS.md 与其内容完全一致,供不同 Agent 框架读取。它回答的不是"这个仓库做什么"(那是 README.md 的职责),而是三个更工程化的问题:
- 环境怎么用——Python 命令必须走哪个虚拟环境、依赖如何安装;
- 改动后如何验证——每次代码变更必须跑哪些测试、类型检查、lint,通过标准是什么;
- 哪些坑要绕开——端口、环境变量、工具链的隐含行为。
这种文档的价值在于:把原本只存在于维护者脑子里的隐性约定显性化,让 Agent(或新加入的开发者)无需试错即可正确操作仓库。下文按原文档章节顺序逐条展开,并给出每条指令在仓库源码中的落点。
Python 环境:一律使用 backend 的 Poetry 虚拟环境
原文档的 Python 环境约定是:
- 所有 Python 命令必须使用 backend 的 Poetry 虚拟环境(
backend-py3.10); - 首选调用方式为
cd backend && poetry run <command>; - 若需要手动激活,用
cd backend && backend/poetry env activate(实际写法为cd backend && poetry env activate)查看当前环境对应的虚拟环境路径,再执行它打印出的source .../bin/activate命令。
这三条规则背后的依据可以直接在仓库中核对:
- 后端是一个非可打包的 Poetry 项目。backend/pyproject.toml 中声明
package-mode = false,依赖 Python 版本约束为python = "^3.10",核心运行时依赖包括fastapi、uvicorn、websockets、openai、anthropic、google-genai、playwright等,开发依赖(dev group)则固定为pytest、pyright、pytest-asyncio三个工具。 - 由于依赖只安装在该虚拟环境里,
poetry run是保证命令与项目锁文件(backend/poetry.lock)所解析版本一致的最安全方式——它自动进入正确环境,避免了系统 Python 缺包或版本漂移。 poetry env activate的作用是查询当前项目虚拟环境的激活命令。在 Cursor Cloud 这类非交互环境中,shell 不一定加载了.bashrc,手动source一个记错的固定路径很容易失败,而通过poetry env activate让 Poetry 自己输出当前解析出的路径,是更健壮的做法。
这里有一个文档专门点出的"反直觉"细节,值得单独说明:
虚拟环境目录名虽然是
backend-...-py3.10,但实际解析到的 Python 是 3.12,而不是 3.10。因为 pyproject.toml 中^3.10表示>=3.10,<4.0,3.12 完全满足该约束。日常操作无需关心具体小版本,统一poetry run即可。
这条说明提醒我们:Poetry 的 caret 约束是"向后兼容到主版本"的语义,环境名中带的小版本号只是创建环境时的快照,不代表版本上限。
测试与类型检查策略:每次变更后的强制验证闭环
原文档的 Testing policy 给出了两条"每次代码变更后必须执行"的规则和一条通过标准:
# 变更后运行后端测试
cd backend && poetry run pytest
# 变更后运行类型检查
cd backend && poetry run pyright
通过标准(Type checking policy)是:被修改的文件中不允许出现新的 pyright 警告。也就是说,仓库允许存量警告存在,但禁止"带伤通过"——你动过的文件必须不引入新告警。
这条策略在仓库配置中有两处直接印证:
- backend/pytest.ini 定义了测试发现规则与默认参数:
testpaths = tests、文件模式test_*.py、函数模式test_*,addopts = -v --tb=short(详细输出 + 短 traceback),并且asyncio_mode = auto让pytest-asyncio自动处理异步测试用例。因此poetry run pytest无需任何额外参数就能在 backend/tests/ 下发现全部 30 余个测试文件。 - backend/pyrightconfig.json 将检查模式设为
basic,reportMissingTypeStubs降为none(不强制第三方库类型桩),并排除image_generation.py;这正是"存量告警可控、增量零告警"策略能够成立的前提——配置已经把噪声压到了合理水平。
从 backend/tests/ 的测试文件命名(如 test_agent_engine.py、test_openai_provider_session.py、test_asset_extraction.py)可以看出,测试覆盖 Agent 引擎、各模型 Provider 会话、资产抽取、评估系统等核心链路,这也解释了为什么文档把"改完必跑全量 pytest"定为硬性规则。
前端校验:pnpm lint 与基线告警的边界
原文档对前端只有一条命令:
cd frontend && pnpm lint
并补充了一句关键说明:如果改动同时涉及前后端,两套校验都要跑("If changes touch both, run both sets")。
结合 frontend/package.json 可以看到 lint 脚本的完整定义:
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
--max-warnings 0 意味着任何一条 lint 告警都会让命令以失败退出码结束。这也引出了原文档最后一条"非显然注意事项":当前 pnpm lint 会报出存量错误(例如 generateCode.ts 中的 @typescript-eslint/no-explicit-any),这些是基线问题(baseline issues),属于代码历史遗留,而不是环境装坏了。理解这条边界的意义在于:Agent 在云环境里跑 lint 失败时,应当把它归类为"已知基线",而不是反复重装依赖试图"修复环境"。
另外,frontend/package.json 通过 packageManager 字段锁定了 pnpm@10.32.1、engines 要求 node >=14.18.0,依赖侧还包含 puppeteer(用于 QA 测试 test:qa 脚本)——这就解释了下一条注意事项中 pnpm install 提示忽略 esbuild/puppeteer 构建脚本的现象:该提示无害,Vite 的 dev/build 与 Jest 测试都不依赖这些 native 构建脚本。
Prompt 代码风格约定:多行提示词一律三引号
原文档的 Prompt formatting 一节规定:
- 多行 prompt 文本优先使用三引号字符串(
"""..."""); - 需要插值的多行 prompt,优先用单个三引号 f-string,而不是把字符串片段拼接起来。
这条约定针对的是后端提示词工程的典型痛点。本仓库的提示词构建集中在 backend/prompts/ 目录(pipeline.py、system_prompt.py、message_builder.py 以及 create/、update/ 子模块),其中存在大量需要插入变量(用户请求、文件快照、设计系统等)的长文本模板。字符串拼接("a" + "b" + var + "c")在换行、缩进、引号转义上都极易出错,而单个三引号 f-string 保持了模板的整体性与可读性,也便于 Agent 在批量改写提示词时做结构化 diff。对维护者来说,这是一条低成本、高收益的风格护栏。
Hosted 分支:连接独立 SaaS 后端的发布形态
原文档用一节简短说明了 hosted 分支的存在:
hosted 版本位于
hosted分支。该分支连接一个 SaaS 后端,位于另一个代码库../screenshot-to-code-saas。
从源码结构看,这一说法有两处印证:
- frontend/package.json 中除常规
dev/build外,还定义了dev-hosted(vite --mode prod)与build-hosted(tsc && vite build --mode prod),即前端本身预留了托管模式的构建入口; - scripts/cursor-cloud-install.sh 末尾会检测同级目录
../screenshot-to-code-saas下的backend/与admin/是否存在,若存在则分别为其执行poetry install --no-root与pnpm install。
这说明 hosted 分支与自托管分支共享同一份前端代码,差异主要体现在后端连接目标与构建模式上。文档把这节放在 Agent 指令中,是为了防止 Agent 误改 hosted 相关逻辑时去错误的代码库里找实现。
Cursor Cloud 云环境:依赖自动刷新与安装脚本
原文档的 "Cursor Cloud specific instructions" 一节给出了云沙箱场景的完整操作约定:
- 依赖在启动时自动刷新(
backend/跑poetry install、frontend/跑pnpm install),因此 Agent 无需手动安装依赖; - 环境初始化脚本为:
bash /agent/repos/screenshot-to-code/scripts/cursor-cloud-install.sh
对应仓库内的真实脚本是 scripts/cursor-cloud-install.sh,其执行逻辑值得完整过一遍:
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT" # 先切到仓库根目录,保证与启动时的工作目录无关
curl -sSL https://install.python-poetry.org | python3 - # 安装/更新 poetry
~/.local/bin/poetry -C backend install # 后端依赖
~/.local/bin/poetry -C backend run playwright install chromium || true # Chromium(失败不阻断)
pnpm -C frontend install # 前端依赖
# 若存在 ../screenshot-to-code-saas,则顺带安装其 backend 与 admin
脚本有三个可取的设计点,与文档中的注意事项一一对应:
- 先
cd到仓库根目录再安装,这正是文档所说"脚本会先切到仓库根目录,因此无论启动时工作目录在哪都能正常工作"; - poetry 使用全路径
~/.local/bin/poetry而非裸命令——因为 poetry 装在~/.local/bin,交互式 shell 通过.bashrc把它放进了 PATH,但非交互脚本(如云沙箱的执行器)不一定加载.bashrc,裸poetry可能报 command not found; playwright install chromium || true允许失败——Chromium 下载失败不阻断整体安装,因为截图预览只是可选能力。
本地服务启动:端口、WebSocket 与同源代码
文档指出服务的规范命令以 README.md 为准("see README.md for the canonical commands"),并给出了两条核心服务的启动方式:
# 后端(FastAPI + WebSocket),在 backend/ 下执行
poetry run uvicorn main:app --reload --port 7001
# 前端(Vite/React),在 frontend/ 下执行
pnpm dev # 然后打开 http://localhost:5173
文档特别强调了三个容易踩坑的细节,均可在源码中找到对应实现:
1. Vite 只绑定 localhost,必须用 http://localhost:5173 访问。
用 http://127.0.0.1:5173 会直接拒绝连接。这是 Vite dev server 的默认 host 行为,对 Agent 来说是一条高频卡点:自动化脚本里写 127.0.0.1 就能复现"服务明明在跑却连不上"的假故障。
2. 前端与后端走 WebSocket 通信,环境变量为 VITE_WS_BACKEND_URL。
文档表述其默认值为 ws://127.0.0.1:7001,生成过程(generation)的流式输出经由该 WebSocket 推送,其余路由为普通 HTTP。从 frontend/src/config.ts 的实现可以看到更细的降级逻辑:
export const WS_BACKEND_URL =
import.meta.env.VITE_WS_BACKEND_URL || SAME_ORIGIN_WS;
即未显式设置 VITE_WS_BACKEND_URL 时,前端会退回同源 WebSocket 地址(HTTP 协议头替换为 ws)。frontend/src/config.ts 中的注释解释了这一设计的动机:配合 Vite dev server 的代理,让应用在隧道/预览 URL 下也能工作——此时"localhost"指向的是查看者自己的机器而不是沙箱。因此在本地标准开发中按文档配置指向 ws://127.0.0.1:7001 即可,而在云端隧道场景下则依赖同源代码自动适配。docker-compose.yml 也再次提示:改后端端口时要同步修改 frontend/.env.local 中的 VITE_WS_BACKEND_URL。
3. 后端端口 7001 有自动避让机制。
虽然文档给出的规范命令显式指定 --port 7001,但 backend/start.py 实现了更宽松的策略:从 --port(默认 7001)开始最多探测 --max-port-attempts(默认 20)个端口,遇到占用自动顺延并打印提示("Port 7001 is in use. Starting backend on port 7002.")。在云沙箱多实例并存时,这一机制能显著减少端口冲突导致的启动失败。
环境变量与 API Key:核心能力的启动前提
原文档的 Non-obvious caveats 中,API Key 一节信息密度最高,完整内容为:
- 截图转代码这一核心功能至少需要一个 LLM Key:
OPENAI_API_KEY、ANTHROPIC_API_KEY或GEMINI_API_KEY三选一; - 设置位置二选一:写入
backend/.env(改完必须重启后端),或通过应用内 Settings 对话框填写; - 一个 Key 都没有时,生成会快速失败并提示 "No OpenAI, Anthropic, or Gemini API key";
REPLICATE_API_KEY(图像生成/编辑能力)只能通过backend/.env配置,UI 不支持。
这条消息字符串在源码中可以直接定位:backend/routes/generate_code.py 中的 throw_error 提示与文档描述逐字对应,并额外给出了补救指引("If you add it to .env, make sure to restart the backend server")。从源码结构看,_get_variant_models 接收 openai_api_key、anthropic_api_key、gemini_api_key 三个可选参数来装配变体模型,也就是说三个 Key 全部缺失才会触发该失败路径——这与"三选一即可"的文档表述一致。
对 Agent 的实际意义是:云沙箱首次跑生成任务前,应先确认 Key 来源(.env 还是 Settings),并记住 .env 路径不热加载这一约束,避免"改了 .env 却以为没生效"的误判。
其他环境注意事项的逐条印证
文档末尾还列了三条云环境专属的"非显然"注意事项,逐条说明如下:
- Playwright Chromium 已预装,供可选的 "Screenshot preview" 工具使用,Settings 页面中显示为 "Available"。这与 scripts/cursor-cloud-install.sh 中
playwright install chromium的安装步骤、pyproject.toml 中playwright = "^1.61.0"的依赖声明相互印证;后端对应实现位于 backend/preview_screenshot/playwright_backend.py。 pnpm install会打印 "Ignored build scripts (esbuild, puppeteer)" 警告,这是 pnpm 出于安全默认忽略依赖包安装脚本的行为,无害,Vite 构建、dev server 和 Jest 测试都不需要批准这些构建。poetry在非交互 shell 中可能不在 PATH(前文已述),解决方案就是统一用~/.local/bin/poetry全路径或poetry run。
结语:一份 Agent 指令文档应有的形态
CLAUDE.md 展示了 AI Agent 协作场景下项目文档的完整写法:
- 环境约束(Poetry venv +
poetry run)保证依赖一致性,并用源码级解释(^3.10语义)消除命名带来的误导; - 验证闭环(pytest + pyright + pnpm lint 三条命令、"改动文件零新告警"的通过标准)让 Agent 可以自主判断改动是否合格,且通过标准与 pytest.ini、pyrightconfig.json 的实际配置严格对齐;
- 风格约定(三引号 f-string)针对本仓库提示词工程的真实痛点;
- 陷阱清单(Vite 只绑 localhost、WebSocket 环境变量、Key 不热加载、lint 基线告警、pnpm 构建脚本警告)把维护者的踩坑经验显性化,避免 Agent 在同样的坑上反复试错。
这套"命令可复制、标准可验证、陷阱有出处"的写法,可以作为为任何多端(前端 + 后端)项目编写 Agent 指令文档的参考模板。
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 StartedRust0622
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