@mmmbuto/nexuscli 0.8.9 โ 0.9.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/README.md +9 -8
- package/lib/cli/status.js +1 -19
- package/lib/config/models.js +7 -0
- package/lib/server/.env.example +1 -1
- package/lib/server/routes/models.js +1 -1
- package/lib/server/server.js +0 -2
- package/package.json +1 -2
- package/lib/server/db.js.old +0 -225
- package/lib/server/docs/API_WRAPPER_CONTRACT.md +0 -682
- package/lib/server/docs/ARCHITECTURE.md +0 -441
- package/lib/server/docs/DATABASE_SCHEMA.md +0 -783
- package/lib/server/docs/DESIGN_PRINCIPLES.md +0 -598
- package/lib/server/docs/NEXUSCHAT_ANALYSIS.md +0 -488
- package/lib/server/docs/PIPELINE_INTEGRATION.md +0 -636
- package/lib/server/docs/README.md +0 -272
- package/lib/server/docs/UI_DESIGN.md +0 -916
- package/lib/server/routes/system.js +0 -29
- package/lib/server/services/version-manager.js +0 -170
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
# NexusCLI Architecture Overview
|
|
2
|
-
|
|
3
|
-
**Version**: 0.1.0
|
|
4
|
-
**Created**: 2025-11-17
|
|
5
|
-
**Status**: Design Phase
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## ๐ฏ Vision
|
|
10
|
-
|
|
11
|
-
NexusCLI is a **distributed control plane** for orchestrating CLI operations across multiple remote nodes, inspired by the architecture of NexusChat's wrapper system but generalized for any CLI tool.
|
|
12
|
-
|
|
13
|
-
**Philosophy**: Separate "what to do" (Control Plane) from "how to execute" (Node Agents).
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## ๐๏ธ System Architecture
|
|
18
|
-
|
|
19
|
-
### High-Level Overview
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
23
|
-
โ WEB INTERFACE โ
|
|
24
|
-
โ (React + Vite Frontend) โ
|
|
25
|
-
โ โ
|
|
26
|
-
โ - Job submission dashboard โ
|
|
27
|
-
โ - Real-time execution logs (SSE streaming) โ
|
|
28
|
-
โ - Node status monitoring โ
|
|
29
|
-
โ - Historical job results โ
|
|
30
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
31
|
-
โ REST API + SSE
|
|
32
|
-
โ
|
|
33
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
34
|
-
โ CONTROL PLANE (NexusCLI) โ
|
|
35
|
-
โ Express.js Backend โ
|
|
36
|
-
โ โ
|
|
37
|
-
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ
|
|
38
|
-
โ โ Job โ โ Node โ โ Stream โ โ
|
|
39
|
-
โ โ Manager โ โ Registry โ โ Manager โ โ
|
|
40
|
-
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ
|
|
41
|
-
โ โ
|
|
42
|
-
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
43
|
-
โ โ Session Storage (RAM + Disk) โ โ
|
|
44
|
-
โ โ - Active jobs (Map) โ โ
|
|
45
|
-
โ โ - Completed jobs (JSON files) โ โ
|
|
46
|
-
โ โ - Node registry (in-memory + periodic sync) โ โ
|
|
47
|
-
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
48
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
49
|
-
โ HTTP REST API
|
|
50
|
-
โ
|
|
51
|
-
โโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโ
|
|
52
|
-
โ โ
|
|
53
|
-
โ โ
|
|
54
|
-
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
|
|
55
|
-
โ NEXUS-WRAPPER โ โ NEXUS-WRAPPER โ
|
|
56
|
-
โ (Node 1) โ โ (Node 2) โ
|
|
57
|
-
โ โ โ โ
|
|
58
|
-
โ - HTTP Server โ ... โ - HTTP Server โ
|
|
59
|
-
โ - PTY Spawner โ โ - PTY Spawner โ
|
|
60
|
-
โ - Output Parser โ โ - Output Parser โ
|
|
61
|
-
โ - Job Storage โ โ - Job Storage โ
|
|
62
|
-
โโโโโโโโโโฌโโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโ
|
|
63
|
-
โ โ
|
|
64
|
-
โ โ
|
|
65
|
-
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
66
|
-
โ CLI Tools โ โ CLI Tools โ
|
|
67
|
-
โ (bash, git, โ โ (python, โ
|
|
68
|
-
โ docker...) โ โ npm...) โ
|
|
69
|
-
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## ๐งฉ Components
|
|
75
|
-
|
|
76
|
-
### 1. Control Plane (NexusCLI Backend)
|
|
77
|
-
|
|
78
|
-
**Location**: `/var/www/cli.wellanet.dev/backend/`
|
|
79
|
-
|
|
80
|
-
**Responsibilities**:
|
|
81
|
-
- Accept job requests from frontend/API clients
|
|
82
|
-
- Maintain registry of available nodes
|
|
83
|
-
- Route jobs to appropriate nodes
|
|
84
|
-
- Aggregate and stream execution events
|
|
85
|
-
- Persist job history and results
|
|
86
|
-
|
|
87
|
-
**Key Modules**:
|
|
88
|
-
|
|
89
|
-
#### Job Manager
|
|
90
|
-
```javascript
|
|
91
|
-
// Handles job lifecycle
|
|
92
|
-
class JobManager {
|
|
93
|
-
create(jobSpec) // Create and queue job
|
|
94
|
-
assign(jobId, nodeId) // Assign job to node
|
|
95
|
-
execute(jobId) // Trigger execution on wrapper
|
|
96
|
-
stream(jobId) // SSE stream of job events
|
|
97
|
-
cancel(jobId) // Kill running job
|
|
98
|
-
getResult(jobId) // Retrieve completed job result
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
#### Node Registry
|
|
103
|
-
```javascript
|
|
104
|
-
// Tracks registered nodes
|
|
105
|
-
class NodeRegistry {
|
|
106
|
-
register(nodeInfo) // Register new node
|
|
107
|
-
heartbeat(nodeId) // Update node status
|
|
108
|
-
getAvailable() // Get online nodes
|
|
109
|
-
selectNode(criteria) // Select best node for job
|
|
110
|
-
unregister(nodeId) // Remove offline node
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
#### Stream Manager (SSE)
|
|
115
|
-
```javascript
|
|
116
|
-
// Manages SSE streams to clients
|
|
117
|
-
class StreamManager {
|
|
118
|
-
createStream(jobId) // Open SSE connection
|
|
119
|
-
emit(jobId, event) // Send event to stream
|
|
120
|
-
closeStream(jobId) // Close connection
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
### 2. Node Agent (nexus-wrapper)
|
|
127
|
-
|
|
128
|
-
**Future Location**: Separate repository (e.g., `~/Dev/nexus-wrapper/`)
|
|
129
|
-
|
|
130
|
-
**Responsibilities**:
|
|
131
|
-
- Expose HTTP API for job execution
|
|
132
|
-
- Spawn PTY processes for CLI tools
|
|
133
|
-
- Parse stdout/stderr into structured events
|
|
134
|
-
- Stream events back to Control Plane
|
|
135
|
-
- Store local job results
|
|
136
|
-
|
|
137
|
-
**Architecture** (based on NexusChat claude-wrapper):
|
|
138
|
-
|
|
139
|
-
```javascript
|
|
140
|
-
// Main server (Express.js)
|
|
141
|
-
const app = express();
|
|
142
|
-
|
|
143
|
-
// Wrapper class (spawns CLI processes)
|
|
144
|
-
class CliWrapper {
|
|
145
|
-
constructor(binPath) {
|
|
146
|
-
this.binPath = binPath;
|
|
147
|
-
this.activeJobs = new Map();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async execute({ jobId, command, workingDir, timeout, onStatus }) {
|
|
151
|
-
// Spawn PTY process (like NexusChat)
|
|
152
|
-
const ptyProcess = pty.spawn('/bin/bash', ['-c', command], {
|
|
153
|
-
cwd: workingDir,
|
|
154
|
-
env: process.env,
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Parse output
|
|
158
|
-
const parser = new OutputParser();
|
|
159
|
-
|
|
160
|
-
ptyProcess.onData((data) => {
|
|
161
|
-
const events = parser.parse(data);
|
|
162
|
-
events.forEach(event => onStatus(event));
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
ptyProcess.onExit(({ exitCode }) => {
|
|
166
|
-
onStatus({ type: 'done', exitCode });
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Output Parser (adapted from NexusChat)
|
|
172
|
-
class OutputParser {
|
|
173
|
-
parse(chunk) {
|
|
174
|
-
// Regex-based parsing
|
|
175
|
-
// Returns: [{ type, category, message, ... }]
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Session Manager (2-layer persistence)
|
|
180
|
-
class SessionManager {
|
|
181
|
-
constructor(storageDir) {
|
|
182
|
-
this.jobs = new Map(); // RAM cache
|
|
183
|
-
this.storageDir = storageDir; // Disk storage
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
create(jobId, data) { /* ... */ }
|
|
187
|
-
get(jobId) { /* ... */ }
|
|
188
|
-
update(jobId, data) { /* ... */ }
|
|
189
|
-
delete(jobId) { /* ... */ }
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
### 3. Frontend (React Dashboard)
|
|
196
|
-
|
|
197
|
-
**Location**: `/var/www/cli.wellanet.dev/frontend/`
|
|
198
|
-
|
|
199
|
-
**Components**:
|
|
200
|
-
|
|
201
|
-
- **Node Status Panel**: Live status of all registered nodes
|
|
202
|
-
- **Job Submission Form**: Create new jobs with tool/command selection
|
|
203
|
-
- **Execution Log**: Real-time streaming output (SSE)
|
|
204
|
-
- **Job History**: Browse past job results
|
|
205
|
-
- **Batch Operations**: Execute jobs on multiple nodes
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## ๐ Execution Flow
|
|
210
|
-
|
|
211
|
-
### Single Job Execution
|
|
212
|
-
|
|
213
|
-
```
|
|
214
|
-
1. User submits job via frontend
|
|
215
|
-
POST /api/v1/jobs
|
|
216
|
-
{
|
|
217
|
-
"nodeId": "node-001",
|
|
218
|
-
"tool": "bash",
|
|
219
|
-
"command": "systemctl status nginx"
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
2. Control Plane validates and queues job
|
|
223
|
-
- Check node is online
|
|
224
|
-
- Create job record
|
|
225
|
-
- Return jobId + stream endpoint
|
|
226
|
-
|
|
227
|
-
3. Control Plane forwards job to wrapper
|
|
228
|
-
POST http://node-001:5000/api/v1/jobs
|
|
229
|
-
{
|
|
230
|
-
"jobId": "job-123",
|
|
231
|
-
"tool": "bash",
|
|
232
|
-
"command": "systemctl status nginx"
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
4. Wrapper spawns PTY process
|
|
236
|
-
- Launch bash command in PTY
|
|
237
|
-
- Attach output parser
|
|
238
|
-
- Start SSE stream to Control Plane
|
|
239
|
-
|
|
240
|
-
5. Wrapper streams events
|
|
241
|
-
- Parse stdout line-by-line
|
|
242
|
-
- Emit status events (executing, tool, output_chunk)
|
|
243
|
-
- Forward to Control Plane
|
|
244
|
-
|
|
245
|
-
6. Control Plane aggregates and relays
|
|
246
|
-
- Receive events from wrapper
|
|
247
|
-
- Add metadata (timestamp, nodeId)
|
|
248
|
-
- Stream to frontend via SSE
|
|
249
|
-
|
|
250
|
-
7. Frontend displays real-time output
|
|
251
|
-
- Render log entries
|
|
252
|
-
- Update status indicators
|
|
253
|
-
- Show completion notification
|
|
254
|
-
|
|
255
|
-
8. Job completes
|
|
256
|
-
- Wrapper saves result to disk
|
|
257
|
-
- Control Plane updates job status
|
|
258
|
-
- Close SSE streams
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## ๐ Data Flow
|
|
264
|
-
|
|
265
|
-
### Request โ Response Flow
|
|
266
|
-
|
|
267
|
-
```
|
|
268
|
-
โโโโโโโโโโโโ 1. POST /jobs โโโโโโโโโโโโโโโโ 2. POST /jobs โโโโโโโโโโโโ
|
|
269
|
-
โ Frontend โ โโโโโโโโโโโโโโโโโโโถโ Control Planeโ โโโโโโโโโโโโโโโโโถ โ Wrapper โ
|
|
270
|
-
โโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโ
|
|
271
|
-
โ โ โ
|
|
272
|
-
โ 3. GET /jobs/:id/stream โ โ
|
|
273
|
-
โ (SSE connection) โ โ
|
|
274
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
|
|
275
|
-
โ โ โ
|
|
276
|
-
โ โ 4. Spawn PTY (node-pty) โ
|
|
277
|
-
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
278
|
-
โ โ โ
|
|
279
|
-
โ โ 5. SSE events โ
|
|
280
|
-
โ โ (status, output_chunk, done) โ
|
|
281
|
-
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
282
|
-
โ โ โ
|
|
283
|
-
โ 6. Relay SSE events โ โ
|
|
284
|
-
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
|
|
285
|
-
โ data: {"type":"output_... โ โ
|
|
286
|
-
โ data: {"type":"done"... โ โ
|
|
287
|
-
โ โ โ
|
|
288
|
-
โ 7. GET /jobs/:id (final) โ โ
|
|
289
|
-
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโถ โ
|
|
290
|
-
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
|
|
291
|
-
โ {"status":"completed",...} โ โ
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
|
-
## ๐ Security Model
|
|
297
|
-
|
|
298
|
-
### Authentication
|
|
299
|
-
|
|
300
|
-
**Control Plane**:
|
|
301
|
-
- JWT tokens for API clients (frontend, external services)
|
|
302
|
-
- Role-based access control (RBAC)
|
|
303
|
-
- Rate limiting per user
|
|
304
|
-
|
|
305
|
-
**Node Wrapper**:
|
|
306
|
-
- Bearer token authentication (issued during registration)
|
|
307
|
-
- IP whitelist (optional - only accept Control Plane IP)
|
|
308
|
-
- TLS/SSL for HTTP communication
|
|
309
|
-
|
|
310
|
-
### Authorization
|
|
311
|
-
|
|
312
|
-
**Job Execution**:
|
|
313
|
-
- Users can only execute jobs on nodes they have access to
|
|
314
|
-
- Tool restrictions per node (e.g., production nodes: read-only tools)
|
|
315
|
-
- Command whitelisting/blacklisting
|
|
316
|
-
|
|
317
|
-
### Isolation
|
|
318
|
-
|
|
319
|
-
**Sandbox**:
|
|
320
|
-
- Each job runs in isolated working directory
|
|
321
|
-
- No access to wrapper process environment
|
|
322
|
-
- Resource limits (CPU, memory, timeout)
|
|
323
|
-
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
## ๐ Scalability Considerations
|
|
327
|
-
|
|
328
|
-
### Horizontal Scaling
|
|
329
|
-
|
|
330
|
-
**Control Plane**:
|
|
331
|
-
- Stateless API server (can run multiple instances)
|
|
332
|
-
- Shared Redis for session storage (alternative to disk)
|
|
333
|
-
- Load balancer for API requests
|
|
334
|
-
|
|
335
|
-
**Node Wrappers**:
|
|
336
|
-
- Independent agents (no coordination needed)
|
|
337
|
-
- Auto-scaling based on job queue length
|
|
338
|
-
- Node pooling (reserve capacity for burst loads)
|
|
339
|
-
|
|
340
|
-
### Performance Optimizations
|
|
341
|
-
|
|
342
|
-
**Based on NexusChat production experience**:
|
|
343
|
-
|
|
344
|
-
1. **PTY Buffering**: Use `node-pty` for efficient I/O
|
|
345
|
-
2. **Event Batching**: Group multiple output_chunk events
|
|
346
|
-
3. **Compression**: Gzip SSE streams for large outputs
|
|
347
|
-
4. **Caching**: Cache node capabilities in Redis
|
|
348
|
-
5. **Lazy Loading**: Load job results on-demand from disk
|
|
349
|
-
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
## ๐งช Testing Strategy
|
|
353
|
-
|
|
354
|
-
### Unit Tests
|
|
355
|
-
|
|
356
|
-
- Job Manager: create, assign, cancel operations
|
|
357
|
-
- Node Registry: registration, heartbeat, selection logic
|
|
358
|
-
- Output Parser: regex patterns, state machine transitions
|
|
359
|
-
|
|
360
|
-
### Integration Tests
|
|
361
|
-
|
|
362
|
-
- End-to-end job execution flow
|
|
363
|
-
- SSE streaming accuracy
|
|
364
|
-
- Error handling and retries
|
|
365
|
-
- Concurrent job execution
|
|
366
|
-
|
|
367
|
-
### Load Tests
|
|
368
|
-
|
|
369
|
-
- 100 concurrent jobs across 10 nodes
|
|
370
|
-
- 1000 jobs/hour sustained throughput
|
|
371
|
-
- Node failure recovery
|
|
372
|
-
- Network partition tolerance
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
|
-
## ๐ Deployment
|
|
377
|
-
|
|
378
|
-
### Control Plane (VPS1)
|
|
379
|
-
|
|
380
|
-
```bash
|
|
381
|
-
# Production deployment
|
|
382
|
-
cd /var/www/cli.wellanet.dev/backend
|
|
383
|
-
npm install --production
|
|
384
|
-
npm run build
|
|
385
|
-
|
|
386
|
-
# Systemd service
|
|
387
|
-
sudo systemctl start nexuscli-backend.service
|
|
388
|
-
sudo systemctl enable nexuscli-backend.service
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Node Wrapper (any server)
|
|
392
|
-
|
|
393
|
-
```bash
|
|
394
|
-
# Install wrapper
|
|
395
|
-
cd /opt/nexus-wrapper
|
|
396
|
-
npm install --production
|
|
397
|
-
|
|
398
|
-
# Configure
|
|
399
|
-
cat > config.json <<EOF
|
|
400
|
-
{
|
|
401
|
-
"controlPlaneUrl": "https://cli.wellanet.dev",
|
|
402
|
-
"nodeId": "node-001",
|
|
403
|
-
"port": 5000,
|
|
404
|
-
"tools": ["bash", "git", "docker"]
|
|
405
|
-
}
|
|
406
|
-
EOF
|
|
407
|
-
|
|
408
|
-
# Start service
|
|
409
|
-
sudo systemctl start nexus-wrapper.service
|
|
410
|
-
sudo systemctl enable nexus-wrapper.service
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
---
|
|
414
|
-
|
|
415
|
-
## ๐ฎ Future Enhancements
|
|
416
|
-
|
|
417
|
-
### Phase 2: Advanced Features
|
|
418
|
-
|
|
419
|
-
- **WebSocket support**: Alternative to SSE for bidirectional communication
|
|
420
|
-
- **Job scheduling**: Cron-like scheduling for recurring jobs
|
|
421
|
-
- **Job templates**: Predefined workflows (e.g., "Deploy to Production")
|
|
422
|
-
- **Approval workflows**: Multi-stage approvals for sensitive operations
|
|
423
|
-
|
|
424
|
-
### Phase 3: WellaNet Integration
|
|
425
|
-
|
|
426
|
-
- **Compute marketplace**: Nodes bid for job execution
|
|
427
|
-
- **Wellacoin tokens**: Pay-per-execution model
|
|
428
|
-
- **Resource optimization**: AI-based node selection
|
|
429
|
-
- **Decentralized registry**: Blockchain-based node tracking
|
|
430
|
-
|
|
431
|
-
---
|
|
432
|
-
|
|
433
|
-
## ๐ Related Documents
|
|
434
|
-
|
|
435
|
-
- [API & Wrapper Contract](./API_WRAPPER_CONTRACT.md) - Detailed API specification
|
|
436
|
-
- [NexusChat CLAUDE.md](../../CLAUDE.md) - Development workflow
|
|
437
|
-
- [NexusChat Wrapper Source](/var/www/chat.mmmbuto.com/servers/claude-code-server/) - Reference implementation
|
|
438
|
-
|
|
439
|
-
---
|
|
440
|
-
|
|
441
|
-
_Generated by Claude Code (Sonnet 4.5) - 2025-11-17_
|