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 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
+ });