@bulolo/hermes-link 0.2.9 → 0.3.1

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.
package/README.md CHANGED
@@ -1,50 +1,221 @@
1
- # @bulolo/hermes-link
1
+ <div align="center">
2
2
 
3
- 本地伴随服务,为 [Hermes Agent](https://github.com/nousresearch/hermes-agent) 提供移动端接入能力,支持局域网直连。
3
+ # hermes-link
4
+
5
+ **Hermes Agent 本地接入层**
6
+
7
+ 为 [Hermes Agent](https://github.com/nousresearch/hermes-agent) 提供完整的客户端 API、多设备鉴权与对话管理,支持局域网 / 公网直连。
8
+
9
+ [![npm](https://img.shields.io/npm/v/@bulolo/hermes-link?color=cb3837&logo=npm)](https://www.npmjs.com/package/@bulolo/hermes-link)
10
+ [![Node](https://img.shields.io/badge/Node.js-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
11
+ [![License](https://img.shields.io/badge/License-MIT-blue)](#)
12
+ [![Platform](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey)](#)
13
+
14
+ 简体中文 | [English](./README.en.md)
15
+
16
+ [npm 包](https://www.npmjs.com/package/@bulolo/hermes-link)
17
+
18
+ <p>
19
+ <a href="https://github.com/bulolo/HermesLink">
20
+ <img src="https://img.shields.io/badge/⭐_Star-项目-yellow?style=for-the-badge&logo=github" alt="Star 项目"/>
21
+ </a>
22
+ </p>
23
+
24
+ **如果这个项目对你有帮助,请点击右上角 ⭐ Star 支持一下,这是对开发者最大的鼓励!**
25
+
26
+ </div>
27
+
28
+ ---
29
+
30
+ ## 概述
31
+
32
+ Hermes Link 是一个运行在本机的后台 HTTP 服务,默认监听 `http://0.0.0.0:18642`。客户端(App / 浏览器)通过公网或局域网直接访问,对话、文件、指令均在本地处理,数据不经过外部服务器。
33
+
34
+ 所有 API 请求分为两类:
35
+
36
+ - **无需鉴权**:`/pair`、`/api/v1/bootstrap`
37
+ - **需要 Bearer Token**:其余接口均需 `Authorization: Bearer hlat_xxx`,通过配对流程获取
38
+
39
+ ## 为什么需要 HermesLink?
40
+
41
+ Hermes Agent 内置了一个 API Server(端口 8642),但它只有 **12 个接口**:
42
+
43
+ | 功能 | Hermes API Server `:8642` | HermesLink `:18642` |
44
+ |------|:---:|:---:|
45
+ | 接口数量 | 12 | **97** |
46
+ | Agent 执行 / 事件流 | ✓ | ✓(代理转发) |
47
+ | 模型列表 / 定时任务 | ✓ | ✓(代理转发) |
48
+ | 认证 | 单一共享 Key | 设备独立 Token,可单独吊销 |
49
+ | 设备配对 | — | ✓ 二维码 / 多设备管理 |
50
+ | 对话存储 | — | ✓ 本地历史 + 附件 |
51
+ | Profile & Memory 管理 | — | ✓ 多 Profile、记忆、权限、工具开关 |
52
+ | 使用统计 | — | ✓ Token 用量按日期 / 模型 / Profile |
53
+ | 工具调用审批 | — | ✓ Approve / deny 流程 |
54
+ | 更新管理 / 开机自启 | — | ✓ |
55
+
56
+ ### 如何选择
57
+
58
+ - **只需本机脚本 / 受信任内部服务直连** → 直接用 Hermes API Server(8642)
59
+ - **开发移动 App 或多设备接入** → 需要 HermesLink(18642)
60
+ - **需要对话历史 / Profile / 统计等完整功能** → 需要 HermesLink(18642)
61
+
62
+ ## 工作原理
63
+
64
+ ```
65
+ 客户端(浏览器 / App)
66
+
67
+ └──→ hermeslink (本机, 端口 18642)
68
+
69
+ ├── 鉴权 / 设备管理 / 对话存储 / Profile & Memory ← HermesLink 自身处理(~87 个接口)
70
+
71
+ └──→ Hermes Agent API Server (127.0.0.1:8642) ← 仅 runs / models / cron jobs(~10 个接口)
72
+ ```
73
+
74
+ 绝大多数功能由 HermesLink 独立完成,不依赖 API Server。API Server 未运行时,认证、配对、对话查询、Profile 管理等接口仍可正常使用,仅 Agent 执行、模型列表、定时任务不可用。所有数据均存储在本机,不经过外部服务器。
4
75
 
5
76
  ## 环境要求
6
77
 
7
- - Node.js >= 20
8
- - 已安装并运行的 [Hermes Agent](https://github.com/nousresearch/hermes-agent)
78
+ ### 1. Node.js >= 20.0.0
79
+
80
+ ```bash
81
+ node --version # 需要 >= v20.0.0
82
+ ```
83
+
84
+ 如未安装,推荐通过 [nvm](https://github.com/nvm-sh/nvm) 安装:
85
+
86
+ ```bash
87
+ nvm install 20
88
+ nvm use 20
89
+ ```
90
+
91
+ ### 2. 启动 Hermes Agent API Server
92
+
93
+ Hermes Link 通过 `127.0.0.1:8642` 与本机的 **Hermes Agent API Server** 通信。
94
+
95
+ > 如果 Hermes Agent API Server 未运行,对话、Profiles 等功能将无法使用,但认证、配对、设备管理、日志等基础接口仍可正常工作。
96
+
97
+ 在 `~/.hermes/.env` 中添加以下配置以启用 API Server:
98
+
99
+ ```bash
100
+ API_SERVER_ENABLED=true
101
+ API_SERVER_KEY=your-secret-key
102
+ API_SERVER_HOST=0.0.0.0
103
+ API_SERVER_CORS_ORIGINS=*
104
+ GATEWAY_ALLOW_ALL_USERS=true
105
+ ```
106
+
107
+ | 配置项 | 说明 |
108
+ |--------|------|
109
+ | `API_SERVER_ENABLED` | 设为 `true` 开启 API Server |
110
+ | `API_SERVER_KEY` | 认证密钥,替换为强随机字符串 `openssl rand -hex 32` |
111
+ | `API_SERVER_HOST` | 监听地址,`0.0.0.0` 允许本机所有网卡 |
112
+ | `API_SERVER_CORS_ORIGINS` | CORS 来源,本地调试可设为 `*` |
113
+
114
+ 配置完成后启动:
115
+
116
+ ```bash
117
+ hermes gateway stop
118
+ hermes gateway start
119
+ 或者
120
+ hermes gateway restart
121
+ ```
122
+
123
+ 验证是否就绪:
124
+
125
+ ```bash
126
+ curl -s http://127.0.0.1:8642/v1/health
127
+ ```
128
+
129
+ 如缺失或版本过旧:
130
+
131
+ ```bash
132
+ hermes update
133
+ ```
9
134
 
10
135
  ## 安装
11
136
 
137
+ npm 包地址:[https://www.npmjs.com/package/@bulolo/hermes-link](https://www.npmjs.com/package/@bulolo/hermes-link)
138
+
12
139
  ```bash
13
- npm install -g @bulolo/hermes-link --registry https://registry.npmjs.org
140
+ npm install -g @bulolo/hermes-link
14
141
  ```
15
142
 
143
+ > 安装后如果终端找不到 `hermeslink` 命令,说明 npm 全局 bin 目录不在 PATH 中,运行以下命令添加:
144
+ >
145
+ > ```bash
146
+ > export PATH="$(npm prefix -g)/bin:$PATH"
147
+ > ```
148
+ >
149
+ > 也可以直接用完整路径调用:`$(npm prefix -g)/bin/hermeslink`
150
+
16
151
  ## 快速开始
17
152
 
18
153
  ```bash
19
- # 启动后台服务
154
+ # 1. 启动后台服务
20
155
  hermeslink start
21
156
 
22
- # 生成配对二维码(手机扫码连接)
157
+ # 2. 生成配对页面(浏览器打开完成配对)
23
158
  hermeslink pair
24
159
 
25
- # 查看状态
160
+ # 3. 查看状态
26
161
  hermeslink status
27
162
 
28
- # 查看日志
163
+ # 4. 查看日志
29
164
  hermeslink logs
30
165
  ```
31
166
 
32
167
  ## 配对流程
33
168
 
169
+ ### 方式一:App 扫码配对(标准流程)
170
+
171
+ 二维码内容是一段 JSON,App 解析后获取连接所需的全部信息:
172
+
173
+ ```json
174
+ {
175
+ "kind": "hermes_link_pairing",
176
+ "version": 1,
177
+ "link_id": "link_xxx",
178
+ "display_name": "Hermes Link",
179
+ "session_id": "ps_xxx",
180
+ "code": "xxx",
181
+ "preferred_urls": ["http://192.168.1.10:18642", "http://127.0.0.1:18642"]
182
+ }
183
+ ```
184
+
185
+ App 拿到上述信息后:
186
+
187
+ ```
188
+ 1. 取 preferred_urls[0] 作为服务地址(优先局域网 IP)
189
+ 2. POST {baseUrl}/api/v1/pairing/claim
190
+ Body: { "session_id": "...", "claim_token": "<code 字段的值>" }
191
+ 3. 响应中获得 access_token 和 refresh_token
192
+ 4. 后续 API 请求携带 Authorization: Bearer <access_token>
193
+ ```
194
+
195
+ ### 方式二:浏览器配对
196
+
197
+ ```bash
198
+ hermeslink pair
199
+ # 在浏览器打开终端输出的 Pairing page URL
200
+ # 点击"在此设备上配对",页面直接显示 access_token 和 refresh_token
34
201
  ```
35
- 1. 运行 hermeslink pair
36
-
37
-
38
- 终端显示二维码
39
-
40
-
41
- 手机 App 扫码
42
-
43
-
44
- 直连本地服务 (局域网 / 127.0.0.1)
202
+
203
+ ### 方式三:命令行脚本(适合自动化)
204
+
205
+ ```bash
206
+ # 1. 获取 connect_token
207
+ CONNECT_TOKEN=$(hermeslink pair 2>&1 | grep "Connect token:" | awk '{print $NF}')
208
+
209
+ # 2. 用 connect_token 换 access_token
210
+ curl -s -X POST http://localhost:18642/api/v1/auth/device-session \
211
+ -H "Authorization: Bearer $CONNECT_TOKEN" \
212
+ -H "Content-Type: application/json" \
213
+ -d '{"device_label":"my-script","device_platform":"cli"}'
45
214
  ```
46
215
 
47
- 配对完成后,手机 App 获得访问令牌,后续请求直接连接本地服务,无需经过外部服务器。
216
+ ---
217
+
218
+ > **Token 有效期**:access_token 2 小时,refresh_token 90 天。access_token 过期后用 refresh_token 换新,无需重新配对。
48
219
 
49
220
  ## 命令一览
50
221
 
@@ -54,37 +225,358 @@ hermeslink logs
54
225
  | `hermeslink stop` | 停止守护进程 |
55
226
  | `hermeslink restart` | 重启守护进程 |
56
227
  | `hermeslink status` | 查看运行状态 |
57
- | `hermeslink pair` | 生成配对二维码 |
228
+ | `hermeslink pair` | 生成配对 URL 和二维码 |
58
229
  | `hermeslink config get` | 查看当前配置 |
59
230
  | `hermeslink config set <key> <value>` | 修改配置 |
60
- | `hermeslink autostart enable` | 开机自启 |
61
- | `hermeslink autostart disable` | 关闭自启 |
62
- | `hermeslink logs` | 查看日志 |
231
+ | `hermeslink autostart on` | 开机自启(同 `enable`)|
232
+ | `hermeslink autostart off` | 关闭自启(同 `disable`)|
233
+ | `hermeslink logs` | 查看 Link 日志 |
63
234
  | `hermeslink logs --gateway` | 查看 Hermes 网关日志 |
64
- | `hermeslink version` | 查看版本 |
235
+ | `hermeslink logs -n 100` | 查看最近 100 条日志 |
236
+ | `hermeslink version` | 查看版本号 |
65
237
 
66
238
  ## 配置
67
239
 
68
240
  ```bash
69
- hermeslink config set port 52379 # 修改监听端口
70
- hermeslink config set lan-host 192.168.1.10 # 手动指定局域网 IP
71
- hermeslink config set language zh-CN # 语言 (auto/en/zh-CN)
72
- hermeslink config set log-level debug # 日志级别 (debug/info/warn/error)
241
+ hermeslink config set port 18642 # 修改监听端口(默认 18642)
242
+ hermeslink config set lan-host 192.168.1.10 # 手动指定局域网 IP(默认自动检测)
243
+ hermeslink config set language zh-CN # 语言:auto / en / zh-CN
244
+ hermeslink config set log-level debug # 日志级别:debug / info / warn / error
73
245
  ```
74
246
 
75
247
  配置文件位于 `~/.hermeslink/config.json`。
76
248
 
77
- ## 工作原理
249
+ ## API 参考
250
+
251
+ 服务默认监听 `http://0.0.0.0:18642`。所有需要鉴权的接口均使用 Bearer Token(`hlat_` 前缀)。
252
+
253
+ ### Token 说明
78
254
 
255
+ | Token | 前缀 | 有效期 | 用途 |
256
+ |-------|------|--------|------|
257
+ | Connect Token | 无前缀(base64url)| 5 分钟,一次性 | 兑换 access_token |
258
+ | Access Token | `hlat_` | 2 小时 | 所有 API 请求 |
259
+ | Refresh Token | `hlrt_` | 90 天 | 刷新 access_token |
260
+
261
+ ### 无需鉴权
262
+
263
+ | 方法 | 路径 | 说明 |
264
+ |------|------|------|
265
+ | GET | `/pair` | 配对网页(浏览器打开) |
266
+ | GET | `/api/v1/bootstrap` | 服务基础信息,含 link_id、版本、能力 |
267
+
268
+ ### 认证 / 设备
269
+
270
+ 所有以下接口需要 Header:`Authorization: Bearer hlat_xxx`
271
+
272
+ | 方法 | 路径 | 说明 |
273
+ |------|------|------|
274
+ | GET | `/api/v1/auth/me` | 当前 Token 信息及设备信息 |
275
+ | POST | `/api/v1/auth/device-session` | 用 connect_token 换取 access/refresh token |
276
+ | POST | `/api/v1/auth/refresh` | 用 refresh_token 换新 access_token |
277
+ | POST | `/api/v1/auth/logout` | 撤销 refresh_token |
278
+
279
+ **POST `/api/v1/auth/device-session`** 的 Authorization 头需放 **connect_token**(不是 hlat_),Body:
280
+
281
+ ```json
282
+ {
283
+ "device_label": "我的设备",
284
+ "device_platform": "ios|android|web|cli",
285
+ "device_model": "可选,设备型号"
286
+ }
79
287
  ```
80
- 手机 App
81
-
82
- └──→ hermeslink (本地, 端口 52379)
83
-
84
- └──→ Hermes Agent (localhost:8642)
288
+
289
+ 响应:
290
+
291
+ ```json
292
+ {
293
+ "ok": true,
294
+ "device": { "device_id": "dev_xxx", "label": "我的设备", "platform": "ios" },
295
+ "access_token": { "token": "hlat_xxx", "expires_at": "2026-05-08T13:00:00Z" },
296
+ "refresh_token": { "token": "hlrt_xxx", "expires_at": "2026-08-06T12:00:00Z" }
297
+ }
298
+ ```
299
+
300
+ **POST `/api/v1/auth/refresh`** Body:
301
+
302
+ ```json
303
+ { "refresh_token": "hlrt_xxx" }
85
304
  ```
86
305
 
87
- `hermeslink` 在本地运行一个 HTTP 服务,手机 App 通过局域网直接访问。对话、文件、指令均在本地处理,数据不经过外部服务器。
306
+ ### 配对
307
+
308
+ | 方法 | 路径 | 说明 |
309
+ |------|------|------|
310
+ | GET | `/api/v1/pairing/session` | 查询配对会话状态(含 claimed 字段) |
311
+ | POST | `/api/v1/pairing/claim` | App 端用来完成配对 |
312
+
313
+ **GET `/api/v1/pairing/session`** 查询参数:`?session_id=ps_xxx`
314
+
315
+ **POST `/api/v1/pairing/claim`** Body:
316
+
317
+ ```json
318
+ {
319
+ "session_id": "ps_xxx",
320
+ "claim_token": "connect_token值",
321
+ "device_label": "My App",
322
+ "device_platform": "ios"
323
+ }
324
+ ```
325
+
326
+ ### 系统状态
327
+
328
+ | 方法 | 路径 | 说明 |
329
+ |------|------|------|
330
+ | GET | `/api/v1/status` | 服务整体状态(版本、设备数、profiles 数量等) |
331
+ | GET | `/api/v1/logs` | 最近日志(`?source=link\|gateway&limit=50`) |
332
+
333
+ ### 设备列表
334
+
335
+ | 方法 | 路径 | 说明 |
336
+ |------|------|------|
337
+ | GET | `/api/v1/devices` | 列出所有已配对设备 |
338
+ | PATCH | `/api/v1/devices/:deviceId` | 重命名设备(`{"label":"新名字"}`) |
339
+ | DELETE | `/api/v1/devices/:deviceId` | 撤销设备(吊销 token) |
340
+ | DELETE | `/api/v1/devices/:deviceId/app-listing` | 从列表中隐藏已撤销设备 |
341
+
342
+ ### 对话(Conversations)
343
+
344
+ | 方法 | 路径 | 说明 |
345
+ |------|------|------|
346
+ | GET | `/api/v1/conversations` | 列出所有对话(`?limit=20&cursor=xxx`) |
347
+ | GET | `/api/v1/conversations/search` | 搜索对话(`?q=关键词`) |
348
+ | POST | `/api/v1/conversations` | 新建对话 |
349
+ | DELETE | `/api/v1/conversations` | 批量删除对话 |
350
+ | DELETE | `/api/v1/conversations/:id` | 删除单个对话 |
351
+ | GET | `/api/v1/conversations/:id/messages` | 获取对话消息列表 |
352
+ | POST | `/api/v1/conversations/:id/messages` | 发送消息 |
353
+ | GET | `/api/v1/conversations/:id/events` | SSE 实时事件流 |
354
+ | GET | `/api/v1/conversations/events` | 所有对话事件流(SSE) |
355
+ | PATCH | `/api/v1/conversations/:id/title` | 重命名对话(`{"title":"新标题"}`) |
356
+ | PATCH | `/api/v1/conversations/:id/model` | 切换模型 |
357
+ | PATCH | `/api/v1/conversations/:id/profile` | 切换 profile |
358
+ | POST | `/api/v1/conversations/:id/ack` | 确认已读 |
359
+ | POST | `/api/v1/conversations/clear-plans` | 创建批量清理计划 |
360
+ | GET | `/api/v1/conversations/clear-plans/:planId` | 查询清理计划状态 |
361
+ | POST | `/api/v1/conversations/clear-plans/:planId/execute` | 执行清理计划 |
362
+ | POST | `/api/v1/conversations/:id/runs/:runId/cancel` | 取消对话内的某次执行 |
363
+ | POST | `/api/v1/conversations/:id/approvals/:approvalId/approve` | 审批工具调用(允许) |
364
+ | POST | `/api/v1/conversations/:id/approvals/:approvalId/deny` | 审批工具调用(拒绝) |
365
+ | POST | `/api/v1/conversations/:id/blobs` | 上传附件 |
366
+ | GET | `/api/v1/conversations/:id/blobs/:blobId` | 下载附件 |
367
+ | DELETE | `/api/v1/conversations/:id/blobs/:blobId` | 删除附件 |
368
+
369
+ ### 统计
370
+
371
+ | 方法 | 路径 | 说明 |
372
+ |------|------|------|
373
+ | GET | `/api/v1/statistics` | 全局使用统计(对话、消息数等) |
374
+ | GET | `/api/v1/statistics/usage` | Token 用量统计(`?days=7&from=2026-05-01&to=2026-05-08&model=xxx&profile=xxx`) |
375
+
376
+ ### 模型(Models)
377
+
378
+ | 方法 | 路径 | 说明 |
379
+ |------|------|------|
380
+ | GET | `/api/v1/models` | 列出可用模型(来自 Hermes API Server,OpenAI 兼容格式) |
381
+ | GET | `/api/v1/model-configs` | 列出全局模型配置 |
382
+ | POST | `/api/v1/model-configs` | 新增全局模型配置 |
383
+ | PATCH | `/api/v1/model-configs/defaults` | 更新默认模型配置 |
384
+ | DELETE | `/api/v1/model-configs` | 删除全局模型配置 |
385
+
386
+ ### Profiles
387
+
388
+ | 方法 | 路径 | 说明 |
389
+ |------|------|------|
390
+ | GET | `/api/v1/profiles` | 列出所有 Profile 名称 |
391
+ | POST | `/api/v1/profiles` | 创建新 Profile(异步,返回 202) |
392
+ | PATCH | `/api/v1/profiles/:name` | 重命名(`{"name":"new-name"}`)或更新元数据 |
393
+ | DELETE | `/api/v1/profiles/:name` | 删除 Profile |
394
+ | GET | `/api/v1/profiles/catalog` | 完整目录(包含各 Profile 的 capabilities、permissions、modelConfigs)|
395
+ | GET | `/api/v1/profile-creation/status` | 查询创建进度 |
396
+ | GET | `/api/v1/profile-creation/events` | 创建进度 SSE 流 |
397
+ | GET | `/api/v1/profiles/:name/status` | Profile 状态(存在性、API Key 配置等) |
398
+ | GET | `/api/v1/profiles/:name/statistics` | Profile 的对话统计 |
399
+ | GET | `/api/v1/profiles/:name/skills` | 列出 Profile Skills(`?include_disabled=true`)|
400
+ | PATCH | `/api/v1/profiles/:name/skills/:skillName` | 启用/禁用 Skill(`{"enabled":true}`) |
401
+ | GET | `/api/v1/profiles/:name/memory` | 查看记忆(USER.md + MEMORY.md) |
402
+ | POST | `/api/v1/profiles/:name/memory/entries` | 新增记忆条目(`{"target":"memory","content":"..."}`) |
403
+ | PATCH | `/api/v1/profiles/:name/memory/entries` | 替换记忆条目(`{"target":"memory","match":"旧内容","content":"新内容"}`)|
404
+ | DELETE | `/api/v1/profiles/:name/memory/entries` | 删除记忆条目(`{"target":"memory","match":"匹配内容"}`)|
405
+ | DELETE | `/api/v1/profiles/:name/memory` | 重置记忆(`{"target":"memory\|user\|all"}`)|
406
+ | PATCH | `/api/v1/profiles/:name/memory/settings` | 更新记忆 Provider 设置 |
407
+ | PATCH | `/api/v1/profiles/:name/memory/provider` | 切换记忆 Provider(`{"provider":"built-in"}`)|
408
+ | GET | `/api/v1/profiles/:name/permissions` | 查看权限配置 |
409
+ | PATCH | `/api/v1/profiles/:name/permissions` | 更新权限配置 |
410
+ | GET | `/api/v1/profiles/:name/tool-configs/:toolKey` | 查看工具配置(toolKey:`web` / `image_gen` / `stt` / `tts` / `messaging` / `homeassistant` / `rl`)|
411
+ | PATCH | `/api/v1/profiles/:name/tool-configs/:toolKey` | 更新工具配置(同上 toolKey)|
412
+ | GET | `/api/v1/profiles/:name/model-configs` | 列出 Profile 的模型配置 |
413
+ | POST | `/api/v1/profiles/:name/model-configs` | 新增 Profile 的模型配置 |
414
+ | PATCH | `/api/v1/profiles/:name/model-configs/defaults` | 更新 Profile 默认模型 |
415
+ | DELETE | `/api/v1/profiles/:name/model-configs` | 删除 Profile 的模型配置 |
416
+
417
+ 记忆 `target` 字段:`"memory"`(Agent 笔记,MEMORY.md)或 `"user"`(用户信息,USER.md)。
418
+
419
+ ### Cron Jobs(定时任务)
420
+
421
+ | 方法 | 路径 | 说明 |
422
+ |------|------|------|
423
+ | GET | `/api/v1/cron-jobs` | 汇总列出所有 Profile 的定时任务 |
424
+ | GET | `/api/v1/profiles/:name/cron-jobs` | 列出指定 Profile 的定时任务 |
425
+ | POST | `/api/v1/profiles/:name/cron-jobs` | 创建定时任务 |
426
+ | GET | `/api/v1/profiles/:name/cron-jobs/:jobId` | 查看定时任务详情 |
427
+ | PATCH | `/api/v1/profiles/:name/cron-jobs/:jobId` | 更新定时任务 |
428
+ | DELETE | `/api/v1/profiles/:name/cron-jobs/:jobId` | 删除定时任务 |
429
+ | POST | `/api/v1/profiles/:name/cron-jobs/:jobId/pause` | 暂停定时任务 |
430
+ | POST | `/api/v1/profiles/:name/cron-jobs/:jobId/resume` | 恢复定时任务 |
431
+ | POST | `/api/v1/profiles/:name/cron-jobs/:jobId/run` | 立即执行定时任务 |
432
+
433
+ ### Runs(执行任务)
434
+
435
+ | 方法 | 路径 | 说明 |
436
+ |------|------|------|
437
+ | POST | `/api/v1/runs` | 向 Hermes Agent 提交执行任务(返回 202) |
438
+ | GET | `/api/v1/runs/:runId/events` | 订阅执行事件流(SSE 代理) |
439
+ | POST | `/api/v1/runs/:runId/cancel` | 取消执行任务 |
440
+
441
+ **POST `/api/v1/runs`** Body:
442
+
443
+ ```json
444
+ {
445
+ "input": "请帮我整理 ~/Downloads 目录",
446
+ "profile": "default",
447
+ "instructions": "可选的系统指令",
448
+ "session_id": "可选的会话 ID",
449
+ "conversation_history": []
450
+ }
451
+ ```
452
+
453
+ 响应(202):
454
+
455
+ ```json
456
+ {
457
+ "run_id": "run_xxx",
458
+ "fallback": false
459
+ }
460
+ ```
461
+
462
+ ### 更新管理(Updates)
463
+
464
+ #### Hermes Agent 更新
465
+
466
+ | 方法 | 路径 | 说明 |
467
+ |------|------|------|
468
+ | GET | `/api/v1/hermes/update-check` | 检查 Hermes Agent 是否有新版本 |
469
+ | GET | `/api/v1/hermes/update/status` | 查询 Hermes 更新进度 |
470
+ | POST | `/api/v1/hermes/update` | 触发 Hermes Agent 更新 |
471
+ | GET | `/api/v1/hermes/update/events` | 更新进度 SSE 流 |
472
+
473
+ #### Link 自身更新
474
+
475
+ | 方法 | 路径 | 说明 |
476
+ |------|------|------|
477
+ | GET | `/api/v1/link/update-check` | 检查 Link 是否有新版本 |
478
+ | GET | `/api/v1/link/update/status` | 查询 Link 更新进度 |
479
+ | POST | `/api/v1/link/update` | 触发 Link 自更新(`{"version":"0.3.0"}`)|
480
+ | GET | `/api/v1/link/update/events` | 更新进度 SSE 流 |
481
+
482
+ ### 系统(System)
483
+
484
+ | 方法 | 路径 | 说明 |
485
+ |------|------|------|
486
+ | GET | `/api/v1/system/status` | 系统详情(版本、自启状态、网络环境)|
487
+ | GET | `/api/v1/system/version` | 仅返回 Link 版本号 |
488
+ | POST | `/api/v1/system/autostart/enable` | 开启开机自启 |
489
+ | POST | `/api/v1/system/autostart/disable` | 关闭开机自启 |
490
+ | GET | `/api/v1/system/logs` | 最近 Link 日志 |
491
+ | GET | `/api/v1/system/logs/gateway` | 最近 Gateway 日志 |
492
+ | GET | `/api/v1/system/updates` | 查询可用更新(Hermes + Link 汇总)|
493
+ | POST | `/api/v1/system/updates/dismiss` | 忽略当前可用更新提示 |
494
+
495
+ ### 错误响应格式
496
+
497
+ 所有错误均返回:
498
+
499
+ ```json
500
+ {
501
+ "ok": false,
502
+ "error": {
503
+ "code": "error_code",
504
+ "message": "Human readable message"
505
+ }
506
+ }
507
+ ```
508
+
509
+ 常见错误码:
510
+
511
+ | code | HTTP | 说明 |
512
+ |------|------|------|
513
+ | `auth_required` | 401 | 未提供 Authorization |
514
+ | `device_access_token_invalid` | 401 | access_token 已过期或无效 |
515
+ | `auth_invalid` | 401 | connect_token 无效或已用过 |
516
+ | `pairing_session_not_found` | 404 | 配对会话不存在 |
517
+ | `pairing_session_expired` | 404 | 配对会话已过期 |
518
+ | `pairing_claim_mismatch` | 409 | 配对 token 不匹配 |
519
+ | `link_not_paired` | 409 | 服务尚未分配 link_id |
520
+
521
+ ## 调用示例
522
+
523
+ ### 全流程脚本
524
+
525
+ ```bash
526
+ #!/bin/bash
527
+ BASE="http://localhost:18642"
528
+
529
+ # Step 1: 生成配对 token
530
+ CONNECT=$(hermeslink pair 2>&1 | grep "Connect token:" | awk '{print $NF}')
531
+ echo "Connect token: $CONNECT"
532
+
533
+ # Step 2: 兑换 access_token 和 refresh_token
534
+ RESP=$(curl -s -X POST "$BASE/api/v1/auth/device-session" \
535
+ -H "Authorization: Bearer $CONNECT" \
536
+ -H "Content-Type: application/json" \
537
+ -d '{"device_label":"my-script","device_platform":"cli"}')
538
+
539
+ ACCESS=$(echo $RESP | python3 -c "import json,sys; print(json.load(sys.stdin)['access_token']['token'])")
540
+ REFRESH=$(echo $RESP | python3 -c "import json,sys; print(json.load(sys.stdin)['refresh_token']['token'])")
541
+ echo "Access: $ACCESS"
542
+ echo "Refresh: $REFRESH"
543
+
544
+ # Step 3: 查询状态
545
+ curl -s "$BASE/api/v1/status" -H "Authorization: Bearer $ACCESS" | python3 -m json.tool
546
+ ```
547
+
548
+ ### 刷新 Token
549
+
550
+ ```bash
551
+ curl -s -X POST http://localhost:18642/api/v1/auth/refresh \
552
+ -H "Content-Type: application/json" \
553
+ -d "{\"refresh_token\":\"$REFRESH\"}"
554
+ ```
555
+
556
+ ### 查询设备列表
557
+
558
+ ```bash
559
+ curl -s http://localhost:18642/api/v1/devices \
560
+ -H "Authorization: Bearer $ACCESS"
561
+ ```
562
+
563
+ ### 查询对话列表
564
+
565
+ ```bash
566
+ curl -s "http://localhost:18642/api/v1/conversations?limit=10" \
567
+ -H "Authorization: Bearer $ACCESS"
568
+ ```
569
+
570
+ ## 开机自启
571
+
572
+ - **macOS**:通过 launchd(`~/Library/LaunchAgents/com.hermes.link.plist`)
573
+ - **Linux**:通过 systemd 用户服务或 XDG autostart
574
+ - **Windows**:通过 Startup 文件夹
575
+
576
+ ```bash
577
+ hermeslink autostart on
578
+ hermeslink autostart off
579
+ ```
88
580
 
89
581
  ## 运行时文件
90
582
 
@@ -93,34 +585,49 @@ hermeslink config set log-level debug # 日志级别 (debug/info/warn/er
93
585
  | 路径 | 说明 |
94
586
  |------|------|
95
587
  | `config.json` | 用户配置 |
96
- | `identity.json` | 设备身份(ed25519 密钥对)|
588
+ | `identity.json` | 设备身份(ed25519 密钥对 + link_id)|
97
589
  | `credentials.json` | 已配对设备的访问令牌 |
590
+ | `app-connect-tokens.json` | 待使用的配对 token(5 分钟有效)|
98
591
  | `conversations/` | 对话数据 |
99
592
  | `blobs/` | 文件附件 |
100
593
  | `pairing/` | 配对会话 |
101
594
  | `link.db` | SQLite 数据库(统计信息)|
102
595
  | `logs/` | 日志文件 |
103
596
 
597
+ 卸载 npm 包不会删除此目录,重新安装后仍可复用同一 link_id。
598
+
104
599
  ## 环境变量
105
600
 
106
601
  | 变量 | 说明 |
107
602
  |------|------|
108
603
  | `HERMESLINK_HOME` | 覆盖运行时目录(默认 `~/.hermeslink`)|
109
604
  | `HERMESLINK_LOG_LEVEL` | 覆盖日志级别 |
110
- | `HERMESLINK_LANG` | 覆盖语言 |
605
+ | `HERMESLINK_LANG` | 覆盖语言(`en` / `zh-CN`)|
111
606
  | `HERMES_BIN` | `hermes` 二进制路径(默认 `hermes`)|
607
+ | `HERMESLINK_LISTEN_HOST` | HTTP 监听地址(默认 `0.0.0.0`)|
112
608
 
113
- ## 开机自启
114
-
115
- - **macOS**:通过 launchd(`~/Library/LaunchAgents/com.hermes.link.plist`)
116
- - **Linux**:通过 systemd 用户服务或 XDG autostart
117
- - **Windows**:通过 Startup 文件夹
609
+ ## 开发调试
118
610
 
119
611
  ```bash
120
- hermeslink autostart enable
121
- hermeslink autostart disable
612
+ # 安装依赖并构建
613
+ npm install
614
+ npm run build
615
+
616
+ # 前台运行(调试)
617
+ npm run dev:run
618
+ # 或
619
+ node dist/cli/index.js daemon --foreground
620
+
621
+ # watch 模式(自动重编译,需手动重启服务)
622
+ npm run dev:watch # 终端1:监听源码变化自动 build
623
+ node dist/cli/index.js daemon --foreground # 终端2:运行服务
624
+
625
+ # TypeScript 类型检查
626
+ npm run check
122
627
  ```
123
628
 
629
+ 服务运行后可访问 `http://localhost:18642/api/v1/bootstrap` 验证是否正常。
630
+
124
631
  ## License
125
632
 
126
633
  MIT
@@ -0,0 +1,18 @@
1
+ // src/core/errors.ts
2
+ var LinkHttpError = class extends Error {
3
+ status;
4
+ code;
5
+ constructor(status, code, message) {
6
+ super(message);
7
+ this.status = status;
8
+ this.code = code;
9
+ }
10
+ };
11
+ function isLinkHttpError(error) {
12
+ return error instanceof LinkHttpError;
13
+ }
14
+
15
+ export {
16
+ LinkHttpError,
17
+ isLinkHttpError
18
+ };