mcpField
R
replit

replit

github.com/skynight137云与 DevOps0108 次安装1 天前更新
代码仓库

关于 replit

面向 Replit/Nix 工作区运行与维护任务的技能集合,按持久化、工具链、浏览器、网络和桌面应用等场景提供操作手册与脚本。它强调 Replit 环境的特殊约束及可重复执行的修复流程。

主要功能

  1. 01Replit/Nix 环境操作指南
  2. 02持久化与环境变量管理
  3. 03Nix 系统库处理
  4. 04浏览器运行与自动化方案
  5. 05端口公开与服务配置
  6. 06跨工作区连接流程

适用场景

  1. 01配置重启后仍保留的数据与环境
  2. 02在工作区运行浏览器或桌面应用
  3. 03排查缺失系统库与 TLS 信任问题
  4. 04通过 Tailscale 连接另一工作区

MCP 相关

文档包含在 Replit 上运行 llm_wiki 桌面应用并通过 HTTP 提供 MCP 的操作说明,包括令牌和服务端口配置。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/skynight137/skills --skill replit

安装后在 Claude Code 中输入/replit确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
anti-bot-detection.md
browser-compare-report.md
browser-lanes.md
camofox.md
exposing-ports.md
hermes-on-replit.md
join-bridge.md
large-files-disk.md
llm-wiki-server.md
nix.md
platform.md
playwright-chromium.md
public-ports.md
session-keepalive.md
setup-tool-checklist.md
shell-rc-and-zsh.md
ssh-hop-manual.md
tls-trust.md
toolchain-layout.md
web-content-lanes.md
scripts/
camofox.py
dot_replit.py
join-lifecycle-test.sh
probe-tailnet-peers.sh
setup.sh
scripts/llm-wiki/
install.sh
llmwiki-server.js
run-public.sh
run-ui.sh
start-app.sh

详细介绍

Replit 工具包——一个技能,四条路径

路径 5 指南:从这台机器连接到另一台机器(sshd 引导、代理端口实际情况、command -v/横幅探测陷阱、通过路径而非粘贴来传递)见 references/join-bridge.md。开始前先了解传输规则:只能通过 ProxyCommand 调用 tailscale 守护进程自带的 nc(tailscale --socket=<sock> nc %h %p)的 Host 配置块 ssh 到另一台机器;直接连接 tailnet IP、使用 -J 跳转,或手动搭建 SOCKS5 转发,都会连到平台网关(先收到 dEob… 二进制前导数据,然后连接关闭),并在状态正常的机器上显示为“banner exchange timeout”。请从现有的 Host 条目复制可用配置块,不要自行设计传输方式。 可达性是按节点而非按 tailnet 计算的:只有列出的对等节点已加入、在该节点运行了准备步骤并持有跳转密钥后,才能响应。使用 scripts/probe-tailnet-peers.sh [port] 进行探测,切勿承诺“tailnet 上的任意机器”都能访问;使用 scripts/socks5-probe.py <ip> <port>... 可扫描单台主机的多个端口(可达性说明见 references/join-bridge.md)。此处会将这台机器自身对应的行打印为 self——请从 JSON 的 Self 字段确定自身节点,切勿依据记忆中的名称,因为普通的 status 表格不会标记任何一行。

这里的一切都只适用于在 Replit/Nix 工作区中运行。该平台会打破 Linux 的常规预期($HOME 会被清除、/nix/store 会进行垃圾回收、npm 会遭遇防火墙拦截),下面每条路径都是针对一项任务经过验证的操作手册。

如果你是第一次使用这台机器,请先阅读 references/platform.md——其他所有路径都默认你了解其持久化规则。

你想要……阅读脚本
持久保存数据/env/rc/git,了解重启后哪些内容会保留,修复环境变量传递references/platform.mdsetup.sh、dot_replit.py
在 setup.sh 中添加工具,决定其负载/配置/缓存的存放位置,为标志或变量命名references/toolchain-layout.mdscripts/setup/
获取未安装的系统库(GTK、libatomic、任意 .so),排查 cannot open shared object filereferences/nix.mdscripts/setup/generate-closure.sh、libpool.sh
无需下载浏览器即可驱动 Playwright(此环境自带 Chromium)references/playwright-chromium.md$REPLIT_PLAYWRIGHT_CHROMIUM_EXECUTABLE、start-replit-chromium.sh
打开对机器人进行强化防护的网站(Cloudflare/Turnstile/WAF),保持已登录的 Firefox 会话,在 Hermes 上进行 Agent 浏览(camofox MCP — 此处的 browser_exec 已损坏)references/camofox.mdsetup.sh --camofox(npm 通道)、camofox-browser、camofox.py
选择合适的浏览器通道(共有 4 种:Replit Playwright Chromium、Hermes Chrome 145、agent-browser CLI、Camofox)references/browser-lanes.md$REPLIT_PLAYWRIGHT_CHROMIUM_EXECUTABLE、.hermes/tools/chromium-1208、agent-browser、:9377
以无头模式运行桌面 GUI 应用(AppImage/Tauri/GTK)——Xvfb、窗口管理器、软件 GL、EGL/加载器陷阱headless-gui-linux 技能start.sh 风格的启动器、xwininfo -root -tree 门控
判断浏览器是否被识别为机器人;测试网站及其解读方式references/anti-bot-detection.md(测试页面:sannysoft、CreepJS、BrowserLeaks、NopeCHA)
比较此环境中的所有浏览器通道(版本、加载、webdriver/隐身、WebGL)——完整运行报告references/browser-compare-report.mdstart-replit-chromium.sh、resolve-libs.sh、launch-hermes-chrome
让 HTTPS 客户端(Python、Node、git、uv)信任 Replit 代理,而不禁用验证references/tls-trust.mdsetup/rc.sh、setup/common.sh(TLS 块)
向浏览器公开服务(声明端口、URL 中的端口、仅 HTTP 的路由器、Tailscale 用户空间、[[workflows]] 配置、硬编码绑定)references/exposing-ports.md.replit [[ports]] / [[workflows]]
确定 Replit 上哪些端口可以公开,以及如何公开端口(在 .replit 中声明,边缘网络仅在重启后开放;支持列表 = 80、3000-3003、4200、5000、5173、6000、6800、8000、8008、8080、8081、8099、9000)references/public-ports.md—
向 Agent 提供知识库(无头运行 llm_wiki 桌面应用 + 通过 HTTP 提供 MCP、只读令牌、supervisor),以及真实桌面 UI 的 noVNC 视图references/llm-wiki-server.mdsetup.sh --llm-wiki(程序包:scripts/llm-wiki/)、run-public.sh、run-ui.sh、start-app.sh、llmwiki-server.js。公共端口 = LLM_WIKI_PORT(默认 8080;仅在 Hermes 仪表盘占用 8080 时使用 8081);本地 Agent 使用环回地址 8199(无需令牌)。MCP 仅运行在服务端口上——应用 API 19828 对 /mcp 的响应为 {"error":"Not found"}。令牌请求头为 x-llm-wiki-token 或 Authorization: Bearer;写入工具为 llm_wiki_write_page,字段为 content(不是 body)。此环境的权威 wiki 位于 -wiki 环境(角色后缀);程序包路径为 $REPL_HOME/llmwiki-app,.replit 工作流为 llm-wiki-server(run-public.sh、waitForPort 8199)、llm-wiki-ui(run-ui.sh)和 tailscale-join;Hermes MCP 条目为 https://<that box's REPLIT_DEV_DOMAIN>/mcp + Authorization: Bearer ${LLM_WIKI_API_TOKEN},令牌从配置文件 .env 读取(请求头会展开 ${VAR};mcp_servers.*.env 的值不会)。项目目录必须实际存在于磁盘上——过时的 LLM_WIKI_PROJECT 若指向不存在的目录,会导致每次 MCP 调用都失败并报错 Failed to resolve project path;请创建带有 wiki/ 基本结构的目录,或让 llm_wiki_set_project 指向现有目录。本地 wiki/ 文件夹仅为镜像。基于此环境经验的页面带有来源标签 repl(Replit 之外的读者可仅凭此标签跳过这些页面);项目的 wiki/SCHEMA.md 记录了标签分类、必需的 frontmatter 和证据规则(`target
管理交互式 shell(zsh + oh-my-zsh + powerlevel10k、持久化 dotfiles、rc 配置规则)references/shell-rc-and-zsh.mdscripts/setup/zsh.sh、scripts/setup/rc.sh、scripts/setup/common.sh(工具变量注册表)
托管仅限浏览器访问的 Web 终端 / 在第二个环境中获取真实 shell,或从此处访问刚创建的一次性环境references/join-bridge.mdscripts/setup.sh --tailscale --join(主机密钥 + 仅密钥 sshd 配置 + 加入 tailnet、持久化 ~/.ssh、打印一行相对于工作区的工作流任务)——单独使用 --tailscale 只会安装二进制文件,--workflow = 在工作流模式下执行相同操作;在工作流参数中内联设置 TAILSCALE_NODE_NAME=<name>(或设置为 Replit 密钥)以固定 tailnet 节点名称,否则保留控制台名称
在 setup.sh 中添加工具或标志(约 2 打调用点、修复/验证/清理、rc 写入器测试框架)references/setup-tool-checklist.mdsetup.sh、scripts/setup/*.sh
Hermes-on-Replit 的专门恢复指南(uv --locked 尾随斜杠故障、官方 Node ≥22 tarball 需要 libatomic):references/hermes-on-replit.md。

关于该平台的七个事实(详见 platform.md)

  1. 重建时,$HOME(/home/runner)会被清空;$REPL_HOME (/home/runner/workspace)会保留。 任何需要保留的内容都应放在工作区中。XDG 变量已预设为指向工作区,但不要想当然地相信这一点——请用 printenv | grep XDG_ 检查。 ~/.ssh 和 ~/.local/bin 也不会例外,即使它们通常存在且看起来已存在一段时间:启动脚本会重写 authorized_keys 并重新发布启动器,因此文件完好并不能证明它们被保留——信任之前,请将目录的修改时间与运行时长进行比较。因此,$HOME 下的 tailnet 节点密钥会在每次启动时重新注册(新的 100.x、新的节点名称),所有已保存的 URL / .ssh/config 条目都会失效;请将节点状态保存在 $TAILSCALE_STATE_DIR($REPL_HOME/.local/state/tailscale,即 XDG 状态目录)中,setup.sh --tailscale / --join 已会使用该目录(旧版 .local/share/tailscale/state 会自动接管)。如果某个工具坚持使用 $HOME 路径(~/.ssh、~/.gnupg),请将其符号链接到 $REPL_HOME(先移入现有目录的内容,再运行 ln -s)——见 references/platform.md §1。
  2. 守护进程绝不会从 shell rc 自动启动。 .bashrc / .profile / $REPL_HOME/.config/bashrc 会在操作员每次打开终端时重新加载,因此其中的服务命令会在每个终端中重新触发,需要在每个 shell 中设置新的守卫。长时间运行的服务(sshd、tailscaled、llm-wiki 堆栈)应放在 .replit 的 [workflows] 条目中,该条目每次启动时运行一次——由操作员根据一条打印出的命令在 Replit UI 中添加,而不是由安装程序写入 .replit:名称和顺序由操作员决定。rc 中只放 PATH / 环境变量。见 references/shell-rc-and-zsh.md。 安装程序输出的全部提示就是那一行命令,且路径应相对于工作区(工作流以工作区为 cwd 运行,这样技能目录移动或重命名后就不会留下失效的绝对路径)。如果条目已存在,请先验证其中指定的路径,再检查它是否带有额外标志——否则失效路径会被“已接入”分支掩盖;grep 时只匹配带有应用自身标志的那一行,因为其他工作流可能指向同一脚本。由此推论,代码中的工作流 args 必须指向持久且相对于工作区的路径(即 $REPL_HOME 下的技能目录),绝不能指向复制到 $HOME 的脚本,并且必须具备幂等性——重复运行是常见的修复方式,因此应跳过正在运行的守护进程,而不是将其杀死(杀掉 tailscaled 会切断当前 ssh 正在使用的 SOCKS 跳转)。
  3. 所有系统级内容都来自 /nix/store,而存储路径可能被 GC 清理。 昨天还能用的路径今天可能就不存在;长期使用的内容需要在存储库之外留一份持久副本(见 nix.md、hermes-on-replit.md)。 这里有两个存储库:共享的只读 /nix/store(约 39 GB),以及每个 repl 独有的 $REPL_HOME/.store/nix(约 0.5 GB),用于保存该 repl 的 nix build / nix profile 输出,使其在沙盒重建后仍然保留。只有需要系统库的工具才会用到其中任一存储库——AppImage / 桌面应用(llm-wiki 的 Tauri 包需要 libX11/mesa 以及 Xvfb/fluxbox/shared-mime-info,camofox 的 Firefox 需要 GTK 闭包),而静态二进制文件(rclone、aria2c、qbittorrent-nox、ffmpeg、cloudflared、tailscale、sshx)和自包含归档(node、uv)则完全不会用到。因此,“为什么只有这个工具使用 nix?”可通过 ldd <binary> 解答:静态二进制文件不会列出任何内容,GUI 程序则会列出必须解析到闭包中的库(.ldpath / closure.txt)。
  4. 仅有 $REPLIT_LD_LIBRARY_PATH 不够,GUI 浏览器需要完整的传递闭包(nix.md §3;scripts/setup/generate-closure.sh)。
  5. npm 受软件包防火墙限制;请固定使用 --registry=https://registry.npmjs.org/。 它可能会直接拒绝某个传递依赖(在带有 CVE 标记的软件包上出现 403 ... Blocked by Security Policy),而且通常只有 dev 依赖树会拉取这个问题依赖。此 shell 还会导出 NODE_ENV=production,此时 npm 会静默省略 devDependencies——tsc: command not found 就是明显的信号。安装和构建时请在命令前加上 env -u NODE_ENV。
  6. Replit 提供由 Playwright 管理的 Chromium——$REPLIT_PLAYWRIGHT_CHROMIUM_EXECUTABLE;绝不要运行 playwright install。
  7. 磁盘分为 4 GB / 256 GB——大文件只能放在工作区卷上。 $HOME、/tmp 和 /mnt/scratch 各自都是约 4 GB 的 overlay/tmpfs(/tmp 和 /mnt/scratch 使用 RAM);$REPL_HOME 是 256 GB 卷,$XDG_DATA_HOME(/home/runner/workspace/.local/share)也位于其上,而 ~/.local/share 这种写法指向的位置不在其上。下载超过几 GB 的内容时,请保存到 $XDG_DATA_HOME/<slug>/,绝不要放到 /tmp、/mnt/scratch 或 ~/。见 references/large-files-disk.md。 搜索 box 或此仓库时,优先使用 rg 而不是 grep(支持递归搜索、遵循 gitignore,速度快得多);它位于 PATH 中,也暂存于 $HERMES_HOME/tools/ripgrep-*。仅在必须在没有 ripgrep 的情况下运行的脚本中使用 grep。

脚本(入口位于 scripts/,模块位于 scripts/setup/)

  • setup.sh — 精简入口:解析安装根目录并配置版本,然后 source scripts/setup/ 中的模块(common → ui_menu/bin/fix/ rc/doctor/安装器/cleanup → cli)。所有接线辅助程序都通过 $SCRIPT_DIR 解析路径,因此每个模块和 setup/ 都必须与 setup.sh 放在同一目录下。 安装根目录:由 setup/root.sh 在任何内容派生路径之前解析一次——SETUP_ROOT 环境变量(操作者明确指定,并记录供后续运行使用)→ 已记录的根目录 → 平台工作区($REPL_HOME)→ 退出。现在不再静默回退到其他布局;每次运行都会打印 setup root: <path> (source: …),根目录在持久化工作区之外时会发出警告(重新创建时会被清除),但若是明确指定的,仍会继续。安装会通过 run_install_step 将载荷路径记录在清单中 (<root>/.config/replit-setup/manifest.tsv);clean() 会在逐工具清理之后清除这些确切路径(带有 Hermes 备份跳过标签的项目除外;tailscale/join 不会被记录——其运行中守护进程的状态由 clean_tailscale 管理)。 布局规则:references/toolchain-layout.md。 这是幂等的工具链/环境配置(node/uv/python 固定版本、LD 路径、rc 链);每次重新创建后都要运行。--doctor 只读验证安装和接线(退出码 2 = 接线缺失);--fix 会重写所有接线,不下载或重新安装任何内容——在 $HOME 被清除或部分重新创建后可安全运行。--doctor --fix = 先报告,再修复。重复运行 --fix 会达到字节级完全一致的固定状态。
    • **安装隔离:**每个工具都在各自的步骤中安装 (run_install_step);某个工具失败时只会发出警告,运行会继续处理其余工具,之后列出失败项并以非零状态退出。--all 不再在首个安装器出错时中止。
    • 清理安全:--clean 会打印预检清单,准确列出将被删除的内容;拥有用户数据的工具会受到保护——hermes 使用其自带 CLI 备份(hermes backup,可通过 hermes import 还原),先将归档复制到 $XDG_CONFIG_HOME/hermes/hermes-backup.zip(位于待删除目录之外);若备份失败则保留原数据(CLI 不可用时回退为手动压缩)。ollama 模型和 camofox 服务端状态会保留。裸运行的非交互式 --clean 会被拒绝(请使用 --clean all -y)。
    • clean_* 必须撤销其对应 install_* 创建的所有内容,而不只是目录。 遗留内容才最容易腐坏:安装器追加到共享文件中的条目 (~/.ssh/authorized_keys、~/.ssh/config、known_hosts)以及套接字文件。也要删除这些内容,但只能删除自己写入的部分——匹配自己使用的端口/套接字/标签注释,确保外部区块不会被删除。唯一的特例:尾网节点密钥($TAILSCALE_STATE_DIR/tailscaled.state)会保留,这样重新加入时会复用同一个节点(从离线变为在线,控制台中不会出现新条目),而不是注册新的身份并让旧身份孤悬。删除它是可选操作,也是一个正式标志——setup.sh --clean tailscale --purge-identity(-pi);仍会在后台遵循环境变量 TAILSCALE_PURGE_IDENTITY=true。该选项是清理时读取的一次性开关,绝不是逐机器设置(不要将它写入 .replit 或任何环境文件;在某台机器上 grep 命中 purge-marker 只能证明已安装的脚本是最新版本),只有启用该选项后,才会留下过期的控制台条目供操作者手动删除。 尾网中的重复节点是状态目录被清除的结果,并非重新加入造成的。
    • **如果清理会杀掉它赖以运行的传输连接,就必须将清理任务从连接中分离,并通过文件验证结果。**杀掉 sshd 后进程仍会存活(子进程会被孤立);杀掉尾网/隧道客户端会导致整条连接中断,客户端会在清理过程中超时。使用 setsid ... >& clean.log & 运行,等待输出的最后一行,并将清理后的断言写入机器上的文件——启动清理的会话可能已经消失。 同样适用于任何长时间运行的远程任务(完整的 --clean all、批量安装→清理操作):前台运行 ssh 'bash setup.sh …' 时,本地客户端超时会将其杀掉,远程任务也会随之终止,因此要将任务分离(setsid/nohup,标准输入指向 /dev/null),并轮询日志文件。分离运行还会导致无法获得命令自身的标准输出:后台远程任务会一直保持 ssh 通道的标准输出打开,因此 ssh box '... app.sh > /tmp/log 2>&1 & sleep 18; echo RESULT' 什么也不会返回——连 echo 的结果都没有——即使每一步都正确执行。将结果(以及每个变体的指标)写入机器上的文件,并在第二次调用中读取;批处理结果被截断或为空,并不能证明命令执行失败。
    • 无冲突安装。机器上通常已经有该工具:由用户手动安装在某个任意目录、创建符号链接加入 PATH,而且 .replit 工作流通常还在运行该副本。受管理的安装必须优先使用,同时不能破坏原有内容——采用现有用户数据/状态,而不是重新初始化(这样就无需重新授权,也不会丢失配置);让所有运行该工具的脚本和工作流都优先使用受管理的二进制文件;替换外部符号链接前先报告;绝不能让旧副本继续在后台掌控运行,却让 setup.sh 报告成功。审计以下四处:$XDG_BIN_HOME/<bin>、 $XDG_DATA_HOME/<tool>/、.replit 工作流的 args,以及任何写死路径的操作者脚本。 脚本中已防范此陷阱:--fix 绝不会执行 hermes 启动器——任何子命令都可能触发完整的源码更新流程(venv 同步、npm 构建、GB 级运行时克隆)。在可用时使用 yq -i 编辑 config.yaml(否则回退到 awk),然后进行验证。 --camofox 会安装npm 全局 Camofox 浏览器 (@askjo/camofox-browser 和 camofox-browser-mcp 加入 PATH,运行引擎位于 $XDG_CACHE_HOME/camoufox),应用 Replit 库依赖闭包和 WebGL 自动跳过设置,并写入运行时环境代码片段以及启动器垫片 ($XDG_BIN_HOME/launch-camofox-browser),供无法加载该代码片段的 shell 使用——工作流、Run 按钮、MCP/cron 子进程。在这些 shell 中直接运行 camofox-browser 会因 libgtk-3.so.0: cannot open shared object file 而失败,并在导入 cookie 时显示含糊的 500 错误;请使用环境代码片段或启动器垫片。 参见 references/camofox.md §“npm-global lane”。 --camofox 需要受管理的 Node.js 26(通过 --node tarball 安装),若缺少会先安装——绝不能使用平台 nix node:依赖闭包会根据安装时运行的 node 生成,而平台 node 生成的闭包会在 store 库路径前置需要比系统版本更新的 glibc 的 sqlite,因此 Firefox 每次启动都会失败,尽管服务端看起来运行正常。闭包会记录其构建时使用的 node(closure.node);下次运行 --camofox 时会自动重新生成版本标记不匹配(或尚未添加版本标记)的闭包。 **冷启动 store(全新工作区,没有 replit.nix):--camofox 会通过命令处理,无需手动编辑。**它会写入/合并工作区根目录下的 replit.nix,添加五个 Camofox 依赖,并在 .replit 中添加 [nix] channel(持久化路径;没有 channel 时,平台会将 repl 转入恢复模式,且永远不会重新构建;无论是否写入配置,平台都不会立即重新构建,因此要等到下次重新构建/重启才会应用);并立即使用 nix-env -iA 为此容器安装依赖(即时路径;Xvfb 通过旧版 nixpkgs.xorg.xorgserver 属性获取——nixpkgs.xorg.xvfb 不是有效的 nix-env -iA 路径)。生成的启动器会根据 $SCRIPT_DIR 推导修复命令——绝不使用写死的克隆路径。
  • dot_replit.py — 以结构化方式编辑 .replit [userenv](使用 tomlkit,不使用正则表达式)。
    • 每次 --fix 后都要验证该固定版本:write_replit_bashrc 需要 tomlkit(可从 .venv、python3 或 uv 获取)。在全新机器上,这些环境都没有 tomlkit,因此该函数会打印 python tomlkit not found, write REPLIT_BASHRC manually,但仍返回 0。这属于失败:工作流 shell 随后会缺少该行,并静默出错。使用 grep REPLIT_BASHRC $REPL_HOME/.replit 检查。在全新机器上运行 setup.sh --fix 前,先将 tomlkit 安装到 .venv 中,或手动在 [userenv.shared] 下添加该行。
  • camofox.py — 用于抓取内容的 CLI(open/nav/eval/screenshot、cookie 导入、保持运行循环)。环境文件约定见 camofox.md。
  • scripts/setup/generate-closure.sh / libpool.sh — 传递库依赖闭包(按进程)和池修复(仅追加)。
  • $XDG_BIN_HOME/launch-camofox-browser — 安装时生成(不是仓库文件):加载闭包 env.sh,预检安装,然后启动 camofox-browser,将完整日志写入 $REPL_HOME/logs/camofox-browser.log,输出简短摘要,并等待服务端启动。无法直接嵌入代码片段的任何 shell 都应使用此入口——.replit 工作流、Run 按钮、cron/MCP 子进程。
  • tests/camofox_mcp_check.sh — 一次性 MCP 健康检查(等待 REST 服务就绪,执行真实的 stdio 握手,并验证 11 个 camofox_* 工具;退出码 0 = 正常运行)。由 .replit 中的“camofox mcp”工作流使用——适配器仅支持 stdio,单独运行时只会永远空闲。

维护此技能(验证,不要相信编辑工具)

  • 对此技能仓库的更改应在 .worktrees/<name> 工作树中的功能分支上进行,并提交 PR 以供审查。切勿直接提交到 main。

  • 此处的 PR 会通过压缩合并,因此分支上的提交永远不会成为 main 的祖先。由此有两点需要注意:合并后,开后续 PR 前先执行 git rebase origin/main,否则差异会再次列出已合并的工作(GitHub 会显示旧提交以及重复的更改);如果你还在编辑时 PR 已合并,后续提交就会滞留在已合并的分支上——从最新提交创建分支,变基到 origin/main,然后推送新分支。描述 PR 前,用 git diff --stat origin/main..HEAD 确认 PR 只包含预期文件。

  • 提交前先对实际目标进行测试,只有测试通过后才提交。先删除临时测试脚本和密钥文件,确保 PR 只包含修复。

  • 暂存机是 repl-test。 此技能的实际运行栈(权威 wiki)在此机器上运行;声称已就绪前,应验证 setup.sh 能在 repl-test 的虚假根目录中安装。当 repl-test 离线时,应询问操作员(唤醒它还是使用虚假根目录),不要擅自决定。操作员按 ROLE 后缀命名机器——<thing>-test(暂存/清扫目标)、<thing>-wiki(知识库机器)、<thing>-cfx(camofox 机器)——因此选择目标前要查看后缀,切勿假定机器就是你记得的那台:通过 tailscale status --json 解析机器(本机查看 Self,其他机器查看对等节点行),不要使用聊天中出现的名称。

  • 在新机器上批量安装后再清扫,会在列表执行到一半时填满 4 GB overlay (zsh 的 nix build、hermes 的约 2.3 GB $HOME/.hermes);在各工具之间检查 df -h,并在继续前删除安装失败留下的文件—— references/setup-tool-checklist.md。

  • 工具链写入可能会覆盖 IDE 终端的 rc 包装器(store overlay 可写):表现为 $REPLIT_BASHRC 正确,但别名失效;从原始的 *-replit-bashrc* 同名副本(较大的 store 文件)恢复—— references/shell-rc-and-zsh.md。

  • 虚假根目录中的 --join 仍会加入真实 tailnet。 tailscale 状态目录是 XDG 状态主目录($REPL_HOME/.local/state/tailscale),因此虚假根目录测试会重新注册此机器自身的节点身份,可能导致实际节点闪断;测试加入隔离时,应先将 statedir 指向临时路径。此外,--join -w(工作流模式)按设计会让任务在前台运行——它会输出摘要,然后只要守护进程仍在运行,就持续监视 tailscaled 套接字;测试长时间挂起说明功能正常,并非卡住。工作流控制台绝不流式传输运行时日志(那会卡住 Replit UI):完整日志保存在 $REPL_HOME/logs/ 下的文件中(包括 tailscaled 的日志),遵循 references/exposing-ports.md 中的规则。

  • 技能的 PR 合并后,功能分支就会消失。 此时 npx -y skills add owner/repo#branch 会失败并提示 Could not find remote branch;合并后应将安装目标改为默认分支(skills add owner/repo)。

  • 切勿仅凭截断的 grep 判断工作流或 MCP 条目缺失。 .replit 中每个工作流都在各自的 [[workflows.workflow]] 块内,因此 grep -A4 <name> 的窗口可能显示的是另一个块,看起来就像目标不存在——先列出所有标题(grep -n '^name = ' .replit),再引用你实际查看的块,之后才能告知操作员该条目不存在。MCP 一致性也要遵循同样的规范:每次更改后,都要比较默认配置和每个配置档的 mcp_servers 键列表(hermes mcp list / hermes -p <p> mcp list),因为单独执行 hermes mcp add 只会写入默认配置,而 hermes chat 和 WebUI 使用的是配置档对应的配置。

  • 工作流集合是操作员各台机器间的不变量,不是每台机器各自临时决定的。 每台机器都应包含 tailscale-join(bash .agents/skills/replit/scripts/setup.sh --tailscale -j -w), 并设置 waitForPort = 1055(tailscaled SOCKS5 端口),以确保各机器处于同一 tailnet;另外还应包含各自的 角色工作流(launch-camofox-browser 使用端口 80,llm-wiki-server 使用端口 8199)和 skills add replit(npx -y skills add skynight137/skills -s replit)。Project 按名称串联这些工作流, 因此工作流名称是标识符:保持 kebab-case 且不带空格;重命名时,还要在同一次编辑中移动每个 args = "<old name>" 引用。指向已重命名或不存在块的 Project 步骤会被静默跳过,导致启动阶段丢失。 waitForPort 若与客户端实际使用的端口不一致,就会把健康服务判为失效(健康检查会等待一个没有服务监听的端口)。 编辑流程必须依次进行:从机器上拉取文件,使用 tomllib 解析文件并列出工作流名称(grep 窗口会显示相邻的 [[workflows.workflow]] 块,让存在的工作流看起来像是不存在),在机器上备份(.replit.bak-$(date +%s)), 写回编辑后的文本,然后重新拉取并解析,以验证名称、waitForPort、[nix] 和 [[ports]]。 切勿盲目手动编辑 .replit TOML,也不要用 grep -A4 判断条目是否存在。

  • 脚本仍在执行时,绝不要覆盖该脚本文件。 Bash 按字节偏移增量读取脚本,因此运行中覆盖文件会导致进程继续执行不同的文本——症状是在磁盘上完全有效的行上出现语法错误(例如 log "...(-lw)" 行报 syntax error near unexpected token '(')。先修改副本,或等运行结束后再编辑。

  • 运行器脚本必须固定自己的根目录;/run/replit/env/latest 不可靠。 该文件会重新生成且可能滞后,因此只加载它的 shell 会将 REPL_HOME/XDG_* 解析为 $HOME——服务仍可运行,但状态(VNC 密码、日志、缓存)会落在 $HOME/.config/...,并在下次重建时丢失。因此,两个 llm-wiki 运行器都应在加载该文件后回退到 bundle 的父目录,并导出 XDG 根目录:REPL_HOME="${REPL_HOME:-$(cd "$APP/.." && pwd)}",再加上 export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$REPL_HOME/.config}"。明显迹象:状态位于 $HOME/.config/<service>/,而机器预期其位于 $REPL_HOME/.config/<service>/;在相信配置前,应从运行中的守护进程自身的 argv(x11vnc 带有 -rfbauth <path>)确认它实际使用的路径。

  • 显示器是否存活,应依据 Xvfb 进程,而不是 /tmp/.X11-unix/X<n>。 tmpfs 会清理套接字,而服务器仍在运行,因此仅检查套接字会误判为“显示器已失效”,继而启动第二个 Xvfb 并报 _XSERVTransSocketUNIXCreateListener ... server already running——随后监控程序每轮都会记录 Xvfb FAILED,UI 也始终无法启动。检查对应显示器的进程(pgrep -f "Xvfb.*[[:space:]]${DISP}([[:space:]]|$)");如果套接字缺失但进程仍在运行,应记录这一情况,而不是重启。选择对此服务具有权威性的产物——对于 tailnet,权威依据是套接字能否响应;对于 X 显示器,则是服务器进程。

  • 每次提交只包含你自己的文件。当工作区中有用户未提交的编辑(一个要点、一整个新参考文件)时,按路径暂存,绝不要执行 git add -A;如果你也必须编辑包含用户修改代码块的文件——通常是 SKILL.md——先将文件另存一份,移除其修改代码块,提交你的更改,然后恢复用户的修改,使其继续保持未提交状态。只有用户要求时才提交他们的工作,并将其作为单独提交,提交说明应描述他们的更改。

  • PR 合并后要收尾:在本地和远程删除其分支,并移除使用过的任何工作树。用户希望清理遗留分支和工作树,而不是把它们当作要保留的历史。

  • 添加或更改工具标志会涉及八个文件中约二十多个位置。按顺序处理 references/setup-tool-checklist.md,并运行其中的测试序列:只接好一半的标志不会报错,而是会不可见,或者工具安装后无法修复或清理。

  • 切勿编写静默回退(except ImportError: pass,或隐藏依赖或信任存储失败的默认值)。应明确指出缺少什么;这里曾因静默跳过而掩盖真正的 TLS 原因。

  • 弃用工具变量不等于“停止注册它”。 从 wire_tool/record_tool_env_vars 中删除名称,会留下旧的 export NAME= 行:emit_managed_block 会重新接管其标记内找到的每个导出项,并再次输出该项,因此继承的值会让此行永远存在。将名称添加到 _FIXED_RC_ENV_VARS(抑制列表),并附上说明原因的注释——这样下次写入时就会删除过时行,并阻止它再次出现。

  • 机器专属身份不应写入 rc。 描述“此”机器的变量(tailnet 节点名称、选择的端口)应由操作员在调用处设定——工作流 args 或 Replit secret——这样脚本只需从环境中读取,而不会将其写入每个 shell。只有每台机器都相同的路径才应导出。

  • 无论脚本在哪里回显密钥,都要对其进行遮蔽。 工具会打印自己的 argv(tailscale up 会逐字输出身份验证密钥),因此在输出到终端前,应通过过滤器处理:| sed -E 's/(tskey-)[A-Za-z0-9_-]+/\1***/g'。

  • rc 注册表中只应包含路径变量;人为选择应排除在外。 操作员会在不同机器上设置不同值的变量(节点名称、角色),应在描述该机器的位置固定——工作流 args 或 Replit secret——并在使用时从环境中读取。托管块会重新输出找到的内容,因此变量一旦进入该块,就会永远被重新接管;仅从 wire_tool/record_tool_env_vars 中弃用它还不够,还要将其添加到 rc.sh 的弃用列表中,否则继承的值会在每次写入时恢复该导出项。

  • 每个事物只保留一个变量:删除旧别名,保留 CLI 标志别名。 保留旧的标志写法作为分派别名是有价值的,但在环境中提供并行的 OLD_VAR:-NEW_VAR 回退没有意义——它会掩盖实际生效的变量,并使操作员必须设置的位置加倍。

  • 绝不要为了躲过 unset,把规范变量快照到私有的 _PREFIX_KEY 副本中。 操作员会看到 _CAMOFOX_KEY="${CAMOFOX_ACCESS_KEY:-}"……这相当于把已有变量重新导出为自创的副本,操作员自然会问为什么不直接使用规范名称。应修复导致变量被清除的问题:收窄保护条件(camofox 代码段现在只会在默认环回端口上清除访问密钥),或设置保护条件读取的输入(加载代码段前执行 export PORT),让规范名称得以保留。

  • 流式输出或记录日志的任何内容都要遮蔽密钥。 tailscale up 会在自身输出中回显完整身份验证密钥;在输出到终端或日志前,通过 sed -E 's/(tskey-)[A-Za-z0-9_-]+/\1***/g' 过滤。

  • 在带引号的 heredoc(<<'PY')中不要转义引号。 Bash 会原样传递文本,因此 \" 会以反斜杠加引号的形式传给 Python;如果同样的 \" 被复制到 shell 级别的 grep 'name = \"x\"' 中,则不会匹配任何内容,因为单引号模式会保留反斜杠。应直接写普通的 ", 并在任何块级重写后对文件运行 bash -n。

  • 删除机器以前用于运行的目录时,应先扫描配置,而不是直接执行 rm。 在配置范围内搜索指向该目录的绝对路径——.replit 工作流 args 和 waitForPort、$REPLIT_BASHRC/rc 文件、~/.ssh/config、操作员脚本——因为指向已删除脚本的工作流看起来仍已接好,直到下次启动时才会失败,而且是静默失败(任务会立即被回收)。删除前先检查路径是否已经失效:目录可能已经不存在,但仍有两个工作流引用它,这正是过时配置隐藏的方式。将必须保留的身份/配置(固定的节点名称、端口)移到仍在使用的工作流 args 中,绝不要写入 rc。

  • 重命名标志或令牌时,也要执行同样的扫描,但方向相反。 同一次提交中应更新所有写有旧令牌的位置:用法/帮助信息、参数解析器、清理分派、菜单标签及其映射、工作流 args(原地迁移,只对令牌执行 sed -E)、文档和技能自身的工具行。每个位置还需遵守两点:只有在没有其他条目已占用新名称时才重命名工作流 name(Replit 会丢弃两个同名条目中的一个);如果移动状态目录会生成新的主机密钥,则保留目录原名,因为所有客户端的 known_hosts 都会发出警告。保留旧标志写法作为可接受的别名,让现有 .replit 条目和操作员习惯继续有效,然后用 grep -rn '<old>' scripts/ docs/ skills/ 验证:任何残留都必须是有意保留的别名,或是你选择保留的路径。

  • 路径只推导一次;使用方从环境中读取。 根据其他路径计算的变量(基于 XA_DATA_DIR 的 XA_STATE_DIR、状态目录内的套接字)只在 common.sh 中赋值;所有使用方都应从环境中读取 $XA_SOCKET,而不是重新写出 $XA_DATA_DIR/state/...——只要有一处重新拼写,下次更改目录布局时就可能导致偏差。应在安装程序和修复路径(--fix)中都注册此类变量:只在安装程序中接入的变量,会在用户执行修复而非安装时消失;这也是为什么当 rc 块中缺少导出项时,--doctor 应发出警告。

  • 工作树分支无法看到主检出中未提交的编辑。 工作树共享 .git,但文件各自独立,因此在主检出中所做的文档或配置编辑不属于任何分支:工作树的 PR 会静默漏掉这些更改,而之后将更改 git pull 到主检出时,会因文件处于脏状态而中止。合并前对主检出运行 git status;如果有更改被遗留,就在刚拉取的 origin/main 上重新应用,并作为后续提交——不要弹出 stash,因为文件重命名可能已移动了该更改所针对的文件。

  • shell 脚本中的 heredoc 辅助代码不得包含反斜杠转义。 在 <<'PY' 中,反斜杠是字面字符,因此 \" 会以转义引号的形式传给 Python(尚可处理),但传给 shell 级别的 grep -qF 'name = \"x\"' 时会变成字面反斜杠,因而永远无法匹配。heredoc 中应直接使用引号;打完补丁后,用 sed -n '<a>,<b>p' 重新查看该代码块——bash -n 会顺利通过一个实际上什么都匹配不到的模式。 当一次批量操作涉及多个文件或重复使用 old_string 时,继续操作前先验证磁盘上的效果:

  • 用 rg 搜索新文本(“failed”/“no match” 行可能只是无害的重复键重试),并确认它出现的次数符合预期——出现两次意味着编辑应用了两次。

  • 一旦 sed -E 脚本中包含引号或 \1,就绝不要手动编写:先在临时副本上运行完全相同的表达式。 连续两次失败(用 | 作分隔符时被拒绝;文本中有空格,却写成了 (--join)?,导致模式悄然未匹配,而 rc=0 却声称成功)看起来与逻辑错误完全相同,脚本还会“成功”运行,却不做任何编辑。

  • sed -ed -i f1 f2 会清空这两个文件。 -ed 会把 d 附加到脚本中,因此每一行都会被删除(wc -l = 0),而 rc=0 看起来仍像是成功。请写成 sed -e 's#a#b#' -e 's#c#d#' -i f1 f2,并在执行多 -e 编辑后立即检查每个文件的 wc -l。恢复方法:git checkout -- <file>——该文件中尚未提交的补丁也会一并丢失,因此请准备好重新应用。

  • 使用绝对路径:之前终端调用中的 cd 会持续生效,因此相对路径可能会指向错误的目录。

  • 每次编辑后都要信任基准检查:对每个模块运行 bash -n(setup.sh 语法检查门禁会执行相同的检查),对变更的文件运行 shellcheck -S warning,并重新运行模块测试框架。

Camofox 公共访问

  • 设置 CAMOFOX_ACCESS_KEY 后,除 /health 外的每个路由都需要 Authorization: Bearer <access key>。浏览器地址栏无法发送该标头,因此在浏览器中看到 {"error":"Unauthorized"} 表示访问门禁正常工作,并非故障。
  • 全局门禁只检查访问密钥;在 /tabs 上使用 CAMOFOX_API_KEY 会得到 401。Cookie 导入则相反:POST /sessions/<u>/cookies 只检查 API 密钥,因此在那里使用访问密钥会得到 403。camofox.py 除导入 Cookie 外,所有请求都发送访问密钥;导入 Cookie 时则使用 API 密钥。
  • 移除访问密钥会让任何知道 URL 的人都能访问所有路由。切勿为了清除公共服务上的 401 而这样做。
  • 运行时环境变量片段只会在默认的本地回环端口保留访问密钥。 当 CAMOFOX_BIND_HOST 为本地回环地址且端口未设置或为 :9377 时,它会取消设置 CAMOFOX_ACCESS_KEY;用于提供主机 :80 服务的 .replit 工作流既不设置绑定主机,也不设置端口,因此顺序很重要——应在载入该片段前导出 PORT,否则公共端口启动时不会设置密钥。每次部署到公共环境时,都要执行一次不带密钥的 curl(预期返回 401),并通过检查运行中进程中的密钥是否存在来确认(tr '\0' '\n' < /proc/<pid>/environ | grep CAMOFOX_),而不是假设工作流环境变量已传递给进程。
  • MCP 适配器应作为本地进程运行,并指向主机的公共 HTTPS URL——而不是通过 ssh <box> bash <wrapper> 跳转。 服务已经公开时,操作人员会认为通过 ssh 启动 MCP 服务的做法不合适(kok mcp kita tetep pake ssh? udah pindah ke https langsung):ssh 用于管理主机,HTTPS 用于使用服务。默认做法:将 @askjo/camofox-browser-mcp 安装到工作区 npm 前缀(npm i -g @askjo/camofox-browser-mcp),然后注册一个本地包装器:hermes mcp add <name> --command <wrapper> --env CAMOFOX_BASE_URL=https://<box-domain> CAMOFOX_USER_ID=<id> ...——不要在 --env 中加入密钥:Hermes 不会展开 mcp_servers.*.env 中的 ${VAR},因此 ${CAMOFOX_ACCESS_KEY} 会以字面字符串形式传给适配器,导致每次调用都返回 401,即使使用相同令牌直接执行 curl 却能成功。包装器会自行从配置文件 .env 中读取 CAMOFOX_ACCESS_KEY / CAMOFOX_API_KEY(密钥只保存在一处,不要再复制到 config.yaml)。使用 hermes -p <profile> mcp add ... 注册:hermes chat 和 WebUI 会解析 PROFILE 配置(profiles/<p>/config.yaml),而直接运行 hermes mcp add 会写入 DEFAULT 配置,因此 CLI 会继续使用旧条目,并在“成功”添加后仍返回 401。包装器只负责修正适配器继承的运行时环境:设置 LD_LIBRARY_PATH=$REPL_HOME/.local/lib(工作区 node 所需的 libatomic),将 node bin 目录加入 PATH,并通过绝对路径执行 node .../server.mjs——在 Hermes 启动的服务中,#!/usr/bin/env node 找不到 node,连接会以 Connection closed 失败。只有在服务未通过 HTTPS 公开时,才保留 ssh 包装器作为备用方案;这种情况下,必须具备以下三项,否则每次调用都会失败,而 hermes mcp test 仍会显示正常(该命令只列出静态工具定义——务必实际调用一次工具):source /run/replit/env/latest(加载环境中的密钥;缺少它会返回 401 Unauthorized)、导出 PORT 以确保片段保留密钥,以及将 CAMOFOX_BASE_URL 指向服务器实际绑定的端口(适配器默认使用 :9377 → camofox error: fetch failed)。camofox_import_cookies 会在运行适配器的机器上解析 Netscape 文件并上传,因此该文件必须存在于那台机器上;以点号开头的域名会触发 Firefox NS_ERROR_ILLEGAL_VALUE,而已预热的用户上下文可能会拒绝新 CAMOFOX_USER_ID 能接受的合成 Cookie——请使用从真实浏览器导出的 Cookie 文件,也就是 camofox.py --cookies 使用的那类文件。还有两项操作细节:hermes mcp add 最后会显示交互式提示 Enable all N tools? [Y/n/select];如果 stdin 不是 TTY,它会静默取消——通过管道传入 printf 'Y\n' | 以非交互方式启用工具,然后用 hermes mcp list 确认;此外,该包装器是 Hermes 主目录中的辅助脚本(参数化 REPL_HOME / HERMES_PROFILE,从配置文件 .env 读取密钥)——根据操作人员于 2026-11-05 作出的决定,它应放在 Hermes 中,而不是此技能中;只需在 Hermes 主目录中编写一次,避免重复输入 LD_LIBRARY_PATH/node/env 前置配置。
  • 公共测试应通过支持发送标头的客户端(curl 或 camofox.py)访问公共 URL。本地访问成功只能证明服务器正常,不能证明代理路径正常。
  • 所有客户端都应保持 TLS 验证开启。按以下顺序诊断公共访问失败:首先执行经过验证的 curl -sS https://<domain>:<port>/health,然后执行 curl -sk(跳过验证)。如果经过验证的请求返回 curl 错误 60,而 -k 请求返回 200,说明服务器正常,只是该客户端的信任存储缺少 Replit 代理根 CA(见下方证书包规则);域名返回 502 则表示该端口上没有服务在监听。在指出任何原因之前,先执行经过验证的请求。关闭验证的客户端只能证明网络可达;绝不能将其报告为认证或工具路径测试通过。
  • curl 和 Python 在同一台主机上可能信任不同的存储。curl 读取 /etc/ssl/certs/ca-certificates.crt,其中包含 Replit 代理根证书;Python 的默认上下文中 cafile=None,且只有 capath=/etc/ssl/certs,因此相同 URL 会导致 CERTIFICATE_VERIFY_FAILED。应在客户端中修复:使用 ssl.create_default_context(cafile='/etc/ssl/certs/ca-certificates.crt'),并将 context= 传给每个 urlopen。先用经过验证的 curl(不带 -k)确认该证书包中包含根证书。certifi 的证书包缺少代理根证书,因此 certifi.where() 无法解决问题。不要禁用验证;PYTHONHTTPSVERIFY 不是 Python 设置,不会起作用。各客户端(Node、Python urllib / httpx / requests、git、uv)的变量表和生成器修复方案见 references/tls-trust.md。
  • Node 也有同样的差异,而且仅设置 NODE_OPTIONS=--use-system-ca 还不够。NODE_EXTRA_CA_CERTS 会覆盖系统存储,而它通常指向的 Nix 证书包缺少代理根证书。为 MCP 适配器和任何 fetch 客户端设置 NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt,并保持验证开启。在相同环境中运行 fetch(url) 确认后,再启动适配器。
  • camofox.py 会对所有探测失败都打印“not ready”,包括证书错误。要查看实际异常,请在设置 CAMOFOX_BASE_URL 后运行 camofox.req(None, 'GET', '/health');_error 字段会指出原因。
  • Camofox MCP 适配器会将所有网络故障报告为 camofox error: fetch failed,这会隐藏 TLS 原因。如果 tools/list 正常,但工具调用失败并显示此文本,说明适配器的 fetch 无法验证代理,而不是服务器有问题。修复客户端信任存储(见上文)后,重新调用工具。
  • 使用 replit secrets exec --keys NAME -- sh -c 'printf %s "$NAME"' 将 Replit 密钥读入 shell 变量。必须使用 -- 形式;不加它时输出为空,看起来就像密钥缺失。--keys 接受以逗号分隔的列表(--keys A,B,或重复指定该标记)——如果把名称作为独立参数传入,第二个名称会被当作要执行的命令,从而出现 exec: "B": executable file not found in $PATH。
  • .hermes/.env 中保存的密钥副本可能与 Replit 密钥不同。如果 /tabs 返回 401,且你认为密钥正确,请先比较两个值的 SHA-256 前缀(绝不要打印密钥),再归咎于服务器。
  • 使用公开的端口号提供服务——让 localPort 和 externalPort 保持一致。 操作人员认为 6080 local → 3000 public 这样的配置是徒增困扰(mending lu ganti port ... biar ga bikin bingung):同一个服务,却要记住两个数字,而且不同环境中的客户端 URL 也不相同。将应用绑定到希望公开的端口({localPort = 3000, externalPort = 3000}),这样 http://127.0.0.1:<port>/vnc.html 和 https://$REPLIT_DEV_DOMAIN:<port>/vnc.html 的格式相同;在运行器中为端口设置默认值(LLM_WIKI_UI_PORT:-3000),并同时修改两侧。编辑 [[ports]] 后无需重启 Repl(已验证可实时生效),因此扁平化现有映射无需停机——修改条目,在新端口上重启监听器,然后再次验证本地和公共访问。 公共端口必须在 .replit [[ports]] 中列出,且 localPort 和 externalPort 必须匹配;服务器必须在该端口上绑定 0.0.0.0。URL 始终包含端口(https://$REPLIT_DEV_DOMAIN:<port>/)——默认端口只能有一个,因此不带端口的域名会访问 80 端口;如果那里没有服务监听,就会返回 502。该列表由平台管理——手动追加的 [[ports]] 条目会在重新序列化时被移除,随后服务就会返回 502,尽管文件看起来没问题。因此,注册端口时,应启动监听器并访问一次 https://$REPLIT_DEV_DOMAIN:<port>/,并以代理返回的响应为准,而不是看文件。路由器仅支持 HTTP(S),因此无法通过这种方式公开非 HTTP 协议(sshd、数据库端口)。请在没有其他服务占用的端口上运行服务:两个 .replit 工作流共用同一个 waitForPort 时不能同时绑定,而 Hermes 仪表板默认使用 8080,因此第二个在该端口运行的服务会悄无声息地绑定失败。调试服务前先检查端口归属:如果 /health 返回了其他服务的响应内容(仪表板会返回 302 + {"error":"unauthenticated",...,"login_url":"/login"}),说明你的监听器从未获得该端口——请选择一个已声明的空闲端口,并将所有客户端都改为连接该端口。详情:references/exposing-ports.md。
  • 启动服务器时必须加载其环境变量,绝不能在未加载 $XDG_CONFIG_HOME/camofox/env.sh 的 shell 中直接运行 node server.js。如果未加载,浏览器就会缺少 GTK/X11 库路径,并因 browserType.launch ... libX11-xcb.so.1 而失败;之后每次标签页调用都会返回 503 browser_launch_timeout。在同一条命令中加载密钥,例如:replit secrets exec --keys CAMOFOX_ACCESS_KEY,CAMOFOX_API_KEY -- sh -c '. $XDG_CONFIG_HOME/camofox/env.sh; node server.js',同时设置 CAMOFOX_BIND_HOST=0.0.0.0,并确认端口已列在 .replit [[ports]] 中。
  • 通过 Replit 界面中的工作流,停止由 .replit 工作流管理的 Camofox 服务器。对 node server.js 执行 kill 会被工作流撤销,工作流会重新启动服务器和 camofox-browser-mcp。只能按明确的 PID 执行 kill;pkill -f 模式可能会匹配到调用它的 shell。
  • 在测试中,对空的 /tabs 请求正文返回 400,表示门禁检查已通过。503 browser_launch_timeout 表示服务器端浏览器启动失败,与身份验证无关。
  • 首次加载受 Cloudflare 保护的网站时,可能会返回拦截页面(httpStatus 403,标题为 Just a moment...,页脚中有 Ray ID)。Camoufox 会自行通过验证:几秒后再次发起相同的导航请求,就会返回 200 和真实页面。不要尝试通过 AX 快照点击 Turnstile——它位于跨源 iframe 中,不会出现在快照里。
  • camofox_import_cookies 会在适配器运行所在的机器上,将 cookiesPath 解析为相对于 $(XDG_CONFIG_HOME)/camofox/cookies 的路径(不是 CAMOFOX_COOKIES_DIR,也不是 ~/.camofox),在该机器上解析文件并上传。必须先将浏览器 JSON 导出文件转换为 Netscape 格式:去掉域名前导点,将 httpOnly 标记放在 #HttpOnly_ 前缀之外,并将会话过期时间映射为遥远的未来时间戳;实际导出的 Cookie 随后即可顺利导入(包含 12 个 Cookie 的 Replit 集合首次尝试就打开了已验证身份的页面),而合成的前导点 Cookie 会触发 Firefox NS_ERROR_ILLEGAL_VALUE。
  • 将门禁状态与目标页面状态区分开来。门禁拒绝时会返回服务器自己的 JSON 错误:{"error":"Unauthorized"}(401)或 {"error":"Forbidden"}(403)。通过门禁检查的 /tabs 调用会返回 200,目标页面的状态则位于 httpStatus 字段中(例如网站返回的 404 或 403)。在将问题归咎于身份验证之前,先查看 httpStatus。
  • 如果 Cookie 导入返回 403,且客户端代码正确,说明运行中的服务器所用密钥与密钥配置中的值不同。服务器只会在启动时读取 CAMOFOX_API_KEY 和 CAMOFOX_ACCESS_KEY,因此更改 Replit 密钥配置后,除非重启服务器,否则不会生效。比较密钥配置与服务器环境变量中密钥的长度(切勿比较具体值),然后使用相同的值重启服务器并重试。
  • .replit 工作流会从 camofox.py 所在的路径运行它。修复合并后,应拉取该路径指向的克隆仓库;较旧提交上的本地克隆仍会运行旧代码,因而会再次出现已修复的 bug。调试代码之前,先在该克隆中运行 git log -1 -- <file>。
  • env -i 会清空 PATH,因此后续的 timeout python3 会因 No such file or directory 而失败。在干净环境中运行时,传入解释器的绝对路径(PY=$(command -v python3))。
  • **绝不要在 Agent shell 中将 PATH 设为固定值。**终端状态会在多次调用之间保留,因此 export PATH=/one/dir:/usr/bin:/bin(为了运行单个工具)会悄悄移除后续每次调用所需的 Nix 和 $XDG_BIN_HOME 条目:几步之后,python3、replit、uv 和平台工具链都会报 command not found,而且报错看起来与修改完全无关。应追加路径(PATH="$PATH:<dir>"),或将导出语句写入 $HERMES_HOME/cache/scratch/ 下的文件,并在后续调用的开头执行 . 文件名。如需恢复,可在全新的登录 shell 中读取平台 PATH(bash -lc 'printf %s "$PATH"'),或重新加载 rc 文件。
  • 找不到 replit 了?它位于 Nix store 中,路径并不固定:/nix/store/*replit-cli-*/bin/replit(平台工具链还需要 -runtime-path/bin)。重新查找其路径,不要据此断定 CLI 已不存在。
  • env -i bash -lc 并不能复现 Replit shell:它会移除 REPLIT_BASHRC/REPLIT_MODE,导致 rc 链不会被加载,shell 看起来像是没有别名或导出变量——这是假故障。要测试 rc 输出,应在普通登录 shell 中,将要测试的变量从继承的 PATH 中移除,而不是使用 env -i:PATH=$(echo "$PATH" | tr ':' '\n' | grep -v 'installs/.*/venv' | paste -sd:) bash -lc '...'。

Setup 脚本环境规则(setup.sh、setup/rc.sh、setup/common.sh)

  • 凭据从环境中读取,绝不写入脚本。 用户会将聊天中引用的令牌或硬编码在安装程序中的令牌视为已泄露(并立即轮换或撤销)。采用 TAILSCALE_AUTH_KEY=… setup.sh --tailscale --join 这种输入方式,并确保首次运行后的一切操作都基于已存储的状态,无需再次提供密钥。打印带占位符(tskey-auth-...)的用法说明是正确的;打印令牌值则不正确。
  • 安装和加入是两个标志,而不是一个。 --tailscale 用于安装二进制文件,然后退出;只有 --join 会写入 sshd 配置/密钥、绑定 $RS_PORT 并运行 tailscale up。-j 等同于 --join;--workflow/-w 表示以工作流模式运行。没有仅准备模式,也没有用于串联操作的第二个标志:如果需要让机器可供检查但不启动监听器,请停在 --tailscale。
重命名设置标志(严格执行——此技能仅供自身使用)

此技能只有一个操作人员,因此重命名时要彻底:不保留别名,也不添加用于改写旧 .replit 条目的迁移代码。旧参数必须明确报错,不能悄然继续运行。

  1. 在同一轮中枚举每个涉及位置——重命名不只是一个 case 分支:参数解析、 用法文本、ui_menu.sh 键、cli.sh 步骤名称、cleanup.sh 目标 + clean_<fn>、 install_<fn> 定义、工作流 shim 匹配字符串、文档、探测脚本。
  2. 短别名和单词别名应归入它们所别名的标志,而不是新增分支:--join|-j、 -w|--workflow 放在同一个 case 分支中,这样别名就不会偏离。帮助 行应列出所有拼写——隐藏别名会带来支持请求。
  3. 清理目标应以工具命名,而不是以动作命名。 --clean tailscale 会拆除整个 join (二进制文件 + sshd + 节点状态 + ssh 配置),因为它由 tailscale 支持;--clean join 看起来只像安装标志,容易误导。一个目标,一个负责人,不留任何东西让第二次调用去处理。
  4. 使用功能名称,而不是旧前缀。 状态目录 $RS_DIR 是 $REPL_HOME/.ssh/join-tailnet, hop 密钥标签是 repl-hop;合并时期的 e2e-sshd/e2e-hop 名称以及后来的 join-sshd 目录名都已移除(该目录保存的是整个 tailnet-join 状态,不只是 sshd)。重命名状态目录会生成新的 sshd 主机密钥——旧的 known_hosts 条目会失效,所以清理 也会清除 [host]:$RS_PORT,并且 clean_tailscale 会清除旧目录名称。
  5. 让未知选项分支调用 die,并在提示中使用新的拼写(die "Unknown option '$1'. … sudah digabung jadi --join")。否则,未知标志会让所有 INSTALL_* 保持 false, 底部的保护逻辑就会落入 INSTALL_ALL——一个过时的工作流条目随后会在每次启动时重新安装整个工具链。
  6. 对任何重命名的脚本/引用使用 git mv,以保留历史记录;更新相关指针(文件头 注释、SKILL.md 行、同级文档)。
  7. 对每个改动过的脚本运行 bash -n,并运行 setup.sh --help——用法 heredoc 是 未加引号的 <<USAGE,所以新帮助文本中的裸反引号会在 --help 执行时作为命令运行(line 4: --tailscale: command not found)。应将其转义为 ```。
  8. 在另一台机器上验证,而不是在这里:全新运行 --tailscale --join,然后在不提供 auth key 的情况下再次运行;它必须打印 sshd sudah hidup/tailnet: <ip> 并保持已加入状态(幂等),旧拼写则必须报错并显示提示。卸载步骤要以分离进程运行 (setsid … >& clean.log & + 轮询日志直至最后一行),因为它会终止 tailscaled,进而断开 tailnet 连接和会话。scripts/join-lifecycle-test.sh 会以这种方式运行整个周期,并将清理后的断言(config/known_hosts/keys/state)输出到文件。
  9. 在清理步骤之前推送更新后的代码树。 清理会让机器断开 tailnet,因此你原本会用来 scp 新脚本的通道也会随之中断;如果测试时清理的是旧副本,机器就会继续使用旧标志运行。先推送代码树,再清理。
  10. 预料到会失去连接,并提前安排恢复路径。 完全卸载后,无法再从这里访问机器上的任何内容(sshd 和 tailscaled 都已停止),所以开始前先准备好恢复命令——cd ~/workspace && TAILSCALE_NODE_NAME=<node> bash skills/replit/scripts/setup.sh --tailscale --join,由操作者在平台 shell 中输入。只要状态目录还在,就不需要 auth key;如果清理操作已经删除状态,则需要 TAILSCALE_AUTH_KEY。
  11. 重命名状态目录会使旧目录成为孤立目录。 不会迁移任何内容,所以 $REPL_HOME/.ssh/<old-name>/ 会保留下来,里面有一个已失效的主机密钥;确认新路径可用后,手动删除该目录。旧主机密钥对应的 known_hosts 条目在相同端口上也会失效,这就是为什么清理要删除 [host]:$RS_PORT,而不是依赖重复使用主机密钥。scp 可能不存在——使用 tar czf - dir | ssh host 'cat > ~/x.tgz' 推送代码树,然后在远端解包。
  • 只导出脚本负责的内容。Replit 已经提供 XDG_CONFIG_HOME、XDG_DATA_HOME 和 XDG_CACHE_HOME;重新导出它们没有任何作用。XDG_BIN_HOME 是工具链负责的唯一 PATH 条目,始终为 $REPL_HOME/.local/bin。
  • HERMES_HOME、HERMES_MANAGED_DIR 和启动器目录是普通的、通过 wire_tool hermes + record_tool_env_vars / record_tool_path_dirs 在单个受管工具链区块中注册的工具配置,并且在 install_hermes 和 fix_derive_wiring 中镜像维护。绝不要在受管标记之外为工具单独添加 rc 区块:每次重写后都必须重新追加第二个区块,才能保持 PATH 优先级稳定;strip_tool_wiring 无法发现它;而未安装该工具的运行会悄悄删除它的行(合并前的 hermes-tools 区块就有这三个问题;lift_legacy_hermes_wiring / strip_legacy_hermes_block 会在一轮操作中迁移这种 rc)。受管范围目录默认为 /etc/hermes,但该目录不存在,而且在 Replit 上无法创建(/etc 是只读挂载,没有 root 权限),因此将其固定在 $HERMES_HOME/managed 下。不要将 HERMES_HOME 放入 _FIXED_RC_ENV_VARS:该列表会抑制工具变量导出,而现在只有它会导出该变量。
  • Hermes 唯一的 PATH 条目是 $HERMES_HOME/hermes-agent/.hermes/bin(稳定启动器;它会自行解析正确的环境)。绝不要添加 installs/*/environments/*/venv/bin 或 hermes-agent/venv/bin:这些 venv 属于隔离会话运行,不会在会话或更新之间保持稳定;如果公共 shell 找到并使用其中一个,就会调用来自可能已不存在的会话的二进制文件。
  • 在此部署的服务不得回退使用由 Hermes 管理的二进制文件。 $HERMES_HOME/tools/*(node、python、ripgrep 等)是 Hermes 的私有安装:它们会在 hermes update 时更换,并会在 --clean hermes 时消失;Hermes 会从其启动的子进程(MCP 子进程、终端子进程)中剥离 LD_LIBRARY_PATH;而在这台机器上,这些 tarball 二进制文件链接到仅存在于 /nix/store 的 libatomic.so.1。如果包装器以 command -v node 结束,就会选中损坏的那个二进制文件,错误则会在很远的地方表现为 Connection closed 或单独的退出码 127。明确选择候选路径($VAR、$XDG_BIN_HOME/node、/usr/local/bin/node、/usr/bin/node),拒绝任何包含 /.hermes/ 的路径,并且明确报告原因并失败,不要继续回退——静默回退正是这个问题花了三个会话才找到的原因。
  • 也绝不要将 $HERMES_HOME/tools/* 放进用户 PATH。这些目录(node、npm、python、uv、gh、ffmpeg、ripgrep)是 Agent 管理的安装:hermes update 会替换它们,卸载或 --clean hermes 会删除它们,所以用户 shell 中的全局安装会跟着二进制文件进入 Hermes 前缀,并随 Hermes 一起消失(npm i -g <pkg> → $HERMES_HOME/tools/npm-*/...),pip 会跟随 Hermes 的解释器,而 PATH 顺序会悄悄决定用户脚本使用的是哪个 node/python。镜像这些目录的 rc 区块看起来方便,实际上会造成数据丢失。用户 shell 应使用平台工具链(.replit nodejs-24 / python3.13 模块 + $XDG_BIN_HOME);Hermes 会从自己的 PATH 运行自己的二进制文件,且不会读取 rc。
  • 不要删除用户 bin 中的启动器转发程序($HOME/.local/bin 中的 hermes、hermes-acp、hermes-agent)。Hermes 会自行发布它们(_launchers.py::expose_cli → _publish_conveniences),并在每次安装/更新时重新创建,所以删除它们没有用;而且 hermes-agent 仅以该转发程序的形式存在(.hermes/bin 只暂存 hermes + hermes-acp),因此删除它就会彻底移除该命令。转发程序会执行持久的 $HERMES_HOME/hermes-agent/.hermes/bin/* 启动器,而不是临时路径——将文件视为过时杂物前,先检查它是否由框架拥有并会重新发布。
  • 每个工具的配置必须存在于三个位置,否则就只配置了一半:安装器(record_tool_env_vars / record_tool_path_dirs)、fix_derive_wiring 和 doctor 检查。fix_derive_wiring 会让 --fix 能够在存在该工具但本次运行未安装它的机器上重建配置行;没有对应分支,--fix 就无法悄悄地恢复它,因此该配置只能在 rc 被重写之前保留。doctor 检查必须基于区块的内容(grep -qE '^export[[:space:]]+ZDOTDIR=' "$BASHRC"),绝不能基于标记或文件是否存在——即使导出语句已消失,标记检查仍会通过。
  • 不要定义 XDG_STATE_HOME。没有脚本会读取它,所以它只是无用配置。
  • TLS 变量应指向平台存储,绝不要指向 $SYSTEM_CERTIFICATE_PATH。如果 /etc/ssl/certs/ca-certificates.crt 可读,SSL_CERT_FILE、SSL_CERT_DIR、NODE_EXTRA_CA_CERTS、REQUESTS_CA_BUNDLE、GIT_SSL_CAINFO 和 AWS_CA_BUNDLE 都应来自该路径。Nix cacert bundle 不包含任何 Replit 根证书(Replit 名称的主题数为 0,而系统 bundle 中有 2 个:“Replit internal proxy Root CA …”、“Replit Localhost Development CA”),所以接入它会用一个不认识代理的存储替换掉原本认识代理的存储——公共主机仍会返回 200,但经过代理的主机会失败;而且它是 /nix/store 路径,可能被 GC 删除,导致 SSL_CERT_FILE 指向不存在的文件,从而彻底破坏所有 TLS 客户端。刻意不设置 NIX_SSL_CERT_FILE:我们运行的程序没有任何程序会读取它,而且 nix 不需要它就能解析自己的缓存。
  • 绝不要设置 npm_config_dangerously_allow_all_scripts。它会绕过每次安装时对 npm 安装脚本的审查(上游明确称其“强烈不建议使用”),包括明确禁止的包。npm ≥ 12 会阻止未经批准的依赖安装脚本,而 npm 10/11 只会发出警告,因此在运行 npm 11 的机器上,该标志毫无用处;等 npm 12 出现时,它还会悄悄禁用防护。应按项目允许脚本:在 package.json 中使用 allowScripts,或运行 npm approve-scripts。
  • 生成的 hermes 别名应调用裸 hermes,而不是 $XDG_BIN_HOME/hermes。受管区块注册的是真正的启动器目录($HERMES_LAUNCHER_DIR = $HERMES_HOME/hermes-agent/.hermes/bin),这是这台机器上唯一提供 hermes / hermes-acp 的位置,因此裸 hermes 会解析到那里——或通过 Hermes 自己的 $HOME/.local/bin 转发程序解析,该转发程序会执行同一个二进制文件。$XDG_BIN_HOME/hermes 别名指向这两者之外的位置。
  • 提交前验证生成的 rc 输出:直接调用生成器(使用 REPLIT_MODE=true 调用 emit_managed_block),对输出运行 bash -n,搜索已删除的名称,然后在干净的 shell 中 source 输出并运行每个别名。文本检查无法发现无法运行的别名。
  • 验证幂等性且不会重复,而不只是验证有效性。运行写入器两次,并要求 md5sum 完全一致(到达固定点意味着清除/追加边界干净,文件不会在每次运行时都发生变化);然后在干净的 shell 中将 rc source 两次并统计每个条目:printf '%s\n' "$PATH" | tr ':' '\n' | sort | uniq -c。未设保护的前置插入在第二次 source 时会重复,而使用 case ":$PATH:" in *":dir:"*) 保护的插入会保持为 1——只 source 一次无法发现这种差异,而且在这台机器上,rc 会在每个 shell 中被 source 多次(平台 bootstrap、REPLIT_BASHRC shim、嵌套 Agent shell)。
  • 别名主体使用变量时,要用双引号括住别名,这样变量会在定义时展开:alias oc="$XDG_BIN_HOME/opencode"。单引号会让 $VAR 保持字面值,调用别名时就会失败。在带引号的 heredoc(<<'EOF')中,应直接写 $VAR;反斜杠会原样写入文件。
  • emit_managed_block 中的区块文本位于未加引号的 heredoc(<<EOF)中,因此 $VAR 会在生成时展开,反引号或 $(...) 会在生成时执行。注释中的一对反引号(`npm i -g`)会在生成时运行 hermes/hermes-acp,随后等待 stdin,导致整个 setup.sh 运行挂起。生成的说明文字不要使用反引号;必须原样写入文件的内容要进行转义(\$VAR)。实际运行前,使用 rc 的副本测试生成器并关闭 stdin(</dev/null):如果未设置 BASHRC,清除步骤中的 awk "$BASHRC" 会读取标准输入并永久阻塞,而不是报错。生成后,搜索文件中是否有多余的反引号,并检查日志中是否有 command not found——这些都是注释被执行的迹象(注释中多余的 `npm approve-scripts` 就足以让 setup.sh 运行 npm)。
  • 测试别名时,应从被 source 的文件中调用,而不是在 source 该文件的同一行调用:bash 只有在该行执行后才会解析文件中的别名。先运行 shopt -s expand_aliases,再运行 source rc.sh,然后将别名单独放在一行运行。
  • 生成区块中的 registry 设置是普通导出,而不是 ${VAR:-default},因此公共 registry 会覆盖之前设置的防火墙值。PIP_TRUSTED_HOST 也使用普通导出。
  • 生成的 rc 面向未来的 shell——如果本次运行自身会安装 npm 包,也必须在进程内覆盖 registry 环境变量(在 source 时由 common.sh 设置)。 在 Replit 上,继承的 npm_config_registry 是内部 HTTP 镜像(http://package-firewall.replit.internal/...),而 npm >= 12 会拒绝非 https 地址,因此基于 npm 的安装(hermes、camofox)会在本应安装它们的运行过程中失败;如果继承的值不是 https://*,就强制使用公共 https registry。
  • --fix 会运行启动它的那个 checkout 中的 setup/ 代码。应从正在测试的分支运行它。main checkout 会重新生成旧区块,所以正在审查中的修复不会显示在当前的 .config/bashrc 中。

Camofox 调优

无需提供环境变量文件模板——接口由导出的变量构成 (CAMOFOX_PORT、CAMOFOX_ACCESS_KEY、TAB_INACTIVITY_MS 等),这些变量从 shell 中读取(或在运行 camofox-browser 前导出):

CAMOFOX_PORT=9000 CAMOFOX_ACCESS_KEY="$(openssl rand -hex 32)" camofox

Camofox 可调参数(及其默认值)记录在 references/camofox.md 中。

安装此技能包

一个技能,一条命令:

npx -y skills add skynight137/skills -s replit

评论

支持 Markdown