让第一次连上Linux 服务器的人,敢敲下一条命令
SSH 终端、AI 助手、高危命令拦截、决策留痕放在同一个窗口。 AI 不替你按下回车——它先说清要执行什么、依据是什么、失败会怎样; 你自己敲的命令,也过同一道闸。
数据只落本机 SQLite · 模型可指向本机 Ollama
演示回放:命令、等级、原因文字逐字取自仓内规则数据 command-rules.json 与 command-gate.ts; 主机地址用 RFC 5737 文档保留段,不是真实机器。终端输出未画,因为这台机器不存在。
示意图:一台本机同时挂着多个远端会话。每条命令和它的输出按会话切成块,拦截与留痕也按会话归属分开存;上面那个终端里每判一条命令,这里就发一颗包。
难的不是记命令,
是按下回车之前那段空白
教程给不了失败样本,聊天框给不了执行现场,终端什么都不解释。 这个工作台把三件事接起来:命令在哪里执行、执行前后发生了什么、哪些命令不该执行。
之前,不是事后
初学者的代价通常是“这台虚机废了,重装”。教学环境里,这等于放弃动手。
先分词,再切段。引号里的 ; | && 不算分隔符, 所以 bash -c "rm -rf /; echo x" 不会被撕成两段绕过。
写法变体同判。rm -rf /、rm -rf "/"、 rm -rf // 归一后再比受保护清单。
三个入口一条闸。人敲的、粘贴的、AI 发起的命令,都从同一个执行口出去。
拦下要能说清。每次拦截留痕:命中哪条规则、为什么、替代做法是什么。
八项能力,
每项都对得上界面上真存在的入口
未接线的占位功能不写在这里。没有生产者的统计值不显示在界面上。
每项都有界面入口
终端优先,不是聊天框优先
三栏可拖拽,终端格默认最大化;多标签、独立第二窗口,同时看两台机器不来回切。拖出来的宽度会记住。
命令与输出切成块
OSC 133 / 633 标记把一次命令和它的输出缝成一块,可回看、可检索,也是 AI 读上下文的单位。
拦截发生在执行之前
词表规则 + tree-sitter-bash 结构化解析 + 只读白名单,输出等级、命中依据、影响范围;拦下的那些不会落进 shell。
AI 的每一步落在屏上
思考、工具调用、待执行命令、返回结果分开展示;诊断 / 部署 / 巡检 / 回滚四种模式走同一套编排,流式可中断。问答记录落本地库:重启后接着看,切服务器不串档,清空按下去就真删。
用量说清是实测还是估算
上下文占用按模型真实窗口计算;实测 token 与估算值分开标注,不拿估出来的数字冒充实测。
结论后面挂着证据链
多源证据加权融合(Dempster 规则 + PCR5 冲突再分配),雷达图显示每条证据的贡献与冲突流向;权重读取失败时降级为无权重融合,界面如实显示降级。
断网也能查的运维知识
命令词典、教程、故障案例三类条目;本地嵌入模型 + SQLite 向量索引做语义检索,不依赖外部 API。读者答复回灌条目成功率。
数据与凭据留在本机
服务器信息、历史、知识库存在本地 SQLite;凭据只在主进程边界内使用,日志、报错、AI 上下文出边界前统一脱敏。
点一条命令,看它怎么被判
下面每条的等级与原因文字都是引擎实际会给出的那一句。 网页只做词表与结构检测的演示;真实判定在本地进程里跑,包含 bash AST 解析。
8 个结构检测器
规则以数据文件维护,改级别不用动代码;但 rm、 userdel、passwd 这类底线命令被数据调低时, 闸会拒绝启动——fail-closed,不让数据把底线改松。
界面长这样
两张图都由自动化脚本从真实运行窗口截取,未替换内容、未做后期。点开放大。
脚本取自运行窗口
三进程,一条受控边界
渲染层拿不到 Node 能力。所有敏感操作经 preload 暴露的类型化通道进主进程;通道名由一张中央常量表出,已登记契约的通道在入口校验入参。
65 条已登记契约
只呈现与发起请求
Zustand 状态容器,Radix + Tailwind 组件层,设计令牌单一来源。沙箱隔离,拿不到 Node 能力。
受控边界
通道名全部来自一张中央常量表;已登记契约的通道在入口校验入参,错误统一按信封返回。
能力与判定
ssh2 连接池、node-pty、SQLite、风险引擎、可信度融合、LLM 客户端、MCP 服务、调度器。
命令执行口
人敲的 / 粘贴的 / AI 发起的 → 同一条风险闸 → 通过才送进 shell。拦截在此发生,留痕也在此写入。
Electron 安全三原则保持不动
- contextIsolation: true
- nodeIntegration: false
- sandbox: true
新增能力优先落在主进程。日志、报错、AI 上下文在离开进程边界前统一脱敏;凭据不进渲染层,也不进任何文本产物。
一条 AI 建议从生成到执行
这是产品的核心承诺:任何改动远端状态的命令,都要走完这四步才会落到终端。
改远端状态都要走完
生成
模型基于终端上下文、命令块历史与知识库给出建议,工具调用在流式过程中即时展示,不事后补写。
判定
命令进风险引擎:词表 + AST + 只读白名单,输出风险等级、命中依据与影响范围。
确认
决策卡由主进程构造——模型无法伪造自己的“已批准”。界面显示准备执行什么、在哪台机器、失败怎么回滚。
留痕
执行结果与当时的证据构成写入本地记录,事后在决策页可回查;被拦下的同样留痕,含原因。
技术栈
选型的判据只有一条:能不能让上面那四步真的跑起来,并且跑得起来还查得到依据。
无 CDN 依赖
| 层 | 选型 |
|---|---|
| 桌面框架 | Electron 43(contextIsolation / sandbox 全开) |
| 界面 | React 19 + TypeScript + Zustand 5 + Radix UI + Tailwind + lucide-react |
| 终端 | xterm.js 5 + node-pty(本机)+ ssh2(远端),两侧命令块口径一致 |
| 编辑器 | Monaco 0.56,资源本地打包,无 CDN 依赖 |
| AI | Vercel AI SDK 7 + 官方 MCP SDK 1.32,Anthropic / Google / OpenAI 兼容端,支持本机 Ollama |
| 命令解析 | web-tree-sitter + tree-sitter-bash |
| 存储 | better-sqlite3 + sqlite-vec 向量索引,@xenova/transformers 本地嵌入 |
| 可观测 | Langfuse 追踪(未配置密钥时自动关闭,不影响主流程) |
| 测试 | Vitest + Playwright(直驱真实应用窗口,不是 DOM 模拟) |
每个数字都带复现命令
单元与结构读数在 2026-10-23 01:45 由本机重跑取得,端到端 01:50 那一轮(含真机靶机),UTC+8。除用例数两条外,这些读数只有一个出口:npm run promo:check。界面和文档里都不出现没有测法的数字。
各带复现命令
| 读数 | 值 | 怎么量的 |
|---|---|---|
| 单元与组件用例 | 3619 通过 | npm test 另有 20 条按环境跳过,共 3639;325 个测试文件(323 通过 / 2 跳过) |
| 端到端用例 | 74 通过 | npx playwright test Playwright 直驱真实应用窗口,13 条按是否需要真服务器 / 真模型跳过 |
| IPC 通道 | 283 | npm run audit:ipc 244 请求-响应 + 39 事件推送 |
| 已登记契约的通道 | 65 | src/shared/ipc-contract.ts 60 请求-响应 + 5 事件推送 |
| TypeScript / TSX 源文件 | 530 | git ls-files src 按 .ts / .tsx 计数 |
| 命令规则词表项 | 226 | npm run promo:check 八张表逐项相加,护栏按分组数(早先手数漏了两组,报成 216) |
| 结构性拦截器 | 8 | denyBaseline.blockDetectors |
每次提交前会跑红的门禁
- ESLint 与 TypeScript 类型检查
- 测试代码零类型错误(存量只降不升)
- 死代码闸
- 设计契约闸:令牌单一来源
- 真机界面可读性审计(含浅色模式对比度)
- IPC 契约一致性(生成物与契约对账)
- 本地密钥扫描
- Git 历史凭据回归扫描
- 内网拓扑按值核对(代码与文档两条腿)
- 只写不读状态键检测
- 按钮真伪普查:逐页真点每个可见按钮
当前状态与边界
这几条写在这里,是为了让“能做”和“现在就能用”不混为一谈。
没做到的不写成做到
- 已公开源码仓库已公开(MIT),但不提供安装包下载:需要界面里那三个 build 脚本之一在本机自行打包,Windows 之外未验证。
- 需连接监控、日志与远端文件能力需要先连上一台 Linux 服务器;未连接时界面显示空状态,不放示例数据。
- 看模型AI 能力取决于所配置的模型。本地小模型下工具调用的稳定性会明显下降,此时建议只用于解释与诊断。
- 会降级可信度融合在证据权重读取失败时降级为无权重融合,界面如实显示降级,不会静默给一个看起来更准的数字。