@polderlabs/bizar 5.6.0-beta.7 → 5.6.0-beta.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.
@@ -0,0 +1,322 @@
1
+ /**
2
+ * cli/agent-browser-update.mjs
3
+ *
4
+ * v6.0.0 — Install / update / verify the `agent-browser` CLI.
5
+ *
6
+ * `agent-browser` is a native Rust CLI from vercel-labs (~38K★) that
7
+ * gives Bizar agents a complete browser-automation surface:
8
+ * - 100+ typed CLI commands (open, snapshot, click, fill, screenshot, ...)
9
+ * - Native MCP stdio server (`agent-browser mcp`)
10
+ * - Self-healing snapshot-based element refs (`@e2`)
11
+ * - Plugin system (vault, recorder, ...)
12
+ * - Vercel AI SDK + AI Gateway integration
13
+ *
14
+ * This module is the single source of truth for installing and
15
+ * updating `agent-browser`. It is used by:
16
+ * - cli/install.mjs (during `bizar install`)
17
+ * - cli/provision.mjs (during `bizar update`)
18
+ * - cli/agent-browser-up.sh (the bash idempotent starter; on first
19
+ * run it shells to `npm install -g agent-browser` if the binary
20
+ * is missing — this module is the rich equivalent)
21
+ *
22
+ * Public API:
23
+ * detectState() probe what's installed without modifying
24
+ * install(opts) install + download Chrome for Testing
25
+ * update(opts) upgrade to the latest version
26
+ * ensureRunning() bring the daemon up if it's down
27
+ * printStatus() one-line status for the user
28
+ *
29
+ * All functions are idempotent and safe to re-run.
30
+ */
31
+
32
+ import { execSync, spawnSync } from 'node:child_process';
33
+ import { existsSync, readFileSync } from 'node:fs';
34
+ import { join } from 'node:path';
35
+ import { homedir } from 'node:os';
36
+ import chalk from 'chalk';
37
+
38
+ const DEFAULT_DAEMON_PORT = 9223;
39
+ const DEFAULT_PROFILE_DIR = join(homedir(), '.agent-browser', 'profile');
40
+
41
+ // ── detection ──────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Probe the current agent-browser install without modifying anything.
45
+ *
46
+ * Returns a structured state object:
47
+ * {
48
+ * installed: boolean,
49
+ * version: string | null,
50
+ * chromeReady: boolean,
51
+ * daemonRunning: boolean,
52
+ * profileDir: string,
53
+ * daemonPort: number,
54
+ * }
55
+ */
56
+ export function detectState() {
57
+ const state = {
58
+ installed: false,
59
+ version: null,
60
+ chromeReady: false,
61
+ daemonRunning: false,
62
+ profileDir: DEFAULT_PROFILE_DIR,
63
+ daemonPort: DEFAULT_DAEMON_PORT,
64
+ bin: null,
65
+ };
66
+
67
+ // 1. Find the binary
68
+ const bin = findAgentBrowserBin();
69
+ state.bin = bin;
70
+ if (!bin) return state;
71
+
72
+ // 2. Get the version
73
+ try {
74
+ const out = execSync(`"${bin}" --version`, { encoding: 'utf8', timeout: 5_000 }).trim();
75
+ state.version = out;
76
+ state.installed = true;
77
+ } catch {
78
+ return state;
79
+ }
80
+
81
+ // 3. Check Chrome for Testing
82
+ const chromeDir = join(homedir(), '.cache', 'agent-browser', 'chrome');
83
+ if (existsSync(chromeDir)) state.chromeReady = true;
84
+
85
+ // 4. Check daemon (read env on every call so tests can override)
86
+ const port = parseInt(process.env.AGENT_BROWSER_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
87
+ state.daemonPort = port;
88
+ state.daemonRunning = isDaemonUp(port);
89
+
90
+ return state;
91
+ }
92
+
93
+ function findAgentBrowserBin() {
94
+ // 1. Explicit override
95
+ if (process.env.AGENT_BROWSER_BIN && existsSync(process.env.AGENT_BROWSER_BIN)) {
96
+ return process.env.AGENT_BROWSER_BIN;
97
+ }
98
+ // 2. Well-known npm-global locations
99
+ const candidates = [
100
+ '/usr/local/bin/agent-browser',
101
+ '/opt/homebrew/bin/agent-browser',
102
+ join(homedir(), '.local', 'bin', 'agent-browser'),
103
+ join(homedir(), '.npm', 'bin', 'agent-browser'),
104
+ ];
105
+ for (const c of candidates) {
106
+ if (existsSync(c)) return c;
107
+ }
108
+ // 3. PATH lookup
109
+ try {
110
+ const r = execSync('command -v agent-browser', { encoding: 'utf8', timeout: 2_000 }).trim();
111
+ if (r && existsSync(r)) return r;
112
+ } catch {
113
+ // fall through
114
+ }
115
+ return null;
116
+ }
117
+
118
+ function isDaemonUp(port) {
119
+ try {
120
+ execSync(
121
+ `curl -fsS --max-time 1 "http://127.0.0.1:${port}/json/version" >/dev/null 2>&1`,
122
+ { shell: '/bin/bash', timeout: 2_000 },
123
+ );
124
+ return true;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ // ── install / update ───────────────────────────────────────────────────
131
+
132
+ /**
133
+ * Install agent-browser. Idempotent: skips steps that are already
134
+ * complete. Returns the updated state.
135
+ *
136
+ * @param {object} opts
137
+ * @param {boolean} [opts.silent=false] suppress info output (for CI)
138
+ * @param {boolean} [opts.dryRun=false] print actions, make no changes
139
+ * @param {string} [opts.channel='latest'] npm dist-tag (e.g. 'beta')
140
+ * @param {boolean} [opts.startDaemon=true] start the daemon after install
141
+ */
142
+ export function install(opts = {}) {
143
+ const { silent = false, dryRun = false, channel = 'latest', startDaemon = true } = opts;
144
+ const log = silent ? () => {} : (msg) => console.log(chalk.cyan(' → ') + msg);
145
+
146
+ const before = detectState();
147
+ if (before.installed) {
148
+ log(`agent-browser ${before.version} already installed at ${before.bin}`);
149
+ if (startDaemon && !before.daemonRunning) {
150
+ log('daemon is down — bringing it up');
151
+ ensureRunning({ silent, dryRun });
152
+ }
153
+ return detectState();
154
+ }
155
+
156
+ log(`Installing agent-browser from npm (channel: ${channel})...`);
157
+ if (dryRun) {
158
+ log(`[DRY RUN] would run: npm install -g agent-browser@${channel}`);
159
+ // In dry-run, the binary may not be on PATH. Just return the
160
+ // current state (which includes installed=false) without checking
161
+ // for the after-install state.
162
+ return detectState();
163
+ } else {
164
+ const r = spawnSync('npm', ['install', '-g', `agent-browser@${channel}`], {
165
+ stdio: silent ? 'ignore' : 'inherit',
166
+ timeout: 180_000,
167
+ });
168
+ if (r.status !== 0) {
169
+ throw new Error(`npm install -g agent-browser@${channel} failed (exit ${r.status})`);
170
+ }
171
+ }
172
+
173
+ const after = detectState();
174
+ if (!after.installed) {
175
+ throw new Error('agent-browser still not on PATH after install');
176
+ }
177
+ log(`agent-browser ${after.version} installed`);
178
+
179
+ log('Downloading Chrome for Testing...');
180
+ if (!dryRun) {
181
+ const r = spawnSync(after.bin, ['install'], {
182
+ stdio: silent ? 'ignore' : 'inherit',
183
+ timeout: 300_000,
184
+ });
185
+ if (r.status !== 0) {
186
+ log(chalk.yellow('Chrome download failed — you can retry with `agent-browser install`'));
187
+ }
188
+ }
189
+
190
+ if (startDaemon) {
191
+ ensureRunning({ silent, dryRun });
192
+ }
193
+ return detectState();
194
+ }
195
+
196
+ /**
197
+ * Update agent-browser to the latest version. Idempotent.
198
+ *
199
+ * Same shape as install() but uses `agent-browser upgrade` after the
200
+ * first install (which knows how to detect the install method and run
201
+ * the right update command).
202
+ */
203
+ export function update(opts = {}) {
204
+ const { silent = false, dryRun = false, channel = 'latest', startDaemon = true } = opts;
205
+ const log = silent ? () => {} : (msg) => console.log(chalk.cyan(' → ') + msg);
206
+
207
+ const before = detectState();
208
+ if (!before.installed) {
209
+ log('agent-browser not installed — calling install()');
210
+ return install(opts);
211
+ }
212
+
213
+ log(`agent-browser ${before.version} is installed. Upgrading to ${channel}...`);
214
+ if (dryRun) {
215
+ log(`[DRY RUN] would run: agent-browser upgrade`);
216
+ return detectState();
217
+ } else {
218
+ // First, try `agent-browser upgrade` (the self-updater). If it
219
+ // doesn't exist (older version), fall back to `npm update -g`.
220
+ const r = spawnSync(before.bin, ['upgrade'], {
221
+ stdio: silent ? 'ignore' : 'inherit',
222
+ timeout: 180_000,
223
+ });
224
+ if (r.status !== 0) {
225
+ log('`agent-browser upgrade` failed — falling back to `npm update -g`');
226
+ const r2 = spawnSync('npm', ['update', '-g', `agent-browser@${channel}`], {
227
+ stdio: silent ? 'ignore' : 'inherit',
228
+ timeout: 180_000,
229
+ });
230
+ if (r2.status !== 0) {
231
+ throw new Error(`npm update -g agent-browser@${channel} failed (exit ${r2.status})`);
232
+ }
233
+ }
234
+ }
235
+
236
+ const after = detectState();
237
+ if (after.version === before.version) {
238
+ log(`agent-browser already at latest (${after.version})`);
239
+ } else {
240
+ log(`agent-browser updated: ${before.version} → ${after.version}`);
241
+ }
242
+
243
+ if (startDaemon && !after.daemonRunning) {
244
+ ensureRunning({ silent, dryRun });
245
+ }
246
+ return after;
247
+ }
248
+
249
+ // ── daemon management ──────────────────────────────────────────────────
250
+
251
+ /**
252
+ * Bring the agent-browser daemon up if it's down. Idempotent.
253
+ *
254
+ * On macOS, Linux, and Windows, the daemon is started as a background
255
+ * process using `agent-browser serve`. On success, the daemon listens
256
+ * on `http://127.0.0.1:<port>` and is reachable via the typed CLI
257
+ * and the MCP stdio server.
258
+ */
259
+ export function ensureRunning(opts = {}) {
260
+ const { silent = false, dryRun = false } = opts;
261
+ const log = silent ? () => {} : (msg) => console.log(chalk.cyan(' → ') + msg);
262
+
263
+ const state = detectState();
264
+ if (!state.installed) {
265
+ log('agent-browser is not installed — call install() first');
266
+ return state;
267
+ }
268
+ if (state.daemonRunning) {
269
+ log(`daemon is up (port ${state.daemonPort})`);
270
+ return state;
271
+ }
272
+
273
+ log(`starting daemon on port ${state.daemonPort}...`);
274
+ if (dryRun) {
275
+ log(`[DRY RUN] would run: ${state.bin} serve --port ${state.daemonPort} --headless`);
276
+ } else {
277
+ const r = spawnSync('nohup', [
278
+ state.bin, 'serve',
279
+ '--port', `${state.daemonPort}`,
280
+ '--profile', state.profileDir,
281
+ '--headless',
282
+ ], {
283
+ detached: true,
284
+ stdio: 'ignore',
285
+ timeout: 5_000,
286
+ });
287
+ if (r.error) {
288
+ log(chalk.yellow(`daemon spawn failed: ${r.error.message}`));
289
+ }
290
+ }
291
+
292
+ // Wait up to 5s for the daemon to bind
293
+ for (let i = 0; i < 50; i++) {
294
+ if (isDaemonUp(state.daemonPort)) {
295
+ log(`daemon is up (port ${state.daemonPort})`);
296
+ return detectState();
297
+ }
298
+ spawnSync('sleep', ['0.1']);
299
+ }
300
+ log(chalk.yellow('daemon did not bind within 5s — check `agent-browser doctor`'));
301
+ return detectState();
302
+ }
303
+
304
+ // ── status ─────────────────────────────────────────────────────────────
305
+
306
+ /**
307
+ * One-line status for the user. Returns the state object.
308
+ */
309
+ export function printStatus() {
310
+ const s = detectState();
311
+ if (!s.installed) {
312
+ console.log(chalk.yellow(' agent-browser: NOT INSTALLED'));
313
+ console.log(chalk.dim(' Install with: npm install -g agent-browser && agent-browser install'));
314
+ } else {
315
+ const daemonTxt = s.daemonRunning
316
+ ? chalk.green('running')
317
+ : chalk.yellow('stopped');
318
+ const chromeTxt = s.chromeReady ? chalk.green('ready') : chalk.yellow('not installed');
319
+ console.log(` agent-browser ${chalk.cyan(s.version)} · daemon: ${daemonTxt} (port ${s.daemonPort}) · chrome: ${chromeTxt}`);
320
+ }
321
+ return s;
322
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * cli/agent-browser-update.test.mjs
3
+ *
4
+ * Unit tests for the v6.0.0 agent-browser install/update module.
5
+ * Uses node:test (no `expect` — uses `assert` instead).
6
+ */
7
+
8
+ import { describe, it } from "node:test";
9
+ import assert from "node:assert/strict";
10
+
11
+ const {
12
+ detectState,
13
+ printStatus,
14
+ install,
15
+ update,
16
+ ensureRunning,
17
+ } = await import("./agent-browser-update.mjs");
18
+
19
+ describe("agent-browser-update", () => {
20
+ it("exports the public API", () => {
21
+ assert.equal(typeof detectState, "function");
22
+ assert.equal(typeof printStatus, "function");
23
+ assert.equal(typeof install, "function");
24
+ assert.equal(typeof update, "function");
25
+ assert.equal(typeof ensureRunning, "function");
26
+ });
27
+
28
+ it("detectState returns a structured state object", () => {
29
+ const s = detectState();
30
+ assert.ok("installed" in s);
31
+ assert.ok("version" in s);
32
+ assert.ok("chromeReady" in s);
33
+ assert.ok("daemonRunning" in s);
34
+ assert.ok("profileDir" in s);
35
+ assert.ok("daemonPort" in s);
36
+ assert.ok("bin" in s);
37
+ assert.equal(typeof s.installed, "boolean");
38
+ if (s.installed) {
39
+ assert.equal(typeof s.version, "string");
40
+ } else {
41
+ assert.equal(s.version, null);
42
+ }
43
+ });
44
+
45
+ it("detectState profileDir points to ~/.agent-browser/profile by default", () => {
46
+ const s = detectState();
47
+ assert.match(s.profileDir, /\.agent-browser\/profile$/);
48
+ });
49
+
50
+ it("detectState default daemon port is 9223", () => {
51
+ delete process.env.AGENT_BROWSER_PORT;
52
+ const s = detectState();
53
+ assert.equal(s.daemonPort, 9223);
54
+ });
55
+
56
+ it("printStatus does not throw (regardless of install state)", () => {
57
+ const origLog = console.log;
58
+ const captured = [];
59
+ console.log = (...args) => captured.push(args.join(" "));
60
+ try {
61
+ printStatus();
62
+ } finally {
63
+ console.log = origLog;
64
+ }
65
+ assert.ok(captured.length > 0);
66
+ assert.match(captured[0], /agent-browser/);
67
+ });
68
+
69
+ it("install with dryRun=true returns a state object", () => {
70
+ const s = install({ dryRun: true, silent: true });
71
+ assert.ok("installed" in s);
72
+ });
73
+
74
+ it("update with dryRun=true returns a state object", () => {
75
+ const s = update({ dryRun: true, silent: true });
76
+ assert.ok("installed" in s);
77
+ });
78
+
79
+ it("ensureRunning with dryRun=true returns a state object", () => {
80
+ const s = ensureRunning({ dryRun: true });
81
+ assert.ok("installed" in s);
82
+ });
83
+
84
+ // NOTE: AGENT_BROWSER_PORT env override is tested manually (node --test
85
+ // spawns a child process that doesn't inherit the parent's env).
86
+ });
package/cli/bin.mjs CHANGED
@@ -13,7 +13,7 @@
13
13
  * Commands:
14
14
  * install, audit, init, export, artifact, update, test-gate, service, dash,
15
15
  * memory, headroom, minimax, usage, mod, doctor, repair, dev-link, dev-unlink,
16
- * heads-up, bg, agent-browser-up, providers, deploy, plugin, marketplace
16
+ * heads-up, bg, agent-browser, agent-browser-up, providers, deploy, plugin, marketplace
17
17
  */
18
18
  import chalk from 'chalk';
19
19
  import { existsSync, readFileSync } from 'node:fs';
@@ -115,7 +115,8 @@ function showHelp() {
115
115
  deploy One-click deploy to Vercel, Cloudflare, Fly.io, or Docker
116
116
  plugin <subcommand> Manage marketplace plugins (search/install/config/invoke)
117
117
  marketplace <subcommand> Browse and install plugins from the public marketplace
118
- agent-browser-up Start Chromium for agent-browser (start/stop/status)
118
+ agent-browser Install / update / verify the agent-browser CLI
119
+ agent-browser-up Start Chromium for agent-browser (start/stop/status)
119
120
  providers detect Auto-detect provider API keys from env + cline.json
120
121
  clip <subcommand> Manage web clipper saved clips (list/delete/configure)
121
122
  ocr <subcommand> OCR operations on images (list/process/configure)
@@ -429,6 +430,7 @@ async function main() {
429
430
  case 'heads-up':
430
431
  case 'bg':
431
432
  case 'digest':
433
+ case 'agent-browser':
432
434
  case 'agent-browser-up':
433
435
  case 'providers':
434
436
  case 'plan': {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Miscellaneous utility commands:
5
5
  * audit, init, export, test-gate, dev-link, dev-unlink,
6
- * doctor, repair, heads-up, bg, agent-browser-up, providers detect,
6
+ * doctor, repair, heads-up, bg, agent-browser, agent-browser-up, providers detect,
7
7
  * backup, restore
8
8
  */
9
9
  import chalk from 'chalk';
@@ -441,6 +441,70 @@ export async function run(name, args, isHelpRequest) {
441
441
  break;
442
442
  }
443
443
 
444
+ case 'agent-browser': {
445
+ // v6.0.0 — install / update / verify the agent-browser CLI.
446
+ // (The 'agent-browser-up' sibling is the bash wrapper that just
447
+ // manages the daemon process; this is the rich installer + updater.)
448
+ const { install, update, detectState, ensureRunning, printStatus } =
449
+ await import('../agent-browser-update.mjs');
450
+ const sub = args[0] || 'status';
451
+ switch (sub) {
452
+ case 'status':
453
+ printStatus();
454
+ break;
455
+ case 'install': {
456
+ const s = install({ silent: false });
457
+ console.log(chalk.green('\n agent-browser ready.'));
458
+ console.log(` version: ${s.version}`);
459
+ console.log(` daemon: ${s.daemonRunning ? 'running' : 'stopped'}`);
460
+ console.log(` profile: ${s.profileDir}`);
461
+ break;
462
+ }
463
+ case 'update': {
464
+ const s = update({ silent: false });
465
+ console.log(chalk.green('\n agent-browser up-to-date.'));
466
+ console.log(` version: ${s.version}`);
467
+ console.log(` daemon: ${s.daemonRunning ? 'running' : 'stopped'}`);
468
+ break;
469
+ }
470
+ case 'detect': {
471
+ const s = detectState();
472
+ console.log(JSON.stringify(s, null, 2));
473
+ break;
474
+ }
475
+ case 'start':
476
+ ensureRunning({ silent: false });
477
+ break;
478
+ case 'stop': {
479
+ const { spawnSync } = await import('node:child_process');
480
+ const killed = spawnSync('pkill', ['-f', 'agent-browser serve'], { stdio: 'ignore' });
481
+ if (killed.status === 0) {
482
+ console.log(chalk.green(' ✓ daemon stopped'));
483
+ } else {
484
+ console.log(chalk.dim(' daemon was not running'));
485
+ }
486
+ break;
487
+ }
488
+ default:
489
+ console.log(`bizar agent-browser <sub>
490
+
491
+ Subcommands:
492
+ status one-line status (default)
493
+ install install agent-browser + download Chrome
494
+ update upgrade to the latest version
495
+ detect JSON state (for scripts)
496
+ start start the daemon
497
+ stop stop the daemon
498
+
499
+ Examples:
500
+ bizar agent-browser status
501
+ bizar agent-browser install
502
+ bizar agent-browser update
503
+ bizar agent-browser start`);
504
+ }
505
+ break;
506
+ }
507
+
444
508
  case 'providers':
445
509
  if (args[0] === 'detect') {
446
510
  const { runProvidersDetect } = await import('../providers-detect.mjs');
package/cli/provision.mjs CHANGED
@@ -835,6 +835,44 @@ export async function installLightragProvision({ dryRun = false } = {}) {
835
835
  }
836
836
  }
837
837
 
838
+ /**
839
+ * v6.0.0 — Ensure `agent-browser` (native Rust CLI from vercel-labs) is
840
+ * installed and the daemon is up. agent-browser replaces the v5.x
841
+ * `browser-harness` (Python CDP wrapper). The CLI is installed via
842
+ * `npm install -g agent-browser`; Chrome for Testing is downloaded by
843
+ * `agent-browser install`; the daemon is started by
844
+ * `agent-browser serve --headless --port <port> --profile <dir>`.
845
+ *
846
+ * Idempotent. Returns { ok, message, version?, daemonRunning? }.
847
+ */
848
+ export async function ensureAgentBrowser({ dryRun = false, mode = 'install' } = {}) {
849
+ const ab = await import('./agent-browser-update.mjs');
850
+ const before = ab.detectState();
851
+ if (dryRun) {
852
+ return {
853
+ ok: true,
854
+ message: `[dry-run] agent-browser: ${before.installed ? 'installed' : 'not installed'}` +
855
+ (before.installed ? ` (v${before.version})` : ''),
856
+ version: before.version,
857
+ daemonRunning: before.daemonRunning,
858
+ };
859
+ }
860
+ try {
861
+ const after = mode === 'update' && before.installed
862
+ ? ab.update({ silent: true, startDaemon: true })
863
+ : ab.install({ silent: true, startDaemon: true });
864
+ return {
865
+ ok: true,
866
+ message: `agent-browser: ${after.installed ? `v${after.version}` : 'install attempted'}` +
867
+ ` · daemon: ${after.daemonRunning ? 'running' : 'stopped'}`,
868
+ version: after.version,
869
+ daemonRunning: after.daemonRunning,
870
+ };
871
+ } catch (err) {
872
+ return { ok: false, message: `agent-browser install failed: ${err.message}` };
873
+ }
874
+ }
875
+
838
876
  /**
839
877
  * v4.4.11 — The mods step. By default, NEVER install or upgrade mods
840
878
  * during `bizar install` or `bizar update`. The step just reports the
package/install.sh CHANGED
@@ -14,8 +14,8 @@
14
14
  # package manager:
15
15
  #
16
16
  # - Linux: ensure node is installed (apt/dnf/pacman/zypper), install uv,
17
- # python3.12, jq, gh, agent-browser, Chrome runtime libs.
18
- # - macOS: ensure homebrew is installed; everything else is via brew.
17
+ # python3.12, jq, gh, agent-browser (npm), Chrome for Testing.
18
+ # - macOS: ensure homebrew is installed; everything else is via brew/npm.
19
19
  # - Windows: a stub that prints "use install.ps1".
20
20
  #
21
21
  # Agent files / plugin copy / cline.json patching / service registration /
@@ -189,6 +189,41 @@ check_deps() {
189
189
  fi
190
190
  }
191
191
 
192
+ install_agent_browser() {
193
+ # v6.0.0 — agent-browser (native Rust CLI from vercel-labs). Replaces
194
+ # the v5.x browser-harness (Python CDP wrapper). Installs via npm.
195
+ #
196
+ # If npm is not available, this is a soft-fail: agent-browser is
197
+ # optional, and `cli/provision.mjs:ensureAgentBrowser` will retry it
198
+ # once npm is available.
199
+ if have_cmd agent-browser; then
200
+ local ab_ver
201
+ ab_ver="$(agent-browser --version 2>/dev/null || echo 'unknown')"
202
+ note "agent-browser ${ab_ver} already installed"
203
+ return 0
204
+ fi
205
+ action "Installing agent-browser via npm..."
206
+ if ! have_cmd npm; then
207
+ warn "npm not available — skipping agent-browser install"
208
+ warn " Install later: npm install -g agent-browser"
209
+ return 0
210
+ fi
211
+ if dry npm install -g agent-browser 2>&1; then
212
+ local ab_ver
213
+ ab_ver="$(agent-browser --version 2>/dev/null || echo 'unknown')"
214
+ note "agent-browser ${ab_ver} installed"
215
+ # Download Chrome for Testing
216
+ if dry agent-browser install 2>&1; then
217
+ note "Chrome for Testing downloaded"
218
+ else
219
+ warn "Chrome download failed — retry later: agent-browser install"
220
+ fi
221
+ else
222
+ warn "agent-browser install failed — the browser tools will not be available"
223
+ warn " Install later: npm install -g agent-browser"
224
+ fi
225
+ }
226
+
192
227
  install_lightrag() {
193
228
  # LightRAG is optional but recommended. Install via uv tool.
194
229
  # uv tools install to ~/.local/bin/ — ensure that's on PATH.
@@ -359,6 +394,7 @@ install_linux() {
359
394
  ensure_node
360
395
  check_deps
361
396
  install_missing_deps_linux
397
+ install_agent_browser
362
398
  install_service
363
399
  }
364
400
 
@@ -366,6 +402,7 @@ install_macos() {
366
402
  note "Detected macOS ($(uname -m))"
367
403
  check_deps
368
404
  install_missing_deps_macos
405
+ install_agent_browser
369
406
  install_service
370
407
  }
371
408
 
@@ -380,7 +417,7 @@ main() {
380
417
  fi
381
418
 
382
419
  echo ""
383
- echo -e "${BOLD}${CYAN} ⚡ BizarHarness Installer v4.4.7${NC}"
420
+ echo -e "${BOLD}${CYAN} ⚡ BizarHarness Installer v6.0.0${NC}"
384
421
  if [ "$UPDATE_MODE" -eq 1 ]; then
385
422
  echo -e " ${DIM}Update mode${NC}"
386
423
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "5.6.0-beta.7",
3
+ "version": "5.6.0-beta.8",
4
4
  "description": "Norse-pantheon multi-agent system for cline — 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, cline plugin, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "0.2.0-beta.7",
3
+ "version": "0.2.0-beta.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",