# herdr-web — 在手机和平板上对着 agent 说话

> 浏览器里的 herdr 终端，一个 Go 二进制。手机上够用，平板上是生产力 —— 横屏 211 列，手写笔口述、整段投给 agent。npm i -g @bysir/herdr-web 就能装（MIT 开源）。发件箱、软键条、面板一览（谁在等你点一行就跳过去）、文件浏览（点终端里的路径直接看图）、横竖屏各一套布局、一台设备配一次。

[herdr-web](#top) [安全](#security) [发件箱](#outbox) [软键条](#softkeys) [面板一览](#panes) [文件](#files) [布局](#layout) [怎么跑](#start)

一个 Go 二进制 · 前端嵌在里面

# 对着 agent 说话，  平板也是生产力

不用装 app。安卓、苹果、平板，扫个码就连上。

不另做一个手机版：网页里是整个 TUI，键盘那套操作一个字都没变。 补的只是手指够得着的那一层 —— 一个真正的输入框、一排自己排的键、 一块能挪开输入法的面板。

[跑起来](#start) [源码](https://github.com/zbysir/herdr-web)

$ `npm i -g @bysir/herdr-web` 复制

需要一个已经在跑的 herdr。MIT，源码在 GitHub。

zsh 42×28

➜ herdr-web git:(main) make test

ok internal/auth 0.8s

ok internal/server 1.9s

➜ herdr-web git:(main)

⟳ w9:pE · claude working

把发件箱那个轮询间隔改成可配的，默认别动，然后跑一遍 make test。

↑ 取回上一条投稿 ⌘↵

^B 前缀CtrlEscTab↑↓

[01安全\
\
穿透不做鉴权 —— 这一层 herdr-web 自己做。](#security) [02双向同步的发件箱\
\
框和终端的输入行双向同步，投出去不会叠成两段。](#outbox) [03软键条\
\
手机没有 Ctrl 键，那就自己排一排。](#softkeys) [04面板一览\
\
十几个 agent 在跑，点一行跳过去 —— 而且自动铺满整屏。](#panes) [05文件浏览\
\
agent 说图生成在哪儿，点那行路径就能看。](#files) [06布局\
\
平板才值得自己排一套 —— 横屏竖屏各存一份。](#layout)

01/ 06能力

## 安全

谁摸到那个地址，谁就有你的机器。

[在 README 里细看 →](https://github.com/zbysir/herdr-web/blob/master/README.zh-CN.md#安全)

Cloudflare Tunnel、Tailscale、frp —— 穿透搭得越好，安全风险就越大。

没有鉴权这一层，具体是这三件事：

●

穿透那头直接就是一个登录 shell

穿透只负责把包送到，不做鉴权 —— 连上来的是谁，它不问。

●

地址或 token 泄露一次，就一直有效

浏览器历史、一张截图、一份 frp 配置、tailnet 里任何一个节点。泄露之后，没有任何一步会再拦你。

●

丢了一台手机，只能把整条通道换掉

所有设备共用一份凭据，就没法只踢那一台。

所以鉴权这一层 herdr-web 自己做，按「这条路可能已经不安全」来做：

扫一次码

一次性配对码换一份 per-device 凭据，放 HttpOnly cookie。URL 里不留秘密。

服务端只存哈希

磁盘上没有任何能直接登录的明文 —— 防止坏 agent 把你的凭证发出去。

能撤销

一条命令踢掉一台设备，下一个请求就 401。

![herdr-web 的配对页：提示在机器上执行 herdr-web pair，可以用相机扫二维码或手输 8 位配对码](https://fsu.creght.com/site/2090728059501023232/1787307365914__shot_pair.png)一台设备只见这页一次。只有坐在机器前的人能出码 —— 网页上（包括已配过对的设备）都不行。

### 为什么推荐使用 passkey

配对码给的是第一层，passkey 是第二层。 差别在这儿：

只有第一层

- ●凭据就是一份 cookie —— 谁拿到，谁就是你
- ●泄露之后一直有效，直到你想起来撤销
- ●没有任何一步会再问一次「还是你吗」

加上 passkey

- ●私钥锁在设备里出不来，服务端只存公钥 —— 被读也没用
- ●默认每 24 小时重验一次，cookie 就算泄露，窗口最多一天
- ●签名绑着 origin，钓鱼站点拿不到能用的那一份

代价是得有个域名：passkey 的签名认的是域名，裸 IP 注册不了 —— 用 IP 连只有第一层，配个域名才有第二层。哪一档要准备什么，见 [放在哪儿跑](#deploy)。

02/ 06能力

## 双向同步的发件箱

按了 Tab 补全、上下翻历史 —— 终端里已经有字，框里还空着。这时候投出去，两段叠一起。

[在 README 里细看 →](https://github.com/zbysir/herdr-web/blob/master/README.zh-CN.md#发件箱语音投稿)

所以框里那段和终端的输入行是双向的：终端那边多出来的字，框里跟着有。 框本身也是终端给不了的东西 —— 终端是字节流，没有选区，输入法只能一路往里灌字符； 落在一个真正的可编辑框里，才能说完一整段再投，说错的字框选重说就改掉。

![发件箱：状态行写着这段话会投给哪个 pane，框里有四个字被选中，底下是软键条](https://fsu.creght.com/site/2090728059501023232/1787307675307__shot_outbox2.png)真实截图。选中的那四个字，再说一遍就被替换掉；状态行一直写着这段话会投给谁、投得出去投不出去。

投给谁不用选

默认跟着 herdr 当前 pane，也能在下拉里钉死一个。

整段投递 ⌘↵

先清空远端输入行再提交。Enter 只换行 —— 说到一半不会被回车打断。

拉回来改

把远端输入框里已有的内容抓回来编辑，默认 500ms 一拍。

图片也能投

存到跑 herdr 那台机器上，把绝对路径插进提示词，agent 自己去读。

03/ 06能力

## 软键条

手机键盘上没有 Ctrl，herdr 的快捷键一个都按不出来。

[在 README 里细看 →](https://github.com/zbysir/herdr-web/blob/master/README.zh-CN.md#软键条)

herdr 的快捷键全靠 `^B` 前缀，所以键做成一条自己排的横条 —— 排什么、排几个都归你。 按键存在服务端，手机 / 平板 / 电脑共用一份，网页上改完三台一起变。

一个键可以是一串

ctrl+b c 一下点出来。具名键、组合键、原样文本都行。

Ctrl / Alt 是粘滞的

点一下亮起，再敲个字母就发出组合键 —— 手机 keydown 不可靠，这层做在数据流上。

危险的键点两下

关 pane、关标签、/clear 默认就带。软键条上键挨得近，误触没法撤销。

六十多个预设

一键灌进「我的按键」，之后每个都能改名字、改谱、改宽度。

手机上的复制粘贴也在这儿

herdr 复制到的是跑 herdr 那台机器的剪贴板，手机上哪儿都粘不出来。📋 取把它拿到手机剪贴板，📥 粘反过来 —— 浏览器只在点击里给剪贴板，所以只能是两个键。

![设置里的软键条编辑器：上面是条上的键，下面是「我的按键」库，六十多个预设铺在里面](https://fsu.creght.com/site/2090728059501023232/1787307364596__shot_softkeys.png)上面是条，下面是「我的按键」。条上放的是引用 —— 拖上去，库里那个不动。

04/ 06能力

## 面板一览

十几个 agent 在跑，哪个在等你回话？一个个翻是翻不过来的。

[在 README 里细看 →](https://github.com/zbysir/herdr-web/blob/master/README.zh-CN.md#面板一览手机上换-pane-的那条路)

软键条发的是按键，而按键只会说「下一个 tab」。 想直接跳到 w5:p3，说不出来，只能一步步走过去 —— 中间每一屏， 正好是手机上根本读不了的那个平铺状态。

![面板一览：一行一个 pane，带 tab 名、路径和 pane id，上面能筛能切排序](https://fsu.creght.com/site/2090728059501023232/1787403917344__shot_panes.png)真实截图。一行一个 pane，上面能筛（tab / 标题 / 路径）、能只看跑着 agent 的。

跳过去就自动全屏

电脑上你习惯分屏 —— 一屏铺四个 pane，扫一眼就知道哪个在干什么。 同一个会话切到手机上，那四格每格只剩巴掌大：字看不清，更别说在里面操作。 所以点面板里那一行，跳过去的同时就把它铺满整屏，不用你再手动放大一次。

等你的排最上面

等你回答 > 跑完了 > 在跑 \> 闲着。红点=等你，绿=跑完了，黄=在跑，闲着不上色。

有变化就弹一张卡

卡上带 agent 说的那段话 —— herdr 的接口里没有这个字段，是读屏抽出来的。点一下跳过去。

角标带未读数

顶栏和软键条那个键上各一个，写着还有几条没看。开一次面板就算看过，刷新页面不丢。还能发系统通知，页面切到后台也收得到。

每行都带 pane id

一个 tab 分成两个 pane 时，两行的标签和路径一模一样，只有 id 不同。

点完看的还是同一个 herdr 终端，键盘那套操作一个字都没变。 这张表只管带你过去，不在上面改东西。

05/ 06能力

## 文件浏览

手机上没有文件浏览器 —— agent 生成的那张图，你打不开。

[在 README 里细看 →](https://github.com/zbysir/herdr-web/blob/master/README.zh-CN.md#文件浏览看-agent-生成的图)

agent 说「图生成在 /tmp/plot-3.png」—— 终端里那行路径就是个链接，点一下图就开了。 想翻文档、翻别的图，点顶栏的 📁。

![文件面板：顶上一个粘绝对路径的框，下面是各 pane 的 cwd、家目录、临时目录](https://fsu.creght.com/site/2090728059501023232/1787403919299__shot_files.png)📁 里的起点是各 pane 的 cwd + 家目录 + 临时目录，一路往上能走到 /。也能直接粘一条路径进去。![文件查看器：顶上是文件名、绝对路径、大小和类型，右边能复制路径、打开所在目录、新标签打开、投给 agent](https://fsu.creght.com/site/2090728059501023232/1787403920083__shot_viewer.png)看图这一屏。右上角能复制路径、开所在目录、新标签打开（那儿能长按存到相册），以及直接投给 agent。

默认没有边界

能打开这页的人已经有一个登录 shell，白名单挡不住他、只会天天挡路（agent 往 /tmp、~/Downloads 写东西是常态）。要边界就配 FILE\_ROOTS。

图直接看，别的只给下载

按魔数认类型，改后缀骗不过去。

SVG 也直接看，但只在 <img> 里

SVG 是能跑脚本的图片。放进 <img> 就落进规范的 secure static mode —— 脚本不跑、外部资源不加载，而且这条不看任何响应头，前面的代理把 CSP 剥了也还在。

绝不以 text/html 吐

同源的 HTML 就是一个能调接口的跳板：agent 写个 html，你点开就把 herdr 交出去了。

看完能投给 agent

把绝对路径插进发件箱，和传图完全同一个模型 —— 让它自己去读那个文件。

06/ 06能力

## 布局

平板卡在中间：屏够大，值得排一套自己的；可它没有键盘。

[在 README 里细看 →](https://github.com/zbysir/herdr-web/blob/master/README.zh-CN.md#底部面板)

为了适配平板，软键条做成能拖的 —— 配系统的悬浮键盘正好， 横屏竖屏都好用。

![herdr-web 的界面：左边是两个 pane 的终端，右下角是缩到半边的底部面板](https://fsu.creght.com/site/2090728059501023232/1787307365277__shot_layout.png)真实截图。整块面板缩到了右半边，左边让给输入法 —— 键那排上边缘三条把手就是拖的地方。

横屏竖屏各存一套

平板横过来是一种用法，竖过来是另一种。两套分开存，调横屏那套不会动到竖屏那套。

平板上配悬浮键盘最顺

把系统键盘捏成悬浮那种（游戏键盘），挪到顺手的一边，中间让给终端。软键条照常在 —— 打字、语音、Ctrl / Esc / 方向键，三样同时在手边。

适配触屏

### 能滚、能点、能拖 pane

|     |     |
| --- | --- |
| 单指滑动 | 按行高换算成滚轮上报发给程序 |
| 单击 | 发一次鼠标点击（点 pane、点 tab 都好使），当场就发、不弹键盘 |
| 长按拖动 pane | 抓住不松手（≈380ms），之后滑动就是拖 —— pane 边框靠这条挪。 |

放在哪儿跑

### 不用域名、不用公网，同一个 Wi-Fi 下就能连

代价只是每台设备第一次点一下「继续访问」。嫌这一下麻烦， 加个域名走 C；出了门也想连，再加个公网入口走 D。 要手机连就必须有 TLS，herdr-web 已内置 ACME 走 DNS-01 认证 —— Cloudflare、AWS Route53、阿里云、腾讯云、华为云、DigitalOcean 六家都能签。 所以 NAT 后面、域名指到内网地址，照样签得出受信任证书。

| 档 | 场景 | 怎么用 | 要准备什么 | 授权方式 |
| --- | --- | --- | --- | --- |
| A | 只本机 | 访问 127.0.0.1 | 不用准备什么 | 配对码（IP 下没有 passkey） |
| B | 局域网 · 用 IP | 访问局域网 IP | 不用准备什么，第一次点一下「继续访问」 | 配对码（IP 下没有 passkey） |
| C | 局域网 · 用域名 | 访问域名 | 一个域名 \+ DNS 的 API token | 第一次配对码，后续 passkey |
| D | 公网 | 在哪儿都访问域名 | 域名 \+ DNS token，外加 Cloudflare Tunnel / Tailscale / frp 之一 | 第一次配对码，后续 passkey |

管理页只绑 127.0.0.1

看证书、点一下续期、出配对码、踢设备，全在一个公网上不存在的口上。

在同一个 Wi-Fi 上就自动走直连

从公网那个地址进来时，网页探一下能不能直接连到这台机器 —— 能就切过去，按键不再绕一趟 VPS。交接走的是单独的一次性短令牌（60 秒、只能在直连口兑），踢掉上级会级联撤销。

断线自动重连

手机切前后台、地铁里断一下，回来自己接上，不用去点那个「连接」。

要从公网连得自己声明

为了安全，默认不开启公网访问，需要显式设置 `HERDR_WEB_EXPOSED=1`。

开始

### 安装并使用

有 node复制

```
npm i -g @bysir/herdr-web
herdr-web
```

只听 127.0.0.1:7788，连上自动敲 herdr。管理页在 7789。

没 node复制

```
curl -fsSL https://raw.githubusercontent.com/zbysir/herdr-web/master/install.sh | sh
```

装到 ~/.local/bin。强制校验 sha256 —— 这东西后面挂着一个登录 shell。

手机连局域网复制

```
HERDR_WEB_HOST=0.0.0.0 herdr-web
```

自动自签证书，终端里画一个二维码给手机扫。

常驻 \+ 自更新复制

```
herdr-web service install
herdr-web update
```

user 级：macOS launchd / Linux systemd。配置是装那一刻从 shell 抄进去的 —— 改完要重新 install（幂等，覆盖 + 重启）。

地址栏里加一段路径就是另一个 herdr session（ `/work`、 `/scratch`）—— 存两个书签就是两套工作现场，关掉浏览器再回来还在。

Windows 没有原生版，在 WSL 里装 —— 浏览器里那个终端要一个真 PTY。全部配置项（只认环境变量）看 [README](https://github.com/zbysir/herdr-web/blob/master/README.zh-CN.md#配置)。

herdr-web给 herdr 用的浏览器终端 + 语音投稿MIT · @bysir/herdr-web [GitHub](https://github.com/zbysir/herdr-web)

> 全站页面清单：[/llms.txt](/llms.txt)
