@fengye404/termpilot 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +49 -200
  2. package/dist/cli.js +45 -29
  3. package/docs/.vitepress/config.mts +64 -0
  4. package/docs/.vitepress/dist/404.html +23 -0
  5. package/docs/.vitepress/dist/README.html +26 -0
  6. package/docs/.vitepress/dist/architecture.html +42 -0
  7. package/docs/.vitepress/dist/assets/README.md.B4-OJVNQ.js +1 -0
  8. package/docs/.vitepress/dist/assets/README.md.B4-OJVNQ.lean.js +1 -0
  9. package/docs/.vitepress/dist/assets/app.BG4pRgiG.js +1 -0
  10. package/docs/.vitepress/dist/assets/architecture.md.JnC1zyYV.js +17 -0
  11. package/docs/.vitepress/dist/assets/architecture.md.JnC1zyYV.lean.js +1 -0
  12. package/docs/.vitepress/dist/assets/chunks/@localSearchIndexroot.l5vdUGaZ.js +1 -0
  13. package/docs/.vitepress/dist/assets/chunks/VPLocalSearchBox.DMeTzGam.js +9 -0
  14. package/docs/.vitepress/dist/assets/chunks/framework.BZohXCq9.js +19 -0
  15. package/docs/.vitepress/dist/assets/chunks/theme.D0_6rd9F.js +2 -0
  16. package/docs/.vitepress/dist/assets/development.md.iwUVjeO6.js +7 -0
  17. package/docs/.vitepress/dist/assets/development.md.iwUVjeO6.lean.js +1 -0
  18. package/docs/.vitepress/dist/assets/getting-started.md.Cirp1CHi.js +1 -0
  19. package/docs/.vitepress/dist/assets/getting-started.md.Cirp1CHi.lean.js +1 -0
  20. package/docs/.vitepress/dist/assets/index.md.D9XElNRh.js +1 -0
  21. package/docs/.vitepress/dist/assets/index.md.D9XElNRh.lean.js +1 -0
  22. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  23. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  24. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  25. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  26. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  27. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  28. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  29. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  30. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  31. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  32. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  33. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  34. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  35. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  36. package/docs/.vitepress/dist/assets/operations-guide.md.DfNiIg5F.js +18 -0
  37. package/docs/.vitepress/dist/assets/operations-guide.md.DfNiIg5F.lean.js +1 -0
  38. package/docs/.vitepress/dist/assets/protocol.md.CCXFJUPR.js +40 -0
  39. package/docs/.vitepress/dist/assets/protocol.md.CCXFJUPR.lean.js +1 -0
  40. package/docs/.vitepress/dist/assets/style.B0lvUXq1.css +1 -0
  41. package/docs/.vitepress/dist/assets/tech-selection-2026.md.Dk_ymWTx.js +1 -0
  42. package/docs/.vitepress/dist/assets/tech-selection-2026.md.Dk_ymWTx.lean.js +1 -0
  43. package/docs/.vitepress/dist/development.html +32 -0
  44. package/docs/.vitepress/dist/favicon.svg +6 -0
  45. package/docs/.vitepress/dist/getting-started.html +26 -0
  46. package/docs/.vitepress/dist/hashmap.json +1 -0
  47. package/docs/.vitepress/dist/index.html +26 -0
  48. package/docs/.vitepress/dist/operations-guide.html +43 -0
  49. package/docs/.vitepress/dist/protocol.html +65 -0
  50. package/docs/.vitepress/dist/tech-selection-2026.html +26 -0
  51. package/docs/.vitepress/dist/vp-icons.css +1 -0
  52. package/docs/.vitepress/theme/custom.css +42 -0
  53. package/docs/.vitepress/theme/index.ts +10 -0
  54. package/docs/README.md +10 -7
  55. package/docs/getting-started.md +136 -0
  56. package/docs/index.md +57 -0
  57. package/docs/operations-guide.md +6 -6
  58. package/docs/public/favicon.svg +6 -0
  59. package/package.json +6 -2
package/README.md CHANGED
@@ -1,99 +1,60 @@
1
1
  # TermPilot
2
2
 
3
- TermPilot 是一个终端优先的远程控制工具。电脑上跑 `tmux` 会话,手机直接打开 relay 域名查看和控制同一批会话。
3
+ [![npm version](https://img.shields.io/npm/v/%40fengye404%2Ftermpilot)](https://www.npmjs.com/package/@fengye404/termpilot)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40fengye404%2Ftermpilot)](https://www.npmjs.com/package/@fengye404/termpilot)
5
+ [![GitHub Actions](https://img.shields.io/github/actions/workflow/status/fengye404/TermPilot/docs.yml?branch=main&label=docs)](https://github.com/fengye404/TermPilot/actions)
4
6
 
5
- 如果你已经准备长期部署,直接看完整运维文档:
7
+ TermPilot 是一个终端优先的远程控制工具。它把电脑上的 `tmux` 会话暴露给手机浏览器,让你在电脑和手机之间无缝切换,同步查看和控制同一批任务。
6
8
 
7
- - [部署与运维指南](/Users/fengye/workspace/TermPilot/docs/operations-guide.md)
8
-
9
- ## 产品形态
9
+ ## 为什么是它
10
10
 
11
11
  - 一个 npm 包:`@fengye404/termpilot`
12
12
  - 一个服务器命令:`termpilot relay`
13
13
  - 一个电脑命令:`termpilot agent`
14
14
  - 手机端不安装,直接打开 relay 域名
15
- - relay 同时负责消息中继和网页托管
16
-
17
- ## 5 分钟快速上手
18
-
19
- ### 1. 启动 relay
15
+ - relay 同时负责 Web UI 托管和 WebSocket 中继
20
16
 
21
- 在云服务器或一台能被手机访问到的机器上执行:
17
+ ## 30 秒理解工作流
22
18
 
23
- ```bash
24
- npm install -g @fengye404/termpilot
25
- termpilot relay
26
- ```
19
+ 1. 在服务器执行 `termpilot relay`
20
+ 2. 在电脑执行 `termpilot agent`
21
+ 3. 手机上打开 relay 域名并输入配对码
22
+ 4. 在电脑直接执行 `termpilot claude code`
23
+ 5. 手机和电脑同时看到同一个会话输出
27
24
 
28
- 默认情况下,`termpilot relay` 会直接在后台启动 relay,不占当前窗口。
25
+ ## 快速开始
29
26
 
30
- 常用 relay 管理命令:
27
+ ### 1. 启动 relay
31
28
 
32
29
  ```bash
30
+ npm install -g @fengye404/termpilot
33
31
  termpilot relay
34
- termpilot relay stop
35
- termpilot relay run
36
32
  ```
37
33
 
38
- - `termpilot relay` 或 `termpilot relay start`:后台启动
39
- - `termpilot relay stop`:停止后台 relay
40
- - `termpilot relay run`:前台运行,适合看日志和排查问题
41
-
42
- 如果你只是先本地体验,也可以直接在自己电脑上跑 relay,然后让手机走局域网访问。
43
-
44
- ### 2. 启动电脑 agent
45
-
46
- 在你的电脑上执行:
34
+ ### 2. 启动 agent
47
35
 
48
36
  ```bash
49
37
  npm install -g @fengye404/termpilot
50
38
  termpilot agent
51
39
  ```
52
40
 
53
- 如果这是第一次运行,`termpilot agent` 会直接在终端里引导你:
41
+ 第一次运行时,`termpilot agent` 会交互式询问:
54
42
 
55
- 1. 输入 relay 域名或 IP
56
- 2. 输入端口,直接回车默认 `8787`
57
- 3. 自动保存本机配置
58
- 4. 后台启动 agent
59
- 5. 输出一次性配对码
43
+ 1. relay 域名或 IP
44
+ 2. 端口,默认 `8787`
60
45
 
61
- 以后日常只需要继续执行:
62
-
63
- ```bash
64
- termpilot agent
65
- ```
66
-
67
- 这条命令会根据当前状态自动处理:
68
-
69
- - 没有后台 agent:按本机已保存配置启动
70
- - 已经有后台 agent:直接显示当前状态
71
- - 想重新给手机配对:执行 `termpilot agent --pair`
72
-
73
- 常用管理命令:
74
-
75
- ```bash
76
- termpilot agent status
77
- termpilot agent stop
78
- termpilot agent --pair
79
- ```
46
+ 然后它会自动保存配置、后台启动 agent,并打印一次性配对码。
80
47
 
81
48
  ### 3. 手机完成配对
82
49
 
83
- 手机浏览器直接打开 relay 域名:
50
+ 手机浏览器打开:
84
51
 
85
52
  - `http://your-domain.com:8787`
86
- - 或反代后的 `https://your-domain.com`
87
-
88
- 然后:
89
-
90
- 1. 输入电脑端刚打印出来的配对码
91
- 2. 点“配对”
92
- 3. 成功后直接进入会话列表
53
+ - `https://your-domain.com`
93
54
 
94
- ### 4. 直接跑一个可同步的任务
55
+ 输入配对码后,直接进入会话列表。
95
56
 
96
- 日常最短路径是:
57
+ ### 4. 直接启动任务
97
58
 
98
59
  ```bash
99
60
  termpilot claude code
@@ -105,163 +66,51 @@ termpilot claude code
105
66
  termpilot open code
106
67
  ```
107
68
 
108
- 这会直接:
69
+ 这会直接创建一个受 TermPilot 管理的 `tmux` 会话并 attach 到当前终端,手机端会同步看到同一个会话。
109
70
 
110
- - 创建一个受 TermPilot 管理的 `tmux` 会话
111
- - 把命令写进这个会话
112
- - 当前终端自动 attach 进去
113
- - 手机端同步看到同一个会话
114
-
115
- ### 5. 你现在应该能做到什么
116
-
117
- 此时你可以:
118
-
119
- - 在电脑上看 `claude code` / `open code` 的流式输出
120
- - 在手机上看同一份输出
121
- - 在手机上补一条命令、发快捷键、关闭会话
122
- - 随时在电脑和手机之间切换
123
-
124
- ### 服务器
125
-
126
- 发布后:
127
-
128
- ```bash
129
- npm install -g @fengye404/termpilot
130
- termpilot relay
131
- ```
132
-
133
- 当前仓库内本地验证:
134
-
135
- ```bash
136
- pnpm install
137
- pnpm build
138
- npm install -g .
139
- termpilot relay
140
- ```
141
-
142
- 常用参数:
71
+ ## 常用命令
143
72
 
144
73
  ```bash
145
74
  termpilot relay
146
- termpilot relay run
147
75
  termpilot relay stop
148
- DATABASE_URL=postgresql://user:pass@127.0.0.1:5432/termpilot termpilot relay
149
- ```
150
-
151
- ### 电脑
152
-
153
- ```bash
154
- npm install -g @fengye404/termpilot
76
+ termpilot relay run
155
77
  termpilot agent
156
- ```
157
-
158
- 如果你只是想看调试日志,可以显式前台运行:
159
-
160
- ```bash
161
- termpilot agent --foreground
162
- ```
163
-
164
- 查看后台状态:
165
-
166
- ```bash
78
+ termpilot agent --pair
167
79
  termpilot agent status
168
- ```
169
-
170
- 停止后台 agent:
171
-
172
- ```bash
173
80
  termpilot agent stop
174
- ```
175
-
176
- 本地测试:
177
-
178
- ```bash
179
- termpilot agent
180
- ```
181
-
182
- ### 手机
183
-
184
- 直接打开 relay 域名:
185
-
186
- - `https://your-domain.com`
187
-
188
- 首次使用时,直接执行上面的 `termpilot agent` 就会进入配置引导并拿到配对码;如果你已经跑着后台 agent、只是想重新给手机配对,用 `termpilot agent --pair`。
189
-
190
- 配对成功后:
191
-
192
- - 访问令牌会自动写回页面
193
- - 手机端默认先显示会话列表
194
- - 点进一个会话后才进入终端详情页
195
- - 连接信息和设备设置都在页面底部折叠区
196
-
197
- ## 日常使用
198
-
199
- ### 直接把命令交给 TermPilot
200
-
201
- ```bash
202
- termpilot agent
203
81
  termpilot claude code
204
82
  termpilot open code
205
- ```
206
-
207
- 如果你想跑别的命令,也可以直接:
208
-
209
- ```bash
210
- termpilot npm run dev
211
- termpilot python worker.py
212
- ```
213
-
214
- ### 手动管理会话
215
-
216
- 创建会话并进入:
217
-
218
- ```bash
219
- termpilot create --name claude-main --cwd /path/to/project
83
+ termpilot create --name my-task --cwd /path/to/project
220
84
  termpilot list
221
85
  termpilot attach --sid <sid>
86
+ termpilot kill --sid <sid>
222
87
  ```
223
88
 
224
- 进入会话以后,你仍然可以自己手动运行:
89
+ ## 文档
225
90
 
226
- ```bash
227
- claude code
228
- #
229
- open code
230
- ```
91
+ - [文档首页](./docs/index.md)
92
+ - [快速开始](./docs/getting-started.md)
93
+ - [部署与运维指南](./docs/operations-guide.md)
94
+ - [当前架构](./docs/architecture.md)
95
+ - [当前协议](./docs/protocol.md)
96
+ - [开发文档](./docs/development.md)
231
97
 
232
- 此时手机和电脑看到的是同一个会话,输出会同步刷新。
98
+ ## 最佳实践
233
99
 
234
- ## 常用命令
100
+ 1. 需要跨端同步的任务,一开始就用 `termpilot claude code`、`termpilot open code` 或 `termpilot create` 启动,不要先在普通终端里跑再想着接管。
101
+ 2. `termpilot agent` 适合作为长期后台入口。第一次配置完之后,日常只需要记住这一条命令。
102
+ 3. relay 长期使用时,优先挂到 HTTPS/WSS 域名后面;本地演示再用裸 IP 和 `8787`。
103
+ 4. 手机更适合看输出、发短命令和轻控制;电脑前的重输入仍然建议在本地终端完成。
104
+
105
+ ## 本地开发
235
106
 
236
107
  ```bash
237
- termpilot relay
238
- termpilot relay stop
239
- termpilot relay run
240
- termpilot agent
241
- termpilot agent --pair
242
- termpilot agent status
243
- termpilot agent stop
244
- termpilot create --name claude-main
245
- termpilot list
246
- termpilot attach --sid <sid>
247
- termpilot kill --sid <sid>
248
- termpilot grants
249
- termpilot audit --limit 30
250
- termpilot revoke --token <accessToken>
251
- termpilot doctor
108
+ pnpm install
109
+ pnpm build
110
+ pnpm docs:dev
111
+ pnpm test:ui-smoke
112
+ pnpm check:stability
252
113
  ```
253
-
254
- ## 最佳实践
255
-
256
- 1. 需要跨端同步的任务,一开始就用 `termpilot create` 创建,不要先在普通终端里跑再想着接管。
257
- 2. 第一次先跑一次 `termpilot agent` 完成本机配置,之后日常就只需要记住这一条命令。
258
- 3. 如果只是想“开一个会话然后立刻跑起来”,优先用 `termpilot claude code` 这类直达命令,不必手动 `create + attach`。
259
- 4. 一个长期任务用一个独立会话,名称直接写任务语义,比如 `claude-main`、`deploy-watch`、`batch-fix`。
260
- 5. 电脑前重操作优先 `termpilot attach`;手机更适合看进度、发短命令、补快捷键和关闭会话。
261
- 6. 普通 iTerm / Terminal 标签页不是 TermPilot 管理对象,不要指望后面“无缝接管”进来。
262
- 7. 手机优先走一次性配对码,不要长期传播访问令牌。
263
- 8. 要长期使用 relay,优先放到 HTTPS/WSS 域名后面,并接 PostgreSQL;本地演示可以先用内存模式。
264
- 9. 换手机或访问权变更时,先 `termpilot grants`,再 `termpilot revoke --token ...`。
265
114
  10. 想排查控制历史时先看 `termpilot audit --limit 30`。
266
115
  11. 服务器上日常用 `termpilot relay` 后台运行;只有排查问题时才用 `termpilot relay run`。
267
116
 
package/dist/cli.js CHANGED
@@ -849,11 +849,7 @@ function isLocalRelayHost(hostname) {
849
849
  }
850
850
  function normalizeRelayUrl(rawHost, rawPort) {
851
851
  const hostInput = rawHost.trim();
852
- const portInput = rawPort.trim() || "8787";
853
- const normalizedPort = Number(portInput);
854
- if (!Number.isFinite(normalizedPort) || normalizedPort <= 0 || normalizedPort > 65535) {
855
- throw new Error("\u7AEF\u53E3\u65E0\u6548\uFF0C\u8BF7\u8F93\u5165 1 \u5230 65535 \u4E4B\u95F4\u7684\u6570\u5B57\u3002");
856
- }
852
+ const portInput = rawPort.trim();
857
853
  if (hostInput.includes("://")) {
858
854
  const parsed = new URL(hostInput);
859
855
  if (parsed.protocol === "http:") {
@@ -861,8 +857,14 @@ function normalizeRelayUrl(rawHost, rawPort) {
861
857
  } else if (parsed.protocol === "https:") {
862
858
  parsed.protocol = "wss:";
863
859
  }
864
- if (!parsed.port) {
865
- parsed.port = String(normalizedPort);
860
+ if (portInput) {
861
+ const normalizedPort2 = Number(portInput);
862
+ if (!Number.isFinite(normalizedPort2) || normalizedPort2 <= 0 || normalizedPort2 > 65535) {
863
+ throw new Error("\u7AEF\u53E3\u65E0\u6548\uFF0C\u8BF7\u8F93\u5165 1 \u5230 65535 \u4E4B\u95F4\u7684\u6570\u5B57\u3002");
864
+ }
865
+ parsed.port = String(normalizedPort2);
866
+ } else if (!parsed.port && parsed.protocol === "ws:" && isLocalRelayHost(parsed.hostname)) {
867
+ parsed.port = "8787";
866
868
  }
867
869
  if (!parsed.pathname || parsed.pathname === "/") {
868
870
  parsed.pathname = "/ws";
@@ -872,6 +874,16 @@ function normalizeRelayUrl(rawHost, rawPort) {
872
874
  return parsed.toString();
873
875
  }
874
876
  const protocol = isLocalRelayHost(hostInput) ? "ws:" : "wss:";
877
+ if (!portInput) {
878
+ if (protocol === "ws:") {
879
+ return `${protocol}//${hostInput}:8787/ws`;
880
+ }
881
+ return `${protocol}//${hostInput}/ws`;
882
+ }
883
+ const normalizedPort = Number(portInput);
884
+ if (!Number.isFinite(normalizedPort) || normalizedPort <= 0 || normalizedPort > 65535) {
885
+ throw new Error("\u7AEF\u53E3\u65E0\u6548\uFF0C\u8BF7\u8F93\u5165 1 \u5230 65535 \u4E4B\u95F4\u7684\u6570\u5B57\u3002");
886
+ }
875
887
  return `${protocol}//${hostInput}:${normalizedPort}/ws`;
876
888
  }
877
889
  async function promptForAgentConfig(deviceId) {
@@ -885,7 +897,7 @@ async function promptForAgentConfig(deviceId) {
885
897
  if (!host) {
886
898
  throw new Error("\u672A\u8F93\u5165 relay \u57DF\u540D\u6216 IP\uFF0C\u5DF2\u53D6\u6D88\u3002");
887
899
  }
888
- const port = await rl.question("\u8BF7\u8F93\u5165 relay \u7AEF\u53E3\uFF08\u76F4\u63A5\u56DE\u8F66\u9ED8\u8BA4 8787\uFF09: ");
900
+ const port = await rl.question("\u8BF7\u8F93\u5165 relay \u7AEF\u53E3\uFF08\u53CD\u4EE3\u57DF\u540D\u53EF\u76F4\u63A5\u56DE\u8F66\uFF1B\u672C\u5730 IP \u9ED8\u8BA4 8787\uFF09: ");
889
901
  const relayUrl = normalizeRelayUrl(host, port);
890
902
  console.log(`\u5C06\u4F7F\u7528 relay: ${relayUrl}`);
891
903
  return { relayUrl, deviceId };
@@ -1324,7 +1336,8 @@ function loadRelayRuntime() {
1324
1336
  pid: parsed.pid,
1325
1337
  host: parsed.host,
1326
1338
  port: parsed.port,
1327
- startedAt: parsed.startedAt
1339
+ startedAt: parsed.startedAt,
1340
+ cliPath: typeof parsed.cliPath === "string" ? parsed.cliPath : void 0
1328
1341
  };
1329
1342
  } catch {
1330
1343
  return null;
@@ -1842,7 +1855,18 @@ function createStaticPath(webDir, urlPath) {
1842
1855
  return resolvedPath;
1843
1856
  }
1844
1857
  function resolveDefaultWebDir(moduleUrl = import.meta.url) {
1845
- return fileURLToPath(new URL("../../app/dist", moduleUrl));
1858
+ const candidates = [
1859
+ "../../app/dist",
1860
+ "../app/dist",
1861
+ "./app/dist"
1862
+ ];
1863
+ for (const candidate of candidates) {
1864
+ const resolvedDir = fileURLToPath(new URL(candidate, moduleUrl));
1865
+ if (existsSync(path3.join(resolvedDir, "index.html"))) {
1866
+ return resolvedDir;
1867
+ }
1868
+ }
1869
+ return fileURLToPath(new URL("../app/dist", moduleUrl));
1846
1870
  }
1847
1871
  async function startRelayServer(options = {}) {
1848
1872
  const config = {
@@ -2333,26 +2357,14 @@ function readRuntimeStatus2() {
2333
2357
  }
2334
2358
  return { runtime, alive };
2335
2359
  }
2336
- function printRuntime(runtime = readRuntimeStatus2().runtime) {
2337
- if (!runtime) {
2338
- console.log("\u540E\u53F0 relay \u5F53\u524D\u672A\u8FD0\u884C\u3002");
2339
- console.log(`\u8FD0\u884C\u65F6\u6587\u4EF6: ${getRelayRuntimeFilePath()}`);
2340
- console.log(`\u65E5\u5FD7: ${getRelayLogFilePath()}`);
2341
- return;
2342
- }
2343
- console.log("\u540E\u53F0 relay \u6B63\u5728\u8FD0\u884C\u3002");
2344
- console.log(`PID: ${runtime.pid}`);
2345
- console.log(`\u76D1\u542C: http://${runtime.host}:${runtime.port}`);
2346
- console.log(`\u542F\u52A8\u65F6\u95F4: ${runtime.startedAt}`);
2347
- console.log(`\u65E5\u5FD7: ${getRelayLogFilePath()}`);
2348
- }
2349
2360
  async function runForeground() {
2350
2361
  const config = loadConfig();
2351
2362
  saveRelayRuntime({
2352
2363
  pid: process.pid,
2353
2364
  host: config.host,
2354
2365
  port: config.port,
2355
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
2366
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2367
+ cliPath: process.argv[1]
2356
2368
  });
2357
2369
  process.on("exit", () => {
2358
2370
  clearRelayRuntime(process.pid);
@@ -2366,11 +2378,14 @@ async function runStart2() {
2366
2378
  const existing = readRuntimeStatus2();
2367
2379
  if (existing.runtime && existing.alive) {
2368
2380
  const sameConfig = existing.runtime.host === config.host && existing.runtime.port === config.port;
2369
- if (sameConfig) {
2370
- printRuntime(existing.runtime);
2371
- return;
2381
+ const sameCliPath = existing.runtime.cliPath === process.argv[1];
2382
+ if (sameConfig && sameCliPath) {
2383
+ console.log("\u68C0\u6D4B\u5230\u540E\u53F0 relay \u5DF2\u5728\u8FD0\u884C\uFF0C\u6B63\u5728\u91CD\u542F\u5230\u5F53\u524D\u914D\u7F6E\u3002");
2384
+ } else if (!sameCliPath) {
2385
+ console.log("\u68C0\u6D4B\u5230\u540E\u53F0 relay \u6B63\u5728\u8FD0\u884C\uFF0C\u4F46\u5B89\u88C5\u7248\u672C\u6216\u5165\u53E3\u5DF2\u53D8\u5316\uFF0C\u6B63\u5728\u91CD\u542F\u5230\u5F53\u524D\u7248\u672C\u3002");
2386
+ } else {
2387
+ console.log("\u68C0\u6D4B\u5230\u540E\u53F0 relay \u5DF2\u5728\u8FD0\u884C\uFF0C\u4F46\u76D1\u542C\u914D\u7F6E\u548C\u5F53\u524D\u547D\u4EE4\u4E0D\u4E00\u81F4\uFF0C\u6B63\u5728\u91CD\u542F\u3002");
2372
2388
  }
2373
- console.log("\u68C0\u6D4B\u5230\u540E\u53F0 relay \u5DF2\u5728\u8FD0\u884C\uFF0C\u4F46\u76D1\u542C\u914D\u7F6E\u548C\u5F53\u524D\u547D\u4EE4\u4E0D\u4E00\u81F4\uFF0C\u6B63\u5728\u91CD\u542F\u3002");
2374
2389
  await runStop2();
2375
2390
  }
2376
2391
  clearRelayRuntime();
@@ -2389,7 +2404,8 @@ async function runStart2() {
2389
2404
  pid: child.pid,
2390
2405
  host: config.host,
2391
2406
  port: config.port,
2392
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
2407
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2408
+ cliPath: process.argv[1]
2393
2409
  });
2394
2410
  console.log(`\u540E\u53F0 relay \u5DF2\u542F\u52A8\uFF0CPID: ${child.pid}`);
2395
2411
  console.log(`\u76D1\u542C: http://${config.host}:${config.port}`);
@@ -0,0 +1,64 @@
1
+ import { defineConfig } from "vitepress";
2
+
3
+ const repo = process.env.GITHUB_REPOSITORY?.split("/")[1] ?? "TermPilot";
4
+ const base = process.env.GITHUB_ACTIONS ? `/${repo}/` : "/";
5
+
6
+ export default defineConfig({
7
+ lang: "zh-CN",
8
+ title: "TermPilot",
9
+ description: "手机和电脑共享同一个 tmux 会话的终端远程控制工具。",
10
+ base,
11
+ lastUpdated: true,
12
+ cleanUrls: true,
13
+ head: [
14
+ ["meta", { name: "theme-color", content: "#1f7a53" }],
15
+ ],
16
+ themeConfig: {
17
+ siteTitle: "TermPilot",
18
+ logo: "/favicon.svg",
19
+ nav: [
20
+ { text: "首页", link: "/" },
21
+ { text: "快速开始", link: "/getting-started" },
22
+ { text: "部署与运维", link: "/operations-guide" },
23
+ { text: "架构", link: "/architecture" },
24
+ { text: "协议", link: "/protocol" },
25
+ ],
26
+ sidebar: [
27
+ {
28
+ text: "开始",
29
+ items: [
30
+ { text: "文档首页", link: "/" },
31
+ { text: "快速开始", link: "/getting-started" },
32
+ { text: "部署与运维指南", link: "/operations-guide" },
33
+ ],
34
+ },
35
+ {
36
+ text: "参考",
37
+ items: [
38
+ { text: "代码架构", link: "/architecture" },
39
+ { text: "协议说明", link: "/protocol" },
40
+ { text: "开发文档", link: "/development" },
41
+ { text: "技术选型", link: "/tech-selection-2026" },
42
+ ],
43
+ },
44
+ ],
45
+ search: {
46
+ provider: "local",
47
+ },
48
+ socialLinks: [
49
+ { icon: "github", link: "https://github.com/fengye404/TermPilot" },
50
+ ],
51
+ editLink: {
52
+ pattern: "https://github.com/fengye404/TermPilot/edit/main/docs/:path",
53
+ text: "在 GitHub 上编辑此页",
54
+ },
55
+ outline: {
56
+ level: [2, 3],
57
+ label: "本页目录",
58
+ },
59
+ footer: {
60
+ message: "为长期任务和跨端终端控制而构建。",
61
+ copyright: "Copyright © 2026 Fengye",
62
+ },
63
+ },
64
+ });
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN" dir="ltr">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>404 | TermPilot</title>
7
+ <meta name="description" content="Not Found">
8
+ <meta name="generator" content="VitePress v1.6.4">
9
+ <link rel="preload stylesheet" href="/assets/style.B0lvUXq1.css" as="style">
10
+ <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
+
12
+ <script type="module" src="/assets/app.BG4pRgiG.js"></script>
13
+ <link rel="preload" href="/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
14
+ <meta name="theme-color" content="#1f7a53">
15
+ <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
16
+ <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
17
+ </head>
18
+ <body>
19
+ <div id="app"></div>
20
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"architecture.md\":\"JnC1zyYV\",\"development.md\":\"iwUVjeO6\",\"getting-started.md\":\"Cirp1CHi\",\"index.md\":\"D9XElNRh\",\"operations-guide.md\":\"DfNiIg5F\",\"protocol.md\":\"CCXFJUPR\",\"readme.md\":\"B4-OJVNQ\",\"tech-selection-2026.md\":\"Dk_ymWTx\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"zh-CN\",\"dir\":\"ltr\",\"title\":\"TermPilot\",\"description\":\"手机和电脑共享同一个 tmux 会话的终端远程控制工具。\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"siteTitle\":\"TermPilot\",\"logo\":\"/favicon.svg\",\"nav\":[{\"text\":\"首页\",\"link\":\"/\"},{\"text\":\"快速开始\",\"link\":\"/getting-started\"},{\"text\":\"部署与运维\",\"link\":\"/operations-guide\"},{\"text\":\"架构\",\"link\":\"/architecture\"},{\"text\":\"协议\",\"link\":\"/protocol\"}],\"sidebar\":[{\"text\":\"开始\",\"items\":[{\"text\":\"文档首页\",\"link\":\"/\"},{\"text\":\"快速开始\",\"link\":\"/getting-started\"},{\"text\":\"部署与运维指南\",\"link\":\"/operations-guide\"}]},{\"text\":\"参考\",\"items\":[{\"text\":\"代码架构\",\"link\":\"/architecture\"},{\"text\":\"协议说明\",\"link\":\"/protocol\"},{\"text\":\"开发文档\",\"link\":\"/development\"},{\"text\":\"技术选型\",\"link\":\"/tech-selection-2026\"}]}],\"search\":{\"provider\":\"local\"},\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/fengye404/TermPilot\"}],\"editLink\":{\"pattern\":\"https://github.com/fengye404/TermPilot/edit/main/docs/:path\",\"text\":\"在 GitHub 上编辑此页\"},\"outline\":{\"level\":[2,3],\"label\":\"本页目录\"},\"footer\":{\"message\":\"为长期任务和跨端终端控制而构建。\",\"copyright\":\"Copyright © 2026 Fengye\"}},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":true}");</script>
21
+
22
+ </body>
23
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN" dir="ltr">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>docs 目录 | TermPilot</title>
7
+ <meta name="description" content="手机和电脑共享同一个 tmux 会话的终端远程控制工具。">
8
+ <meta name="generator" content="VitePress v1.6.4">
9
+ <link rel="preload stylesheet" href="/assets/style.B0lvUXq1.css" as="style">
10
+ <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
+
12
+ <script type="module" src="/assets/app.BG4pRgiG.js"></script>
13
+ <link rel="preload" href="/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
14
+ <link rel="modulepreload" href="/assets/chunks/theme.D0_6rd9F.js">
15
+ <link rel="modulepreload" href="/assets/chunks/framework.BZohXCq9.js">
16
+ <link rel="modulepreload" href="/assets/README.md.B4-OJVNQ.lean.js">
17
+ <meta name="theme-color" content="#1f7a53">
18
+ <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
19
+ <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
20
+ </head>
21
+ <body>
22
+ <div id="app"><div class="Layout" data-v-1bdd7537><!--[--><!--]--><!--[--><span tabindex="-1" data-v-0a4b5d46></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-0a4b5d46>Skip to content</a><!--]--><!----><header class="VPNav" data-v-1bdd7537 data-v-07e0ee65><div class="VPNavBar" data-v-07e0ee65 data-v-bfcfe748><div class="wrapper" data-v-bfcfe748><div class="container" data-v-bfcfe748><div class="title" data-v-bfcfe748><div class="VPNavBarTitle has-sidebar" data-v-bfcfe748 data-v-6ce24249><a class="title" href="/" data-v-6ce24249><!--[--><!--]--><!--[--><img class="VPImage logo" src="/favicon.svg" alt data-v-105fdaa6><!--]--><span data-v-6ce24249>TermPilot</span><!--[--><!--]--></a></div></div><div class="content" data-v-bfcfe748><div class="content-body" data-v-bfcfe748><!--[--><!--]--><div class="VPNavBarSearch search" data-v-bfcfe748><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-bfcfe748 data-v-d1dc8fec><span id="main-nav-aria-label" class="visually-hidden" data-v-d1dc8fec> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-d1dc8fec data-v-82ee0671><!--[--><span data-v-82ee0671>首页</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/getting-started" tabindex="0" data-v-d1dc8fec data-v-82ee0671><!--[--><span data-v-82ee0671>快速开始</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/operations-guide" tabindex="0" data-v-d1dc8fec data-v-82ee0671><!--[--><span data-v-82ee0671>部署与运维</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/architecture" tabindex="0" data-v-d1dc8fec data-v-82ee0671><!--[--><span data-v-82ee0671>架构</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/protocol" tabindex="0" data-v-d1dc8fec data-v-82ee0671><!--[--><span data-v-82ee0671>协议</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-bfcfe748 data-v-d994237e><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-d994237e data-v-9a85cf30 data-v-b1949eaf><span class="check" data-v-b1949eaf><span class="icon" data-v-b1949eaf><!--[--><span class="vpi-sun sun" data-v-9a85cf30></span><span class="vpi-moon moon" data-v-9a85cf30></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-bfcfe748 data-v-a61e22dd data-v-f3c77898><!--[--><a class="VPSocialLink no-icon" href="https://github.com/fengye404/TermPilot" aria-label="github" target="_blank" rel="noopener" data-v-f3c77898 data-v-8a10446f><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-bfcfe748 data-v-6d16081b data-v-35350ec6><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-35350ec6><span class="vpi-more-horizontal icon" data-v-35350ec6></span></button><div class="menu" data-v-35350ec6><div class="VPMenu" data-v-35350ec6 data-v-52b08d90><!----><!--[--><!--[--><!----><div class="group" data-v-6d16081b><div class="item appearance" data-v-6d16081b><p class="label" data-v-6d16081b>Appearance</p><div class="appearance-action" data-v-6d16081b><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-6d16081b data-v-9a85cf30 data-v-b1949eaf><span class="check" data-v-b1949eaf><span class="icon" data-v-b1949eaf><!--[--><span class="vpi-sun sun" data-v-9a85cf30></span><span class="vpi-moon moon" data-v-9a85cf30></span><!--]--></span></span></button></div></div></div><div class="group" data-v-6d16081b><div class="item social-links" data-v-6d16081b><div class="VPSocialLinks social-links-list" data-v-6d16081b data-v-f3c77898><!--[--><a class="VPSocialLink no-icon" href="https://github.com/fengye404/TermPilot" aria-label="github" target="_blank" rel="noopener" data-v-f3c77898 data-v-8a10446f><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-bfcfe748 data-v-1e510fee><span class="container" data-v-1e510fee><span class="top" data-v-1e510fee></span><span class="middle" data-v-1e510fee></span><span class="bottom" data-v-1e510fee></span></span></button></div></div></div></div><div class="divider" data-v-bfcfe748><div class="divider-line" data-v-bfcfe748></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-1bdd7537 data-v-18894e30><div class="container" data-v-18894e30><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-18894e30><span class="vpi-align-left menu-icon" data-v-18894e30></span><span class="menu-text" data-v-18894e30>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-18894e30 data-v-bdfacdf2><button data-v-bdfacdf2>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-1bdd7537 data-v-1d1d7027><div class="curtain" data-v-1d1d7027></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-1d1d7027><span class="visually-hidden" id="sidebar-aria-label" data-v-1d1d7027> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-594fed9e><section class="VPSidebarItem level-0" data-v-594fed9e data-v-cd30f15d><div class="item" role="button" tabindex="0" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><h2 class="text" data-v-cd30f15d>开始</h2><!----></div><div class="items" data-v-cd30f15d><!--[--><div class="VPSidebarItem level-1 is-link" data-v-cd30f15d data-v-cd30f15d><div class="item" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><a class="VPLink link link" href="/" data-v-cd30f15d><!--[--><p class="text" data-v-cd30f15d>文档首页</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-cd30f15d data-v-cd30f15d><div class="item" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><a class="VPLink link link" href="/getting-started" data-v-cd30f15d><!--[--><p class="text" data-v-cd30f15d>快速开始</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-cd30f15d data-v-cd30f15d><div class="item" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><a class="VPLink link link" href="/operations-guide" data-v-cd30f15d><!--[--><p class="text" data-v-cd30f15d>部署与运维指南</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-594fed9e><section class="VPSidebarItem level-0" data-v-594fed9e data-v-cd30f15d><div class="item" role="button" tabindex="0" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><h2 class="text" data-v-cd30f15d>参考</h2><!----></div><div class="items" data-v-cd30f15d><!--[--><div class="VPSidebarItem level-1 is-link" data-v-cd30f15d data-v-cd30f15d><div class="item" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><a class="VPLink link link" href="/architecture" data-v-cd30f15d><!--[--><p class="text" data-v-cd30f15d>代码架构</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-cd30f15d data-v-cd30f15d><div class="item" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><a class="VPLink link link" href="/protocol" data-v-cd30f15d><!--[--><p class="text" data-v-cd30f15d>协议说明</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-cd30f15d data-v-cd30f15d><div class="item" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><a class="VPLink link link" href="/development" data-v-cd30f15d><!--[--><p class="text" data-v-cd30f15d>开发文档</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-cd30f15d data-v-cd30f15d><div class="item" data-v-cd30f15d><div class="indicator" data-v-cd30f15d></div><a class="VPLink link link" href="/tech-selection-2026" data-v-cd30f15d><!--[--><p class="text" data-v-cd30f15d>技术选型</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-1bdd7537 data-v-f89b4fb6><div class="VPDoc has-sidebar has-aside" data-v-f89b4fb6 data-v-1870bdbd><!--[--><!--]--><div class="container" data-v-1870bdbd><div class="aside" data-v-1870bdbd><div class="aside-curtain" data-v-1870bdbd></div><div class="aside-container" data-v-1870bdbd><div class="aside-content" data-v-1870bdbd><div class="VPDocAside" data-v-1870bdbd data-v-2bdf3a15><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-2bdf3a15 data-v-3d03f0c2><div class="content" data-v-3d03f0c2><div class="outline-marker" data-v-3d03f0c2></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-3d03f0c2>本页目录</div><ul class="VPDocOutlineItem root" data-v-3d03f0c2 data-v-87a812ef><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-2bdf3a15></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-1870bdbd><div class="content-container" data-v-1870bdbd><!--[--><!--]--><main class="main" data-v-1870bdbd><div style="position:relative;" class="vp-doc _README" data-v-1870bdbd><div><h1 id="docs-目录" tabindex="-1">docs 目录 <a class="header-anchor" href="#docs-目录" aria-label="Permalink to &quot;docs 目录&quot;">​</a></h1><p>如果你在 GitHub 仓库里浏览源码,推荐从下面几个入口开始:</p><ul><li><a href="./">文档首页</a></li><li><a href="./getting-started">快速开始</a></li><li><a href="./operations-guide">部署与运维指南</a></li><li><a href="./architecture">代码架构</a></li><li><a href="./development">开发文档</a></li></ul><p>如果 GitHub Pages 已经启用,优先看站点版本,它会带导航、目录和搜索。</p></div></div></main><footer class="VPDocFooter" data-v-1870bdbd data-v-b170fa4f><!--[--><!--]--><div class="edit-info" data-v-b170fa4f><div class="edit-link" data-v-b170fa4f><a class="VPLink link vp-external-link-icon no-icon edit-link-button" href="https://github.com/fengye404/TermPilot/edit/main/docs/README.md" target="_blank" rel="noreferrer" data-v-b170fa4f><!--[--><span class="vpi-square-pen edit-link-icon" data-v-b170fa4f></span> 在 GitHub 上编辑此页<!--]--></a></div><div class="last-updated" data-v-b170fa4f><p class="VPLastUpdated" data-v-b170fa4f data-v-33a4642e>Last updated: <time datetime="2026-03-11T17:35:45.000Z" data-v-33a4642e></time></p></div></div><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-b170fa4f><span class="visually-hidden" id="doc-footer-aria-label" data-v-b170fa4f>Pager</span><div class="pager" data-v-b170fa4f><!----></div><div class="pager" data-v-b170fa4f><a class="VPLink link pager-link next" href="/" data-v-b170fa4f><!--[--><span class="desc" data-v-b170fa4f>Next page</span><span class="title" data-v-b170fa4f>文档首页</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><footer class="VPFooter has-sidebar" data-v-1bdd7537 data-v-9a5ea44b><div class="container" data-v-9a5ea44b><p class="message" data-v-9a5ea44b>为长期任务和跨端终端控制而构建。</p><p class="copyright" data-v-9a5ea44b>Copyright © 2026 Fengye</p></div></footer><!--[--><!--]--></div></div>
23
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"architecture.md\":\"JnC1zyYV\",\"development.md\":\"iwUVjeO6\",\"getting-started.md\":\"Cirp1CHi\",\"index.md\":\"D9XElNRh\",\"operations-guide.md\":\"DfNiIg5F\",\"protocol.md\":\"CCXFJUPR\",\"readme.md\":\"B4-OJVNQ\",\"tech-selection-2026.md\":\"Dk_ymWTx\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"zh-CN\",\"dir\":\"ltr\",\"title\":\"TermPilot\",\"description\":\"手机和电脑共享同一个 tmux 会话的终端远程控制工具。\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"siteTitle\":\"TermPilot\",\"logo\":\"/favicon.svg\",\"nav\":[{\"text\":\"首页\",\"link\":\"/\"},{\"text\":\"快速开始\",\"link\":\"/getting-started\"},{\"text\":\"部署与运维\",\"link\":\"/operations-guide\"},{\"text\":\"架构\",\"link\":\"/architecture\"},{\"text\":\"协议\",\"link\":\"/protocol\"}],\"sidebar\":[{\"text\":\"开始\",\"items\":[{\"text\":\"文档首页\",\"link\":\"/\"},{\"text\":\"快速开始\",\"link\":\"/getting-started\"},{\"text\":\"部署与运维指南\",\"link\":\"/operations-guide\"}]},{\"text\":\"参考\",\"items\":[{\"text\":\"代码架构\",\"link\":\"/architecture\"},{\"text\":\"协议说明\",\"link\":\"/protocol\"},{\"text\":\"开发文档\",\"link\":\"/development\"},{\"text\":\"技术选型\",\"link\":\"/tech-selection-2026\"}]}],\"search\":{\"provider\":\"local\"},\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/fengye404/TermPilot\"}],\"editLink\":{\"pattern\":\"https://github.com/fengye404/TermPilot/edit/main/docs/:path\",\"text\":\"在 GitHub 上编辑此页\"},\"outline\":{\"level\":[2,3],\"label\":\"本页目录\"},\"footer\":{\"message\":\"为长期任务和跨端终端控制而构建。\",\"copyright\":\"Copyright © 2026 Fengye\"}},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":true}");</script>
24
+
25
+ </body>
26
+ </html>