组网机群索引 —— 跨机文件秒级检索与操作
这是什么
一组通过 Tailscale 组网的 Windows 机器,每台都跑着 Everything,统一装在
D:\Program Files\Everything-1.4.1.1032.x64\Everything。
这套能力让 Agent 能像操作本机一样操作这四台机器:找文件、看目录、读内容、改配置、转发文件。
用户不需要接触任何命令行,只在 Agent 对话框里说人话。
> 配套文档(均与本文件 SKILL.md 同级,位于技能根目录,可离线查阅):
> - REFERENCE.md —— 技术手册:命令全表、JSON 输出契约、退出码逐条详解、配置项、传输上限与 SHA256、ETP 排序归一化、Base64 回传、令牌生命周期与回滚规则、run 白名单设计、deploy 校验对象。
> - TROUBLESHOOTING.md —— 故障排查:按现象查,覆盖 ETP 排序 500、Session 0 僵尸、es.exe IPC 不可用、scp 中文路径失败、沙箱回收子进程、自动拉起失败等历史踩坑。
> - MANIFEST.txt —— 发布包文件清单(打包漏文件的自检依据)。
>
> 这两份文档是主文档的托底,不是可选附件——排障前先看 TROUBLESHOOTING.md。
> 声明不等于证明:想知道托底文档是否真的在,可跑 "$Z" selfcheck(逐项列文件,
> 缺则退出码 2;空文件/被截断也会报出来),"$Z" health 也会顺带输出一行
> docs_ok: true/false,或在 --json 下作为顶层 local_docs 字段。
>
> selfcheck 还查文档之间有没有自相矛盾——文件都在、体积够,不代表内容不打架。
> 格式改版后漏改的那一处会被抓出来并报 文件:行号(比如 v1.7.1 改了 JSON 结构,
> 主文档表格漏了三处旧写法,就是这样被发现的)。改动任何格式后跑一次 selfcheck。
>
> 信任根说明:selfcheck 与 MANIFEST.txt 自身是这份清单的信任根,不参与自检
> ——清单没法验证自己,否则就成了自己证明自己。两者同时缺失说明技能包已损坏,
> 请重新安装,不要继续按「文件缺失」排查。
> 刚装好还没配置? 发布包不含 config/nodes.toml(含真实 IP/用户名)。
> "$Z" selfcheck 会报「待你补 1 项」并打印 copy 命令——缺配置时它仍能跑,
> 因为它正是告诉你缺什么的那条命令。其余碰远端的命令会退出码 2 并给同样的指引。
命名与三层架构
正式名组网机群索引,内部目录名用拼音 zuwang-index(避开中文路径在 SSH/Python 里的编码坑)。
三个概念分别对应架构的三层,缺一不可:
| 概念 | 层级 | 含义 |
|---|---|---|
| 组网 | 网络层 | Tailscale 互联成的 tailnet,机器之间点对点直连 |
| 机群 | 设备层 | 四台统一纳管的机器,Everything 版本和路径完全一致 |
| 索引 | 功能层 | 每台的索引独立存储,对上层呈现统一命名空间 |
> 术语边界(内部 ↔ 汇报):本文档和脚本里出现的 ETP、Base64、SSH、-EncodedCommand、
> Session 0 等是内部实现术语,只在配置和排障时出现;汇报给用户时要转成人话——
> 「检索通道」「操作通道」「一号机/二号机」等,不要把这些词甩给用户(详见行为规范第 6 条)。
与既有技能的分工(重要,不要重复造轮子)
你的技能库里已有:
| 技能 | 解决什么 |
|---|---|
tailscale-win-mesh |
组网 + 连得上 + 传得了(含大量 Tailscale/SSH 踩坑记录) |
remote-win-fleet |
机群运维总纲,负责决策路由和环境现状 |
remote-win-shared-printer |
共享打印机 |
easytier-win-mesh |
第二条组网链路 |
本技能补的是「找得到」这一层。前三个解决「机器能连上」,本技能解决「文件秒级找到并就地操作」。
遇到 Tailscale 连不上、SSH 认证失败、scp 静默失败等问题时,转 tailscale-win-mesh,那里有完整的踩坑清单。本技能不重复这些内容。
节点与口语别名
节点表不在本文档里,也不在脚本里,在 config/nodes.toml。
想看当前拓扑:
"$Z" nodes
改机器、加机器、改 IP、加别名 —— 一律改 config/nodes.toml。
用户配了四台,口语别名是「本机 / 一号机 / 二号机 / 三号机」。
真实节点名和 IP 只存在于 config/nodes.toml(已加入 .gitignore)。
用户几乎总是用口语名。 收到「在一号机上找 xxx」时直接理解为 --on 一号机,
脚本会自己查别名表,不要反问机器代号。
用户不说机器名时,默认全机群搜索。
本机也用真实组网地址,不用 127.0.0.1。
理由是少一个「本机是特例」的分支,health 不会出现假阴性,
且和另三台写法完全一致。这条在 nodes.toml 里已生效。
双通道架构(必须理解)
| 通道 | 协议 | 用途 | 特点 |
|---|---|---|---|
| 检索通道 | ETP,端口 5767 | 找完整路径、找目录、带大小/修改时间、排序 | 只读、毫秒级、不受权限影响 |
| 操作通道 | SSH,端口 22 | 列目录、读文件、改文件、传文件、启动程序 | 读写、需认证、慢一些 |
规则:找东西一律走 ETP(快),动东西才走 SSH。
ETP 服务端已配置 etp_server_allow_file_download=0,只能查位置不能下载内容——这是有意的安全设计。
检索结果带完整路径。 协议层固定开 PATH_COLUMN / SIZE_COLUMN / DATE_MODIFIED_COLUMN,
否则 ETP 只回文件名,等于告诉你「有」却不说「在哪」,用户的原始诉求根本没法满足。
网络安全边界(部署与运维必读)
这套能力只在你自己的 Tailscale 组网内有意义,暴露面必须压到最小:
- 只走 Tailscale:四台机器通过 tailnet 点对点直连,ETP(5767) 与 SSH(22) 都不应出现在公网。
- 防火墙按子网收窄:
install-node.ps1装好后,5767 只放行 Tailscale 子网
100.64.0.0/10(remoteip=100.64.0.0/10),不对该网段外的任何地址开放。 - 开 ETP 认证(建议):在
nodes.toml给节点配etp_user/etp_pass,
并在install-node.ps1用-EtpUser/-EtpPass写入 Everything.ini 的
etp_server_username/etp_server_password。配了之后客户端会自动发USER/PASS登录,
不配则匿名(兼容现有无鉴权集群)。凭据和 IP 一样只存在nodes.toml,不外泄。 - SSH 用密钥:22 端口靠
~/.ssh/config的密钥认证,不要开密码登录。 - 不绑公网:
etp_server_bindings保持0.0.0.0配合上面的防火墙子网限制即可,
不要手动改成对公网地址监听。 - 凭据只读不下载:ETP 服务端
etp_server_allow_file_download=0,查得到位置但取不到内容;
取内容走 SSH,且.ssh/.aws/.config/.gnupg、浏览器Login Data/Cookies/Web Data、
NTUSER.DAT、C:\Windows\System32\config\*、pagefile.sys/hiberfil.sys等系统/凭据路径
受config/nodes.toml的deny_paths拦截(模板已带完整清单)。 - 双重兜底:即便
deny_paths被配空,引擎也有一份内置DEFAULT_DENY_PATHS兜底,
凭据/系统目录读和取回都拦(读到内容就等于泄露),不会因「忘了配」就裸奔。 - 兜底默认不可关闭:这份内置清单没有开关,
nodes.toml里的
allow/allow_paths也覆盖不了它——放行就等于把凭据交出去,刻意不给绕过入口。
需要读这类路径时,在那台机器上直接操作,不要经本脚本绕。
查某条路径命中了哪条规则:"$Z" why "<路径>" --on 节点。
> 满足以上几点,这套能力就是一个「自己人互相找文件」的闭环,不会把检索口子开给外人。
执行方式
首选 wrapper(自己找解释器、自己拼绝对路径,跨 shell 不会因路径问题失败):
# POSIX(Git Bash / MSYS2)
Z="C:/Users/<你的用户名>/.workbuddy/skills/zuwang-index/scripts/zuwang"
"$Z" health
# Windows(cmd.exe / PowerShell)
Z="C:\Users\<你的用户名>\.workbuddy\skills\zuwang-index\scripts\zuwang.cmd"
& $Z health
解释器按 ZUWANG_PY → py -3 → python → python3 顺序自动探测,
找不到就用 ZUWANG_PY 指到 python.exe 的完整路径。
没有 wrapper 时(技能市场只收 .py,wrapper 可能没随包下发)直接调用引擎,
功能完全等价:
# Windows
python "%USERPROFILE%\.workbuddy\skills\zuwang-index\scripts\zuwang.py" health
# POSIX
python3 ~/.workbuddy/skills/zuwang-index/scripts/zuwang.py health
引擎自带配置定位(从自身路径往上找 config/nodes.toml),从任何目录调用都工作。
编码在脚本内部处理完(输出前清掉 UTF-16 空字节),不要再接 | tr -d '\000'。
节点配置
节点拓扑不在脚本里,在 config/nodes.toml。脚本启动时加载,找不到配置会直接报错退出。
- 改机器、加机器、改 IP → 改
config/nodes.toml,不要改脚本 - 模板:
config/nodes.toml.example(脱敏,可对外发布) - 可用环境变量
ZUWANG_NODES指定另一份配置
动作清单
只读动作(直接执行,无需确认)
"$Z" health # 机群通道体检(本机掉了会自动拉起)
"$Z" nodes [--json] # 列出机群拓扑(含 require_token 当前值)
"$Z" search <关键词> [--on 节点] [--show N] # 跨机搜索
"$Z" locate <完整文件名> [--on 节点] # 跨机定位同名文件
"$Z" recent [关键词] [--on 节点] [--days N] # 最近改动(带大小和时间)
"$Z" big [关键词] --on 节点 [--mb 100] # 大文件排查(带大小和时间)
"$Z" ls "<远端目录>" --on 节点 [--depth N] # 浏览目录
"$Z" read "<远端文件>" --on 节点 [--lines 60] # 预览文件内容
"$Z" why "<路径>" --on 节点 [--action X] [--json] # 解释为什么被拦/能不能动(只读,不执行)
开工前先花一条命令把状态问清楚——两条命令字段分工不同,别指望一条全有:
| 想知道 | 用哪条命令 | 读哪个字段 |
|---|---|---|
| 现在是不是强校验模式 | 两条都有 | require_token |
| 哪些节点通道在线 | health |
data.<节点>.etp / .ssh |
| 某节点是不是只读 | nodes |
data.<节点>.read_only |
| 能不能跑程序 | nodes |
data.<节点>.allowed_programs |
| 装在哪 / 装没装 | nodes |
data.<节点>.install_dir |
| 技能包完不完整 | health |
顶层 local_docs.docs_ok |
health独有:每节点通道状态(etp/ssh)、自动拉起标记、顶层local_docsnodes独有:read_only、allowed_programs、install_dir、etp_host/etp_port- 两条都有:
label、require_token
> 两条命令的 data 形状一致,都是「节点名 → 该节点信息」:
> health 读 data.<节点>.etp,nodes 读 data.<节点>.read_only。
> 顶层额外挂 health 的 local_docs 与 nodes 的 require_token_uniform/_scope。
> require_token 是全局值(配在 [global.policy],节点级可覆盖)。
> nodes 会在开头直接告诉你「全局一致」还是「N/M 台开启」,各节点字段只是方便你
> 读单个节点对象时不必回查——别把它读成「这台开、那台关」。
> 判一致性看顶层 require_token_uniform(true = 所有节点相同)。
local_docs 单列在 JSON 顶层(不进 data),因为「机群通道健康」和「本机文件齐全」
是两件事:它不影响 health 的退出码,退出码只看通道(0 全通 / 9 部分不通)。
docs_ok=false 时同一条记录里就有缺哪个:missing(缺失或 0 字节)与
empty(空壳/被截断)两个字符串数组,hint 是一条全局行动提示(字符串,
不是每文件一条),照它补齐即可,不用再跑一次 selfcheck。
why 固定输出三段,读的时候按段取,别只看第一行:
| 段 | 人话输出 | JSON 字段 | 作用 |
|---|---|---|---|
| 1 结论 | 【结论】放行 / 拦截 | data.allowed |
一句话回答「能不能动」 |
| 2 命中规则 | 【命中规则】哪条规则拦的 | data.reason |
放行时为 null(无命中) |
| 3 建议动作 | 【建议动作】下一步做什么 | data.advice |
只给合规路径,不给绕过入口 |
why 的 data 是单节点对象(因为 --on 必填,只查一台),字段直接是
allowed/reason/advice,不带节点层——和 health/nodes 的
data.<节点>.* 不是一回事,别混着取。另有 scope(本次判的是哪台机器上的
什么权限)与 other_side_unchecked(跨机动作的另一端提示)。
fetch 与 send 跨两端,why 只判 --on 指定的那台:
| 动作 | why --on X 判的是 |
另一端 |
|---|---|---|
fetch |
X 上的读权限 | 本机写入(本机不需要远端权限),仅提醒 --to 已存在时要 --yes |
send |
X 上的读权限(源文件) | --to 目标机的写权限——本次没判,要另跑一次 |
send 建议两端都查:why "<源文件>" --on <源> --action send 与
why "<落盘目录>" --on <目标> --action send。跨机转发涉及两台机器的策略,
只查一端等于没查全。
--on 必填:why 是「在这台机上能不能动这个路径」的单点判断。各节点的只读/白名单/
保护规则都不同,不指定机器给不出确定答案(硬要指定全部会得到一条混合结论,等于没答)。
要看所有节点状态用 nodes。
--action X 只接受 11 个文件类动作:read/fetch/put/edit/send/run/
ls/search/locate/recent/big,不传按 read 判定。
写动作会额外走 read_only/allow/deny 三道节点闸门——同一路径 read 放行、put 被拒是正常的。
传了别的动作一律退出码 2,且分两种说法:
- 认识的命令但不是文件类(
health/nodes/plan/deploy…)→ 明确说「不支持」,
因为它们不带文件路径语义,报「未知」会让人以为命令不存在 - 完全不认识(
write、wri…)→ 报「未知动作」并附近似拼写提示
两种都不会默默当 read 放行——拼错动作名却拿到一个放行结论,比直接报错危险得多。
--on 三种写法都认,说人话时哪种都可能冒出来:
--on 一号机 --on 二号机 # 重复传
--on 一号机,二号机 # 逗号分隔
--on 一号机、二号机 # 顿号分隔
不给 --on 就是全机群。检索类命令会并发打到所有目标节点。
--show N 只控制屏幕显示前 N 条(默认 15),JSON 里的 total 永远是命中总数,
truncated 表示「还有没显示完的」。要拿到更多就加大 --limit(控制实际取回条数,
默认 50)。两者作用不同:--limit 是「查多少」,--show 是「念几条」。
取回与写入动作
# 取回本机:对远端只读,但需要确认的是「本机已有同名文件别被静默覆盖」。
# --to 指向的文件已存在时,必须加 --yes,且脚本自动备份原文件再覆盖。
"$Z" fetch "<远端文件>" --on 节点 [--to <本地>]
# ↓ 以下五个会改动远端,必须加 --yes,否则脚本直接拒绝
"$Z" put <本地文件> "<远端目录>" --on 节点 --yes # 送到远端
"$Z" edit "<远端文件>" --find "<旧>" --replace "<新>" --on 节点 --yes # 改(自动备份)
"$Z" send "<源节点文件>" --on <源> --to <目标> [--dest <目录>] --yes # 跨机转发(--dest 不传 = 目标机 home)
# 远端启动。默认 allowed_programs = [](不限制,装完不额外收紧);配了白名单后,
# 白名单外的程序一律拒绝(退出码 3)——白名单是硬约束,不是提示,
# 不存在「用户口头同意就能跑」的路径。
"$Z" run "<远端程序>" --on 节点 [--args "参数"] --yes
# 远程部署。部署前校验 payload 里 Everything.exe 版本必须 1.4.1.*(不符退出码 2 中止),
# 并记录 install-node.ps1 的 SHA256 进审计;装完自动回一条 health 自检。
"$Z" deploy --on 节点 [--payload 目录] --yes
> 两个默认值要记住:run 不配白名单等于不限制(不是"只允许系统程序");
> deploy 会在写盘前先卡版本,版本不对宁可不装——1.5.x 把 ETP 拆成插件,装了通道就废。
>
> 确实要上 1.5.x 怎么办:先在那台机器上确认 etp_server64.dll 已装且端口 5767 能连上,
> 再考虑放开版本校验(当前脚本不支持,需自行放宽部署前的版本校验逻辑后承担风险)。
> 本机的正确解法是装回 1.4.1.1032——它内置 ETP,不需要任何插件。
> fetch 不是「无需确认」的只读动作:它会在本机写文件,落盘到已有文件时必须
> --yes + 自动备份(这个规则在行为规范第 5 条「覆盖已存在文件需确认」里一以贯之,
> 与脚本行为一致)。read/ls/search 这类「只查不改」的才是真正无需确认。
>
> 备份命名三个动作统一:都是 <原文件名>.bak-<时间戳>(fetch 覆盖本机文件、
> edit 改远端文件、deploy 改对端 Everything.ini,同一套规则)。
> 看到 .bak-* 就知道是被覆盖/被改之前的原件,回滚直接改回去即可。
>
> 不传 --to 的落盘位置:<当前目录>/downloads/<远端文件名>(目录自动建)。
> 脚本会明确输出「未指定 --to,已按默认规则放到 ./downloads/ 下」,不会静默落盘。
确认动作(plan,元动作)
plan 不真正执行,只生成确认摘要 + 一次性令牌,并把摘要里「会改变世界的内容」
原样算进令牌哈希。Agent 先跑 plan,把摘要念给用户,用户同意后再带令牌执行。
# put:绑「目标目录 + 源文件 SHA256」(同一文件内容变了令牌就失效)
"$Z" plan put --on 一号机 --target "D:\工作\报告.docx" --local "本机/报告.docx"
# edit:绑「路径 + find + replace」(同意改 8080 却去改 80,令牌过不了)
"$Z" plan edit --on 一号机 --target "D:\app\conf.ini" --find "8080" --replace "9090"
# run:绑「程序路径 + args」
"$Z" plan run --on 一号机 --target "D:\app\svc.exe" --args "--safe"
# send:绑「源文件 + 落盘目录 + 目标机」三样。
# 注意 --target 传的是**源文件**(在 --on 那台机器上),落盘目录用 --dest
# (不传 --dest 时落到目标机的 home,与 send 执行时一致)。
"$Z" plan send --on 一号机 --target "D:\工作\报告.docx" --to 二号机 --dest "E:\drop"
plan 本身也会写进审计日志(action=plan),方便回溯「当时给用户看的摘要是什么」。
> --target 的含义随动作变化,传错会让令牌绑到错误的东西上:
>
> | 动作 | --target 是 | 配 --local | 配 --args | 配 --to | 配 --dest |
> |---|---|---|---|---|---|
> | put | 落盘后的完整文件路径 | 源文件(绑 SHA256) | — | — | — |
> | edit | 要改的远端文件路径 | — | — | — | — |
> | run | 远端程序路径 | — | 启动参数 | — | — |
> | send | 源节点上的源文件路径 | — | — | 目标机(必填) | 落盘目录 |
>
> 注意 put 的 --target 是含文件名的完整路径(不是目录);
> send 的 --target 是源文件(落盘目录用 --dest,目标机用 --to,两者都必填)。
> 这两个最容易搞反。
>
> send 的权限按目标机判:写发生在 --to 那台,所以 plan send 会校验
> 目标机的落盘目录(--on 指的源节点只涉及读)。摘要会把源机器、目标机器、
> 源文件、落盘目录四项都念出来,令牌也绑这四项 + 源文件内容。
> 漏了 --to 会直接退出码 2。
权限与确认(必须遵守)
写动作一律加 --yes,且加之前必须先向用户确认。
脚本不会偷偷执行——没传 --yes 就直接拒绝,并把拒绝记进审计日志。
四道闸门在 config/nodes.toml 的 [global.policy] 配置:
read_only(整节点只读)、deny(黑名单)、allow(白名单)、deny_paths(路径保护)。
默认保护 C:\Windows\* 等系统目录,正斜杠写法同样拦截。
Agent 侧纪律:
- 不要为了"让用户省事"而自作主张加 --yes
- 被权限拒绝时,把原因原样告诉用户,不要改路径或换节点去绕
- 批量/覆盖/删除类操作,先列清单给用户看,拿到确认再执行
确认令牌(--token,可选强校验)
--yes 只是命令行标志,脚本无法验证「人真的看过并同意」。令牌机制补上这一环,
默认关闭(全局 require_token=false 时只靠 --yes),开启后写动作还要一次性令牌。
令牌绑定的是「内容」不是「路径」:摘要哈希由「动作 + 节点 + 归一化路径 +
真正改变世界的内容」组成——
edit 绑 (find, replace)、put 绑「源文件 SHA256」、run 绑 args、
send 绑 (源文件, 落盘目录, 目标机) 三样。
所以「同意改 8080 却实际改成 80」「同意传这份文件却换成另一份」「同意发到 A 却发到 B」
都会因哈希不匹配被拒。
念给用户要用人话,不要念哈希:Agent 把摘要转述给用户时只用「展示层」描述,校验层哈希只在审计里存——
- edit → 念「把 8080 改成 9090」
- put → 念「源文件:报告.docx(2.3MB,修改于 2026-10-07 14:20)」,不念那串 64 位十六进制(用户看不懂也记不住)
- run → 念「运行 svc.exe --safe」
- send → 念「一号机的 D:\工作\报告.docx → 发到二号机的 E:\drop」
> send 的绑定节点是目标机(写发生在那里),源文件只涉及读;
> plan send 必须带 --to,漏了退出码 2。摘要会把源机器/目标机器/源文件/落盘目录四项都念出来。
即:展示用人话,校验用哈希,两者绑同一个内容。用户凭人话判断是否同意,令牌凭哈希防篡改;
plan 命令输出里 put 已经只给文件名/大小/修改时间,SHA256 只在 binding 字段(审计用)里。
若执行时哈希不匹配被拒(常见于 put):说明源文件在用户确认之后又变了。这是正确的安全行为,
但用户会困惑「我明明同意了」——要告诉用户「文件在你确认后被改动过,请重新确认」,
然后重新 plan 拿新令牌,不要自行重跑 plan 覆盖过去。
其他失配原因(都按「重新 plan」处理,别自己找绕过路径):
- send 换了目标机或落盘目录
- 路径写法变了(斜杠方向、末尾斜杠)——这类不该失配,已做归一化;
若真失配说明引擎有 bug,走 audit --action send 查最近记录
失配时脚本会直接给出可照抄的 plan 命令(按各动作的真实参数拼,不是描述串)。
把它原样跑一遍拿新令牌即可——不用自己拼参数,拼错会再失败一次。
> 提示里的节点名用你本次执行时的原写法回放(你写 --on 一号机 它就写 一号机),
> 不是统一成本名。想换写法也行——别名/本名已归一化,绑定相同。
执行时建议用与 plan 相同的写法(同一个别名、同一种斜杠方向)。
虽然节点名和路径都做了归一化,但保持一致能排除掉不必要的变量——
真出问题时就只剩一个可疑点。
生命周期:
- 生成:
"$Z" plan <动作> --on 节点 --target ... (动作内容参数)
输出「确认摘要 + 一次性令牌」,令牌 15 分钟有效 - 展示:Agent 把摘要原样念给用户(动作 / 机器 / 目标 / 内容摘要 / 令牌)
- 确认:用户口头同意
- 携带执行:
"<动作>" ... --on 节点 --yes --token <令牌> - 审计:令牌绑定内容哈希,错配/过期/复用一律拒绝,并记入审计
令牌结算(重试与回滚):令牌在执行前预扣,执行完按退出码决定命运——
- 退出码 5/6/8(检索通道不可达 / 操作通道不可达 / 超时,即远端没被真正触达):
令牌回滚,可拿同一个令牌重试
- 其它(成功 0 / 被拒 3 / 不存在 7 / 部分成功 9):令牌作废,不可复用
> 退出码 4 = 缺 --yes 或 --token。拿到这个码就说明该先向用户确认,
> 不要自己编造令牌去闯。
审计
"$Z" audit [--failed] [--action X] [-n N]
每次动作(含被拒的、plan 生成的)都记进 audit/zuwang-audit.jsonl。
出现争议时先看这个日志,不要凭记忆回答。
action 字段的取值就是命令名,另有两个非命令值:
action |
何时记 | 含义 |
|---|---|---|
<命令名> |
每次调用 | health / search / put / edit / send / run / deploy / plan / why … |
autostart |
health 把本机 Everything 拉起来时 |
改变了本机状态,所以留痕;查 "$Z" audit --action autostart |
每条含 ts / action / node / target / ok / detail;不记密码与令牌原文。
运维动作(装新机器时用)
"$Z" deploy --on 节点 [--payload "<本机Everything目录>"] [--install-dir "<远端目录>"]
把 scripts/install-node.ps1 推到目标机并执行:部署本体 → 写 ETP 配置 →
防火墙放行 → 建登录触发计划任务(避开 Session 0)→ 自检端口。
本机要装就直接在本地跑 install-node.ps1,不要走 deploy。
节点发现
"$Z" discover # 默认走 tailscale status,够用
"$Z" discover --scan # 强制扫网段(tailscale 读不到节点时)
"$Z" discover --port 5767 --jobs 64
优先读 tailscale status 拿真实节点表,拿不到才退回扫网段。
--scan 强制扫网段的 5767 端口、--port 改端口、--jobs 调并发,细节见 REFERENCE.md。
用户问「新加了一台机器怎么纳管」时用这个,找出 IP 后写进 nodes.toml。
结构化输出与退出码
所有动作都支持 --json,schema 统一:
ok, action, nodes, data, error, total, truncated, elapsed_ms, exit_code
人类可读输出在 --json 模式下会被静默掉,stdout 只有一条 JSON,不会被中间进度污染。
退出码决定你下一步该干什么,不要靠解析人话文本判断成功失败:
| 码 | 含义 | 该怎么做 |
|---|---|---|
| 0 | 成功 | 直接汇报 |
| 1 | 通用失败 | 看 error 字段 |
| 2 | 配置错误 | 检查 nodes.toml。也用于参数错误与 selfcheck 判定发布包不合格(缺文件/空壳)——都是「配置或契约不满足」,不是远端故障 |
| 3 | 被权限模型拒绝 | 原样告诉用户,不要换路径/换节点绕过去;可跑 "$Z" why "<路径>" --on 节点 查命中哪条规则再解释 |
| 4 | 缺确认(没 --yes 或 --token) |
先向用户确认 |
| 5 | 检索通道不可达 | 提示那台机器的检索服务没起来 |
| 6 | 操作通道不可达 | SSH 不通,转 tailscale-win-mesh |
| 7 | 目标不存在 | 换个关键词或确认路径 |
| 8 | 超时 | 缩小范围重试 |
| 9 | 部分成功 | 汇报哪几台通了、哪几台没通 |
完整字段说明见 REFERENCE.md,故障排查见 TROUBLESHOOTING.md。
行为规范
1. 绝不把命令甩给用户
用户明确表示不懂代码。所有命令由 Agent 自己执行,只把结果讲给用户听。
- 不要说「你可以运行这个命令」
- 不要贴 PowerShell / SSH / Python 代码块给用户
- 不要让用户手动复制路径
- 回复应该是结论 + 文件清单 + 下一步建议
2. 搜索类请求的标准流程
用户说「在一号机上找 xxx」时:
- 用
search或locate查(指定--on) - 结果超过
--show条时先给前--show条(默认 15),并说明总数 - 结果里同时有目录和文件时,分开说明
- 找到疑似目标后主动用
read或ls进一步确认,而不是把一堆路径丢给用户 - 用口语节点名汇报:「一号机上有 3 个匹配,其中最像是你要的……」
3. 「看看某台机器上有什么」的标准流程
- 先
ls桌面、文档、下载、项目根目录 - 找到有意义的目录后
ls --depth 2展开 - 按「项目 / 文档 / 工具 / 其他」分类汇报
- 指出哪些是做到一半的项目(有源码但无构建产物、有 .git 未提交等)
4. 修改类请求必须先确认
改远端文件前,先 read 把相关片段给用户看,说明你理解要改什么,得到确认再 edit。
edit 会自动生成 .bak-时间戳 备份,汇报时必须告诉用户备份在哪。
内容有歧义时(例如「把端口改成 8080」但文件里有多个端口),列出所有候选让用户选,不要自己猜。
5. 高风险操作必须先确认
以下操作必须先说明影响范围并获得明确同意:
- 删除任何文件
- 覆盖已存在的文件
- 批量修改
- 修改系统配置、注册表、启动项
- 移动或重命名文件
- 向用户未指定的位置写入文件
6. 不要暴露底层细节
- 不要提「ETP」「Base64」「-EncodedCommand」等实现细节,除非用户主动问
- 汇报用节点的口语别名(「一号机」「二号机」)而不是配置里的节点 key(
node-a/node-b)——
别名是给人听的,节点 key 是配置细节
7. 通道异常时的处理
- 本机检索通道连不上:脚本会自动拉起一次 Everything 再重试(看到「已自动拉起」就是这条生效了)。
只有自动拉起也失败,才需要人去双击。
自动拉起会写审计(action=autostart),所以事后能查到「谁在什么时候把本机进程拉起来的」——
它改变了本机状态,不该只留在 health 输出里。查法:"$Z" audit --action autostart - 远端某台检索通道连不上:需要那台机器处于已登录状态(计划任务是登录触发的),
或在那台机器上手动启动一次 Everything - 「操作通道异常」:SSH 不通,文件操作做不了,但检索可能仍可用
- 想禁止自动拉起:加
--no-autostart
8. 固定汇报模板
搜索类结果一律按这个形状回,别每次换说法:
在【机器名】上找到 N 个匹配,最像是你要的:
1. <完整路径>
2. <完整路径>
3. <完整路径>
(共 N 条,已列出前 {show} 条(由 --show 控制,默认 15)。另有 N-{show} 条未显示)
要我打开 / 取回 / 继续看其中哪一个?
改动类结果:
已改【机器名】上的 <路径>:把「旧」换成了「新」,共 N 处。
原文件已备份到 <备份路径>。
多条结果、跨多台时,按机器分组,每组给路径清单,最后给一句建议。
永远给完整路径——用户要靠它去定位,只给文件名等于没说。
常见场景对应
| 用户说 | 怎么做 |
|---|---|
| 「在其他机器上找某个文件」 | search 全机群;locate 找同名文件 |
| 「在某台机器上找 xxx」 | search <kw> --on 节点 |
| 「看看某台机器上有什么东西」 | ls 桌面/文档/下载 → ls --depth 2 展开 |
| 「某台机器上有哪些项目」 | search 找项目标志(package.json、.sln、pom.xml、pyproject.toml)再 ls 确认 |
| 「某台上装了哪些 skill」 | ls "<用户目录>\.workbuddy\skills" --on 节点 看技能目录;或 search SKILL.md --on 节点 找技能定义文件(注意:skill 是目录不是单文件,别指望一条 search skill 出全) |
| 「最近改过什么」 | recent --days N |
| 「磁盘满了 / 找大文件」 | big --mb 100 |
| 「把某台上的文件转发给我」 | fetch --on 节点(本机已有同名文件时需 --yes,脚本自动备份原文件) |
| 「把 A 机的文件发给 B 机」 | send --on A --to B |
| 「改某台机器上的配置」 | read 确认 → 告知用户 → edit |
| 「在某台机器上打开/运行某个程序」 | run --on 节点 |
已知环境事实(要点)
本机专属信息(IP、用户名、路径)一律写在 config/nodes.toml,本文档只记通用规律。
以下结论均在 2026-10 于 Everything 1.4.1.1032 / Windows 11 / Python 3.13 实测验证:
- 远端 Everything 用计划任务(InteractiveToken)开机自启,本机用启动文件夹
- 远端不能用 SSH 直接拉起 GUI 程序(会落 Session 0 变僵尸进程),必须走计划任务交互式会话
- 各机 Everything 安装路径可能不一致,用
nodes看install_dir,不要硬编码路径 - 文件/目录大小等传输上限与 SHA256 校验、ETP 排序归一化、Base64 回传等协议级细节,见
REFERENCE.md
> 排障与协议级细节请从本文档分流到 REFERENCE.md(技术手册)与 TROUBLESHOOTING.md(故障排查),
> 本文件只保留 Agent 必须遵守的行为纪律。