erne-universal 0.2.0 → 0.3.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 +92 -26
- package/agents/feature-builder.md +88 -0
- package/agents/senior-developer.md +77 -0
- package/bin/cli.js +4 -2
- package/dashboard/package.json +10 -0
- package/dashboard/public/agents.js +329 -0
- package/dashboard/public/canvas.js +275 -0
- package/dashboard/public/index.html +113 -0
- package/dashboard/public/sidebar.js +107 -0
- package/dashboard/public/ws-client.js +69 -0
- package/dashboard/server.js +191 -0
- package/docs/assets/dashboard-preview.png +0 -0
- package/docs/superpowers/plans/2026-03-11-agent-dashboard.md +1537 -0
- package/docs/superpowers/specs/2026-03-11-agent-dashboard-design.md +275 -0
- package/hooks/hooks.json +14 -0
- package/lib/dashboard.js +156 -0
- package/lib/init.js +294 -0
- package/lib/start.js +26 -0
- package/lib/update.js +60 -0
- package/package.json +3 -1
- package/scripts/daily-news/scan-ai-agents.js +222 -0
- package/scripts/daily-news/scan-rn-expo.js +233 -0
- package/scripts/hooks/dashboard-event.js +89 -0
- package/scripts/sync/issue-to-clickup.js +108 -0
- package/scripts/validate-all.js +1 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# ERNE Agent Dashboard — Design Spec
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-03-11
|
|
4
|
+
**Status:** Approved
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
A local pixel-art visual dashboard that shows all ERNE agents working in real-time. When a user starts the ERNE agent system, a local server launches and opens a browser with an animated "office" where each agent sits at a desk, moves between rooms, and visually reflects its current status.
|
|
9
|
+
|
|
10
|
+
Inspired by OpenClaw's "Agents Team" view.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Claude Code Hooks → HTTP POST → Dashboard Server → WebSocket → Browser Canvas
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Components
|
|
19
|
+
|
|
20
|
+
1. **Dashboard Server** (`dashboard/server.js`) — Node.js HTTP + WebSocket server. Uses `ws` npm package for WebSocket (the only dependency).
|
|
21
|
+
2. **Claude Code Hooks** — `PreToolUse` (pattern: `Agent`) and `PostToolUse` (pattern: `Agent`) hooks that POST events to `localhost:3333/api/events` via a Node.js hook script.
|
|
22
|
+
3. **Frontend** (`dashboard/public/`) — HTML5 Canvas pixel-art office + WebSocket client
|
|
23
|
+
4. **CLI Integration** (`bin/cli.js`) — `erne dashboard` and `erne start` commands added to COMMANDS map
|
|
24
|
+
|
|
25
|
+
### File Structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
dashboard/
|
|
29
|
+
├── server.js # HTTP + WebSocket server (ws package)
|
|
30
|
+
├── package.json # { "dependencies": { "ws": "^8" } }
|
|
31
|
+
├── public/
|
|
32
|
+
│ ├── index.html # Entry point
|
|
33
|
+
│ ├── canvas.js # Office renderer (rooms, furniture, grid)
|
|
34
|
+
│ ├── agents.js # Agent sprites & animations
|
|
35
|
+
│ ├── sidebar.js # Status panel
|
|
36
|
+
│ ├── ws-client.js # WebSocket connection (auto-reconnect)
|
|
37
|
+
│ └── sprites/ # Pixel art assets (optional future PNGs)
|
|
38
|
+
scripts/hooks/
|
|
39
|
+
├── dashboard-event.js # Hook script that parses stdin and POSTs to dashboard
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Changes to Existing Files
|
|
43
|
+
|
|
44
|
+
- **`bin/cli.js`**: Add `dashboard` and `start` to `COMMANDS` map. `dashboard` spawns `dashboard/server.js`. `start` runs existing init + spawns dashboard in background.
|
|
45
|
+
- **`hooks/hooks.json`**: Add two new entries for `PreToolUse` (pattern: `Agent`) and `PostToolUse` (pattern: `Agent`) pointing to `dashboard-event.js`, profile: `["minimal", "standard", "strict"]`.
|
|
46
|
+
- **`package.json`**: Add `ws` as dependency.
|
|
47
|
+
|
|
48
|
+
## Office Layout
|
|
49
|
+
|
|
50
|
+
4 rooms, each mapped to agent roles:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
┌──────────────────────────────────────────────┐
|
|
54
|
+
│ ERNE HQ │
|
|
55
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
56
|
+
│ │ DEVELOPMENT │ │ REVIEW │ │
|
|
57
|
+
│ │ architect │ │ code-reviewer│ │
|
|
58
|
+
│ │ native- │ │ upgrade- │ │
|
|
59
|
+
│ │ bridge- │ │ assistant │ │
|
|
60
|
+
│ │ builder │ │ │ │
|
|
61
|
+
│ │ expo-config │ │ │ │
|
|
62
|
+
│ │ ui-designer │ │ │ │
|
|
63
|
+
│ └──────────────┘ └──────────────┘ │
|
|
64
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────┐ │
|
|
65
|
+
│ │ TESTING │ │ CONFERENCE │ │SIDE- │ │
|
|
66
|
+
│ │ tdd-guide │ │ (multi-agent │ │BAR │ │
|
|
67
|
+
│ │ performance │ │ tasks) │ │ │ │
|
|
68
|
+
│ │ profiler │ │ │ │ │ │
|
|
69
|
+
│ └──────────────┘ └──────────────┘ └──────┘ │
|
|
70
|
+
└──────────────────────────────────────────────┘
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Agent-Room Assignment
|
|
74
|
+
|
|
75
|
+
| Agent | Default Room | Sprite Trait |
|
|
76
|
+
|-------|-------------|-------------|
|
|
77
|
+
| architect | Development | Blue hard hat |
|
|
78
|
+
| native-bridge-builder | Development | Wrench |
|
|
79
|
+
| expo-config-resolver | Development | Gear icon |
|
|
80
|
+
| ui-designer | Development | Paintbrush |
|
|
81
|
+
| code-reviewer | Review | Glasses |
|
|
82
|
+
| upgrade-assistant | Review | Arrow up |
|
|
83
|
+
| tdd-guide | Testing | Test tube |
|
|
84
|
+
| performance-profiler | Testing | Stopwatch |
|
|
85
|
+
|
|
86
|
+
## Agent States & Animations
|
|
87
|
+
|
|
88
|
+
- **IDLE** — Sitting at desk, head bob animation
|
|
89
|
+
- **WORKING** — Typing on computer, screen text animation, colored indicator on desk
|
|
90
|
+
- **MOVING** — Walking to Conference Room for multi-agent tasks
|
|
91
|
+
- **DONE** — Green checkmark popup, then returns to IDLE
|
|
92
|
+
|
|
93
|
+
Note: ERROR state is deferred to v2. In v1, agents that time out transition directly from WORKING to IDLE.
|
|
94
|
+
|
|
95
|
+
## Event System
|
|
96
|
+
|
|
97
|
+
### Hook Implementation
|
|
98
|
+
|
|
99
|
+
Claude Code hooks receive context via stdin as JSON. The hook script `scripts/hooks/dashboard-event.js` reads stdin, extracts the agent name from the tool input parameters, and POSTs to the dashboard server.
|
|
100
|
+
|
|
101
|
+
**Hook entries in `hooks/hooks.json`:**
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"event": "PreToolUse",
|
|
106
|
+
"pattern": "Agent",
|
|
107
|
+
"script": "dashboard-event.js",
|
|
108
|
+
"command": "node scripts/hooks/run-with-flags.js dashboard-event.js",
|
|
109
|
+
"profiles": ["minimal", "standard", "strict"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"event": "PostToolUse",
|
|
113
|
+
"pattern": "Agent",
|
|
114
|
+
"script": "dashboard-event.js",
|
|
115
|
+
"command": "node scripts/hooks/run-with-flags.js dashboard-event.js",
|
|
116
|
+
"profiles": ["minimal", "standard", "strict"]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**`dashboard-event.js` logic:**
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
// Reads hook context from stdin (JSON)
|
|
124
|
+
// PreToolUse with Agent pattern → extracts agent name from tool_input.prompt
|
|
125
|
+
// PostToolUse with Agent pattern → agent completed
|
|
126
|
+
// Maps agent name to ERNE agent by keyword matching
|
|
127
|
+
// POSTs { type, agent, task } to http://localhost:3333/api/events
|
|
128
|
+
// Silently fails if dashboard server is not running (non-blocking)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The script uses `http` built-in module (no dependencies). It matches agent names by scanning the `prompt` field for known ERNE agent keywords (architect, code-reviewer, tdd-guide, etc.).
|
|
132
|
+
|
|
133
|
+
### Event Schema
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
{
|
|
137
|
+
type: "agent:start" | "agent:complete" | "tool:call",
|
|
138
|
+
agent: "architect" | "code-reviewer" | ...,
|
|
139
|
+
task: "Planning authentication module", // optional, from prompt
|
|
140
|
+
timestamp: 1710000000000 // added by server
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Server State (in-memory)
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
agents: {
|
|
148
|
+
"architect": {
|
|
149
|
+
status: "working", // idle | working | done
|
|
150
|
+
task: "Planning auth module",
|
|
151
|
+
room: "development",
|
|
152
|
+
startedAt: 1710000000000,
|
|
153
|
+
lastEvent: 1710000005000
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Auto-timeout: if no event from agent for 5 minutes, status transitions directly to IDLE.
|
|
159
|
+
|
|
160
|
+
## CLI Integration
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
erne dashboard # Start on port 3333, open browser
|
|
164
|
+
erne dashboard --port 4444 # Custom port
|
|
165
|
+
erne dashboard --no-open # Don't open browser
|
|
166
|
+
|
|
167
|
+
erne start # Existing functionality + dashboard in background
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Changes to `bin/cli.js`
|
|
171
|
+
|
|
172
|
+
Add to `COMMANDS`:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
dashboard: () => require('../lib/dashboard'),
|
|
176
|
+
start: () => require('../lib/start'),
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Update help text to include `dashboard` and `start` commands.
|
|
180
|
+
|
|
181
|
+
### `lib/dashboard.js` Flow
|
|
182
|
+
|
|
183
|
+
1. Parse `--port` (default 3333) and `--no-open` flags from `process.argv`
|
|
184
|
+
2. Check if port is available (try `net.createServer().listen()`)
|
|
185
|
+
3. Start `dashboard/server.js` via `child_process.fork()`
|
|
186
|
+
4. Open `http://localhost:{port}` in browser (`open` on macOS, `xdg-open` on Linux)
|
|
187
|
+
5. Console: `ERNE Dashboard running at http://localhost:{port}`
|
|
188
|
+
6. Handle SIGINT for clean shutdown (kill child process)
|
|
189
|
+
|
|
190
|
+
### `lib/start.js` Flow
|
|
191
|
+
|
|
192
|
+
1. Run existing `init` logic
|
|
193
|
+
2. Spawn dashboard in background (detached, stdio ignored)
|
|
194
|
+
3. Log dashboard URL
|
|
195
|
+
|
|
196
|
+
### Auto Hook Setup
|
|
197
|
+
|
|
198
|
+
On first `erne dashboard` run, checks `.claude/hooks.json` for dashboard-event entries. If missing, prompts:
|
|
199
|
+
```
|
|
200
|
+
Dashboard hooks not configured. Add them now? (Y/n)
|
|
201
|
+
```
|
|
202
|
+
If yes, appends the two hook entries to the hooks array.
|
|
203
|
+
|
|
204
|
+
## Error Handling
|
|
205
|
+
|
|
206
|
+
### Dashboard Server
|
|
207
|
+
- **Port in use**: Log error and suggest `--port` flag, exit 1
|
|
208
|
+
- **CORS**: Not needed — hooks use `curl`/`http.request` (not browser), and the browser frontend is served from the same origin
|
|
209
|
+
- **Malformed events**: Validate incoming JSON, ignore invalid payloads with a warning log
|
|
210
|
+
|
|
211
|
+
### WebSocket Client (browser)
|
|
212
|
+
- **Disconnection**: Auto-reconnect with exponential backoff (1s, 2s, 4s, max 30s)
|
|
213
|
+
- **Connection indicator**: Small dot in the corner — green=connected, red=disconnected
|
|
214
|
+
|
|
215
|
+
### Hook Script
|
|
216
|
+
- **Dashboard not running**: `dashboard-event.js` catches connection errors silently (no stderr, no exit code) so it never blocks Claude Code
|
|
217
|
+
- **Invalid stdin**: Skip and exit 0
|
|
218
|
+
|
|
219
|
+
## Sprite System
|
|
220
|
+
|
|
221
|
+
### Procedural Pixel Art (v1)
|
|
222
|
+
|
|
223
|
+
Sprites are drawn programmatically on Canvas (no PNG files needed for v1). Each agent is 32x32 pixels with unique visual traits.
|
|
224
|
+
|
|
225
|
+
### Sprite Sheet Layout (per agent, programmatic)
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
┌────┬────┬────┬────┐
|
|
229
|
+
│ I1 │ I2 │ I3 │ I4 │ IDLE (row 0)
|
|
230
|
+
├────┼────┼────┼────┤
|
|
231
|
+
│ W1 │ W2 │ W3 │ W4 │ WORKING (row 1)
|
|
232
|
+
├────┼────┼────┼────┤
|
|
233
|
+
│ M1 │ M2 │ M3 │ M4 │ MOVING (row 2)
|
|
234
|
+
├────┼────┼────┼────┤
|
|
235
|
+
│ D1 │ D2 │ D3 │ D4 │ DONE (row 3)
|
|
236
|
+
└────┴────┴────┴────┘
|
|
237
|
+
128x128 total per agent
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Rendering
|
|
241
|
+
|
|
242
|
+
- Office background: pre-rendered static tilemap
|
|
243
|
+
- Rooms separated by doors
|
|
244
|
+
- Each desk has assigned `{x, y}` position
|
|
245
|
+
- Animation: `requestAnimationFrame` at 12 FPS sprite / 60 FPS canvas
|
|
246
|
+
- Room furniture: desks, computers, whiteboard, coffee machine
|
|
247
|
+
|
|
248
|
+
### Color Palette
|
|
249
|
+
|
|
250
|
+
- Office background: #2C2137, #4A3F5C, #7B6B8D
|
|
251
|
+
- Room floor: #8B7355
|
|
252
|
+
- Furniture: #5C4033, #8B6914
|
|
253
|
+
- Status: WORKING=#4CAF50, IDLE=#9E9E9E, DONE=#2196F3
|
|
254
|
+
|
|
255
|
+
## Scope Boundaries
|
|
256
|
+
|
|
257
|
+
**In scope (v1):**
|
|
258
|
+
- Dashboard server with WebSocket (`ws` package)
|
|
259
|
+
- Pixel art office with 4 rooms
|
|
260
|
+
- 8 agent sprites (procedural)
|
|
261
|
+
- Agent status tracking via Claude Code hooks (PreToolUse/PostToolUse with Agent pattern)
|
|
262
|
+
- Sidebar with agent list and status
|
|
263
|
+
- CLI commands (`erne dashboard`, `erne start` integration)
|
|
264
|
+
- Auto hook configuration
|
|
265
|
+
- WebSocket auto-reconnect
|
|
266
|
+
- Auto-timeout for stale agents
|
|
267
|
+
|
|
268
|
+
**Out of scope (v2+):**
|
|
269
|
+
- ERROR state with dedicated hook
|
|
270
|
+
- Hand-drawn PNG sprites
|
|
271
|
+
- Task history / timeline view
|
|
272
|
+
- Multiple user sessions
|
|
273
|
+
- Remote access
|
|
274
|
+
- Sound effects
|
|
275
|
+
- Agent chat/communication visualization
|
package/hooks/hooks.json
CHANGED
|
@@ -97,6 +97,20 @@
|
|
|
97
97
|
"command": "node scripts/hooks/run-with-flags.js accessibility-check.js",
|
|
98
98
|
"profiles": ["strict"]
|
|
99
99
|
},
|
|
100
|
+
{
|
|
101
|
+
"event": "PreToolUse",
|
|
102
|
+
"pattern": "Agent",
|
|
103
|
+
"script": "dashboard-event.js",
|
|
104
|
+
"command": "node scripts/hooks/run-with-flags.js dashboard-event.js",
|
|
105
|
+
"profiles": ["minimal", "standard", "strict"]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"event": "PostToolUse",
|
|
109
|
+
"pattern": "Agent",
|
|
110
|
+
"script": "dashboard-event.js",
|
|
111
|
+
"command": "node scripts/hooks/run-with-flags.js dashboard-event.js",
|
|
112
|
+
"profiles": ["minimal", "standard", "strict"]
|
|
113
|
+
},
|
|
100
114
|
{
|
|
101
115
|
"event": "Stop",
|
|
102
116
|
"script": "continuous-learning-observer.js",
|
package/lib/dashboard.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// lib/dashboard.js — Launch the ERNE Agent Dashboard
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const net = require('net');
|
|
5
|
+
const { fork } = require('child_process');
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const readline = require('readline/promises');
|
|
10
|
+
const { stdin, stdout } = require('process');
|
|
11
|
+
|
|
12
|
+
const HOOKS_PATH = path.resolve(__dirname, '..', 'hooks', 'hooks.json');
|
|
13
|
+
const SERVER_PATH = path.resolve(__dirname, '..', 'dashboard', 'server.js');
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const args = argv.slice(3);
|
|
17
|
+
let port = 3333;
|
|
18
|
+
let open = true;
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
if (args[i] === '--port' && args[i + 1]) {
|
|
22
|
+
const parsed = parseInt(args[i + 1], 10);
|
|
23
|
+
if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
24
|
+
console.error(`Invalid port: ${args[i + 1]} (must be 1-65535)`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
port = parsed;
|
|
28
|
+
i++;
|
|
29
|
+
} else if (args[i] === '--no-open') {
|
|
30
|
+
open = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { port, open };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function checkPort(port) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const server = net.createServer();
|
|
40
|
+
server.once('error', (err) => {
|
|
41
|
+
if (err.code === 'EADDRINUSE') {
|
|
42
|
+
reject(new Error(`Port ${port} is already in use`));
|
|
43
|
+
} else {
|
|
44
|
+
reject(err);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
server.once('listening', () => {
|
|
48
|
+
server.close(() => resolve());
|
|
49
|
+
});
|
|
50
|
+
server.listen(port);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function ensureHooksConfigured() {
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs.readFileSync(HOOKS_PATH, 'utf8');
|
|
57
|
+
const data = JSON.parse(raw);
|
|
58
|
+
const hasDashboardHook = data.hooks.some(
|
|
59
|
+
(h) => h.script === 'dashboard-event.js'
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (hasDashboardHook) return;
|
|
63
|
+
|
|
64
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
65
|
+
const answer = await rl.question(
|
|
66
|
+
' Dashboard hooks not configured. Add them now? (Y/n) '
|
|
67
|
+
);
|
|
68
|
+
rl.close();
|
|
69
|
+
|
|
70
|
+
if (answer.toLowerCase() === 'n') return;
|
|
71
|
+
|
|
72
|
+
data.hooks.push(
|
|
73
|
+
{
|
|
74
|
+
event: 'PreToolUse',
|
|
75
|
+
pattern: 'Agent',
|
|
76
|
+
script: 'dashboard-event.js',
|
|
77
|
+
command: 'node scripts/hooks/run-with-flags.js dashboard-event.js',
|
|
78
|
+
profiles: ['minimal', 'standard', 'strict'],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
event: 'PostToolUse',
|
|
82
|
+
pattern: 'Agent',
|
|
83
|
+
script: 'dashboard-event.js',
|
|
84
|
+
command: 'node scripts/hooks/run-with-flags.js dashboard-event.js',
|
|
85
|
+
profiles: ['minimal', 'standard', 'strict'],
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
fs.writeFileSync(HOOKS_PATH, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
90
|
+
console.log(' Dashboard hooks added to hooks.json');
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err.code === 'ENOENT') {
|
|
93
|
+
console.warn(' Warning: hooks/hooks.json not found, skipping hook configuration');
|
|
94
|
+
} else {
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function openBrowser(url) {
|
|
101
|
+
const platform = process.platform;
|
|
102
|
+
try {
|
|
103
|
+
if (platform === 'darwin') {
|
|
104
|
+
execFileSync('open', [url]);
|
|
105
|
+
} else if (platform === 'linux') {
|
|
106
|
+
execFileSync('xdg-open', [url]);
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// Silently ignore — browser open is best-effort
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = async function dashboard() {
|
|
114
|
+
const { port, open } = parseArgs(process.argv);
|
|
115
|
+
|
|
116
|
+
await checkPort(port);
|
|
117
|
+
await ensureHooksConfigured();
|
|
118
|
+
|
|
119
|
+
// Ensure ws dependency is installed
|
|
120
|
+
const dashboardDir = path.resolve(__dirname, '..', 'dashboard');
|
|
121
|
+
if (!fs.existsSync(path.join(dashboardDir, 'node_modules', 'ws'))) {
|
|
122
|
+
console.log(' Installing dashboard dependencies...');
|
|
123
|
+
require('child_process').execSync('npm install --production', { cwd: dashboardDir, stdio: 'ignore' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const child = fork(SERVER_PATH, [], {
|
|
127
|
+
env: { ...process.env, ERNE_DASHBOARD_PORT: String(port) },
|
|
128
|
+
stdio: 'pipe',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
child.stdout.on('data', (data) => process.stdout.write(data));
|
|
132
|
+
child.stderr.on('data', (data) => process.stderr.write(data));
|
|
133
|
+
|
|
134
|
+
const url = `http://localhost:${port}`;
|
|
135
|
+
|
|
136
|
+
// Wait briefly for the server to start before opening the browser
|
|
137
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
138
|
+
console.log(` ERNE Dashboard: ${url}`);
|
|
139
|
+
|
|
140
|
+
if (open) {
|
|
141
|
+
openBrowser(url);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const shutdown = () => {
|
|
145
|
+
child.kill('SIGTERM');
|
|
146
|
+
process.exit(0);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
process.on('SIGINT', shutdown);
|
|
150
|
+
process.on('SIGTERM', shutdown);
|
|
151
|
+
|
|
152
|
+
// Keep alive until child exits
|
|
153
|
+
await new Promise((resolve) => {
|
|
154
|
+
child.on('exit', resolve);
|
|
155
|
+
});
|
|
156
|
+
};
|