# Local Proxy

> 让 AI 在用户没有开系统 VPN 的情况下访问外网。为单条命令临时注入本地代理（默认 127.0.0.1:7891），用于 git push/pull GitHub、npm/pip 安装、调用境外 API 等。全程不写系统代理注册表、不建虚拟网卡、不改路由表或 DNS，只在被执行的子进程内生效。当任务涉及 GitHub、境外网站、被墙服务，或出现连接超时、DNS 污染、SSL 握手失败时使用。

- Skill: `n0-1-c/local-proxy` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add n0-1-c/local-proxy`
- Raw SKILL.md: https://api.skillmd.com/api/skills/n0-1-c/local-proxy/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: N0-1-C (https://skillmd.com/u/n0-1-c)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/n0-1-c/local-proxy

---


# local-proxy

## 路径约定（先看这里）

下文命令里的 `<skill目录>` 指**本技能所在目录**，也就是本文件所在的那个目录：

- AI 调用时：直接用技能元信息里给出的 base directory，不要猜、不要沿用别人机器上的路径。
- 人工使用时：替换成你自己的实际路径 —— 用户级 `~/.workbuddy/skills/local-proxy`，项目级 `<项目>/.workbuddy/skills/local-proxy`。
- `<node24>` 指本机 Node ≥ 24 的 `node.exe` 绝对路径（只有「Node 脚本里要用 `fetch`」时才需要；其它场景用当前 `node` 即可）。

运行期数据（订阅地址、`settings.json`、生成的配置、日志）固定在 `~/.workbuddy/local-proxy/`，
**与技能目录装在哪无关** —— 所以技能目录随便挪，不用重新配置。

## 解决什么问题

AI 需要访问外网（最典型的是 `git push` 到 GitHub），但用户系统上并没有开 VPN。
本 skill 在**同一次工具调用内**完成：启动本地代理内核 → 把代理注入这条命令的环境变量 → 执行 → 关闭内核。
不碰系统代理开关、不建虚拟网卡、不改路由表和 DNS。

## 用之前，只需要准备一样东西：订阅地址

这是**唯一的必填项**。没有它 skill 跑不起来；除此之外所有东西都已自带。

1. 打开你机场（VPN 服务商）的官网 → 用户中心 → 找「一键订阅」或「Clash 订阅」→ 复制链接
2. 把链接**单独一行**粘贴进 `~/.workbuddy/local-proxy/subscription.txt`（不要加引号、不要有空格）
3. 跑一次让配置生效：

```bash
node "<skill目录>/scripts/proxy.mjs" refresh
```

看到 `config written` 加一行套餐信息（已用流量 / 到期日）就成功了。

> 订阅地址等同于你的账号密码：不要发给别人，不要贴进聊天，不要提交进 git 仓库。

如果跳过这步直接跑 `run`，skill 会停下来并打印上面这三步 —— 它不会瞎猜，也不会拿别的代理顶替。

## 铁律（不得违反）

1. **绝不用 TUN 模式**，**绝不写系统代理**（注册表 `ProxyEnable`），**绝不改 DNS 或路由表**。
   只用 `mixed-port` 端口模式 + 环境变量注入。TUN 才会动系统网络，端口模式不会。
2. 代理只监听 `127.0.0.1`。订阅原文里写的是 `allow-lan: true` + `bind-address: '*'`（等于把代理暴露给整个局域网），
   生成配置时会强制覆盖为 `false` / `127.0.0.1`，不要绕过这一步去手工改配置文件。
3. 订阅地址等同于账号密码，**不得**打印到对话、写入日志或提交进 git 仓库。
   它保存在 `~/.workbuddy/local-proxy/subscription.txt`。

## 环境要求

**必需**（缺任何一个都跑不起来）

| 项 | 要求 | 说明 |
|---|---|---|
| Node.js | **≥ 18** | 用来跑 `proxy.mjs` / `tunnel.mjs`。**不需要任何 npm 依赖**，全用 Node 内置模块 |
| 操作系统 | **Windows x64** | 内置的 `mihomo.exe` 是 Windows amd64 二进制 |
| Clash 格式订阅 | 一个订阅地址 | 写进 `~/.workbuddy/local-proxy/subscription.txt` |

**可选**（只影响对应的那类操作，缺了不影响别的）

| 项 | 用在哪 |
|---|---|
| `git` | git push / pull / clone —— 最典型的用途。**不在 PATH 上时就写全路径**（便携版 git 很常见） |
| `curl` | 用 curl 下载或调 API（Windows 10 1803+ 自带） |
| `python` | 用 Python 脚本联网 |
| `ssh` 客户端 | 只有 `run --ssh` 需要（OpenSSH for Windows） |
| Node ≥ 24 | 只有"Node 脚本里用 `fetch`"才需要；Node 22 可用于其他所有场景 |

**不需要**：管理员权限、不用装 VPN 客户端、不用改任何系统设置、不用另外下载内核或 GeoIP 库（都已随 skill 自带）。

一条命令确认全部依赖：

```bash
node "<skill目录>/scripts/proxy.mjs" doctor
```

输出分 `host` / `skill` 两段：`[!!]` 是必须修的，`[--]` 是可选项缺失（正常）。

## 主用法

```bash
node "<skill目录>/scripts/proxy.mjs" <命令> [参数]
```

需要联网的命令一律套在 `run` 里：

```bash
# 单条命令
node "<skill目录>/scripts/proxy.mjs" run "git push origin main"

# 多条命令串起来（推荐，一次调用只启动一次内核）
node "<skill目录>/scripts/proxy.mjs" run "git add -A && git commit -m \"update\" && git push"

# ssh:// 远程的仓库要加 --ssh
node "<skill目录>/scripts/proxy.mjs" run --ssh "git push origin main"
```

## 命令表

| 命令 | 用途 |
|---|---|
| `run "<命令>"` | **主入口**。自动启内核 → 注入代理 → 执行 → 关内核，退出码原样透传 |
| `run --keep "<命令>"` | 同上，但执行完不关内核（用户自己在终端里连续操作时有用） |
| `run --ssh "<命令>"` | 额外为 git 的 ssh:// 远程开隧道（见下） |
| `status` | 运行状态、套餐额度、当前节点、出口 IP |
| `nodes` | 列出所有代理组和节点 |
| `pick <节点名>\|auto` | 切换节点，支持子串模糊匹配（如 `pick 日本1`） |
| `refresh` | 重新拉订阅、重建配置并重载 |
| `up` / `down` | 手工启停 |
| `env` | 打印环境变量（人工排错用） |
| `doctor` | 自检：内核/geo 库/订阅/端口/配置是否安全 |

## 关键限制（务必先读，否则会误判）

### 1. 必须用 `run`，不能指望 `up` 之后的进程还活着

沙箱在**每次工具调用结束时回收整个进程树**，所以上一条调用里 `up` 起来的内核，下一条调用就没了
（`status` 会显示 `DOWN` + `pid ... (stale)`）。
因此启动与执行必须落在同一次调用内 —— 这正是 `run` 做的事。

### 2. 各类客户端是否认 `HTTP_PROXY` 环境变量（实测）

| 客户端 | 走代理 | 说明 |
|---|---|---|
| `git`（https 远程） | ✅ | 凭据已存好时直接可用；没存过会等登录，见 §4 |
| `git`（ssh 远程） | 需 `--ssh` | 见下一节 |
| `curl` | ✅ | |
| Python `requests` / `urllib` | ✅ | 默认 `trust_env=True` |
| **Node 内置 `fetch`（v22）** | ❌ | undici 不读 `*_PROXY`，会直连并超时 |
| **Node 内置 `fetch`（v24）** | ✅ | 需 `NODE_USE_ENV_PROXY=1`，`run` 已自动带上 |
| `npm` / `pip` | ✅ | |
| 宿主的联网搜索、网页抓取工具 | ❌ | 在宿主侧执行，**不经过本地代理** |

Node 写脚本要用 `fetch` 时，改走 v24：

```bash
node "<skill目录>/scripts/proxy.mjs" run "\"<node24>\" your-script.mjs"
```

（`<node24>` = 本机 Node ≥ 24 的 `node.exe` 绝对路径，见开头「路径约定」。）

### 3. SSH 远程要加 `--ssh`

`git@github.com:...` 这类远程不走 HTTP 代理。加 `--ssh` 后 skill 会生成一份临时 ssh 配置并通过 `GIT_SSH_COMMAND` 注入，
只影响这条命令的子进程。

两个本机实测结论：
- 这个机场**不通 `github.com:22`** —— CONNECT 返回 200 但永远等不到 SSH banner，
  所以 `github.com` / `gist.github.com` 被自动改写到 `ssh.github.com:443`（GitHub 官方的备用端点），实测可拿到 banner。
- 使用 `--ssh` 会把主机密钥写入 `~/.ssh/known_hosts`（等价于首次连接时确认），属于预期行为。

用 HTTPS 远程的话不需要 `--ssh`；凭据的事见 §4。

### 4. 首次推送可能需要凭据（不要假设已经存过）

`git` 本身可能就不在 PATH 上（便携版 git 很常见）。`doctor` 里那行 `[--] git`
只在你不用 git 时才无所谓 —— 要用就先确认调得到，调不到就写全路径。

凭据同理，**不要假设已经存过**：`~/.gitconfig` 不存在、凭据管理器里没有
`git:https://github.com`，都是常态。这时第一次 `git push` **必然需要交互式登录**，
在非交互环境里表现为**命令卡住不动**。

遇到这种情况不要反复重试：先停掉卡住的命令，让用户在自己的终端里手动 `git push` 一次完成登录。
只要那次选择把凭据存进了凭据管理器，之后 AI 就能静默使用。

> 要探测先禁用交互再加超时，否则会被凭据 helper 挂住：
> 设 `GIT_TERMINAL_PROMPT=0`，用 Git Credential Manager 的话再加 `GCM_INTERACTIVE=never`。

### 5. 要调 GitHub API 时怎么拿凭据

**先探测，不要假设有。** 唯一的途径是 `git credential fill`，而本机没存过凭据时
它会挂起等输入 —— 所以必须像下面这样禁用交互，并自己加超时：

```js
const c = spawn('git', ['credential', 'fill'], {
  stdio: ['pipe', 'pipe', 'pipe'],
  env: { ...process.env, GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'never' },
});
c.stdin.end('protocol=https\nhost=github.com\n\n');   // 从 stdout 解析出 password，仅用于 Authorization 头
```

有输出才用 token，且**绝不能把 token 打印出来或落盘**；
没有输出（或超时）就是**没有凭据** —— 此时不要重试，直接告诉用户需要先手动登录一次，
或由用户提供 PAT 后再继续。凭据一旦存好，同一段代码就能静默复用。

## 电脑上已经开了 VPN / 代理软件时会怎样

结论：**绝大多数情况完全正常，两者并列共存，互不干扰。**

| 情况 | 结果 | 依据 |
|---|---|---|
| 别的代理客户端在跑（端口模式，只在监听） | ✅ 完全正常 | 本机实测：Clash for Windows 占着 7890、另一个代理占着 14711，两个都在跑，skill 全程正常 |
| 系统代理开着（注册表 `ProxyEnable=1`） | ✅ 正常 | `run` 总是给子进程显式写 `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`，覆盖系统设置。而且 git 和 curl 只认环境变量，根本不读注册表 |
| 环境变量里已存在 `*_PROXY` | ✅ 正常 | 实测把环境变量指向**死端口 9999**，`run` 依然成功 → 说明覆盖是彻底的，不会继承 |
| 别的软件占用了 7891 / 9091 | ❌ 起不来 | 改 `settings.json` 里的端口；`doctor` 会直接把冲突报出来 |
| TUN 模式的 VPN（建虚拟网卡 + 改全局路由） | ⚠️ 能用，但变成双层代理 | 内核对外的连接会被 TUN 再抓一次，"代理套代理"——更慢、更绕，而且此时"不动系统网络"这条承诺是被那个软件破坏的，不是本 skill |
| 真正的 VPN（WireGuard / OpenVPN 等改默认路由） | ⚠️ 同上 | 同上 |

两点值得单独说：

1. **内核启动时会被主动剥掉代理环境变量**（`coreEnv()`）。原因：如果环境里 `HTTP_PROXY` 指向 `127.0.0.1:7891`，一个"听话"的内核会连上自己形成死循环。内核本身就是代理，不需要再走代理。
2. **本 skill 从不修改系统代理，也从不关闭别人的代理。** 它和系统里已有的 VPN 是并列关系，不是替代关系。所以"电脑上开着 VPN"和"用这个 skill"可以同时成立。

顺带一提：既然已经有全局代理在跑了，你也可以直接用它 —— 但那是全局生效的、需要你手动开。本 skill 的价值在于**按需、只给单条命令**，用完即走。

## 配置

| 文件 | 作用 |
|---|---|
| `~/.workbuddy/local-proxy/subscription.txt` | 订阅地址（**敏感**，换订阅就改这里，然后 `refresh`） |
| `~/.workbuddy/local-proxy/settings.json` | 端口。默认代理 `7891`、控制 `9091` |
| `~/.workbuddy/local-proxy/run/config.yaml` | 生成的安全配置，不要手工改，用 `refresh` 重建 |
| `~/.workbuddy/local-proxy/mihomo.log` | 内核日志（级别 warning） |

端口 7891 是特意避开 Clash for Windows 的 7890 的，两者可以同时存在、互不干扰。

## 排错

先跑 `doctor`，再看 `references/troubleshooting.md`。

