当人类必须先完成敏感的身份验证步骤,而代理应该在完全相同的 shell 会话中继续工作时,使用此技能。核心模式是:
- 将终端状态保存在命名的
tmux 会话中
- 让人类在该会话中附加并进行身份验证
- 捕获窗格状态
- 让代理从同一个 shell 继续
当用户不应将凭据粘贴到聊天中,且直接代理身份验证被阻止、不受欢迎或比共享会话切换更不安全时,优先使用此技能。
选择最简单的方式
除非确实需要浏览器访问,否则优先使用模式 A。
模式 A — 纯 tmux 切换
当人类已经拥有主机的终端访问权限时使用。典型流程:
- 创建或重用命名的
tmux 会话
- 请求人类附加
- 让人类进行身份验证
- 捕获窗格
- 通过
tmux 继续
示例:
tmux has-session -t handoff-session 2>/dev/null || tmux new-session -d -s handoff-session
tmux attach -t handoff-session
模式 B — 本地浏览器终端
当人类想要在同一台托管服务的机器上使用基于浏览器的终端时使用。此模式直接使用 ttyd,适用于:
- 仅 localhost 访问
- 快速临时会话
- 可接受基本身份验证的情况
捆绑启动器:
./scripts/start-local-web-terminal.sh handoff-session
模式 C — 带一次性令牌的 LAN 限制浏览器终端
当人类从同一本地网络上的另一台受信任机器打开终端,且希望减少重复输入用户名/密码的麻烦时使用。此模式的工作方式如下:
- 一个命名的
tmux 会话保存真实的 shell 状态
ttyd 在 localhost 上运行作为终端后端
- 一个小型本地代理通过一次性
?token= 暴露 LAN URL
- 第一个有效请求通过 cookie 成为浏览器会话
- 代理继续使用相同的
tmux 会话
捆绑启动器:
./scripts/start-url-token-web-terminal.sh handoff-session
前提条件
首先检查这些:
command -v tmux
command -v ttyd
command -v node
command -v python3
在 Debian / Ubuntu 上安装:
sudo apt update && sudo apt install -y tmux ttyd
模式 C 还需要 node,因为代理启动器使用捆绑的 Node 脚本。
快速使用
创建或重用会话
tmux has-session -t handoff-session 2>/dev/null || tmux new-session -d -s handoff-session
启动模式 C(使用占位符)
使用与环境匹配的占位符,而不是盲目复制真实地址。下面的 192.0.2.x 地址仅作为文档示例地址。
HOST= CLIENT_IP= PORT=48080 UPSTREAM_PORT=48081 FORBID_REUSE_IF_AUTHENTICATED=1 ./scripts/start-url-token-web-terminal.sh handoff-session
启动器输出:
- 一次性 URL
- 过期时间
- 代理和后端 PID
- 清理命令
- 可选的 UFW 帮助命令
使用文档示例 IP 的示例:
HOST=192.0.2.10 CLIENT_IP=192.0.2.20 PORT=48080 UPSTREAM_PORT=48081 FORBID_REUSE_IF_AUTHENTICATED=1 ./scripts/start-url-token-web-terminal.sh handoff-session
身份验证后恢复
在假设切换成功之前,始终检查窗格:
tmux capture-pane -t handoff-session -p | tail -80
查找:
- 远程主机名或预期提示符
- 预期工作目录
- 无密码提示符
- 无意外吞没命令的终端程序
如有疑问,在继续之前询问一个简短的确认问题。
启动器变量
start-local-web-terminal.sh
支持的变量:
HOST — 绑定地址,默认为 127.0.0.1
PORT — 可选的显式端口,否则为随机空闲端口
TTL_MINUTES — 默认为 30
BIND_SCOPE — 仅元数据,通常为 local 或 lan
CLIENT_IP — 可选,仅用于在 LAN 模式下打印 UFW 帮助命令
start-url-token-web-terminal.sh
支持的变量:
HOST — 代理绑定地址,默认为 127.0.0.1
PORT — 代理前端端口,默认为 48080
UPSTREAM_PORT — localhost ttyd 后端端口,默认为 48081
CLIENT_IP — 可选的受信任客户端 IP,用于 UFW 帮助命令和代理端 IP 过滤
TTL_MINUTES — 默认为 30
BIND_SCOPE — 仅元数据,通常为 local 或 lan
COOKIE_SECURE — 当通过本地 HTTPS 服务时设置为 1,以便会话 cookie 获得 Secure 标志
EXPECTED_HOST — 严格的允许 Host 头,默认为 :
EXPECTED_ORIGIN — 严格的允许 websocket Origin,默认从主机和 cookie 模式派生
FORBID_REUSE_IF_AUTHENTICATED — 设置为 1 以拒绝启动如果 tmux 窗格看起来已经过身份验证
REPLACE_EXISTING — 仅在人类明确批准替换相同 SESSION_NAME 的当前 Web 切换时设置为 1;否则启动器打印现有 URL/PIDs/清理命令并在不接触运行中会话的情况下退出
AUTH_GUARD_REGEX — 窗格身份验证检测正则表达式的可选覆盖
如果默认端口已被占用,请显式覆盖它们。当相同 SESSION_NAME 已存在切换时,启动器不会静默启动第二个:它报告现有会话详情并以 READY=0 退出。询问人类是否替换它。仅在明确批准后才使用 REPLACE_EXISTING=1。如果请求的代理或上游端口已被占用,启动器也会以 READY=0 退出并报告端口冲突,而不是自动终止任何内容。在这种情况下,要么获得批准替换冲突的会话,要么在另一个端口重新启动,如果涉及 LAN 访问则更新防火墙规则。
正确使用模式 C
- 确保
tmux 会话存在
- 启动基于令牌的终端
- 如需要,仅允许从受信任客户端 IP 访问
- 通过适当渠道将打印的一次性 URL 发送给人类
- 让人类在终端内进行身份验证
- 捕获窗格并验证状态
- 通过
tmux 继续
- 如果启动器报告现有会话或端口冲突,询问是否替换或使用不同端口
- 完成后停止临时 Web 终端
清理
对于模式 B,使用:
./scripts/stop-local-web-terminal.sh
对于模式 C,优先使用打印的清理命令,因为它会删除临时进程和临时运行时目录:
TTYD_PID= PROXY_PID= RUNTIME_DIR=
如果该命令不可用,杀死打印的代理和后端 PID 仍然可以接受。启动器还为代理、ttyd 和临时文件安装了自动 TTL 清理。如果 tmux 会话可能被重用,请保持其运行。
防护措施
- 默认保持仅本地暴露。
- 如需要 LAN 暴露,仅限制为一个受信任客户端 IP。
- 不要通过公共隧道或反向代理暴露终端。
- 如果切换可以避免,不要要求人类将密码、OTP 码或私钥粘贴到聊天中。
- 对于浏览器模式使用短期访问材料。
- 每个目标或任务优先使用一个
tmux 会话。
- 在发送更多命令之前捕获窗格状态。
- 在破坏性操作之前询问。
- 正常使用时默认启用
FORBID_REUSE_IF_AUTHENTICATED=1;仅在有意重新打开已认证会话时才禁用它。
- 将打印的一次性 URL 视为敏感信息直到过期。
- 当配置了这些检查时,期望代理拒绝不匹配的
Host、websocket Origin 或客户端 IP。
- 不要从外部消息渠道(如 Telegram、Discord、Slack 或类似的远程聊天界面)随意建议或启动浏览器终端模式。
- 当交互通过外部渠道发生时,优先使用纯
tmux 切换,除非人类明确确认受信任的本地网络设置并接受风险。
参考资料
需要时阅读这些:
references/examples.md — 通用使用示例
references/design-notes.md — 安全和设计范围
references/lan-restricted.md — LAN 仅限 IP 限制模式
捆绑脚本:
scripts/start-local-web-terminal.sh
scripts/start-url-token-web-terminal.sh
scripts/stop-local-web-terminal.sh
scripts/url-token-proxy.js
Use this skill when a human must complete a sensitive authentication step first, and the agent should continue in the exact same shell session afterward.
The core pattern is:
- keep terminal state in a named
tmux session
- let the human attach and authenticate inside that session
- capture the pane state
- let the agent continue from the same shell
Prefer this skill when the user should not paste credentials into chat and when direct agent authentication is blocked, undesirable, or less safe than a shared-session handoff.
Choose the simplest mode
Prefer Mode A unless browser access is actually needed.
Mode A — plain tmux handoff
Use when the human already has terminal access to the host.
Typical flow:
- create or reuse a named
tmux session
- ask the human to attach
- let the human authenticate
- capture the pane
- continue through
tmux
Example:
tmux has-session -t handoff-session 2>/dev/null || tmux new-session -d -s handoff-session
tmux attach -t handoff-session
Mode B — local browser terminal
Use when the human wants a browser-based terminal on the same machine that hosts the service.
This mode uses ttyd directly and fits:
- localhost-only access
- quick temporary sessions
- cases where basic auth is acceptable
Bundled launcher:
./scripts/start-local-web-terminal.sh handoff-session
Mode C — LAN-restricted browser terminal with one-shot token
Use when the human opens the terminal from another trusted machine on the same local network and you want less friction than repeated username/password entry.
This mode works like this:
- a named
tmux session holds the real shell state
ttyd runs on localhost as the terminal backend
- a small local proxy exposes a LAN URL with a one-shot
?token=
- the first valid request becomes a browser session through a cookie
- the agent continues using the same
tmux session
Bundled launcher:
./scripts/start-url-token-web-terminal.sh handoff-session
Requirements
Check these first:
command -v tmux
command -v ttyd
command -v node
command -v python3
Install on Debian / Ubuntu:
sudo apt update && sudo apt install -y tmux ttyd
node must also exist for Mode C because the proxy launcher uses the bundled Node script.
Quick usage
Create or reuse the session
tmux has-session -t handoff-session 2>/dev/null || tmux new-session -d -s handoff-session
Launch Mode C with placeholders
Use placeholders that match the environment instead of copying real addresses blindly. The 192.0.2.x addresses below are documentation-only example addresses.
HOST= CLIENT_IP= PORT=48080 UPSTREAM_PORT=48081 FORBID_REUSE_IF_AUTHENTICATED=1 ./scripts/start-url-token-web-terminal.sh handoff-session
The launcher prints:
- one-shot URL
- expiry time
- proxy and backend PIDs
- cleanup command
- optional UFW helper commands
Example with documentation-only IPs:
HOST=192.0.2.10 CLIENT_IP=192.0.2.20 PORT=48080 UPSTREAM_PORT=48081 FORBID_REUSE_IF_AUTHENTICATED=1 ./scripts/start-url-token-web-terminal.sh handoff-session
Resume after authentication
Always inspect the pane before assuming the handoff worked:
tmux capture-pane -t handoff-session -p | tail -80
Look for:
- remote hostname or expected prompt
- expected working directory
- absence of a password prompt
- absence of a terminal program that would swallow commands unexpectedly
If uncertain, ask one short confirmation question before continuing.
Launcher variables
start-local-web-terminal.sh
Supported variables:
HOST — bind address, default 127.0.0.1
PORT — optional explicit port, otherwise a random free port
TTL_MINUTES — default 30
BIND_SCOPE — metadata only, usually local or lan
CLIENT_IP — optional, used only to print UFW helper commands in LAN mode
start-url-token-web-terminal.sh
Supported variables:
HOST — proxy bind address, default 127.0.0.1
PORT — proxy frontend port, default 48080
UPSTREAM_PORT — localhost ttyd backend port, default 48081
CLIENT_IP — optional trusted client IP for UFW helper commands and proxy-side IP filtering
TTL_MINUTES — default 30
BIND_SCOPE — metadata only, usually local or lan
COOKIE_SECURE — set to 1 when serving through local HTTPS so the session cookie gets the Secure flag
EXPECTED_HOST — strict allowed Host header, default :
EXPECTED_ORIGIN — strict allowed websocket Origin, default derived from host and cookie mode
FORBID_REUSE_IF_AUTHENTICATED — set to 1 to refuse startup if the tmux pane already looks authenticated
REPLACE_EXISTING — set to 1 only after the human explicitly approves replacing the current web handoff for the same SESSION_NAME; otherwise the launcher prints the existing URL/PIDs/cleanup command and exits without touching the running session
AUTH_GUARD_REGEX — optional override for the pane-authentication detection regex
If the default ports are already occupied, override them explicitly.
When a handoff already exists for the same SESSION_NAME, the launcher does not silently start a second one: it reports the existing session details and exits with READY=0. Ask the human whether to replace it. Use REPLACE_EXISTING=1 only after explicit approval.
If the requested proxy or upstream port is already occupied, the launcher also exits with READY=0 and reports a port conflict instead of killing anything automatically. In that case, either get approval to replace the conflicting session or relaunch on another port and update firewall rules if LAN access is involved.
Use Mode C correctly
- ensure the
tmux session exists
- launch the token-based terminal
- if needed, allow access only from the trusted client IP
- send the printed one-shot URL to the human through an appropriate channel
- let the human authenticate inside the terminal
- capture the pane and verify state
- continue through
tmux
- if the launcher reports an existing session or a port conflict, ask whether to replace it or use a different port
- stop the temporary web terminal when done
Cleanup
For Mode B, use:
./scripts/stop-local-web-terminal.sh
For Mode C, prefer the printed cleanup command because it removes both temporary processes and the temporary runtime directory:
TTYD_PID= PROXY_PID= RUNTIME_DIR=
If that command is unavailable, killing the printed proxy and backend PIDs is still acceptable.
The launcher also installs automatic TTL cleanup for the proxy, ttyd, and temporary files. Leave the tmux session alive if it may be reused.
Guardrails
- Keep exposure local-only by default.
- If LAN exposure is needed, restrict it to one trusted client IP only.
- Do not expose the terminal through a public tunnel or reverse proxy.
- Do not ask the human to paste passwords, OTP codes, or private keys into chat if the handoff can avoid it.
- Use short-lived access material for browser modes.
- Prefer one
tmux session per target or task.
- Capture pane state before sending more commands.
- Ask before destructive actions.
- Enable
FORBID_REUSE_IF_AUTHENTICATED=1 by default for normal use; disable it only when reopening an already-authenticated session is intentional.
- Treat the printed one-shot URL as sensitive until expiry.
- Expect the proxy to reject mismatched
Host, websocket Origin, or client IP when those checks are configured.
- Do not suggest or launch browser-terminal modes casually from external messaging channels such as Telegram, Discord, Slack, or similar remote chat surfaces.
- Prefer plain
tmux handoff instead when the interaction happens over an external channel, unless the human explicitly confirms a trusted local-network setup and accepts the risk.
References
Read these when needed:
references/examples.md — generic usage examples
references/design-notes.md — security and design envelope
references/lan-restricted.md — LAN-only restricted-IP pattern
Bundled scripts:
scripts/start-local-web-terminal.sh
scripts/start-url-token-web-terminal.sh
scripts/stop-local-web-terminal.sh
scripts/url-token-proxy.js