@polderlabs/bizar 2.6.0 → 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/copy.mjs +39 -34
- 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/service.mjs
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli/service.mjs
|
|
4
|
+
*
|
|
5
|
+
* v3.0.0 — Background service daemon for Bizar.
|
|
6
|
+
*
|
|
7
|
+
* Subcommands:
|
|
8
|
+
* start — spawn the service detached, return immediately
|
|
9
|
+
* stop — kill the service
|
|
10
|
+
* status — show running state
|
|
11
|
+
* logs — tail the service log
|
|
12
|
+
*
|
|
13
|
+
* The service:
|
|
14
|
+
* - Watches per-project schedules
|
|
15
|
+
* - Fires due schedules (interval / cron / once)
|
|
16
|
+
* - Records every run in schedules.json
|
|
17
|
+
* - Logs to ~/.config/bizar/service.log
|
|
18
|
+
* - Writes its PID to ~/.config/bizar/service.pid
|
|
19
|
+
*/
|
|
20
|
+
import {
|
|
21
|
+
existsSync,
|
|
22
|
+
readFileSync,
|
|
23
|
+
writeFileSync,
|
|
24
|
+
mkdirSync,
|
|
25
|
+
appendFileSync,
|
|
26
|
+
} from 'node:fs';
|
|
27
|
+
import { join, dirname } from 'node:path';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
import { spawn } from 'node:child_process';
|
|
31
|
+
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = dirname(__filename);
|
|
34
|
+
const HOME = homedir();
|
|
35
|
+
const BIZAR_HOME = join(HOME, '.config', 'bizar');
|
|
36
|
+
const LOG_FILE = join(BIZAR_HOME, 'service.log');
|
|
37
|
+
const PID_FILE = join(BIZAR_HOME, 'service.pid');
|
|
38
|
+
const TICK_MS = 5_000; // 5s
|
|
39
|
+
|
|
40
|
+
function nowIso() {
|
|
41
|
+
return new Date().toISOString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ensureDir() {
|
|
45
|
+
mkdirSync(BIZAR_HOME, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function logLine(line) {
|
|
49
|
+
try {
|
|
50
|
+
ensureDir();
|
|
51
|
+
appendFileSync(LOG_FILE, `[${nowIso()}] ${line}\n`, 'utf8');
|
|
52
|
+
} catch {
|
|
53
|
+
/* ignore */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readPid() {
|
|
58
|
+
if (!existsSync(PID_FILE)) return null;
|
|
59
|
+
try {
|
|
60
|
+
const pid = parseInt(readFileSync(PID_FILE, 'utf8').trim(), 10);
|
|
61
|
+
return Number.isFinite(pid) ? pid : null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isAlive(pid) {
|
|
68
|
+
if (!pid) return false;
|
|
69
|
+
try {
|
|
70
|
+
process.kill(pid, 0);
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function startService() {
|
|
78
|
+
const existing = readPid();
|
|
79
|
+
if (existing && isAlive(existing)) {
|
|
80
|
+
console.log(`Service already running (pid ${existing}).`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Spawn detached child
|
|
84
|
+
const child = spawn(process.execPath, [__filename, '_daemon'], {
|
|
85
|
+
detached: true,
|
|
86
|
+
stdio: 'ignore',
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
env: process.env,
|
|
89
|
+
});
|
|
90
|
+
child.on('error', (err) => {
|
|
91
|
+
console.error(`Failed to start service: ${err.message}`);
|
|
92
|
+
});
|
|
93
|
+
child.unref();
|
|
94
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
95
|
+
const pid = readPid();
|
|
96
|
+
if (pid && isAlive(pid)) {
|
|
97
|
+
console.log(`Bizar service started (pid ${pid}).`);
|
|
98
|
+
console.log(`Log: ${LOG_FILE}`);
|
|
99
|
+
} else {
|
|
100
|
+
console.log('Service start command issued, but PID not yet alive. Check the log.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function stopService() {
|
|
105
|
+
const pid = readPid();
|
|
106
|
+
if (!pid) {
|
|
107
|
+
console.log('No Bizar service is running.');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!isAlive(pid)) {
|
|
111
|
+
console.log(`Stale PID file (pid ${pid} not running). Cleaning up.`);
|
|
112
|
+
try { writeFileSync(PID_FILE, ''); } catch { /* ignore */ }
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
process.kill(pid, 'SIGTERM');
|
|
117
|
+
console.log(`Stopped Bizar service (pid ${pid}).`);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.log(`Could not stop service: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
writeFileSync(PID_FILE, '');
|
|
123
|
+
} catch { /* ignore */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function serviceStatus() {
|
|
127
|
+
const pid = readPid();
|
|
128
|
+
if (!pid) {
|
|
129
|
+
console.log('Bizar service: stopped (no PID file)');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!isAlive(pid)) {
|
|
133
|
+
console.log(`Bizar service: stopped (stale PID file: ${pid})`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log(`Bizar service: running (pid ${pid})`);
|
|
137
|
+
console.log(`Log: ${LOG_FILE}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function tailLogs(follow) {
|
|
141
|
+
if (!existsSync(LOG_FILE)) {
|
|
142
|
+
console.log('(no log file yet)');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Read the last 50 lines synchronously; if --follow, switch to a tail
|
|
146
|
+
// loop.
|
|
147
|
+
const text = readFileSync(LOG_FILE, 'utf8');
|
|
148
|
+
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
149
|
+
const tail = lines.slice(-50).join('\n');
|
|
150
|
+
console.log(tail);
|
|
151
|
+
if (follow) {
|
|
152
|
+
console.log('--- following log (Ctrl-C to exit) ---');
|
|
153
|
+
let pos = text.length;
|
|
154
|
+
setInterval(() => {
|
|
155
|
+
try {
|
|
156
|
+
const cur = readFileSync(LOG_FILE, 'utf8');
|
|
157
|
+
if (cur.length > pos) {
|
|
158
|
+
process.stdout.write(cur.slice(pos));
|
|
159
|
+
pos = cur.length;
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
/* ignore */
|
|
163
|
+
}
|
|
164
|
+
}, 1000);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The actual daemon loop. Imports the runner from the bizarre-dash package
|
|
170
|
+
* via a relative path (so this file works without npm packaging).
|
|
171
|
+
*/
|
|
172
|
+
async function daemonLoop() {
|
|
173
|
+
try {
|
|
174
|
+
writeFileSync(PID_FILE, String(process.pid), 'utf8');
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.error(`Cannot write PID file ${PID_FILE}: ${err.message}`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
logLine(`service started (pid ${process.pid})`);
|
|
180
|
+
|
|
181
|
+
// Resolve the runner module. The daemon lives in the bizar package;
|
|
182
|
+
// the runner lives in the bizarre-dash package. We try a few candidate
|
|
183
|
+
// paths so this works whether bizarre-dash is installed globally,
|
|
184
|
+
// locally, or alongside the source tree (development).
|
|
185
|
+
const candidates = [
|
|
186
|
+
// dev: <repo>/bizar-dash/src/server/schedules-runner.mjs
|
|
187
|
+
join(__dirname, '..', 'bizar-dash', 'src', 'server', 'schedules-runner.mjs'),
|
|
188
|
+
// npm-global fallback
|
|
189
|
+
join(HOME, '.npm-global', 'lib', 'node_modules', '@polderlabs', 'bizar-dash', 'src', 'server', 'schedules-runner.mjs'),
|
|
190
|
+
];
|
|
191
|
+
// Probe $PWD and global node_modules
|
|
192
|
+
try {
|
|
193
|
+
const { execSync } = await import('node:child_process');
|
|
194
|
+
const root = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
195
|
+
candidates.push(join(root, '@polderlabs', 'bizar-dash', 'src', 'server', 'schedules-runner.mjs'));
|
|
196
|
+
} catch {
|
|
197
|
+
/* ignore */
|
|
198
|
+
}
|
|
199
|
+
// local node_modules (cwd)
|
|
200
|
+
candidates.push(join(process.cwd(), 'node_modules', '@polderlabs', 'bizar-dash', 'src', 'server', 'schedules-runner.mjs'));
|
|
201
|
+
// local node_modules of this package
|
|
202
|
+
candidates.push(join(__dirname, '..', 'node_modules', '@polderlabs', 'bizar-dash', 'src', 'server', 'schedules-runner.mjs'));
|
|
203
|
+
// also try the parent (when service lives inside a subdir)
|
|
204
|
+
candidates.push(join(__dirname, '..', '..', 'node_modules', '@polderlabs', 'bizar-dash', 'src', 'server', 'schedules-runner.mjs'));
|
|
205
|
+
|
|
206
|
+
let runner = null;
|
|
207
|
+
for (const c of candidates) {
|
|
208
|
+
if (existsSync(c)) {
|
|
209
|
+
try {
|
|
210
|
+
runner = await import(c);
|
|
211
|
+
logLine(`using runner at ${c}`);
|
|
212
|
+
break;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
logLine(`failed to import runner at ${c}: ${err.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!runner) {
|
|
219
|
+
logLine('FATAL: could not locate @polderlabs/bizar-dash/schedules-runner. Service exiting.');
|
|
220
|
+
try { writeFileSync(PID_FILE, ''); } catch { /* ignore */ }
|
|
221
|
+
process.exit(2);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let stopping = false;
|
|
225
|
+
const shutdown = () => {
|
|
226
|
+
if (stopping) return;
|
|
227
|
+
stopping = true;
|
|
228
|
+
logLine(`service stopping (pid ${process.pid})`);
|
|
229
|
+
try { writeFileSync(PID_FILE, ''); } catch { /* ignore */ }
|
|
230
|
+
process.exit(0);
|
|
231
|
+
};
|
|
232
|
+
process.on('SIGTERM', shutdown);
|
|
233
|
+
process.on('SIGINT', shutdown);
|
|
234
|
+
|
|
235
|
+
const tick = async () => {
|
|
236
|
+
try {
|
|
237
|
+
const fired = await runner.schedulesRunner.tick();
|
|
238
|
+
if (Array.isArray(fired) && fired.length) {
|
|
239
|
+
logLine(`tick fired ${fired.length} schedule(s)`);
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
logLine(`tick error: ${err.message}`);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Run immediately, then on a 5s interval
|
|
247
|
+
await tick();
|
|
248
|
+
setInterval(tick, TICK_MS);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function runService(sub, _rest) {
|
|
252
|
+
if (sub === 'start') {
|
|
253
|
+
await startService();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (sub === 'stop') {
|
|
257
|
+
stopService();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (sub === 'status') {
|
|
261
|
+
serviceStatus();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (sub === 'logs') {
|
|
265
|
+
tailLogs(false);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (sub === 'follow' || sub === 'tail') {
|
|
269
|
+
tailLogs(true);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
console.log(`
|
|
273
|
+
bizar service — Manage the background service daemon
|
|
274
|
+
|
|
275
|
+
Usage:
|
|
276
|
+
bizar service start
|
|
277
|
+
bizar service stop
|
|
278
|
+
bizar service status
|
|
279
|
+
bizar service logs
|
|
280
|
+
`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── direct entry: when invoked as `node cli/service.mjs _daemon` ──────────
|
|
284
|
+
const isMain = import.meta.url === `file://${process.argv[1]}`;
|
|
285
|
+
if (isMain) {
|
|
286
|
+
const sub = process.argv[2];
|
|
287
|
+
if (sub === '_daemon') {
|
|
288
|
+
// Run the daemon loop — never returns
|
|
289
|
+
await daemonLoop();
|
|
290
|
+
} else if (sub === 'stop') {
|
|
291
|
+
stopService();
|
|
292
|
+
} else if (sub === 'status') {
|
|
293
|
+
serviceStatus();
|
|
294
|
+
} else if (sub === 'logs') {
|
|
295
|
+
tailLogs(false);
|
|
296
|
+
} else if (sub === 'start') {
|
|
297
|
+
await startService();
|
|
298
|
+
} else {
|
|
299
|
+
console.log(`
|
|
300
|
+
bizar service — Manage the background service daemon
|
|
301
|
+
|
|
302
|
+
Usage:
|
|
303
|
+
node cli/service.mjs start
|
|
304
|
+
node cli/service.mjs stop
|
|
305
|
+
node cli/service.mjs status
|
|
306
|
+
node cli/service.mjs logs
|
|
307
|
+
`);
|
|
308
|
+
}
|
|
309
|
+
}
|
package/package.json
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polderlabs/bizar",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, per-project Hindsight memory, mods, schedules, and a background service daemon",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"bizar": "cli/bin.mjs"
|
|
7
|
+
"bizar": "cli/bin.mjs",
|
|
8
|
+
"install": "cli/bin.mjs"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"cli/",
|
|
11
12
|
"config/",
|
|
12
|
-
"
|
|
13
|
-
"src/",
|
|
14
|
-
"templates/",
|
|
15
|
-
"vite.config.ts",
|
|
16
|
-
"tsconfig.json",
|
|
17
|
-
"!cli/*.test.mjs"
|
|
13
|
+
"templates/"
|
|
18
14
|
],
|
|
19
15
|
"scripts": {
|
|
20
|
-
"dev": "vite",
|
|
21
|
-
"build": "vite build",
|
|
22
|
-
"preview": "vite preview",
|
|
23
16
|
"typecheck": "tsc --noEmit",
|
|
24
17
|
"postinstall": "node cli/bin.mjs --postinstall"
|
|
25
18
|
},
|
|
@@ -29,22 +22,25 @@
|
|
|
29
22
|
"norse",
|
|
30
23
|
"multi-agent",
|
|
31
24
|
"router",
|
|
32
|
-
"cli"
|
|
25
|
+
"cli",
|
|
26
|
+
"platform",
|
|
27
|
+
"mods",
|
|
28
|
+
"schedules"
|
|
33
29
|
],
|
|
34
30
|
"license": "MIT",
|
|
35
31
|
"dependencies": {
|
|
36
32
|
"boxen": "^8.0.0",
|
|
37
33
|
"chalk": "^5.4.0",
|
|
38
|
-
"chokidar": "^3.6.0",
|
|
39
|
-
"express": "^4.22.2",
|
|
40
34
|
"inquirer": "^12.0.0",
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
35
|
+
"ora": "^8.1.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@polderlabs/bizar-dash": ">=3.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"@polderlabs/bizar-dash": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
48
44
|
},
|
|
49
45
|
"engines": {
|
|
50
46
|
"node": ">=20"
|
|
@@ -62,10 +58,6 @@
|
|
|
62
58
|
},
|
|
63
59
|
"devDependencies": {
|
|
64
60
|
"@types/node": "^22.19.21",
|
|
65
|
-
"
|
|
66
|
-
"@types/react-dom": "^18.3.7",
|
|
67
|
-
"@vitejs/plugin-react": "^4.7.0",
|
|
68
|
-
"typescript": "^5.9.3",
|
|
69
|
-
"vite": "^5.4.21"
|
|
61
|
+
"typescript": "^5.9.3"
|
|
70
62
|
}
|
|
71
63
|
}
|