---
title: "命令参考"
description: "TSLink 所有命令的完整参考"
url: "https://tslink.md/zh/docs/commands"
locale: "zh"
product_version: "0.1.1"
source: "https://github.com/anydoor7/tslink/blob/v0.1.1/docs/cli-reference.md"
---

> Documentation index: https://tslink.md/zh/llms.txt · Installed binary is authoritative: `tslink manifest`.

## 概述

TSLink 提供了一组简洁的命令来管理 Tailscale 网络上的服务。所有命令遵循 `tslink <命令> [参数] [标志]` 的模式。

## 认证

### `tslink login`

通过引导菜单录入并存储凭据。普通 `login` 不是浏览器 OAuth callback；单独的页面打开 helper 是另一项操作。

```bash
tslink login
```

**非交互式模式**（适用于 CI/CD 和自动化）：

```bash
# 通过 stdin（不把 secret 放进 argv 或 history）
printf %s "$TSLINK_API_KEY" | tslink login --api-key-stdin
printf %s "$TSLINK_CLIENT_SECRET" | tslink login --client-secret-stdin

# 通过 secret manager 预注入的环境变量
tslink login
```

选择恰好一个明确凭据来源（一个 stdin flag 或一个兼容 argv flag）；多个明确来源是 usage error。未指定明确来源时，先考虑环境变量，再进入交互提示。`--json` 禁用交互录入；自动化优先使用 stdin 或 secret manager 注入的环境，避免 argv/history 泄漏。

**标志：**

| 标志                      | 描述                                                               |
| ----------------------- | ---------------------------------------------------------------- |
| `--api-key-stdin`       | 从 stdin 读取 API 访问令牌                                              |
| `--client-secret-stdin` | 从 stdin 读取 OAuth 客户端密钥                                           |
| `--manage-acl`          | 显式 opt in 远端 ACL tag-owner mutation；默认 login 不会改写共享 ACL policy   |
| `--api-key <令牌>`        | 兼容性 API 访问令牌路径；自动化中避免使用，因为 argv 可能泄漏                             |
| `--client-secret <密钥>`  | 兼容性 OAuth 客户端密钥路径；自动化中避免使用，因为 argv 可能泄漏                          |
| `--expires-in <时长>`     | 以「从现在起的时长」记录 API 访问令牌有效期（`90d`、`30d` 或 Go duration）；仅适用于 api-key |
| `--expires-at <时间戳>`    | 以 RFC3339 时间戳记录 API 访问令牌有效期；仅适用于 api-key，且与 `--expires-in` 互斥    |
| `--retire-other`        | 当前凭证验证并提交成功后，删除另一个凭证槽位；默认 api-key 与 client-secret 两个槽位并存         |
| `--open-keys-page`      | 独立辅助命令：打开 Tailscale API keys 页面，退出码 0 且不存储任何凭证                   |
| `--open-oauth-page`     | 独立辅助命令：打开 Tailscale OAuth 页面，退出码 0 且不存储任何凭证                      |

TSLink 接受两种密钥类型：

* **API 访问令牌**（`tskey-api-*`）— 输入时自动验证，`tslink serve` 时自动派生认证密钥。
* **OAuth 客户端密钥**（`tskey-client-*`）— 不会周期性过期，但当前 Tailscale REST 标签/设备自动化能力比 API-token 模式更窄。

凭据优先使用系统钥匙串（macOS Keychain、Linux Secret Service、Windows Credential Manager）。明文 `0600` 文件回退仅在 macOS/Linux 证明旧钥匙串值不存在或已删除后允许；钥匙串不可达或状态不确定时拒绝写入，应恢复访问后重试。Windows 不允许凭据文件回退。无头运行本身不保证可以回退。

API 令牌生成地址：[管理后台 → Keys](https://login.tailscale.com/admin/settings/keys)。OAuth 密钥生成地址：[管理后台 → OAuth](https://login.tailscale.com/admin/settings/oauth)（点击 "+ credential" → "OAuth client" → 按所需 API 操作配置 scopes → 复制 **client secret**）。

API 访问令牌会过期，TSLink 会记录过期时间。传 `--expires-in` 或 `--expires-at` 时按你声明的值存储；两个都不传时，TSLink 按 90 天上限假定，并记录 `expires_at_source=assumed_max`，这样后续 `tslink doctor` 能区分「假定的期限」和「声明的期限」，而不会把猜测当成事实。

`--open-keys-page` 和 `--open-oauth-page` 是引导辅助命令，不是登录方式。只有当 stdin 是交互式终端且 `CI` 未设置时才真的打开浏览器，否则打印 URL 和引导步骤。两种情况下都以退出码 0 结束且不存储任何东西，因此可以安全地在「还在找凭证从哪来」的脚本里运行。

### `tslink logout`

清除 Tailscale 认证状态并移除已存储的凭证（API 密钥和/或 OAuth 客户端密钥）。在登出之前必须先停止网关。

```bash
tslink logout
```

这会从系统钥匙串中移除所有凭证（以及旧文件），并移除 `~/.config/tslink/` 中的节点状态。你的服务注册表（`registry.json`）会被保留。

**Flags:**

| Flag                              | 说明                               |
| --------------------------------- | -------------------------------- |
| `--kind <api-key\|client-secret>` | 只移除该凭证槽位及其 metadata；节点状态和另一个槽位保留 |

用 `--kind` 可以在保留另一个凭证继续可用的前提下退役其中一个，例如把自动化从 OAuth client secret 迁移到 API 访问令牌之后。

## 服务管理

### `tslink add <名称>`

默认 `add` 会确保后台网关运行，可能返回授权交接。完成授权后用 `url --wait`；不要在默认 `add` 后再启动另一个 `serve`。同名条目会被替换。

注册一个新服务以暴露到你的 tailnet。必须提供 `--proxy`、`--dir` 或 `--tcp` 之一。

**服务类型标志：**

| 标志                  | 描述                   | 示例                         |
| ------------------- | -------------------- | -------------------------- |
| `--proxy <目标>`      | 反向代理到本地 Web 服务       | `--proxy localhost:3000`   |
| `--dir <路径>`        | 通过 HTTPS 暴露文件目录      | `--dir ~/Documents/shared` |
| `--tcp <host:port>` | 原始 TCP 代理（数据库、自定义协议） | `--tcp localhost:5432`     |

**行为标志：**

| 标志                    | 描述                                                                                          | 要求                  |
| --------------------- | ------------------------------------------------------------------------------------------- | ------------------- |
| `--ephemeral`         | 请求临时节点；控制面按不活跃状态清理，而非断开即移除                                                                  | 任何类型                |
| `--tags <标签>`         | 逗号分隔的 ACL 标签（如 `tag:web,tag:internal`）。省略时使用默认标签（`tag:tsmain`）。                             | 任何类型                |
| `--allow <身份>`        | proxy/file HTTP 服务的允许身份。拒绝与 `--tcp` 和 `--funnel` 组合；原始 TCP 由 Tailscale ACL/tag 和目标服务自身认证保护。 | `--proxy` 或 `--dir` |
| `--funnel`            | 请求通过 [Tailscale Funnel](https://tailscale.com/kb/1223/funnel) 公开暴露                          | 仅 `--proxy`         |
| `--public`            | 确认 `--funnel` 会将代理服务暴露到公共互联网                                                                | `--funnel`          |
| `--funnel-ttl <时长>`   | 访问期限至少 1h，默认 24h，公网上限默认 7d；支持相对/绝对形式，新的公开拒绝 never。                                          | `--funnel`          |
| `--no-auto-provision` | 关闭 Funnel policy 自动 provisioning                                                            | `--funnel`          |
| `--control-url <URL>` | 服务级控制服务器 URL（如 Headscale）                                                                   | 任何类型                |
| `--no-daemon-install` | 只保存配置，不安装也不启动后台服务                                                                           | 任何类型                |
| `--dry-run`           | 校验并打印服务，不写 registry、不启动网关，默认 `false`                                                        | 任意类型                |
| `--wait <时长>`         | 等待一个精确的 runtime URL 或 enrollment URL（默认 `30s`；`--wait=0` 表示注册后不等待）                          | 任何类型                |

**其他标志：**

| Flag                           | 说明                                                                                |
| ------------------------------ | --------------------------------------------------------------------------------- |
| `--ack-unlimited-request-body` | 明确确认移除 HTTP 请求体上限；需 --max-request-body unlimited。                                 |
| `--force-unsafe-public`        | 危险：覆盖 recipe 的 never-public 拒绝，可能公开主机控制或私有数据；需 --funnel --public，add 还需 --recipe。 |
| `--health-body`                | 匹配前 64 KiB 正文中的子串，仅 proxy；勿把秘密放入参数。                                               |
| `--health-interval`            | 后端检查间隔，10s 至 1d，默认 1m。                                                            |
| `--health-path`                | HTTP 后端检查路径，默认 /，仅 proxy。                                                         |
| `--health-status-max`          | HTTP 期望状态上限，默认 299，仅 proxy。                                                       |
| `--health-status-min`          | HTTP 期望状态下限，默认 200，仅 proxy。                                                       |
| `--health-timeout`             | 后端检查超时，100ms 至 30s，默认 5s。                                                         |
| `--idle-timeout`               | HTTP keep-alive 空闲超时，默认 60s。                                                      |
| `--max-request-body`           | HTTP 上传上限，默认 32MiB；unlimited 需 --ack-unlimited-request-body。                      |
| `--preserve-host`              | 转发节点可信的 canonical 外部 Host，仅 proxy；普通注册默认关闭，recipe 有自己的默认值。                        |
| `--recipe`                     | 应用 recipe ID；默认预览，--yes 应用。                                                       |
| `--request-header-timeout`     | HTTP header 读取超时，默认 10s。                                                          |
| `--request-read-timeout`       | 请求体无读取进展的最长时间，默认 30s，不是总上传时长。                                                     |
| `--yes`                        | 应用审阅过的 recipe plan；add 需要 --recipe。                                               |
| `--requestable`                | 明确在私有 portal 请求表单披露此应用名称；仅私有 HTTP/文件，默认关闭。                                        |

**示例：**

```bash
# 暴露一个 Web 应用
tslink add webapp --proxy localhost:3000

# 暴露一个 API 服务器，带 ACL 标签
tslink add api --proxy 127.0.0.1:8080 --tags tag:api,tag:prod

# 暴露一个目录用于文件共享
tslink add docs --dir ~/Documents/shared

# 通过 TCP 暴露 PostgreSQL 数据库
tslink add mydb --tcp localhost:5432

# 请求临时节点；本地注册保留至主动移除
tslink add devserver --proxy :8080 --ephemeral

# 通过 Tailscale Funnel 公开访问
tslink add public-site --proxy localhost:3000 --funnel --public

# 限制特定用户访问
tslink add internal --proxy localhost:9090 --allow user@example.com,tag:admin

# 为该服务指定控制服务器
tslink add headscale-app --proxy localhost:3000 --control-url https://headscale.example.com
```

### `tslink remove <名称>`

按名称移除已注册的服务。本地 registry 移除会立即完成。本地 tsnet state 只有在远端身份被证明已清除后才删除；daemon 运行时由其 reconciliation 负责删除 state。远端 tailnet 清理只有在 TSLink 拥有匹配设备的精确所有权证明时才会尝试；否则匹配候选会报告为 protected，并跳过清理。

```bash
tslink remove webapp
```

`remove` 是幂等的。移除一个从未注册过的名字是一次成功调用，只是什么都没移除，所以要回答「服务是否已经没了」看 `removed` 而不是 `ok`：

```json
{"type":"tslink.result","ok":true,"schema_version":1,"command":"remove","code":0,"data":{"name":"docs-example-nonexistent","removed":false,"device_cleaned":false,"device_cleanup_skipped":false}}
```

如果网关正在运行，服务将通过热重载移除，无需重启。

**Flags:**

| Flag       | 说明                                      |
| ---------- | --------------------------------------- |
| `--strict` | 服务不存在时返回 `not_found`（退出码 5），而不是上面那种幂等成功 |

幂等是清理脚本该有的默认行为：重跑第二遍不应该失败。`--strict` 面向相反的场景 —— 调用方希望「这个名字确实注册过」成为一个被强制检查的前置条件。

### `tslink list`

以表格格式显示所有已注册的服务，无论网关是否运行。

```bash
tslink list
```

输出列：

| 列          | 描述                                   |
| ---------- | ------------------------------------ |
| `NAME`     | 服务在 tailnet 上的主机名                    |
| `TYPE`     | 服务类型（proxy、file 或 tcp）               |
| `BACKEND`  | 本地目标（proxy/tcp 为 host:port，file 为路径） |
| `ENDPOINT` | 预期的 tailnet 端点类型                     |
| `EXPOSURE` | 暴露方式（tailnet、allow-list 或 Funnel）    |

**标志：**

| 标志                | 描述                                            |
| ----------------- | --------------------------------------------- |
| `--name <name>`   | 只返回名字完全匹配的那个服务                                |
| `--type <type>`   | 按服务类型过滤：`proxy`、`file` 或 `tcp`                |
| `--fields <list>` | `--json` 视图的逗号分隔精简字段                          |
| `--verbose`       | 返回完整的 owner-only 诊断视图                         |
| `--tailnet`       | 只读：列出整个 tailnet 中每一台带 TSLink 标签的设备，而非本机已注册的服务 |

#### `tslink list --tailnet`

默认列表读的是本机的 `~/.config/tslink/registry.json`。`--tailnet` 改为查询 Tailscale API，报告 tailnet 中每一台带 TSLink 标签的设备：本机注册的服务、其它机器注册的服务，以及孤儿节点。它回答的是单机注册表回答不了的跨机器问题。

```bash
tslink list --tailnet
tslink list --tailnet --json
```

每一行都标明它来自机器边界的哪一侧：

| `origin`             | 含义                                                       |
| -------------------- | -------------------------------------------------------- |
| `local_registry`     | 本机 `registry.json` 中有一个主机名完全相同的服务                        |
| `local_name_variant` | 该主机名是本机某个已注册服务的 `<service>-N` tsnet 冲突变体，这是本机留下孤儿节点的常见形态 |
| `unregistered`       | 本机注册表对该主机名一无所知：它是另一台机器的服务，或是一个孤儿节点                       |

人类可读输出以 `N of M TSLink-owned tailnet devices are not registered on this machine.` 结尾，JSON 负载在 `count` 之外还带 `registered_count` 与 `unregistered_count`。每个结果都带一个常量字段 `cleanup_authority`，因为这个视图暴露了一条真实的限制：`tslink cleanup` 只删除精确 NodeID 记录在本机 `node-ownership.json` 中的设备，所以本机注册表没有命名的设备，必须回到创建它的那台机器上清理。`--tailnet` 永远不输出 NodeID，也永远不删除任何东西。

`--tailnet` 需要已存储的 Tailscale API 凭证（`tskey-api-*` 访问令牌或 OAuth 客户端密钥）。没有凭证时它以 `auth_error`（退出码 3）失败，并在 `error.next` 中给出 bootstrap 指引；它不会返回空列表。它与 `--name`、`--type`、`--fields`、`--verbose` 互斥（`usage_error`，退出码 2），因为那些 flag 过滤的是本机已注册服务，而 `--tailnet` 报告的是 tailnet 设备。

### `tslink share <path|port|host:port>`

注册一个一次性的 proxy 或 file 服务，需要时启动 daemon，并等待一个精确的 runtime URL——这是从零到一个 URL 最快的路径。已存在的目录会作为 file 服务暴露其内容；普通文件只暴露选中的文件并返回该文件的 URL，不暴露同级文件或父目录列表；裸端口或 `host:port` 目标会变成 HTTP 代理。分享默认是临时（ephemeral）的，重复分享同一目标会复用已有服务，而不是创建带后缀的孤立节点。

```bash
tslink share ./build
tslink share ./report.html          # URL 直接指向 report.html
tslink share 3000
tslink share localhost:8080 --name preview
tslink share ./build --ephemeral=false
```

在零凭据首次运行时，stdout 只有一行——Tailscale 授权 URL，stderr 会打印出精确的续接命令（`tslink url <name> --wait`）。加 `--json` 时，这是一个成功的 `status:"needs_login"` 结果，携带 `auth_url`——不是认证错误。

**标志：**

| 标志                    | 描述                                                                  |
| --------------------- | ------------------------------------------------------------------- |
| `--name <名称>`         | 请求的服务名（DNS label）。匹配的目标会复用它；不相关的名字冲突会得到一个数字后缀，而不是覆盖已有服务。            |
| `--ephemeral`         | 使用临时 tailnet 节点（默认 `true`；传 `--ephemeral=false` 获得持久状态）             |
| `--wait <时长>`         | 等待一个精确的 runtime URL（默认 30 秒；与 `tslink url` 不同，`share` 不需要传这个标志就会等待） |
| `--no-daemon-install` | 要求后台服务已在运行；不去安装它                                                    |

| Flag                           | 说明                                                           |
| ------------------------------ | ------------------------------------------------------------ |
| `--ack-unlimited-request-body` | 明确确认移除 HTTP 请求体上限；需 --max-request-body unlimited。            |
| `--idle-timeout`               | HTTP keep-alive 空闲超时，默认 60s。                                 |
| `--max-request-body`           | HTTP 上传上限，默认 32MiB；unlimited 需 --ack-unlimited-request-body。 |
| `--preserve-host`              | 转发节点可信的 canonical 外部 Host，仅 proxy；普通注册默认关闭，recipe 有自己的默认值。   |
| `--request-header-timeout`     | HTTP header 读取超时，默认 10s。                                     |
| `--request-read-timeout`       | 请求体无读取进展的最长时间，默认 30s，不是总上传时长。                                |

### `tslink url <name>`

打印一个已注册服务的精确 runtime URL，可选择等待它就绪。

```bash
tslink url myapp
tslink url myapp --wait
tslink url myapp --wait --raw
```

**标志：**

| 标志            | 描述                                            |
| ------------- | --------------------------------------------- |
| `--wait [时长]` | 只有传入时才等待精确 runtime URL；裸 `--wait` 表示 30 秒     |
| `--raw`       | 只打印 URL 和一个换行符，不带 JSON envelope；与 `--json` 冲突 |

如果 runtime 尚未报告精确的 tailnet 主机名，且你没有传 `--wait`，`tslink url` 会以非零状态退出，`error.code` 为 `url_not_ready`，而不是打印占位符。

## 网关

### `tslink serve`

启动 TSLink 网关。为每个已注册的服务启动一个嵌入式 tsnet 节点并开始服务。

**标志：**

| 标志                    | 描述                                                                                                                                                                                                             |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--daemon`            | 作为守护进程在后台运行                                                                                                                                                                                                    |
| `--control-url <URL>` | 自定义控制服务器 URL（如 Headscale）— 覆盖全局配置                                                                                                                                                                              |
| `--manage-acl`        | 显式开启普通启动 tag-owner ensure；另行确认公开的 Funnel auto-provisioning 仍默认开启                                                                                                                                               |
| `--mcp`               | 在专用的 tailnet-only 节点上提供远程 MCP 控制面；默认关闭，且要求 `config.json` 中配置非空 owner `mcp.allow` 或明确 `mcp.bindings`。参见[远程 MCP 控制面](https://tslink.md/zh/docs/mcp-server.md#%E8%BF%9C%E7%A8%8B-mcp-%E6%8E%A7%E5%88%B6%E9%9D%A2) |
| `--no-auto-provision` | 对本次 serve 进程内的所有服务关闭 Funnel policy 自动 provisioning                                                                                                                                                             |
| `--no-browser`        | 打印 Tailscale 登录 URL，不打开浏览器                                                                                                                                                                                     |

```bash
# 在前台运行
tslink serve

# 作为后台守护进程运行
tslink serve --daemon

# 使用 Headscale 控制服务器（单次覆盖）
tslink serve --control-url https://headscale.example.com
```

启动时，TSLink 自动：

* 解析凭证并为每个服务启动一个 tsnet 节点。
* 使用 `--manage-acl` 和具有所需 scopes 的凭据配置普通 tag-owner policy。显式公开的 Funnel 默认配置共享 grant，除非 `--no-auto-provision` 或服务设置关闭。准确所有权设备清理是独立 API 操作。
* 监视 `registry.json` 的变化并热重载服务。

### `tslink stop`

停止正在运行的 TSLink 网关（前台或守护进程）。读取 `tslink.pid` 并验证进程属于 TSLink，最多等待 5 秒终止。macOS/Linux 使用 `SIGTERM` 正常关闭；Windows 使用进程终止，不是正常关闭。

`stop` 不会移除自动启动注册。受管理的 macOS LaunchAgent 启用了 `KeepAlive`，停止后会按 30 秒节流重启；需要持续停止时先执行 `tslink uninstall`。Linux systemd 用户服务的重启策略是 `on-failure`，成功的正常关闭会让它保持停止。Windows 的启动注册在卸载前仍会于下次登录生效。卸载后，停止任何剩余的手动网关，再用 `tslink status --json` 验证后才删除状态。

```bash
tslink stop
```

### `tslink status`

JSON 的 `data.supervision` 报告已验证的 manager、installed、autostart、restart\_on\_exit，以及可选 detail / evidence / autostart\_scope。它与 `daemon_running` 分开；进程存活不证明自动启动或监督有效。

显示 TSLink 网关的当前状态：守护进程状态、认证状态和已注册服务数量。

```bash
tslink status
```

输出示例：

```
→ tslink: running (pid 12345)
→ tailnet: authenticated
→ services: 3 registered
```

**Flags:**

| Flag            | 说明                            |
| --------------- | ----------------------------- |
| `--urls`        | 显示仅限所有者可见的服务端点概览              |
| `--name <name>` | 按准确服务名过滤 `--urls`；要求 `--urls` |

`--urls` 打印每个服务的可达位置。这是 owner-only 输出：它列出的端点就是你自己注册表里定义的那些，因此请按对待注册表本身的方式对待这份结果。

## 标签管理

### `tslink tags`

管理 TSLink 服务的 ACL 标签。API-key 模式可以通过 Tailscale API 创建或拉取标签；client-secret-only 模式不要在验证前依赖标签/设备自动化。

**子命令：**

| 子命令                                       | 描述                                                                   |
| ----------------------------------------- | -------------------------------------------------------------------- |
| `list`                                    | 列出所有已注册服务及其标签                                                        |
| `pull`                                    | 从 Tailscale API 打印当前 ACL 标签                                          |
| `add <服务> <标签>`                           | 为特定服务添加标签                                                            |
| `set <服务> <标签>`                           | 用一个标签替换特定服务的标签                                                       |
| `set-default <标签>`                        | 设置未指定 `--tags` 时使用的默认标签                                              |
| `delete-remote <标签> --force --manage-acl` | 通过本地安全检查和显式远端 ACL opt-in 后，经 API 从 Tailscale ACL 策略中全局移除 ACL 标签所有者规则 |

#### `tslink tags list`

列出所有已注册服务及其标签。

```bash
tslink tags list
```

#### `tslink tags pull`

从 Tailscale API 打印当前 ACL 标签。
client-secret-only 模式会跳过该远端拉取，因为它需要 API 访问令牌。

```bash
tslink tags pull
```

#### `tslink tags add <服务> <标签>`

为特定已注册服务添加标签。

```bash
tslink tags add webapp tag:staging
```

#### `tslink tags set <服务> <标签>`

用一个标签替换特定服务的标签。

```bash
tslink tags set webapp tag:web
```

#### `tslink tags set-default <标签>`

设置未指定 `--tags` 时应用于服务的默认标签。持久化在 `GlobalConfig.DefaultTag` 中。

```bash
# 将默认值从 tag:tsmain 更改为自定义标签
tslink tags set-default tag:myteam
```

#### `tslink tags delete-remote <标签> --force --manage-acl`

通过本地安全检查后，经 API 从 Tailscale ACL 策略中全局移除 ACL 标签所有者规则。此操作会全局修改你的 tailnet ACL 策略，且远端 ACL mutation 默认关闭，因此必须同时显式传入 `--force` 和 `--manage-acl`。

```bash
tslink tags delete-remote tag:deprecated --force --manage-acl
```

## 维护

### `tslink cleanup`

对照本地 registry 与 tailnet：停用已过期 Funnel、删除本机具有准确所有权证明且不再需要的设备，并报告本机对共享 Funnel grant 的使用情况。永不删除共享 grant。

```bash
# 预览 -- 这是默认行为
tslink cleanup

# 实际执行
tslink cleanup --dry-run=false
```

**Flags:**

| Flag                 | 默认      | 说明                                                                              |
| -------------------- | ------- | ------------------------------------------------------------------------------- |
| `--dry-run`          | `true`  | 预览对账结果，不做注册表、设备或 ACL 删除；传 `--dry-run=false` 才实际执行                               |
| `--manage-acl`       | `false` | 只报告本机是否使用共享 Funnel tag-owner / `nodeAttrs` grant；永不删除共享 grant，也不查询 tailnet ACLs |
| `--adopt <hostname>` | 无       | 为一个字面量 TSLink-tagged 遗留设备 hostname 预览或记录精确 ownership                            |
| `--force`            | `false` | 确认显式指名的 `--adopt` 迁移                                                            |

`--dry-run` 默认为 `true`，所以裸命令不删除任何东西。这个默认值是安全属性而不是便利性：删除要求持久化的精确 NodeID ownership proof，hostname 匹配仅用于发现。hostname 匹配上但 NodeID 未被证明的设备会出现在 `devices_protected` 里，永不删除。

`--adopt` 面向那些在 ownership proof 机制之前创建的设备。它只接受一个字面量 hostname，写入 proof 要求恰好一个 TSLink-tagged 远端匹配，外加 `--force --dry-run=false`；匹配多于一个时报 cardinality 冲突而不是替你猜。预览过程是只读的。

JSON envelope 会说明这次 cleanup 为什么什么都没做 —— 这比「它没做事」这个事实本身更重要：`device_cleanup_skipped` 配合 `device_skip_reason` 区分了 `registry.json` 结构上不可信、ownership ledger 不可用、orphan 缺少 `retired_at` provenance、没有可用的 API client、hostname-only 匹配受保护、以及远端清理失败这几种情况。输出中永远不出现 NodeID，只出现 hostname。

### `tslink registry`

直接检视本地服务注册表，不经过 runtime。

#### `tslink registry check [path]`

严格校验一份 `registry.json` 并报告其中所有问题，且不修改文件。不带参数时校验当前生效的注册表；传路径则校验别处的文件，例如你正准备安装的候选文件，或来自另一台机器的副本。

```bash
tslink registry check
tslink registry check ./candidate-registry.json --json
```

返回结果包含 `path`、`schema_version`、`valid_services`、`total_services`，以及一个 `issues[]` 数组，其中每一项用 `index` 和 `name` 指名出问题的服务，并带稳定的 `code` 和给人读的 `message`。所有被拒绝的条目在一次调用中全部报出，因此 `valid_services` 小于 `total_services` 时，你能准确知道 runtime 会丢弃哪些服务、以及为什么，而不是只看到 loader 恰好最先撞上的那一个问题。

## 邀请

### `tslink invite`

邀请某个人加入你的 tailnet，或把某个 TSLink 拥有的服务设备共享给 tailnet 之外的人。

这里的每个子命令都会通过 Tailscale API 产生对外副作用：真实的人会收到真实的邀请。它们是 TSLink 中仅有的、效果能被你之外的人感知到的命令，因此每一条都显式指名收件人、要求由 [`tslink login`](https://tslink.md/zh/docs/commands.md#tslink-login) 存储的 user-owned `tskey-api-` 令牌，并在 JSON envelope 中返回一个记录本次发送内容的 `remote_side_effect_plan` 对象。

设备共享还有第二重要求：TSLink 只共享它自己注册的设备，按精确 `nodeId` 匹配。无法证明归属的设备永远不会成为候选，因此打错服务名的结果是失败，而不是共享出一台不相干的机器。

**子命令：**

| 子命令                                 | 说明                         |
| ----------------------------------- | -------------------------- |
| `user <email>`                      | 邀请一个用户加入 tailnet           |
| `device <service> <email>`          | 把一个 TSLink 拥有的服务设备共享给外部用户  |
| `list`                              | 列出未接受的用户邀请和 TSLink 拥有的设备邀请 |
| `revoke <id> --kind <user\|device>` | 撤销一个用户或设备邀请                |
| `resend <id> --kind <user\|device>` | 重发一个以邮件方式创建的用户或设备邀请        |

#### `tslink invite user <email>`

邀请一个人加入你的 tailnet。

```bash
tslink invite user teammate@example.com
tslink invite user teammate@example.com --role auditor
tslink invite user teammate@example.com --print-link
```

| Flag           | 默认       | 说明                                                                               |
| -------------- | -------- | -------------------------------------------------------------------------------- |
| `--role <角色>`  | `member` | 接受邀请后分配的角色：`member`、`admin`、`it-admin`、`network-admin`、`billing-admin`、`auditor` |
| `--print-link` | `false`  | 不发邮件；返回 API 提供的邀请 URL 供你自行送达                                                     |

`--print-link` 改变的是由谁送达邀请，不是谁可以接受邀请。JSON envelope 明确陈述送达事实而不是暗示它：Tailscale 发出邮件时 `emailed: true`，你自行送达时 `emailed: false` 并附带 `invite_url`。该 URL 由 Tailscale API 原样返回 -- TSLink 从不自行构造 -- 并且它是 bearer 凭证：谁持有谁就能接受。

#### `tslink invite device <service> <email>`

把某个已注册服务背后的设备共享给外部用户，让对方无需加入你的 tailnet 就能访问那一台机器。

```bash
tslink invite device my-api partner@example.com
tslink invite device my-api partner@example.com --multi-use
```

| Flag                | 默认      | 说明                           |
| ------------------- | ------- | ---------------------------- |
| `--print-link`      | `false` | 不发邮件；返回 API 提供的邀请 URL 供你自行送达 |
| `--multi-use`       | `false` | 允许该设备邀请被接受多次                 |
| `--allow-exit-node` | `false` | 允许接收方把共享设备用作 exit node       |

`--multi-use` 和 `--allow-exit-node` 各自都会放大这份邀请授予的范围，各自都默认关闭。multi-use 邀请 URL 在第一次被接受之后仍然有效；exit-node 共享则允许接收方把自己的流量路由经过这台机器。

#### `tslink invite list`

列出未接受的用户邀请，以及这个 TSLink 拥有的服务对应的设备邀请。

```bash
tslink invite list
tslink invite list --json
```

| Flag          | 默认      | 说明                                |
| ------------- | ------- | --------------------------------- |
| `--show-urls` | `false` | 在人类可读输出和 JSON 输出中包含 bearer 邀请 URL |

URL 默认不打印，因为它们是 bearer 凭证：打印一次就等于把一份可被接受的邀请放进你的终端回滚区，以及任何会捕获它的地方。

JSON envelope 把「空结果」和「不完整结果」区分开。`complete: true` 表示每个被请求的设备目标都无错误地检查过；`false` 表示有部分设备结果缺失。`device_targets[]` 携带逐服务的结果，因此一个已检查、`invite_count: 0` 的目标读作「没有未接受的邀请」，而不是「没查到」。

#### `tslink invite revoke <id> --kind <user|device>`

撤销一个未接受的邀请。

```bash
tslink invite revoke 12345 --kind user
```

| Flag                    | 默认 | 说明     |
| ----------------------- | -- | ------ |
| `--kind <user\|device>` | 必填 | 邀请命名空间 |

`--kind` 刻意没有默认值。用户邀请 ID 和设备邀请 ID 是两个独立的数字命名空间，同一个数字可以在两边各指向一个邀请；要求显式给出命名空间，是为了让目标是被指定的而不是被猜出来的。撤销设备邀请还要求精确的 TSLink 节点 ownership proof。只有在 Tailscale API 接受撤销之后才会出现 `revoked: true`。

#### `tslink invite resend <id> --kind <user|device>`

重发一个最初以邮件方式创建的邀请。

```bash
tslink invite resend 12345 --kind user
```

| Flag                    | 默认 | 说明     |
| ----------------------- | -- | ------ |
| `--kind <user\|device>` | 必填 | 邀请命名空间 |

用 `--print-link` 创建的邀请没有邮件地址，无法重发。重发输出也不会再次打印邀请 URL：把一份 bearer 凭证重印到第二个界面上没有任何收益，因此结果只报告 `emailed` 就停下。

## 诊断、访问解释与模板

### `tslink doctor`

运行本地诊断，并通过与其它命令相同的 JSON envelope 和退出码 contract 返回 warning/critical 阈值。

**Flags:**

| Flag               | 说明                                                                                       |
| ------------------ | ---------------------------------------------------------------------------------------- |
| `--probe-external` | 探测非 loopback 的服务目标                                                                       |
| `--probe-remote`   | 用一次 device-list 读取向 Tailscale API 验证每个已存储凭证，并把 `last_verified` 记入 `credential-meta.json` |

两者默认关闭，因为两者都会伸到本进程之外：`--probe-external` 会向非 loopback 目标建立连接，`--probe-remote` 每个已存储凭证消耗一次 Tailscale API 调用。默认运行保持在本地且不产生费用。

Doctor 还会从本机 `tailscaled` 读取并报告本节点是否开启了 Tailscale SSH。人类可读输出打印 `Tailscale SSH (this node): <state>`；JSON 负载带有 `tailscale_ssh.state` 与 `tailscale_ssh.acl_rule_required: true`。这项检查存在的原因是：`tailscale ssh <host> tslink <command>` 是从 tailnet 内另一台机器操作这份安装的零代码路径，而这条路径需要两个都位于 Tailscale 层的条件：本节点开启了 Tailscale SSH，且 tailnet ACL 中有一条允许调用者的 `ssh` 规则。TSLink 只检测第一个条件并指出两者；它不会开启 Tailscale SSH，也不会修改 policy 文件。

| 状态         | Finding code             | 含义                                                                                |
| ---------- | ------------------------ | --------------------------------------------------------------------------------- |
| `enabled`  | `tailscale_ssh_enabled`  | 一旦 tailnet ACL 有 `ssh` 规则允许调用者，`tailscale ssh <this-host> tslink list --json` 即可用 |
| `disabled` | `tailscale_ssh_disabled` | 在这台机器上运行 `tailscale set --ssh` 并添加 ACL `ssh` 规则，才能使用远程路径                          |
| `unknown`  | `tailscale_ssh_unknown`  | 一秒内无法读取本机 Tailscale client 状态；检查 `tailscale status`                               |

三种结果都是 informational，永远不会改变 doctor 的 status 或退出码。

### `tslink access explain`

解释一个已注册服务如何被访问，包括 tailnet endpoint、`--allow` 行为和 Funnel 暴露。

### `tslink template list`

列出内置个人模板。

### `tslink template show`

显示一个内置模板定义。

### `tslink template apply`

应用内置模板。使用 `--dry-run` 可在不写入 registry 的情况下预览 plan。

| 标志                    | 描述                     |
| --------------------- | ---------------------- |
| `--dry-run`           | 预览模板 plan，不写入 registry |
| `--yes`               | 把模板里缺失的服务写入 registry   |
| `--no-daemon-install` | 只应用配置，不安装也不启动后台服务      |

## 配置

### `tslink config`

管理持久化在 `~/.config/tslink/config.json` 中的全局 TSLink 设置。

**子命令：**

| 子命令           | 描述      | 示例                                                     |
| ------------- | ------- | ------------------------------------------------------ |
| `set <键> <值>` | 设置配置值   | `tslink config set control-url https://hs.example.com` |
| `get <键>`     | 获取配置值   | `tslink config get control-url`                        |
| `list`        | 列出所有配置值 | `tslink config list`                                   |

**可用配置键：**

| 键             | 描述                      | 默认值          |
| ------------- | ----------------------- | ------------ |
| `control-url` | 自定义控制服务器 URL（Headscale） | Tailscale 默认 |

```bash
# 设置 Headscale 控制服务器
tslink config set control-url https://headscale.example.com

# 查看当前值
tslink config get control-url

# 清除（恢复 Tailscale 默认）
tslink config set control-url ""

# 显示所有设置
tslink config list
```

### `tslink logs`

查看 TSLink 守护进程日志输出。默认读取 stderr 日志（`tslink.err.log`）的最后 50 行。

**标志：**

| 标志             | 描述                                       | 默认值   |
| -------------- | ---------------------------------------- | ----- |
| `--last <N>`   | 显示的行数                                    | 50    |
| `--level <级别>` | 按最低日志级别过滤（`debug`、`info`、`warn`、`error`） | （无）   |
| `--source <源>` | 日志源：`out`（标准输出）或 `err`（标准错误）             | `err` |

```bash
# 显示最后 50 行日志
tslink logs

# 显示最后 100 行
tslink logs --last 100

# 仅显示错误
tslink logs --level error

# 显示标准输出日志
tslink logs --source out

# JSON 输出
tslink logs --last 20 --json
```

## 系统

### `tslink install`

注册 TSLink 为开机自启动。

```bash
tslink install
```

* **macOS**：创建 LaunchAgent（`~/Library/LaunchAgents/com.tslink.daemon.plist`）
* **Linux**：创建 systemd 用户服务（`~/.config/systemd/user/tslink.service`）
* **Windows**：注册交互用户 Task Scheduler 任务，启动内建 supervisor 并恢复 daemon 崩溃。`--startup` 为明确选择的 fallback，无崩溃恢复。

| Flag                  | 含义                                                              |
| --------------------- | --------------------------------------------------------------- |
| `--force`             | **仅 macOS**；Linux/Windows 不支持。即使 launchd 域不可用也继续升级（可能启动第二个守护进程） |
| `--no-auto-provision` | 在已安装后台服务中关闭自动 Funnel policy provisioning，并在受管理重启后保留             |

在 macOS 上，`gui/$(id -u)` 只在该用户存在桌面（Aqua）会话时才存在。可用性取决于会话本身，而不是你是否通过 SSH 连接。首次安装会回退到 `user/$(id -u)`；升级时若某个曾经使用过的域无法检查，则拒绝执行，因为 TSLink 无法证明那个域是空的。该拒绝以 exit 1 返回，`error.code` 为 `launchctl_domain_unavailable`，其 `data` 给出不可用的域、完整的 `--force` 命令以及残留风险。

| Flag        | 说明                                     |
| ----------- | -------------------------------------- |
| `--startup` | 仅 Windows：明确使用 Startup fallback，无崩溃恢复。 |

### `tslink uninstall`

移除自启动注册。

```bash
tslink uninstall
```

| Flag      | 含义                                                                      |
| --------- | ----------------------------------------------------------------------- |
| `--force` | **仅 macOS**；Linux/Windows 不支持。即使 launchd 域不可用也移除 plist（可能留下一个仍在运行的守护进程） |

同样的域规则适用。当没有任何域确认任务已卸载、且至少有一个域无法检查时，`uninstall` 保留 plist 并以 exit 1 退出，使恢复句柄得以保存。`--force` 会照样移除它，并说明如何检查以及如何移除可能仍在加载的任务。

## 编程式访问

已发布 manifest 中的命令，除 stdio 的 `tslink mcp` 服务器外，都接受 `--json`，并向 stdout 写出一个带版本的信封，因此 owner 侧自动化就是同一套 CLI 加一个 flag。Cobra 的 `help` 和 `completion` 命令不在 manifest 中，只输出纯文本。MCP client 通过 `tslink mcp` 或远程 MCP 控制面执行同样的操作。

**常见自动化命令：**

| 自动化 action       | 当前入口                                                              |
| ---------------- | ----------------------------------------------------------------- |
| `list`           | `tslink list --json`                                              |
| `add`            | `tslink add <name> --proxy <host:port> --json`（或 `--dir`、`--tcp`） |
| `remove`         | `tslink remove <name> --json`                                     |
| `status`         | `tslink status --json`                                            |
| `doctor`         | `tslink doctor --json`                                            |
| `access_explain` | `tslink access explain <name> --json`                             |
| `template_list`  | `tslink template list --json`                                     |
| `template_plan`  | `tslink template apply <template> --dry-run --json`               |
| `template_apply` | `tslink template apply <template> --yes --json`                   |
| `manifest`       | `tslink manifest --json`                                          |

```bash
# 列出本机已注册的服务
tslink list --json

# 添加服务
tslink add myapp --proxy localhost:3000 --json

# 删除服务
tslink remove myapp --json

# 查看状态
tslink status --json

# 运行只读诊断
tslink doctor --json

# 解释一个服务的本地访问模型
tslink access explain myapp --json

# 先预览、再应用内置模板
tslink template list --json
tslink template apply local-web --dry-run --json
tslink template apply local-web --yes --json
```

`--json` 只改变输出格式。`tslink add --json` 与人类路径使用同一套安全护栏：Funnel 服务必须传 `--public`，TCP 服务会拒绝 `--allow`，因为 TSLink 不会对原始 TCP 字节流应用 HTTP 身份检查。

每个 `--json` 响应都使用 [JSON 输出格式](https://tslink.md/zh/docs/commands.md#json-%E8%BE%93%E5%87%BA%E6%A0%BC%E5%BC%8F) 一节描述的带版本信封：命令载荷放在 `data` 下，结构化错误放在 `error` 下，`command` 记录产生该输出的命令。一次成功（在没有任何服务的机器上执行 `tslink list --json`）如下：

```json
{"type": "tslink.result", "ok": true, "schema_version": 1, "command": "list", "code": 0, "data": {"schema_version": 1, "services": [], "count": 0}}
```

一次用法错误（`tslink list --tailnet --verbose --json`）如下：

```json
{"type": "tslink.result", "ok": false, "schema_version": 1, "command": "list", "code": 2, "error": {"code": "usage_error", "message": "--tailnet conflicts with --verbose; --verbose filters this machine's registered services, while --tailnet reports tailnet devices", "next": ["tslink --help"]}}
```

MCP client 可以通过 `tslink mcp`（stdio）以及 `tslink serve --mcp` 启动的 tailnet-only 远程控制面执行同样的操作；参见 [TSLink 作为 MCP 服务器](https://tslink.md/zh/docs/mcp-server.md)。

### `tslink mcp`

在本地通过 stdio 启动一个 Model Context Protocol 服务器，让 MCP 客户端把 TSLink 当作工具来驱动，而不必解析 CLI 输出。它在 stdin/stdout 上使用按行分隔的 JSON-RPC 2.0，owner 会话暴露 44 个工具，精简角色更少：从 `share`、`list`、`unshare`、`status`，到 `add`、`url`、`tags_*`、`access_explain`、`doctor`、`logs`、`invite_*` 和 `template_*`。

```bash
tslink mcp
```

MCP 进程本身不会打开任何网络监听端口，也不需要 `mcp.allow` 条目；`share` 可能会启动独立的 TSLink daemon 和它请求的 tsnet 服务。协议帧走 stdout、诊断信息走 stderr，因此这里会拒绝 `--json`：stdout 已被协议帧独占。

`tslink serve --mcp` 在一个专用的 tailnet-only tsnet 节点上通过 HTTPS 提供依角色变化的工具集合（owner 为 44 个），供 tailnet 内其它机器上的 MCP client 使用。它默认关闭，且要求 `config.json` 中配置非空 owner `mcp.allow` 或明确 `mcp.bindings`。

客户端配置、完整的工具 schema、远程控制面和端到端示例，请看 [TSLink 作为 MCP 服务器](https://tslink.md/zh/docs/mcp-server.md)。

| 选项               | 说明                                                          |
| ---------------- | ----------------------------------------------------------- |
| `--apps`         | 精简 MCP 会话可见的逗号分隔应用。                                         |
| `--inventory`    | 明确允许 viewer 读取全部应用清单；不授予应用访问或访问历史。                          |
| `--max-duration` | 单应用人员授权的最长时长；精简本地 operator/manager 默认 24h。                  |
| `--scope`        | MCP 角色：owner、viewer、app-operator 或 people-manager；默认 owner。 |

### `tslink manifest`

打印已安装二进制文件对自身的机器可读描述：每个命令、每个标志、退出码、错误码、凭据来源，以及导出的安全能力清单。这是 agent——以及本文档站点自己的 parity 检查——用来确认某个具体安装的构建实际支持什么，而不是相信文字描述的方式。

`tslink manifest` 对 `tslink --help` 隐藏（它是面向机器的命令，不是人类的操作步骤），但它和其它子命令一样可以直接运行：

```bash
tslink manifest              # 完整 manifest，缩进 JSON
tslink manifest --compact    # 仅命令、标志和稳定错误码
tslink manifest --json       # 完整 manifest，包在标准 --json envelope 里
```

**标志：**

| 标志          | 描述             |
| ----------- | -------------- |
| `--compact` | 只打印命令、标志和稳定错误码 |

本文档站点的 CLI parity 检查（`pnpm parity:check`）把已提交的 fixture 与这份 manifest 的形状逐项比对；该 fixture 由 TSLink 自己的 `go run ./tools/gen-manifest` 生成，不是手工维护的。

## Roadmap / Experimental 命令和字段

关于 roadmap 命令和字段（middleware、Docker 集成、Admin REST API、自定义域名/ACME）的详情，请参阅[实验性与路线图](https://tslink.md/zh/docs/experimental-roadmap.md#roadmap-%E5%91%BD%E4%BB%A4)。

## 全局标志

| 标志          | 描述                                                  |
| ----------- | --------------------------------------------------- |
| `--json`    | 带版本 CLI JSON；`tslink mcp` 因 stdout 保留给协议 frames 而拒绝 |
| `--version` | 显示已安装版本                                             |
| `--help`    | 显示任何命令的帮助信息                                         |

```bash
tslink --help
tslink add --help
tslink status --json
```

### 退出码

所有命令返回语义化退出码，便于程序化错误处理：

| 退出码 | 含义   | 示例                                                                                             |
| --- | ---- | ---------------------------------------------------------------------------------------------- |
| 0   | 成功   | 命令完成                                                                                           |
| 1   | 一般错误 | 意外失败                                                                                           |
| 2   | 用法错误 | 无效的参数或标志                                                                                       |
| 3   | 认证错误 | 缺少或无效的凭证                                                                                       |
| 4   | 冲突   | `tslink serve` 时已在运行                                                                           |
| 5   | 未找到  | `tslink url` 指向不存在的服务。`tslink remove` 是幂等的：移除一个未注册的名字是一次成功调用，只是什么都没移除（`removed: false`，exit 0） |
| 64  | 警告   | `tslink doctor` 完成但有警告                                                                         |
| 65  | 严重   | `tslink doctor` 发现严重问题                                                                         |

### JSON 输出格式

传递 `--json` 时，CLI 命令输出 JSON 信封；`tslink mcp` 是明确的例外：

```json
{
  "type": "tslink.result",
  "ok": true,
  "schema_version": 1,
  "command": "status",
  "code": 0,
  "data": {
    "supervision": {
      "manager": "none",
      "installed": false,
      "autostart": false,
      "restart_on_exit": false,
      "detail": "No launchd ownership/autostart could be verified. Run: tslink install"
    },
    "daemon_running": false,
    "daemon_state": "absent",
    "daemon_pid": 0,
    "ownership_proof_available": true,
    "authenticated": false,
    "credential_stored": false,
    "credentials": {
      "api_key": {
        "present": false,
        "expiry_state": "none",
        "early_warning": {
          "state": "none",
          "source": ""
        }
      },
      "client_secret": {
        "present": false,
        "expiry_state": "none",
        "early_warning": {
          "state": "none",
          "source": ""
        }
      }
    },
    "credential_expiry_state": "none",
    "node_authorized": false,
    "authorized_service_count": 0,
    "auth_status": "not_authenticated",
    "service_count": 0,
    "services": [],
    "guest_links": [],
    "access_log": {
      "current": false,
      "enabled": true,
      "last_write": null,
      "drops": 0,
      "size_bytes": 0,
      "error": "access_log_not_started",
      "updated_at": "0001-01-01T00:00:00Z"
    },
    "portal": {
      "enabled": false,
      "state": "disabled"
    },
    "alerts": {
      "notifier": "none",
      "events": []
    }
  }
}
```

错误时，`error` 字段是带稳定字符串 `code` 的结构化对象。下面是网关未运行时 `tslink url nonexistent --json` 的实际前提错误；即使名称不存在，也会先返回 exit `1` / `daemon_not_running`。网关运行后，对不存在服务的查询才会到达 exit `5` / `not_found`：

```json
{
  "type": "tslink.result",
  "ok": false,
  "schema_version": 1,
  "command": "url",
  "code": 1,
  "error": {
    "code": "daemon_not_running",
    "message": "TSLink is not running; install and start its background service with 'tslink install'",
    "next": [
      "tslink install"
    ]
  }
}
```

## 应用与访问者

### `tslink apps`

通过 recipes 列出、发现和分享自托管应用，不安装或配置应用。

### `tslink apps list`

列出带版本的 recipe catalog 与应用侧配置建议。

### `tslink apps detect`

只读、有限的 loopback HTTP GET 指纹检测。部分结果返回 complete: false；匹配不能证明认证或健康。

### `tslink apps share`

默认预览，--yes 才应用，--dry-run 优先。保留已有名称；先核对应用登录、proxy trust、健康路径与主机端口。

| Flag                           | 说明                                                                                |
| ------------------------------ | --------------------------------------------------------------------------------- |
| `--ack-unlimited-request-body` | 明确确认移除 HTTP 请求体上限；需 --max-request-body unlimited。                                 |
| `--allow`                      | 私有 HTTP 身份，逗号分隔；与 Funnel 冲突。                                                      |
| `--control-url`                | 自定义控制服务器 URL；不能与 Funnel 同用。                                                       |
| `--dry-run`                    | 只预览，不写入，即使同时使用 --yes。                                                             |
| `--ephemeral`                  | 请求临时节点，不会自动删除本地注册信息。                                                              |
| `--force-unsafe-public`        | 危险：覆盖 recipe 的 never-public 拒绝，可能公开主机控制或私有数据；需 --funnel --public，add 还需 --recipe。 |
| `--funnel`                     | 公共互联网暴露，需 --public；不支持人员授权或 --allow。                                              |
| `--funnel-ttl`                 | 访问期限至少 1h，默认 24h，公网上限默认 7d；支持相对/绝对形式，新的公开拒绝 never。                                |
| `--health-body`                | 匹配前 64 KiB 正文中的子串，仅 proxy；勿把秘密放入参数。                                               |
| `--health-interval`            | 后端检查间隔，10s 至 1d，默认 1m。                                                            |
| `--health-path`                | HTTP 后端检查路径，默认 /，仅 proxy。                                                         |
| `--health-status-max`          | HTTP 期望状态上限，默认 299，仅 proxy。                                                       |
| `--health-status-min`          | HTTP 期望状态下限，默认 200，仅 proxy。                                                       |
| `--health-timeout`             | 后端检查超时，100ms 至 30s，默认 5s。                                                         |
| `--idle-timeout`               | HTTP keep-alive 空闲超时，默认 60s。                                                      |
| `--max-request-body`           | HTTP 上传上限，默认 32MiB；unlimited 需 --ack-unlimited-request-body。                      |
| `--name`                       | 覆盖 recipe 默认服务名。                                                                  |
| `--no-auto-provision`          | 禁用 Funnel policy 自动配置。                                                            |
| `--no-daemon-install`          | 只保存注册表，不安装或启动 daemon。                                                             |
| `--preserve-host`              | 转发节点可信的 canonical 外部 Host，仅 proxy；普通注册默认关闭，recipe 有自己的默认值。                        |
| `--proxy`                      | 覆盖实际 loopback HTTP(S) 主机端口。                                                       |
| `--public`                     | 明确确认 Funnel 将应用公开到互联网。                                                            |
| `--request-header-timeout`     | HTTP header 读取超时，默认 10s。                                                          |
| `--request-read-timeout`       | 请求体无读取进展的最长时间，默认 30s，不是总上传时长。                                                     |
| `--tags`                       | 逗号分隔的 tag: 标签；Tier 1 入网仍为用户拥有的无标签节点。                                              |
| `--yes`                        | 应用审阅过的 recipe plan；add 需要 --recipe。                                               |

```bash
tslink apps share jellyfin
tslink apps share jellyfin --yes
```

### `tslink people`

管理人员的私有 HTTP/文件访问。TCP 与公共 Funnel 无法按人限制。见[人员分享](https://tslink.md/zh/docs/people-sharing.md)。

### `tslink people add`

为新访问者授权，--apps 必填。首次授权收紧应用范围。--invite 创建逐应用邀请，--print-links 展示 bearer link。本地授权不需 token；创建邀请需用户自己的 API token。

| Flag            | 说明                                                                                           |
| --------------- | -------------------------------------------------------------------------------------------- |
| `--apps`        | 私有 HTTP/文件应用，逗号分隔，或当前支持应用的 all；不含未来应用。                                                       |
| `--for`         | 访问期限；预设 1h、8h、24h、3d、7d，支持相对或 `until <date/time>`；新人员默认 24h，update 省略时保留期限。Guest/公开拒绝 never。 |
| `--invite`      | 创建或续做逐应用 single-use device invitations，需用户自己的 API token。                                     |
| `--print-links` | 明确展示 bearer invitation URL；需要 --invite。                                                      |

```bash
tslink people add alice@example.com --apps photos --for 7d
```

| 选项            | 说明                                                                       |
| ------------- | ------------------------------------------------------------------------ |
| `--ack-never` | 明确确认 tailnet 成员永久授权；device-invited guest 不允许。                            |
| `--qr`        | 把准确 portal URL（portal 停用时首个有效应用）渲染成终端 QR。                                |
| `--qr-invite` | 改为指定应用的 bearer 邀请；需 --invite --print-links 加 --qr 或 --qr-png，QR 是凭证。     |
| `--qr-png`    | 把私有 QR PNG 写入已存在目录；JSON 只包含 payload 文本。                                  |
| `--until`     | 绝对截止时间：RFC3339、YYYY-MM-DD 或 YYYY-MM-DDTHH:MM，无 offset 时用本地时区；与 --for 互斥。 |

### `tslink people list`

读取人员、授权、期限与撤销记录，不创建邀请。

### `tslink people update`

修改应用范围、期限或明确续做邀请。只改应用时保留已有期限，新增应用默认 24h；--for 应用于所有选定应用。查看 complete 与逐应用 state/code。未知 POST 需主人核对后 reconcile；替换邀请保留授权和期限。

| Flag                 | 说明                                                                                           |
| -------------------- | -------------------------------------------------------------------------------------------- |
| `--apps`             | 私有 HTTP/文件应用，逗号分隔，或当前支持应用的 all；不含未来应用。                                                       |
| `--for`              | 访问期限；预设 1h、8h、24h、3d、7d，支持相对或 `until <date/time>`；新人员默认 24h，update 省略时保留期限。Guest/公开拒绝 never。 |
| `--invite`           | 创建或续做逐应用 single-use device invitations，需用户自己的 API token。                                     |
| `--print-links`      | 明确展示 bearer invitation URL；需要 --invite。                                                      |
| `--reconcile-invite` | 主人核对后指定 app=id 或 app=none 解决未知 POST；update 需 --invite。                                       |
| `--replace-invite`   | 主人确认 app=旧记录ID，远端证实缺失后替换；需 --invite，不能同时 --apps 或 --for。                                     |

| 选项            | 说明                                                                       |
| ------------- | ------------------------------------------------------------------------ |
| `--ack-never` | 明确确认 tailnet 成员永久授权；device-invited guest 不允许。                            |
| `--qr`        | 把准确 portal URL（portal 停用时首个有效应用）渲染成终端 QR。                                |
| `--qr-invite` | 改为指定应用的 bearer 邀请；需 --invite --print-links 加 --qr 或 --qr-png，QR 是凭证。     |
| `--qr-png`    | 把私有 QR PNG 写入已存在目录；JSON 只包含 payload 文本。                                  |
| `--until`     | 绝对截止时间：RFC3339、YYYY-MM-DD 或 YYYY-MM-DDTHH:MM，无 offset 时用本地时区；与 --for 互斥。 |

### `tslink people remove`

先保存本地拒绝，再清理待接受邀请。没有 token 也能拒绝；远端清理可能未完成，已接受的网络分享可能保留。新 HTTP/文件请求被拒绝，已有流可继续。

| Flag                 | 说明                                                     |
| -------------------- | ------------------------------------------------------ |
| `--reconcile-invite` | 主人核对后指定 app=id 或 app=none 解决未知 POST；update 需 --invite。 |

## 访问流程与历史

### `tslink access log`

读取保留的历史与汇总，不调用远程 API、不创建文件/锁。见[访问历史](https://tslink.md/zh/docs/access-history.md)。

| 选项           | 类型 / 默认值    | 说明                                     |
| ------------ | ----------- | -------------------------------------- |
| `--app`      | string      | 按应用/服务过滤。                              |
| `--decision` | string      | 过滤 allowed 或 denied。                   |
| `--limit`    | int / `100` | 返回事件数量 1..10000；summary 覆盖所有匹配，默认 100。 |
| `--since`    | string      | 正 Go duration（如 24h）或 RFC3339 下界。      |
| `--until`    | string      | RFC3339 上界，包含端点。                       |
| `--who`      | string      | 按 login、节点名或 tag 过滤。                   |

### `tslink access path <app> <prefix|full|off|inherit|true|false>`

设置单应用路径隐私。全局 off 优先，full 可能保存敏感应用路径，inherit 清除本地覆盖。

### `tslink extend <service>`

把一个人员授权（--person）或 Funnel（省略）设为当前时间加时长，始终返回 JSON。到期需 --regrant，撤销仍拒绝。见[期限](https://tslink.md/zh/docs/durations.md)。

| 选项            | 类型 / 默认值       | 说明                                                                                           |
| ------------- | -------------- | -------------------------------------------------------------------------------------------- |
| `--ack-never` | bool / `false` | 明确确认 tailnet 成员永久授权；device-invited guest 不允许。                                                |
| `--for`       | string         | 访问期限；预设 1h、8h、24h、3d、7d，支持相对或 `until <date/time>`；新人员默认 24h，update 省略时保留期限。Guest/公开拒绝 never。 |
| `--person`    | string         | 选择人员在此服务上的授权；省略时选择 Funnel。                                                                   |
| `--regrant`   | bool / `false` | 明确重新激活已到期授权或 Funnel；人员撤销仍生效。                                                                 |
| `--until`     | string         | 绝对截止时间：RFC3339、YYYY-MM-DD 或 YYYY-MM-DDTHH:MM，无 offset 时用本地时区；与 --for 互斥。                     |

### `tslink guest`

管理单个 HTTP proxy 应用的浏览器 guest 授权，公网 Funnel 必须有访客 gate。

### `tslink guest create <app>`

\--for 必填，启用 gate 时显式 --public。--print-link 前需私有应用在线，只展示一次 bearer 链接；拒绝 file/TCP 与已有开放 Funnel。见[访客链接](https://tslink.md/zh/docs/guest-links.md)。

| 选项             | 类型 / 默认值       | 说明                                             |
| -------------- | -------------- | ---------------------------------------------- |
| `--for`        | string         | 必填，按共同期限策略选择；预设 1h、8h、24h、3d、7d，访客/公开拒绝 never。 |
| `--label`      | string         | 主人给链接的备注，不代表身份。                                |
| `--pin`        | bool / `false` | 从隐藏终端输入或 stdin 读取 PIN，不得放入 argv。               |
| `--print-link` | bool / `false` | 明确返回一次 bearer 链接与可发送消息。                        |
| `--public`     | bool / `false` | 确认通过 Funnel 暴露公网，并强制访客认证。                      |

### `tslink guest list`

列出不含秘密的授权、期限和本地使用估计，不返回 bearer token。

### `tslink guest revoke <id>`

永久撤销该 ID，取消受跟踪的 guest 流；已经接受的有界请求可能完成。

### `tslink guest show <id>`

检查单个授权，不恢复链接或 PIN。

### `tslink mcp-audit`

Owner 读取有界 intent/completion receipt；缺 completion 表示结果未知，不保证审计完整。

### `tslink portal`

管理每台主机的私有 portal，不汇总多主机、不改变应用注册。

### `tslink portal disable`

只关闭 portal 监听，保留入网状态及 owner/admin 身份。

### `tslink portal enable`

\--owner 必填，保存私有 portal 配置，由 daemon 应用。用 status --urls 获取准确运行 URL。远程 MCP 还需当前 portal owner；首次设置/恢复需本地进行。

| 选项           | 类型 / 默认值           | 说明                             |
| ------------ | ------------------ | ------------------------------ |
| `--admins`   | stringSlice / `[]` | 额外管理员 login；这些身份可打开所有私有应用。     |
| `--funnel`   | bool / `false`     | 明确拒绝：portal 必须保持 tailnet-only。 |
| `--hostname` | string / `home`    | Portal 节点 hostname，默认 home。    |
| `--owner`    | string             | 主人实际的 Tailscale login，必填。      |

### `tslink requests`

查看真人 tailnet 成员从 portal 发出的访问请求。见[portal 与请求](https://tslink.md/zh/docs/portal-requests.md)。

### `tslink requests approve <id>`

\--for 必填，按主人选择的期限授权单应用；相同重试不续期。远程 owner/people-manager 还需当前 portal owner、应用/期限范围；精简角色要求人员已存在。

| 选项            | 类型 / 默认值       | 说明                                             |
| ------------- | -------------- | ---------------------------------------------- |
| `--ack-never` | bool / `false` | 明确确认 tailnet 成员永久授权；device-invited guest 不允许。  |
| `--for`       | string         | 必填，按共同期限策略选择；预设 1h、8h、24h、3d、7d，访客/公开拒绝 never。 |

### `tslink requests deny <id>`

拒绝一个请求，相同重试返回原决定；远程调用有相同 portal-owner 检查。

| 选项         | 类型 / 默认值 | 说明                         |
| ---------- | -------- | -------------------------- |
| `--reason` | string   | 可选拒绝原因，最多 500 个字符，会展示给访问者。 |

### `tslink requests list`

读取持久 inbox，保留维护可能写入；备注是不可信数据。

普通命令继承 `--json`；`extend` 始终返回 JSON。MCP stdout 专用于协议 frames。
