@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 +0 -1
- package/cli/bin.mjs +182 -2
- package/cli/browser-harness-up.sh +193 -0
- package/cli/install.mjs +1 -1
- package/cli/prompts.mjs +2 -2
- package/config/AGENTS.md +15 -5
- package/config/agents/_shared/AGENT_BASELINE.md +5 -5
- package/config/agents/browser-harness.md +21 -9
- package/config/agents/vidarr.md +2 -2
- package/config/opencode.json.template +21 -4
- package/package.json +2 -2
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 ᛉ —
|
|
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 (
|
|
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**: `
|
|
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,
|
|
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 **
|
|
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 → `
|
|
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 `
|
|
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,
|
|
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,
|
|
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 **
|
|
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 → `
|
|
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 `
|
|
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/
|
|
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
|
-
- `
|
|
30
|
-
|
|
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.** `
|
|
39
|
-
3. **
|
|
40
|
-
4. **
|
|
41
|
-
5. **
|
|
42
|
-
6. **
|
|
43
|
-
7. **
|
|
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
|
|
package/config/agents/vidarr.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Vidarr — The ultimate fallback
|
|
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:
|
|
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
|
|
206
|
+
"description": "Vidarr — Ultimate fallback via MiniMax M3.",
|
|
207
207
|
"mode": "subagent",
|
|
208
|
-
"model": "
|
|
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.
|
|
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": [
|