@polderlabs/bizar 2.6.1 → 3.0.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/cli/bin.mjs +158 -130
- package/cli/plan.test.mjs +2331 -0
- package/cli/service.mjs +309 -0
- package/package.json +19 -27
- package/cli/dashboard/api.mjs +0 -473
- package/cli/dashboard/browser.mjs +0 -40
- package/cli/dashboard/server.mjs +0 -366
- package/cli/dashboard/state.mjs +0 -438
- package/cli/dashboard/tasks-store.mjs +0 -203
- package/cli/dashboard/watcher.mjs +0 -81
- package/cli/dashboard.mjs +0 -97
- package/dist/assets/index-BVvY22Gt.css +0 -1
- package/dist/assets/index-CO3c8O32.js +0 -285
- package/dist/assets/index-CO3c8O32.js.map +0 -1
- package/dist/index.html +0 -18
- package/src/App.tsx +0 -233
- package/src/components/Button.tsx +0 -55
- package/src/components/Card.tsx +0 -40
- package/src/components/EmptyState.tsx +0 -30
- package/src/components/Modal.tsx +0 -137
- package/src/components/Spinner.tsx +0 -19
- package/src/components/StatusBadge.tsx +0 -25
- package/src/components/Tag.tsx +0 -28
- package/src/components/Toast.tsx +0 -142
- package/src/components/Topbar.tsx +0 -88
- package/src/index.html +0 -17
- package/src/lib/api.ts +0 -71
- package/src/lib/markdown.tsx +0 -59
- package/src/lib/types.ts +0 -200
- package/src/lib/utils.ts +0 -79
- package/src/lib/ws.ts +0 -132
- package/src/main.tsx +0 -12
- package/src/styles/main.css +0 -2324
- package/src/views/Agents.tsx +0 -199
- package/src/views/Chat.tsx +0 -255
- package/src/views/Config.tsx +0 -250
- package/src/views/Overview.tsx +0 -267
- package/src/views/Plans.tsx +0 -667
- package/src/views/Projects.tsx +0 -155
- package/src/views/Settings.tsx +0 -253
- package/src/views/Tasks.tsx +0 -567
- package/tsconfig.json +0 -23
- package/vite.config.ts +0 -24
package/cli/dashboard/server.mjs
DELETED
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cli/dashboard/server.mjs
|
|
3
|
-
*
|
|
4
|
-
* Wires the API router, the file watcher, the WebSocket layer, and the
|
|
5
|
-
* static frontend into a single Express + http.Server pair.
|
|
6
|
-
*
|
|
7
|
-
* Lifecycle:
|
|
8
|
-
* createServer() → returns handles but does NOT listen
|
|
9
|
-
* server.listen(...) → caller (cli/dashboard.mjs or tests) starts it
|
|
10
|
-
* close() → stops watcher, closes wss, closes http server
|
|
11
|
-
*
|
|
12
|
-
* v2.6.0: serves the Vite-built React SPA from `dist/` instead of the
|
|
13
|
-
* vanilla-JS `dashboard/` directory.
|
|
14
|
-
*/
|
|
15
|
-
import express from 'express';
|
|
16
|
-
import { WebSocketServer } from 'ws';
|
|
17
|
-
import { createServer as createHttpServer } from 'node:http';
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
|
-
import { dirname, join } from 'node:path';
|
|
20
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
21
|
-
import { createApiRouter } from './api.mjs';
|
|
22
|
-
import { createState } from './state.mjs';
|
|
23
|
-
import { createWatcher } from './watcher.mjs';
|
|
24
|
-
|
|
25
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
// cli/dashboard/server.mjs → repo root is two levels up
|
|
27
|
-
const DIST_DIR = join(__dirname, '..', '..', 'dist');
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @param {object} opts
|
|
31
|
-
* @param {number} opts.port - requested port (caller chooses after findFreePort)
|
|
32
|
-
* @param {string} opts.projectRoot
|
|
33
|
-
* @param {string} opts.opencodeConfigDir
|
|
34
|
-
* @param {string} opts.bizarRoot
|
|
35
|
-
*/
|
|
36
|
-
export function createServer({
|
|
37
|
-
port,
|
|
38
|
-
projectRoot,
|
|
39
|
-
opencodeConfigDir,
|
|
40
|
-
bizarRoot,
|
|
41
|
-
}) {
|
|
42
|
-
const app = express();
|
|
43
|
-
app.use(express.json({ limit: '1mb' }));
|
|
44
|
-
|
|
45
|
-
// JSON parse errors must come back as JSON, not Express's HTML page.
|
|
46
|
-
// body-parser emits the error on `app`, so we attach here rather than
|
|
47
|
-
// on the router.
|
|
48
|
-
app.use(
|
|
49
|
-
(
|
|
50
|
-
err,
|
|
51
|
-
_req,
|
|
52
|
-
res,
|
|
53
|
-
_next, // eslint-disable-line no-unused-vars
|
|
54
|
-
) => {
|
|
55
|
-
const status = err?.status || err?.statusCode || 400;
|
|
56
|
-
res.status(status).json({
|
|
57
|
-
error: err?.type || 'bad_request',
|
|
58
|
-
message: err?.message || String(err),
|
|
59
|
-
});
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// State + watcher are created once for the lifetime of the server
|
|
64
|
-
const state = createState({ projectRoot, opencodeConfigDir, bizarRoot });
|
|
65
|
-
|
|
66
|
-
const watchPaths = [
|
|
67
|
-
state.paths.opencodeJson,
|
|
68
|
-
state.paths.agentsDir,
|
|
69
|
-
state.paths.commandsDir,
|
|
70
|
-
state.paths.bizarDir,
|
|
71
|
-
state.paths.plansDir,
|
|
72
|
-
state.paths.globalPlansDir,
|
|
73
|
-
].filter((p) => existsSync(p) || safeCanCreate(p));
|
|
74
|
-
|
|
75
|
-
const watcher = createWatcher({
|
|
76
|
-
paths: watchPaths,
|
|
77
|
-
onChange: (event, p) => {
|
|
78
|
-
// Broadcast to every live WS client
|
|
79
|
-
wss.clients.forEach((client) => {
|
|
80
|
-
if (client.readyState === 1) {
|
|
81
|
-
try {
|
|
82
|
-
client.send(
|
|
83
|
-
JSON.stringify({
|
|
84
|
-
type: 'change',
|
|
85
|
-
event,
|
|
86
|
-
path: p,
|
|
87
|
-
ts: Date.now(),
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
} catch {
|
|
91
|
-
/* dropped — client likely disconnected */
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// HTTP + WS server pair so they can share a port
|
|
99
|
-
const server = createHttpServer(app);
|
|
100
|
-
const wss = new WebSocketServer({ server, path: '/ws' });
|
|
101
|
-
|
|
102
|
-
/** Broadcast a message to all connected WS clients. */
|
|
103
|
-
function broadcast(msg) {
|
|
104
|
-
const payload = JSON.stringify(msg);
|
|
105
|
-
wss.clients.forEach((client) => {
|
|
106
|
-
if (client.readyState === 1) {
|
|
107
|
-
try {
|
|
108
|
-
client.send(payload);
|
|
109
|
-
} catch {
|
|
110
|
-
/* dropped */
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Routes
|
|
117
|
-
app.use(
|
|
118
|
-
'/api',
|
|
119
|
-
createApiRouter({
|
|
120
|
-
state,
|
|
121
|
-
watcher,
|
|
122
|
-
projectRoot,
|
|
123
|
-
opencodeConfigDir,
|
|
124
|
-
bizarRoot,
|
|
125
|
-
broadcast,
|
|
126
|
-
}),
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
// ── Static frontend (React SPA in dist/) ─────────────────────────
|
|
130
|
-
// If dist/ hasn't been built yet, serve a helpful message instead of 404.
|
|
131
|
-
const distBuilt =
|
|
132
|
-
existsSync(DIST_DIR) &&
|
|
133
|
-
existsSync(join(DIST_DIR, 'index.html'));
|
|
134
|
-
|
|
135
|
-
if (distBuilt) {
|
|
136
|
-
// Serve hashed assets under /assets/ explicitly so they cache aggressively
|
|
137
|
-
const assetsDir = join(DIST_DIR, 'assets');
|
|
138
|
-
if (existsSync(assetsDir)) {
|
|
139
|
-
app.use(
|
|
140
|
-
'/assets',
|
|
141
|
-
express.static(assetsDir, {
|
|
142
|
-
maxAge: '1y',
|
|
143
|
-
immutable: true,
|
|
144
|
-
index: false,
|
|
145
|
-
}),
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
app.use(
|
|
149
|
-
express.static(DIST_DIR, {
|
|
150
|
-
extensions: ['html'],
|
|
151
|
-
// index.html should NOT be cached aggressively (it references hashed bundles)
|
|
152
|
-
setHeaders: (res, filePath) => {
|
|
153
|
-
if (filePath.endsWith('index.html')) {
|
|
154
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
}),
|
|
158
|
-
);
|
|
159
|
-
// SPA fallback — any unmatched non-API route gets the index.
|
|
160
|
-
app.get('*', (req, res, next) => {
|
|
161
|
-
if (req.path.startsWith('/api') || req.path === '/ws') return next();
|
|
162
|
-
res.sendFile(join(DIST_DIR, 'index.html'));
|
|
163
|
-
});
|
|
164
|
-
} else {
|
|
165
|
-
// No build yet — tell the user what to do.
|
|
166
|
-
app.get('/', (_req, res) => {
|
|
167
|
-
res
|
|
168
|
-
.status(503)
|
|
169
|
-
.type('html')
|
|
170
|
-
.send(renderNotBuiltPage());
|
|
171
|
-
});
|
|
172
|
-
app.get('*', (_req, res) => {
|
|
173
|
-
if (_req.path.startsWith('/api') || _req.path === '/ws') {
|
|
174
|
-
res.status(404).json({
|
|
175
|
-
error: 'not_found',
|
|
176
|
-
message: `no route for ${_req.method} ${_req.originalUrl}`,
|
|
177
|
-
});
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
res.status(503).type('html').send(renderNotBuiltPage());
|
|
181
|
-
});
|
|
182
|
-
// eslint-disable-next-line no-console
|
|
183
|
-
console.warn(
|
|
184
|
-
`[dashboard] dist/ not found at ${DIST_DIR}. ` +
|
|
185
|
-
`Run \`npm run build\` from the repo root to build the React SPA.`,
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// WebSocket handshake — send initial snapshot, accept simple commands
|
|
190
|
-
wss.on('connection', (ws) => {
|
|
191
|
-
try {
|
|
192
|
-
ws.send(
|
|
193
|
-
JSON.stringify({
|
|
194
|
-
type: 'snapshot',
|
|
195
|
-
ts: Date.now(),
|
|
196
|
-
data: {
|
|
197
|
-
overview: state.getOverview(),
|
|
198
|
-
agents: state.getAgents(),
|
|
199
|
-
plans: state.getPlans(),
|
|
200
|
-
projects: state.getProjects(),
|
|
201
|
-
config: state.getConfig(),
|
|
202
|
-
settings: state.getSettings(),
|
|
203
|
-
tasks: state.getTasks(),
|
|
204
|
-
},
|
|
205
|
-
}),
|
|
206
|
-
);
|
|
207
|
-
} catch {
|
|
208
|
-
/* ignore */
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
ws.on('message', (raw) => {
|
|
212
|
-
let msg;
|
|
213
|
-
try {
|
|
214
|
-
msg = JSON.parse(raw.toString());
|
|
215
|
-
} catch {
|
|
216
|
-
return; // ignore malformed
|
|
217
|
-
}
|
|
218
|
-
switch (msg?.type) {
|
|
219
|
-
case 'ping':
|
|
220
|
-
try {
|
|
221
|
-
ws.send(JSON.stringify({ type: 'pong', ts: Date.now() }));
|
|
222
|
-
} catch {
|
|
223
|
-
/* ignore */
|
|
224
|
-
}
|
|
225
|
-
break;
|
|
226
|
-
case 'refresh':
|
|
227
|
-
try {
|
|
228
|
-
ws.send(
|
|
229
|
-
JSON.stringify({
|
|
230
|
-
type: 'snapshot',
|
|
231
|
-
ts: Date.now(),
|
|
232
|
-
data: {
|
|
233
|
-
overview: state.getOverview(),
|
|
234
|
-
agents: state.getAgents(),
|
|
235
|
-
plans: state.getPlans(),
|
|
236
|
-
projects: state.getProjects(),
|
|
237
|
-
config: state.getConfig(),
|
|
238
|
-
settings: state.getSettings(),
|
|
239
|
-
tasks: state.getTasks(),
|
|
240
|
-
},
|
|
241
|
-
}),
|
|
242
|
-
);
|
|
243
|
-
} catch {
|
|
244
|
-
/* ignore */
|
|
245
|
-
}
|
|
246
|
-
break;
|
|
247
|
-
case 'dismiss-notification':
|
|
248
|
-
// No-op for now — UI keeps a local dismiss list
|
|
249
|
-
break;
|
|
250
|
-
default:
|
|
251
|
-
// Unknown type — silently ignore
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
watcher.start();
|
|
258
|
-
|
|
259
|
-
function close() {
|
|
260
|
-
try {
|
|
261
|
-
watcher.stop();
|
|
262
|
-
} catch {
|
|
263
|
-
/* ignore */
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
wss.clients.forEach((c) => c.terminate());
|
|
267
|
-
wss.close();
|
|
268
|
-
} catch {
|
|
269
|
-
/* ignore */
|
|
270
|
-
}
|
|
271
|
-
try {
|
|
272
|
-
server.close();
|
|
273
|
-
} catch {
|
|
274
|
-
/* ignore */
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
app,
|
|
280
|
-
server,
|
|
281
|
-
wss,
|
|
282
|
-
state,
|
|
283
|
-
watcher,
|
|
284
|
-
port,
|
|
285
|
-
close,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function safeCanCreate(p) {
|
|
290
|
-
// chokidar accepts both existing and not-yet-existing paths, but it logs
|
|
291
|
-
// noisily on the latter. We treat anything inside an existing parent as
|
|
292
|
-
// watchable.
|
|
293
|
-
try {
|
|
294
|
-
return existsSync(dirname(p));
|
|
295
|
-
} catch {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function renderNotBuiltPage() {
|
|
301
|
-
return `<!DOCTYPE html>
|
|
302
|
-
<html lang="en">
|
|
303
|
-
<head>
|
|
304
|
-
<meta charset="UTF-8" />
|
|
305
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
306
|
-
<title>Bizar Dashboard — not built</title>
|
|
307
|
-
<style>
|
|
308
|
-
:root { color-scheme: dark; }
|
|
309
|
-
body {
|
|
310
|
-
margin: 0;
|
|
311
|
-
min-height: 100vh;
|
|
312
|
-
display: flex;
|
|
313
|
-
align-items: center;
|
|
314
|
-
justify-content: center;
|
|
315
|
-
background: #0b0e14;
|
|
316
|
-
color: #c9d1d9;
|
|
317
|
-
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
318
|
-
padding: 24px;
|
|
319
|
-
}
|
|
320
|
-
.card {
|
|
321
|
-
max-width: 560px;
|
|
322
|
-
background: #12161f;
|
|
323
|
-
border: 1px solid #f87171;
|
|
324
|
-
border-radius: 12px;
|
|
325
|
-
padding: 32px;
|
|
326
|
-
box-shadow: 0 12px 32px rgba(0,0,0,0.5);
|
|
327
|
-
}
|
|
328
|
-
h1 { margin: 0 0 12px; color: #f87171; font-size: 22px; }
|
|
329
|
-
p { margin: 0 0 12px; line-height: 1.6; }
|
|
330
|
-
code {
|
|
331
|
-
font-family: 'JetBrains Mono', monospace;
|
|
332
|
-
background: #1a1f2b;
|
|
333
|
-
border: 1px solid #232a39;
|
|
334
|
-
padding: 2px 6px;
|
|
335
|
-
border-radius: 4px;
|
|
336
|
-
font-size: 13px;
|
|
337
|
-
}
|
|
338
|
-
pre {
|
|
339
|
-
background: #1a1f2b;
|
|
340
|
-
border: 1px solid #232a39;
|
|
341
|
-
padding: 12px 16px;
|
|
342
|
-
border-radius: 8px;
|
|
343
|
-
font-family: 'JetBrains Mono', monospace;
|
|
344
|
-
font-size: 13px;
|
|
345
|
-
overflow-x: auto;
|
|
346
|
-
}
|
|
347
|
-
</style>
|
|
348
|
-
</head>
|
|
349
|
-
<body>
|
|
350
|
-
<div class="card">
|
|
351
|
-
<h1>Dashboard not built</h1>
|
|
352
|
-
<p>The React SPA has not been built yet. The Bizar dashboard server
|
|
353
|
-
is running, but the frontend bundle is missing.</p>
|
|
354
|
-
<p>Build the React + Vite frontend from the repo root:</p>
|
|
355
|
-
<pre><code>npm run build</code></pre>
|
|
356
|
-
<p>Then restart this server (<code>bizar dashboard start</code>).
|
|
357
|
-
For local development with hot reload, run <code>npm run dev</code>
|
|
358
|
-
from a second terminal and open the URL Vite prints.</p>
|
|
359
|
-
<p>The REST API and WebSocket are still live at
|
|
360
|
-
<code>/api/*</code> and <code>/ws</code>.</p>
|
|
361
|
-
</div>
|
|
362
|
-
</body>
|
|
363
|
-
</html>`;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export { DIST_DIR, renderNotBuiltPage };
|