# Site Builder

> 用对话把用户的想法做成一个真实可访问的网站并一键发布上线。当用户说"做一个网站/页面/门户/展示站/落地页/看板/H5 并要能打开访问""帮我搭个站""把这份内容做成网页发布出去"，或要在已发布站点上继续迭代修改时，务必使用本技能。它教你两条建站路径——简单内容手写静态站、复杂/精美需求用预装的 React 工程模板构建——并用 choose_design 让用户三选一设计方案，最后用 publish_site 发布为平台托管站点，拿到形如 /site/<slug>/ 的访问链接交付给用户。

- Skill: `zju-real/site-builder` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zju-real/site-builder`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zju-real/site-builder/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: ZJU-REAL (https://skillmd.com/u/zju-real)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zju-real/site-builder

---


# 对话建站（Site Builder）

本技能教你把用户的需求做成一个**完整可访问的网站**，发布后返回一个可直接打开的链接
（形如 `/site/<slug>/`）。发布能力由「站点」插件的 `site_publish` MCP 提供。

> **桌面本机/混合模式：先在本地项目中保存源码，再构建发布。**
> 本地项目提示词给出的真实绝对路径优先于本文的 /workspace 和 /myspace 示例。
> 在本地项目的 sites/<站点名>/ 中初始化、编写和修改源码；新站点入口会先准备本地项目。
> 静态站显式传 src_dir 指向这个页面目录；React 站传项目内 source_dir 和实际构建输出 src_dir。
> 云端仅接收托管文件，本机保存站点编号与源码关联。编辑时严格使用当前站点上下文的
> source_dir / publish_dir / site_id；不重新建站、不去旧会话临时目录或 /myspace 找源码。
>
> **云端模式：发布后平台会自动为站点建一个同名"项目"并把源码文件存进去**（后端自动完成）。
> 用户随后可在「实验室 → 站点」点站点卡片的「编辑」按钮，通过对话继续修改。

## 沟通原则（面向非技术用户）

把用户当成不懂技术的知识工作者：
- 谈**他的站点**——内容、样式、进度、访问方式；**不要**在给用户的消息里出现
  vite / npm / build / node_modules / 构建产物 / 目录路径 这类词，改说
  「正在搭建页面」「正在生成站点」「即将发布」。
- 每个阶段（准备设计方案 / 搭建 / 发布）至多一句简短进度播报。
- 可恢复的技术问题自己默默重试解决，不向用户展示报错细节；只有真正需要用户
  决策或提供信息时才提问。

## 第一步：选路径（先判断，再动手）

| 用哪条 | 判据 |
|---|---|
| **A. 静态站 fast path** | 单页内容展示、通知/介绍/菜单类落地页、需求简单明确、无组件化交互诉求 |
| **B. React 工程 capability path** | 多页面应用、交互组件、图表/数据看板、用户要求"好看/精致/App 感"、预期持续迭代 |

拿不准时：内容型选 A，产品型选 B。编辑已有站点时**不换路径**（见编辑一节）。

## 路径 A：静态站（保持简单）

1. **生成完整静态站**（用 write / bash）：
   - 放在 **`/workspace/site/`** 下（项目会话则直接在项目文件夹里）；
   - **必须**有 `index.html` 入口；可以有多页面、子目录、`css/`、`js/`、图片；
   - 样式/脚本尽量**内联或本地化**——外部 CDN 在内网环境可能加载不到。
2. **发布**：`publish_site(title='站点名称', src_dir='<实际页面目录>')`。必须明确页面根目录，不能默认上传整个项目。
3. **交付**：把返回 `url` 以 markdown 链接发给用户（管理入口见「交付话术」）。

## 路径 B：React 工程（复杂/精美站点）

### B1. 初始化（一条命令，秒级完成）

```bash
bash "${SITE_TEMPLATE_HOME:-/opt/site-template}/init-react-site.sh" /workspace/site-src/<英文短名>
```

模板预装 React 18 + antd + echarts + tailwind v4 + lucide/motion/dayjs，依赖开箱即用。
脚本幂等，可反复执行（重开会话后也先跑它自愈环境）；它会打印本工程的构建
命令、产物目录与发布参数，后续步骤以其输出为准。

### B2. 设计三选一（choose_design，新建站点必做）

在动手写正式代码**之前**，让用户从 3 个设计方向里挑一个：

1. 若用户需求里关键信息缺失（受众/用途/内容/风格偏好），先用**一条消息**问清
   1-2 个最关键的问题；用户已给出完整方向或明确说"你定"则跳过。
2. 写 **3 个风格迥异的单文件 mockup** 到 `/workspace/design_options/{a,b,c}.html`：
   - 每个自包含（内联 CSS，不引外部资源），用**真实感内容**（真实标题/数据/文案，
     不要 lorem ipsum），三个方案用相同内容、只比设计（配色/布局/字体/密度）；
   - 避免自绘 SVG 插画——用排版、色彩、CSS 形状表达设计感。
3. 逐个截图并登记：
   ```bash
   npx playwright screenshot --viewport-size=1280,900 \
     file:///workspace/design_options/a.html /workspace/design_options/a.png
   ```
   每张图调 `sandbox_get_artifact(src_path='/workspace/design_options/a.png',
   name='design-a.png')` 拿 `file_id`。**这些截图是选择器素材，不要 pin_to_workspace。**
4. 调 `choose_design(question='您喜欢哪种设计风格？', options=[{id, title, brief,
   image_file_id}, ...])`——工具会**挂起等用户在界面上点选**（等多久都正常）。
5. 拿到返回后**严格按选中方案**展开：布局/配色/字体以该 mockup 为准，不混入其它
   方案元素；用户跳过或超时则选你最推荐的方案并向用户说明一句。

编辑已有站点、或用户已给出完整明确的设计要求时，**跳过**本步骤。

### B3. 实现 → 构建 → 发布

1. 按选定方案改 `src/`（页面放 `src/pages/`，路由表 `src/App.jsx`），并把
   `index.html` 的 `<title>` 改成真实站点名（模板占位是"站点建设中"，留着会
   显示在用户浏览器标签页上）。工程硬约束：
   - **HashRouter 不许换、`vite.config.mjs` 的 `base: './'` 不许改**（改了发布后打不开）；
   - 禁外部 CDN；静态资源放 `src/assets/` 交给构建打包；
   - 动态数据用 `src/lib/siteApi.js`（kvGet/kvSet/kvDelete/submitForm）；
   - 界面图标用预装图标库（`lucide-react` 或 `@ant-design/icons`），**禁止拿
     emoji 当图标**（跨系统渲染不一致、色相杂乱，是"AI 生成感"最强的元素）；
   - echarts 图表注意数值标签防裁切：柱状图外置 label 要给 `grid.top` 留够
     空间或适当抬高 `yAxis.max`；轴刻度单位归口到标题一处，不要每个刻度都带；
   - 优先用预装库（antd/echarts/…）；确需新依赖：编辑 `package.json` 后**重跑
     init 脚本**（此时会物化独立依赖副本并增量安装，可能需要几分钟——bash 调用
     记得给足超时），**禁止**在工程目录里直接 `npm install`。
2. 构建：`cd /workspace/site-src/<名> && npm run build`（产物自动落
   `/workspace/.site-dist/<名>/`）。构建报错先修再继续，禁止带错发布。
3. **发布前自检清单**（逐条过，都是高频遗漏）：
   - `index.html` 的 `<title>` 已改成真实站点名（不是"站点建设中"）；
   - 图表上的统计标注（均值线/合计等）由数据**计算得出**，不是拍脑袋写死的数；
   - 涨跌/同比类徽章按数据符号驱动（负增长要红色下箭头，不能写死绿色上箭头）；
   - y 轴不写死 min/max（换数据会裁剪出图），柱宽用 `barMaxWidth` 而非固定值；
   - 删掉未使用的 import 和死变量；重复样式抽成常量或小组件。
3. 发布（**两个参数都必传**）：
   ```
   publish_site(title='站点名',
                src_dir='/workspace/.site-dist/<名>',
                source_dir='/workspace/site-src/<名>')
   ```
   产物进托管，**源码工程**镜像进项目文件夹（保证「编辑」进来的是可改的源码）。
   漏传 `source_dir` 会导致项目里的源码被产物覆盖——绝不允许。
4. **尽早发第一版**：完成核心页面即可先发布（发布后源码即进项目保险箱，
   不受沙箱回收影响），再迭代细节发新版。

## 编辑已有站点（项目入口、新会话、原会话和编辑按钮统一）

**编辑必须显式传原 site_id 和准确 src_dir。不依赖按钮或会话替你补参数。**
只有用户明确要求新建另一个站点时才省略 site_id。

1. **先查目标**：本地项目调用 `list_project_sites()`，获取当前账号在当前项目的
   site_id、title、url、version、source_dir、publish_dir。即使是新会话也使用这个工具。
   按用户提到的网址、标题及源码选择；多个候选无法区分时先询问，不能选最近发布的站点。
   工具报错不等于没有站点。查不到时查看此前发布回执或请用户指定目标，不能自动新建。
   云端项目使用可用的站点查询工具或此前发布回执取得编号，不从目录名猜测。
2. **核对入口再改**：读取目标源码，同时检查 `publish_dir/index.html`。
   项目根目录和 `sites/<名字>/` 各有 index.html 时，不能混淆二者。
   发布记录与文件不一致时先查明正确页面目录，再显式指定；不要复制到根目录掩盖问题。
3. **原地修改**：静态站修改实际页面；React 工程修改 source_dir 中的源码，
   恢复依赖并重新构建，确认构建成功、产物根 index.html 存在。
4. **显式发布**：
   - 静态站：`publish_site(title='站点名称', site_id='<查询得到的原编号>', src_dir='<实际页面目录>')`。
   - 构建型：`publish_site(title='站点名称', site_id='<查询得到的原编号>', source_dir='<源码工程目录>', src_dir='<本次构建输出目录>')`。
   本机源码和产物都必须在真实本地项目目录内，不使用 /myspace 或旧会话临时路径。
5. **验证后交付**：核对返回的 site_id 与目标完全一致、URL 保持不变；
   有此前版本号时核对版本递增。通过页面读取/浏览工具检查返回的首页实际包含本次修改，
   不能仅凭 ok=true 宣称页面已更新。编号变化或首页仍是旧内容时先定位，不继续创建新站。
   无法访问时明确说明尚未完成线上页面验证。
6. 编辑迭代不做设计三选一。

> 新建同样必须显式传 src_dir，成功后保留回执中的 site_id 和准确目录供后续修改。
> 本地查询工具不可用时，不套用“后端按项目自动定位”的旧规则；读取最新技能并核对能力版本。

## 沙箱生命周期规则（React 工程务必遵守）

沙箱空闲一段时间会被回收销毁，只有**项目文件夹**（`/myspace/...`）里的文件永久幸存：
- **源码只能放项目文件夹或尽快通过发布进入项目**；`/workspace/` 其余区域随沙箱销毁丢失。
- `node_modules` / 构建产物 / 缓存**永远不进项目文件夹**（工程里的 `node_modules`
  是指向 `/workspace` 临时区的符号链接，不要删除或替换成真实目录）。
- 重开会话后发现依赖目录消失/链接失效 → **重跑 init 脚本**即自愈（秒级）。
- 发布或 init 过程中 `package.json` / `package-lock.json` 落入项目文件夹时可能弹一次
  「同步到我的空间」确认，属正常，等用户确认即可。

## 约束与要点

- 规模上限：≤300 文件、总量 ≤30MB、单文件 ≤10MB（React 产物通常几 MB，余量充足）。
- 公开站点在浏览器里以**沙箱模式**运行（无 cookie / localStorage）——不要写依赖
  登录态或浏览器本地存储的逻辑，持久化用下面的轻后端 API。
- 可见性 `visibility`：`public`（默认，凭链接访问）/ `private`（仅本人登录可见）/


## 站点内置轻后端 API（可选，需要动态能力时用）

站内 JS 用**相对路径**（不带前导 `/`）fetch，平台已配好 CORS：

- **KV 存储**（计数器 / 分数 / 简单配置）：
  - 读：`GET __api/kv/<key>` → `{value, exists}`
  - 写：`PUT __api/kv/<key>`，body `{"value":"..."}`（≤4KB，≤200 键）
- **表单收集**（留言 / 报名 / 反馈，站主可在站点管理里导出 CSV）：
  - `POST __api/forms/<form_key>`，body 为扁平 JSON 对象（≤8KB）

> `__api/` 是保留前缀，站点文件不能用这个目录名。React 工程用
> `src/lib/siteApi.js` 封装，静态站直接 `fetch('__api/kv/score')`。

## 禁止事项清单

- 禁止在工程目录（尤其 `/myspace/` 下）直接 `npm install`——加依赖走"改
  package.json + 重跑 init 脚本"。
- 禁止把源码目录当站点发布（路径 B 的 `src_dir` 必须指向构建产物目录）。
- 禁止改 `base: './'`、换 BrowserRouter、引外部 CDN。
- 禁止构建报错未修就发布；禁止编辑会话里绕开原文件另建新站。
- 禁止把设计 mockup 截图 pin 给用户、或对同一问题反复调 choose_design。

## 交付话术

把返回的 `url` 以 **markdown 链接**发给用户，并告知：可在「实验室 → 站点」里管理
（改可见性 / 版本回滚 / 看访问量 / 导出表单数据），点站点卡片上的「编辑」按钮可随时
回来通过对话继续修改。

## 示例

用户："做一个部门数据看板网站，能看各科室的月度指标，要好看。"
你（路径 B）：init 模板 → 问一句"指标数据我先用示例数据占位，之后您可以发我真实数据"
→ 写 3 个 mockup（深色科技风 / 明亮商务风 / 极简卡片风）截图 → `choose_design` 等用户
选 → 按选中方案实现（antd 布局 + echarts 图表 + siteApi 存配置）→ `npm run build` →
`publish_site(title='部门数据看板', src_dir='/workspace/.site-dist/dashboard',
source_dir='/workspace/site-src/dashboard')` → 交付链接 + 管理/编辑指引。

