首页
/ Superpowers Visual Companion:浏览器端可视化头脑风暴的完整实践指南

Superpowers Visual Companion:浏览器端可视化头脑风暴的完整实践指南

2026-09-04 12:42:22作者:邬祺芯Juliet

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_dirstate_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" 启动,进程即可跨轮存活;如需后续交互,捕获返回的 shellIdread_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):六步操作法

这是指南的核心操作规范,完整继承如下:

  1. 确认服务器存活,然后把 HTML 写入 screen_dir 的新文件

    • 必做:在引用 URL 或推送屏幕之前,先确认服务器存活 —— 检查 $STATE_DIR/server-info 存在且 $STATE_DIR/server-stopped 不存在。若已关闭,用相同的 --project-dir 通过 start-server.sh 重启 —— 它会复用同一端口,用户已打开的标签页自行重连(服务器宕机期间页面显示 “paused” 遮罩),你不需要发新 URL。服务器默认 4 小时空闲自动退出(可用 --idle-timeout-minutes 调整)。
    • 用语义化文件名:platform.htmlvisual-style.htmllayout.html
    • 绝不复用文件名 —— 每张屏幕都是新文件
    • 使用文件创建工具 —— 绝不用 cat/heredoc(会把噪音倒进终端)
    • 服务器自动提供最新文件
  2. 告知用户预期并结束你的回合

    • 每步都提醒 URL(不只是第一次)
    • 用简短文字概括屏幕上是什么(如“正在展示首页的 3 个布局方案”)
    • 请用户在终端回复:“看一下,告诉我你的想法。想选哪个就点一下。”
  3. 你的下一轮(用户在终端回复之后):

    • 若存在则读取 $STATE_DIR/events —— 它包含用户浏览器交互(点击、选择)的 JSON 行
    • 与用户的终端文字合并,得到全貌
    • 终端消息是主要反馈;state_dir/events 提供结构化交互数据
  4. 迭代或前进 —— 若反馈改变了当前屏幕,写一个新文件(如 layout-v2.html);只有当前步骤被确认后才进入下一个问题。

  5. 回到终端时卸载屏幕 —— 当下一步不需要浏览器时(如澄清问题、权衡讨论),推送一个等待屏清掉过时内容:

    <!-- 文件名: 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>
    

    这防止用户在对话已推进时仍盯着一个已解决的选项。下一个视觉问题出现时,照常推送新的内容文件即可。

  6. 循环往复,直到完成。

源码层面可以佐证第 1 步的细节:服务器宕机后写 state/server-stopped(含原因与时间戳),启动时写 state/server-infoserver.cjsonListenshutdown 函数);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.jstoggleSelect 实现看:单选容器内点击会先清除所有 .option/.cardselected 类再选中当前项;多选容器内只做 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.cjshandleMessageserver.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 及其测试,理解它们能解释指南中多条“看似教条”的规则:

  • 会话密钥为何必须贯穿 URLisAuthorized()?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-minutesBRAINSTORM_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.htmlvisual-style.htmllayout.html
  • 绝不复用文件名 —— 每张屏幕必须是新文件
  • 迭代时加版本后缀:layout-v2.htmllayout-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_TELEMETRYCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC)为任意真值即可禁用,此时页头只显示纯文本版本号(见 READMEserver.cjs 第 107–112 行)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
983
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384