@polderlabs/bizar 4.5.2 → 4.7.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 (71) hide show
  1. package/bizar-dash/dist/assets/main-DGGq-iZI.js +361 -0
  2. package/bizar-dash/dist/assets/main-DGGq-iZI.js.map +1 -0
  3. package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-CWqPoGaT.js} +18 -18
  4. package/bizar-dash/dist/assets/mobile-CWqPoGaT.js.map +1 -0
  5. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-i4Uv9eW8.js} +1 -1
  6. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-i4Uv9eW8.js.map} +1 -1
  7. package/bizar-dash/dist/index.html +2 -2
  8. package/bizar-dash/dist/mobile.html +2 -2
  9. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  10. package/bizar-dash/src/server/logger.mjs +71 -0
  11. package/bizar-dash/src/server/memory-lightrag.mjs +45 -6
  12. package/bizar-dash/src/server/metrics.mjs +193 -0
  13. package/bizar-dash/src/server/routes/chat.mjs +6 -3
  14. package/bizar-dash/src/server/routes/lightrag.mjs +3 -2
  15. package/bizar-dash/src/server/routes/memory.mjs +5 -4
  16. package/bizar-dash/src/server/routes/misc.mjs +2 -1
  17. package/bizar-dash/src/server/routes/overview.mjs +2 -1
  18. package/bizar-dash/src/server/server.mjs +62 -0
  19. package/bizar-dash/src/web/components/SearchModal.tsx +3 -2
  20. package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
  21. package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
  22. package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
  23. package/bizar-dash/src/web/lib/i18n.ts +25 -0
  24. package/bizar-dash/src/web/locales/en.json +52 -0
  25. package/bizar-dash/src/web/main.tsx +5 -0
  26. package/bizar-dash/src/web/views/Activity.tsx +24 -17
  27. package/bizar-dash/src/web/views/History.tsx +90 -75
  28. package/bizar-dash/src/web/views/Overview.tsx +11 -8
  29. package/bizar-dash/src/web/views/Settings.tsx +7 -1
  30. package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
  31. package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
  32. package/bizar-dash/tests/components/Button.test.tsx +41 -0
  33. package/bizar-dash/tests/components/Card.test.tsx +42 -0
  34. package/bizar-dash/tests/components/Modal.test.tsx +104 -0
  35. package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
  36. package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
  37. package/bizar-dash/tests/components/Toast.test.tsx +108 -0
  38. package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
  39. package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
  40. package/bizar-dash/tests/lib/i18n.test.ts +46 -0
  41. package/bizar-dash/tests/lib/utils.test.ts +194 -0
  42. package/bizar-dash/tests/logger.test.mjs +207 -0
  43. package/bizar-dash/tests/metrics.test.mjs +183 -0
  44. package/bizar-dash/tests/server-bugfixes.test.mjs +2 -2
  45. package/bizar-dash/tests/setup.ts +7 -0
  46. package/bizar-dash/vitest.config.ts +13 -0
  47. package/cli/artifact-cli.mjs +605 -0
  48. package/cli/artifact-render.mjs +621 -0
  49. package/cli/artifact-server.mjs +847 -0
  50. package/cli/artifact.mjs +38 -2096
  51. package/cli/bg.mjs +5 -13
  52. package/cli/bin.mjs +149 -1361
  53. package/cli/commands/artifact.mjs +20 -0
  54. package/cli/commands/dash.mjs +160 -0
  55. package/cli/commands/headroom.mjs +204 -0
  56. package/cli/commands/install.mjs +169 -0
  57. package/cli/commands/memory.mjs +25 -0
  58. package/cli/commands/minimax.mjs +265 -0
  59. package/cli/commands/mod.mjs +185 -0
  60. package/cli/commands/service.mjs +65 -0
  61. package/cli/commands/usage.mjs +109 -0
  62. package/cli/commands/util.mjs +306 -0
  63. package/cli/doctor.mjs +1 -13
  64. package/cli/provision.mjs +2 -13
  65. package/cli/service-controller.mjs +1 -11
  66. package/cli/service.mjs +1 -11
  67. package/cli/utils.mjs +41 -1
  68. package/package.json +6 -1
  69. package/bizar-dash/dist/assets/main-B4OfGAwz.js +0 -361
  70. package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +0 -1
  71. package/bizar-dash/dist/assets/mobile-lbH6szyX.js.map +0 -1
@@ -0,0 +1,306 @@
1
+ /**
2
+ * cli/commands/util.mjs
3
+ *
4
+ * Miscellaneous utility commands:
5
+ * audit, init, export, test-gate, dev-link, dev-unlink,
6
+ * doctor, repair, heads-up, bg, browser-harness-up, providers detect
7
+ */
8
+ import chalk from 'chalk';
9
+ import { existsSync, readFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { spawn } from 'node:child_process';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ // ── Help texts ──────────────────────────────────────────────────────────────────
15
+
16
+ export function showAuditHelp() {
17
+ console.log(`
18
+ bizar audit — Run security audit on agent configuration
19
+
20
+ Usage:
21
+ bizar audit
22
+ `);
23
+ }
24
+
25
+ export function showInitHelp() {
26
+ console.log(`
27
+ bizar init — Initialize .bizar/ in current project
28
+
29
+ Usage:
30
+ bizar init
31
+
32
+ Description:
33
+ Detects the project stack, creates .bizar/PROJECT.md and
34
+ .bizar/AGENTS_SELF_IMPROVEMENT.md and installs relevant skills.
35
+ The per-project knowledge graph (in .bizar/graph/) is provided
36
+ by the graphify mod — install it from the mod registry for
37
+ that feature.
38
+ `);
39
+ }
40
+
41
+ export function showExportHelp() {
42
+ console.log(`
43
+ bizar export — Export agents/rules to another harness
44
+
45
+ Usage:
46
+ bizar export [claude|cursor|opencode]
47
+
48
+ Description:
49
+ Copies installed Bizar agents and rules into another harness format.
50
+ `);
51
+ }
52
+
53
+ export function showTestGateHelp() {
54
+ console.log(`
55
+ bizar test-gate — Detect & run the project's test suite
56
+ `);
57
+ }
58
+
59
+ export function showDevLinkHelp() {
60
+ console.log(`
61
+ bizar dev-link / dev-unlink — Manage a symlink from the opencode plugin dir
62
+ to a local source checkout, so edits propagate to opencode on next session.
63
+
64
+ Usage:
65
+ bizar dev-link [source-dir] Symlink source-dir (default: ./plugins/bizar)
66
+ to ~/.config/opencode/plugins/bizar
67
+ bizar dev-link --force Replace an existing deployed copy
68
+ bizar dev-unlink Remove the dev symlink + restore from npm
69
+ bizar dev-unlink --force Remove even if not a symlink (destructive)
70
+
71
+ Description:
72
+ By default, opencode loads the Bizar plugin from
73
+ ~/.config/opencode/plugins/bizar, which is a real directory copied
74
+ from the npm package. Edits to plugins/bizar/ in the BizarHarness
75
+ repo don't propagate until you re-run the installer.
76
+
77
+ \`bizar dev-link\` replaces that directory with a symlink pointing
78
+ at your local checkout, so source edits are picked up immediately.
79
+ \`bizar dev-unlink\` reverses the change by removing the symlink
80
+ and re-installing the deployed copy from the npm package.
81
+
82
+ While the dev link is in place, \`bizar update\` will skip the
83
+ plugin-copy step (and print a warning) so it doesn't clobber the
84
+ link. Run \`bizar dev-unlink\` first, or pass --force to overwrite.
85
+
86
+ Examples:
87
+ bizar dev-link
88
+ bizar dev-link /home/me/projects/bizar/plugins/bizar
89
+ bizar dev-link --force
90
+ bizar dev-unlink
91
+ `);
92
+ }
93
+
94
+ export function showDoctorHelp() {
95
+ console.log(`
96
+ bizar doctor — Check the BizarHarness install for health issues
97
+
98
+ Usage:
99
+ bizar doctor
100
+
101
+ Description:
102
+ Runs a battery of health checks against the local install:
103
+ • opencode CLI reachable
104
+ • ~/.config/opencode/opencode.json parses as JSON
105
+ • the Bizar plugin is registered
106
+ • plugin path resolves
107
+ • @polderlabs/bizar-plugin is installed globally
108
+ • core agent files are installed (odin, quick, thor, tyr)
109
+ • headroom / semble / skills on PATH (lenient — at least one)
110
+ • dashboard reachable (skipped if no port file)
111
+ • provider.minimax block + MiniMax model flags are sane
112
+
113
+ Prints ✓/✗ for each check and a final summary. Exits non-zero
114
+ if any check fails. Use \`bizar doctor\` after a manual config
115
+ edit or to diagnose "why is opencode misbehaving?" questions.
116
+
117
+ Related:
118
+ bizar update Update + auto-run doctor on success
119
+ `);
120
+ }
121
+
122
+ export function showRepairHelp() {
123
+ console.log(`
124
+ bizar repair — Fix common install issues
125
+
126
+ Usage:
127
+ bizar repair Diagnose + fix stale bin symlinks and mismatched versions
128
+ bizar repair --dry-run Show what would change without modifying anything
129
+ bizar repair --bin-only Only fix the bin symlink; skip version checks
130
+ `);
131
+ }
132
+
133
+ // ── Test gate ──────────────────────────────────────────────────────────────────
134
+
135
+ export async function runTestGate() {
136
+ console.log(chalk.bold.hex('#a855f7')('\n ᚦ TEST GATE ᚦ\n'));
137
+ const { execSync } = await import('node:child_process');
138
+ const cwd = process.cwd();
139
+ const possible = [
140
+ { cmd: 'npm test', check: 'package.json' },
141
+ { cmd: 'pytest', check: 'pyproject.toml' },
142
+ { cmd: 'cargo test', check: 'Cargo.toml' },
143
+ { cmd: 'go test ./...', check: 'go.mod' },
144
+ ];
145
+ for (const suite of possible) {
146
+ try {
147
+ if (existsSync(join(cwd, suite.check))) {
148
+ console.log(` Running: ${suite.cmd}`);
149
+ execSync(suite.cmd, { stdio: 'inherit', timeout: 120000, cwd });
150
+ console.log('\n ✓ Test gate passed\n');
151
+ return true;
152
+ }
153
+ } catch {
154
+ console.log(`\n ✗ Test gate failed: ${suite.cmd}\n`);
155
+ process.exit(1);
156
+ }
157
+ }
158
+ console.log(' No test suite detected. Install one to use the test gate.\n');
159
+ return false;
160
+ }
161
+
162
+ // ── Main dispatcher ────────────────────────────────────────────────────────────
163
+
164
+ export async function run(name, args, isHelpRequest) {
165
+ switch (name) {
166
+ case 'audit':
167
+ if (isHelpRequest) showAuditHelp();
168
+ else {
169
+ const { runAudit } = await import('../audit.mjs');
170
+ await runAudit();
171
+ }
172
+ break;
173
+
174
+ case 'init':
175
+ if (isHelpRequest) showInitHelp();
176
+ else {
177
+ const { runInit } = await import('../init.mjs');
178
+ await runInit(process.cwd());
179
+ }
180
+ break;
181
+
182
+ case 'export':
183
+ if (isHelpRequest) showExportHelp();
184
+ else {
185
+ const { runExport } = await import('../export.mjs');
186
+ const targetFlag = args.includes('--target') ? args[args.indexOf('--target') + 1] : null;
187
+ await runExport(targetFlag);
188
+ }
189
+ break;
190
+
191
+ case 'test-gate':
192
+ if (isHelpRequest) showTestGateHelp();
193
+ else await runTestGate();
194
+ break;
195
+
196
+ case 'dev-link':
197
+ if (isHelpRequest) showDevLinkHelp();
198
+ else {
199
+ const { createDevLink } = await import('../dev-link.mjs');
200
+ const positional = args.filter((a) => !a.startsWith('-'));
201
+ const flags = args.filter((a) => a.startsWith('-'));
202
+ const sourceDir = positional[0] ?? null;
203
+ const force = flags.includes('--force') || flags.includes('-f');
204
+ const ok = createDevLink(sourceDir, { force });
205
+ if (!ok) process.exit(1);
206
+ }
207
+ break;
208
+
209
+ case 'dev-unlink':
210
+ if (isHelpRequest) showDevLinkHelp();
211
+ else {
212
+ const { removeDevLink } = await import('../dev-link.mjs');
213
+ const force = args.includes('--force') || args.includes('-f');
214
+ const ok = await removeDevLink({ force });
215
+ if (!ok) process.exit(1);
216
+ }
217
+ break;
218
+
219
+ case 'doctor':
220
+ if (isHelpRequest) showDoctorHelp();
221
+ else {
222
+ const { runDoctor } = await import('../doctor.mjs');
223
+ const wantJson = args.includes('--json');
224
+ const result = await runDoctor({ silent: wantJson, json: wantJson });
225
+ if (wantJson) process.stdout.write(JSON.stringify(result) + '\n');
226
+ if (result.failed > 0) process.exit(1);
227
+ }
228
+ break;
229
+
230
+ case 'repair':
231
+ if (isHelpRequest) showRepairHelp();
232
+ else {
233
+ const { runRepair } = await import('../repair.mjs');
234
+ const dryRun = args.includes('--dry-run');
235
+ const binOnly = args.includes('--bin-only');
236
+ const result = await runRepair({ dryRun, binOnly });
237
+ if (!result.ok) {
238
+ for (const n of result.notes) console.log(` ${n}`);
239
+ process.exit(1);
240
+ }
241
+ for (const n of result.notes) console.log(` ${n}`);
242
+ if (result.fixed.length > 0) {
243
+ console.log(chalk.green('\n Repair complete. Re-run your shell or `hash -r` to pick up the new path.'));
244
+ }
245
+ }
246
+ break;
247
+
248
+ case 'heads-up':
249
+ if (isHelpRequest) {
250
+ console.log(' heads-up <subcommand> Manage pre-push / pre-release heads-ups (list/check/archive)');
251
+ } else {
252
+ const { runHeadsUp } = await import('../heads-up.mjs');
253
+ await runHeadsUp(args[0], args.slice(1));
254
+ }
255
+ break;
256
+
257
+ case 'bg':
258
+ if (isHelpRequest) {
259
+ console.log(' bg <subcommand> Manage background agents (list/view/kill/logs)');
260
+ } else {
261
+ const { runBg } = await import('../bg.mjs');
262
+ await runBg(args[0], args.slice(1));
263
+ }
264
+ break;
265
+
266
+ case 'browser-harness-up': {
267
+ const { execFileSync } = await import('node:child_process');
268
+ const sub = args[0] || 'start';
269
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
270
+ const scriptPath = join(__dirname, '..', 'browser-harness-up.sh');
271
+ try {
272
+ const out = execFileSync('bash', [scriptPath, sub], {
273
+ encoding: 'utf8',
274
+ stdio: 'inherit',
275
+ });
276
+ if (out) process.stdout.write(out);
277
+ } catch (err) {
278
+ console.error(chalk.red(` ✗ browser-harness-up ${sub} failed (exit ${err.status ?? 1})`));
279
+ process.exit(err.status || 1);
280
+ }
281
+ break;
282
+ }
283
+
284
+ case 'providers':
285
+ if (args[0] === 'detect') {
286
+ const { runProvidersDetect } = await import('../providers-detect.mjs');
287
+ await runProvidersDetect(args.slice(1));
288
+ }
289
+ break;
290
+
291
+ case 'plan':
292
+ // Alias for artifact
293
+ if (isHelpRequest) {
294
+ const { showHelp } = await import('../artifact-cli.mjs');
295
+ showHelp();
296
+ } else {
297
+ const { runArtifact } = await import('../artifact-cli.mjs');
298
+ await runArtifact(args, {});
299
+ }
300
+ break;
301
+
302
+ default:
303
+ return false;
304
+ }
305
+ return true;
306
+ }
package/cli/doctor.mjs CHANGED
@@ -21,9 +21,8 @@
21
21
  import chalk from 'chalk';
22
22
  import { spawnSync } from 'node:child_process';
23
23
  import { existsSync, readFileSync } from 'node:fs';
24
- import { homedir } from 'node:os';
25
24
  import { join } from 'node:path';
26
- import { opencodeConfigDir, opencodeAgentsDir } from './utils.mjs';
25
+ import { opencodeConfigDir, opencodeAgentsDir, which, bizarConfigDir } from './utils.mjs';
27
26
 
28
27
  // v3.20.11: list every agent the install script is expected to deploy.
29
28
  // Adding a new agent to `config/agents/` without adding it here causes
@@ -50,12 +49,6 @@ const REQUIRED_AGENTS = [
50
49
 
51
50
  // ── helpers ─────────────────────────────────────────────────────────────────
52
51
 
53
- function bizarConfigDir() {
54
- return process.env.XDG_CONFIG_HOME
55
- ? join(process.env.XDG_CONFIG_HOME, 'bizar')
56
- : join(homedir(), '.config', 'bizar');
57
- }
58
-
59
52
  /**
60
53
  * Run a check function and capture its result. The check either
61
54
  * returns a string message (pass) or throws an Error (fail).
@@ -73,11 +66,6 @@ async function runCheck(name, fn) {
73
66
  }
74
67
  }
75
68
 
76
- function which(cmd) {
77
- const probe = spawnSync('which', [cmd], { stdio: 'ignore' });
78
- return probe.status === 0;
79
- }
80
-
81
69
  // ── individual checks ───────────────────────────────────────────────────────
82
70
 
83
71
  async function checkOpencodeReachable() {
package/cli/provision.mjs CHANGED
@@ -42,9 +42,11 @@ import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync, mkdirSync
42
42
  import { homedir } from 'node:os';
43
43
  import { join, dirname } from 'node:path';
44
44
  import { fileURLToPath } from 'node:url';
45
+ import { bizarConfigDir } from './utils.mjs';
45
46
 
46
47
  const __filename = fileURLToPath(import.meta.url);
47
48
  const __dirname = dirname(__filename);
49
+ const HOME = homedir();
48
50
 
49
51
  // `cli/provision.mjs` lives at `<pkg>/cli/provision.mjs`. The repo root
50
52
  // (where `plugins/bizar/`, `bizar-dash/`, `package.json` etc. live) is
@@ -53,19 +55,6 @@ const __dirname = dirname(__filename);
53
55
  export const REPO_ROOT = join(__dirname, '..');
54
56
  export const PKG_MAIN = '@polderlabs/bizar';
55
57
 
56
- const HOME = homedir();
57
-
58
- function bizarConfigDir() {
59
- if (process.platform === 'win32') {
60
- return process.env.APPDATA
61
- ? join(process.env.APPDATA, 'bizar')
62
- : join(HOME, '.config', 'bizar');
63
- }
64
- return process.env.XDG_CONFIG_HOME
65
- ? join(process.env.XDG_CONFIG_HOME, 'bizar')
66
- : join(HOME, '.config', 'bizar');
67
- }
68
-
69
58
  export const BIZAR_HOME = bizarConfigDir();
70
59
  export const OPENCODE_DIR =
71
60
  process.platform === 'win32'
@@ -39,6 +39,7 @@ import {
39
39
  import { homedir, platform } from 'node:os';
40
40
  import { join, dirname, resolve, isAbsolute } from 'node:path';
41
41
  import { fileURLToPath } from 'node:url';
42
+ import { bizarConfigDir } from './utils.mjs';
42
43
 
43
44
  const __filename = fileURLToPath(import.meta.url);
44
45
  const __dirname = dirname(__filename);
@@ -47,17 +48,6 @@ const PLATFORM = platform();
47
48
 
48
49
  // ── Path helpers ──────────────────────────────────────────────────────────────
49
50
 
50
- function bizarConfigDir() {
51
- if (PLATFORM === 'win32') {
52
- return process.env.APPDATA
53
- ? join(process.env.APPDATA, 'bizar')
54
- : join(HOME, '.config', 'bizar');
55
- }
56
- return process.env.XDG_CONFIG_HOME
57
- ? join(process.env.XDG_CONFIG_HOME, 'bizar')
58
- : join(HOME, '.config', 'bizar');
59
- }
60
-
61
51
  function userUnitDir() {
62
52
  if (PLATFORM === 'win32') return null;
63
53
  return process.env.XDG_CONFIG_HOME
package/cli/service.mjs CHANGED
@@ -35,22 +35,12 @@ import { join, dirname, resolve } from 'node:path';
35
35
  import { homedir } from 'node:os';
36
36
  import { fileURLToPath } from 'node:url';
37
37
  import { spawn } from 'node:child_process';
38
+ import { bizarConfigDir } from './utils.mjs';
38
39
 
39
40
  const __filename = fileURLToPath(import.meta.url);
40
41
  const __dirname = dirname(__filename);
41
42
  const HOME = homedir();
42
43
 
43
- function bizarConfigDir() {
44
- if (process.platform === 'win32') {
45
- return process.env.APPDATA
46
- ? join(process.env.APPDATA, 'bizar')
47
- : join(HOME, '.config', 'bizar');
48
- }
49
- return process.env.XDG_CONFIG_HOME
50
- ? join(process.env.XDG_CONFIG_HOME, 'bizar')
51
- : join(HOME, '.config', 'bizar');
52
- }
53
-
54
44
  const BIZAR_HOME = bizarConfigDir();
55
45
  const LOG_FILE = join(BIZAR_HOME, 'service.log');
56
46
  const PID_FILE = join(BIZAR_HOME, 'service.pid');
package/cli/utils.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { access, constants, readFile } from 'node:fs/promises';
2
- import { spawnSync } from 'node:child_process';
2
+ import { spawnSync, execFileSync } from 'node:child_process';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { existsSync, readFileSync } from 'node:fs';
6
7
 
7
8
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
8
9
  const REPO_ROOT = join(__dirname, '..');
@@ -153,3 +154,42 @@ export function buildSummary(components, agents, target, skillPacks = []) {
153
154
  parts,
154
155
  };
155
156
  }
157
+
158
+ // ── Path helpers ─────────────────────────────────────────────────────────────────
159
+
160
+ /**
161
+ * Return the platform-specific Bizar config directory.
162
+ * Windows: %APPDATA%\bizar
163
+ * Unix: $XDG_CONFIG_HOME/bizar (default: ~/.config/bizar)
164
+ */
165
+ export function bizarConfigDir() {
166
+ if (process.platform === 'win32') {
167
+ return process.env.APPDATA
168
+ ? join(process.env.APPDATA, 'bizar')
169
+ : join(homedir(), '.config', 'bizar');
170
+ }
171
+ return process.env.XDG_CONFIG_HOME
172
+ ? join(process.env.XDG_CONFIG_HOME, 'bizar')
173
+ : join(homedir(), '.config', 'bizar');
174
+ }
175
+
176
+ /**
177
+ * Check if a command exists on PATH.
178
+ * Returns true if found, false otherwise.
179
+ */
180
+ export function which(cmd) {
181
+ const probe = spawnSync('which', [cmd], { stdio: 'ignore' });
182
+ return probe.status === 0;
183
+ }
184
+
185
+ /**
186
+ * Check if a command exists on PATH (bg.mjs variant that returns the path or null).
187
+ */
188
+ export function whichPath(cmd) {
189
+ try {
190
+ const out = execFileSync('which', [cmd], { encoding: 'utf8' });
191
+ return out.trim() || null;
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "4.5.2",
3
+ "version": "4.7.0",
4
4
  "description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness. v4 ships as a single npm package bundling the dashboard server, opencode plugin, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "build:sdk": "tsc -p packages/sdk/tsconfig.json",
22
22
  "build:dash": "vite build",
23
23
  "test:sdk": "node_modules/.bin/vitest run --root packages/sdk",
24
+ "test:web": "cd bizar-dash && npx vitest run",
24
25
  "test:sdk:watch": "node_modules/.bin/vitest --root packages/sdk",
25
26
  "test": "npm run typecheck && npm run test:sdk && bun test plugins/bizar/tests/loop.test.ts plugins/bizar/tests/block.test.ts plugins/bizar/tests/stall-think.test.ts plugins/bizar/tests/tools/bg-get-comments.test.ts plugins/bizar/tests/tools/bg-spawn-delegation.test.ts plugins/bizar/tests/tools/opencode-runner.test.ts plugins/bizar/tests/settings.test.ts plugins/bizar/tests/commands.test.ts plugins/bizar/tests/commands-impl.test.ts plugins/bizar/tests/tools/plan-action.test.ts plugins/bizar/tests/tools/wait-for-feedback.test.ts plugins/bizar/tests/tools/read-glyph-feedback.test.ts plugins/bizar/tests/reasoning-clean.test.ts plugins/bizar/tests/key-rotation.test.ts && node bizar-dash/tests/smoke-v2.mjs && node --test bizar-dash/tests/path-safe.test.mjs bizar-dash/tests/tmux-wrap.test.mjs bizar-dash/tests/opencode-sessions-detail.test.mjs bizar-dash/tests/opencode-runner.test.mjs bizar-dash/tests/mod-instructions.node.test.mjs bizar-dash/tests/mod-upgrade.node.test.mjs bizar-dash/tests/graphify-mod-spawn.node.test.mjs bizar-dash/tests/no-agent-browser.node.test.mjs bizar-dash/tests/providers-store-backup-keys.node.test.mjs bizar-dash/tests/dashboard-ports.test.mjs bizar-dash/tests/submit-feedback.test.mjs bizar-dash/tests/yaml.test.mjs bizar-dash/tests/memory-store.test.mjs bizar-dash/tests/memory-schema.test.mjs bizar-dash/tests/memory-secrets.test.mjs bizar-dash/tests/memory-git.test.mjs bizar-dash/tests/memory-sync.test.mjs bizar-dash/tests/obsidian-back-compat.test.mjs bizar-dash/tests/memory-lightrag.test.mjs bizar-dash/tests/memory-config.test.mjs bizar-dash/tests/memory-cli.test.mjs bizar-dash/tests/memory-cli-readlistdelete.test.mjs bizar-dash/tests/memory-cli-setup.test.mjs bizar-dash/tests/memory-conflicts.test.mjs bizar-dash/tests/memory-namespace.test.mjs bizar-dash/tests/memory-path-safety.test.mjs bizar-dash/tests/memory-protocol-drift.test.mjs bizar-dash/tests/memory-roundtrip.test.mjs bizar-dash/tests/cli-bugfixes.test.mjs bizar-dash/tests/server-bugfixes.test.mjs bizar-dash/tests/frontend-bugfixes.test.mjs",
26
27
  "build": "npm run build:sdk && npm run build:dash",
@@ -77,12 +78,16 @@
77
78
  },
78
79
  "devDependencies": {
79
80
  "@opencode-ai/plugin": "^1.17.7",
81
+ "@testing-library/jest-dom": "^6.9.1",
82
+ "@testing-library/react": "^16.3.2",
83
+ "@testing-library/user-event": "^14.6.1",
80
84
  "@types/better-sqlite3": "^7.6.13",
81
85
  "@types/bun": "latest",
82
86
  "@types/node": "^22.0.0",
83
87
  "@types/react": "^18.3.0",
84
88
  "@types/react-dom": "^18.3.0",
85
89
  "@vitejs/plugin-react": "^4.3.0",
90
+ "jsdom": "^25.0.1",
86
91
  "typescript": "^5.6.0",
87
92
  "vite": "^5.0.0",
88
93
  "vitest": "^4.1.9"