@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.
Files changed (44) hide show
  1. package/cli/bin.mjs +158 -130
  2. package/cli/copy.mjs +39 -34
  3. package/cli/plan.test.mjs +2331 -0
  4. package/cli/service.mjs +309 -0
  5. package/package.json +19 -27
  6. package/cli/dashboard/api.mjs +0 -473
  7. package/cli/dashboard/browser.mjs +0 -40
  8. package/cli/dashboard/server.mjs +0 -366
  9. package/cli/dashboard/state.mjs +0 -438
  10. package/cli/dashboard/tasks-store.mjs +0 -203
  11. package/cli/dashboard/watcher.mjs +0 -81
  12. package/cli/dashboard.mjs +0 -97
  13. package/dist/assets/index-BVvY22Gt.css +0 -1
  14. package/dist/assets/index-CO3c8O32.js +0 -285
  15. package/dist/assets/index-CO3c8O32.js.map +0 -1
  16. package/dist/index.html +0 -18
  17. package/src/App.tsx +0 -233
  18. package/src/components/Button.tsx +0 -55
  19. package/src/components/Card.tsx +0 -40
  20. package/src/components/EmptyState.tsx +0 -30
  21. package/src/components/Modal.tsx +0 -137
  22. package/src/components/Spinner.tsx +0 -19
  23. package/src/components/StatusBadge.tsx +0 -25
  24. package/src/components/Tag.tsx +0 -28
  25. package/src/components/Toast.tsx +0 -142
  26. package/src/components/Topbar.tsx +0 -88
  27. package/src/index.html +0 -17
  28. package/src/lib/api.ts +0 -71
  29. package/src/lib/markdown.tsx +0 -59
  30. package/src/lib/types.ts +0 -200
  31. package/src/lib/utils.ts +0 -79
  32. package/src/lib/ws.ts +0 -132
  33. package/src/main.tsx +0 -12
  34. package/src/styles/main.css +0 -2324
  35. package/src/views/Agents.tsx +0 -199
  36. package/src/views/Chat.tsx +0 -255
  37. package/src/views/Config.tsx +0 -250
  38. package/src/views/Overview.tsx +0 -267
  39. package/src/views/Plans.tsx +0 -667
  40. package/src/views/Projects.tsx +0 -155
  41. package/src/views/Settings.tsx +0 -253
  42. package/src/views/Tasks.tsx +0 -567
  43. package/tsconfig.json +0 -23
  44. package/vite.config.ts +0 -24
package/cli/bin.mjs CHANGED
@@ -1,8 +1,25 @@
1
1
  #!/usr/bin/env node
2
-
3
- import chalk from 'chalk';
2
+ /**
3
+ * cli/bin.mjs
4
+ *
5
+ * v3.0.0 — `bizar` runtime CLI.
6
+ *
7
+ * Architecture:
8
+ * - `bizar` is the core runtime + installer + audit/init/export/update/plan
9
+ * + service commands.
10
+ * - The dashboard lives in a separate package, `@polderlabs/bizar-dash`.
11
+ * If it's installed, `bizar dashboard` / `bizar --web*` will defer to it.
12
+ * If not, the user is told to install it.
13
+ *
14
+ * Subcommands (unchanged from v2.7.0):
15
+ * install, audit, init, export, plan, update, test-gate, service
16
+ *
17
+ * Flags (unchanged):
18
+ * --web / --no-web / --web-only / --bg / --detach
19
+ */
4
20
  import { existsSync } from 'node:fs';
5
21
  import { join } from 'node:path';
22
+ import chalk from 'chalk';
6
23
  import { runInstaller, runPostInstall } from './install.mjs';
7
24
  import { runAudit } from './audit.mjs';
8
25
  import { runInit } from './init.mjs';
@@ -17,20 +34,33 @@ function showHelp() {
17
34
  Bizar — Norse Pantheon Agent System for opencode
18
35
 
19
36
  Usage:
20
- bizar Run interactive installer
37
+ bizar Launch the TUI dashboard (default)
38
+ bizar --web Launch TUI + auto-open web dashboard
39
+ bizar --no-web Launch TUI only (no browser)
40
+ bizar --web-only Web dashboard only (no TUI, in browser)
41
+ bizar --bg, --detach Launch web dashboard in background, return to shell
42
+ install Run the interactive installer
43
+ bizar install Same as \`install\`
21
44
  bizar audit Run security audit on agent configuration
22
45
  bizar init Initialize .bizar/ in current project
23
- bizar export [target] Export agents/rules to another harness (claude|cursor|opencode)
24
- bizar plan <subcommand> Manage visual plans (new, open, list, delete, export, templates)
46
+ bizar export [target] Export agents/rules to another harness
47
+ bizar plan <subcommand> Manage visual plans
25
48
  bizar test-gate Detect & run the project's test suite
26
49
  bizar update Update opencode, bizar, and/or bizar-plugin
27
- bizar dashboard [start|stop|status] Launch or control the web dashboard (v2.5.0+)
50
+ bizar service Manage the background service daemon
51
+ bizar dashboard Launch the web dashboard (uses bizar-dash)
28
52
  bizar --help Show this help
29
53
 
30
54
  Install:
31
- npm install -g @polderlabs/bizar Install globally, then run 'bizar'
32
- npm install -g @polderlabs/bizar-plugin Install the Bizar opencode plugin
33
- npx @polderlabs/bizar Run without installing
55
+ npm install -g @polderlabs/bizar Install globally
56
+ npm install -g @polderlabs/bizar-dash Optional companion dashboard
57
+ npm install -g @polderlabs/bizar-plugin Bizar opencode plugin
58
+
59
+ Notes:
60
+ The TUI is the default command — press 1-8 for tabs, q to quit.
61
+ \`bizar --bg\` launches the dashboard detached; \`bizar dashboard stop\`
62
+ terminates it. The web dashboard lives in the \`@polderlabs/bizar-dash\`
63
+ package — if it's not installed, you'll be prompted to install it.
34
64
  `);
35
65
  }
36
66
 
@@ -40,157 +70,131 @@ function showAuditHelp() {
40
70
 
41
71
  Usage:
42
72
  bizar audit
43
-
44
- Description:
45
- Scans opencode agent definitions for security issues:
46
- - Agents with read/edit/bash permission conflicts
47
- - Agents lacking mode or model definitions
48
- - Suspicious tool access patterns
49
73
  `);
50
74
  }
51
75
 
52
76
  function showInitHelp() {
53
77
  console.log(`
54
78
  bizar init — Initialize .bizar/ in current project
55
-
56
- Usage:
57
- bizar init
58
-
59
- Description:
60
- Creates a .bizar/ directory in the current project with:
61
- - PROJECT.md (living project description)
62
- - AGENTS_SELF_IMPROVEMENT.md (lesson log)
63
79
  `);
64
80
  }
65
81
 
66
82
  function showExportHelp() {
67
83
  console.log(`
68
84
  bizar export — Export agents/rules to another harness
69
-
70
- Usage:
71
- bizar export --target <harness>
72
-
73
- Targets:
74
- claude Export to Claude Code format
75
- cursor Export to Cursor format
76
- opencode Export to OpenCode format (default)
77
-
78
- Description:
79
- Converts Bizar agent definitions and rules
80
- to the target harness's native format.
81
85
  `);
82
86
  }
83
87
 
84
88
  function showTestGateHelp() {
85
89
  console.log(`
86
90
  bizar test-gate — Detect & run the project's test suite
91
+ `);
92
+ }
93
+
94
+ function showServiceHelp() {
95
+ console.log(`
96
+ bizar service — Manage the background service daemon
87
97
 
88
98
  Usage:
89
- bizar test-gate
99
+ bizar service start Start the service in background
100
+ bizar service stop Stop the running service
101
+ bizar service status Show whether the service is running
102
+ bizar service logs Tail the service log
90
103
 
91
104
  Description:
92
- Auto-detects the project's test framework (npm test, pytest,
93
- cargo test, go test) and runs it. Exits with non-zero on failure.
105
+ The service watches per-project schedules (cron / interval / once)
106
+ and runs them at the right time. It logs to
107
+ ~/.config/bizar/service.log and writes its PID to
108
+ ~/.config/bizar/service.pid.
94
109
  `);
95
110
  }
96
111
 
97
112
  function showDashboardHelp() {
98
113
  console.log(`
99
- bizar dashboard — Launch or control the Bizar web dashboard
114
+ bizar dashboard — Launch the web dashboard (uses @polderlabs/bizar-dash)
100
115
 
101
116
  Usage:
102
- bizar dashboard Start the dashboard (default action = start)
103
- bizar dashboard start Start the dashboard in the current process
104
- bizar dashboard stop Kill the running dashboard (reads PID file)
105
- bizar dashboard status Print port + URL of any running dashboard
117
+ bizar dashboard Start the dashboard
118
+ bizar dashboard start Same
119
+ bizar dashboard stop Kill the running dashboard
120
+ bizar dashboard status Show port + URL
106
121
 
107
122
  Description:
108
- Starts a local Express + WebSocket server on 127.0.0.1, opens the
109
- user's default browser to the dashboard URL, and broadcasts live
110
- file-change events from config/, agents/, commands-bizar/, .bizar/,
111
- and plans/. The server binds loopback only — never expose it.
123
+ The dashboard lives in a separate package. If it's not installed,
124
+ you'll see an install hint pointing at @polderlabs/bizar-dash.
112
125
  `);
113
126
  }
114
127
 
115
- async function runDashboard(action) {
116
- const sub = action || 'start';
117
- const { launchDashboard, PORT_FILE, PID_FILE } = await import('./dashboard.mjs');
118
- const { existsSync, readFileSync, unlinkSync } = await import('node:fs');
119
-
120
- if (sub === 'status') {
121
- if (existsSync(PORT_FILE)) {
122
- const port = readFileSync(PORT_FILE, 'utf8').trim();
123
- console.log(`Bizar dashboard is running at http://localhost:${port}/`);
124
- if (existsSync(PID_FILE)) {
125
- console.log(`PID: ${readFileSync(PID_FILE, 'utf8').trim()}`);
126
- }
127
- } else {
128
- console.log('No Bizar dashboard is running. Use: bizar dashboard start');
129
- }
130
- return;
131
- }
132
-
133
- if (sub === 'stop') {
134
- if (!existsSync(PID_FILE)) {
135
- console.log('No Bizar dashboard is running.');
136
- return;
137
- }
138
- const pid = parseInt(readFileSync(PID_FILE, 'utf8').trim(), 10);
139
- if (!Number.isFinite(pid)) {
140
- console.log(`Bad PID file: ${PID_FILE}`);
141
- return;
142
- }
143
- try {
144
- process.kill(pid, 'SIGTERM');
145
- console.log(`Stopped Bizar dashboard (pid ${pid}).`);
146
- } catch (err) {
147
- console.log(`Could not stop dashboard (pid ${pid}): ${err.message}`);
148
- }
149
- try { unlinkSync(PORT_FILE); } catch { /* ignore */ }
150
- try { unlinkSync(PID_FILE); } catch { /* ignore */ }
151
- return;
128
+ /**
129
+ * Detect whether @polderlabs/bizar-dash is installed locally.
130
+ * We probe the global npm root, but also look in the local node_modules
131
+ * of the bizar package itself.
132
+ */
133
+ async function findBizarDash() {
134
+ const { execSync } = await import('node:child_process');
135
+ try {
136
+ const root = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
137
+ const dashPath = join(root, '@polderlabs', 'bizar-dash', 'src', 'cli.mjs');
138
+ if (existsSync(dashPath)) return dashPath;
139
+ } catch {
140
+ /* fall through */
152
141
  }
142
+ // Local fallback — node_modules of this package
143
+ const here = new URL('..', import.meta.url);
144
+ const localPath = join(here.pathname, '..', 'node_modules', '@polderlabs', 'bizar-dash', 'src', 'cli.mjs');
145
+ if (existsSync(localPath)) return localPath;
146
+ return null;
147
+ }
153
148
 
154
- if (sub !== 'start') {
155
- showDashboardHelp();
149
+ /**
150
+ * Delegate a subcommand to the bizar-dash CLI, if installed.
151
+ */
152
+ async function delegateToDash(argsForDash) {
153
+ const dashPath = await findBizarDash();
154
+ if (!dashPath) {
155
+ console.log('The Bizar dashboard lives in a separate package.');
156
+ console.log('Install it with:');
157
+ console.log(chalk.cyan(' npm install -g @polderlabs/bizar-dash'));
156
158
  return;
157
159
  }
158
-
159
- // Start keep the process alive so the server stays up
160
- await launchDashboard();
161
- console.log(chalk.dim(' Press Ctrl-C to stop the dashboard.'));
162
- // Wait forever; SIGINT will exit the process.
163
- await new Promise(() => {});
160
+ const { spawn } = await import('node:child_process');
161
+ const child = spawn(process.execPath, [dashPath, ...argsForDash], {
162
+ stdio: 'inherit',
163
+ cwd: process.cwd(),
164
+ env: process.env,
165
+ });
166
+ await new Promise((resolve, reject) => {
167
+ child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`exit ${code}`))));
168
+ child.on('error', reject);
169
+ });
164
170
  }
165
171
 
166
- function showUpdateHelp() {
167
- console.log(`
168
- bizar update Update opencode, bizar, and/or bizar-plugin
169
-
170
- Usage:
171
- bizar update Interactive: ask which components to update
172
- bizar update --all Update everything non-interactively
173
- bizar update opencode Update only opencode
174
- bizar update bizar Update only the bizar CLI package
175
- bizar update plugin Update only the @polderlabs/bizar-plugin package
176
-
177
- Description:
178
- By default, prompts for each component (opencode, @polderlabs/bizar,
179
- @polderlabs/bizar-plugin) before running its update. With --all, runs
180
- every update without prompting. With explicit subcommands, runs only
181
- the named update.
172
+ function parseFlag(name) {
173
+ const idx = args.indexOf(name);
174
+ if (idx === -1) return null;
175
+ return args[idx + 1] || null;
176
+ }
182
177
 
183
- "opencode" here means the underlying opencode CLI on $PATH — the
184
- update uses the opencode installer's own update command.
185
- "@polderlabs/bizar" and "@polderlabs/bizar-plugin" are updated via
186
- 'npm install -g <pkg>@latest'.
187
- `);
178
+ async function readAutoLaunchWeb() {
179
+ try {
180
+ const fs = await import('node:fs');
181
+ const os = await import('node:os');
182
+ const path = await import('node:path');
183
+ const file = path.join(os.homedir(), '.config', 'bizar', 'settings.json');
184
+ if (!fs.existsSync(file)) return true;
185
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
186
+ if (parsed && parsed.dashboard && typeof parsed.dashboard.autoLaunchWeb === 'boolean') {
187
+ return parsed.dashboard.autoLaunchWeb;
188
+ }
189
+ } catch {
190
+ /* fall through */
191
+ }
192
+ return true;
188
193
  }
189
194
 
190
195
  async function runTestGate() {
191
196
  console.log(chalk.bold.hex('#a855f7')('\n ᚦ TEST GATE ᚦ\n'));
192
197
  const { execSync } = await import('node:child_process');
193
-
194
198
  const cwd = process.cwd();
195
199
  const possible = [
196
200
  { cmd: 'npm test', check: 'package.json' },
@@ -198,7 +202,6 @@ async function runTestGate() {
198
202
  { cmd: 'cargo test', check: 'Cargo.toml' },
199
203
  { cmd: 'go test ./...', check: 'go.mod' },
200
204
  ];
201
-
202
205
  for (const suite of possible) {
203
206
  try {
204
207
  if (existsSync(join(cwd, suite.check))) {
@@ -212,18 +215,21 @@ async function runTestGate() {
212
215
  process.exit(1);
213
216
  }
214
217
  }
215
-
216
218
  console.log(' No test suite detected. Install one to use the test gate.\n');
217
219
  return false;
218
220
  }
219
221
 
220
- function parseFlag(name) {
221
- const idx = args.indexOf(name);
222
- if (idx === -1) return null;
223
- return args[idx + 1] || null;
222
+ /**
223
+ * Service commands — start / stop / status / logs.
224
+ *
225
+ * Implementation lives in cli/service.mjs (created below).
226
+ */
227
+ async function runServiceCommand(sub) {
228
+ const { runService } = await import('./service.mjs');
229
+ await runService(sub || 'status', args.slice(2));
224
230
  }
225
231
 
226
- if (args.includes('--postinstall')) {
232
+ if (args[0] === '--postinstall') {
227
233
  await runPostInstall();
228
234
  } else if (args[0] === 'audit') {
229
235
  if (args.includes('--help') || args.includes('-h')) showAuditHelp();
@@ -233,27 +239,49 @@ if (args.includes('--postinstall')) {
233
239
  else await runInit(process.cwd());
234
240
  } else if (args[0] === 'export') {
235
241
  if (args.includes('--help') || args.includes('-h')) showExportHelp();
236
- else {
237
- const target = parseFlag('--target');
238
- await runExport(target);
239
- }
242
+ else await runExport(parseFlag('--target'));
240
243
  } else if (args[0] === 'test-gate') {
241
244
  if (args.includes('--help') || args.includes('-h')) showTestGateHelp();
242
245
  else await runTestGate();
243
246
  } else if (args[0] === 'update') {
244
247
  if (args.includes('--help') || args.includes('-h')) {
245
- showUpdateHelp();
248
+ console.log('Run bizar update [opencode|bizar|plugin]');
246
249
  } else {
247
250
  await runUpdate(args.slice(1));
248
251
  }
249
252
  } else if (args[0] === 'plan') {
250
- const planArgs = args.slice(1);
251
- await runPlan(planArgs, {});
253
+ await runPlan(args.slice(1), {});
254
+ } else if (args[0] === 'install') {
255
+ await runInstaller();
256
+ } else if (args[0] === 'service') {
257
+ if (args.includes('--help') || args.includes('-h')) showServiceHelp();
258
+ else await runServiceCommand(args[1]);
252
259
  } else if (args[0] === 'dashboard') {
253
260
  if (args.includes('--help') || args.includes('-h')) showDashboardHelp();
254
- else await runDashboard(args[1]);
261
+ else await delegateToDash(args.slice(1));
262
+ } else if (args.includes('--bg') || args.includes('--detach')) {
263
+ // Delegate to bizarre-dash
264
+ await delegateToDash(['--bg']);
265
+ } else if (args.includes('--web-only')) {
266
+ await delegateToDash(['--web-only']);
255
267
  } else if (args.includes('--help') || args.includes('-h')) {
256
268
  showHelp();
257
269
  } else {
258
- await runInstaller();
270
+ // Default: launch the TUI dashboard. The TUI lives in bizar-dash.
271
+ // We try to use the bizar-dash package's TUI, but we also support a
272
+ // bundled fallback that imports from this package's local copy.
273
+ const dashPath = await findBizarDash();
274
+ if (dashPath) {
275
+ const skipWeb = args.includes('--no-web');
276
+ const forceWeb = args.includes('--web');
277
+ const settingAuto = await readAutoLaunchWeb();
278
+ const launchWeb = !skipWeb && (forceWeb || settingAuto);
279
+ await delegateToDash(['tui', ...(launchWeb ? [] : ['--no-web'])]);
280
+ } else {
281
+ console.log('The Bizar dashboard is in a separate package:');
282
+ console.log(chalk.cyan(' npm install -g @polderlabs/bizar-dash'));
283
+ console.log('');
284
+ console.log('Or run the installer to set up everything:');
285
+ console.log(chalk.cyan(' npx -y @polderlabs/bizar install'));
286
+ }
259
287
  }
package/cli/copy.mjs CHANGED
@@ -272,48 +272,53 @@ export async function installPluginBizar(projectRoot) {
272
272
  // The Bizar plugin now lives in a separate npm package
273
273
  // (@polderlabs/bizar-plugin). The interactive installer copies it from
274
274
  // there; the source tree no longer carries plugins/bizar/.
275
- // Silent returnno warning needed.
275
+ spinner.info(chalk.dim(' ℹ No local plugins/bizar/ using @polderlabs/bizar-plugin from npm'));
276
276
  return { copied: 0, errors: [] };
277
277
  }
278
278
 
279
- const destDir = join(projectRoot, '.opencode', 'plugins', 'bizar');
280
- await mkdir(destDir, { recursive: true });
281
-
282
- // Exclude patterns per spec §9.2
283
- const isExcluded = (entry) => {
284
- const parts = entry.split('/');
285
- return parts.some(part =>
286
- part === 'node_modules' ||
287
- part === 'dist' ||
288
- part === '.DS_Store' ||
289
- part.endsWith('.log')
290
- );
291
- };
279
+ try {
280
+ const destDir = join(projectRoot, '.opencode', 'plugins', 'bizar');
281
+ await mkdir(destDir, { recursive: true });
282
+
283
+ // Exclude patterns per spec §9.2
284
+ const isExcluded = (entry) => {
285
+ const parts = entry.split('/');
286
+ return parts.some(part =>
287
+ part === 'node_modules' ||
288
+ part === 'dist' ||
289
+ part === '.DS_Store' ||
290
+ part.endsWith('.log')
291
+ );
292
+ };
292
293
 
293
- const files = await readdirRecursive(srcDir);
294
- const errors = [];
295
- let copied = 0;
294
+ const files = await readdirRecursive(srcDir);
295
+ const errors = [];
296
+ let copied = 0;
296
297
 
297
- for (const file of files) {
298
- if (isExcluded(file)) continue;
299
- const src = join(srcDir, file);
300
- const dst = join(destDir, file);
301
- const dstParent = dirname(dst);
302
- try {
303
- await mkdir(dstParent, { recursive: true });
304
- await copyFile(src, dst);
305
- copied++;
306
- } catch (err) {
307
- errors.push(`Failed to copy ${file}: ${err.message}`);
298
+ for (const file of files) {
299
+ if (isExcluded(file)) continue;
300
+ const src = join(srcDir, file);
301
+ const dst = join(destDir, file);
302
+ const dstParent = dirname(dst);
303
+ try {
304
+ await mkdir(dstParent, { recursive: true });
305
+ await copyFile(src, dst);
306
+ copied++;
307
+ } catch (err) {
308
+ errors.push(`Failed to copy ${file}: ${err.message}`);
309
+ }
308
310
  }
309
- }
310
311
 
311
- if (errors.length === 0) {
312
- spinner.succeed(chalk.green(`Installed Bizar plugin (${copied} files)`));
313
- } else {
314
- spinner.warn(chalk.yellow(`Installed Bizar plugin (${copied} files, ${errors.length} errors)`));
312
+ if (errors.length === 0) {
313
+ spinner.succeed(chalk.green(`Installed Bizar plugin (${copied} files)`));
314
+ } else {
315
+ spinner.warn(chalk.yellow(`Installed Bizar plugin (${copied} files, ${errors.length} errors)`));
316
+ }
317
+ return { copied, errors };
318
+ } catch (err) {
319
+ spinner.fail(chalk.red(`Failed to install Bizar plugin: ${err.message}`));
320
+ return { copied: 0, errors: [err.message] };
315
321
  }
316
- return { copied, errors };
317
322
  }
318
323
 
319
324
  export async function installRtk() {