给 nl2sh 接上一条“猫尾巴”Tailcat:Android 端侧 Agent 终于会自己“打洞”了

简介: nl2sh集成Tailcat,为Android端侧Agent赋予“自动打洞”能力:无需公网IP、不依赖完整组网,仅凭一串加密地址(如`tcXYZ...`),即可临时暴露端口、映射远端服务至`127.0.0.1`、安全传文件——真正实现“即用即走”的跨网调试与协作。

给 nl2sh 接上一条“猫尾巴”Tailcat:Android 端侧 Agent 终于会自己“打洞”了

nl2sh + Tailcat 实战:跨网暴露端口、临时传文件、把远端服务映射成本机 127.0.0.1。
nl2sh-tailcat-wechat-cover.png


有些 Android 调试问题,难点根本不在 Android。

难点在一句很朴素的话:

“你能不能先连到这台设备?”

设备在公司内网、实验室 Wi-Fi、客户现场、路由器后面、电视机后面,甚至压根没有一个你能直接访问的公网 IP。

你明明已经知道:

  • Web 调试页开在 9999
  • 某个本地服务开在 8080
  • ADB TCP 在 5555
  • 日志文件就在 /data/local/tmp/xxx.log

但你人在另一张网。

于是调试流程突然从“查问题”,变成了“网络工程师模拟考试”:

配 VPN?
开端口?
做 NAT?
让对方装 Tailscale?
申请账号?
配 ACL?
SSH 反向隧道?
-L、-R 到底谁在前谁在后?
为什么昨天还能连今天又不行?

一个本来五分钟能定位的问题,先花半小时证明“我们俩确实不在一个局域网”。

现在,nl2sh 新加入了 Tailcat 工具组。

这件事乍看只是“多了几个网络工具”。

实际体验更像是:

原来 nl2sh 只是 Android 设备上的一双手。
现在这双手突然能从 NAT 后面伸出来了。


先说 Tailcat 是什么:可以把它理解成“会打洞的 netcat”

Tailcat 是 Tailscale 开源的一个项目。

它自己的定位很直接:

像 netcat,但跑在 Tailscale 的数据平面上,不依赖 Tailscale 的控制平面。

翻译成人话:

普通 nc 的世界观大概是:

你知道我的 IP
        +
你能访问我的端口
        =
我们可以通信

现实世界通常是:

公司 NAT
家庭路由器
移动网络
防火墙
多层内网
↓
“你知道 IP 也没什么用”

Tailcat 换了一种玩法。

服务端启动后,生成一串类似这样的 Tailcat Address:

tcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...

把这串地址通过微信、IM、工单、邮件,或者别的可信渠道交给另一端。

另一端拿着地址连接。

底层使用的是 Tailscale 数据平面的能力:

Tailcat Address
      │
      ▼
DERP 引导建立连接
      │
      ▼
尝试 NAT Traversal
      │
      ├── 能直连:WireGuard 点对点
      │
      └── 不能直连:DERP 中继

流量本身使用 WireGuard 端到端加密。

重点是,它不要求你先建立一个完整的 Tailscale 网络,也不要求双方先加入同一个 Tailnet。

所以我更喜欢把它理解成:

一根临时的、加密的、会自动找路的网络延长线。

而不是“又一套 VPN”。

Tailcat v0.7.0 对 Android 也更友好了:官方 Linux 二进制可以直接运行在 Android / adb shell / rooted shell 等环境,并补上了 Android DNS、系统 CA 和网络接口探测方面的适配。

这也是它和 nl2sh 组合起来很有意思的地方。


nl2sh 加的不是一个 tailcat 命令,而是一组 Agent 工具

如果只是让大模型执行:

tailcat ...

其实没什么值得单独写一篇文章。

真正有意思的是,nl2sh 没有把 Tailcat 当成“随便执行的 shell 命令”,而是把常用能力拆成了结构化工具。

当前一共 7 个:

工具 用途 风险级别
tailcat_check 检查 Tailcat 是否存在、查看版本 ReadOnly
tailcat_receive_stream 接收一次原始数据流,写入一个新文件 Mutating
tailcat_receive 在指定目录启动文件接收箱 Mutating
tailcat_send_file 把本地文件发送到 Tailcat 地址 Dangerous
tailcat_serve 暴露一个本机 TCP 端口 Dangerous
tailcat_status 查看当前 nl2sh 管理的 Tailcat 监听器 ReadOnly
tailcat_stop 停止当前 Tailcat 监听器 Mutating

这背后其实体现了一个很重要的思路:

Agent 不应该只会“执行命令”,还应该知道自己正在做什么。

比如:

“查版本”和“把 root 控制端口暴露到远端”,显然不是一回事。

如果它们最后都只是:

execute_shell_command(...)

那安全系统看到的只是一串字符。

而结构化工具知道:

这是查版本
这是接收文件
这是向外发送文件
这是开放本地服务

于是 nl2sh 可以在执行前采用不同级别的确认策略。


真实使用:我让它共享 9999,它先把我拦下来了

这次测试里,我先问:

检查 tailcat 是否可用,查看版本。

nl2sh 调用了:

tailcat_check

返回:

binary=/data/local/tmp/tailcat
v0.7.0

到这里平平无奇。

真正有意思的是下一段。

我说:

请帮我共享本地端口;先说明需要我提供哪些具体信息。

它先确认:

tailcat_status

当前没有监听器。

然后问我需要共享哪个端口。

我回复:

9999

按照“工具包装器”的思路,这时候最简单的实现就是:

tailcat_serve(port=9999)

完事。

但 nl2sh 没这么干。

它先查了一遍本机监听:

0.0.0.0:9999   LISTEN   23208/nl2sh

继续检查进程:

/data/local/tmp/nl2sh
UID 0

然后告诉我:

9999 不是普通业务端口。

它正是当前 nl2sh 自己的控制通道,而且以 root 身份运行。
如果共享这个端口,本质上就是把这台设备的 Agent 控制入口暴露出去。

所以它没有直接执行。

而是停下来要求我明确确认。

我回复:

确认暴露 9999

这时候才真正执行:

tailcat_serve

并返回一串 Tailcat Address。

这里我就不把真实地址放出来了。

原因后面讲。


这一小段,比“成功执行 tailcat”重要得多

这正好解释了我为什么越来越喜欢 端侧 Agent + 结构化 Tool 这种组合。

传统脚本的逻辑是:

你让我共享 9999
→ 我共享 9999

更理想的 Agent 应该是:

你让我共享 9999
        │
        ▼
先确认 9999 是什么
        │
        ▼
发现这是 root nl2sh 控制通道
        │
        ▼
风险语义发生变化
        │
        ▼
要求用户再次明确确认
        │
        ▼
才执行

注意,这不是“AI 替你做决定”。

恰恰相反。

它是先把原本隐藏在命令背后的事实翻出来:

你共享的不是一个数字 9999,你共享的是 9999 后面的那个东西。

然后把决定重新交给你。

这才是 Agent 比一堆 shell alias 更有价值的地方。


对端怎么用?一条命令把远端 9999 变成本机 127.0.0.1:9999

共享完成后,我又问:

我想让对端访问 127.0.0.1:9999,就相当于访问这台设备的 9999,怎么做?

nl2sh 没有凭印象回答。

它现场查看了设备上 Tailcat v0.7.0 的帮助信息,确认 forward 的语义后,给出了:

tailcat forward <tc-addr> 9999

Tailcat 的 forward 默认绑定:

127.0.0.1

所以最终链路就是:

对端程序
    │
    ▼
127.0.0.1:9999
    │
    ▼
tailcat forward
    │
    ▼
WireGuard / NAT Traversal / DERP
    │
    ▼
Android 设备
    │
    ▼
localhost:9999

于是对端的软件根本不需要知道:

  • Android 设备真实 IP 是多少
  • 两边是不是同一个局域网
  • 中间有几层 NAT
  • 服务到底在哪个 Wi-Fi
  • 有没有公网 IPv4
  • 有没有 IPv6

对它来说,服务就在:

127.0.0.1:9999

这就很舒服了。

如果本地 9999 已经被占用:

tailcat forward <tc-addr> 18099:9999

那么:

对端 127.0.0.1:18099
            ↓
Android 设备 localhost:9999

你甚至可以把一个“远在天边”的 Android 本地调试页,伪装成电脑上的本地服务。


这对 nl2sh 意味着什么?

以前 nl2sh 解决的是:

我已经在 Android 设备上了,接下来怎么查?

例如:

  • 看应用进程
  • 查前台 Activity
  • 查网络
  • 看 logcat
  • 分析 ANR
  • 看 tombstone
  • 检查 APK
  • 分析音频
  • 看 UI 树
  • 截图
  • 执行受控 shell

加入 Tailcat 以后,它开始能解决另一类问题:

设备上的东西,怎么安全、临时地伸到另一台机器?

两者一结合,使用方式一下就多了。


场景一:把 Android 上的 Web 调试页临时给远端访问

比如某个设备上跑着:

127.0.0.1:8080

以前常见做法:

adb forward
ssh -L
ssh -R
VPN
frp
公网反代
路由器端口映射

现在可以直接告诉 nl2sh:

帮我共享本机 8080 端口,先检查监听进程和风险,再执行。

它完成检查后启动 tailcat_serve。

对端:

tailcat forward <tc-addr> 8080

然后浏览器打开:

http://127.0.0.1:8080

结束后再告诉 nl2sh:

停止 Tailcat 共享。

对应:

tailcat_stop

整个过程非常适合:

  • 临时调试
  • 客户现场问题
  • 测试机 Web 页面
  • 内部诊断接口
  • 一次性的远程协作

场景二:把文件直接塞进 Android,不搭 HTTP Server

假设电脑上有:

bugreport.zip

想传进 Android。

你可以在 nl2sh 里说:

用 Tailcat 接收一次原始数据流,保存到 /sdcard/Download/bugreport.zip。

nl2sh 会调用:

tailcat_receive_stream

它只允许写入一个不存在的新文件。

然后返回一个 Tailcat Address。

电脑上:

cat bugreport.zip | tailcat <tc-addr>

文件就过去了。

不需要:

python -m http.server

不需要临时找一台 HTTP Server。

不需要问:

“你这台设备能访问我电脑的 192.168.x.x 吗?”

更不需要经历:

“等等,你的 192.168.x.x 是哪个网卡?”


场景三:做一个临时“文件投递箱”

如果不是只传一个原始流,而是希望连续投递文件,可以让 nl2sh 在一个已经存在的目录启动接收箱:

在 /sdcard/Download/inbox 启动 Tailcat 文件接收。

对应:

tailcat_receive

另一端使用:

tailcat cp report.pdf <tc-addr>:

这种模式底层走 Tailcat 的文件服务。

nl2sh 当前的 copy 发送模式依赖发送端系统存在 scp。

这点对 Android 尤其要注意:

很多 adb shell 环境并没有 scp。

因此在 Android 设备上,单文件临时传输时我反而更推荐 stream 模式。

少一层依赖,简单粗暴。

有时候,简单粗暴就是工程美学。


场景四:把 Android 本地服务“搬”到电脑 localhost

这是我认为日常最实用的一种。

假设电视、车机、盒子、手机里跑了一个服务:

localhost:9999

你在设备上:

共享 9999。

nl2sh 返回:

<tc-addr>

电脑:

tailcat forward <tc-addr> 9999

以后你电脑上的任何软件都继续访问:

127.0.0.1:9999

甚至原来的工具都不用改配置。

例如:

浏览器
curl
Postman
调试器
自研客户端

它们根本不知道中间多了一条 WireGuard 隧道。


场景五:远程 ADB——能做,但先想清楚你在暴露什么

Tailcat 官方自己的例子里就有 ADB:

服务端把 5555 暴露出来后,对端:

tailcat forward <tc-addr> 5555
adb connect 127.0.0.1:5555

从网络技术角度完全成立。

但放到 nl2sh 的场景里,我反而建议更谨慎。

因为:

“暴露 5555”

并不是一个单纯的网络动作。

它实际意味着:

暴露 Android ADB 能力

是否安全,取决于:

  • ADB 当前认证状态
  • 设备构建类型
  • adbd 权限
  • 远端是谁
  • 这条地址是否泄漏
  • 是否只在临时调试时使用

这和前面 9999 的例子其实是同一件事。

不要只看端口号,要看端口后面是什么。


Tailcat Address 不是普通 URL,最好把它当“临时钥匙”

Tailcat Address 长得很像一串“奇怪 URL”。

但更合理的理解是:

它是连接信息,也是能力凭据的一部分。

Tailcat 当前默认会在地址中带入 WireGuard 预共享密钥信息。

所以文章截图、日志、群聊、Issue 里不要随手贴完整地址。

这次真实会话里生成的地址,我在本文全部写成:

<tc-addr>

如果把它原样放进公众号文章,然后再配一句:

“大家可以试试。”

那文章可能会突然从技术分享升级成线上事故复盘。


nl2sh 为 Tailcat 做了哪些“看不见,但很重要”的限制?

这部分我觉得比命令列表更值得看。


1. Tailcat 工具组默认关闭

最新实现中:

[tool_groups]
tailcat = false

默认不会把 Tailcat 工具直接暴露给模型。

在 Web 的“能做什么”页面里,可以按组打开,也可以只启用某几个工具。

例如你只想:

检查
状态
停止

完全没必要把“发送文件”和“开放端口”一起打开。

也可以在配置里做单工具覆盖:

[tool_overrides]
tailcat_check = true
tailcat_status = true
tailcat_serve = false

这是我很认可的一点:

能力不是“代码里有了,模型就全能用”。

工具存在和工具授权,是两回事。


2. 发送文件、暴露端口都属于高风险操作

nl2sh 对 Tailcat 做了单独的风险分类。

类似:

version / help / ping / ls
→ ReadOnly

recv
→ Mutating

发送数据 / 开放端口
→ Dangerous

因此即使模型退回去直接调用 shell:

tailcat ...

也不会因为“没有走结构化工具”就绕过风险等级。

换句话说:

关掉 Tailcat Tool,不等于 shell 里的 Tailcat 突然变成无风险命令。

这是正确的安全边界。


3. 接收文件不会随便覆盖已有文件

tailcat_receive_stream 要求目标:

  • 有合法父目录
  • 是一个新文件
  • 文件已存在就拒绝

也就是不会因为一句:

“接到 /data/important.conf”

直接把已有文件冲掉。


4. nl2sh 进程退出,监听器跟着结束

Tailcat 监听不是“启动以后就扔在后台不管”。

nl2sh 会管理当前进程启动的 listener。

实现里使用临时 key:

--key=new

并且在 Linux / Android 上给子进程设置父进程退出信号。

效果可以简单理解为:

nl2sh 活着
   │
   └── Tailcat listener 活着

nl2sh 退出
   │
   └── listener 一起结束

这非常适合临时调试。

否则最怕的情况就是:

三个月后有人问:
“这台电视上为什么有个不知道谁开的隧道?”

然后所有人开始考古。


5. 当前进程只管理一个 Tailcat listener

如果已经有一个接收器或端口共享在跑,再启动新的,会要求先停止旧的。

这看起来“不够强大”。

但对 Agent 来说,我觉得反而是好事。

因为:

一个任务
一个明确的地址
一个生命周期
一个停止动作

比同时在后台挂七八个隧道,更容易让人理解当前到底发生了什么。

Agent 系统里,可理解性也是安全能力的一部分。


为什么没有把 Tailcat 所有能力全做成 Tool?

Tailcat 本身的能力远不止这些。

它还支持:

  • ssh
  • socks
  • exit-node
  • 多端口
  • 文件服务
  • ping
  • forward
  • 自定义 DERP
  • 更复杂的端口映射

但 nl2sh 当前结构化工具故意只选了几种非常明确的场景:

检查
收文件
发文件
共享一个 localhost TCP 端口
看状态
停止

这其实是一个很好的 Agent Tool 设计原则:

不是底层 CLI 有多少参数,就给模型暴露多少参数。

CLI 是给人用的。

Tool 是给 Agent 用的。

Agent Tool 更应该:

  • 参数少
  • 语义窄
  • 风险明确
  • 容易预览
  • 容易确认
  • 容易回收

真需要 Tailcat 高级功能时,nl2sh 仍然可以通过 shell 使用。

只是这时候会继续经过自己的命令安全分类。


怎么开启?

首先,设备上需要有可信的 Tailcat 二进制。

Android 默认路径:

/data/local/tmp/tailcat

配置:

tailcat_binary_path = "/data/local/tmp/tailcat"

然后开启工具组:

[tool_groups]
tailcat = true

也可以直接在 Web 的“能做什么”页面打开 Tailcat。

建议第一句话先问:

检查 Tailcat 是否可用并告诉我版本。

理想结果:

binary=/data/local/tmp/tailcat
v0.7.0

然后再开始真正的网络动作。


我日常会怎么用?

如果是我,我不会记七个 Tool 名字。

我会直接跟 nl2sh 说人话。

临时开放 Web 页面

检查 8080 当前是谁在监听。如果是我预期的服务,就通过 Tailcat 暴露,执行前把风险告诉我。

给 Android 传一个文件

用 Tailcat 接收一次原始文件流,保存到 /sdcard/Download/test.apk,不要覆盖已有文件。

从 Android 发日志

把 /data/local/tmp/error.log 发到这个 Tailcat 地址,优先用不依赖 scp 的方式。

查看当前有没有忘记关的隧道

看一下 Tailcat 当前状态。

用完关掉

停止 Tailcat listener。

这就是 Agent 的意义:

不是把:

tailcat serve
tailcat forward
tailcat recv
tailcat cp

全背下来。

而是告诉它:

我想实现什么。

它再根据设备当前状态选择工具。


nl2sh + Tailcat 最适合的,并不是“长期组网”

如果你有十几台机器,需要:

  • 长期在线
  • 稳定设备身份
  • ACL
  • DNS
  • 子网路由
  • 设备管理
  • 长期访问控制

那完整的 Tailscale、WireGuard、企业 VPN 等方案更适合。

Tailcat 的优势反而是:

临时、点对点、低配置成本。

例如:

“这个问题就今天查一下”
“我只想临时看一个 8080”
“给我传一个 200MB 的 bugreport”
“让远端工具暂时认为设备服务就在 localhost”

这种场景里,如果先搭一套完整网络,多少有点像:

为了借邻居一把螺丝刀,先成立一个物业公司。

Tailcat 就没那么大阵仗。


更重要的是:端侧 Agent 开始获得“网络行动能力”

我之前一直把 nl2sh 定义成:

Android 端侧运行的 Agent Harness。

它最大的特点不是聊天。

而是模型就在设备旁边:

LLM
 │
 ▼
nl2sh Agent
 │
 ├── Android 状态
 ├── shell
 ├── 文件
 ├── UI
 ├── APK
 ├── 音频
 └── 网络诊断

Tailcat 加进来以后,这张图变成:

                    ┌───────────────┐
                    │   远端电脑     │
                    │ browser/curl  │
                    │ adb/debugger  │
                    └───────┬───────┘
                            │
                      Tailcat Address
                            │
                 WireGuard / DERP / P2P
                            │
┌───────────────────────────▼───────────────────────────┐
│                    Android Device                     │
│                                                       │
│   LLM → nl2sh Agent → Tool → 本地文件 / 端口 / 系统能力 │
│                                                       │
└───────────────────────────────────────────────────────┘

以前 Agent 的“手脚”只在本机。

现在多了一条可以按需伸出去的“猫尾巴”。

这才是这次更新真正让我觉得有意思的地方。


最后

nl2sh 还在持续迭代。

项目地址:

如果你平时也经常在 Android、电视、车机、盒子、IoT 设备上:

  • adb shell
  • 查日志
  • 查网络
  • 看进程
  • 分析系统状态
  • 临时远程调试

可以试试 nl2sh。

觉得项目有点意思,欢迎点个 Star。

如果你也在折腾端侧 Agent、Android 系统调试、远程设备诊断,欢迎关注公众号,也欢迎私信交流你实际遇到的奇怪问题。

毕竟工程师最不缺的,就是:

“理论上应该可以,实际上就是不通。”

而这类问题,刚好最适合拿来喂 Agent。


参考资料

目录
相关文章
|
11天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
7853 14
|
9天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
1710 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
10天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
1542 11
|
6天前
|
人工智能 JavaScript 芯片
DeepSeek 官方偷偷上传 Harness 桌面端安装包,我已经用上了。。附最新下载地址
DeepSeek Harness 官方的桌面端安装包被网友扒出来了,2 分钟讲明白如何使用,体验如何,适合作为 AI 编程工具么?附最新 Windows 和 Mac 双端的下载地址
1486 1
|
8天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)
|
23天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
3755 10
|
18天前
|
缓存 IDE Java
【保姆级】Android Studio下载、安装和汉化教程(2026最新)
Android Studio 是 Google 官方推出的免费 Android 应用开发集成环境,基于 IntelliJ IDEA,内置模拟器、调试器、性能分析及 Compose 界面工具,功能全面,文档丰富,是安卓开发首选工具。(239字)
1910 1