@polderlabs/bizar 3.20.0 → 3.20.8

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/audit.mjs CHANGED
@@ -80,7 +80,6 @@ export async function runAudit() {
80
80
  'opencode/deepseek-v4-flash-free',
81
81
  'minimax/MiniMax-M2.7',
82
82
  'minimax/MiniMax-M3',
83
- 'openai/gpt-5.5',
84
83
  ];
85
84
  if (!validModels.includes(model)) {
86
85
  warnings.push({ path, severity: 'WARN', msg: `Unknown model: ${model}` });
package/cli/bin.mjs CHANGED
@@ -85,10 +85,12 @@ function showHelp() {
85
85
  update Auto-update everything (opencode + bizar + dash + plugin)
86
86
  service Manage the background service daemon
87
87
  bg <subcommand> Manage background agents (list/view/kill/logs)
88
- dash <subcommand> Manage the dashboard (start/stop/status/tui)
88
+ dash <subcommand> Manage the dashboard (start/stop/status/cleanup/tui)
89
+ browser-harness-up Start Chromium for browser-harness (start/stop/status/restart)
89
90
  dev-link [src] Symlink the local plugin source into opencode's plugin dir
90
91
  dev-unlink Remove the dev symlink and restore the deployed copy
91
92
  doctor Check the BizarHarness install for health issues
93
+ mod <subcommand> Manage mods (install/upgrade/list via the dashboard API)
92
94
 
93
95
  Examples:
94
96
  bizar install
@@ -292,17 +294,168 @@ function showServiceHelp() {
292
294
  `);
293
295
  }
294
296
 
297
+ function showModHelp() {
298
+ console.log(`
299
+ bizar mod — Manage mods (via the dashboard's HTTP API)
300
+
301
+ Usage:
302
+ bizar mod upgrade <id> [--backup] Upgrade an installed mod to the latest version
303
+ bizar mod install <id> Install a mod from the registry
304
+ bizar mod list List installed mods
305
+ bizar mod registry Show registry URL + available mods
306
+
307
+ Description:
308
+ Subcommands call the running dashboard's HTTP API. If no dashboard is
309
+ reachable, you'll be told to run \`bizar dash start\` first.
310
+
311
+ \`bizar mod upgrade <id>\` will:
312
+ 1. Snapshot the existing version of the mod
313
+ 2. Optionally back up the folder (--backup)
314
+ 3. Remove the existing copy (and its opencode-config instructions)
315
+ 4. Install the latest version from the registry
316
+ 5. Re-install the new mod's instruction files into opencode config
317
+ 6. Print from-version → to-version
318
+ `);
319
+ }
320
+
321
+ async function runModCommand(modArgs) {
322
+ const sub = modArgs[0];
323
+ const positional = modArgs.slice(1).filter((a) => !a.startsWith('-'));
324
+ const flags = modArgs.slice(1).filter((a) => a.startsWith('-'));
325
+
326
+ if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
327
+ showModHelp();
328
+ return;
329
+ }
330
+
331
+ // Read dashboard port from the port file the dashboard writes on start.
332
+ const { readFileSync: rfs } = await import('node:fs');
333
+ const { join: joinPath } = await import('node:path');
334
+ const portFile = joinPath(getBizarConfigDir(), 'dashboard.port');
335
+ let port = null;
336
+ try {
337
+ port = parseInt(rfs(portFile, 'utf8').trim(), 10);
338
+ if (!Number.isFinite(port) || port <= 0) port = null;
339
+ } catch {
340
+ port = null;
341
+ }
342
+ if (!port) {
343
+ console.error(chalk.red(' ✗ Dashboard is not running (no port file at ' + portFile + ').'));
344
+ console.error(chalk.dim(' Start it first: `bizar dash start --bg`'));
345
+ process.exit(1);
346
+ }
347
+ const baseUrl = `http://127.0.0.1:${port}`;
348
+
349
+ async function postJson(path, body) {
350
+ const res = await fetch(`${baseUrl}${path}`, {
351
+ method: 'POST',
352
+ headers: { 'Content-Type': 'application/json' },
353
+ body: JSON.stringify(body || {}),
354
+ });
355
+ const text = await res.text();
356
+ let json = null;
357
+ try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
358
+ if (!res.ok) {
359
+ const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
360
+ throw new Error(`${path} failed: ${msg}`);
361
+ }
362
+ return json;
363
+ }
364
+ async function getJson(path) {
365
+ const res = await fetch(`${baseUrl}${path}`);
366
+ const text = await res.text();
367
+ let json = null;
368
+ try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
369
+ if (!res.ok) {
370
+ const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
371
+ throw new Error(`${path} failed: ${msg}`);
372
+ }
373
+ return json;
374
+ }
375
+
376
+ if (sub === 'install') {
377
+ const id = positional[0];
378
+ if (!id) {
379
+ console.error(chalk.red(' ✗ Missing mod id. Usage: bizar mod install <id>'));
380
+ process.exit(1);
381
+ }
382
+ try {
383
+ const m = await postJson('/api/mods', { id });
384
+ console.log(chalk.green(` ✓ Installed "${m.id}" v${m.version}`));
385
+ } catch (err) {
386
+ console.error(chalk.red(` ✗ ${err.message}`));
387
+ process.exit(1);
388
+ }
389
+ } else if (sub === 'upgrade') {
390
+ const id = positional[0];
391
+ if (!id) {
392
+ console.error(chalk.red(' ✗ Missing mod id. Usage: bizar mod upgrade <id> [--backup]'));
393
+ process.exit(1);
394
+ }
395
+ const backup = flags.includes('--backup') || flags.includes('-b');
396
+ try {
397
+ const r = await postJson(`/api/mods/${encodeURIComponent(id)}/upgrade`, { backup });
398
+ const note = r.backupPath ? chalk.dim(` (backup: ${r.backupPath})`) : '';
399
+ console.log(chalk.green(` ✓ Upgraded "${id}" v${r.from} → v${r.to}`) + note);
400
+ } catch (err) {
401
+ console.error(chalk.red(` ✗ ${err.message}`));
402
+ process.exit(1);
403
+ }
404
+ } else if (sub === 'list') {
405
+ try {
406
+ const r = await getJson('/api/mods');
407
+ const mods = r.mods || [];
408
+ if (mods.length === 0) {
409
+ console.log(chalk.dim(' (no mods installed)'));
410
+ return;
411
+ }
412
+ for (const m of mods) {
413
+ const state = m.enabled ? chalk.green('enabled ') : chalk.yellow('disabled');
414
+ console.log(` ${m.id.padEnd(20)} v${m.version.padEnd(10)} ${state} ${m.name || ''}`);
415
+ }
416
+ } catch (err) {
417
+ console.error(chalk.red(` ✗ ${err.message}`));
418
+ process.exit(1);
419
+ }
420
+ } else if (sub === 'registry') {
421
+ try {
422
+ const r = await getJson('/api/mods/registry');
423
+ console.log(chalk.dim(` Source: ${r.registry?.source || '(unknown)'}`));
424
+ console.log(chalk.dim(` Updated: ${r.registry?.updatedAt || '(unknown)'}`));
425
+ console.log('');
426
+ const mods = r.mods || [];
427
+ if (mods.length === 0) {
428
+ console.log(chalk.dim(' (no mods in registry)'));
429
+ return;
430
+ }
431
+ for (const m of mods) {
432
+ const installed = m.installed ? chalk.green(`installed v${m.installedVersion || '?'}`) : chalk.dim('not installed');
433
+ const upgrade = m.upgradeAvailable ? chalk.yellow(` ↑ v${m.upgradeAvailable} available`) : '';
434
+ console.log(` ${m.id.padEnd(20)} v${(m.latest || '?').padEnd(10)} ${installed}${upgrade} ${m.name || ''}`);
435
+ }
436
+ } catch (err) {
437
+ console.error(chalk.red(` ✗ ${err.message}`));
438
+ process.exit(1);
439
+ }
440
+ } else {
441
+ console.error(chalk.red(` ✗ Unknown mod subcommand: ${sub}`));
442
+ showModHelp();
443
+ process.exit(1);
444
+ }
445
+ }
446
+
295
447
  function showDashHelp() {
296
448
  console.log(`
297
449
  bizar dash — Manage the Bizar dashboard
298
450
 
299
451
  Usage:
300
- bizar dash <subcommand> [options]
452
+ bizar dash <subcommand> [options] 
301
453
 
302
454
  Subcommands:
303
455
  start [--bg] [--port N] Start the dashboard (default port 4321)
304
456
  stop Stop the running dashboard
305
457
  status Show dashboard port and URL
458
+ cleanup Find + kill zombie/orphan dashboards
306
459
  tui [--no-web] Launch the TUI
307
460
 
308
461
  Options:
@@ -511,10 +664,32 @@ async function main() {
511
664
  // v3.11.1 — Background agent manager (list / view / kill / logs).
512
665
  const { runBg } = await import('./bg.mjs');
513
666
  await runBg(args[1], args.slice(2));
667
+ } else if (args[0] === 'browser-harness-up') {
668
+ // v3.20.7 — Browser-harness daemon: start / stop / status / restart
669
+ // Chromium with remote debugging so the browser-harness Python tool
670
+ // (from https://github.com/browser-use/browser-harness) can connect.
671
+ const { execFileSync } = await import('node:child_process');
672
+ const sub = args[1] || 'start';
673
+ const scriptPath = join(import.meta.dirname || process.cwd(), 'browser-harness-up.sh');
674
+ try {
675
+ const out = execFileSync('bash', [scriptPath, sub], {
676
+ encoding: 'utf8',
677
+ stdio: 'inherit',
678
+ });
679
+ if (out) process.stdout.write(out);
680
+ } catch (err) {
681
+ console.error(chalk.red(` ✗ browser-harness-up ${sub} failed (exit ${err.status ?? 1})`));
682
+ process.exit(err.status || 1);
683
+ }
514
684
  } else if (args[0] === 'providers' && args[1] === 'detect') {
515
685
  // v3.16.0 — Auto-detect provider API keys from env + opencode.json.
516
686
  const { runProvidersDetect } = await import('./providers-detect.mjs');
517
687
  await runProvidersDetect(args.slice(2));
688
+ } else if (args[0] === 'mod') {
689
+ // v3.20.5 — Mod manager. Talks to the running dashboard over HTTP
690
+ // (the dashboard owns the actual mod install/upgrade logic and the
691
+ // opencode-config install/uninstall of instruction files).
692
+ await runModCommand(args.slice(1));
518
693
  } else if (args[0] === 'dash' || args[0] === 'dashboard') {
519
694
  // `bizar dashboard` is a deprecated alias for `bizar dash`
520
695
  if (args[0] === 'dashboard') {
@@ -628,6 +803,11 @@ async function runDash(dashArgs) {
628
803
  case 'status':
629
804
  await dashModule.status(subOpts);
630
805
  break;
806
+ case 'cleanup':
807
+ // v3.20.7 — Find + kill zombie/orphan dashboards.
808
+ // Pass through --force and --kill flags to the dash module.
809
+ await dashModule.cleanup(subOpts);
810
+ break;
631
811
  case 'tui':
632
812
  await dashModule.tui(subOpts);
633
813
  break;
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # cli/browser-harness-up.sh — start Chromium with remote debugging enabled
4
+ # and register the browser-harness skill. Survives parent shell exit
5
+ # via setsid + nohup. Idempotent: re-running is a no-op if already up.
6
+ #
7
+ # v3.20.7 — Required by the browser-harness Bizar agent for any
8
+ # browser-driven E2E verification. The agent runs `browser-harness
9
+ # skill` calls; this script makes sure Chrome is alive on CDP 9222
10
+ # with the DevToolsActivePort file browser-harness auto-detects.
11
+ #
12
+ # Usage:
13
+ # cli/browser-harness-up.sh # start if not running
14
+ # cli/browser-harness-up.sh status # print status
15
+ # cli/browser-harness-up.sh stop # kill chrome
16
+ # cli/browser-harness-up.sh restart # stop + start
17
+ #
18
+ # Environment overrides:
19
+ # BH_CHROME_BIN — path to a chrome-headless-shell / chrome binary
20
+ # (default: chrome-headless-shell from puppeteer cache)
21
+ # BH_PORT — CDP port (default: 9222)
22
+ # BH_PROFILE — chrome user-data-dir (default: ~/.config/chromium)
23
+ #
24
+ # Exit codes:
25
+ # 0 started / already running / status clean
26
+ # 1 failed to start (missing binary, port busy, etc.)
27
+ #
28
+ set -euo pipefail
29
+
30
+ BH_PORT="${BH_PORT:-9222}"
31
+ BH_PROFILE="${BH_PROFILE:-$HOME/.config/chromium}"
32
+ BH_DEVTOOLS_FILE="$BH_PROFILE/DevToolsActivePort"
33
+
34
+ # Find the chrome binary. Prefer chrome-headless-shell from the puppeteer
35
+ # cache (it's smaller, has no crashpad, and starts cleanly under daemon).
36
+ # Fall back to the system chromium / chrome / google-chrome.
37
+ find_chrome_bin() {
38
+ if [ -n "${BH_CHROME_BIN:-}" ] && [ -x "$BH_CHROME_BIN" ]; then
39
+ echo "$BH_CHROME_BIN"
40
+ return 0
41
+ fi
42
+ local puppeteer_bin
43
+ puppeteer_bin="$(find "$HOME/.cache/puppeteer" -name chrome-headless-shell -type f 2>/dev/null | head -1)"
44
+ if [ -n "$puppeteer_bin" ] && [ -x "$puppeteer_bin" ]; then
45
+ echo "$puppeteer_bin"
46
+ return 0
47
+ fi
48
+ for cand in chromium google-chrome chrome; do
49
+ local p
50
+ p="$(command -v "$cand" 2>/dev/null || true)"
51
+ if [ -n "$p" ] && [ -x "$p" ]; then
52
+ echo "$p"
53
+ return 0
54
+ fi
55
+ done
56
+ return 1
57
+ }
58
+
59
+ is_chrome_running() {
60
+ # The DevToolsActivePort file's format varies across Chrome versions:
61
+ # - older: PID\nPORT\nWS_PATH
62
+ # - newer: PORT\nWS_PATH
63
+ # We can't reliably extract a PID, so we check the TCP port directly.
64
+ # That's the only invariant that matters for browser-harness: it
65
+ # connects via CDP on this port.
66
+ if (echo >"/dev/tcp/127.0.0.1/$BH_PORT") 2>/dev/null; then
67
+ if [ -f "$BH_DEVTOOLS_FILE" ]; then
68
+ echo "port:$BH_PORT"
69
+ return 0
70
+ fi
71
+ fi
72
+ return 1
73
+ }
74
+
75
+ # Find the chrome PID by scanning /proc (used by `do_stop`). Linux only.
76
+ find_chrome_pid() {
77
+ local pid
78
+ for pid in $(pgrep -f 'chrome.*--remote-debugging-port=' 2>/dev/null); do
79
+ # Confirm it's our chrome (matches our port)
80
+ local cmdline
81
+ cmdline="$(tr '\0' ' ' </proc/"$pid"/cmdline 2>/dev/null)"
82
+ if echo "$cmdline" | grep -q -- "--remote-debugging-port=$BH_PORT"; then
83
+ echo "$pid"
84
+ return 0
85
+ fi
86
+ done
87
+ return 1
88
+ }
89
+
90
+ do_status() {
91
+ if is_chrome_running >/dev/null; then
92
+ local pid
93
+ pid="$(find_chrome_pid 2>/dev/null || echo '?')"
94
+ echo " ✓ Chromium is running (pid $pid, port $BH_PORT, profile $BH_PROFILE)"
95
+ if [ -f "$BH_DEVTOOLS_FILE" ]; then
96
+ echo " ✓ DevToolsActivePort: $(head -1 "$BH_DEVTOOLS_FILE" 2>/dev/null)"
97
+ fi
98
+ if command -v browser-harness >/dev/null 2>&1; then
99
+ echo " ✓ browser-harness: $(browser-harness --version 2>&1 | head -1)"
100
+ if [ -f "$HOME/.opencode/skills/browser-harness/SKILL.md" ]; then
101
+ echo " ✓ Skill registered at ~/.opencode/skills/browser-harness/SKILL.md"
102
+ else
103
+ echo " ! Skill not yet registered — run: browser-harness skill > ~/.opencode/skills/browser-harness/SKILL.md"
104
+ fi
105
+ else
106
+ echo " ! browser-harness not installed — run: uv tool install --python 3.12 --upgrade --force browser-harness"
107
+ fi
108
+ return 0
109
+ else
110
+ echo " ✗ Chromium is NOT running on port $BH_PORT (profile $BH_PROFILE)"
111
+ return 1
112
+ fi
113
+ }
114
+
115
+ do_start() {
116
+ if is_chrome_running >/dev/null; then
117
+ local pid
118
+ pid="$(find_chrome_pid 2>/dev/null || echo '?')"
119
+ echo " ✓ Chromium already running (pid $pid, port $BH_PORT)"
120
+ return 0
121
+ fi
122
+
123
+ local bin
124
+ bin="$(find_chrome_bin)" || {
125
+ echo " ✗ No chromium / chrome binary found." >&2
126
+ echo " Set BH_CHROME_BIN or install one of: chromium, google-chrome, chrome," >&2
127
+ echo " or place chrome-headless-shell in ~/.cache/puppeteer/." >&2
128
+ return 1
129
+ }
130
+
131
+ mkdir -p "$BH_PROFILE"
132
+ echo " Starting $bin on port $BH_PORT (profile $BH_PROFILE)"
133
+
134
+ # setsid + nohup + redirect stdio so the daemon survives the parent
135
+ # shell exit (the Bizar background-process footgun — see AGENTS.md
136
+ # rule #25). The parent shell can exit cleanly while chrome keeps
137
+ # running on its own session.
138
+ setsid bash -c "nohup '$bin' \
139
+ --headless \
140
+ --disable-gpu \
141
+ --no-sandbox \
142
+ --disable-crashpad \
143
+ --remote-debugging-port=$BH_PORT \
144
+ --remote-debugging-address=127.0.0.1 \
145
+ --user-data-dir=$BH_PROFILE \
146
+ </dev/null >/tmp/chromium-bh.log 2>&1 & disown"
147
+
148
+ # Wait for the port to come up (up to 8s)
149
+ for _ in $(seq 1 40); do
150
+ if (echo >"/dev/tcp/127.0.0.1/$BH_PORT") 2>/dev/null; then
151
+ sleep 0.3 # give chrome time to write DevToolsActivePort
152
+ if is_chrome_running >/dev/null; then
153
+ local pid
154
+ pid="$(find_chrome_pid 2>/dev/null || echo '?')"
155
+ echo " ✓ Chromium started (pid $pid, port $BH_PORT)"
156
+ return 0
157
+ fi
158
+ fi
159
+ sleep 0.2
160
+ done
161
+ echo " ✗ Failed to start Chrome on port $BH_PORT (see /tmp/chromium-bh.log)" >&2
162
+ return 1
163
+ }
164
+
165
+ do_stop() {
166
+ local pid
167
+ if pid="$(find_chrome_pid 2>/dev/null)"; then
168
+ echo " Stopping Chromium (pid $pid)"
169
+ kill "$pid" 2>/dev/null || true
170
+ sleep 1
171
+ if kill -0 "$pid" 2>/dev/null; then
172
+ kill -9 "$pid" 2>/dev/null || true
173
+ fi
174
+ rm -f "$BH_DEVTOOLS_FILE"
175
+ echo " ✓ Stopped"
176
+ else
177
+ echo " (Chromium was not running)"
178
+ fi
179
+ }
180
+
181
+ do_restart() {
182
+ do_stop
183
+ do_start
184
+ }
185
+
186
+ # Entry point
187
+ case "${1:-start}" in
188
+ status) do_status ;;
189
+ start) do_start ;;
190
+ stop) do_stop ;;
191
+ restart) do_restart ;;
192
+ *) echo "Usage: $0 {start|stop|restart|status}" >&2; exit 1 ;;
193
+ esac
package/cli/install.mjs CHANGED
@@ -375,7 +375,7 @@ export async function runInstaller() {
375
375
  console.log(chalk.dim(' You can configure API keys now or later via /connect in opencode.'));
376
376
  const keys = await promptApiKeys();
377
377
 
378
- if (keys.opencodeZen || keys.minimax || keys.openai |) {
378
+ if (keys.opencodeZen || keys.minimax || keys.openai) {
379
379
  console.log(chalk.dim('\n Keys noted. Add them to your opencode.json or run /connect in opencode.\n'));
380
380
  }
381
381
 
package/cli/prompts.mjs CHANGED
@@ -10,7 +10,7 @@ const AGENTS_LIST = [
10
10
  { name: 'Thor ᚦ — MiniMax-M2.7 (Mid impl.)', value: 'thor.md', checked: true },
11
11
  { name: 'Baldr ᛒ — MiniMax-M2.7 (Design)', value: 'baldr.md', checked: true },
12
12
  { name: 'Tyr ᛏ — MiniMax-M3 (Complex impl.)', value: 'tyr.md', checked: true },
13
- { name: 'Vidarr ᛉ — GPT-5.5 (Last resort)', value: 'vidarr.md', checked: false },
13
+ { name: 'Vidarr ᛉ — MiniMax-M3 (Last resort)', value: 'vidarr.md', checked: false },
14
14
  { name: 'Forseti ᚨ — MiniMax-M3 (Plan audit)', value: 'forseti.md', checked: true },
15
15
  ];
16
16
 
@@ -102,7 +102,7 @@ export async function promptApiKeys() {
102
102
  {
103
103
  type: 'password',
104
104
  name: 'openai',
105
- message: 'OpenAI API key (GPT-5.5optional for Vidarr):',
105
+ message: 'OpenAI / opencode-zen API key (optional — for non-default agents):',
106
106
  mask: '*',
107
107
  },
108
108
  {
package/config/AGENTS.md CHANGED
@@ -195,7 +195,7 @@ Odin (`@odin`) is the All-Father and primary/default agent. He analyzes each req
195
195
 
196
196
  ### Vidarr
197
197
 
198
- - **Model**: `openai/gpt-5.5` (via OpenAI ChatGPT subscription)
198
+ - **Model**: `minimax/MiniMax-M3` (via MiniMax (direct), reasoning enabled)
199
199
  - **Use for**: The ultimate fallback — when Tyr stalls, debugging is stuck, or novel insight is needed
200
200
  - **Cost**: Highest — use very sparingly, last resort only
201
201
 
@@ -285,7 +285,7 @@ From the project root, the user (or heimdall via `/init` or any other agent prom
285
285
 
286
286
  ## General Agent Baseline — Always-On Behavior
287
287
 
288
- This section is the single source of truth for every Bizar agent's behavior. It is **adapted from the upstream system prompt and translated to Bizar**. Every Claude-specific reference has been mapped to the Bizar equivalent (BizarHarness, opencode, obsidian, Semble, Skills CLI, agent-browser, the opencode tool set). All agents **MUST** follow these rules at all times.
288
+ This section is the single source of truth for every Bizar agent's behavior. It is **adapted from the upstream system prompt and translated to Bizar**. Every Claude-specific reference has been mapped to the Bizar equivalent (BizarHarness, opencode, obsidian, Semble, Skills CLI, browser-harness, the opencode tool set). All agents **MUST** follow these rules at all times.
289
289
 
290
290
  > **Tool name translation table** (used throughout this baseline):
291
291
  >
@@ -424,7 +424,17 @@ Load the SKILL.md via the `skill` tool before writing code or making changes cov
424
424
 
425
425
  #### Browser interaction
426
426
 
427
- For browser-driven E2E validation, use **agent-browser** (the `agent_browser_*` tools). Common operations: `open`, `snapshot`, `click`, `type`, `fill`, `press`, `screenshot`, `eval`, `wait_for_*`. Do **not** install headless Chrome via raw shell commands when agent-browser is available.
427
+ For browser-driven E2E validation, use **browser-harness** (the Python tool from https://github.com/browser-use/browser-harness). Invoke via `bash` heredoc:
428
+
429
+ ```bash
430
+ browser-harness <<'PY'
431
+ new_tab("https://example.com")
432
+ wait_for_load()
433
+ print(page_info())
434
+ PY
435
+ ```
436
+
437
+ Common operations: `new_tab`, `goto_url`, `wait_for_load`, `page_info`, `click_at_xy`, `fill_input`, `press_key`, `scroll`, `capture_screenshot`, `js`, `cdp("Domain.method", ...)`. Read the SKILL.md at `~/.opencode/skills/browser-harness/SKILL.md` on first use. Do **not** install headless Chrome via raw shell commands when browser-harness is available.
428
438
 
429
439
  ### skills_mandatory_read
430
440
 
@@ -433,7 +443,7 @@ Before writing any code, creating any file, or running any computer tool, **scan
433
443
  Concrete triggers:
434
444
  - Frontend/React work → `frontend-design` or framework-specific skill
435
445
  - Backend/API work → framework-specific skill
436
- - Browser E2E → `agent-browser` SKILL.md
446
+ - Browser E2E → `browser-harness` SKILL.md
437
447
  - Skill creation → `skill-creator` SKILL.md
438
448
  - BizarHarness-specific work → `~/.opencode/skills/bizar/` SKILL.md (always)
439
449
  - Self-improvement logging → `~/.opencode/skills/self-improvement/` SKILL.md (always)
@@ -516,7 +526,7 @@ For Bizar-internal claims (citing files, lines, tool results), use file:line ref
516
526
  ### images_and_visual_content
517
527
 
518
528
  - Bizar does not have an `image_search` tool. Do not assume one exists.
519
- - For local screenshots and image inspection, use `agent-browser` (`agent_browser_screenshot` + `agent_browser_eval`).
529
+ - For local screenshots and image inspection, use `browser-harness` (`capture_screenshot(path="...")` + `js(...)` inside a `<<'PY' ... PY` heredoc).
520
530
  - For image generation, dispatch to `@baldr` (design) or use a user-supplied image-generation MCP server if connected.
521
531
  - Never claim to inspect or edit an image that isn't actually available.
522
532
 
@@ -5,7 +5,7 @@ description: Always-on rules for every Bizar agent. Loaded automatically by open
5
5
 
6
6
  # Agent Baseline — Always-On Rules
7
7
 
8
- Every Bizar agent follows these rules at all times. They are translated from the upstream Claude Fable 5 system prompt, with every Claude-specific tool / function / directory mapped to the BizarHarness equivalent (opencode tools, Semble, Skills CLI, Obsidian vault, agent-browser, dashboard artifact pipeline).
8
+ Every Bizar agent follows these rules at all times. They are translated from the upstream Claude Fable 5 system prompt, with every Claude-specific tool / function / directory mapped to the BizarHarness equivalent (opencode tools, Semble, Skills CLI, Obsidian vault, browser-harness, dashboard artifact pipeline).
9
9
 
10
10
  ---
11
11
 
@@ -384,7 +384,7 @@ The following rules apply to every agent at all times. They are the single sourc
384
384
 
385
385
  ### Knowledge Cutoff and Research-First
386
386
 
387
- - Bizar does not have a single knowledge cutoff shared by all models. Subagents may run on DeepSeek V4 Flash, MiniMax M2.7 / M3, or GPT-5.5, each with their own training window.
387
+ - Bizar does not have a single knowledge cutoff shared by all models. Subagents may run on DeepSeek V4 Flash (opencode-zen, free tier) or MiniMax M2.7 / M3, each with their own training window.
388
388
  - For facts that change quickly (current positions, prices, breaking news) or anything that could have changed recently, **search before answering**: use `websearch` and `webfetch` or delegate to `@mimir` for deep research.
389
389
  - For stable technical knowledge (language semantics, well-established APIs, mathematical truths), answer directly without search.
390
390
  - Default to reading `.obsidian/INDEX.md` at session start to retrieve prior project context before answering anything project-specific.
@@ -402,7 +402,7 @@ Bizar can connect to external tools via MCP servers. Always check what's connect
402
402
 
403
403
  **Domain skills:** see section 3 above.
404
404
 
405
- **Browser interaction:** for browser-driven E2E validation, use **agent-browser** (the `agent_browser_*` tools). Do **not** install headless Chrome via raw shell commands when agent-browser is available.
405
+ **Browser interaction:** for browser-driven E2E validation, use **browser-harness** (the Python tool from https://github.com/browser-use/browser-harness). It exposes raw CDP via a `bash` heredoc: `browser-harness <<'PY' ... PY`. Do **not** install headless Chrome via raw shell commands when browser-harness is available.
406
406
 
407
407
  ### Mandatory Skill Read
408
408
 
@@ -412,7 +412,7 @@ Concrete triggers:
412
412
 
413
413
  - Frontend/React work → `frontend-design` or framework-specific skill
414
414
  - Backend/API work → framework-specific skill
415
- - Browser E2E → `agent-browser` SKILL.md
415
+ - Browser E2E → `browser-harness` SKILL.md
416
416
  - Skill creation → `skill-creator` SKILL.md
417
417
  - BizarHarness-specific work → `~/.opencode/skills/bizar/SKILL.md` (always)
418
418
  - Self-improvement logging → `~/.opencode/skills/self-improvement/SKILL.md` (always)
@@ -494,7 +494,7 @@ For Bizar-internal claims (citing files, lines, tool results), use `file:line` r
494
494
  ### Images and Visual Content
495
495
 
496
496
  - Bizar does not have an `image_search` tool. Do not assume one exists.
497
- - For local screenshots and image inspection, use `agent-browser` (`agent_browser_screenshot` + `agent_browser_eval`).
497
+ - For local screenshots and image inspection, use `browser-harness` (`capture_screenshot(path="...")` + `js(...)` inside a `<<'PY' ... PY` heredoc).
498
498
  - For image generation, dispatch to `@baldr` (design) or use a user-supplied image-generation MCP server if connected.
499
499
  - Never claim to inspect or edit an image that isn't actually available.
500
500
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: browser-harness — Primary agent for browser-driven E2E verification. No-edit permissions. Drives Chromium via CDP for end-to-end testing of web apps.
3
3
  mode: primary
4
- model: minimax/MiniMax-M2.7
4
+ model: openrouter/minimax/minimax-m2.7
5
5
  color: "#84cc16"
6
6
  permission:
7
7
  read: allow
@@ -26,8 +26,19 @@ You are browser-harness — the silent observer. You drive a real browser via CD
26
26
 
27
27
  ## Tools Available
28
28
 
29
- - `agent_browser_*` tools (`open`, `snapshot`, `click`, `type`, `fill`, `press`, `screenshot`, `eval`, `wait_for_*`)
30
- - read, glob, grep
29
+ - **Primary: `browser-harness` (Python via uv)** installed at `~/.local/bin/browser-harness` from https://github.com/browser-use/browser-harness. Use heredoc syntax:
30
+ ```bash
31
+ browser-harness <<'PY'
32
+ new_tab("https://example.com")
33
+ wait_for_load()
34
+ print(page_info())
35
+ PY
36
+ ```
37
+ Pre-imported helpers: `new_tab`, `goto_url`, `page_info`, `click_at_xy`, `type_text`, `fill_input`, `press_key`, `scroll`, `capture_screenshot`, `list_tabs`, `current_tab`, `switch_tab`, `js`, `cdp`, `wait_for_load`, `ensure_real_tab`, `ensure_daemon`. The daemon auto-starts and attaches to Chrome at `~/.config/chromium/` on CDP port 9222.
38
+
39
+ - Setup: if Chrome is not running, `cli/browser-harness-up.sh start` (or `bizar browser-harness-up start`).
40
+ - The browser-harness SKILL.md lives at `~/.opencode/skills/browser-harness/SKILL.md` — read it on first use.
41
+ - Read, glob, grep
31
42
  - bash for `npx bizar dev` to start the dev server, `curl` for health checks
32
43
  - webfetch, websearch
33
44
  - edit/write **denied** — you cannot modify the project
@@ -35,12 +46,13 @@ You are browser-harness — the silent observer. You drive a real browser via CD
35
46
  ## Workflow
36
47
 
37
48
  1. **Start the app if needed.** `npx bizar dev` or the project's dev command. Wait for the port to be ready.
38
- 2. **Open the URL.** `agent_browser_open <url>`.
39
- 3. **Take a snapshot.** `agent_browser_snapshot` to see the DOM.
40
- 4. **Interact.** `agent_browser_click`, `agent_browser_fill`, `agent_browser_press` use the accessibility tree, not pixel coordinates.
41
- 5. **Capture state.** `agent_browser_screenshot` for visual evidence.
42
- 6. **Evaluate.** `agent_browser_eval` to run JS in the page context.
43
- 7. **Report.** What you did, what you saw, what passed, what failed.
49
+ 2. **Open the URL.** `new_tab("https://...")` inside a `browser-harness <<'PY' ... PY` heredoc, or `goto_url(...)` if a tab is already open.
50
+ 3. **Wait for load.** `wait_for_load()` after every navigation.
51
+ 4. **Inspect.** `js("document.querySelector(...)...")` for DOM extraction.
52
+ 5. **Interact.** `click_at_xy(x, y)` after a screenshot — the helpers are pre-imported.
53
+ 6. **Capture state.** `capture_screenshot(path="/tmp/...png")` for visual evidence.
54
+ 7. **Evaluate.** `js(...)` to run arbitrary JS in the page context.
55
+ 8. **Report.** What you did, what you saw, what passed, what failed.
44
56
 
45
57
  ## Output Style
46
58
 
@@ -1,7 +1,7 @@
1
1
  ---
2
- description: Vidarr — The ultimate fallback using GPT-5.5. For the hardest problems when Tyr stalls, debugging is stuck, or novel insight is needed. Use sparingly — highest cost.
2
+ description: Vidarr — The ultimate fallback via MiniMax M3. For the hardest problems when Tyr stalls, debugging is stuck, or novel insight is needed. Use sparingly — highest cost.
3
3
  mode: subagent
4
- model: openai/gpt-5.5
4
+ model: minimax/MiniMax-M3
5
5
  color: "#0ea5e9"
6
6
  permission:
7
7
  read: allow
@@ -62,7 +62,7 @@
62
62
  "permission": {
63
63
  "read": "allow",
64
64
  "list": "allow",
65
- "question": "allow",,
65
+ "question": "allow"
66
66
  }
67
67
  },
68
68
  "frigg": {
@@ -77,7 +77,7 @@
77
77
  "grep": "allow",
78
78
  "bash": "allow",
79
79
  "webfetch": "allow",
80
- "websearch": "allow",,,
80
+ "websearch": "allow",
81
81
  "question": "allow"
82
82
  }
83
83
  },
@@ -203,9 +203,9 @@
203
203
  }
204
204
  },
205
205
  "vidarr": {
206
- "description": "Vidarr — Ultimate fallback via GPT-5.5.",
206
+ "description": "Vidarr — Ultimate fallback via MiniMax M3.",
207
207
  "mode": "subagent",
208
- "model": "openai/gpt-5.5",
208
+ "model": "minimax/MiniMax-M3",
209
209
  "color": "#dc2626",
210
210
  "permission": {
211
211
  "read": "allow",
@@ -237,6 +237,23 @@
237
237
  "websearch": "allow"
238
238
  }
239
239
  },
240
+ "browser-harness": {
241
+ "description": "browser-harness — Primary agent for browser-driven E2E verification. No-edit permissions. Drives Chrome via CDP for end-to-end testing of web apps.",
242
+ "mode": "primary",
243
+ "model": "minimax/MiniMax-M2.7",
244
+ "color": "#84cc16",
245
+ "permission": {
246
+ "read": "allow",
247
+ "bash": "allow",
248
+ "glob": "allow",
249
+ "grep": "allow",
250
+ "list": "allow",
251
+ "webfetch": "allow",
252
+ "websearch": "allow",
253
+ "edit": "deny",
254
+ "write": "deny"
255
+ }
256
+ },
240
257
  "semble-search": {
241
258
  "description": "Code search agent for exploring any codebase.",
242
259
  "mode": "subagent",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "3.20.0",
3
+ "version": "3.20.8",
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.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,7 @@
19
19
  "build:sdk": "npm run build -w @polderlabs/bizar-sdk",
20
20
  "test:sdk": "npm run test -w @polderlabs/bizar-sdk",
21
21
  "test:sdk:watch": "npm run test:watch -w @polderlabs/bizar-sdk",
22
- "test": "npm run typecheck && npm run test:sdk && (cd plugins/bizar && bun test tests/loop.test.ts tests/block.test.ts tests/stall-think.test.ts tests/tools/bg-get-comments.test.ts tests/tools/bg-spawn-delegation.test.ts tests/tools/opencode-runner.test.ts tests/settings.test.ts tests/commands.test.ts tests/commands-impl.test.ts tests/tools/plan-action.test.ts tests/tools/wait-for-feedback.test.ts tests/reasoning-clean.test.ts 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-runner.test.mjs",
22
+ "test": "npm run typecheck && npm run test:sdk && (cd plugins/bizar && bun test tests/loop.test.ts tests/block.test.ts tests/stall-think.test.ts tests/tools/bg-get-comments.test.ts tests/tools/bg-spawn-delegation.test.ts tests/tools/opencode-runner.test.ts tests/settings.test.ts tests/commands.test.ts tests/commands-impl.test.ts tests/tools/plan-action.test.ts tests/tools/wait-for-feedback.test.ts tests/reasoning-clean.test.ts 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-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",
23
23
  "build": "npm run build:sdk"
24
24
  },
25
25
  "keywords": [