十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

superpowers 零依赖 Brainstorm Server 实现:仅用 Node 内置模块构建 RFC 6455 WebSocket 本地服务器的完整方案

superpowers 零依赖 Brainstorm Server 实现:仅用 Node 内置模块构建 RFC 6455 WebSocket 本地服务器的完整方案 superpowers 零依赖 Brainstorm Server 实现仅用 Node 内置模块构建 RFC 6455 WebSocket 本地服务器的完整方案【免费下载链接】superpowersAn agentic skills framework software development methodology that works.项目地址: https://gitcode.com/GitHub_Trending/su/superpowers本文围绕 superpowers 仓库中的实施计划 2026-03-11-zero-dep-brainstorm-server.md 展开讲解如何把 brainstorm 可视化伴侣服务器从依赖 vendored node_modulesexpress、ws、chokidar共 714 个跟踪文件的方案重构为一个只使用http、crypto、fs、path四个 Node 内置模块的单文件server.js。读完后你可以掌握从零实现 RFC 6455 WebSocket 握手与帧编解码、用fs.watch做防抖文件监听、以及一个既是可运行服务、又是可单测模块的双角色 Node 文件设计。背景与动机为什么要去掉 vendored 依赖superpowers 的 brainstorming 技能见 skills/brainstorming/SKILL.md会在本地启动一个可视化伴侣服务器agent 把 HTML 屏幕写入会话目录用户在浏览器里看到并点击选项选择结果回传给 agent。早期实现里这个服务器依赖打包进仓库的node_modules。设计文档 2026-03-11-zero-dep-brainstorm-server-design.md 明确给出了替换动机供应链风险vendored 的依赖被冻结在某个版本无法获得安全补丁714 个第三方代码文件未经审计地提交进仓库对 vendored 代码的修改在 git 历史中看起来和正常提交无异难以分辨。文档同时承认实际风险较低——这只是一个 localhost-only 的开发服务器——但消除它的方法很简单因此执行替换。替换后的对照关系为BeforeAfterindex.jspackage.jsonpackage-lock.json 714 个node_modules文件单个server.js约 250–300 行express、ws、chokidar 三个依赖无依赖无静态文件服务/files/*路由直接服务屏幕目录而helper.js、frame-template.html、start-server.sh仅一处改名、stop-server.sh全部保持不变服务器的对外行为契约stdout JSON 协议、文件布局也不变。总体架构一个文件两种身份计划文档的目标Goal与架构Architecture定义如下Goal:Replace the brainstorm servers vendored node_modules with a single zero-dependencyserver.jsusing Node built-ins.Architecture:Single file with WebSocket protocol (RFC 6455 text frames), HTTP server (httpmodule), and file watching (fs.watch). Exports protocol functions for unit testing when required as a module.这个文件承担双重角色直接运行node server.js启动 HTTP/WebSocket 服务器被 requirerequire(./server.js)导出computeAcceptKey、encodeFrame、decodeFrame、OPCODES供单元测试直接调用协议层无需起服务、无需真实网络。计划文档的File Map完整列出了改动清单是理解整个变更范围的最佳入口Create:skills/brainstorming/scripts/server.js— 零依赖实现本体Modify:skills/brainstorming/scripts/start-server.sh:94,100— 启动命令中的index.js改为server.jsModify:.gitignore:6— 删除!skills/brainstorming/scripts/node_modules/白名单例外Delete:skills/brainstorming/scripts/index.js、package.json、package-lock.json、node_modules/714 文件No changes:helper.js、frame-template.html、stop-server.sh实施采用 subagent 驱动的三块Chunk拆分协议层 → 服务器与应用逻辑 → 切换与清理每块独立提交且每一步都有对应测试测试先行tests/brainstorm-server/ws-protocol.test.js单元与tests/brainstorm-server/server.test.js集成在实施前就已存在。Chunk 1WebSocket 协议层RFC 6455这是计划中 Task 1 的内容也是技术含金量最高的一块不引入ws库自己实现文本帧的完整协议。握手Sec-WebSocket-Accept 的计算RFC 6455 规定服务器应答Sec-WebSocket-Accept字段为SHA1(clientKey 魔法 GUID)的 Base64 编码。计划中的实现仅三行const crypto require(crypto); const OPCODES { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; const WS_MAGIC 258EAFA5-E914-47DA-95CA-C5AB0DC85B11; function computeAcceptKey(clientKey) { return crypto.createHash(sha1).update(clientKey WS_MAGIC).digest(base64); }单元测试 tests/brainstorm-server/ws-protocol.test.js 直接使用了 RFC 6455 第 4.2.2 节的官方示例向量来验证客户端 Key 为dGhlIHNhbXBsZSBub25jZQ时Accept 值必须是s3pPLMBiTxaQ9kYGzzhZRbKxOo同时验证随机 16 字节 Key 的产出是合法 Base64 且长度恒为 28 字符SHA-1 输出 20 字节 → Base64 28 字符。帧编码服务器帧永远不带掩码encodeFrame实现三种长度编码对应 RFC 6455 的长度字段规则payload 126 字节2 字节头FINopcode长度126–65535 字节4 字节头FINopcode12616 位长度65535 字节10 字节头FINopcode12764 位长度writeBigUInt64BE。function encodeFrame(opcode, payload) { const fin 0x80; const len payload.length; let header; if (len 126) { header Buffer.alloc(2); header[0] fin | opcode; header[1] len; } else if (len 65536) { header Buffer.alloc(4); header[0] fin | opcode; header[1] 126; header.writeUInt16BE(len, 2); } else { header Buffer.alloc(10); header[0] fin | opcode; header[1] 127; header.writeBigUInt64BE(BigInt(len), 2); } return Buffer.concat([header, payload]); }单测对边界值做了精确断言125 字节小帧上限头部第二字节为 125、总长 127126 字节恰好落入 16 位扩展编码头部第二字节 126200 字节帧总长 20470000 字节帧走 64 位扩展编码。帧解码客户端帧必须带掩码decodeFrame处理来自客户端的帧。协议要求客户端帧必须掩码防缓存投毒因此对未掩码帧直接抛错对不完整缓冲返回null而非抛错——这是缓冲累积模式的关键。解码按位解析第一字节取 opcodefirstByte 0x0F、第二字节的掩码位secondByte 0x80与长度低 7 位长度字段为 126/127 时再读扩展长度随后用 4 字节掩码逐字节 XOR 还原 payload最终返回{ opcode, payload, bytesConsumed }供调用方推进缓冲function decodeFrame(buffer) { if (buffer.length 2) return null; const firstByte buffer[0]; const secondByte buffer[1]; const opcode firstByte 0x0F; const masked (secondByte 0x80) ! 0; let payloadLen secondByte 0x7F; let offset 2; if (!masked) throw new Error(Client frames must be masked); if (payloadLen 126) { if (buffer.length 4) return null; payloadLen buffer.readUInt16BE(2); offset 4; } else if (payloadLen 127) { if (buffer.length 10) return null; payloadLen Number(buffer.readBigUInt64BE(2)); offset 10; } const maskOffset offset; const dataOffset offset 4; const totalLen dataOffset payloadLen; if (buffer.length totalLen) return null; const mask buffer.slice(maskOffset, dataOffset); const data Buffer.alloc(payloadLen); for (let i 0; i payloadLen; i) { data[i] buffer[dataOffset i] ^ mask[i % 4]; } return { opcode, payload: data, bytesConsumed: totalLen }; }文件末尾按计划追加模块导出完成协议可单测的设计module.exports { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };协议层完成后运行cd tests/brainstorm-server node ws-protocol.test.js全部通过后以Add WebSocket protocol layer for zero-dep brainstorm server提交。值得说明的是设计文档对刻意不实现什么也做了明确取舍二进制帧、分片消息、permessage-deflate 扩展、子协议全部跳过——本地客户端之间传输的是小体积 JSON 文本消息用不上且扩展与子协议在握手阶段协商不主动声明即永远不会激活。Chunk 2HTTP 服务器与应用逻辑Task 2 在server.js中补齐配置、路由、WebSocket 连接处理、文件监听与服务启动构成完整可运行的服务器。配置常量全部来自环境变量计划 Step 1 定义了四个可选环境变量与 spec 的 Configuration 一节一一对应const http require(http); const fs require(fs); const path require(path); const PORT process.env.BRAINSTORM_PORT || (49152 Math.floor(Math.random() * 16383)); const HOST process.env.BRAINSTORM_HOST || 127.0.0.1; const URL_HOST process.env.BRAINSTORM_URL_HOST || (HOST 127.0.0.1 ? localhost : HOST); const SCREEN_DIR process.env.BRAINSTORM_DIR || /tmp/brainstorm;环境变量作用默认值BRAINSTORM_PORT绑定端口随机高端口 49152–65535BRAINSTORM_HOST绑定接口127.0.0.1BRAINSTORM_URL_HOST启动 JSON 中展示的 URL 主机名host 为127.0.0.1时取localhost否则与 host 相同BRAINSTORM_DIR屏幕目录路径/tmp/brainstorm随机高端口 只绑回环地址是该服务器零配置、零冲突、零暴露的基本盘每个会话拿一个不同的随机高位端口天然避免与本机其他服务撞车。模板加载与屏幕选取Step 2 在模块作用域预加载frame-template.html与helper.js得到helperInjection把 helper 包进script标签注入每页并提供一组纯函数function isFullDocument(html) { const trimmed html.trimStart().toLowerCase(); return trimmed.startsWith(!doctype) || trimmed.startsWith(html); } function wrapInFrame(content) { return frameTemplate.replace(!-- CONTENT --, content); } function getNewestScreen() { const files fs.readdirSync(SCREEN_DIR) .filter(f f.endsWith(.html)) .map(f { const fp path.join(SCREEN_DIR, f); return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; }) .sort((a, b) b.mtime - a.mtime); return files.length 0 ? files[0].path : null; }isFullDocument用于区分agent 推送的是完整 HTML 文档还是内容片段片段会被包进 frame 模板替换!-- CONTENT --占位符完整文档则原样返回。目录中没有 HTML 时返回首页硬编码的等待页WAITING_PAGE文案为 Waiting for Claude to push a screen...。HTTP 路由三个分支handleRequest实现了设计文档所述的全部路由function handleRequest(req, res) { if (req.method GET req.url /) { const screenFile getNewestScreen(); let html screenFile ? (raw isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, utf-8)) : WAITING_PAGE; if (html.includes(/body)) { html html.replace(/body, helperInjection \n/body); } else { html helperInjection; } res.writeHead(200, { Content-Type: text/html }); res.end(html); } else if (req.method GET req.url.startsWith(/files/)) { const fileName req.url.slice(7); // strip /files/ const filePath path.join(SCREEN_DIR, path.basename(fileName)); if (!fs.existsSync(filePath)) { res.writeHead(404); res.end(Not found); return; } const ext path.extname(filePath).toLowerCase(); const contentType MIME_TYPES[ext] || application/octet-stream; res.writeHead(200, { Content-Type: contentType }); res.end(fs.readFileSync(filePath)); } else { res.writeHead(404); res.end(Not found); } }要点GET /按 mtime 选取最新.html、注入 helper.js有/body就在其前注入否则追加到页尾GET /files/*用path.basename剥离路径成分、按硬编码的 MIME 表html/css/js/json/png/jpg/jpeg/gif/svg返回静态资源替代了 express 的静态服务其余请求一律 404。WebSocket 连接处理握手、缓冲累积与消息分发handleUpgrade在 HTTP 服务器的upgrade事件上完成协议切换校验sec-websocket-key用computeAcceptKey计算应答写回HTTP/1.1 101 Switching Protocols头后接管原始 socket。之后每个连接有独立的累积缓冲data事件中循环调用decodeFrame直到返回null缓冲不完整或缓冲耗尽——这正是设计文档 Buffer accumulation 一节的落地socket.on(data, (chunk) { buffer Buffer.concat([buffer, chunk]); while (buffer.length 0) { let result; try { result decodeFrame(buffer); } catch (e) { socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); clients.delete(socket); return; } if (!result) break; buffer buffer.slice(result.bytesConsumed); // TEXT - handleMessage / CLOSE - 回 close 帧 / PING - 回 PONG / PONG - 忽略 // 未知 opcode - 以 1003Unsupported Data关闭 } });消息与应用层行为由三个函数承载handleMessage(text)TEXT 帧按 JSON 解析解析失败仅记 stderr 后继续不破坏连接成功后以{ source: user-event, ...event }打印到 stdout且当事件含choice字段时追加到SCREEN_DIR/.events每行一个 JSON——这是用户在浏览器里的选择回传给 agent 的通道broadcast(msg)把消息编码为 TEXT 帧写给clients集合中的每个 socket写入失败的连接被剔除debounceTimersMap按文件名 100ms 防抖规避 macOS/Linux 上常见的重复事件。startServer 与条件入口startServer把生命周期串起来if (require.main module)保证只有直接运行时才启动function startServer() { if (!fs.existsSync(SCREEN_DIR)) fs.mkdirSync(SCREEN_DIR, { recursive: true }); const server http.createServer(handleRequest); server.on(upgrade, handleUpgrade); const watcher fs.watch(SCREEN_DIR, (eventType, filename) { if (!filename || !filename.endsWith(.html)) return; if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename)); debounceTimers.set(filename, setTimeout(() { debounceTimers.delete(filename); const filePath path.join(SCREEN_DIR, filename); if (eventType rename fs.existsSync(filePath)) { const eventsFile path.join(SCREEN_DIR, .events); if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); console.log(JSON.stringify({ type: screen-added, file: filePath })); } else if (eventType change) { console.log(JSON.stringify({ type: screen-updated, file: filePath })); } broadcast({ type: reload }); }, 100)); }); watcher.on(error, (err) console.error(fs.watch error:, err.message)); server.listen(PORT, HOST, () { const info JSON.stringify({ type: server-started, port: Number(PORT), host: HOST, url_host: URL_HOST, url: http:// URL_HOST : PORT, screen_dir: SCREEN_DIR }); console.log(info); fs.writeFileSync(path.join(SCREEN_DIR, .server-info), info \n); }); } if (require.main module) { startServer(); }文件监听用fs.watch替代 chokidar新文件rename 且文件存在删除.events并记screen-added内容变更记screen-updated但不清.events两种事件都向所有浏览器客户端广播{ type: reload }。启动成功时既向 stdout 打印server-startedJSON也写入SCREEN_DIR/.server-info——这样即使服务器以后台方式运行、stdout 被隐藏agent 也能从文件里读回连接信息。错误处理遵循记录并继续原则坏 JSON 记 stderr、未知 opcode 以 1003 关闭、断连即从广播集剔除、fs.watch出错记 stderr计划明确不做优雅关停逻辑进程生命周期由 shell 脚本经 SIGTERM 接管。Chunk 3切换与清理Task 3 完成新旧实现切换共五步改启动脚本skills/brainstorming/scripts/start-server.sh 中第 94、100 行的启动命令把node index.js改为node server.js前台与nohup后台两种启动方式各一行删 gitignore 例外移除.gitignore第 6 行的!skills/brainstorming/scripts/node_modules/让该目录重新落入忽略规则删除旧文件git rm掉index.js、package.json、package-lock.json与整个node_modules/跑双测试套件cd tests/brainstorm-server node ws-protocol.test.js node server.test.js全绿才算完成提交Remove vendored node_modules, swap to zero-dep server.js。Task 4 是收尾的人工冒烟测试五个步骤cd skills/brainstorming/scripts BRAINSTORM_DIR/tmp/brainstorm-smoke BRAINSTORM_PORT9876 node server.js预期 stdout 出现端口 9876 的server-startedJSON浏览器打开http://localhost:9876应显示等待页 Waiting for Claude to push a screen...随后echo h2Hello from smoke test/h2 /tmp/brainstorm-smoke/test.html写入屏幕目录浏览器自动重载并以 frame 模板包裹显示该片段DevTools 中 WebSocket 应保持 connected 且状态指示器显示 Connected最后 Ctrl-C 停止并rm -rf /tmp/brainstorm-smoke清理。从计划到当前仓库源码层面的演进印证计划文档是 2026-03-11 的实施快照当前仓库中该方案的落地文件是 skills/brainstorming/scripts/server.cjs文件名由server.js演进为server.cjs。对照计划代码阅读现在的实现可以清楚看到零依赖 协议函数导出的核心骨架被完整保留并在后续迭代中加固协议层与计划一致并加了安全上限computeAcceptKey/encodeFrame/decodeFrame/OPCODES的实现与计划逐行对应见 server.cjs#L8-L81此外新增MAX_FRAME_PAYLOAD_BYTES 10 * 1024 * 1024在decodeFrame中对超限的 64 位扩展长度与 16 位长度都抛出 WebSocket frame payload exceeds maximum allowed size——这是计划版没有的资源保护。导出面扩大当前module.exports在计划的四个导出之外增加了browserLauncherForPlatform与MAX_FRAME_PAYLOAD_BYTES见 server.cjs#L716-L723对应新增的平台化浏览器启动逻辑。配置面扩展在计划的环境变量之外当前实现还识别BRAINSTORM_PORT_FILE重启复用上次端口让已打开的浏览器标签自动重连、BRAINSTORM_TOKEN/BRAINSTORM_TOKEN_FILE会话密钥、BRAINSTORM_OWNER_PID属主进程存亡看门狗、BRAINSTORM_IDLE_TIMEOUT_MS默认 4 小时空闲自退出、BRAINSTORM_OPEN/BRAINSTORM_OPEN_CMD批准后自动开浏览器等目录布局也从单一SCREEN_DIR细化为SESSION_DIR/contentSESSION_DIR/state两级见 server.cjs#L85-L104。认证与安全头所有 HTTP 请求与 WebSocket upgrade 都先过isAuthorized?key查询参数或会话 Cookie均以crypto.timingSafeEqual恒定时间比较upgrade 还校验 Origin 与http://host同源响应统一带X-Frame-Options: DENY、CSP frame-ancestors none、Cache-Control: no-store等安全头见 server.cjs#L321-L383。这些属于计划之后叠加的加固读计划文档时应将其视为演进增量而非原始范围。生命周期管理startServer中新增了属主进程存活检查process.kill(pid, 0)探测、EADDRINUSE时一次性回退随机端口、以及 shutdown 时关闭 watcher/定时器/全部已升级 socket 后process.exit(0)的完整收尾见 server.cjs#L616-L644——弥补了计划中不做优雅关停的部分职责。启动脚本 start-server.sh 同样保留并扩展了计划中的契约除--project-dir、--host、--url-host外还接受--idle-timeout-minutes默认 240 分钟、--open、--foreground/--background生成${PID}-$(date %s)形式的会话目录以nohup env BRAINSTORM_DIR... node server.cjs ... 后台启动Windows/Git Bash 或 Codex CI 环境自动切换前台模式随后最多轮询 5 秒等待日志出现server-started命中后校验进程存活并回显该行 JSON见 start-server.sh#L178-L205。停止端 stop-server.sh 则通过--brainstorm-server-id实例参数核对 PID 真实性、先 SIGTERM 等待约 2 秒再升级 SIGKILL且只删除/tmp下的临时会话目录--project-dir的持久目录保留供事后回看见 stop-server.sh#L73-L118。测试体系协议单测 服务器集成测该方案的可验证性设计值得单独强调。tests/brainstorm-server/ws-protocol.test.js 是纯单元测试它require服务器模块当前路径为../../skills/brainstorming/scripts/server.cjs直接用断言驱动computeAcceptKey、encodeFrame、decodeFrame覆盖 RFC 6455 官方握手向量、三种长度编码的边界125/126/200/70000 字节、掩码强制、不完整缓冲返回null等场景全程不启动 HTTP 服务器。集成测试 tests/brainstorm-server/server.test.js 则验证完整服务器行为——HTTP 服务、WebSocket 通信、文件监听与 brainstorm 工作流使用 npm 的ws包作为测试专用客户端依赖声明于 tests/brainstorm-server/package.json不随技能分发给最终用户。这一生产零依赖、测试可依赖的边界划分是设计文档 Testing 一节的原意也解释了为何计划 Chunk 2 的集成测试步骤允许npm install。要点速查协议核心握手 SHA1(key 258EAFA5-E914-47DA-95CA-C5AB0DC85B11)的 Base64客户端帧必须掩码未掩码即抛错不完整缓冲返回null触发累积而非报错未知 opcode 以 1003 关闭。路由GET /取 mtime 最新的.html片段包 frame、注入 helper.js无文件时给等待页GET /files/*静态资源 MIME 表其余 404。配置BRAINSTORM_PORT默认随机 49152–65535、BRAINSTORM_HOST默认127.0.0.1、BRAINSTORM_URL_HOST默认localhost、BRAINSTORM_DIR默认/tmp/brainstorm全部可选。stdout JSON 事件协议server-started同时写入.server-info文件、screen-added/screen-updated同时向浏览器广播reload、user-event含choice的事件另追加进.events。实施节奏协议层 → 服务器逻辑 → 切换清理三个 Chunk每步测试先行、独立提交冒烟测试覆盖启动 JSON、等待页、HTML 热更新与 WebSocket 连通。演进提示计划中的server.js在当前仓库对应 server.cjs阅读当前实现时会额外看到 10MB 帧上限、会话 Token 认证、属主进程看门狗与端口复用等后续加固均为同一零依赖骨架之上的增量。【免费下载链接】superpowersAn agentic skills framework software development methodology that works.项目地址: https://gitcode.com/GitHub_Trending/su/superpowers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表