@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.en.md +630 -0
- package/README.md +552 -45
- package/dist/chunk-NP3Y2NVF.js +18 -0
- package/dist/chunk-ZO2S4ZIO.js +8790 -0
- package/dist/cli/index.js +47 -70
- package/dist/errors-KGEGMUYS.js +8 -0
- package/dist/http/app.d.ts +0 -46
- package/dist/http/app.js +2 -1
- package/package.json +14 -6
- package/dist/chunk-GHAP2EHA.js +0 -3649
package/README.en.md
ADDED
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# hermes-link
|
|
4
|
+
|
|
5
|
+
**Local access layer for Hermes Agent**
|
|
6
|
+
|
|
7
|
+
Provides full client API, multi-device auth and conversation management for [Hermes Agent](https://github.com/nousresearch/hermes-agent), with LAN and internet connectivity.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@bulolo/hermes-link)
|
|
10
|
+
[](https://nodejs.org/)
|
|
11
|
+
[](#)
|
|
12
|
+
[](#)
|
|
13
|
+
|
|
14
|
+
[中文](./README.md) | **English**
|
|
15
|
+
|
|
16
|
+
[npm package](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-Project-yellow?style=for-the-badge&logo=github" alt="Star Project"/>
|
|
21
|
+
</a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
**If this project helps you, please ⭐ Star it — it means a lot to the developer!**
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Overview
|
|
31
|
+
|
|
32
|
+
Hermes Link is a background HTTP service running on your local machine, listening on `http://0.0.0.0:18642` by default. Clients (App / browser) connect directly over LAN or the internet — all conversations, files and commands are processed locally, with no data leaving your machine.
|
|
33
|
+
|
|
34
|
+
All API requests fall into two categories:
|
|
35
|
+
|
|
36
|
+
- **No auth required**: `/pair`, `/api/v1/bootstrap`
|
|
37
|
+
- **Bearer Token required**: all other endpoints require `Authorization: Bearer hlat_xxx`, obtained through the pairing flow
|
|
38
|
+
|
|
39
|
+
## Why HermesLink?
|
|
40
|
+
|
|
41
|
+
Hermes Agent ships with a built-in API Server (port 8642), but it only exposes **12 endpoints**:
|
|
42
|
+
|
|
43
|
+
| Feature | Hermes API Server `:8642` | HermesLink `:18642` |
|
|
44
|
+
|---------|:---:|:---:|
|
|
45
|
+
| Endpoint count | 12 | **97** |
|
|
46
|
+
| Agent execution / event stream | ✓ | ✓ (proxied) |
|
|
47
|
+
| Model list / Cron jobs | ✓ | ✓ (proxied) |
|
|
48
|
+
| Authentication | Single shared key | Per-device token, individually revocable |
|
|
49
|
+
| Device pairing | — | ✓ QR code / multi-device management |
|
|
50
|
+
| Conversation storage | — | ✓ Local history + attachments |
|
|
51
|
+
| Profile & Memory management | — | ✓ Multi-profile, memory, permissions, tool switches |
|
|
52
|
+
| Usage statistics | — | ✓ Token usage by date / model / profile |
|
|
53
|
+
| Tool call approval | — | ✓ Approve / deny flow |
|
|
54
|
+
| Update management / autostart | — | ✓ |
|
|
55
|
+
|
|
56
|
+
### When to use which
|
|
57
|
+
|
|
58
|
+
- **Local scripts / trusted internal services calling Hermes directly** → use Hermes API Server (8642)
|
|
59
|
+
- **Building a mobile app or multi-device access** → HermesLink (18642) required
|
|
60
|
+
- **Need conversation history / Profile management / statistics** → HermesLink (18642) required
|
|
61
|
+
|
|
62
|
+
## How It Works
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Client (browser / App)
|
|
66
|
+
│
|
|
67
|
+
└──→ hermeslink (local machine, port 18642)
|
|
68
|
+
│
|
|
69
|
+
├── auth / device management / conversation storage / Profile & Memory ← handled by HermesLink (~87 endpoints)
|
|
70
|
+
│
|
|
71
|
+
└──→ Hermes Agent API Server (127.0.0.1:8642) ← only runs / models / cron jobs (~10 endpoints)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The vast majority of functionality is handled by HermesLink independently, without relying on the API Server. If the API Server is not running, auth, pairing, conversation queries, and Profile management all continue to work — only Agent execution, model listing, and cron jobs become unavailable. All data is stored locally.
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
### 1. Node.js >= 20.0.0
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
node --version # must be >= v20.0.0
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If not installed, use [nvm](https://github.com/nvm-sh/nvm):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
nvm install 20
|
|
88
|
+
nvm use 20
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Start the Hermes Agent API Server
|
|
92
|
+
|
|
93
|
+
HermesLink communicates with the **Hermes Agent API Server** at `127.0.0.1:8642`.
|
|
94
|
+
|
|
95
|
+
> If the API Server is not running, conversation and Profile features will be unavailable, but auth, pairing, device management, and logs still work fine.
|
|
96
|
+
|
|
97
|
+
Add the following to `~/.hermes/.env` to enable the 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
|
+
| Option | Description |
|
|
108
|
+
|--------|-------------|
|
|
109
|
+
| `API_SERVER_ENABLED` | Set to `true` to enable the API Server |
|
|
110
|
+
| `API_SERVER_KEY` | Auth key — replace with a strong random string: `openssl rand -hex 32` |
|
|
111
|
+
| `API_SERVER_HOST` | Listen address — `0.0.0.0` allows all network interfaces |
|
|
112
|
+
| `API_SERVER_CORS_ORIGINS` | CORS origins — can be `*` for local development |
|
|
113
|
+
|
|
114
|
+
Then restart:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
hermes gateway restart
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Verify it's running:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
curl -s http://127.0.0.1:8642/v1/health
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If missing or outdated:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
hermes update
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Installation
|
|
133
|
+
|
|
134
|
+
npm package: [https://www.npmjs.com/package/@bulolo/hermes-link](https://www.npmjs.com/package/@bulolo/hermes-link)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm install -g @bulolo/hermes-link
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> If the `hermeslink` command is not found after installation, the npm global bin directory is not in your PATH. Fix it with:
|
|
141
|
+
>
|
|
142
|
+
> ```bash
|
|
143
|
+
> export PATH="$(npm prefix -g)/bin:$PATH"
|
|
144
|
+
> ```
|
|
145
|
+
>
|
|
146
|
+
> Or call it directly: `$(npm prefix -g)/bin/hermeslink`
|
|
147
|
+
|
|
148
|
+
## Quick Start
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# 1. Start the background daemon
|
|
152
|
+
hermeslink start
|
|
153
|
+
|
|
154
|
+
# 2. Open the pairing page in a browser
|
|
155
|
+
hermeslink pair
|
|
156
|
+
|
|
157
|
+
# 3. Check status
|
|
158
|
+
hermeslink status
|
|
159
|
+
|
|
160
|
+
# 4. View logs
|
|
161
|
+
hermeslink logs
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Pairing
|
|
165
|
+
|
|
166
|
+
### Method 1: App QR code scan (standard flow)
|
|
167
|
+
|
|
168
|
+
The QR code contains a JSON payload the app parses to get everything it needs:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"kind": "hermes_link_pairing",
|
|
173
|
+
"version": 1,
|
|
174
|
+
"link_id": "link_xxx",
|
|
175
|
+
"display_name": "Hermes Link",
|
|
176
|
+
"session_id": "ps_xxx",
|
|
177
|
+
"code": "xxx",
|
|
178
|
+
"preferred_urls": ["http://192.168.1.10:18642", "http://127.0.0.1:18642"]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Once the app has this:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
1. Use preferred_urls[0] as the base URL (LAN IP preferred)
|
|
186
|
+
2. POST {baseUrl}/api/v1/pairing/claim
|
|
187
|
+
Body: { "session_id": "...", "claim_token": "<value of the code field>" }
|
|
188
|
+
3. Response contains access_token and refresh_token
|
|
189
|
+
4. Include Authorization: Bearer <access_token> on all subsequent requests
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Method 2: Browser pairing
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
hermeslink pair
|
|
196
|
+
# Open the Pairing page URL printed in the terminal
|
|
197
|
+
# Click "Pair on this device" — the page shows your access_token and refresh_token
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Method 3: CLI / script (automation-friendly)
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# 1. Get the connect_token
|
|
204
|
+
CONNECT_TOKEN=$(hermeslink pair 2>&1 | grep "Connect token:" | awk '{print $NF}')
|
|
205
|
+
|
|
206
|
+
# 2. Exchange it for an access_token
|
|
207
|
+
curl -s -X POST http://localhost:18642/api/v1/auth/device-session \
|
|
208
|
+
-H "Authorization: Bearer $CONNECT_TOKEN" \
|
|
209
|
+
-H "Content-Type: application/json" \
|
|
210
|
+
-d '{"device_label":"my-script","device_platform":"cli"}'
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
> **Token expiry**: access_token lasts 2 hours, refresh_token lasts 90 days. When the access_token expires, use the refresh_token to get a new one — no need to re-pair.
|
|
216
|
+
|
|
217
|
+
## Commands
|
|
218
|
+
|
|
219
|
+
| Command | Description |
|
|
220
|
+
|---------|-------------|
|
|
221
|
+
| `hermeslink start` | Start the background daemon |
|
|
222
|
+
| `hermeslink stop` | Stop the daemon |
|
|
223
|
+
| `hermeslink restart` | Restart the daemon |
|
|
224
|
+
| `hermeslink status` | Show running status |
|
|
225
|
+
| `hermeslink pair` | Generate pairing URL and QR code |
|
|
226
|
+
| `hermeslink config get` | Show current config |
|
|
227
|
+
| `hermeslink config set <key> <value>` | Update a config value |
|
|
228
|
+
| `hermeslink autostart on` | Enable autostart on login (alias: `enable`) |
|
|
229
|
+
| `hermeslink autostart off` | Disable autostart (alias: `disable`) |
|
|
230
|
+
| `hermeslink logs` | View Link logs |
|
|
231
|
+
| `hermeslink logs --gateway` | View Hermes gateway logs |
|
|
232
|
+
| `hermeslink logs -n 100` | View last 100 log lines |
|
|
233
|
+
| `hermeslink version` | Show version |
|
|
234
|
+
|
|
235
|
+
## Configuration
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
hermeslink config set port 18642 # Change listen port (default: 18642)
|
|
239
|
+
hermeslink config set lan-host 192.168.1.10 # Set LAN IP manually (default: auto-detect)
|
|
240
|
+
hermeslink config set language en # Language: auto / en / zh-CN
|
|
241
|
+
hermeslink config set log-level debug # Log level: debug / info / warn / error
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Config file is at `~/.hermeslink/config.json`.
|
|
245
|
+
|
|
246
|
+
## API Reference
|
|
247
|
+
|
|
248
|
+
Service listens on `http://0.0.0.0:18642` by default. All authenticated endpoints use a Bearer Token with the `hlat_` prefix.
|
|
249
|
+
|
|
250
|
+
### Token Types
|
|
251
|
+
|
|
252
|
+
| Token | Prefix | Expiry | Purpose |
|
|
253
|
+
|-------|--------|--------|---------|
|
|
254
|
+
| Connect Token | none (base64url) | 5 min, one-time | Exchange for access_token |
|
|
255
|
+
| Access Token | `hlat_` | 15 min | All API requests |
|
|
256
|
+
| Refresh Token | `hlrt_` | 90 days | Refresh access_token |
|
|
257
|
+
|
|
258
|
+
### No Auth Required
|
|
259
|
+
|
|
260
|
+
| Method | Path | Description |
|
|
261
|
+
|--------|------|-------------|
|
|
262
|
+
| GET | `/pair` | Pairing web page (open in browser) |
|
|
263
|
+
| GET | `/api/v1/bootstrap` | Service info: link_id, version, capabilities |
|
|
264
|
+
|
|
265
|
+
### Auth / Devices
|
|
266
|
+
|
|
267
|
+
All endpoints below require `Authorization: Bearer hlat_xxx`
|
|
268
|
+
|
|
269
|
+
| Method | Path | Description |
|
|
270
|
+
|--------|------|-------------|
|
|
271
|
+
| GET | `/api/v1/auth/me` | Current token info and device details |
|
|
272
|
+
| POST | `/api/v1/auth/device-session` | Exchange connect_token for access/refresh tokens |
|
|
273
|
+
| POST | `/api/v1/auth/refresh` | Refresh access_token using refresh_token |
|
|
274
|
+
| POST | `/api/v1/auth/logout` | Revoke refresh_token |
|
|
275
|
+
|
|
276
|
+
**POST `/api/v1/auth/device-session`** — Authorization header takes the **connect_token** (not hlat_). Body:
|
|
277
|
+
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"device_label": "My Device",
|
|
281
|
+
"device_platform": "ios|android|web|cli",
|
|
282
|
+
"device_model": "optional device model"
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Response:
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"ok": true,
|
|
291
|
+
"device": { "device_id": "dev_xxx", "label": "My Device", "platform": "ios" },
|
|
292
|
+
"access_token": { "token": "hlat_xxx", "expires_at": "2026-05-08T13:00:00Z" },
|
|
293
|
+
"refresh_token": { "token": "hlrt_xxx", "expires_at": "2026-08-06T12:00:00Z" }
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**POST `/api/v1/auth/refresh`** Body:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{ "refresh_token": "hlrt_xxx" }
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Pairing
|
|
304
|
+
|
|
305
|
+
| Method | Path | Description |
|
|
306
|
+
|--------|------|-------------|
|
|
307
|
+
| GET | `/api/v1/pairing/session` | Query pairing session status (includes `claimed` field) |
|
|
308
|
+
| POST | `/api/v1/pairing/claim` | Complete pairing from the app side |
|
|
309
|
+
|
|
310
|
+
**GET `/api/v1/pairing/session`** query: `?session_id=ps_xxx`
|
|
311
|
+
|
|
312
|
+
**POST `/api/v1/pairing/claim`** Body:
|
|
313
|
+
|
|
314
|
+
```json
|
|
315
|
+
{
|
|
316
|
+
"session_id": "ps_xxx",
|
|
317
|
+
"claim_token": "<connect_token value>",
|
|
318
|
+
"device_label": "My App",
|
|
319
|
+
"device_platform": "ios"
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### System Status
|
|
324
|
+
|
|
325
|
+
| Method | Path | Description |
|
|
326
|
+
|--------|------|-------------|
|
|
327
|
+
| GET | `/api/v1/status` | Overall service status (version, device count, profile count, etc.) |
|
|
328
|
+
| GET | `/api/v1/logs` | Recent logs (`?source=link\|gateway&limit=50`) |
|
|
329
|
+
|
|
330
|
+
### Devices
|
|
331
|
+
|
|
332
|
+
| Method | Path | Description |
|
|
333
|
+
|--------|------|-------------|
|
|
334
|
+
| GET | `/api/v1/devices` | List all paired devices |
|
|
335
|
+
| PATCH | `/api/v1/devices/:deviceId` | Rename device (`{"label":"New Name"}`) |
|
|
336
|
+
| DELETE | `/api/v1/devices/:deviceId` | Revoke device (invalidates its tokens) |
|
|
337
|
+
| DELETE | `/api/v1/devices/:deviceId/app-listing` | Hide a revoked device from the list |
|
|
338
|
+
|
|
339
|
+
### Conversations
|
|
340
|
+
|
|
341
|
+
| Method | Path | Description |
|
|
342
|
+
|--------|------|-------------|
|
|
343
|
+
| GET | `/api/v1/conversations` | List conversations (`?limit=20&cursor=xxx`) |
|
|
344
|
+
| GET | `/api/v1/conversations/search` | Search conversations (`?q=keyword`) |
|
|
345
|
+
| POST | `/api/v1/conversations` | Create a new conversation |
|
|
346
|
+
| DELETE | `/api/v1/conversations` | Bulk delete conversations |
|
|
347
|
+
| DELETE | `/api/v1/conversations/:id` | Delete a single conversation |
|
|
348
|
+
| GET | `/api/v1/conversations/:id/messages` | Get messages in a conversation |
|
|
349
|
+
| POST | `/api/v1/conversations/:id/messages` | Send a message |
|
|
350
|
+
| GET | `/api/v1/conversations/:id/events` | SSE event stream for a conversation |
|
|
351
|
+
| GET | `/api/v1/conversations/events` | SSE stream for all conversations |
|
|
352
|
+
| PATCH | `/api/v1/conversations/:id/title` | Rename conversation (`{"title":"New Title"}`) |
|
|
353
|
+
| PATCH | `/api/v1/conversations/:id/model` | Switch model |
|
|
354
|
+
| PATCH | `/api/v1/conversations/:id/profile` | Switch profile |
|
|
355
|
+
| POST | `/api/v1/conversations/:id/ack` | Acknowledge events read |
|
|
356
|
+
| POST | `/api/v1/conversations/clear-plans` | Create a bulk-clear plan |
|
|
357
|
+
| GET | `/api/v1/conversations/clear-plans/:planId` | Query clear plan status |
|
|
358
|
+
| POST | `/api/v1/conversations/clear-plans/:planId/execute` | Execute clear plan |
|
|
359
|
+
| POST | `/api/v1/conversations/:id/runs/:runId/cancel` | Cancel an in-progress run |
|
|
360
|
+
| POST | `/api/v1/conversations/:id/approvals/:approvalId/approve` | Approve a tool call |
|
|
361
|
+
| POST | `/api/v1/conversations/:id/approvals/:approvalId/deny` | Deny a tool call |
|
|
362
|
+
| POST | `/api/v1/conversations/:id/blobs` | Upload attachment |
|
|
363
|
+
| GET | `/api/v1/conversations/:id/blobs/:blobId` | Download attachment |
|
|
364
|
+
| DELETE | `/api/v1/conversations/:id/blobs/:blobId` | Delete attachment |
|
|
365
|
+
|
|
366
|
+
### Statistics
|
|
367
|
+
|
|
368
|
+
| Method | Path | Description |
|
|
369
|
+
|--------|------|-------------|
|
|
370
|
+
| GET | `/api/v1/statistics` | Global usage stats (conversation count, message count, etc.) |
|
|
371
|
+
| GET | `/api/v1/statistics/usage` | Token usage (`?days=7&from=2026-05-01&to=2026-05-08&model=xxx&profile=xxx`) |
|
|
372
|
+
|
|
373
|
+
### Models
|
|
374
|
+
|
|
375
|
+
| Method | Path | Description |
|
|
376
|
+
|--------|------|-------------|
|
|
377
|
+
| GET | `/api/v1/models` | List available models (from Hermes API Server, OpenAI-compatible format) |
|
|
378
|
+
| GET | `/api/v1/model-configs` | List global model configs |
|
|
379
|
+
| POST | `/api/v1/model-configs` | Add global model config |
|
|
380
|
+
| PATCH | `/api/v1/model-configs/defaults` | Update default model config |
|
|
381
|
+
| DELETE | `/api/v1/model-configs` | Delete global model config |
|
|
382
|
+
|
|
383
|
+
### Profiles
|
|
384
|
+
|
|
385
|
+
| Method | Path | Description |
|
|
386
|
+
|--------|------|-------------|
|
|
387
|
+
| GET | `/api/v1/profiles` | List all profile names |
|
|
388
|
+
| POST | `/api/v1/profiles` | Create new profile (async, returns 202) |
|
|
389
|
+
| PATCH | `/api/v1/profiles/:name` | Rename (`{"name":"new-name"}`) or update metadata |
|
|
390
|
+
| DELETE | `/api/v1/profiles/:name` | Delete profile |
|
|
391
|
+
| GET | `/api/v1/profiles/catalog` | Full catalog (capabilities, permissions, modelConfigs per profile) |
|
|
392
|
+
| GET | `/api/v1/profile-creation/status` | Query creation progress |
|
|
393
|
+
| GET | `/api/v1/profile-creation/events` | Creation progress SSE stream |
|
|
394
|
+
| GET | `/api/v1/profiles/:name/status` | Profile status (existence, API key configuration, etc.) |
|
|
395
|
+
| GET | `/api/v1/profiles/:name/statistics` | Profile conversation statistics |
|
|
396
|
+
| GET | `/api/v1/profiles/:name/skills` | List profile skills (`?include_disabled=true`) |
|
|
397
|
+
| PATCH | `/api/v1/profiles/:name/skills/:skillName` | Enable/disable skill (`{"enabled":true}`) |
|
|
398
|
+
| GET | `/api/v1/profiles/:name/memory` | View memory (USER.md + MEMORY.md) |
|
|
399
|
+
| POST | `/api/v1/profiles/:name/memory/entries` | Add memory entry (`{"target":"memory","content":"..."}`) |
|
|
400
|
+
| PATCH | `/api/v1/profiles/:name/memory/entries` | Replace memory entry (`{"target":"memory","match":"old","content":"new"}`) |
|
|
401
|
+
| DELETE | `/api/v1/profiles/:name/memory/entries` | Delete memory entry (`{"target":"memory","match":"text to match"}`) |
|
|
402
|
+
| DELETE | `/api/v1/profiles/:name/memory` | Reset memory (`{"target":"memory\|user\|all"}`) |
|
|
403
|
+
| PATCH | `/api/v1/profiles/:name/memory/settings` | Update memory provider settings |
|
|
404
|
+
| PATCH | `/api/v1/profiles/:name/memory/provider` | Switch memory provider (`{"provider":"built-in"}`) |
|
|
405
|
+
| GET | `/api/v1/profiles/:name/permissions` | View permissions config |
|
|
406
|
+
| PATCH | `/api/v1/profiles/:name/permissions` | Update permissions config |
|
|
407
|
+
| GET | `/api/v1/profiles/:name/tool-configs/:toolKey` | View tool config (toolKey: `web` / `image_gen` / `stt` / `tts` / `messaging` / `homeassistant` / `rl`) |
|
|
408
|
+
| PATCH | `/api/v1/profiles/:name/tool-configs/:toolKey` | Update tool config |
|
|
409
|
+
| GET | `/api/v1/profiles/:name/model-configs` | List profile model configs |
|
|
410
|
+
| POST | `/api/v1/profiles/:name/model-configs` | Add profile model config |
|
|
411
|
+
| PATCH | `/api/v1/profiles/:name/model-configs/defaults` | Update profile default model |
|
|
412
|
+
| DELETE | `/api/v1/profiles/:name/model-configs` | Delete profile model config |
|
|
413
|
+
|
|
414
|
+
Memory `target` values: `"memory"` (agent notes, MEMORY.md) or `"user"` (user info, USER.md).
|
|
415
|
+
|
|
416
|
+
### Cron Jobs
|
|
417
|
+
|
|
418
|
+
| Method | Path | Description |
|
|
419
|
+
|--------|------|-------------|
|
|
420
|
+
| GET | `/api/v1/cron-jobs` | List all cron jobs across all profiles |
|
|
421
|
+
| GET | `/api/v1/profiles/:name/cron-jobs` | List cron jobs for a specific profile |
|
|
422
|
+
| POST | `/api/v1/profiles/:name/cron-jobs` | Create cron job |
|
|
423
|
+
| GET | `/api/v1/profiles/:name/cron-jobs/:jobId` | Get cron job details |
|
|
424
|
+
| PATCH | `/api/v1/profiles/:name/cron-jobs/:jobId` | Update cron job |
|
|
425
|
+
| DELETE | `/api/v1/profiles/:name/cron-jobs/:jobId` | Delete cron job |
|
|
426
|
+
| POST | `/api/v1/profiles/:name/cron-jobs/:jobId/pause` | Pause cron job |
|
|
427
|
+
| POST | `/api/v1/profiles/:name/cron-jobs/:jobId/resume` | Resume cron job |
|
|
428
|
+
| POST | `/api/v1/profiles/:name/cron-jobs/:jobId/run` | Run cron job immediately |
|
|
429
|
+
|
|
430
|
+
### Runs
|
|
431
|
+
|
|
432
|
+
| Method | Path | Description |
|
|
433
|
+
|--------|------|-------------|
|
|
434
|
+
| POST | `/api/v1/runs` | Submit a run to Hermes Agent (returns 202) |
|
|
435
|
+
| GET | `/api/v1/runs/:runId/events` | Subscribe to run event stream (SSE proxy) |
|
|
436
|
+
| POST | `/api/v1/runs/:runId/cancel` | Cancel a run |
|
|
437
|
+
|
|
438
|
+
**POST `/api/v1/runs`** Body:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"input": "Please organise my ~/Downloads folder",
|
|
443
|
+
"profile": "default",
|
|
444
|
+
"instructions": "optional system instructions",
|
|
445
|
+
"session_id": "optional session ID",
|
|
446
|
+
"conversation_history": []
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Response (202):
|
|
451
|
+
|
|
452
|
+
```json
|
|
453
|
+
{
|
|
454
|
+
"run_id": "run_xxx",
|
|
455
|
+
"fallback": false
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Updates
|
|
460
|
+
|
|
461
|
+
#### Hermes Agent
|
|
462
|
+
|
|
463
|
+
| Method | Path | Description |
|
|
464
|
+
|--------|------|-------------|
|
|
465
|
+
| GET | `/api/v1/hermes/update-check` | Check for a new Hermes Agent version |
|
|
466
|
+
| GET | `/api/v1/hermes/update/status` | Query Hermes update progress |
|
|
467
|
+
| POST | `/api/v1/hermes/update` | Trigger Hermes Agent update |
|
|
468
|
+
| GET | `/api/v1/hermes/update/events` | Update progress SSE stream |
|
|
469
|
+
|
|
470
|
+
#### Link itself
|
|
471
|
+
|
|
472
|
+
| Method | Path | Description |
|
|
473
|
+
|--------|------|-------------|
|
|
474
|
+
| GET | `/api/v1/link/update-check` | Check for a new Link version |
|
|
475
|
+
| GET | `/api/v1/link/update/status` | Query Link update progress |
|
|
476
|
+
| POST | `/api/v1/link/update` | Trigger Link self-update (`{"version":"0.3.0"}`) |
|
|
477
|
+
| GET | `/api/v1/link/update/events` | Update progress SSE stream |
|
|
478
|
+
|
|
479
|
+
### System
|
|
480
|
+
|
|
481
|
+
| Method | Path | Description |
|
|
482
|
+
|--------|------|-------------|
|
|
483
|
+
| GET | `/api/v1/system/status` | System details (version, autostart state, network info) |
|
|
484
|
+
| GET | `/api/v1/system/version` | Link version only |
|
|
485
|
+
| POST | `/api/v1/system/autostart/enable` | Enable autostart on login |
|
|
486
|
+
| POST | `/api/v1/system/autostart/disable` | Disable autostart |
|
|
487
|
+
| GET | `/api/v1/system/logs` | Recent Link logs |
|
|
488
|
+
| GET | `/api/v1/system/logs/gateway` | Recent gateway logs |
|
|
489
|
+
| GET | `/api/v1/system/updates` | Available updates (Hermes + Link combined) |
|
|
490
|
+
| POST | `/api/v1/system/updates/dismiss` | Dismiss current update notification |
|
|
491
|
+
|
|
492
|
+
### Error Response Format
|
|
493
|
+
|
|
494
|
+
All errors return:
|
|
495
|
+
|
|
496
|
+
```json
|
|
497
|
+
{
|
|
498
|
+
"ok": false,
|
|
499
|
+
"error": {
|
|
500
|
+
"code": "error_code",
|
|
501
|
+
"message": "Human readable message"
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Common error codes:
|
|
507
|
+
|
|
508
|
+
| code | HTTP | Description |
|
|
509
|
+
|------|------|-------------|
|
|
510
|
+
| `auth_required` | 401 | No Authorization header |
|
|
511
|
+
| `device_access_token_invalid` | 401 | access_token expired or invalid |
|
|
512
|
+
| `auth_invalid` | 401 | connect_token invalid or already used |
|
|
513
|
+
| `pairing_session_not_found` | 404 | Pairing session not found |
|
|
514
|
+
| `pairing_session_expired` | 404 | Pairing session expired |
|
|
515
|
+
| `pairing_claim_mismatch` | 409 | Pairing token mismatch |
|
|
516
|
+
| `link_not_paired` | 409 | Service has not been assigned a link_id yet |
|
|
517
|
+
|
|
518
|
+
## Examples
|
|
519
|
+
|
|
520
|
+
### Full pairing and usage script
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
#!/bin/bash
|
|
524
|
+
BASE="http://localhost:18642"
|
|
525
|
+
|
|
526
|
+
# Step 1: Generate a connect token
|
|
527
|
+
CONNECT=$(hermeslink pair 2>&1 | grep "Connect token:" | awk '{print $NF}')
|
|
528
|
+
echo "Connect token: $CONNECT"
|
|
529
|
+
|
|
530
|
+
# Step 2: Exchange for access_token and refresh_token
|
|
531
|
+
RESP=$(curl -s -X POST "$BASE/api/v1/auth/device-session" \
|
|
532
|
+
-H "Authorization: Bearer $CONNECT" \
|
|
533
|
+
-H "Content-Type: application/json" \
|
|
534
|
+
-d '{"device_label":"my-script","device_platform":"cli"}')
|
|
535
|
+
|
|
536
|
+
ACCESS=$(echo $RESP | python3 -c "import json,sys; print(json.load(sys.stdin)['access_token']['token'])")
|
|
537
|
+
REFRESH=$(echo $RESP | python3 -c "import json,sys; print(json.load(sys.stdin)['refresh_token']['token'])")
|
|
538
|
+
echo "Access: $ACCESS"
|
|
539
|
+
echo "Refresh: $REFRESH"
|
|
540
|
+
|
|
541
|
+
# Step 3: Check status
|
|
542
|
+
curl -s "$BASE/api/v1/status" -H "Authorization: Bearer $ACCESS" | python3 -m json.tool
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Refresh token
|
|
546
|
+
|
|
547
|
+
```bash
|
|
548
|
+
curl -s -X POST http://localhost:18642/api/v1/auth/refresh \
|
|
549
|
+
-H "Content-Type: application/json" \
|
|
550
|
+
-d "{\"refresh_token\":\"$REFRESH\"}"
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### List devices
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
curl -s http://localhost:18642/api/v1/devices \
|
|
557
|
+
-H "Authorization: Bearer $ACCESS"
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### List conversations
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
curl -s "http://localhost:18642/api/v1/conversations?limit=10" \
|
|
564
|
+
-H "Authorization: Bearer $ACCESS"
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Autostart
|
|
568
|
+
|
|
569
|
+
- **macOS**: via launchd (`~/Library/LaunchAgents/com.hermes.link.plist`)
|
|
570
|
+
- **Linux**: via systemd user service or XDG autostart
|
|
571
|
+
- **Windows**: via the Startup folder
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
hermeslink autostart on
|
|
575
|
+
hermeslink autostart off
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## Runtime Files
|
|
579
|
+
|
|
580
|
+
All files are stored under `~/.hermeslink/`:
|
|
581
|
+
|
|
582
|
+
| Path | Description |
|
|
583
|
+
|------|-------------|
|
|
584
|
+
| `config.json` | User configuration |
|
|
585
|
+
| `identity.json` | Device identity (ed25519 key pair + link_id) |
|
|
586
|
+
| `credentials.json` | Access tokens for paired devices |
|
|
587
|
+
| `app-connect-tokens.json` | Pending pairing tokens (5-minute TTL) |
|
|
588
|
+
| `conversations/` | Conversation data |
|
|
589
|
+
| `blobs/` | File attachments |
|
|
590
|
+
| `pairing/` | Pairing sessions |
|
|
591
|
+
| `link.db` | SQLite database (usage statistics) |
|
|
592
|
+
| `logs/` | Log files |
|
|
593
|
+
|
|
594
|
+
Uninstalling the npm package does not delete this directory — reinstalling reuses the same link_id.
|
|
595
|
+
|
|
596
|
+
## Environment Variables
|
|
597
|
+
|
|
598
|
+
| Variable | Description |
|
|
599
|
+
|----------|-------------|
|
|
600
|
+
| `HERMESLINK_HOME` | Override the runtime directory (default: `~/.hermeslink`) |
|
|
601
|
+
| `HERMESLINK_LOG_LEVEL` | Override log level |
|
|
602
|
+
| `HERMESLINK_LANG` | Override language (`en` / `zh-CN`) |
|
|
603
|
+
| `HERMES_BIN` | Path to the `hermes` binary (default: `hermes`) |
|
|
604
|
+
| `HERMESLINK_LISTEN_HOST` | HTTP listen address (default: `0.0.0.0`) |
|
|
605
|
+
|
|
606
|
+
## Development
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
# Install dependencies and build
|
|
610
|
+
npm install
|
|
611
|
+
npm run build
|
|
612
|
+
|
|
613
|
+
# Run in foreground (for debugging)
|
|
614
|
+
npm run dev:run
|
|
615
|
+
# or
|
|
616
|
+
node dist/cli/index.js daemon --foreground
|
|
617
|
+
|
|
618
|
+
# Watch mode (auto-rebuild on source changes, restart manually)
|
|
619
|
+
npm run dev:watch # terminal 1: watch and rebuild
|
|
620
|
+
node dist/cli/index.js daemon --foreground # terminal 2: run the service
|
|
621
|
+
|
|
622
|
+
# TypeScript type check
|
|
623
|
+
npm run check
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
After starting, visit `http://localhost:18642/api/v1/bootstrap` to verify the service is running.
|
|
627
|
+
|
|
628
|
+
## License
|
|
629
|
+
|
|
630
|
+
MIT
|