@bulolo/hermes-link 0.3.4 → 0.3.6

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.
@@ -0,0 +1,739 @@
1
+ <div align="center">
2
+
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.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 执行、模型列表、定时任务不可用。所有数据均存储在本机,不经过外部服务器。
75
+
76
+ ## 环境要求
77
+
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
+ ```
134
+
135
+ ## 安装
136
+
137
+ npm 包地址:[https://www.npmjs.com/package/@bulolo/hermes-link](https://www.npmjs.com/package/@bulolo/hermes-link)
138
+
139
+ ```bash
140
+ npm install -g @bulolo/hermes-link
141
+ ```
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
+
151
+ ## 快速开始
152
+
153
+ ```bash
154
+ # 1. 启动后台服务
155
+ hermeslink start
156
+
157
+ # 2. 生成配对页面(浏览器打开完成配对)
158
+ hermeslink pair
159
+
160
+ # 3. 查看状态
161
+ hermeslink status
162
+
163
+ # 4. 查看日志
164
+ hermeslink logs
165
+ ```
166
+
167
+ ## 配对流程
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
201
+ ```
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"}'
214
+ ```
215
+
216
+ ---
217
+
218
+ > **Token 有效期**:access_token 2 小时,refresh_token 90 天。access_token 过期后用 refresh_token 换新,无需重新配对。
219
+
220
+ ## 命令一览
221
+
222
+ | 命令 | 说明 |
223
+ |------|------|
224
+ | `hermeslink start` | 启动后台守护进程 |
225
+ | `hermeslink stop` | 停止守护进程 |
226
+ | `hermeslink restart` | 重启守护进程 |
227
+ | `hermeslink status` | 查看运行状态 |
228
+ | `hermeslink pair` | 生成配对 URL 和二维码 |
229
+ | `hermeslink config get` | 查看当前配置 |
230
+ | `hermeslink config set <key> <value>` | 修改配置 |
231
+ | `hermeslink autostart on` | 开机自启(同 `enable`)|
232
+ | `hermeslink autostart off` | 关闭自启(同 `disable`)|
233
+ | `hermeslink logs` | 查看 Link 日志 |
234
+ | `hermeslink logs --gateway` | 查看 Hermes 网关日志 |
235
+ | `hermeslink logs -n 100` | 查看最近 100 条日志 |
236
+ | `hermeslink version` | 查看版本号 |
237
+
238
+ ## 配置
239
+
240
+ ```bash
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
245
+ ```
246
+
247
+ 配置文件位于 `~/.hermeslink/config.json`。
248
+
249
+ ## API 参考
250
+
251
+ 服务默认监听 `http://0.0.0.0:18642`。所有需要鉴权的接口均使用 Bearer Token(`hlat_` 前缀)。
252
+
253
+ ### Token 说明
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
+ }
287
+ ```
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" }
304
+ ```
305
+
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
+ | 方法 | 路径 | 说明 |
347
+ |------|------|------|
348
+ | GET | `/api/v1/conversations` | 列出活跃对话(`?limit=20&cursor=xxx`) |
349
+ | GET | `/api/v1/conversations/search` | 搜索活跃对话(`?q=关键词`) |
350
+ | POST | `/api/v1/conversations` | 新建对话 |
351
+ | DELETE | `/api/v1/conversations` | 批量删除对话(`{"conversation_ids":["conv_xxx"]}`) |
352
+ | DELETE | `/api/v1/conversations/:id` | 删除单个对话 |
353
+ | GET | `/api/v1/conversations/:id/messages` | 获取对话消息列表(含 `runtime` 上下文信息) |
354
+ | POST | `/api/v1/conversations/:id/messages` | 发送消息 |
355
+ | GET | `/api/v1/conversations/:id/events` | SSE 实时事件流 |
356
+ | GET | `/api/v1/conversations/events` | 所有对话事件流(SSE) |
357
+ | PATCH | `/api/v1/conversations/:id/title` | 重命名对话(`{"title":"新标题"}`) |
358
+ | PATCH | `/api/v1/conversations/:id/model` | 切换模型(`{"model_id":"xxx"}`) |
359
+ | PATCH | `/api/v1/conversations/:id/profile` | 切换 profile |
360
+ | POST | `/api/v1/conversations/:id/ack` | 确认已读 |
361
+ | POST | `/api/v1/conversations/:id/runs/:runId/cancel` | 取消对话内的某次执行 |
362
+ | POST | `/api/v1/conversations/:id/approvals/:approvalId/approve` | 审批工具调用(允许,`{"scope":"once\|session\|always"}`) |
363
+ | POST | `/api/v1/conversations/:id/approvals/:approvalId/deny` | 审批工具调用(拒绝) |
364
+ | POST | `/api/v1/conversations/:id/blobs` | 上传附件 |
365
+ | GET | `/api/v1/conversations/:id/blobs/:blobId` | 下载附件 |
366
+ | DELETE | `/api/v1/conversations/:id/blobs/:blobId` | 删除附件 |
367
+
368
+ #### 归档对话
369
+
370
+ | 方法 | 路径 | 说明 |
371
+ |------|------|------|
372
+ | GET | `/api/v1/conversations/archived` | 列出已归档对话(`?limit=20&cursor=xxx`) |
373
+ | GET | `/api/v1/conversations/archived/search` | 搜索已归档对话(`?q=关键词`) |
374
+ | POST | `/api/v1/conversations/:id/archive` | 归档对话(发送新消息时自动恢复为活跃) |
375
+ | POST | `/api/v1/conversations/:id/unarchive` | 取消归档 |
376
+
377
+ #### 批量清理计划
378
+
379
+ | 方法 | 路径 | 说明 |
380
+ |------|------|------|
381
+ | POST | `/api/v1/conversations/clear-plans` | 创建批量删除计划(`{"target_status":"active\|archived"}`) |
382
+ | GET | `/api/v1/conversations/clear-plans/:planId` | 查询计划状态 |
383
+ | POST | `/api/v1/conversations/clear-plans/:planId/execute` | 执行计划(批量删除) |
384
+
385
+ #### 批量归档计划
386
+
387
+ | 方法 | 路径 | 说明 |
388
+ |------|------|------|
389
+ | POST | `/api/v1/conversations/archive-plans` | 创建批量归档计划(`{"exclude_conversation_ids":[]}`) |
390
+ | GET | `/api/v1/conversations/archive-plans/:planId` | 查询计划状态 |
391
+ | POST | `/api/v1/conversations/archive-plans/:planId/execute` | 执行计划(批量归档) |
392
+
393
+ #### 响应结构说明
394
+
395
+ **ConversationSummary**(对话列表每项):
396
+
397
+ ```json
398
+ {
399
+ "id": "conv_xxx",
400
+ "title": "对话标题",
401
+ "created_at": "2026-05-09T00:00:00.000Z",
402
+ "updated_at": "2026-05-09T00:00:00.000Z",
403
+ "last_event_seq": 12,
404
+ "usage": { "input_tokens": 100, "output_tokens": 200, "total_tokens": 300, "updated_at": "..." },
405
+ "profile": { "uid": "prof_xxx", "name": "default", "display_name": "default", "avatar_url": null },
406
+ "last_message": { "id": "msg_xxx", "role": "assistant", "content_preview": "消息摘要..." }
407
+ }
408
+ ```
409
+
410
+ **GET `/api/v1/conversations/:id/messages`** 响应新增 `runtime` 字段:
411
+
412
+ ```json
413
+ {
414
+ "ok": true,
415
+ "messages": [...],
416
+ "last_event_seq": 12,
417
+ "runtime": {
418
+ "profile": { "name": "default", "display_name": "default", "avatar_url": null },
419
+ "model": { "id": "claude-3-5-sonnet", "provider": "anthropic", "context_window": 200000 },
420
+ "context": { "input_tokens": 100, "output_tokens": 200, "total_tokens": 300, "usage_percent": 0, "source": "estimated" }
421
+ },
422
+ "page": { "limit": 50, "has_more_before": false, "has_more_after": false, "oldest_message_id": "...", "newest_message_id": "..." }
423
+ }
424
+ ```
425
+
426
+ **LinkMessage**(消息对象):
427
+
428
+ ```json
429
+ {
430
+ "id": "msg_xxx",
431
+ "schema_version": 1,
432
+ "conversation_id": "conv_xxx",
433
+ "role": "user|assistant|tool|system",
434
+ "status": "queued|streaming|completed|failed|cancelled",
435
+ "created_at": "...",
436
+ "updated_at": "...",
437
+ "sender": { "id": "app_user", "type": "human|agent|system|tool", "display_name": "Me" },
438
+ "parts": [{ "type": "text", "text": "消息内容" }],
439
+ "attachments": [],
440
+ "blocks": [],
441
+ "agent_events": [],
442
+ "approvals": []
443
+ }
444
+ ```
445
+
446
+ **DELETE `/api/v1/conversations/:id`** 响应新增字段:
447
+
448
+ ```json
449
+ {
450
+ "ok": true,
451
+ "conversation_id": "conv_xxx",
452
+ "hermes_deleted": false,
453
+ "deleted_at": "2026-05-09T00:00:00.000Z"
454
+ }
455
+ ```
456
+
457
+ ### 统计
458
+
459
+ | 方法 | 路径 | 说明 |
460
+ |------|------|------|
461
+ | GET | `/api/v1/statistics` | 全局使用统计(`?profile=xxx&profile_uid=xxx`) |
462
+ | GET | `/api/v1/statistics/usage` | Token 用量统计(`?days=7&from=2026-05-01&to=2026-05-08&model=xxx&profile=xxx`) |
463
+
464
+ **GET `/api/v1/statistics`** 响应:
465
+
466
+ ```json
467
+ {
468
+ "ok": true,
469
+ "statistics": {
470
+ "conversations": { "total": 10, "active": 8, "archived": 1, "deleted": 1 },
471
+ "tokens": { "input_tokens": 5000, "output_tokens": 8000, "total_tokens": 13000 },
472
+ "messages": { "total": 120 },
473
+ "runs": { "total": 50 },
474
+ "models": { "total": 0 },
475
+ "profiles": { "total": 0 },
476
+ "skills": { "total": 0 },
477
+ "tools": { "total": 0 }
478
+ }
479
+ }
480
+ ```
481
+
482
+ ### 模型(Models)
483
+
484
+ | 方法 | 路径 | 说明 |
485
+ |------|------|------|
486
+ | GET | `/api/v1/models` | 列出可用模型(来自 Hermes API Server,OpenAI 兼容格式) |
487
+ | GET | `/api/v1/model-configs` | 列出全局模型配置 |
488
+ | POST | `/api/v1/model-configs` | 新增全局模型配置 |
489
+ | PATCH | `/api/v1/model-configs/defaults` | 更新默认模型配置 |
490
+ | DELETE | `/api/v1/model-configs` | 删除全局模型配置 |
491
+
492
+ ### Profiles
493
+
494
+ | 方法 | 路径 | 说明 |
495
+ |------|------|------|
496
+ | GET | `/api/v1/profiles` | 列出所有 Profile 名称 |
497
+ | POST | `/api/v1/profiles` | 创建新 Profile(异步,返回 202) |
498
+ | PATCH | `/api/v1/profiles/:name` | 重命名(`{"name":"new-name"}`)或更新元数据 |
499
+ | DELETE | `/api/v1/profiles/:name` | 删除 Profile |
500
+ | GET | `/api/v1/profiles/catalog` | 完整目录(包含各 Profile 的 capabilities、permissions、modelConfigs)|
501
+ | GET | `/api/v1/profile-creation/status` | 查询创建进度 |
502
+ | GET | `/api/v1/profile-creation/events` | 创建进度 SSE 流 |
503
+ | GET | `/api/v1/profiles/:name/status` | Profile 状态(存在性、API Key 配置等) |
504
+ | GET | `/api/v1/profiles/:name/statistics` | Profile 的对话统计 |
505
+ | GET | `/api/v1/profiles/:name/skills` | 列出 Profile Skills(`?include_disabled=true`)|
506
+ | PATCH | `/api/v1/profiles/:name/skills/:skillName` | 启用/禁用 Skill(`{"enabled":true}`) |
507
+ | GET | `/api/v1/profiles/:name/memory` | 查看记忆(USER.md + MEMORY.md) |
508
+ | POST | `/api/v1/profiles/:name/memory/entries` | 新增记忆条目(`{"target":"memory","content":"..."}`) |
509
+ | PATCH | `/api/v1/profiles/:name/memory/entries` | 替换记忆条目(`{"target":"memory","match":"旧内容","content":"新内容"}`)|
510
+ | DELETE | `/api/v1/profiles/:name/memory/entries` | 删除记忆条目(`{"target":"memory","match":"匹配内容"}`)|
511
+ | DELETE | `/api/v1/profiles/:name/memory` | 重置记忆(`{"target":"memory\|user\|all"}`)|
512
+ | PATCH | `/api/v1/profiles/:name/memory/settings` | 更新记忆 Provider 设置 |
513
+ | PATCH | `/api/v1/profiles/:name/memory/provider` | 切换记忆 Provider(`{"provider":"built-in"}`)|
514
+ | GET | `/api/v1/profiles/:name/permissions` | 查看权限配置 |
515
+ | PATCH | `/api/v1/profiles/:name/permissions` | 更新权限配置 |
516
+ | GET | `/api/v1/profiles/:name/tool-configs/:toolKey` | 查看工具配置(toolKey:`web` / `image_gen` / `stt` / `tts` / `messaging` / `homeassistant` / `rl`)|
517
+ | PATCH | `/api/v1/profiles/:name/tool-configs/:toolKey` | 更新工具配置(同上 toolKey)|
518
+ | GET | `/api/v1/profiles/:name/model-configs` | 列出 Profile 的模型配置 |
519
+ | POST | `/api/v1/profiles/:name/model-configs` | 新增 Profile 的模型配置 |
520
+ | PATCH | `/api/v1/profiles/:name/model-configs/defaults` | 更新 Profile 默认模型 |
521
+ | DELETE | `/api/v1/profiles/:name/model-configs` | 删除 Profile 的模型配置 |
522
+
523
+ 记忆 `target` 字段:`"memory"`(Agent 笔记,MEMORY.md)或 `"user"`(用户信息,USER.md)。
524
+
525
+ ### Cron Jobs(定时任务)
526
+
527
+ | 方法 | 路径 | 说明 |
528
+ |------|------|------|
529
+ | GET | `/api/v1/cron-jobs` | 汇总列出所有 Profile 的定时任务 |
530
+ | GET | `/api/v1/profiles/:name/cron-jobs` | 列出指定 Profile 的定时任务 |
531
+ | POST | `/api/v1/profiles/:name/cron-jobs` | 创建定时任务 |
532
+ | GET | `/api/v1/profiles/:name/cron-jobs/:jobId` | 查看定时任务详情 |
533
+ | PATCH | `/api/v1/profiles/:name/cron-jobs/:jobId` | 更新定时任务 |
534
+ | DELETE | `/api/v1/profiles/:name/cron-jobs/:jobId` | 删除定时任务 |
535
+ | POST | `/api/v1/profiles/:name/cron-jobs/:jobId/pause` | 暂停定时任务 |
536
+ | POST | `/api/v1/profiles/:name/cron-jobs/:jobId/resume` | 恢复定时任务 |
537
+ | POST | `/api/v1/profiles/:name/cron-jobs/:jobId/run` | 立即执行定时任务 |
538
+
539
+ ### Runs(执行任务)
540
+
541
+ | 方法 | 路径 | 说明 |
542
+ |------|------|------|
543
+ | POST | `/api/v1/runs` | 向 Hermes Agent 提交执行任务(返回 202) |
544
+ | GET | `/api/v1/runs/:runId/events` | 订阅执行事件流(SSE 代理) |
545
+ | POST | `/api/v1/runs/:runId/cancel` | 取消执行任务 |
546
+
547
+ **POST `/api/v1/runs`** Body:
548
+
549
+ ```json
550
+ {
551
+ "input": "请帮我整理 ~/Downloads 目录",
552
+ "profile": "default",
553
+ "instructions": "可选的系统指令",
554
+ "session_id": "可选的会话 ID",
555
+ "conversation_history": []
556
+ }
557
+ ```
558
+
559
+ 响应(202):
560
+
561
+ ```json
562
+ {
563
+ "run_id": "run_xxx",
564
+ "fallback": false
565
+ }
566
+ ```
567
+
568
+ ### 更新管理(Updates)
569
+
570
+ #### Hermes Agent 更新
571
+
572
+ | 方法 | 路径 | 说明 |
573
+ |------|------|------|
574
+ | GET | `/api/v1/hermes/update-check` | 检查 Hermes Agent 是否有新版本 |
575
+ | GET | `/api/v1/hermes/update/status` | 查询 Hermes 更新进度 |
576
+ | POST | `/api/v1/hermes/update` | 触发 Hermes Agent 更新 |
577
+ | GET | `/api/v1/hermes/update/events` | 更新进度 SSE 流 |
578
+
579
+ #### Link 自身更新
580
+
581
+ | 方法 | 路径 | 说明 |
582
+ |------|------|------|
583
+ | GET | `/api/v1/link/update-check` | 检查 Link 是否有新版本 |
584
+ | GET | `/api/v1/link/update/status` | 查询 Link 更新进度 |
585
+ | POST | `/api/v1/link/update` | 触发 Link 自更新(`{"version":"0.3.0"}`)|
586
+ | GET | `/api/v1/link/update/events` | 更新进度 SSE 流 |
587
+
588
+ ### 系统(System)
589
+
590
+ | 方法 | 路径 | 说明 |
591
+ |------|------|------|
592
+ | GET | `/api/v1/system/status` | 系统详情(版本、自启状态、网络环境)|
593
+ | GET | `/api/v1/system/version` | 仅返回 Link 版本号 |
594
+ | POST | `/api/v1/system/autostart/enable` | 开启开机自启 |
595
+ | POST | `/api/v1/system/autostart/disable` | 关闭开机自启 |
596
+ | GET | `/api/v1/system/logs` | 最近 Link 日志 |
597
+ | GET | `/api/v1/system/logs/gateway` | 最近 Gateway 日志 |
598
+ | GET | `/api/v1/system/updates` | 查询可用更新(Hermes + Link 汇总)|
599
+ | POST | `/api/v1/system/updates/dismiss` | 忽略当前可用更新提示 |
600
+
601
+ ### 错误响应格式
602
+
603
+ 所有错误均返回:
604
+
605
+ ```json
606
+ {
607
+ "ok": false,
608
+ "error": {
609
+ "code": "error_code",
610
+ "message": "Human readable message"
611
+ }
612
+ }
613
+ ```
614
+
615
+ 常见错误码:
616
+
617
+ | code | HTTP | 说明 |
618
+ |------|------|------|
619
+ | `auth_required` | 401 | 未提供 Authorization |
620
+ | `device_access_token_invalid` | 401 | access_token 已过期或无效 |
621
+ | `auth_invalid` | 401 | connect_token 无效或已用过 |
622
+ | `pairing_session_not_found` | 404 | 配对会话不存在 |
623
+ | `pairing_session_expired` | 404 | 配对会话已过期 |
624
+ | `pairing_claim_mismatch` | 409 | 配对 token 不匹配 |
625
+ | `link_not_paired` | 409 | 服务尚未分配 link_id |
626
+
627
+ ## 调用示例
628
+
629
+ ### 全流程脚本
630
+
631
+ ```bash
632
+ #!/bin/bash
633
+ BASE="http://localhost:18642"
634
+
635
+ # Step 1: 生成配对 token
636
+ CONNECT=$(hermeslink pair 2>&1 | grep "Connect token:" | awk '{print $NF}')
637
+ echo "Connect token: $CONNECT"
638
+
639
+ # Step 2: 兑换 access_token 和 refresh_token
640
+ RESP=$(curl -s -X POST "$BASE/api/v1/auth/device-session" \
641
+ -H "Authorization: Bearer $CONNECT" \
642
+ -H "Content-Type: application/json" \
643
+ -d '{"device_label":"my-script","device_platform":"cli"}')
644
+
645
+ ACCESS=$(echo $RESP | python3 -c "import json,sys; print(json.load(sys.stdin)['access_token']['token'])")
646
+ REFRESH=$(echo $RESP | python3 -c "import json,sys; print(json.load(sys.stdin)['refresh_token']['token'])")
647
+ echo "Access: $ACCESS"
648
+ echo "Refresh: $REFRESH"
649
+
650
+ # Step 3: 查询状态
651
+ curl -s "$BASE/api/v1/status" -H "Authorization: Bearer $ACCESS" | python3 -m json.tool
652
+ ```
653
+
654
+ ### 刷新 Token
655
+
656
+ ```bash
657
+ curl -s -X POST http://localhost:18642/api/v1/auth/refresh \
658
+ -H "Content-Type: application/json" \
659
+ -d "{\"refresh_token\":\"$REFRESH\"}"
660
+ ```
661
+
662
+ ### 查询设备列表
663
+
664
+ ```bash
665
+ curl -s http://localhost:18642/api/v1/devices \
666
+ -H "Authorization: Bearer $ACCESS"
667
+ ```
668
+
669
+ ### 查询对话列表
670
+
671
+ ```bash
672
+ curl -s "http://localhost:18642/api/v1/conversations?limit=10" \
673
+ -H "Authorization: Bearer $ACCESS"
674
+ ```
675
+
676
+ ## 开机自启
677
+
678
+ - **macOS**:通过 launchd(`~/Library/LaunchAgents/com.hermes.link.plist`)
679
+ - **Linux**:通过 systemd 用户服务或 XDG autostart
680
+ - **Windows**:通过 Startup 文件夹
681
+
682
+ ```bash
683
+ hermeslink autostart on
684
+ hermeslink autostart off
685
+ ```
686
+
687
+ ## 运行时文件
688
+
689
+ 所有文件存储于 `~/.hermeslink/`:
690
+
691
+ | 路径 | 说明 |
692
+ |------|------|
693
+ | `config.json` | 用户配置 |
694
+ | `identity.json` | 设备身份(ed25519 密钥对 + link_id)|
695
+ | `credentials.json` | 已配对设备的访问令牌 |
696
+ | `app-connect-tokens.json` | 待使用的配对 token(5 分钟有效)|
697
+ | `conversations/` | 对话数据 |
698
+ | `blobs/` | 文件附件 |
699
+ | `pairing/` | 配对会话 |
700
+ | `link.db` | SQLite 数据库(统计信息)|
701
+ | `logs/` | 日志文件 |
702
+
703
+ 卸载 npm 包不会删除此目录,重新安装后仍可复用同一 link_id。
704
+
705
+ ## 环境变量
706
+
707
+ | 变量 | 说明 |
708
+ |------|------|
709
+ | `HERMESLINK_HOME` | 覆盖运行时目录(默认 `~/.hermeslink`)|
710
+ | `HERMESLINK_LOG_LEVEL` | 覆盖日志级别 |
711
+ | `HERMESLINK_LANG` | 覆盖语言(`en` / `zh-CN`)|
712
+ | `HERMES_BIN` | `hermes` 二进制路径(默认 `hermes`)|
713
+ | `HERMESLINK_LISTEN_HOST` | HTTP 监听地址(默认 `0.0.0.0`)|
714
+
715
+ ## 开发调试
716
+
717
+ ```bash
718
+ # 安装依赖并构建
719
+ npm install
720
+ npm run build
721
+
722
+ # 前台运行(调试)
723
+ npm run dev:run
724
+ # 或
725
+ node dist/cli/index.js daemon --foreground
726
+
727
+ # watch 模式(自动重编译,需手动重启服务)
728
+ npm run dev:watch # 终端1:监听源码变化自动 build
729
+ node dist/cli/index.js daemon --foreground # 终端2:运行服务
730
+ node dist/cli/index.js pair
731
+ # TypeScript 类型检查
732
+ npm run check
733
+ ```
734
+
735
+ 服务运行后可访问 `http://localhost:18642/api/v1/bootstrap` 验证是否正常。
736
+
737
+ ## License
738
+
739
+ MIT