Hub 内嵌浏览器预览 localhost 应用。 Use when: 写前端代码、跑 dev server、需要看页面效果、调 UI、operator说"看看效果"。 Not for: 后端纯 API 开发、不涉及页面的工作。 Output: 前端页面在 Hub browser panel 中实时预览。
71
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Hub 内置了嵌入式浏览器面板(F120),可以直接预览运行中的 localhost 应用。猫猫写完前端代码不用让operator切浏览器看效果。
-p 会话必须用仓库的 managed launcher:
pnpm preview:process start --port PORT --cwd /absolute/project/path -- COMMAND [ARGS...]
macOS 上它会把目标直接注册为一次性 user LaunchAgent;普通 detached child、nohup、setsid 或 PTY 都不算独立托管。托管预览默认 8 小时自动到期(可用 --lifetime-seconds N 缩短,最长 24 小时),避免遗留 watcher 持续制造文件事件。localhost:port 按 Go改代码 → HMR 热更新 → browser panel 内页面自动刷新,无需手动操作。
operator说过:"别手动让我输入,你最好打开浏览器,把页面放出来。"
猫应该主动替operator打开浏览器,不要等operator点 toast 或手动输 URL。
Step 1: 确认目标服务器在跑
若由猫启动,先用 managed launcher 启动/查状态:
pnpm preview:process start --port PORT --cwd /absolute/project/path -- COMMAND [ARGS...]
pnpm preview:process status --port PORT --cwd /absolute/project/path --json
→ 只有 status=running 才进入下步;unavailable/unmanaged/stopped 必须如实报告
→ macOS 跨回合展示还必须有 origin=launchd;origin=detached 只证明 launcher 已退出,
没证明脱离 invocation supervisor,不得承诺“回复后还会活着”
curl -s -o /dev/null -w "%{http_code}" http://localhost:PORT
→ 200/301/304 = 可以继续
→ 000/connection refused = 服务器没起来,先启动再说
Step 2: 调用 typed MCP
cat_cafe_preview_open({
port: PORT,
path: "/",
worktreeId: "当前 worktreeId(有就传)",
threadId: "当前 threadId(有就传)"
})
Step 3: 读返回的 deliveryStatus,再决定怎么报告
→ applied = Hub 前端真实接收并应用了(面板已打开)——只有这时才能说"已打开"
→ queued = 目标 thread 不在前台,已写入其 ThreadState;切到该 thread 自动揭示
→ blocked = presentation lock 等阻止了展示(看 deliveryReason)
→ unconfirmed = 没有任何 Hub 客户端确认送达(没连接 / 无匹配 client)——必须如实说"未能确认打开"admission ≠ visible:
allowed: true只证明服务端受理了请求。报告"已打开"之前必须看到deliveryStatus: "applied"。
running ≠ durable:同一 invocation 内的
status: "running"只证明当前可达。macOS 只有origin: "launchd"才证明已经交给用户会话级服务管理;“确实跨回合存活”必须由后续 invocation 的 status/HTTP 探针或operator现场画面确认。
durable ≠ immortal:
status --json的expiresAt是硬截止时间。展示提前结束就显式stop;确需延长时,先停掉旧实例再重新start,不要绕过租约另开后台 watcher。
| 参数 | 必填 | 说明 |
|---|---|---|
port | 是 | dev server 端口号 |
path | 否 | 页面路径,默认 / |
threadId | invocation 免传 / agent-key 必传 | invocation 调用由服务端从 invocation 记录推导;持久 agent-key 必须显式传,缺失直接报错。传了保证精确送达到该 thread |
worktreeId | 建议传 | 精确到 worktree;不传走 user-scope 送达,同用户其他 tab 会按 thread 归属 queue |
怎么获取 worktreeId:就是你当前工作的 worktree 目录名。例如你在
cat-cafe-f120-fix目录里工作,worktreeId 就是cat-cafe-f120-fix。如果你在主仓库cat-cafe里,就不需要传。
| 现象 | 原因 | 修法 |
|---|---|---|
| 右侧无反应 | 目标服务器没在跑 / 未认证或 thread 归属不对 / MCP callback 未配置 | 先 curl localhost:PORT 确认目标服务,再读工具返回的 deliveryStatus / 错误(401=未认证,400/403=thread scope) |
{"error":"Proxy error","message":"socket hang up"} | 目标服务器已退出 | 重启服务器,再刷新 Browser panel |
| 猫回复后页面立刻 stopped | 服务仍在 invocation 的 PTY/进程监督域;detached/PPID=1 也可能被 supervisor 按 coalition 回收 | macOS 用 pnpm preview:process start ... 并确认 status=running, origin=launchd;结束展示时用同一 cwd/port 执行 stop |
| 打开了系统 Chrome | 用了 Playwright/Chrome MCP 等外部工具 | 不要用外部浏览器工具! auto-open 是 Hub 内嵌预览,不是系统浏览器 |
| 两个重复 tab | React Strict Mode(已修复) | 升级到最新代码 |
html 参数(后端不支持);简单 HTML 可视化用 html_widget rich blockoperator拍板:"简单的用富文本,复杂的用猫主动打开浏览器。"
| 场景 | 方式 | 怎么做 |
|---|---|---|
| 简单可视化(图表、动画、计算器) | html_widget rich block 内联渲染 | 用 rich-messaging skill 发 html_widget block |
| 复杂应用(完整页面、多组件交互) | 猫主动打开浏览器 | 调用 auto-open API |
| 项目 | 说明 |
|---|---|
| Preview Gateway | 独立端口(默认 4100),反向代理 localhost 应用 |
| 为什么不直连 | iframe 跨端口需要代理剥离 X-Frame-Options/CSP |
| iframe sandbox | allow-scripts allow-forms allow-popups allow-downloads allow-same-origin(安全:独立 origin) |
| WebSocket/HMR | 代理层支持 WebSocket 升级,Vite/Next/Webpack HMR 正常工作 |
| 端口排除 | Clowder AI 自身端口(3003/3004/6398/6399/18888 等)自动排除 |
| 审计 | 每次 open/close/navigate 都有审计日志 |
| Console 面板 | bridge script 注入到 iframe,捕获 console.log/warn/error,在面板展示 |
| 一键截图 | SVG foreignObject + canvas 截图,上传后端,toast 展示 |
| 送达契约 | 认证 + exact-thread:anonymous → 401;invocation 推导 thread;agent-key 必传 threadId 且校验归属。事件只发射一次到 caller 的 user room(tenant scope,无 preview:global/worktree 广播),回执同房间收集 |
| 多 Tab | 同时预览多个 localhost 页面,Tab 切换独立状态;同一事件多 tab 各自回执,服务端聚合取最优(applied > blocked > queued,skipped 不参评) |
| 进程来源 | preview:process status --json 返回 origin。macOS 的 launchd 是跨 invocation 托管;其他平台的 detached 只保证 launcher 退出后继续运行,宿主 supervisor 是否回收仍需外部 service manager 证明 |
| 生命周期 | 每个 managed preview 都必须返回 expiresAt;默认 8 小时、最长 24 小时,到期后 launcher 会 TERM→KILL 自己拥有的进程组。长期展示要显式续开,不允许无限 watcher |
-p 中启动的 dev server → 必须走 preview:process,并在报告中同时给出 status 与 origin;不能把 running/detached 写成跨回合已存活expiresAt;任务结束或不再展示时执行同一 cwd/port 的 preview:process stophtml_widget rich block 内联渲染${TMPDIR}/cat-cafe-evidence/...,不要落仓库根目录(见 ../.cat-cafe-shared-refs/evidence-output-contract.md)cat_cafe_preview_open — 服务器没跑 = proxy errornohup、setsid 或普通 detached child 冒充长期托管 — PPID=1 仍可能被 invocation supervisor 按进程族回收launchctl submit 包裹会立即退出的 launcher — inferred keepalive 会反复重启 launcher 形成自旋;macOS 直接用 preview:process 生成的一次性 LaunchAgentopen 命令打开系统浏览器 — F120 是 Hub 内嵌预览,走 iframe,不走系统浏览器/api/preview/auto-open 的 curl — 主路径是 cat_cafe_preview_open| Skill | 关注点 |
|---|---|
| browser-preview(本 skill) | Hub 内预览 localhost 前端页面 |
tdd | 写代码的测试驱动纪律 |
quality-gate | 开发完成后的自检(含对照设计稿) |
730c37b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.