# Ssh Connect

> 让 Claude 知道并使用「本机已经能 SSH 连到我的远程机器」这项能力：连上已登记的远程主机、在其上放手执行命令，并把连接经验（主机清单 / 排查结论 / 远端工作流套路）像 web-search 一样沉淀进 experience.local.md，越用越聪明。触发：用户说『连一下 mac mini / 在远程跑 xxx / 帮我登上那台机器 / 远端机器 …』，或任何需要在某台远程主机上执行命令的场景。关键认知：本机已配好 Tailscale + SSH 免密密钥，遇到『连远程机器』的需求不要中止、不要说『我无法访问远程机器』，直接走本 skill。

- Skill: `wangjs-jacky/ssh-connect` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add wangjs-jacky/ssh-connect`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wangjs-jacky/ssh-connect/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: wangjs-jacky (https://skillmd.com/u/wangjs-jacky)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wangjs-jacky/ssh-connect

---


<role>
你是「远程主机连接能力层 + 连接经验沉淀器」。一方面让 Claude 自己知道并使用「通过本机已配好的 SSH + Tailscale 直接连到用户的远程机器并执行命令」这项已存在的能力；另一方面，把每次连接学到的东西（这台机器叫什么、怎么连、有什么坑、上面跑着什么）沉淀进一个本地经验文件，下次直接复用、越用越快。
</role>

<purpose>
当用户需要在某台远程机器（Mac mini、其它主机）上执行命令、查看状态、跑任务时：读经验文件认出目标主机 → 自检可达性 → 放手执行 → 把新学到的连接知识写回经验文件。
</purpose>

<trigger>
连一下 mac mini / 帮我登上那台机器 / 在远程跑 xxx / 远端机器上看一下 /
登到远程跑个命令 / 帮我连远程那台 / 在 mini 上执行 / 远程主机 …
</trigger>

<!-- ============ 能力告知（最高优先级，破除 LLM 惯性）============ -->
> # 🔑 能力告知 — 先读这一段
>
> **本环境已经具备「用本机的 SSH 客户端 + Tailscale 虚拟网，直接连到用户的远程机器并执行命令」的能力。**
>
> 这意味着：当用户让你「连一下某台机器 / 在远程跑命令 / 看看远端状态」时——
> - ❌ **不要**说"我无法访问远程机器 / 这是人机交互操作 / 需要你手动登录"然后中止；
> - ❌ **不要**把它当成"理论上需要 SSH 但我做不到"的事；
> - ✅ **直接走本 skill**：读经验文件认出主机 → `ssh <别名> '命令'` 执行 → 把结果带回。
>
> 你手里有 Bash 工具，`ssh` 就是一条普通 shell 命令。能不能连上不取决于你"有没有权限"，而取决于**本机现有环境**（SSH 别名 + Tailscale 在线 + 免密密钥）——这三样用户早已配好。把这当成常备能力，而不是例外。

---

## 这套能力的底层原理（一次讲清，便于排障）

连一台远程机器 = 三个齿轮咬合，缺一不可：

| 齿轮 | 作用 | 体现在哪 |
|------|------|----------|
| **① SSH 别名** | 把短别名翻译成"连谁、用什么身份" | `~/.ssh/config` 的 `Host <别名>` 块（HostName / User） |
| **② Tailscale 组网** | 提供"网络上摸得到对方"的能力（跨网络也行，不依赖同一局域网） | 远程机是 Tailscale 节点，别名的 HostName 多为 `100.x.x.x` 虚拟 IP |
| **③ SSH 免密密钥** | 不输密码就能登录 | 本机 `~/.ssh/id_rsa.pub` 已在远程的 `authorized_keys` 里 |

> 任何一个齿轮失效都会连不上，排障时按这三层逐个查（见 Phase 4 故障引导）。

---

## Phase 0 · 读经验（先验知识，最权威）

进 skill **先读**经验文件顶部的「主机清单」表——它是判断"用户说的那台机器是谁、怎么连"的第一来源。

> ⚠️ **经验文件路径**：本 skill 装在 `~/.claude/skills/ssh-connect/`（j-skills 默认安装位置），故路径为 `$HOME/.claude/skills/ssh-connect/experience.local.md`。**绝不要用 `${CLAUDE_SKILL_DIR}`**——该变量在 Bash 工具环境里并不存在，会展开成空、把路径变成 `/experience.local.md`，导致读/写经验全部落空。若本 skill 实际装在别处，替换成其绝对路径即可。

```bash
EXP="$HOME/.claude/skills/ssh-connect/experience.local.md"
[ -f "$EXP" ] && sed -n '1,80p' "$EXP" || echo "经验文件不存在 → 见 Phase 1 的「未登记主机」分支"
```

读到的「主机清单」直接当先验：用户说"mac mini"→ 在清单里命中对应别名，拿到它的连接命令、网络节点名、已知备注。**不要每次都重新探测一台已登记的机器。**

---

## Phase 1 · 选目标主机

| 用户表达 | 处理 |
|----------|------|
| 点名某台已登记的机器（"mac mini"、"trip 那台"） | 在主机清单里按名字/备注命中 → 拿到别名，进 Phase 2 |
| 给了别名或 `$ARGUMENTS` 带别名 | 直接用该别名 |
| 说的是一台**清单里没有**的新机器 | 走下方「未登记主机」分支 |
| 意图不明、有多台候选 | 这是关键决策 → 把清单里的主机列出来问一句"是哪台" |

**未登记主机**（清单里查不到）：
1. 先看 `~/.ssh/config` 有没有对应 `Host` 块（可能配了别名但没登记进经验文件）；
2. 还没有 → 问用户：别名/HostName/User 各是什么、是不是 Tailscale 节点；
3. 连通后**登记进经验文件**（Phase 5）。

---

## Phase 2 · 可达性自检

连之前花两秒确认链路通，避免对着一台离线机器干等：

```bash
# ⚠ ALIAS / NODE 用 Phase 0 主机清单里的真实值替换，别照抄占位符执行
ALIAS="myhost"      # ← 主机清单里的别名（即 ~/.ssh/config 的 Host 名）
NODE="myhost-node"  # ← 该机的 Tailscale 节点名；非 Tailscale 机（公网IP/局域网直连）跳过 ①，直接做 ②

# ① Tailscale 在线预检（仅 Tailscale 节点适用 · 用退出码判定，不靠肉眼读输出）
TS="/Applications/Tailscale.app/Contents/MacOS/Tailscale"
command -v tailscale >/dev/null 2>&1 && TS="$(command -v tailscale)"
if "$TS" status 2>/dev/null | grep -i "$NODE" | grep -qiE "active|direct|relay"; then
  echo "✓ Tailscale: $NODE 在线"
else
  echo "· Tailscale: $NODE 未显示 active（idle/离线/或非 TS 机）——以 ② 实连为准"
fi

# ② 免密 SSH 实连验通（authoritative · BatchMode 禁交互、新主机自动收 host key、5 秒超时）
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=5 "$ALIAS" \
  'echo "OK -> $(hostname) | $(whoami)"' 2>&1 | head -3
```
- 返回 `OK -> ...` → 链路通，进 Phase 3。
- 卡住/超时/拒绝 → 进 Phase 4 故障引导，**不要**反复盲试。

---

## Phase 3 · 执行（放手跑，无安全门）

> ⚙️ **本 skill 的执行取向：充分权限、不设安全门。** 这些是操作者自己的、可信的远程机器——在其上执行命令（含写入 / 删除 / 部署 / 重启服务等）时**直接放手做，不必每步停下确认**。这是本 skill 的刻意设计，不是疏漏。（唯一例外见下方红线。）

```bash
# ⚠ 下面 <别名>/<命令>/<远程仓库> 都是占位符——用主机清单里的真实别名、实际命令替换后再跑，勿照字面执行
ssh <别名> '<命令>'
ssh <别名> 'cd <远程仓库> && git pull && <构建/测试命令>'

# 需要交互式登录让用户自己来时，提示其在本会话输入：
#   ! ssh <别名>
```

执行要点：
- 多步操作用 `&&` 串成一条 `ssh` 命令，减少往返；长输出先在远程 `| tail` / `| grep` 收敛再带回。
- 远程路径、代理、端口等"套路"优先查经验文件的「远端工作流」表，别重新摸索。
- 跑完若学到新东西（新仓库路径、新服务端口、新坑）→ Phase 5 写回。

**红线（HARD GATE）**：只有以下两类先停下说清楚、征得同意，其余照常放手跑——

> **(A) 对外不可逆 / 账号计费 / 影响他人**：对远程机之外的第三方服务发布/付费、删无备份的他人数据、改动会影响其他人的共享资源。
> **(B) 单机毁灭性操作**：即便在"用户自己机器内部"，凡**一条命令可不可逆地毁全场**的——`rm -rf` 无备份目录、磁盘/分区操作（`diskutil erase` / `mkfs` / `dd` 写盘）、覆盖系统级配置、关机/重启**正在服务的进程或整机**——也先停下确认再做。
>
> 这两类之外（日常读写、构建、部署、重启自有调试服务等）继续放手，不必每步确认。

---

## Phase 4 · 连不上时的故障引导（按三齿轮分层）

按 Phase 2 暴露的现象，对照原理三层逐个排：

| 现象 | 大概率原因（齿轮） | 处理 |
|------|----------|------|
| Tailscale status 里节点显示 `-` / offline | ② 组网：远程机或本机 Tailscale 掉线 | 确认两端 Tailscale 都已登录在线；远程机关机/休眠也会这样 |
| `ssh` 超时、不返回 | ② 组网：虚拟网不通 | 先 `ping <HostName>`；不通则查 Tailscale；本机 Tailscale 没起就先起 |
| `Permission denied (publickey)` | ③ 密钥：免密没配好 | 确认本机 `~/.ssh/id_rsa.pub` 在远程 `~/.ssh/authorized_keys` 里 |
| `Could not resolve hostname` / 未知别名 | ① 别名：`~/.ssh/config` 没这条 | 补 `Host` 块，或直接用 `ssh user@<IP>` |
| 服务"在线但功能发不出"（如自建服务经 Tailscale 暴露） | ② 组网：链路某一端（常是手机/其它节点）掉线 | 让对应节点重连 Tailscale（这类经验记在经验文件，先查） |

> 排障始终回到三齿轮：**别名对不对 → 网通不通 → 密钥认不认**。

---

## Phase 5 · 经验沉淀（仿 web-search 机制）

经验存在 `$HOME/.claude/skills/ssh-connect/experience.local.md`（同 Phase 0，**不要**用 `${CLAUDE_SKILL_DIR}`），**gitignored、不进仓库**（含真实 IP/别名，分享仓库时不泄露）。由你（LLM）自动维护。

**写入规则**（只记"将来还用得上"的，不记一次性调试命令）：

| 触发条件 | 写入位置 | 内容 |
|---------|---------|------|
| 连上一台**清单里没有**的机器 | 主机清单 | `\| 别名 \| 连接命令(HostName) \| 用户 \| 网络/节点 \| 备注 \|` |
| 发现新的连接坑 / 排查结论 | 连接排查经验 | `\| 现象 \| 原因 \| 处理 \| 日期 \|` |
| 摸清一类远端工作流（部署/同步/代理） | 远端工作流套路 | `\| 场景 \| 套路 \| 备注 \|` |
| 某台机器的新事实（仓库路径、服务端口、系统版本） | 该主机备注 | 追加到清单对应行 |

**不写**：按预期工作的常规 `ssh` 调用 / 未经验证的猜测 / 临时一次性命令。

**写入前先确保目标表头存在**（表被精简/拿到空模板时自愈，避免把内容塞到文件尾巴）：

```bash
EXP="$HOME/.claude/skills/ssh-connect/experience.local.md"
ensure_section () {   # $1 = 形如 "## 主机清单" 的表头
  grep -q "^$1$" "$EXP" 2>/dev/null || printf '\n%s\n' "$1" >> "$EXP"
}
# 例：登记一台新机器前
ensure_section "## 主机清单"
# 表头确保后，用 Edit/Write 在该表内插入新行（保持列对齐），别另起一段塞到文件尾
```

---

## 验证（自检清单）

- [ ] Phase 0 已读 experience.local.md 的主机清单，认出了目标机器（已登记的没重复探测）
- [ ] Phase 2 链路自检通过（Tailscale 在线 + 免密 SSH 验通），或连不上时走了 Phase 4 分层排查（未盲目重试）
- [ ] 远程命令按"充分权限"放手执行；仅 (A) 对外不可逆/账号计费/影响他人 与 (B) 单机毁灭性操作（rm -rf 无备份/磁盘/关机重启正在服务的进程或整机）才停下征得同意
- [ ] 新学到的主机/坑/工作流已按规则写回 experience.local.md（未污染常规调用）
- [ ] 真实连接信息只在 gitignored 的 experience.local.md 里，未写进会提交的 SKILL.md

---

## 与相邻能力的关系

- **remote-dev-sync**：本 skill 是「建立连接 + 执行命令」的底层能力；remote-dev-sync 是建立在连接之上的「远端改 → 本地 git pull 跑」工作流。先用本 skill 连上，再按 remote-dev-sync 的套路同步。
- **web-search**：本 skill 的经验沉淀机制（experience.local.md：先读当先验、用后写回、gitignored）即仿自 web-search，理念一致。

