browser-preview by starchild-ai-agent/official-skills
npx skills add https://github.com/starchild-ai-agent/official-skills --skill browser-preview你已经了解 preview_serve 和 preview_stop。本技能填补了空白:在 preview_serve 返回一个 URL 之后会发生什么——用户如何实际看到它。
前端有一个右侧面板,包含两个标签页:工作区 和 浏览器。浏览器在 iframe 内渲染预览 URL。当你调用 preview_serve 时,前端会自动打开一个浏览器标签页并加载该 URL。
关键事实:
preview_serve 调用会创建一个浏览器标签页https://<host>/preview/{id}/广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
用户的浏览器无法访问 localhost 或 127.0.0.1。 这些地址指向的是服务器容器,而不是用户的机器。预览架构使用反向代理:
User's Browser → https://<host>/preview/{id}/path → (reverse proxy) → 127.0.0.1:{port}/path
规则:
http://localhost:{port} 或 http://127.0.0.1:{port} —— 他们无法访问到/preview/{id}/(或完整 URL https://<host>/preview/{id}/)curl http://localhost:{port} 仅用于你自己的服务器端诊断——切勿建议用户将其作为"测试"预览的方式preview_serve 返回的 url 字段(格式:/preview/{id}/)因为预览是在 /preview/{id}/ 路径下提供的,所以 HTML/JS/CSS 中的绝对路径会失效。反向代理在转发到后端之前会剥离 /preview/{id} 前缀,但浏览器会从域名根目录解析绝对路径。
问题示例:
<!-- ❌ 失效:浏览器请求 https://host/static/app.js → 404(绕过了预览代理) -->
<script src="/static/app.js"></script>
<!-- ✅ 有效:浏览器请求 https://host/preview/{id}/static/app.js → 正确代理 -->
<script src="static/app.js"></script>
<script src="./static/app.js"></script>
修复的常见模式:
| 失效(绝对路径) | 修复后(相对路径) |
|---|---|
"/static/app.js" | "static/app.js" 或 "./static/app.js" |
"/api/users" | "api/users" 或 "./api/users" |
"/images/logo.png" | "images/logo.png" 或 "./images/logo.png" |
url('/fonts/x.woff') | url('./fonts/x.woff') |
fetch('/data.json') | fetch('data.json') |
检查所有出现路径的地方:
src、href 属性fetch()、XMLHttpRequest、动态导入url() 引用'/static/')publicPath、base、assetPrefix)⚠️ 要彻底——通常修复了 CSS 的 url() 但漏掉了像 '/static/'(带单引号)这样的 JS 字符串字面量。在所有文件类型中搜索所有绝对路径的出现。
切勿查看像 preview/、output/ 或随机文件夹这样的工作区目录来了解预览状态。那些是用户数据,不是预览服务状态。
唯一的真实来源:
/data/previews.json(正在运行的服务)/data/preview_history.json(所有过去的服务)preview_serve / preview_stop 工具curl 进行端口检查(仅限服务器端,用于你自己的诊断)不要使用 ls/find 在工作区目录上诊断预览问题。不要调用像 list_scheduled_tasks 这样不相关的工具。保持专注。
当用户报告任何浏览器问题时,请严格按照以下顺序操作:
cat /data/previews.json 2>/dev/null || echo "NO_REGISTRY"
⚠️ 你的 bash 当前工作目录是 /data/workspace/。注册表位于 /data/previews.json(绝对路径,向上走一级)。始终使用绝对路径。
JSON 结构:
{
"previews": [
{"id": "f343befc", "title": "My App", "dir": "/data/workspace/my-project", "command": "npm run dev", "port": 9080, "is_builtin": false}
]
}
如果注册表有条目 → 转到步骤 3(验证服务) 如果注册表为空或缺失 → 转到步骤 4(检查历史记录)
对于注册表中的每个预览,检查端口是否在服务器端响应(这是你的诊断,不是给用户的):
curl -s -o /dev/null -w "%{http_code}" http://localhost:{port}
如果端口响应(200):
/preview/{id}/preview_stop(id) 然后 preview_serve(dir, title, command)。这会强制前端重新注册标签页。如果端口不响应:
进程崩溃但注册表条目保留。重新创建:
preview_stop(id="{id}")
preview_serve(dir="{dir}", title="{title}", command="{command}")
当没有正在运行的服务时,使用两级查找来找到用户可以预览的项目:
cat /data/preview_history.json 2>/dev/null || echo "NO_HISTORY"
JSON 结构:
{
"history": [
{
"id": "f343befc",
"title": "Trading System",
"dir": "/data/workspace/my-project",
"command": "python main.py",
"port": 8000,
"is_builtin": false,
"created_at": 1709100000.0,
"last_started_at": 1709200000.0
}
]
}
历史记录条目永远不会被 preview_stop 删除——它们会在重启后持久存在。只有当项目目录不再存在时,条目才会被自动清理。
如果历史记录有条目:
dir、title 和 command 调用 preview_serve如果用户说某个项目在历史记录中缺失 → 回退到第 2 级。
find /data/workspace -maxdepth 2 \( -name "package.json" -o -name "index.html" -o -name "*.html" -o -name "app.py" -o -name "main.py" -o -name "vite.config.*" \) -not -path "*/node_modules/*" -not -path "*/skills/*" -not -path "*/memory/*" -not -path "*/prompt/*" -not -path "*/.git/*" 2>/dev/null
然后:
preview_serve不要只说"没有服务在运行"就停止。 始终先检查历史记录,然后扫描,并提供选项。
| 用户说 | 你该做 |
|---|---|
| "标签页消失了" / "tab 不见了" | 步骤 1 → 2 → 3 或 4 |
| "空白页面" / "白屏" | 检查端口(服务器端),如果无响应 → 重新创建;如果响应 → 检查绝对路径问题 |
| "不更新" / "内容没更新" | 建议点击浏览器标签页中的刷新按钮,或重新创建预览 |
| "端口冲突" / "端口冲突" | preview_stop 旧的 → preview_serve 新的 |
| "看不到服务" / "⋮ menu empty" | preview_stop + preview_serve 强制重新注册 |
| "我的项目在哪里" / "what did I build" | 读取 /data/preview_history.json 并列出条目 |
| "资源加载失败" / "JS/CSS 404" | 检查绝对路径(/static/、/api/),修复为相对路径 |
当你无法做某事时,告诉用户手动操作(例如,"点击浏览器标签页中的刷新")。如果手动操作无效,使用 preview_stop + preview_serve 重新创建预览。
url() 路径,但漏掉了带有绝对路径的 JS 字符串字面量/preview/{id}/ 作为面向用户的 URLcurl localhost:{port} 用于你自己的服务器端诊断preview_stop + preview_serve 重启,然后告诉用户检查浏览器面板每周安装量
3.5K
仓库
GitHub 星标
1
首次出现
13 天前
安全审计
安装于
openclaw3.5K
cline51
gemini-cli51
kimi-cli51
github-copilot51
cursor51
You already know preview_serve and preview_stop. This skill fills the gap: what happens after preview_serve returns a URL — how the user actually sees it.
The frontend has a right-side panel with two tabs: Workspace and Browser. Browser renders preview URLs inside an iframe. When you call preview_serve, the frontend automatically opens a Browser tab loading that URL.
Key facts:
preview_serve call creates one Browser tabhttps://<host>/preview/{id}/The user's browser CANNOT accesslocalhost or 127.0.0.1. These addresses point to the server container, not the user's machine. The preview architecture uses a reverse proxy :
User's Browser → https://<host>/preview/{id}/path → (reverse proxy) → 127.0.0.1:{port}/path
Rules:
http://localhost:{port} or http://127.0.0.1:{port} — they cannot reach it/preview/{id}/ (or the full URL https://<host>/preview/{id}/)curl http://localhost:{port} is for your own server-side diagnostics only — never suggest it to the user as a way to "test" the previewurl field returned by preview_serve (format: /preview/{id}/)Because previews are served under /preview/{id}/, absolute paths in HTML/JS/CSS will break. The reverse proxy strips the /preview/{id} prefix before forwarding to the backend, but the browser resolves absolute paths from the domain root.
Example of the problem:
<!-- ❌ BROKEN: browser requests https://host/static/app.js → 404 (bypasses preview proxy) -->
<script src="/static/app.js"></script>
<!-- ✅ WORKS: browser requests https://host/preview/{id}/static/app.js → proxied correctly -->
<script src="static/app.js"></script>
<script src="./static/app.js"></script>
Common patterns to fix:
| Broken (absolute) | Fixed (relative) |
|---|---|
"/static/app.js" | "static/app.js" or "./static/app.js" |
"/api/users" | "api/users" or "./api/users" |
"/images/logo.png" | "images/logo.png" or |
Check ALL places where paths appear:
src, href attributesfetch(), XMLHttpRequest, dynamic importsurl() references'/static/' in template strings or concatenation)publicPath, base, assetPrefix)⚠️ Be thorough — it's common to fix CSS url() but miss JS string literals like '/static/' (with single quotes). Search for ALL occurrences of absolute paths across all file types.
Never look at workspace directories like preview/, output/, or random folders to understand preview state. Those are user data, not preview service state.
The only sources of truth:
/data/previews.json (running services)/data/preview_history.json (all past services)preview_serve / preview_stop toolscurl (server-side only, for your diagnostics)Do NOT use ls/find on workspace directories to diagnose preview issues. Do NOT call unrelated tools like list_scheduled_tasks. Stay focused.
When a user reports any Browser problem, follow this exact sequence:
cat /data/previews.json 2>/dev/null || echo "NO_REGISTRY"
⚠️ Your bash CWD is /data/workspace/. The registry is at /data/previews.json (absolute path, one level up). Always use the absolute path.
JSON structure:
{
"previews": [
{"id": "f343befc", "title": "My App", "dir": "/data/workspace/my-project", "command": "npm run dev", "port": 9080, "is_builtin": false}
]
}
If registry has entries → Go to Step 3 (verify services) If registry is empty or missing → Go to Step 4 (check history)
For each preview in the registry, check if the port is responding server-side (this is your diagnostic, not for the user):
curl -s -o /dev/null -w "%{http_code}" http://localhost:{port}
If port responds (200):
/preview/{id}/preview_stop(id) then preview_serve(dir, title, command) using the info from the registry. This forces the frontend to re-register the tab.If port does NOT respond:
Process crashed but registry entry remains. Recreate:
preview_stop(id="{id}")
preview_serve(dir="{dir}", title="{title}", command="{command}")
When there are no running services, use a two-tier lookup to find projects the user can preview:
cat /data/preview_history.json 2>/dev/null || echo "NO_HISTORY"
JSON structure:
{
"history": [
{
"id": "f343befc",
"title": "Trading System",
"dir": "/data/workspace/my-project",
"command": "python main.py",
"port": 8000,
"is_builtin": false,
"created_at": 1709100000.0,
"last_started_at": 1709200000.0
}
]
}
History entries are never removed bypreview_stop — they persist across restarts. Entries are automatically pruned only when the project directory no longer exists.
If history has entries:
preview_serve with the dir, title, and command from the history entryIf user says a project is missing from history → fall through to Tier 2.
find /data/workspace -maxdepth 2 \( -name "package.json" -o -name "index.html" -o -name "*.html" -o -name "app.py" -o -name "main.py" -o -name "vite.config.*" \) -not -path "*/node_modules/*" -not -path "*/skills/*" -not -path "*/memory/*" -not -path "*/prompt/*" -not -path "*/.git/*" 2>/dev/null
Then:
preview_serve with the appropriate directoryDon't just say "no services running" and stop. Always check history first, then scan, and offer options.
| User says | You do |
|---|---|
| "tab disappeared" / "tab 不见了" | Step 1 → 2 → 3 or 4 |
| "blank page" / "白屏" | Check port (server-side), if dead → recreate; if alive → check for absolute path issues |
| "not updating" / "内容没更新" | Suggest refresh button in Browser tab, or recreate preview |
| "port conflict" / "端口冲突" | preview_stop old → preview_serve new |
| "can't see service" / "⋮ menu empty" | preview_stop + preview_serve to force re-register |
| "where's my project" / "what did I build" | Read /data/preview_history.json and list entries |
When you can't do something, tell the user the manual action (e.g., "click refresh in Browser tab"). If manual action doesn't work, recreate the preview with preview_stop + preview_serve.
url() paths but missing JS string literals with absolute paths/preview/{id}/ as the user-facing URLcurl localhost:{port} only for your own server-side diagnosticspreview_stop + preview_serve to restart, then tell user to check Browser panelWeekly Installs
3.5K
Repository
GitHub Stars
1
First Seen
13 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
openclaw3.5K
cline51
gemini-cli51
kimi-cli51
github-copilot51
cursor51
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
"./images/logo.png"url('/fonts/x.woff') | url('./fonts/x.woff') |
fetch('/data.json') | fetch('data.json') |
| "resource load failed" / "JS/CSS 404" | Check for absolute paths (/static/, /api/), fix to relative paths |