@m64/nats-pi-bridge 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # nats-pi-bridge
2
+
3
+ Standalone headless service that spawns and manages PI coding agent sessions on demand via NATS.
4
+
5
+ > **Zero PI install required.** The bridge embeds the full [`@mariozechner/pi-coding-agent`](https://github.com/badlogic/pi-mono) runtime as library code — agent loop, tool execution (read/write/edit/bash), session management, model registry, all in-process. One `npx @m64/nats-pi-bridge` command gives you a fully operational PI worker addressable over NATS. No global `pi` binary, no TUI, no interactive CLI. Ideal for Docker containers, CI runners, and remote hosts where a full interactive coding agent would be overkill.
6
+ >
7
+ > **All you need on the host:** Node.js ≥20.6, a NATS server (or `demo.nats.io`), and an API key for any supported model provider.
8
+
9
+ This bridge exposes session lifecycle + prompting as a `pi-exec` NATS microservice. Callers send structured JSON to a permanent **intake** endpoint to create, list, and stop sessions. Persistent sessions get their own per-session subject for follow-ups using the standard streaming wire protocol shared with the sibling implementations:
10
+
11
+ - `nats-pi-channel` — PI extension for **interactive** PI sessions
12
+ - `nats-claude-channel` — Claude Code MCP channel
13
+ - `nats-channel` (OpenClaw) — OpenClaw NATS channel
14
+
15
+ ## Quick start
16
+
17
+ No install, no setup files — just [Node.js](https://nodejs.org) ≥20.6 and the [nats CLI](https://github.com/nats-io/natscli). You'll be streaming agent output over NATS in under a minute.
18
+
19
+ > **Prerequisite:** An API key in `~/.pi/agent/auth.json`. Create it directly — PI doesn't need to be installed:
20
+ >
21
+ > ```bash
22
+ > mkdir -p ~/.pi/agent && cat > ~/.pi/agent/auth.json <<'EOF'
23
+ > {"anthropic": {"type": "api_key", "key": "sk-ant-YOUR-KEY-HERE"}}
24
+ > EOF
25
+ > ```
26
+ >
27
+ > Swap `anthropic` for any other provider (`openrouter`, `openai`, `google`, …). If you already have PI installed locally, `pi /login` populates this file for you automatically.
28
+
29
+ ### 1. Run the bridge
30
+
31
+ In one terminal:
32
+
33
+ ```bash
34
+ npx @m64/nats-pi-bridge
35
+ ```
36
+
37
+ That's it. It connects to `demo.nats.io` by default and registers a `pi-exec` NATS microservice with an intake on `agents.pi-exec.$USER`:
38
+
39
+ ```
40
+ pi-exec: connecting to demo.nats.io (context: default)
41
+ pi-exec: connected
42
+ pi-exec: intake registered on agents.pi-exec.yourname
43
+ ```
44
+
45
+ ### 2. Fire a one-shot prompt
46
+
47
+ In a second terminal:
48
+
49
+ ```bash
50
+ nats --server demo.nats.io req agents.pi-exec.$USER \
51
+ '{"sessionMode":"run","body":"Write a haiku about NATS","cwd":"/tmp"}' \
52
+ --wait-for-empty --timeout 60s
53
+ ```
54
+
55
+ The agent's reply streams back chunk-by-chunk and an empty message closes the stream. The session is disposed the moment it's done — fire and forget.
56
+
57
+ ### 3. Keep a session alive across prompts
58
+
59
+ Now create a persistent session, prove it remembers context with a follow-up, then stop it cleanly:
60
+
61
+ ```bash
62
+ # Start the session + run the initial prompt
63
+ nats --server demo.nats.io req agents.pi-exec.$USER \
64
+ '{"sessionMode":"session","body":"Pick a random 3-digit number and remember it","cwd":"/tmp","sessionId":"demo"}' \
65
+ --wait-for-empty --timeout 120s
66
+
67
+ # See it in the live session list
68
+ nats --server demo.nats.io req agents.pi-exec.$USER \
69
+ '{"sessionMode":"list"}' --timeout 5s
70
+
71
+ # Follow-up on the per-session subject — proves the session remembers
72
+ nats --server demo.nats.io req agents.pi-exec.$USER.demo \
73
+ "What number did you pick?" --wait-for-empty --timeout 60s
74
+
75
+ # Stop it cleanly — the per-session instance disappears from `nats micro list`
76
+ nats --server demo.nats.io req agents.pi-exec.$USER \
77
+ '{"sessionMode":"stop","sessionId":"demo"}' --timeout 5s
78
+ ```
79
+
80
+ Ctrl-C the bridge when you're done. Every active session is disposed during graceful shutdown.
81
+
82
+ ## Architecture
83
+
84
+ See [DESIGN.md](./DESIGN.md) for the full architectural document.
85
+
86
+ The bridge runs as a single Node.js process. The `pi-exec` service has:
87
+
88
+ - **Intake instance** (permanent): `agents.pi-exec.<owner>` — control plane
89
+ - **Per-session instances** (dynamic): `agents.pi-exec.<owner>.<sessionId>` — data plane
90
+
91
+ Each session is its own NATS microservice instance (the same pattern that `nats-pi-channel` uses across processes — but here within one process). On stop, the session's instance is removed cleanly via `service.stop()`.
92
+
93
+ ```
94
+ nats micro list
95
+ → pi-exec │ 0.0.1 │ ABC123 │ intake
96
+ │ │ DEF456 │ worker-1 — /home/mario/code/nats-zig
97
+ │ │ GHI789 │ sid-gpt — /home/mario/code/sid-gpt
98
+ ```
99
+
100
+ ## Install
101
+
102
+ ```bash
103
+ # Run without installing (what the Quick Start shows)
104
+ npx @m64/nats-pi-bridge
105
+
106
+ # Or install globally
107
+ npm install -g @m64/nats-pi-bridge
108
+ nats-pi-bridge
109
+ ```
110
+
111
+ Requires Node.js ≥20.6 (pulled in via the PI SDK's engine requirement).
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ git clone <this repo>
117
+ cd nats-pi-bridge
118
+ npm install
119
+
120
+ # Run from TypeScript source (tsx)
121
+ npm start
122
+
123
+ # Watch mode
124
+ npm run dev
125
+
126
+ # Build the Node-compatible bundle into dist/
127
+ npm run build
128
+ node dist/server.js
129
+ ```
130
+
131
+ ## Configuration
132
+
133
+ Optional config file: `~/.pi-exec/config.json`
134
+
135
+ ```json
136
+ {
137
+ "context": "my-nats-context",
138
+ "defaultModel": "anthropic/claude-sonnet-4-5",
139
+ "defaultThinkingLevel": "off",
140
+ "defaultMaxLifetime": 1800
141
+ }
142
+ ```
143
+
144
+ Environment overrides:
145
+
146
+ | Var | Description |
147
+ |---|---|
148
+ | `NATS_CONTEXT` | NATS CLI context name (looks for `~/.config/nats/context/<name>.json`). If unset, connects to `demo.nats.io`. |
149
+ | `PI_EXEC_DEFAULT_MODEL` | Default model spec, e.g. `anthropic/claude-sonnet-4-5` |
150
+ | `PI_EXEC_DEFAULT_MAX_LIFETIME` | Default max session lifetime in seconds |
151
+
152
+ PI credentials are read from `~/.pi/agent/auth.json` (the standard PI agent location). Run `pi /login` interactively once to populate it.
153
+
154
+ ## Intake protocol
155
+
156
+ Send structured JSON to `agents.pi-exec.<owner>`:
157
+
158
+ ```typescript
159
+ type IntakeRequest = {
160
+ sessionMode: "run" | "session" | "stop" | "list";
161
+ body?: string; // prompt text (run/session)
162
+ cwd?: string; // working directory (run/session) — resolved to absolute
163
+ from?: string; // caller identity (optional)
164
+ sessionId?: string; // REQUIRED for session and stop modes
165
+ model?: string; // e.g. "anthropic/claude-sonnet-4-5"
166
+ thinkingLevel?: string; // off|minimal|low|medium|high|xhigh
167
+ maxLifetime?: number; // seconds, 0 = no expiry
168
+ };
169
+ ```
170
+
171
+ | `sessionMode` | Required fields | Behaviour |
172
+ |---|---|---|
173
+ | `run` | `body`, `cwd` | Ephemeral: create session, prompt, stream chunks, dispose |
174
+ | `session` | `body`, `cwd`, `sessionId` | Persistent: create session + per-session NATS instance, prompt initial, stream chunks |
175
+ | `stop` | `sessionId` | Dispose session, remove instance |
176
+ | `list` | — | Return all active sessions as JSON |
177
+
178
+ Per-session prompt subject (`agents.pi-exec.<owner>.<sessionId>`) accepts the standard wire protocol: plain text or `{from, body}` JSON, streaming chunks back, empty payload terminates the stream.
179
+
180
+ ## Testing
181
+
182
+ Start the bridge:
183
+
184
+ ```bash
185
+ npm start # from source
186
+ # or
187
+ node dist/server.js # from the built bundle
188
+ ```
189
+
190
+ In another terminal:
191
+
192
+ ```bash
193
+ # Discoverability
194
+ nats micro list
195
+ nats micro info pi-exec
196
+
197
+ # Single-shot run
198
+ nats req agents.pi-exec.$USER \
199
+ '{"sessionMode":"run","body":"What files are here?","cwd":"/tmp"}' \
200
+ --wait-for-empty --timeout 120s
201
+
202
+ # Create persistent session
203
+ nats req agents.pi-exec.$USER \
204
+ '{"sessionMode":"session","body":"List the project structure","cwd":"/path/to/project","sessionId":"worker-1"}' \
205
+ --wait-for-empty --timeout 120s
206
+
207
+ # Follow-up via per-session subject
208
+ nats req agents.pi-exec.$USER.worker-1 "Now fix the failing tests" \
209
+ --wait-for-empty --timeout 120s
210
+
211
+ # List sessions
212
+ nats req agents.pi-exec.$USER '{"sessionMode":"list"}' --timeout 5s
213
+
214
+ # Inspect a session
215
+ nats req agents.pi-exec.$USER.worker-1.inspect "" --timeout 5s
216
+
217
+ # Stop a session
218
+ nats req agents.pi-exec.$USER \
219
+ '{"sessionMode":"stop","sessionId":"worker-1"}' --timeout 5s
220
+ ```
221
+
222
+ ## Error responses
223
+
224
+ All error responses follow `{"error": "<code>", ...context}`. Examples:
225
+
226
+ ```json
227
+ {"error":"invalid_json", "message":"..."}
228
+ {"error":"invalid_request", "message":"sessionMode required"}
229
+ {"error":"invalid_sessionMode", "sessionMode":"explode"}
230
+ {"error":"missing_fields", "required":["body","cwd","sessionId"]}
231
+ {"error":"invalid_sessionId", "sessionId":"...", "message":"..."}
232
+ {"error":"session_exists", "sessionId":"...", "subject":"...", "message":"..."}
233
+ {"error":"session_create_failed", "message":"..."}
234
+ {"error":"not_found", "sessionId":"..."}
235
+ {"error":"shutting_down"}
236
+ ```
237
+
238
+ Streaming modes (`run`, initial `session` prompt) emit text chunks + an empty terminator instead of a JSON wrapper. Errors during streaming are emitted as `error: <message>` text chunks followed by the empty terminator.