Superpowers Visual Companion:浏览器端可视化头脑风暴的完整实践指南
Superpowers 的 brainstorming 技能附带一个可选的「Visual Companion」(可视化伴侣):一个零依赖的本地 Node.js 服务器,在 AI Agent 与用户的设计讨论过程中,把 UI 线框图、布局对比、架构示意等视觉内容实时推送到浏览器标签页中,并回收用户的点击选择作为结构化反馈。本文基于官方指南 visual-companion.md 展开,结合 server.cjs 等服务端源码与 tests/brainstorm-server 测试用例,完整讲解其使用决策、启动参数、协作循环、内容片段写法与底层安全机制,读完你可以直接在任何 Agent 驱动的设计讨论中启用并驾驭这套工具。
一、先判断该不该用:浏览器 vs 终端的决策准则
Visual Companion 的定位是工具而非模式:接受伴侣意味着「凡是适合可视化呈现的问题都可以走浏览器」,但不代表每个问题都要走浏览器。指南要求按问题(per-question)而非按会话(per-session)决策,判断标准只有一条:
用户「看到」这个问题是否比「读到」它更容易理解?
适合浏览器的内容(本身就是视觉性的):
- UI 线框图 —— 线框、布局、导航结构、组件设计
- 架构图 —— 系统组件、数据流、关系图
- 并排视觉对比 —— 两种布局、两种配色、两个设计方向
- 设计打磨 —— 问题本身关乎观感、间距、视觉层级
- 空间关系 —— 状态机、流程图、实体关系图
适合终端的内容(文字或表格性的):
- 需求与范围问题 —— “X 是什么意思?”“哪些功能在范围内?”
- 概念性 A/B/C 选择 —— 在文字描述的方案之间做选择
- 权衡清单 —— 优缺点、对比表格
- 技术决策 —— API 设计、数据建模、架构路线选择
- 澄清性问题 —— 答案是文字而非视觉偏好的任何问题
一个关键反直觉点:关于 UI 话题的问题不等于视觉问题。“你想要什么类型的向导(wizard)?”是概念性问题 —— 用终端;“这几个向导布局哪个感觉对?”才是视觉问题 —— 用浏览器。主技能文档 SKILL.md 进一步约束了启用时机:不要一上来就推荐伴侣,而是在第一个真正“看比说清楚”的问题出现时,单独发一条消息询问用户(并提示它仍较新、可能消耗较多 token),用户同意后才带 --open 启动服务器。
二、工作原理:文件监听、WebSocket 与会话密钥
指南给出的核心模型是:
服务器监听一个目录中的 HTML 文件,把最新的一个提供给浏览器。你把 HTML 内容写入
screen_dir,用户在浏览器中看到并可点击选择;选择被记录到state_dir/events,你在下一轮读取。
从源码 server.cjs 可以确认并补充这一机制的完整细节:
- 目录监听:
fs.watch(CONTENT_DIR, ...)监听content/目录下的.html文件(忽略点文件与 macOS 资源分叉._*.html边车文件),带 100ms 防抖;新文件会触发screen-added日志、清除旧的state/events文件,并向所有已连接的 WebSocket 客户端广播{type:'reload'},浏览器端收到后自动location.reload()(见 helper.js 第 100 行)。 - 最新文件判定:
getNewestScreen()按mtime排序取最新,且只接受位于content/内的常规文件(符号链接、硬链接逃逸一律拒绝)——这正是“永远不要复用文件名,每张屏幕用新文件”这一规则的底层原因。 - 片段 vs 完整文档:如果 HTML 文件以
<!DOCTYPE或<html开头,服务器原样提供(只注入 helper 脚本);否则自动包裹进 frame 模板 —— 加上页头、主题 CSS、连接状态指示和全部交互基础设施。默认写内容片段即可。该行为由 server.test.js 中 “serves full HTML documents as-is” 与 “wraps content fragments in frame template” 两个用例直接验证。 - 会话密钥:启动 JSON 的
url字段形如http://localhost:52341/?key=ab12…。服务器拒绝任何不带密钥的请求(返回 403 页面),密钥同时门禁 HTTP 与 WebSocket 访问,防止其他浏览器标签页或同网络设备的注入。首次加载后浏览器通过 HttpOnly cookie(名称为brainstorm-key-<port>)记住密钥,刷新与/files/*静态资源即可免重复携带。
因此操作上的硬性要求是:始终把 url 字段的完整 URL(含查询串)交给用户,绝不剥掉 ?key=…,也绝不发裸的 http://host:port。
三、启动会话:命令、参数与平台差异
基本启动命令
# 在用户批准伴侣之后启动。--open 会在第一张屏幕出现时自动打开用户浏览器;
# --project-dir 让 mockup 持久化,并支持同端口重启。
scripts/start-server.sh --project-dir /path/to/project --open
# 返回: {"type":"server-started","port":52341,
# "url":"http://localhost:52341/?key=ab12…",
# "screen_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/content",
# "state_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/state"}
注意这里的 scripts/start-server.sh 相对技能目录 skills/brainstorming/ 而言,仓库根目录下的实际路径是 skills/brainstorming/scripts/start-server.sh。
启动后务必保存返回 JSON 中的 screen_dir 与 state_dir。带 --open 时,浏览器会在你推送第一张屏幕时自行打开 —— 不必再要求用户手动打开,但仍要分享 URL 作为兜底(headless / 远程环境下不会自动打开)。
完整参数说明
结合脚本头部注释(start-server.sh 第 3–18 行),启动脚本支持的全部参数为:
| 参数 | 作用 | 默认值 |
|---|---|---|
--project-dir <path> |
会话文件存到 <path>/.superpowers/brainstorm/ 而非 /tmp,服务器停止后文件仍保留 |
无(用 /tmp) |
--host <bind-host> |
绑定的接口 | 127.0.0.1 |
--url-host <host> |
返回 URL JSON 中显示的主机名 | 绑定主机为回环时显示 localhost |
--idle-timeout-minutes <n> |
空闲 n 分钟后自动关机(必须为正整数) | 240(4 小时) |
--open |
第一张屏幕出现时自动打开浏览器(仅在用户批准后使用) | 关 |
--foreground / --no-daemon |
在前台终端运行(不后台化) | 关 |
--background / --daemon |
强制后台模式(覆盖 Codex 的自动前台化) | 关 |
服务器默认绑定随机高端口(49152–65534,见 server.cjs 第 86 行);若指定了 --project-dir,已绑定的端口与会话密钥会持久化到 .superpowers/brainstorm/.last-port 与 .last-token,重启时复用,已打开的浏览器标签页凭原 cookie 直接重连(lifecycle.test.js 中 “persists the bound port AND key, and restores both on restart” 用例验证了这一点)。
查找连接信息
服务器会把启动 JSON 写入 $STATE_DIR/server-info。如果后台启动了服务器却没捕获 stdout,读取该文件即可拿到 URL 和端口。使用 --project-dir 时,去 <project>/.superpowers/brainstorm/ 下找会话目录。
为什么强烈建议 --project-dir:带上它,mockup 持久化在 .superpowers/brainstorm/ 中,服务器重启后还能找回;不带则文件落在 /tmp,停止时即被清理。指南同时提醒:若 .gitignore 中还没有,应提醒用户把 .superpowers/ 加进去。
按平台启动
不同 Agent 宿主对后台进程的回收策略不同,脚本对此做了自动适配(见脚本第 97–107 行:检测 CODEX_CI 环境变量或 MSYS/Cygwin/MINGW shell 时自动切前台模式):
Claude Code —— 默认模式即可,脚本自己把服务器后台化:
scripts/start-server.sh --project-dir /path/to/project --open
Windows 上脚本自动检测并切换到前台模式(会阻塞工具调用)。此时在 Bash 工具调用上设置 run_in_background: true,让服务器跨轮存活,下一轮再读 $STATE_DIR/server-info 取 URL 与端口。
Codex —— Codex 会回收后台进程;脚本自动检测 CODEX_CI 并切换前台模式,正常执行即可,无需额外标志:
scripts/start-server.sh --project-dir /path/to/project --open
Gemini CLI —— 用 --foreground,并在 shell 工具调用上设 is_background: true,让进程跨轮存活:
scripts/start-server.sh --project-dir /path/to/project --open --foreground
Copilot CLI —— 用 --foreground,通过 bash 工具以 mode: "async" 启动,进程即可跨轮存活;如需后续交互,捕获返回的 shellId 供 read_bash / stop_bash 使用:
scripts/start-server.sh --project-dir /path/to/project --open --foreground
其他环境 —— 通用原则:服务器必须在后台跨会话轮存活。若你的环境会回收分离进程,就用 --foreground 加上平台自己的后台执行机制。启动脚本还有一个兜底:后台模式下它会在启动后持续轮询进程,若发现进程被宿主杀掉,会输出带 --foreground 重试提示的 JSON 错误(脚本第 188–200 行)。
远程/容器化场景:URL 从浏览器不可达时,绑定非回环主机:
scripts/start-server.sh \
--project-dir /path/to/project \
--host 0.0.0.0 \
--url-host localhost
--url-host 控制返回 URL JSON 中打印的主机名(例如绑定 0.0.0.0 但通过隧道访问时,仍显示 localhost)。
四、协作循环(The Loop):六步操作法
这是指南的核心操作规范,完整继承如下:
-
确认服务器存活,然后把 HTML 写入
screen_dir的新文件:- 必做:在引用 URL 或推送屏幕之前,先确认服务器存活 —— 检查
$STATE_DIR/server-info存在且$STATE_DIR/server-stopped不存在。若已关闭,用相同的--project-dir通过start-server.sh重启 —— 它会复用同一端口,用户已打开的标签页自行重连(服务器宕机期间页面显示 “paused” 遮罩),你不需要发新 URL。服务器默认 4 小时空闲自动退出(可用--idle-timeout-minutes调整)。 - 用语义化文件名:
platform.html、visual-style.html、layout.html - 绝不复用文件名 —— 每张屏幕都是新文件
- 使用文件创建工具 —— 绝不用
cat/heredoc(会把噪音倒进终端) - 服务器自动提供最新文件
- 必做:在引用 URL 或推送屏幕之前,先确认服务器存活 —— 检查
-
告知用户预期并结束你的回合:
- 每步都提醒 URL(不只是第一次)
- 用简短文字概括屏幕上是什么(如“正在展示首页的 3 个布局方案”)
- 请用户在终端回复:“看一下,告诉我你的想法。想选哪个就点一下。”
-
你的下一轮(用户在终端回复之后):
- 若存在则读取
$STATE_DIR/events—— 它包含用户浏览器交互(点击、选择)的 JSON 行 - 与用户的终端文字合并,得到全貌
- 终端消息是主要反馈;
state_dir/events提供结构化交互数据
- 若存在则读取
-
迭代或前进 —— 若反馈改变了当前屏幕,写一个新文件(如
layout-v2.html);只有当前步骤被确认后才进入下一个问题。 -
回到终端时卸载屏幕 —— 当下一步不需要浏览器时(如澄清问题、权衡讨论),推送一个等待屏清掉过时内容:
<!-- 文件名: waiting.html(或 waiting-2.html 等) --> <div style="display:flex;align-items:center;justify-content:center;min-height:60vh"> <p class="subtitle">Continuing in terminal...</p> </div>这防止用户在对话已推进时仍盯着一个已解决的选项。下一个视觉问题出现时,照常推送新的内容文件即可。
-
循环往复,直到完成。
源码层面可以佐证第 1 步的细节:服务器宕机后写 state/server-stopped(含原因与时间戳),启动时写 state/server-info(server.cjs 的 onListen 与 shutdown 函数);helper 端在断连 15 秒后显示 “Companion paused” 遮罩,重连成功后自动通过带密钥的 bootstrap 页面刷新 cookie 并回到正常页面(helper.js 第 4–5、91–94 行)。
五、编写内容片段:Frame 模板与可用 CSS 类
只需写页面内部的内容。服务器自动把它包裹进 frame 模板 —— 页头、主题 CSS(含跟随系统的明暗主题)、连接状态、全部交互基础设施都由服务器提供。
最小示例:
<h2>Which layout works better?</h2>
<p class="subtitle">Consider readability and visual hierarchy</p>
<div class="options">
<div class="option" data-choice="a" onclick="toggleSelect(this)">
<div class="letter">A</div>
<div class="content">
<h3>Single Column</h3>
<p>Clean, focused reading experience</p>
</div>
</div>
<div class="option" data-choice="b" onclick="toggleSelect(this)">
<div class="letter">B</div>
<div class="content">
<h3>Two Column</h3>
<p>Sidebar navigation with main content</p>
</div>
</div>
</div>
就这些。不需要 <html>、CSS、<script> 标签 —— 服务器都提供了。
Frame 模板提供的 CSS 类
以下分类与 frame-template.html 中的样式定义一一对应:
选项(A/B/C 选择):
<div class="options">
<div class="option" data-choice="a" onclick="toggleSelect(this)">
<div class="letter">A</div>
<div class="content">
<h3>Title</h3>
<p>Description</p>
</div>
</div>
</div>
多选:在容器上加 data-multiselect,用户即可选中/取消多个选项,每次点击切换该项的选中样式:
<div class="options" data-multiselect>
<!-- 同样的 option 标记 —— 用户可选择/取消多个 -->
</div>
从 helper.js 的 toggleSelect 实现看:单选容器内点击会先清除所有 .option/.card 的 selected 类再选中当前项;多选容器内只做 classList.toggle('selected'),并维护全局 window.selectedChoice 记录最后一次选择。
卡片(视觉设计展示):
<div class="cards">
<div class="card" data-choice="design1" onclick="toggleSelect(this)">
<div class="card-image"><!-- mockup 内容 --></div>
<div class="card-body">
<h3>Name</h3>
<p>Description</p>
</div>
</div>
</div>
.cards 是自适应网格(repeat(auto-fit, minmax(280px, 1fr))),.card-image 固定 16:10 宽高比,适合放线框截图。
Mockup 容器:
<div class="mockup">
<div class="mockup-header">Preview: Dashboard Layout</div>
<div class="mockup-body"><!-- 你的 mockup HTML --></div>
</div>
分栏视图(并排对比):
<div class="split">
<div class="mockup"><!-- 左 --></div>
<div class="mockup"><!-- 右 --></div>
</div>
.split 为两列网格,视口小于 700px 时自动折为单列。
Pros/Cons(优缺点对比):
<div class="pros-cons">
<div class="pros"><h4>Pros</h4><ul><li>Benefit</li></ul></div>
<div class="cons"><h4>Cons</h4><ul><li>Drawback</li></ul></div>
</div>
Mock 元素(线框积木件):
<div class="mock-nav">Logo | Home | About | Contact</div>
<div style="display: flex;">
<div class="mock-sidebar">Navigation</div>
<div class="mock-content">Main content area</div>
</div>
<button class="mock-button">Action Button</button>
<input class="mock-input" placeholder="Input field">
<div class="placeholder">Placeholder area</div>
排版与区块:
h2—— 页面标题h3—— 小节标题.subtitle—— 标题下的次级文本.section—— 带底部间距的内容块.label—— 小号大写字母标签
除预置类外,指南还留了扩展空间:helper 暴露了 window.brainstorm.send(event) 与 window.brainstorm.choice(value, metadata) 两个显式 API,屏幕 HTML 可携带内联脚本发送自定义事件;静态资源(如本地图片)可放入 content/ 目录、经 /files/<name> 路径引用(服务器仅放行 content/ 内的常规文件)。
六、浏览器事件格式:读取用户反馈
用户在浏览器中点击选项时,交互被记录到 $STATE_DIR/events(每行一个 JSON 对象)。推送新屏幕时该文件会被自动清除(server.cjs 文件监听回调中的 fs.unlinkSync(eventsFile),对应测试 “clears state/events on new screen”)。
{"type":"click","choice":"a","text":"Option A - Simple Layout","timestamp":1706000101}
{"type":"click","choice":"c","text":"Option C - Complex Grid","timestamp":1706000108}
{"type":"click","choice":"b","text":"Option B - Hybrid","timestamp":1706000115}
完整事件流展示了用户的探索路径 —— 他们可能在敲定之前点击多个选项。最后一个 choice 事件通常是最终选择,但点击模式本身也能揭示值得追问的犹豫或偏好。若 $STATE_DIR/events 不存在,说明用户没有与浏览器交互 —— 只用终端文字。
从 server.cjs 的 handleMessage 与 server.test.js 用例(“writes choice events to state/events”“does NOT write non-choice events to state/events”)可以确认一个实现细节:只有携带 choice 字段的事件才会落盘到 events 文件;没有 choice 的事件(如 hover)只记录到服务器 stdout(以 "source":"user-event" 打头),不会污染反馈文件。
七、源码级深潜:让协作循环可靠的几个关键机制
以下内容直接来自 server.cjs 及其测试,理解它们能解释指南中多条“看似教条”的规则:
- 会话密钥为何必须贯穿 URL:
isAuthorized()对?key=参数或 cookie 做恒定时间比较(crypto.timingSafeEqual);无密钥请求得到 403 提示页。WebSocket 升级还额外校验Origin === "http://" + Host,即使某个本地恶意标签页偷到了 cookie,也打不开事件通道 —— 这条加固的完整威胁模型见设计文档 visual-companion-auth-hardening-design。 - 重启为何能“无感”重连:
--project-dir模式下,端口与密钥分别持久化到.last-port/.last-token(权限 0600,且脚本以umask 077创建会话文件)。重启命中同一端口、同一密钥时,已开标签页的 cookie 依然有效;helper 还从sessionStorage读取密钥拼进 WebSocket URL,不依赖 cookie 行为。若首选端口被别的服务器占用,会回退到随机高端口,并且不覆盖共享的.last-port/.last-token(避免破坏另一个会话的已开标签页)。 /files/*为何不能读到会话密钥:server-info文件里嵌着带密钥的 URL。服务器的文件服务对空名、点文件、符号链接、以及任何 realpath 逃逸出content/的硬链接一律 404(isRegularFileInsideContentDir 检查非符号链接、nlink === 1与 realpath 前缀),测试套件中有专门的 “does not serve symlinks that escape content dir via /files/” 等回归用例。- 空闲超时与所有权监控:看门狗默认每 60 秒检查一次(
BRAINSTORM_LIFECYCLE_CHECK_MS可调),宿主 Agent 进程死亡即shutdown('owner process exited'),空闲超过IDLE_TIMEOUT_MS(默认 4 小时,--idle-timeout-minutes经BRAINSTORM_IDLE_TIMEOUT_MS传入)即shutdown('idle timeout');关机时销毁所有 WebSocket、删除server-info、写入server-stopped。只有已认证的请求才算“活动”——测试 “unauthenticated requests do not defeat the idle timeout” 验证了刷 403 请求无法拖延超时。 --open的克制设计:自动打开浏览器是显式 opt-in(BRAINSTORM_OPEN环境变量由--open设置)、只在第一张屏幕就绪且尚无任何客户端连接时触发一次、且只在回环绑定下生效;打开的 URL 必须携带密钥(无密钥的 URL 会 403)。这解释了指南中“--open只在用户批准伴侣之后使用”的告诫。
八、设计技巧与文件命名
设计技巧(指南原文要点):
- 保真度匹配问题 —— 布局问题给线框,打磨问题给精细稿
- 每页都要解释问题本身 —— 写“哪个布局更专业?”,而不只是“选一个”
- 先迭代再前进 —— 反馈改变当前屏幕时,写新版本
- 每屏 2–4 个选项为上限
- 重要处使用真实内容 —— 摄影作品集就用真实图片(如 Unsplash),占位内容会掩盖设计问题
- 保持 mockup 简单 —— 聚焦布局与结构,而非像素级完美
文件命名:
- 语义化命名:
platform.html、visual-style.html、layout.html - 绝不复用文件名 —— 每张屏幕必须是新文件
- 迭代时加版本后缀:
layout-v2.html、layout-v3.html - 服务器按修改时间(mtime)提供最新文件
九、清理:停止服务器
scripts/stop-server.sh $SESSION_DIR
$SESSION_DIR 即启动 JSON 中 screen_dir 的上一级目录(.../brainstorm/<session-id>)。stop-server.sh 的安全设计值得注意:它通过 server-instance-id 校验 PID 确实属于本次启动的服务器实例(防止重启或 PID 回绕后误杀无关进程,存疑即按 stale_pid 处理);先 SIGTERM 优雅停止(最多约 2 秒),仍存活再 SIGKILL;只有 /tmp 下的临时会话目录会被删除,.superpowers/ 下的持久化目录被保留,mockup 可留待日后查阅。停止后会写入 state/server-stopped 标记 —— 这正是协作循环第 1 步要检查的信号之一。
十、参考文件索引
| 内容 | 路径 |
|---|---|
| 本指南(权威操作规范) | visual-companion.md |
| brainstorming 主技能(何时提供伴侣) | SKILL.md |
| 启动脚本(参数、平台适配) | start-server.sh |
| 停止脚本(安全停机、临时目录清理) | stop-server.sh |
| 零依赖服务器(HTTP + 手写 RFC 6455 WebSocket) | server.cjs |
| Frame 模板(CSS 参考) | frame-template.html |
| 客户端 helper(重连、事件上报、选中状态) | helper.js |
| 集成测试(提供/监听/事件落盘行为) | server.test.js |
| 生命周期测试(空闲超时、端口/密钥持久化、重启重连) | lifecycle.test.js |
| 认证加固设计(威胁模型与边界) | auth-hardening-design |
最后一个与配置相关的备注:伴侣页面头部默认加载带版本号的 Prime Radiant 品牌图(用于粗略统计使用人数,不含项目内容)。设置 SUPERPOWERS_DISABLE_TELEMETRY(或 Claude Code 的 DISABLE_TELEMETRY、CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC)为任意真值即可禁用,此时页头只显示纯文本版本号(见 README 与 server.cjs 第 107–112 行)。
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