@mmmbuto/nexuscli 0.7.5 → 0.7.7
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 +12 -4
- package/bin/nexuscli.js +6 -6
- package/frontend/dist/assets/{index-CikJbUR5.js → index-BAY_sRAu.js} +1704 -1704
- package/frontend/dist/assets/{index-Bn_l1e6e.css → index-CHOlrfA0.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/lib/server/.env.example +1 -1
- package/lib/server/db.js.old +225 -0
- package/lib/server/docs/API_WRAPPER_CONTRACT.md +682 -0
- package/lib/server/docs/ARCHITECTURE.md +441 -0
- package/lib/server/docs/DATABASE_SCHEMA.md +783 -0
- package/lib/server/docs/DESIGN_PRINCIPLES.md +598 -0
- package/lib/server/docs/NEXUSCHAT_ANALYSIS.md +488 -0
- package/lib/server/docs/PIPELINE_INTEGRATION.md +636 -0
- package/lib/server/docs/README.md +272 -0
- package/lib/server/docs/UI_DESIGN.md +916 -0
- package/lib/server/lib/pty-adapter.js +15 -1
- package/lib/server/routes/chat.js +70 -8
- package/lib/server/routes/codex.js +61 -7
- package/lib/server/routes/gemini.js +66 -12
- package/lib/server/routes/sessions.js +7 -2
- package/lib/server/server.js +2 -0
- package/lib/server/services/base-cli-wrapper.js +137 -0
- package/lib/server/services/claude-wrapper.js +11 -1
- package/lib/server/services/cli-loader.js.backup +446 -0
- package/lib/server/services/codex-output-parser.js +8 -0
- package/lib/server/services/codex-wrapper.js +13 -4
- package/lib/server/services/context-bridge.js +24 -20
- package/lib/server/services/gemini-wrapper.js +26 -8
- package/lib/server/services/session-manager.js +20 -0
- package/lib/server/services/workspace-manager.js +1 -1
- package/lib/server/tests/performance.test.js +1 -1
- package/lib/server/tests/services.test.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
# NexusCLI API & Wrapper Contract Specification
|
|
2
|
+
|
|
3
|
+
**Version**: 0.1.0
|
|
4
|
+
**Created**: 2025-11-17
|
|
5
|
+
**Based on**: NexusChat wrapper architecture
|
|
6
|
+
**Purpose**: Official API contract for NexusCLI control plane and nexus-wrapper node agents
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 📋 Table of Contents
|
|
11
|
+
|
|
12
|
+
1. [Architecture Overview](#architecture-overview)
|
|
13
|
+
2. [Control Plane API (NexusCLI)](#control-plane-api-nexuscli)
|
|
14
|
+
3. [Node Agent API (nexus-wrapper)](#node-agent-api-nexus-wrapper)
|
|
15
|
+
4. [Data Structures](#data-structures)
|
|
16
|
+
5. [Event Streaming](#event-streaming)
|
|
17
|
+
6. [Error Handling](#error-handling)
|
|
18
|
+
7. [Pipeline Integration](#pipeline-integration)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🏗️ Architecture Overview
|
|
23
|
+
|
|
24
|
+
### Philosophy: Separation of Concerns
|
|
25
|
+
|
|
26
|
+
**NexusChat Pattern** (adapted from analysis):
|
|
27
|
+
```
|
|
28
|
+
User Request → LibreChat API → Wrapper → Claude CLI → PTY Output → Parser → SSE Events → Client
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**NexusCLI Pattern** (generalized):
|
|
32
|
+
```
|
|
33
|
+
Control Plane → Job API → nexus-wrapper → Remote CLI → Stdout/Stderr → Parser → SSE Events → Dashboard
|
|
34
|
+
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
|
|
35
|
+
Orchestrator REST API HTTP Agent Local Tools Raw Output Structured Real-time UI/Logs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Key Principles (from NexusChat)
|
|
39
|
+
|
|
40
|
+
1. **Stateless Wrappers**: Each wrapper is a lightweight HTTP server that spawns CLI processes
|
|
41
|
+
2. **Session Persistence**: 2-layer (RAM cache + Disk storage) for crash recovery
|
|
42
|
+
3. **Event-Driven Streaming**: SSE (Server-Sent Events) for real-time progress
|
|
43
|
+
4. **Output Parsing**: Regex-based parsing of stdout/stderr into structured events
|
|
44
|
+
5. **Tool Abstraction**: Generic tool interface (Bash, Read, Write, etc.)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🎛️ Control Plane API (NexusCLI)
|
|
49
|
+
|
|
50
|
+
### Base URL
|
|
51
|
+
```
|
|
52
|
+
http://localhost:4000/api/v1
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Authentication
|
|
56
|
+
```
|
|
57
|
+
Authorization: Bearer <jwt_token>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### 1. Node Management
|
|
63
|
+
|
|
64
|
+
#### `POST /nodes/register`
|
|
65
|
+
Register a new node with the control plane.
|
|
66
|
+
|
|
67
|
+
**Request**:
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"nodeId": "node-001",
|
|
71
|
+
"hostname": "server-prod-1",
|
|
72
|
+
"ipAddress": "192.168.1.10",
|
|
73
|
+
"capabilities": {
|
|
74
|
+
"tools": ["bash", "python", "git", "docker"],
|
|
75
|
+
"maxConcurrentJobs": 5,
|
|
76
|
+
"platform": "linux",
|
|
77
|
+
"arch": "x86_64"
|
|
78
|
+
},
|
|
79
|
+
"wrapperVersion": "1.0.0",
|
|
80
|
+
"wrapperEndpoint": "http://192.168.1.10:5000"
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Response** (200 OK):
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"nodeId": "node-001",
|
|
88
|
+
"status": "registered",
|
|
89
|
+
"token": "<node_auth_token>",
|
|
90
|
+
"heartbeatInterval": 30000
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### `GET /nodes`
|
|
95
|
+
List all registered nodes.
|
|
96
|
+
|
|
97
|
+
**Response** (200 OK):
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"nodes": [
|
|
101
|
+
{
|
|
102
|
+
"nodeId": "node-001",
|
|
103
|
+
"hostname": "server-prod-1",
|
|
104
|
+
"status": "online",
|
|
105
|
+
"lastHeartbeat": "2025-11-17T10:30:00Z",
|
|
106
|
+
"activeJobs": 2,
|
|
107
|
+
"capabilities": { "tools": ["bash", "python"] }
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### `POST /nodes/:nodeId/heartbeat`
|
|
114
|
+
Update node status (sent by wrapper every N seconds).
|
|
115
|
+
|
|
116
|
+
**Request**:
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"status": "online",
|
|
120
|
+
"activeJobs": 2,
|
|
121
|
+
"systemLoad": 0.45,
|
|
122
|
+
"timestamp": "2025-11-17T10:30:00Z"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Response** (200 OK):
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"acknowledged": true
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### 2. Job Management
|
|
136
|
+
|
|
137
|
+
#### `POST /jobs`
|
|
138
|
+
Create a new job for execution on a node.
|
|
139
|
+
|
|
140
|
+
**Request**:
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"nodeId": "node-001",
|
|
144
|
+
"tool": "bash",
|
|
145
|
+
"command": "ls -la /var/www",
|
|
146
|
+
"workingDir": "/tmp",
|
|
147
|
+
"timeout": 30000,
|
|
148
|
+
"metadata": {
|
|
149
|
+
"userId": "user-123",
|
|
150
|
+
"requestId": "req-456"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Response** (201 Created):
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"jobId": "job-789",
|
|
159
|
+
"nodeId": "node-001",
|
|
160
|
+
"status": "queued",
|
|
161
|
+
"createdAt": "2025-11-17T10:30:00Z",
|
|
162
|
+
"streamEndpoint": "/jobs/job-789/stream"
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### `GET /jobs/:jobId`
|
|
167
|
+
Get job status and results.
|
|
168
|
+
|
|
169
|
+
**Response** (200 OK):
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"jobId": "job-789",
|
|
173
|
+
"nodeId": "node-001",
|
|
174
|
+
"status": "completed",
|
|
175
|
+
"tool": "bash",
|
|
176
|
+
"command": "ls -la /var/www",
|
|
177
|
+
"result": {
|
|
178
|
+
"exitCode": 0,
|
|
179
|
+
"stdout": "total 24\ndrwxr-xr-x 6 root root...",
|
|
180
|
+
"stderr": "",
|
|
181
|
+
"duration": 123
|
|
182
|
+
},
|
|
183
|
+
"createdAt": "2025-11-17T10:30:00Z",
|
|
184
|
+
"completedAt": "2025-11-17T10:30:01Z"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### `GET /jobs/:jobId/stream` (SSE)
|
|
189
|
+
Real-time event stream for job execution.
|
|
190
|
+
|
|
191
|
+
**SSE Events**:
|
|
192
|
+
```javascript
|
|
193
|
+
// Event: status
|
|
194
|
+
data: {"type":"status","category":"queued","message":"Job queued on node-001","timestamp":"..."}
|
|
195
|
+
|
|
196
|
+
// Event: status (execution started)
|
|
197
|
+
data: {"type":"status","category":"executing","message":"Running bash command...","icon":"🔧","timestamp":"..."}
|
|
198
|
+
|
|
199
|
+
// Event: output_chunk (incremental stdout)
|
|
200
|
+
data: {"type":"output_chunk","stream":"stdout","text":"total 24\n","isIncremental":true}
|
|
201
|
+
|
|
202
|
+
// Event: status (tool execution)
|
|
203
|
+
data: {"type":"status","category":"tool","message":"Bash: ls -la /var/www","icon":"🔧","toolOutput":"..."}
|
|
204
|
+
|
|
205
|
+
// Event: response_done
|
|
206
|
+
data: {"type":"response_done","exitCode":0,"duration":123}
|
|
207
|
+
|
|
208
|
+
// Event: done
|
|
209
|
+
data: {"type":"done","jobId":"job-789","status":"completed"}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### `DELETE /jobs/:jobId`
|
|
213
|
+
Cancel a running job.
|
|
214
|
+
|
|
215
|
+
**Response** (200 OK):
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"jobId": "job-789",
|
|
219
|
+
"status": "cancelled"
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### 3. Batch Operations
|
|
226
|
+
|
|
227
|
+
#### `POST /batch`
|
|
228
|
+
Execute a job on multiple nodes.
|
|
229
|
+
|
|
230
|
+
**Request**:
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"nodeIds": ["node-001", "node-002", "node-003"],
|
|
234
|
+
"tool": "bash",
|
|
235
|
+
"command": "systemctl status nginx",
|
|
236
|
+
"parallel": true
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Response** (201 Created):
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"batchId": "batch-abc",
|
|
244
|
+
"jobs": [
|
|
245
|
+
{"jobId": "job-001", "nodeId": "node-001"},
|
|
246
|
+
{"jobId": "job-002", "nodeId": "node-002"},
|
|
247
|
+
{"jobId": "job-003", "nodeId": "node-003"}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 🤖 Node Agent API (nexus-wrapper)
|
|
255
|
+
|
|
256
|
+
### Base URL (on each node)
|
|
257
|
+
```
|
|
258
|
+
http://<node-ip>:5000/api/v1
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Authentication
|
|
262
|
+
```
|
|
263
|
+
Authorization: Bearer <node_auth_token>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### 1. Health & Capabilities
|
|
269
|
+
|
|
270
|
+
#### `GET /health`
|
|
271
|
+
Health check endpoint.
|
|
272
|
+
|
|
273
|
+
**Response** (200 OK):
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"status": "ok",
|
|
277
|
+
"nodeId": "node-001",
|
|
278
|
+
"wrapperVersion": "1.0.0",
|
|
279
|
+
"uptime": 86400,
|
|
280
|
+
"activeJobs": 2
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### `GET /capabilities`
|
|
285
|
+
List available tools and capabilities.
|
|
286
|
+
|
|
287
|
+
**Response** (200 OK):
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"tools": [
|
|
291
|
+
{
|
|
292
|
+
"name": "bash",
|
|
293
|
+
"version": "5.1.16",
|
|
294
|
+
"available": true
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"name": "python",
|
|
298
|
+
"version": "3.11.5",
|
|
299
|
+
"available": true
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"name": "git",
|
|
303
|
+
"version": "2.39.2",
|
|
304
|
+
"available": true
|
|
305
|
+
}
|
|
306
|
+
],
|
|
307
|
+
"platform": "linux",
|
|
308
|
+
"arch": "x86_64",
|
|
309
|
+
"maxConcurrentJobs": 5
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
### 2. Job Execution
|
|
316
|
+
|
|
317
|
+
#### `POST /jobs`
|
|
318
|
+
Execute a job locally on this node.
|
|
319
|
+
|
|
320
|
+
**Request**:
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"jobId": "job-789",
|
|
324
|
+
"tool": "bash",
|
|
325
|
+
"command": "ls -la /var/www",
|
|
326
|
+
"workingDir": "/tmp",
|
|
327
|
+
"timeout": 30000,
|
|
328
|
+
"env": {
|
|
329
|
+
"PATH": "/usr/local/bin:/usr/bin"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Response** (202 Accepted):
|
|
335
|
+
```json
|
|
336
|
+
{
|
|
337
|
+
"jobId": "job-789",
|
|
338
|
+
"status": "accepted",
|
|
339
|
+
"streamEndpoint": "/jobs/job-789/stream"
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### `GET /jobs/:jobId/stream` (SSE)
|
|
344
|
+
Real-time output stream (like NexusChat claude-wrapper).
|
|
345
|
+
|
|
346
|
+
**SSE Events** (same format as Control Plane):
|
|
347
|
+
```javascript
|
|
348
|
+
// status events
|
|
349
|
+
data: {"type":"status","category":"executing","message":"Running command..."}
|
|
350
|
+
|
|
351
|
+
// stdout chunks
|
|
352
|
+
data: {"type":"output_chunk","stream":"stdout","text":"line 1\n"}
|
|
353
|
+
|
|
354
|
+
// stderr chunks
|
|
355
|
+
data: {"type":"output_chunk","stream":"stderr","text":"warning: ...\n"}
|
|
356
|
+
|
|
357
|
+
// completion
|
|
358
|
+
data: {"type":"response_done","exitCode":0,"duration":123}
|
|
359
|
+
data: {"type":"done"}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### `GET /jobs/:jobId`
|
|
363
|
+
Get job result (after completion).
|
|
364
|
+
|
|
365
|
+
**Response** (200 OK):
|
|
366
|
+
```json
|
|
367
|
+
{
|
|
368
|
+
"jobId": "job-789",
|
|
369
|
+
"status": "completed",
|
|
370
|
+
"exitCode": 0,
|
|
371
|
+
"stdout": "total 24\ndrwxr-xr-x...",
|
|
372
|
+
"stderr": "",
|
|
373
|
+
"duration": 123,
|
|
374
|
+
"startedAt": "2025-11-17T10:30:00Z",
|
|
375
|
+
"completedAt": "2025-11-17T10:30:01Z"
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### `DELETE /jobs/:jobId`
|
|
380
|
+
Kill a running job.
|
|
381
|
+
|
|
382
|
+
**Response** (200 OK):
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"jobId": "job-789",
|
|
386
|
+
"status": "killed",
|
|
387
|
+
"signal": "SIGTERM"
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## 📦 Data Structures
|
|
394
|
+
|
|
395
|
+
### Node
|
|
396
|
+
```typescript
|
|
397
|
+
interface Node {
|
|
398
|
+
nodeId: string; // Unique node identifier
|
|
399
|
+
hostname: string; // Node hostname
|
|
400
|
+
ipAddress: string; // Node IP address
|
|
401
|
+
status: 'online' | 'offline' | 'error';
|
|
402
|
+
capabilities: {
|
|
403
|
+
tools: string[]; // Available CLI tools
|
|
404
|
+
maxConcurrentJobs: number;
|
|
405
|
+
platform: string; // 'linux' | 'darwin' | 'windows'
|
|
406
|
+
arch: string; // 'x86_64' | 'arm64'
|
|
407
|
+
};
|
|
408
|
+
wrapperVersion: string;
|
|
409
|
+
wrapperEndpoint: string;
|
|
410
|
+
lastHeartbeat: string; // ISO 8601 timestamp
|
|
411
|
+
activeJobs: number;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Job
|
|
416
|
+
```typescript
|
|
417
|
+
interface Job {
|
|
418
|
+
jobId: string;
|
|
419
|
+
nodeId: string;
|
|
420
|
+
status: 'queued' | 'executing' | 'completed' | 'failed' | 'cancelled' | 'killed';
|
|
421
|
+
tool: string; // 'bash' | 'python' | 'git' | etc.
|
|
422
|
+
command: string; // Command to execute
|
|
423
|
+
workingDir?: string; // Working directory (optional)
|
|
424
|
+
timeout?: number; // Timeout in milliseconds
|
|
425
|
+
env?: Record<string, string>; // Environment variables
|
|
426
|
+
result?: {
|
|
427
|
+
exitCode: number;
|
|
428
|
+
stdout: string;
|
|
429
|
+
stderr: string;
|
|
430
|
+
duration: number; // Milliseconds
|
|
431
|
+
};
|
|
432
|
+
metadata?: Record<string, any>; // Custom metadata
|
|
433
|
+
createdAt: string;
|
|
434
|
+
startedAt?: string;
|
|
435
|
+
completedAt?: string;
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Event (SSE)
|
|
440
|
+
```typescript
|
|
441
|
+
interface Event {
|
|
442
|
+
type: 'status' | 'output_chunk' | 'response_done' | 'done' | 'error';
|
|
443
|
+
|
|
444
|
+
// For type: 'status'
|
|
445
|
+
category?: 'queued' | 'executing' | 'tool' | 'streaming' | 'complete' | 'warning';
|
|
446
|
+
message?: string;
|
|
447
|
+
icon?: string; // Emoji icon
|
|
448
|
+
|
|
449
|
+
// For type: 'output_chunk'
|
|
450
|
+
stream?: 'stdout' | 'stderr';
|
|
451
|
+
text?: string;
|
|
452
|
+
isIncremental?: boolean;
|
|
453
|
+
|
|
454
|
+
// For type: 'response_done'
|
|
455
|
+
exitCode?: number;
|
|
456
|
+
duration?: number;
|
|
457
|
+
|
|
458
|
+
// For type: 'error'
|
|
459
|
+
error?: string;
|
|
460
|
+
details?: string;
|
|
461
|
+
|
|
462
|
+
timestamp?: string; // ISO 8601
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## 🌊 Event Streaming
|
|
469
|
+
|
|
470
|
+
### SSE (Server-Sent Events)
|
|
471
|
+
|
|
472
|
+
**Inspired by NexusChat**: Real-time streaming via SSE for live job progress.
|
|
473
|
+
|
|
474
|
+
**Headers**:
|
|
475
|
+
```http
|
|
476
|
+
Content-Type: text/event-stream
|
|
477
|
+
Cache-Control: no-cache
|
|
478
|
+
Connection: keep-alive
|
|
479
|
+
X-Accel-Buffering: no
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Event Flow** (based on NexusChat output-parser.js):
|
|
483
|
+
|
|
484
|
+
1. **Queued**:
|
|
485
|
+
```javascript
|
|
486
|
+
data: {"type":"status","category":"queued","message":"Job queued","icon":"⏱️"}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
2. **Executing**:
|
|
490
|
+
```javascript
|
|
491
|
+
data: {"type":"status","category":"executing","message":"Running bash command...","icon":"🔧"}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
3. **Output Streaming** (incremental):
|
|
495
|
+
```javascript
|
|
496
|
+
data: {"type":"output_chunk","stream":"stdout","text":"line 1\n","isIncremental":true}
|
|
497
|
+
data: {"type":"output_chunk","stream":"stdout","text":"line 2\n","isIncremental":true}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
4. **Tool Events** (if detected):
|
|
501
|
+
```javascript
|
|
502
|
+
data: {"type":"status","category":"tool","message":"Git: clone repository","icon":"🔀"}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
5. **Completion**:
|
|
506
|
+
```javascript
|
|
507
|
+
data: {"type":"response_done","exitCode":0,"duration":123}
|
|
508
|
+
data: {"type":"done","jobId":"job-789"}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
6. **Error**:
|
|
512
|
+
```javascript
|
|
513
|
+
data: {"type":"error","error":"Command failed","details":"...\n"}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## ⚠️ Error Handling
|
|
519
|
+
|
|
520
|
+
### Error Response Format
|
|
521
|
+
|
|
522
|
+
**HTTP Error Response**:
|
|
523
|
+
```json
|
|
524
|
+
{
|
|
525
|
+
"error": "Error message",
|
|
526
|
+
"code": "ERROR_CODE",
|
|
527
|
+
"details": "Additional details...",
|
|
528
|
+
"timestamp": "2025-11-17T10:30:00Z"
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Common Error Codes
|
|
533
|
+
|
|
534
|
+
| Code | HTTP Status | Description |
|
|
535
|
+
|------|-------------|-------------|
|
|
536
|
+
| `NODE_NOT_FOUND` | 404 | Node ID does not exist |
|
|
537
|
+
| `NODE_OFFLINE` | 503 | Node is offline or unreachable |
|
|
538
|
+
| `JOB_NOT_FOUND` | 404 | Job ID does not exist |
|
|
539
|
+
| `JOB_TIMEOUT` | 408 | Job exceeded timeout limit |
|
|
540
|
+
| `TOOL_NOT_AVAILABLE` | 400 | Requested tool not available on node |
|
|
541
|
+
| `AUTHENTICATION_FAILED` | 401 | Invalid or missing auth token |
|
|
542
|
+
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests |
|
|
543
|
+
| `INTERNAL_ERROR` | 500 | Internal server error |
|
|
544
|
+
|
|
545
|
+
### Retry Strategy (inspired by NexusChat wrapper)
|
|
546
|
+
|
|
547
|
+
- **Connection errors**: Retry up to 3 times with exponential backoff
|
|
548
|
+
- **Timeout errors**: No retry (user-initiated cancellation)
|
|
549
|
+
- **Tool errors**: No retry (fix command and resubmit)
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## 🔄 Pipeline Integration
|
|
554
|
+
|
|
555
|
+
### Workflow: Analysis → Coordination → Execution → Test → Deploy
|
|
556
|
+
|
|
557
|
+
**Based on NexusChat CLAUDE.md pipeline**, adapted for distributed CLI:
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
561
|
+
│ CONTROL PLANE │
|
|
562
|
+
│ │
|
|
563
|
+
│ 1. ANALYSIS │
|
|
564
|
+
│ - User submits job request │
|
|
565
|
+
│ - Validate command, tool, node availability │
|
|
566
|
+
│ │
|
|
567
|
+
│ 2. COORDINATION │
|
|
568
|
+
│ - Select target node(s) │
|
|
569
|
+
│ - Queue job │
|
|
570
|
+
│ - Establish SSE stream │
|
|
571
|
+
│ │
|
|
572
|
+
│ 3. EXECUTION (delegated to nexus-wrapper) │
|
|
573
|
+
│ - POST /jobs to wrapper API │
|
|
574
|
+
│ - Wrapper spawns PTY process │
|
|
575
|
+
│ - Output parser streams events │
|
|
576
|
+
│ │
|
|
577
|
+
│ 4. TEST (optional - automated verification) │
|
|
578
|
+
│ - Check exit code │
|
|
579
|
+
│ - Validate output format │
|
|
580
|
+
│ - Assert expected results │
|
|
581
|
+
│ │
|
|
582
|
+
│ 5. DEPLOY (future - chained jobs) │
|
|
583
|
+
│ - Trigger dependent jobs │
|
|
584
|
+
│ - Update deployment status │
|
|
585
|
+
│ │
|
|
586
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
587
|
+
|
|
588
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
589
|
+
│ NEXUS-WRAPPER (Node) │
|
|
590
|
+
│ │
|
|
591
|
+
│ - Receive job request │
|
|
592
|
+
│ - Spawn PTY process (like NexusChat claude-wrapper) │
|
|
593
|
+
│ - Parse stdout/stderr with OutputParser │
|
|
594
|
+
│ - Emit SSE events (status, output_chunk, done) │
|
|
595
|
+
│ - Store result in local job storage │
|
|
596
|
+
│ │
|
|
597
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Session Persistence (NexusChat pattern)
|
|
601
|
+
|
|
602
|
+
**2-Layer Storage**:
|
|
603
|
+
1. **RAM Cache** (Map): Fast access for active jobs
|
|
604
|
+
2. **Disk Storage** (JSON files): Persistence across restarts
|
|
605
|
+
|
|
606
|
+
**File Structure** (per wrapper):
|
|
607
|
+
```
|
|
608
|
+
/var/lib/nexus-wrapper/jobs/
|
|
609
|
+
├── job-001.json
|
|
610
|
+
├── job-002.json
|
|
611
|
+
└── job-003.json
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Job File Format**:
|
|
615
|
+
```json
|
|
616
|
+
{
|
|
617
|
+
"jobId": "job-001",
|
|
618
|
+
"status": "completed",
|
|
619
|
+
"command": "ls -la",
|
|
620
|
+
"result": {
|
|
621
|
+
"exitCode": 0,
|
|
622
|
+
"stdout": "...",
|
|
623
|
+
"stderr": "",
|
|
624
|
+
"duration": 123
|
|
625
|
+
},
|
|
626
|
+
"createdAt": "2025-11-17T10:30:00Z",
|
|
627
|
+
"completedAt": "2025-11-17T10:30:01Z"
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## 🎯 Implementation Notes
|
|
634
|
+
|
|
635
|
+
### From NexusChat Analysis
|
|
636
|
+
|
|
637
|
+
1. **PTY over Subprocess** (claude-wrapper.js:61)
|
|
638
|
+
- Use `node-pty` for real PTY (fixes CLI spawn issues)
|
|
639
|
+
- Handles ANSI escape codes correctly
|
|
640
|
+
- Better signal handling (SIGTERM, SIGKILL)
|
|
641
|
+
|
|
642
|
+
2. **Output Parser State Machine** (output-parser.js:13)
|
|
643
|
+
- States: `idle | tool_execution | tool_output | thinking | response`
|
|
644
|
+
- Regex patterns for detecting tool markers
|
|
645
|
+
- Line-by-line parsing for accuracy
|
|
646
|
+
|
|
647
|
+
3. **SSE Streaming** (server.js:71-76)
|
|
648
|
+
- Set correct headers (`text/event-stream`, `no-cache`)
|
|
649
|
+
- Disable nginx buffering (`X-Accel-Buffering: no`)
|
|
650
|
+
- Emit events as `data: {JSON}\n\n`
|
|
651
|
+
|
|
652
|
+
4. **Session Manager** (session-manager.js)
|
|
653
|
+
- Load from disk on cache miss (lazy loading)
|
|
654
|
+
- Save to disk on every update (durability)
|
|
655
|
+
- List() loads all from disk (ensure completeness)
|
|
656
|
+
|
|
657
|
+
5. **Graceful Shutdown** (server.js:248)
|
|
658
|
+
- Handle SIGTERM for systemd compatibility
|
|
659
|
+
- Clean up active PTY processes
|
|
660
|
+
- Save in-flight jobs to disk
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## 📚 References
|
|
665
|
+
|
|
666
|
+
- **NexusChat Source**: `/var/www/chat.mmmbuto.com/servers/claude-code-server/`
|
|
667
|
+
- **NexusChat CLAUDE.md**: Development workflow and pipeline
|
|
668
|
+
- **LibreChat API**: Upstream reference for agents/chat routes
|
|
669
|
+
- **Model**: Based on production wrapper serving 100+ daily requests
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
**Next Steps**:
|
|
674
|
+
1. Implement Control Plane API endpoints (Express.js)
|
|
675
|
+
2. Create nexus-wrapper reference implementation
|
|
676
|
+
3. Adapt OutputParser for generic CLI tools
|
|
677
|
+
4. Add WebSocket support (alternative to SSE)
|
|
678
|
+
5. Implement authentication & rate limiting
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
_Generated by Claude Code (Sonnet 4.5) - 2025-11-17_
|