hermes-to-claude 0.1.0
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/DESIGN.md +233 -0
- package/LICENSE +21 -0
- package/README.md +236 -0
- package/build.mjs +35 -0
- package/dist/hbridge.mjs +1677 -0
- package/dist/statusline.mjs +50 -0
- package/docs/local-mode.md +67 -0
- package/docs/mcp-spec.md +85 -0
- package/docs/spawn-mechanism.md +96 -0
- package/package.json +29 -0
- package/scripts/setup-mcp.cjs +53 -0
package/DESIGN.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# H-Bridge (hbridge) Design
|
|
2
|
+
|
|
3
|
+
Hermes Bridge — local HTTP bridge from Hermes Agent to Claude Code.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install
|
|
9
|
+
├─ preinstall: build → dist/hbridge.mjs + dist/statusline.mjs
|
|
10
|
+
├─ install: npm deps
|
|
11
|
+
└─ postinstall: register statusLine + skill in ~/.claude configs
|
|
12
|
+
|
|
13
|
+
/hbridge enable
|
|
14
|
+
└─ HTTP server on homePort(cwd)
|
|
15
|
+
├─ GET /health → {"status":"ok"}
|
|
16
|
+
├─ POST /v1/task/create → Bridge.createTask(prompt)
|
|
17
|
+
│ → new Session → spawn Claude
|
|
18
|
+
├─ POST /v1/task/cancel → Session.cancel()
|
|
19
|
+
├─ POST /v1/task/permission → Session.respondPermission()
|
|
20
|
+
├─ GET /v1/task/output?task_id=x → Bridge.getTaskOutput(id)
|
|
21
|
+
├─ GET /v1/task?task_id=x → Bridge.getTask(id)
|
|
22
|
+
└─ GET /v1/task/output/stream?id=x → SSE streaming
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Session Pool
|
|
26
|
+
|
|
27
|
+
Each task gets its own `Session` (one Claude Code child process). Sessions run in parallel up to `maxConcurrent` (default 3). If at capacity, tasks are queued in `_pendingQueue` and start when a slot opens.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Bridge.createTask(prompt)
|
|
31
|
+
├─ _sessions.size < maxConcurrent
|
|
32
|
+
│ └─ new Session → spawn Claude → send prompt → onComplete → persist
|
|
33
|
+
└─ _sessions.size >= maxConcurrent
|
|
34
|
+
└─ _pendingQueue.push → _dequeueNext() when slot opens
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Session Lifecycle
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Session.start()
|
|
41
|
+
├─ spawn npx @anthropic-ai/claude-code --print ...
|
|
42
|
+
├─ StdioTransport (stdin/stdout NDJSON)
|
|
43
|
+
├─ send prompt (type: "user")
|
|
44
|
+
├─ _onMessage() handles all NDJSON message types
|
|
45
|
+
│ ├─ stream_event → progressive text accumulation
|
|
46
|
+
│ ├─ assistant → content block parsing (text/tool_use/tool_result)
|
|
47
|
+
│ ├─ control_request/can_use_tool → permission pipeline
|
|
48
|
+
│ ├─ tool_progress → SSE forwarding
|
|
49
|
+
│ ├─ user → multi-turn auto-respond
|
|
50
|
+
│ └─ stop_reason / result → _finishTask() → persist + cleanup
|
|
51
|
+
└─ _cleanup() → kill child, close transport
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Permission Pipeline
|
|
55
|
+
|
|
56
|
+
By default, when Claude wants to use a tool (Bash, Read, Write...), it sends a `can_use_tool` control_request. The Session blocks and emits an SSE `permission_request` event. Hermes must respond via `POST /v1/task/permission`.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Claude → {"type":"control_request","request":{"subtype":"can_use_tool","tool_name":"Bash",...}}
|
|
60
|
+
→ Session._onMessage
|
|
61
|
+
→ if permissionMode == "bypass": auto-allow (no SSE event)
|
|
62
|
+
→ if permissionMode == "approve": block + emit permission_request SSE
|
|
63
|
+
→ Hermes sees SSE → POST /v1/task/permission
|
|
64
|
+
→ Session.respondPermission("allow"|"deny")
|
|
65
|
+
→ control_response → Claude proceeds or stops
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Modes (set per-task at creation):
|
|
69
|
+
| `permission_mode` | Behavior |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `"approve"` (default) | Hermes must approve each tool call |
|
|
72
|
+
| `"bypass"` | Session auto-approves all tool calls |
|
|
73
|
+
| `skip_permissions: true` | `--dangerously-skip-permissions` flag, Claude never asks |
|
|
74
|
+
|
|
75
|
+
## API Endpoints
|
|
76
|
+
|
|
77
|
+
| Endpoint | Method | Auth | Description |
|
|
78
|
+
|----------|--------|------|-------------|
|
|
79
|
+
| `/health` | GET | No | Health check |
|
|
80
|
+
| `/v1/task/create` | POST | Yes | Create a task + optional `permission_mode` + `skip_permissions` |
|
|
81
|
+
| `/v1/task/cancel` | POST | Yes | Cancel a running or queued task |
|
|
82
|
+
| `/v1/task/permission` | POST | Yes | `{"task_id","behavior","updatedInput?","message?"}` |
|
|
83
|
+
| `/v1/task/output?task_id=x` | GET | Yes | Get task result (memory → disk fallback) |
|
|
84
|
+
| `/v1/task/output/stream?task_id=x` | GET | Yes | SSE streaming (chunk, permission_request, done, error) |
|
|
85
|
+
| `/v1/task?task_id=x` | GET | Yes | Get task status only |
|
|
86
|
+
|
|
87
|
+
### SSE Streaming Events
|
|
88
|
+
|
|
89
|
+
Connect to `/v1/task/output/stream?task_id=xxx`:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
data: {"type":"connected","taskId":"task_xxx"}
|
|
93
|
+
data: {"type":"chunk","text":"Fixing bug..."}
|
|
94
|
+
data: {"type":"permission_request","task_id":"t1","tool_name":"Bash","input":{"command":"git status"}}
|
|
95
|
+
data: {"type":"done","exitCode":0}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Task Lifecycle Diagram
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Hermes hbridge:<port> Session (Claude Code)
|
|
102
|
+
│ │ │
|
|
103
|
+
│ POST /v1/task/create │ │
|
|
104
|
+
│ {"prompt":"fix bug"} │ │
|
|
105
|
+
│──────────────────────────────▶│ │
|
|
106
|
+
│ {"task_id":"task_xxx", │ spawn Claude Code │
|
|
107
|
+
│ "status":"created"} │ stdin (NDJSON): │
|
|
108
|
+
│◀──────────────────────────────│ {"type":"user", │
|
|
109
|
+
│ (immediate, no wait) │ "message":{"role":"user", │
|
|
110
|
+
│ │ "content":"fix bug"}, │
|
|
111
|
+
│ │──────────────────────────────▶│
|
|
112
|
+
│ │ │ execute
|
|
113
|
+
│ │ stdout (NDJSON): │
|
|
114
|
+
│ │ stream_event (progressive) │
|
|
115
|
+
│ │ SSE: chunk events │
|
|
116
|
+
│ │ result / stop_reason │
|
|
117
|
+
│ │◀──────────────────────────────│
|
|
118
|
+
│ │ task persisted to disk │
|
|
119
|
+
│ │ │
|
|
120
|
+
│ GET /v1/task/output? │ │
|
|
121
|
+
│ task_id=task_xxx │ │
|
|
122
|
+
│──────────────────────────────▶│ │
|
|
123
|
+
│ {"retrieval_status":"success",│ │
|
|
124
|
+
│ "task":{"status":"done", │ │
|
|
125
|
+
│ "result":"Fixed...", │ │
|
|
126
|
+
│ "exitCode":0, │ │
|
|
127
|
+
│ "usage":{...}}} │ │
|
|
128
|
+
│◀──────────────────────────────│ │
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Task Result Format
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"retrieval_status": "success" | "pending",
|
|
136
|
+
"task": {
|
|
137
|
+
"id": "task_xxx",
|
|
138
|
+
"status": "done" | "failed" | "running",
|
|
139
|
+
"result": "Hello.",
|
|
140
|
+
"exitCode": 0,
|
|
141
|
+
"usage": {
|
|
142
|
+
"total_cost_usd": 0.01234,
|
|
143
|
+
"input_tokens": 150,
|
|
144
|
+
"output_tokens": 300,
|
|
145
|
+
"cache_creation_input_tokens": 10,
|
|
146
|
+
"cache_read_input_tokens": 20
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Design Rationale: Key vs Port
|
|
153
|
+
|
|
154
|
+
**Key is machine-global**, **port is per-directory**. This follows Claude Code's working-directory-centric model.
|
|
155
|
+
|
|
156
|
+
Claude Code reads its context — `CLAUDE.md`, project skills, `.claude/settings.json` — from the **current working directory**. Each project has its own conventions, its own skills, its own CLAUDE.md. To serve the right context, hbridge must spawn Claude Code in the right directory.
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
/project-a/CLAUDE.md → hbridge :9200 → Claude Code (cwd=/project-a)
|
|
160
|
+
/project-b/CLAUDE.md → hbridge :9201 → Claude Code (cwd=/project-b)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Port per directory** (`9200 + MD5(cwd) % 600`) means:
|
|
164
|
+
- Each project gets a stable, predictable port
|
|
165
|
+
- You can run hbridge for multiple projects on the same machine without conflict
|
|
166
|
+
- The port is deterministic — Hermes computes it from the directory path without asking the server
|
|
167
|
+
|
|
168
|
+
**Key per machine** means:
|
|
169
|
+
- Hermes only needs one credential per machine, not one per project
|
|
170
|
+
- The key is random, generated once, persisted in `~/.hbridge_key`
|
|
171
|
+
- Same auth works across all directories on the same host
|
|
172
|
+
|
|
173
|
+
Key format: `hb_` + 8 base52 chars (≈ 45 bits entropy, `crypto.randomBytes(8)`, stored once).
|
|
174
|
+
|
|
175
|
+
## Key & Port Derivation
|
|
176
|
+
|
|
177
|
+
- **Port**: `9200 + (MD5(cwd)[0:2] % 600)` — range [9200, 9799], stable per directory
|
|
178
|
+
- **Key**: `hb_` + 8 random base52 characters — generated once, stored in `~/.hbridge_key`. Same for all directories on one machine.
|
|
179
|
+
|
|
180
|
+
## Task Persistence
|
|
181
|
+
|
|
182
|
+
Completed tasks are saved to `~/.hbridge_tasks.jsonl` (JSONL, append-only, max 2000 entries). On server restart, tasks are loaded into memory. `getTaskOutput` checks session → memory cache → disk.
|
|
183
|
+
|
|
184
|
+
## Files
|
|
185
|
+
|
|
186
|
+
### Runtime (gitignored)
|
|
187
|
+
| File | Purpose |
|
|
188
|
+
|------|---------|
|
|
189
|
+
| `~/.hbridge_state.json` | running/stopped flag + port + lastClientIP/lastActiveAt |
|
|
190
|
+
| `~/.hbridge_tasks.jsonl` | Completed task records (JSONL, survive restart) |
|
|
191
|
+
|
|
192
|
+
### Config (written by postinstall)
|
|
193
|
+
| File | Purpose |
|
|
194
|
+
|------|---------|
|
|
195
|
+
| `~/.claude/settings.json` | StatusLine command (highest priority) |
|
|
196
|
+
|
|
197
|
+
## StatusLine
|
|
198
|
+
|
|
199
|
+
Claude Code polls ~3-5s:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
▶️ hbridge: on | :<port>
|
|
203
|
+
⏹️ hbridge: off
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Liveness is determined by polling `127.0.0.1:<port>/health` — no state-file dependency.
|
|
207
|
+
|
|
208
|
+
## Changelog
|
|
209
|
+
|
|
210
|
+
### #51 — Multi-session + persistence
|
|
211
|
+
- **Sequential queue busy-polls** ✅ Fixed
|
|
212
|
+
- Old: `createTask` polled `setInterval` until previous task finished
|
|
213
|
+
- New: Session pool with promise queue — no busy-polling
|
|
214
|
+
- **Task timeout kills queue** ✅ Fixed (see #52)
|
|
215
|
+
- **Persistence**: Completed tasks saved to `~/.hbridge_tasks.jsonl`, survive restart
|
|
216
|
+
- **Session class**: One Claude Code child process per task
|
|
217
|
+
- **Parallelism**: Up to 3 concurrent tasks (configurable via `maxConcurrent`)
|
|
218
|
+
|
|
219
|
+
### #52 — Remove hard 5-min timeout
|
|
220
|
+
- `TASK_TIMEOUT_MS` default changed from 300_000 to 0 (no timeout)
|
|
221
|
+
- Timeout is now opt-in: pass `taskTimeoutMs` to Bridge/Session constructor
|
|
222
|
+
|
|
223
|
+
### #53 — Home mode direct enable
|
|
224
|
+
- Home mode (`HBRIDGE_HOME=1`) now calls `cmd_enable()` directly
|
|
225
|
+
- No longer goes through `--stdio` MCP layer
|
|
226
|
+
- `--stdio` removed — hbridge is HTTP-only
|
|
227
|
+
|
|
228
|
+
### #54+ — Permission pipeline
|
|
229
|
+
- `--dangerously-skip-permissions` replaced by Hermes-controlled permission pipeline
|
|
230
|
+
- Default mode `"approve"`: every tool call waits for Hermes decision
|
|
231
|
+
- `POST /v1/task/permission` for allow/deny/modify
|
|
232
|
+
- SSE `permission_request` events for real-time Hermes notification
|
|
233
|
+
- Task-level `permission_mode` and `skip_permissions` overrides
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xu Han
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Hermes-Claude-Bridge (hbridge)
|
|
2
|
+
|
|
3
|
+
**Hermes-Agent** controls multiple **Claude Code** instances via HTTP — one agent, many Claude workers. No Pro/Max subscription required.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
📱 User ----HTTP----> 🤖 Hermes-Agent ----hbridge----> 🏭 Claude Code (deploy role)
|
|
7
|
+
├───hbridge───> 🔧 Claude Code (coding role)
|
|
8
|
+
├───hbridge───> 🧪 Claude Code (testing role)
|
|
9
|
+
└───hbridge───> 🔬 Claude Code (building role)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. hbridge Advantages
|
|
15
|
+
|
|
16
|
+
- **No Pro/Max required** — works with any Claude Code via stdio; no Anthropic subscription needed.
|
|
17
|
+
- **One agent, many Claudes** — one Hermes-Agent routes tasks to multiple Claude Code instances, each in its own project directory with its own CLAUDE.md and skills.
|
|
18
|
+
- **Default-off, secure** — zero ports open until you explicitly `enable`. Auth key protects all endpoints.
|
|
19
|
+
- **Cross-platform** — Windows / Linux / macOS. Single codebase.
|
|
20
|
+
- **Local-only** — no cloud, no external API. Fully offline.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. For Human Users
|
|
25
|
+
|
|
26
|
+
### Prepare
|
|
27
|
+
|
|
28
|
+
hbridge requires **Node.js ≥ 20**. Install it for your platform:
|
|
29
|
+
|
|
30
|
+
| Platform | Command |
|
|
31
|
+
|----------|---------|
|
|
32
|
+
| Linux (Ubuntu/Debian) | `sudo apt install nodejs npm` |
|
|
33
|
+
| macOS | `brew install node` |
|
|
34
|
+
| Windows | `winget install OpenJS.NodeJS` or download from https://nodejs.org |
|
|
35
|
+
|
|
36
|
+
### Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/xuhancn/hermes-to-claude.git
|
|
40
|
+
cd hermes-claude-bridge
|
|
41
|
+
npm install && npm run build
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Start in Claude Code
|
|
45
|
+
|
|
46
|
+
After installation, enable hbridge from inside Claude Code:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
/hbridge enable
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This starts the HTTP server. The port is derived from the working directory (see Authentication and Port below). Check status at any time:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
/hbridge status
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Home Mode — For Headless Machines
|
|
59
|
+
|
|
60
|
+
When Hermes-Agent runs on a server with no display, no manual command is needed. Export the **environment variable** before starting hbridge:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
export HBRIDGE_HOME=1 # environment variable — auto-starts hbridge
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With Home mode active, the server listens on `127.0.0.1` only. Authentication is **disabled** — safe because only local processes can reach it. Hermes-Agent connects without managing keys.
|
|
67
|
+
|
|
68
|
+
### Authentication and Port
|
|
69
|
+
|
|
70
|
+
The port is **deterministic**: `MD5(cwd)` → first 2 bytes → `9200 + (value % 600)`. Each project directory gets its own port. This is intentional:
|
|
71
|
+
|
|
72
|
+
- Each Claude Code instance runs in its own working directory
|
|
73
|
+
- Claude reads its own CLAUDE.md, skills, and project files from that directory
|
|
74
|
+
- Port = project — you always know which Claude you're talking to
|
|
75
|
+
|
|
76
|
+
The auth key (`hb_` + 8 random base52 characters) is written to `~/.hbridge_key` once and reused across all directories on the same machine. All HTTP endpoints (except `/health`) require HTTP Basic Auth with username `bridge` and the key as password.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 3. For Hermes-Agent
|
|
81
|
+
|
|
82
|
+
Hermes-Agent discovers and controls hbridge via HTTP. Here is the complete API.
|
|
83
|
+
|
|
84
|
+
### Security
|
|
85
|
+
|
|
86
|
+
Like Claude Code CLI, hbridge requires authentication for all mutating endpoints. But Hermes-Agent can simplify — either handle the auth flow itself, or bypass it entirely with `skip_permissions` for trusted workloads. Hermes-Agent must handle the same authentication and permission flow that human users would, or explicitly choose to bypass it.
|
|
87
|
+
|
|
88
|
+
### Create a Task
|
|
89
|
+
|
|
90
|
+
Spawn a Claude Code session:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
curl -X POST http://<host>:<port>/v1/task/create \
|
|
94
|
+
-H "Authorization: Basic <base64(bridge:key)>" \
|
|
95
|
+
-H "Content-Type: application/json" \
|
|
96
|
+
-d '{"prompt": "fix the off-by-one bug in main.c"}'
|
|
97
|
+
# → {"task_id":"task_xxx","status":"created"}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
To skip permission prompts for development workloads:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
curl -X POST http://<host>:<port>/v1/task/create \
|
|
104
|
+
-H "Authorization: Basic <base64(bridge:key)>" \
|
|
105
|
+
-H "Content-Type: application/json" \
|
|
106
|
+
-d '{"prompt": "...", "skip_permissions": true}'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Optional field | Type | Description |
|
|
110
|
+
|---------------|------|-------------|
|
|
111
|
+
| `permission_mode` | `"approve"` (default) or `"bypass"` | Tool approval behavior |
|
|
112
|
+
| `skip_permissions` | `boolean` | Skip all permission prompts |
|
|
113
|
+
| `cwd` | string | Working directory for the Claude session |
|
|
114
|
+
| `sessionId` | string | Reuse an existing Claude session |
|
|
115
|
+
|
|
116
|
+
### Health Check
|
|
117
|
+
|
|
118
|
+
Verify the server is reachable — no authentication required:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
curl http://<host>:<port>/health
|
|
122
|
+
# → {"status":"ok"}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Check health after creating a task to confirm the bridge is running and ready.
|
|
126
|
+
|
|
127
|
+
### Get Task Output
|
|
128
|
+
|
|
129
|
+
Poll until `status` is `"done"` or `"failed"`. Long-running tasks (code generation, test suites) may take minutes.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
curl "http://<host>:<port>/v1/task/output?task_id=task_xxx" \
|
|
133
|
+
-H "Authorization: Basic <base64(bridge:key)>"
|
|
134
|
+
# → {"retrieval_status":"success","task":{"status":"done","result":"Fixed.","exitCode":0}}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Cancel a Task
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
curl -X POST http://<host>:<port>/v1/task/cancel \
|
|
141
|
+
-H "Authorization: Basic <base64(bridge:key)>" \
|
|
142
|
+
-H "Content-Type: application/json" \
|
|
143
|
+
-d '{"task_id":"task_xxx"}'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Permission Pipeline
|
|
147
|
+
|
|
148
|
+
hbridge includes a full permission pipeline — just like CI tools. Hermes-Agent can decide per-task how to handle tool approvals:
|
|
149
|
+
|
|
150
|
+
| Mode | Behavior |
|
|
151
|
+
|------|----------|
|
|
152
|
+
| `approve` (default) | Claude sends `control_request` → Hermes decides allow/deny |
|
|
153
|
+
| `bypass` | Claude sends request but doesn't block; Hermes can log for audit |
|
|
154
|
+
| `skip_permissions` | `--dangerously-skip-permissions` — no requests, maximum speed |
|
|
155
|
+
|
|
156
|
+
For quick development tasks, use `skip_permissions`. For production workloads that need an audit trail, keep the default `approve` mode. Hermes-Agent can also respond with `updatedInput` to modify tool parameters before Claude executes them.
|
|
157
|
+
|
|
158
|
+
### Architecture
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
Hermes ──HTTP──▶ hbridge :<port> ──spawn──▶ Session (Claude Code)
|
|
162
|
+
│ │ │
|
|
163
|
+
│ Control Layer │ server.mjs (HTTP routing) │ reads CLAUDE.md
|
|
164
|
+
│ (create/cancel/ │ bridge.mjs (session pool) │ loads skills
|
|
165
|
+
│ output/poll) │ session.mjs (per-task proc) │ edits files
|
|
166
|
+
│ │ │
|
|
167
|
+
│ Transport Layer │ StdioTransport │
|
|
168
|
+
│ (SSE streaming) │ (NDJSON stdin/stdout pipe) │ Claude stdout
|
|
169
|
+
│ │ │
|
|
170
|
+
└─ poll or SSE ◀────┴─ task output (status/result) ─┘
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Hermes-Agent dispatches tasks through two layers:
|
|
174
|
+
|
|
175
|
+
- **Control Layer**: HTTP endpoints for task lifecycle — create, cancel, poll for results, health. This is what Sections 3.1–3.5 describe.
|
|
176
|
+
- **Transport Layer**: Streaming, real-time progress, and persistent transcript via SSE and NDJSON. This is described below.
|
|
177
|
+
|
|
178
|
+
Claude Code loads project-specific CLAUDE.md and skills from the working directory as a per-task child process. Each session is isolated.
|
|
179
|
+
|
|
180
|
+
### Transport Layer
|
|
181
|
+
|
|
182
|
+
Beyond polling `getTaskOutput`, Hermes-Agent can receive **real-time streaming** via Server-Sent Events (SSE) and read the raw Claude Code NDJSON transcript:
|
|
183
|
+
|
|
184
|
+
#### SSE Streaming
|
|
185
|
+
|
|
186
|
+
Subscribe to a task's output stream to receive events as they happen — no polling needed:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
curl -N "http://<host>:<port>/v1/task/output/stream?task_id=task_xxx" \
|
|
190
|
+
-H "Authorization: Basic <base64(bridge:key)>"
|
|
191
|
+
# event: chunk
|
|
192
|
+
# data: {"task_id":"task_xxx","chunk":"Fixing the off-by-one...\n"}
|
|
193
|
+
#
|
|
194
|
+
# event: done
|
|
195
|
+
# data: {"task_id":"task_xxx","status":"done","exitCode":0}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Event types pushed over SSE:
|
|
199
|
+
|
|
200
|
+
| Event | Description |
|
|
201
|
+
|-------|-------------|
|
|
202
|
+
| `chunk` | Incremental text output from Claude Code |
|
|
203
|
+
| `status` | Task status change (running → done / failed) |
|
|
204
|
+
| `tool_progress` | Tool execution progress (tool name + elapsed time) |
|
|
205
|
+
| `permission_request` | Claude requests tool approval (in `approve` mode) |
|
|
206
|
+
|
|
207
|
+
#### NDJSON Transcript
|
|
208
|
+
|
|
209
|
+
The raw Claude Code stdout is saved to `~/.hbridge_transcript.jsonl`. Each line is a complete NDJSON message from Claude Code — `assistant` messages, `stream_event` deltas, `tool_use` blocks, and `result` messages with usage data:
|
|
210
|
+
|
|
211
|
+
```jsonl
|
|
212
|
+
{"type":"stream_event","event":{"type":"content_block_delta","delta":{"type":"text_delta","text":"Fixing"}}}
|
|
213
|
+
{"type":"stream_event","event":{"type":"content_block_delta","delta":{"type":"text_delta","text":" the"}}}
|
|
214
|
+
{"type":"assistant","message":{"content":[{"type":"text","text":"Fixed the off-by-one error."}]}}
|
|
215
|
+
{"type":"result","subtype":"success","result":"...","usage":{"input_tokens":120,"output_tokens":45}}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Hermes-Agent can tail this file for full Claude Code session history, or consume the SSE stream for live updates. The transport layer ensures no output is lost — even if the HTTP connection drops, the transcript file preserves every message.
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
### Document References
|
|
222
|
+
|
|
223
|
+
| Document | Contents |
|
|
224
|
+
|----------|----------|
|
|
225
|
+
| [DESIGN.md](DESIGN.md) | Architecture, protocol, session lifecycle, permission pipeline, API reference, changelog |
|
|
226
|
+
| [docs/local-mode.md](docs/local-mode.md) | HBRIDGE_HOME auto-start mode |
|
|
227
|
+
| [docs/spawn-mechanism.md](docs/spawn-mechanism.md) | Claude Code spawn protocol, NDJSON format |
|
|
228
|
+
| [docs/mcp-spec.md](docs/mcp-spec.md) | MCP tool definitions (Claude Code integration) |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
|
235
|
+
|
|
236
|
+
Created by **Xu Han** — [github.com/xuhancn](https://github.com/xuhancn)
|
package/build.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import * as esbuild from 'esbuild'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const SRC_DIR = path.resolve(__dirname, 'src')
|
|
8
|
+
|
|
9
|
+
// Generate version from git
|
|
10
|
+
const count = execSync("git rev-list --count HEAD", { encoding: "utf8" }).trim();
|
|
11
|
+
const hash = execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim();
|
|
12
|
+
const HBRIDGE_VERSION = `v${count}.0.${hash}`;
|
|
13
|
+
console.log(`✓ version ${HBRIDGE_VERSION}`);
|
|
14
|
+
|
|
15
|
+
// Build hbridge CLI
|
|
16
|
+
await esbuild.build({
|
|
17
|
+
entryPoints: [path.join(SRC_DIR, 'hbridge', 'cli.mjs')],
|
|
18
|
+
bundle: true,
|
|
19
|
+
platform: 'node',
|
|
20
|
+
format: 'esm',
|
|
21
|
+
outfile: 'dist/hbridge.mjs',
|
|
22
|
+
external: ['zod', 'chalk', 'axios', '@anthropic-ai/sdk', 'qrcode', 'lodash-es', 'get-east-asian-width'],
|
|
23
|
+
define: {
|
|
24
|
+
'globalThis.HBRIDGE_VERSION': JSON.stringify(HBRIDGE_VERSION),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Build statusline — pure Node.js, no external deps
|
|
29
|
+
await esbuild.build({
|
|
30
|
+
entryPoints: [path.join(SRC_DIR, 'hbridge', 'statusline.mjs')],
|
|
31
|
+
bundle: true,
|
|
32
|
+
platform: 'node',
|
|
33
|
+
format: 'esm',
|
|
34
|
+
outfile: 'dist/statusline.mjs',
|
|
35
|
+
});
|